Repository: OpenMDAO/OpenMDAO

Issue:
The NumPy 2.x test workflow is failing on MacOS after update to SuperLU v7.0.0
### Task Description

The [MacOS Latest, NumPy 2 (No pyOptSparse)](https://github.com/OpenMDAO/OpenMDAO/actions/runs/13106160752/job/36601862705#logs) workflow is failing with errors like this:

```
  File "/Users/runner/miniconda3/envs/test/lib/python3.13/site-packages/openmdao/test_suite/test_examples/beam_optimization/components/multi_states_comp.py", line 47, in solve_nonlinear
    self.lu = splu(self.K)
              ~~~~^^^^^^^^
  File "/Users/runner/miniconda3/envs/test/lib/python3.13/site-packages/scipy/sparse/linalg/_dsolve/linsolve.py", line 428, in splu
    return _superlu.gstrf(N, A.nnz, A.data, indices, indptr,
           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                          csc_construct_func=csc_construct_func,
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                          ilu=False, options=_options)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Factor is exactly singular
```

Comparing the conda environment with a [recent run ](https://github.com/OpenMDAO/OpenMDAO/actions/runs/12981838067) in which all tests passed, I see the following updated package:

<img width="1061" alt="Image" src="https://github.com/user-attachments/assets/eb5c3c30-e844-4655-ba20-8394cf2e912c" />

Hints from issue comments:
@swryan: The updated version of superlu seems to be a red herring.   

I pinned it back to version 5.2.2 and the workflow is still failing with the same errors:
https://github.com/swryan/OpenMDAO/actions/runs/13120343915/job/36604576803

It may instead have to do with an update to the MacOS GitHub image from:
```
  Image: macos-13
  Version: 20250120.597
```
to
```
  Image: macos-13
  Version: 20250129.634
```@robfalck: ```
RuntimeError: 'parallel.sub_0.states_comp' <class MultiStatesComp>: Error calling solve_nonlinear(), Factor is exactly singular
```

Smells like it could be MPI related.@swryan: You're right.. I thought when I first looked at it that that were non-MPI test failures as well, but looking at it again, they are in fact all MPI tests...

Solution patch:
diff --git a/openmdao/core/component.py b/openmdao/core/component.py
index d8432ac1b..8fd6918fa 100644
--- a/openmdao/core/component.py
+++ b/openmdao/core/component.py
@@ -15,7 +15,7 @@ from scipy.sparse import issparse, coo_matrix, csr_matrix
 
 from openmdao.core.system import System, _supported_methods, _DEFAULT_COLORING_META, \
     global_meta_names, collect_errors, _iter_derivs
-from openmdao.core.constants import INT_DTYPE, _DEFAULT_OUT_STREAM
+from openmdao.core.constants import INT_DTYPE, _DEFAULT_OUT_STREAM, _SetupStatus
 from openmdao.jacobians.dictionary_jacobian import DictionaryJacobian, _CheckingJacobian
 from openmdao.utils.units import simplify_unit
 from openmdao.utils.name_maps import abs_key_iter, abs_key2rel_key, rel_name2abs_name, \
@@ -28,7 +28,7 @@ from openmdao.utils.indexer import Indexer, indexer
 import openmdao.utils.coloring as coloring_mod
 from openmdao.utils.om_warnings import issue_warning, MPIWarning, DistributedComponentWarning, \
     DerivativesWarning, warn_deprecation, OMInvalidCheckDerivativesOptionsWarning
-from openmdao.utils.code_utils import is_lambda, LambdaPickleWrapper, get_partials_deps, \
+from openmdao.utils.code_utils import is_lambda, LambdaPickleWrapper, get_function_deps, \
     get_return_names
 from openmdao.approximation_schemes.complex_step import ComplexStep
 from openmdao.approximation_schemes.finite_difference import FiniteDifference
@@ -410,7 +410,7 @@ class Component(System):
                     raise RuntimeError(f"{self.msginfo}: compute_primal must be defined if using "
                                        "a derivs_method option of 'cs' or 'fd'")
                 # declare all partials as 'cs' or 'fd'
-                for of, wrt in get_partials_deps(self.compute_primal,
+                for of, wrt in get_function_deps(self.compute_primal,
                                                  self._var_rel_names['output']):
                     self.declare_partials(of, wrt, method=method)
             else:
@@ -2225,12 +2225,14 @@ class Component(System):
             Where derivs_dict is a dict, where the top key is the component pathname.
             Under the top key, the subkeys are the (of, wrt) keys of the subjacs.
             Within the (of, wrt) entries are the following keys:
-            'rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev', and
-            'rank_inconsistent'.
-            For 'rel error', 'abs error', and 'magnitude' the value is a tuple containing norms
-            for forward - fd, adjoint - fd, forward - adjoint.
+            'rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev', 'vals_at_max_abs',
+            'vals_at_max_rel', and 'rank_inconsistent'.
             For 'J_fd', 'J_fwd', 'J_rev' the value is a numpy array representing the computed
             Jacobian for the three different methods of computation.
+            For 'rel error', 'abs error', 'vals_at_max_abs' and 'vals_at_max_rel' the value is a
+            tuple containing values for forward - fd, reverse - fd, forward - reverse. For
+            'magnitude' the value is a tuple indicating the maximum magnitude of values found in
+            Jfwd, Jrev, and Jfd.
             The boolean 'rank_inconsistent' indicates if the derivative wrt a serial variable is
             inconsistent across MPI ranks.
 
@@ -2242,6 +2244,12 @@ class Component(System):
         if out_stream == _DEFAULT_OUT_STREAM:
             out_stream = sys.stdout
 
+        if self._problem_meta is None:
+            if self._problem_meta['setup_status'] < _SetupStatus.POST_FINAL_SETUP:
+                raise RuntimeError(f"{self.msginfo}: Can't check_partials before final_setup. Also,"
+                                   " make sure to set valid input values before calling "
+                                   "check_partials either manually or by running the model.")
+
         self._check_fds_differ(method, step, form, step_calc, minimum_step)
 
         # Make sure we're in a valid state
@@ -2363,9 +2371,7 @@ class Component(System):
                                         d = mfree_directions[inp]
                                         mhat = derivs
                                         dhat = deriv['J_fwd'][:, idx]
-
-                                        deriv['directional_fwd_rev'] = (mhat.dot(m),
-                                                                        dhat.dot(d))
+                                        deriv['directional_fwd_rev'] = (dhat.dot(d), mhat.dot(m))
                                     else:
                                         meta = abs2meta_in[out_abs] if out_abs in abs2meta_in \
                                             else abs2meta_out[out_abs]
@@ -2416,16 +2422,15 @@ class Component(System):
                             deriv_value = None
                             copy = False
 
-                        # If not compact printing, testing for pairs that are not dependent so
-                        # that we suppress printing them unless the fd is non zero.
+                        # Testing for pairs that are not dependent so that we suppress printing them
+                        # unless the fd is non zero.
                         # Note: subjacs_info is empty for undeclared partials, which is the default
                         # behavior now.
-                        if not compact_print:
-                            try:
-                                if not subjacs[abs_key]['dependent']:
-                                    nondep_derivs.add(rel_key)
-                            except KeyError:
+                        try:
+                            if not subjacs[abs_key]['dependent']:
                                 nondep_derivs.add(rel_key)
+                        except KeyError:
+                            nondep_derivs.add(rel_key)
 
                         if force_dense:
                             if rows is not None:
@@ -2551,7 +2556,7 @@ class Component(System):
 
                         if 'directional_fd_rev' not in deriv:
                             deriv['directional_fd_rev'] = []
-                        deriv['directional_fd_rev'].append((dhat.dot(d), mhat.dot(m)))
+                        deriv['directional_fd_rev'].append((mhat.dot(m), dhat.dot(d)))
 
         # convert to regular dict from defaultdict
         partials_data = {key: dict(d) for key, d in partials_data.items()}
@@ -2565,9 +2570,12 @@ class Component(System):
                           category=DerivativesWarning)
 
         incon_keys = self._get_inconsistent_keys()
+
+        # if compact_print is True, we'll show all derivatives, even non-dependent ones
+        nondeps = set() if compact_print else nondep_derivs
         # force iterator to run so that error info will be added to partials_data
         err_iter = list(_iter_derivs(partials_data, show_only_incorrect, all_fd_options, False,
-                                     nondep_derivs, self.matrix_free, abs_err_tol, rel_err_tol,
+                                     nondeps, self.matrix_free, abs_err_tol, rel_err_tol,
                                      incon_keys))
 
         worst = None
@@ -2581,6 +2589,23 @@ class Component(System):
                 self._deriv_display(err_iter, partials_data, rel_err_tol, abs_err_tol, out_stream,
                                     all_fd_options, False, show_only_incorrect)
 
+        # check for zero subjacs that are declared as dependent
+        zero_keys = set()
+        for key, meta in partials_data.items():
+            if key in nondep_derivs:
+                continue
+            abs_key = rel_key2abs_key(self, key)
+            if abs_key in self._subjacs_info:
+                maxmag = max([mag.max() for mag in meta['magnitude']])
+                if maxmag == 0.0:
+                    zero_keys.add(key)
+
+        if zero_keys:
+            issue_warning(f"\nComponent '{self.pathname}' has zero derivatives for the "
+                          "following variable pairs that were declared as 'dependent': "
+                          f"{sorted(zero_keys)}.\n",
+                          category=DerivativesWarning)
+
         # add pathname to the partials dict to make it compatible with the return value
         # from Problem.check_partials and passable to assert_check_partials.
         return {self.pathname: partials_data}, worst
diff --git a/openmdao/core/problem.py b/openmdao/core/problem.py
index 233765997..b218d6780 100644
--- a/openmdao/core/problem.py
+++ b/openmdao/core/problem.py
@@ -54,6 +54,7 @@ from openmdao.utils.om_warnings import issue_warning, DerivativesWarning, warn_d
     OMInvalidCheckDerivativesOptionsWarning
 import openmdao.utils.coloring as coloring_mod
 from openmdao.utils.file_utils import _get_outputs_dir, text2html, get_work_dir
+from openmdao.utils.testing_utils import _fix_comp_check_data
 
 try:
     from openmdao.vectors.petsc_vector import PETScVector
@@ -1201,24 +1202,17 @@ class Problem(object, metaclass=ProblemMetaclass):
         dict of dicts of dicts
             First key is the component name.
             Second key is the (output, input) tuple of strings.
-            Third key is one of ['rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev',
-            'rank_inconsistent'].
-            For 'rel error', 'abs error', and 'magnitude' the value is a tuple containing norms for
-            forward - fd, adjoint - fd, forward - adjoint.
-            For 'J_fd', 'J_fwd', 'J_rev' the value is a numpy array representing the computed
-            Jacobian for the three different methods of computation.
+            The third key is one of:
+            'rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev', 'vals_at_max_abs',
+            'vals_at_max_rel', and 'rank_inconsistent'.
+            For 'rel error', 'abs error', 'vals_at_max_abs' and 'vals_at_max_rel' the value is a
+            tuple containing values for forward - fd, adjoint - fd, forward - adjoint. For
+            'magnitude' the value is a tuple indicating the maximum magnitude of values found in
+            Jfwd, Jrev, and Jfd.
             The boolean 'rank_inconsistent' indicates if the derivative wrt a serial variable is
             inconsistent across MPI ranks.
         """
         model = self.model
-
-        # on master, we were doing this:
-        # if self._metadata['setup_status'] < _SetupStatus.POST_FINAL_SETUP:
-        #     self.final_setup()
-
-        # model.run_apply_nonlinear()
-
-        # but it seems more correct to run the model first if it hasn't been run yet, correct?
         if self._metadata['setup_status'] < _SetupStatus.POST_FINAL_SETUP or model.iter_count == 0:
             self.run_model()
 
@@ -1357,14 +1351,16 @@ class Problem(object, metaclass=ProblemMetaclass):
         Returns
         -------
         Dict of Dicts of Tuples of Floats
-
             First key:
                 is the (output, input) tuple of strings;
             Second key:
-                is one of ['rel error', 'abs error', 'magnitude', 'fdstep'];
+                'rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev', 'vals_at_max_abs',
+                'vals_at_max_rel', and 'rank_inconsistent'.
 
-            For 'rel error', 'abs error', 'magnitude' the value is: A tuple containing norms for
-                forward - fd, adjoint - fd, forward - adjoint.
+            For 'rel error', 'abs error', 'vals_at_max_abs' and 'vals_at_max_rel' the value is a
+            tuple containing values for forward - fd, reverse - fd, forward - reverse. For
+            'magnitude' the value is a tuple indicating the maximum magnitude of values found in
+            Jfwd, Jrev, and Jfd.
         """
         if out_stream == _DEFAULT_OUT_STREAM:
             out_stream = sys.stdout
@@ -1547,7 +1543,7 @@ class Problem(object, metaclass=ProblemMetaclass):
                         # check directional fwd against fd (one must have negative seed)
                         meta['J_fwd'] = total_info.J[:, Jcalc_slices['wrt'][wrt].start]
                         meta['J_fd'].append(Jfd[:, Jcalc_slices['wrt'][wrt].start])
-                        meta['directional_fd_fwd'].append((meta['J_fwd'], meta['J_fd'][-1]))
+                        meta['directional_fd_fwd'].append((meta['J_fd'][-1], meta['J_fwd']))
                     else:  # rev
                         if 'directional_fd_rev' not in meta:
                             meta['directional_fd_rev'] = []
@@ -1562,7 +1558,7 @@ class Problem(object, metaclass=ProblemMetaclass):
                         mhat_dot_m = mhat.dot(m)
 
                         # Dot product test for adjoint validity.
-                        meta['directional_fd_rev'].append((dhat_dot_d, mhat_dot_m))
+                        meta['directional_fd_rev'].append((mhat_dot_m, dhat_dot_d))
                         meta['J_rev'] = dhat_dot_d
                         meta['J_fd'].append(mhat_dot_m)
                 else:
@@ -2539,13 +2535,6 @@ def _fix_check_data(data):
     data : dict
         Dictionary containing derivative information keyed by system name.
     """
-    names = ['J_fd', 'abs error', 'rel error', 'magnitude', 'directional_fd_fwd',
-             'directional_fd_rev']
-
     for sdata in data.values():
         for dct in sdata.values():
-            for name in names:
-                if name in dct:
-                    dct[name] = dct[name][0]
-            if 'steps' in dct:
-                del dct['steps']
+            _fix_comp_check_data(dct)
diff --git a/openmdao/core/system.py b/openmdao/core/system.py
index 4f827fdb4..892d038c1 100644
--- a/openmdao/core/system.py
+++ b/openmdao/core/system.py
@@ -8,7 +8,7 @@ import functools
 import textwrap
 
 from contextlib import contextmanager
-from collections import defaultdict, namedtuple
+from collections import defaultdict
 from itertools import chain
 from enum import IntEnum
 from io import StringIO
@@ -28,7 +28,7 @@ from openmdao.utils.options_dictionary import OptionsDictionary
 from openmdao.utils.record_util import create_local_meta, check_path, has_match
 from openmdao.utils.units import is_compatible, unit_conversion, simplify_unit
 from openmdao.utils.variable_table import write_var_table, NA
-from openmdao.utils.array_utils import evenly_distrib_idxs, shape_to_len, safe_norm
+from openmdao.utils.array_utils import evenly_distrib_idxs, shape_to_len, get_errors
 from openmdao.utils.name_maps import name2abs_name, name2abs_names
 from openmdao.utils.coloring import _compute_coloring, Coloring, \
     _STD_COLORING_FNAME, _DEF_COMP_SPARSITY_ARGS, _ColSparsityJac
@@ -6899,8 +6899,9 @@ class System(object, metaclass=SystemMetaclass):
             title = f"{sys_type}: {sys_class_name} '{sys_name}'"
 
         print(f"{add_border(title, '-')}\n", file=sys_buffer)
+        parts = []
 
-        for key, fd_norm, fd_opts, directional, above_abs, above_rel, inconsistent in err_iter:
+        for key, fd_opts, directional, above_abs, above_rel, inconsistent in err_iter:
 
             if above_abs or above_rel or inconsistent:
                 num_bad_jacs += 1
@@ -6924,10 +6925,12 @@ class System(object, metaclass=SystemMetaclass):
 
             abs_errs = derivative_info['abs error']
             rel_errs = derivative_info['rel error']
-            magnitudes = derivative_info['magnitude']
+            abs_vals = derivative_info['vals_at_max_abs']
+            rel_vals = derivative_info['vals_at_max_rel']
+            denom_idxs = derivative_info['denom_idx']
             steps = derivative_info['steps']
 
-            Jfor = derivative_info.get('J_fwd')
+            Jfwd = derivative_info.get('J_fwd')
             Jrev = derivative_info.get('J_rev')
 
             if len(steps) > 1:
@@ -6935,148 +6938,175 @@ class System(object, metaclass=SystemMetaclass):
             else:
                 stepstrs = [""]
 
-            if fd_norm == 0.:
-                if magnitudes[0].forward is None:
-                    divname = 'Jrev'
-                else:
-                    divname = 'Jfor'
-            else:
-                divname = 'Jfd'
-
-            # Magnitudes
-            sys_buffer.write(f"  {sys_name}: {of} wrt {wrt}")
-            if not isinstance(of, tuple) and lcons and of.strip("'") in lcons:
-                sys_buffer.write(" (Linear constraint)")
-
-            sys_buffer.write('\n')
-            if magnitudes[0].forward is not None:
-                sys_buffer.write(f'     Forward Magnitude: {magnitudes[0].forward:.6e}\n')
-
-            if magnitudes[0].reverse is not None:
-                sys_buffer.write(f'     Reverse Magnitude: {magnitudes[0].reverse:.6e}\n')
-
             fd_desc = f"{fd_opts['method']}:{fd_opts['form']}"
-            for i in range(len(magnitudes)):
-                sys_buffer.write(f'          Fd Magnitude: '
-                                 f'{magnitudes[i].fd:.6e} ({fd_desc}{stepstrs[i]})\n')
-            sys_buffer.write('\n')
+            parts.append(f"  {sys_name}: {of} wrt {wrt}")
+            if not isinstance(of, tuple) and lcons and of.strip("'") in lcons:
+                parts[-1] += " (Linear constraint)"
+            parts.append('')
 
-            for i in range(len(magnitudes)):
+            for i in range(len(abs_errs)):
                 # Absolute Errors
                 if directional:
                     if totals and abs_errs[i].forward is not None:
                         err = _format_error(abs_errs[i].forward, abs_error_tol)
-                        sys_buffer.write(f'    Absolute Error (Jfor - Jfd){stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Absolute Error (Jfwd - Jfd){stepstrs[i]} : {err}')
+                        parts.append(f'      fwd value: {abs_vals[i].forward[0]:.6e}')
+                        parts.append(f'      fd value: {abs_vals[i].forward[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
 
-                    if abs_errs[i].reverse is not None:
+                    if ('directional_fd_rev' in derivative_info and
+                            derivative_info['directional_fd_rev'][i]):
                         err = _format_error(abs_errs[i].reverse, abs_error_tol)
-                        sys_buffer.write(f'    Absolute Error ([rev, fd] Dot Product Test)'
-                                         f'{stepstrs[i]} : {err}\n')
+                        parts.append('    Max Absolute Error ([rev, fd] Dot Product Test)'
+                                     f'{stepstrs[i]} : {err}')
+                        fd, rev = derivative_info['directional_fd_rev'][i]
+                        parts.append(f'      rev value: {rev:.6e}')
+                        parts.append(f'      fd value: {fd:.6e} ({fd_desc}{stepstrs[i]})\n')
                 else:
                     if abs_errs[i].forward is not None:
                         err = _format_error(abs_errs[i].forward, abs_error_tol)
-                        sys_buffer.write(f'    Absolute Error (Jfor - Jfd){stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Absolute Error (Jfwd - Jfd){stepstrs[i]} : {err}')
+                        parts.append(f'      fwd value: {abs_vals[i].forward[0]:.6e}')
+                        parts.append(f'      fd value: {abs_vals[i].forward[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
 
                     if abs_errs[i].reverse is not None:
                         err = _format_error(abs_errs[i].reverse, abs_error_tol)
-                        sys_buffer.write(f'    Absolute Error (Jrev - Jfd){stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Absolute Error (Jrev - Jfd){stepstrs[i]} : {err}')
+                        parts.append(f'      rev value: {abs_vals[i].reverse[0]:.6e}')
+                        parts.append(f'      fd value: {abs_vals[i].reverse[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
 
             if directional:
-                if abs_errs[0].forward_reverse is not None:
-                    err = _format_error(abs_errs[0].forward_reverse, abs_error_tol)
-                    sys_buffer.write('    Absolute Error ([rev, for] Dot Product Test) : {err}\n')
-            else:
-                if abs_errs[0].forward_reverse is not None:
-                    err = _format_error(abs_errs[0].forward_reverse, abs_error_tol)
-                    sys_buffer.write(f'    Absolute Error (Jrev - Jfor) : {err}\n')
+                if ('directional_fwd_rev' in derivative_info and
+                        derivative_info['directional_fwd_rev']):
+                    err = _format_error(abs_errs[0].fwd_rev, abs_error_tol)
+                    parts.append(f'    Max Absolute Error ([rev, fwd] Dot Product Test) : {err}')
+                    fwd, rev = derivative_info['directional_fwd_rev']
+                    parts.append(f'      rev value: {rev:.6e}')
+                    parts.append(f'      fwd value: {fwd:.6e}\n')
+            elif abs_errs[0].fwd_rev is not None:
+                err = _format_error(abs_errs[0].fwd_rev, abs_error_tol)
+                parts.append(f'    Max Absolute Error (Jrev - Jfwd) : {err}')
+                parts.append(f'      rev value: {abs_vals[0].fwd_rev[0]:.6e}')
+                parts.append(f'      fwd value: {abs_vals[0].fwd_rev[1]:.6e}\n')
+
+            divname = {
+                'fwd': ['Jfwd', 'Jfd'],
+                'rev': ['Jrev', 'Jfd'],
+                'fwd_rev': ['Jrev', 'Jfwd']
+            }
 
-            sys_buffer.write('\n')
+            for i in range(len(abs_errs)):
+                didxs = denom_idxs[i]
+                divname_fwd = divname['fwd'][didxs['fwd']]
+                divname_rev = divname['rev'][didxs['rev']]
+                divname_fwd_rev = divname['fwd_rev'][didxs['fwd_rev']]
 
-            for i in range(len(magnitudes)):
                 # Relative Errors
                 if directional:
                     if totals and rel_errs[i].forward is not None:
                         err = _format_error(rel_errs[i].forward, rel_error_tol)
-                        sys_buffer.write(f'    Relative Error (Jfor - Jfd) / {divname}'
-                                         f'{stepstrs[i]} : {err}\n')
-
-                    if rel_errs[i].reverse is not None:
+                        parts.append(f'    Max Relative Error (Jfwd - Jfd) / {divname_fwd}'
+                                     f'{stepstrs[i]} : {err}')
+                        parts.append(f'      fwd value: {rel_vals[i].forward[0]:.6e}')
+                        parts.append(f'      fd value: {rel_vals[i].forward[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
+
+                    if ('directional_fd_rev' in derivative_info and
+                            derivative_info['directional_fd_rev'][i]):
                         err = _format_error(rel_errs[i].reverse, rel_error_tol)
-                        sys_buffer.write(f'    Relative Error ([rev, fd] Dot Product Test) '
-                                         f'/ {divname}{stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Relative Error ([rev, fd] Dot Product Test) '
+                                     f'/ {divname_rev}{stepstrs[i]} : {err}')
+                        parts.append(f'      rev value: {rel_vals[i].reverse[0]:.6e}')
+                        parts.append(f'      fd value: {rel_vals[i].reverse[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
                 else:
                     if rel_errs[i].forward is not None:
                         err = _format_error(rel_errs[i].forward, rel_error_tol)
-                        sys_buffer.write(f'    Relative Error (Jfor - Jfd) / {divname}'
-                                         f'{stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Relative Error (Jfwd - Jfd) / {divname_fwd}'
+                                     f'{stepstrs[i]} : {err}')
+                        parts.append(f'      fwd value: {rel_vals[i].forward[0]:.6e}')
+                        parts.append(f'      fd value: {rel_vals[i].forward[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
 
                     if rel_errs[i].reverse is not None:
                         err = _format_error(rel_errs[i].reverse, rel_error_tol)
-                        sys_buffer.write(f'    Relative Error (Jrev - Jfd) / {divname}'
-                                         f'{stepstrs[i]} : {err}\n')
+                        parts.append(f'    Max Relative Error (Jrev - Jfd) / {divname_rev}'
+                                     f'{stepstrs[i]} : {err}')
+                        parts.append(f'      rev value: {rel_vals[i].reverse[0]:.6e}')
+                        parts.append(f'      fd value: {rel_vals[i].reverse[1]:.6e} '
+                                     f'({fd_desc}{stepstrs[i]})\n')
 
-            if directional:
-                if rel_errs[0].forward_reverse is not None:
-                    err = _format_error(rel_errs[0].forward_reverse, rel_error_tol)
-                    sys_buffer.write(f'    Relative Error ([rev, for] Dot Product Test) / '
-                                     f'{divname} : {err}\n')
-            else:
-                if rel_errs[0].forward_reverse is not None:
-                    err = _format_error(rel_errs[0].forward_reverse, rel_error_tol)
-                    sys_buffer.write(f'    Relative Error (Jrev - Jfor) / {divname} : {err}\n')
+            if rel_errs[0].fwd_rev is not None:
+                if directional:
+                    err = _format_error(rel_errs[0].fwd_rev, rel_error_tol)
+                    parts.append(f'    Max Relative Error ([rev, fwd] Dot Product Test) / '
+                                 f'{divname_fwd_rev} : {err}')
+                    rev, fwd = derivative_info['directional_fwd_rev']
+                    parts.append(f'      rev value: {rev:.6e}')
+                    parts.append(f'      fwd value: {fwd:.6e}\n')
+                else:
+                    err = _format_error(rel_errs[0].fwd_rev, rel_error_tol)
+                    parts.append(f'    Max Relative Error (Jrev - Jfwd) / {divname_fwd_rev} : '
+                                 f'{err}')
+                    parts.append(f'      rev value: {rel_vals[0].fwd_rev[0]:.6e}')
+                    parts.append(f'      fwd value: {rel_vals[0].fwd_rev[1]:.6e}\n')
 
             if inconsistent:
-                sys_buffer.write('\n    * Inconsistent value across ranks *\n')
+                parts.append('\n    * Inconsistent value across ranks *\n')
 
             comm = self._problem_meta['comm']
             if MPI and comm.size > 1:
-                sys_buffer.write(f'\n    MPI Rank {comm.rank}\n')
-            sys_buffer.write('\n')
+                parts.append(f'\n    MPI Rank {comm.rank}\n')
 
             with np.printoptions(linewidth=240):
                 # Raw Derivatives
-                if magnitudes[0].forward is not None:
+                if abs_errs[0].forward is not None:
                     if directional:
-                        sys_buffer.write('    Directional Derivative (Jfor)')
+                        parts.append('    Directional Derivative (Jfwd)')
                     else:
-                        sys_buffer.write('    Raw Forward Derivative (Jfor)')
-                    Jstr = textwrap.indent(str(Jfor), '    ')
-                    sys_buffer.write(f"\n{Jstr}\n\n")
+                        parts.append('    Raw Forward Derivative (Jfwd)')
+                    Jstr = textwrap.indent(str(Jfwd), '    ')
+                    parts.append(f"{Jstr}\n")
 
                 fdtype = fd_opts['method'].upper()
 
-                if magnitudes[0].reverse is not None:
+                if abs_errs[0].reverse is not None:
                     if directional:
                         if totals:
-                            sys_buffer.write('    Directional Derivative (Jrev) Dot Product')
+                            parts.append('    Directional Derivative (Jrev) Dot Product')
                         else:
-                            sys_buffer.write('    Directional Derivative (Jrev)')
+                            parts.append('    Directional Derivative (Jrev)')
                     else:
-                        sys_buffer.write('    Raw Reverse Derivative (Jrev)')
+                        parts.append('    Raw Reverse Derivative (Jrev)')
                     Jstr = textwrap.indent(str(Jrev), '    ')
-                    sys_buffer.write(f"\n{Jstr}\n\n")
+                    parts.append(f"{Jstr}\n")
 
                 try:
                     fds = derivative_info['J_fd']
                 except KeyError:
                     fds = [0.]
 
-                for i in range(len(magnitudes)):
+                for i in range(len(abs_errs)):
                     fd = fds[i]
 
+                    Jstr = textwrap.indent(str(fd), '    ')
                     if directional:
-                        if totals and magnitudes[i].reverse is not None:
-                            sys_buffer.write(f'    Directional {fdtype} Derivative (Jfd) '
-                                             f'Dot Product{stepstrs[i]}\n    {fd}\n\n')
+                        if totals and abs_errs[i].reverse is not None:
+                            parts.append(f'    Directional {fdtype} Derivative (Jfd) '
+                                         f'Dot Product{stepstrs[i]}\n{Jstr}\n')
                         else:
-                            sys_buffer.write(f"    Directional {fdtype} Derivative (Jfd)"
-                                             f"{stepstrs[i]}\n    {fd}\n\n")
+                            parts.append(f"    Directional {fdtype} Derivative (Jfd)"
+                                         f"{stepstrs[i]}\n{Jstr}\n")
                     else:
-                        Jstr = textwrap.indent(str(fd), '    ')
-                        sys_buffer.write(f"    Raw {fdtype} Derivative (Jfd){stepstrs[i]}"
-                                         f"\n{Jstr}\n\n")
+                        parts.append(f"    Raw {fdtype} Derivative (Jfd){stepstrs[i]}"
+                                     f"\n{Jstr}\n")
+
+            parts.append(' -' * 30)
+            parts.append('')
 
-            sys_buffer.write(' -' * 30 + '\n')
+        sys_buffer.write('\n'.join(parts))
 
         if not show_only_incorrect or num_bad_jacs > 0:
             out_stream.write(sys_buffer.getvalue())
@@ -7143,7 +7173,7 @@ class System(object, metaclass=SystemMetaclass):
         table_data = []
         worst_subjac = None
 
-        for key, fd_norm, fd_opts, directional, above_abs, above_rel, inconsistent in err_iter:
+        for key, _, directional, above_abs, above_rel, inconsistent in err_iter:
 
             if above_abs or above_rel or inconsistent:
                 num_bad_jacs += 1
@@ -7156,14 +7186,8 @@ class System(object, metaclass=SystemMetaclass):
             if indices is not None:
                 of = f'{of} (index size: {indices})'
 
-            # need this check because if directional may be list
-            if isinstance(wrt, str):
-                wrt = f"'{wrt}'"
-            if isinstance(of, str):
-                of = f"'{of}'"
-
             if directional:
-                wrt = f"(d){wrt}"
+                wrt = f"(d) {wrt}"
 
             err_desc = []
             if above_abs:
@@ -7176,85 +7200,102 @@ class System(object, metaclass=SystemMetaclass):
 
             abs_errs = derivative_info['abs error']
             rel_errs = derivative_info['rel error']
-            magnitudes = derivative_info['magnitude']
+            abs_vals = derivative_info['vals_at_max_abs']
+            rel_vals = derivative_info['vals_at_max_rel']
             steps = derivative_info['steps']
 
-            for magnitude, abs_err, rel_err, step in zip(magnitudes, abs_errs, rel_errs, steps):
-                if magnitude.reverse is not None:
-                    calc_mag = magnitude.reverse
-                    calc_abs = abs_err.reverse
-                    calc_rel = rel_err.reverse
+            # loop over different fd step sizes
+            for abs_err, rel_err, abs_val, rel_val, step in zip(abs_errs, rel_errs,
+                                                                abs_vals, rel_vals,
+                                                                steps):
 
                 # use forward even if both fwd and rev are defined
-                if magnitude.forward is not None:
-                    calc_mag = magnitude.forward
+                if abs_err.forward is not None:
                     calc_abs = abs_err.forward
                     calc_rel = rel_err.forward
+                    calc_abs_val_fd = abs_val.forward[1]
+                    calc_rel_val_fd = rel_val.forward[1]
+                    calc_abs_val = abs_val.forward[0]
+                    calc_rel_val = rel_val.forward[0]
+                elif abs_err.reverse is not None:
+                    calc_abs = abs_err.reverse
+                    calc_rel = rel_err.reverse
+                    calc_abs_val_fd = abs_val.reverse[1]
+                    calc_rel_val_fd = rel_val.reverse[1]
+                    calc_abs_val = abs_val.reverse[0]
+                    calc_rel_val = rel_val.reverse[0]
+
+                start = [of, wrt, step] if len(steps) > 1 else [of, wrt]
 
                 if totals:
-                    if len(steps) > 1:
-                        table_data.append([of, wrt, step, calc_mag, magnitude.fd,
-                                           calc_abs, calc_rel, err_desc])
-                    else:
-                        table_data.append([of, wrt, calc_mag, magnitude.fd,
-                                           calc_abs, calc_rel, err_desc])
-                else:
+                    table_data.append(start +
+                                      [calc_abs_val, calc_abs_val_fd, calc_abs,
+                                       calc_rel_val, calc_rel_val_fd, calc_rel,
+                                       err_desc])
+                else:  # partials
                     if matrix_free:
-                        if len(steps) > 1:
-                            table_data.append([of, wrt, step, magnitude.forward,
-                                               magnitude.reverse, magnitude.fd,
-                                               abs_err.forward, abs_err.reverse,
-                                               abs_err.forward_reverse, rel_err.forward,
-                                               rel_err.reverse, rel_err.forward_reverse,
-                                               err_desc])
-                        else:
-                            table_data.append([of, wrt, magnitude.forward,
-                                               magnitude.reverse, magnitude.fd,
-                                               abs_err.forward, abs_err.reverse,
-                                               abs_err.forward_reverse, rel_err.forward,
-                                               rel_err.reverse, rel_err.forward_reverse,
-                                               err_desc])
+                        table_data.append(start +
+                                          [abs_val.forward[0], abs_val.forward[1],
+                                           abs_err.forward,
+                                           abs_val.reverse[0], abs_val.reverse[1],
+                                           abs_err.reverse,
+                                           abs_val.fwd_rev[0], abs_val.fwd_rev[1],
+                                           abs_err.fwd_rev,
+                                           rel_val.forward[0], rel_val.forward[1],
+                                           rel_err.forward,
+                                           rel_val.reverse[0], rel_val.reverse[1],
+                                           rel_err.reverse,
+                                           rel_val.fwd_rev[0], rel_val.fwd_rev[1],
+                                           rel_err.fwd_rev,
+                                           err_desc])
                     else:
-                        if len(steps) > 1:
-                            table_data.append([of, wrt, step, magnitude.forward,
-                                               magnitude.fd, abs_err.forward,
-                                               rel_err.forward, err_desc])
+                        if abs_val.forward is not None:
+                            table_data.append(start +
+                                              [abs_val.forward[0], abs_val.forward[1],
+                                               abs_err.forward,
+                                               rel_val.forward[0], rel_val.forward[1],
+                                               rel_err.forward,
+                                               err_desc])
                         else:
-                            table_data.append([of, wrt, magnitude.forward, magnitude.fd,
-                                               abs_err.forward, rel_err.forward,
+                            table_data.append(start +
+                                              [abs_val.reverse[0], abs_val.reverse[1],
+                                               abs_err.reverse,
+                                               rel_val.reverse[0], rel_val.reverse[1],
+                                               rel_err.reverse,
                                                err_desc])
-                        assert abs_err.forward_reverse is None
-                        assert rel_err.forward_reverse is None
-                        assert abs_err.reverse is None
-                        assert rel_err.reverse is None
+
+                        assert abs_err.fwd_rev is None
+                        assert rel_err.fwd_rev is None
 
                     # See if this subjacobian has the greater error in the derivative computation
                     # compared to the other subjacobians so far
-                    for err in rel_err[:2]:
-                        if err is None or np.isnan(err):
-                            continue
-
-                        if worst_subjac is None or err > worst_subjac[0]:
-                            worst_subjac = (err, table_data[-1])
+                    if worst_subjac is None or rel_err.max() > worst_subjac[0]:
+                        worst_subjac = (rel_err.max(), table_data[-1])
 
         headers = []
         if table_data:
-            headers = ["of '<variable>'", "wrt '<variable>'"]
+            headers = ["'of' variable", "'wrt' variable"]
             if len(steps) > 1:
                 headers.append('step')
 
             if matrix_free:
-                headers.extend(['fwd mag.', 'rev mag.', 'check mag.', 'a(fwd-chk)',
-                                'a(rev-chk)', 'a(fwd-rev)', 'r(fwd-chk)', 'r(rev-chk)',
-                                'r(fwd-rev)', 'error desc'])
+                headers.extend(['a(fwd val)', 'a(fd val)', 'a(fwd-fd)',
+                                'a(rev val)', 'a(rchk val)', 'a(rev-fd)',
+                                'a(fwd val)', 'a(rev val)', 'a(fwd-rev)',
+                                'r(fwd val)', 'r(fd val)', 'r(fwd-fd)',
+                                'r(rev val)', 'r(rchk val)', 'r(rev-fd)',
+                                'r(fwd val)', 'r(rev val)', 'r(fwd-rev)',
+                                'error desc'])
             else:
-                headers.extend(['calc mag.', 'check mag.', 'a(cal-chk)', 'r(cal-chk)',
+                headers.extend(['a(calc val)', 'a(fd val)', 'a(calc-fd)',
+                                'r(calc val)', 'r(fd val)', 'r(calc-fd)',
                                 'error desc'])
 
             _print_deriv_table(table_data, headers, sys_buffer)
 
             if show_worst and worst_subjac is not None:
-                print(f"\nWorst Sub-Jacobian (rel. error): {worst_subjac[0]}\n", file=sys_buffer)
+                print(f"\nWorst Sub-Jacobian (relative error): {worst_subjac[0]}\n",
+                      file=sys_buffer)
                 _print_deriv_table([worst_subjac[1]], headers, sys_buffer)
 
         if not show_only_incorrect or num_bad_jacs > 0:
@@ -7266,8 +7307,74 @@ class System(object, metaclass=SystemMetaclass):
         return worst_subjac + (headers,)
 
 
-_ErrorTuple = namedtuple('ErrorTuple', ['forward', 'reverse', 'forward_reverse'])
-_MagnitudeTuple = namedtuple('MagnitudeTuple', ['forward', 'reverse', 'fd'])
+class _ErrorData(object):
+    __slots__ = ['forward', 'reverse', 'fwd_rev']
+
+    def __init__(self, forward=None, reverse=None, fwd_rev=None):
+        self.forward = forward
+        self.reverse = reverse
+        self.fwd_rev = fwd_rev
+
+    def __iter__(self):
+        yield self.forward
+        yield self.reverse
+        yield self.fwd_rev
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}(forward={self.forward}, reverse={self.reverse}, " \
+            f"fwd_rev={self.fwd_rev})"
+
+    def max(self):
+        ret = 0.0
+        for err in self:
+            if err is not None:
+                if isinstance(err, tuple):
+                    for e in err:
+                        if ret < np.abs(e):
+                            ret = np.abs(e)
+                else:
+                    if ret < np.abs(err):
+                        ret = np.abs(err)
+        return ret
+
+    def __getitem__(self, idx):
+        return tuple(self)[idx]
+
+
+class _MagnitudeData(object):
+    __slots__ = ['forward', 'reverse', 'fd']
+
+    def __init__(self):
+        self.forward = 0.
+        self.reverse = 0.
+        self.fd = 0.
+
+    def __iter__(self):
+        yield self.forward
+        yield self.reverse
+        yield self.fd
+
+    def __repr__(self):
+        return f"_MagnitudeData(forward={self.forward}, reverse={self.reverse}, fd={self.fd})"
+
+    def max(self):
+        ret = 0.0
+        for mag in self:
+            if mag is not None and mag > ret:
+                ret = mag
+        return ret
+
+    def __getitem__(self, idx):
+        return tuple(self)[idx]
+
+    def update(self, J, Jtype):
+        if J is not None and J.size > 0:
+            if Jtype == 'fwd':
+                self.forward = max(self.forward, np.max(np.abs(J)))
+            elif Jtype == 'rev':
+                self.reverse = max(self.reverse, np.max(np.abs(J)))
+            elif Jtype == 'fd':
+                self.fd = max(self.fd, np.max(np.abs(J)))
 
 
 def _print_deriv_table(table_data, headers, out_stream, tablefmt='grid'):
@@ -7317,92 +7424,98 @@ def _compute_deriv_errors(derivative_info, matrix_free, directional, totals):
     float
         The norm of the FD jacobian.
     """
-    nan = float('nan')
-
     Jforward = derivative_info.get('J_fwd')
     Jreverse = derivative_info.get('J_rev')
-    forward = Jforward is not None
-    reverse = Jreverse is not None
-
-    rev_norm = fwd_norm = fwd_rev_error = None
-    calc_norm = 0.
-    if reverse:
-        rev_norm = calc_norm = safe_norm(Jreverse)
-    if forward:
-        fwd_norm = calc_norm = safe_norm(Jforward)
 
     try:
         fdinfo = derivative_info['J_fd']
         steps = derivative_info['steps']
     except KeyError:
         # this can happen when a partial is not declared, which means it should be zero
-        fdinfo = (np.zeros(1),)
+        fdinfo = (None,)
         steps = (None,)
 
-    if matrix_free:
-        derivative_info['matrix_free'] = True
-        if directional:
-            if forward and reverse:
-                mhatdotm, dhatdotd = derivative_info['directional_fwd_rev']
-                fwd_rev_error = safe_norm(mhatdotm - dhatdotd)
-            else:
-                fwd_rev_error = None
-        elif not totals:
-            fwd_rev_error = safe_norm(Jforward - Jreverse)
-
     derivative_info['abs error'] = []
     derivative_info['rel error'] = []
     derivative_info['magnitude'] = []
+    derivative_info['vals_at_max_abs'] = []
+    derivative_info['vals_at_max_rel'] = []
+    derivative_info['denom_idx'] = []
     derivative_info['steps'] = []
-    fdnorms = []
 
-    for i, fd in enumerate(fdinfo):
-        step = steps[i]
-        fd_norm = safe_norm(fd)
-        fdnorms.append(fd_norm)
+    abs_mags = _MagnitudeData()
+    abs_mags.update(Jforward, 'fwd')
+    abs_mags.update(Jreverse, 'rev')
 
-        fwd_error = rev_error = None
+    abs_errs_fwd_rev = abs_vals_fwd_rev = rel_errs_fwd_rev = rel_vals_fwd_rev = None
+    fwd_rev_didx = 1
+    if matrix_free:
+        derivative_info['matrix_free'] = True
+        if directional:
+            if Jforward is not None and Jreverse is not None:
+                mhatdotm, dhatdotd = derivative_info['directional_fwd_rev']
+                (abs_errs_fwd_rev, abs_vals_fwd_rev, rel_errs_fwd_rev, rel_vals_fwd_rev,
+                 fwd_rev_didx) = get_errors(dhatdotd, mhatdotm)
+        elif not totals:
+            (abs_errs_fwd_rev, abs_vals_fwd_rev, rel_errs_fwd_rev, rel_vals_fwd_rev,
+             fwd_rev_didx) = get_errors(Jforward, Jreverse)
 
-        if reverse and not directional:
-            rev_error = safe_norm(Jreverse - fd)
+    for i, Jfd in enumerate(fdinfo):
+        abs_errs = _ErrorData()
+        abs_vals = _ErrorData()
+        rel_errs = _ErrorData()
+        rel_vals = _ErrorData()
+        denom_idxs = {'fwd': 1, 'rev': 1, 'fwd_rev': fwd_rev_didx}
 
-        if forward:
-            fwd_error = safe_norm(Jforward - fd)
+        step = steps[i]
+        abs_mags.update(Jfd, 'fd')
 
         if directional:
-            if reverse:
-                mhatdotm, dhatdotd = derivative_info['directional_fd_rev'][i]
-                rev_error = safe_norm(mhatdotm - dhatdotd)
-                if not totals:
-                    rev_norm = None
-            if forward and totals:
-                mhatdotm, dhatdotd = derivative_info['directional_fd_fwd'][i]
-                fwd_error = safe_norm(mhatdotm - dhatdotd)
-
-        derivative_info['abs error'].append(_ErrorTuple(fwd_error, rev_error, fwd_rev_error))
-        derivative_info['magnitude'].append(_MagnitudeTuple(fwd_norm, rev_norm, fd_norm))
-        derivative_info['steps'].append(step)
-
-        # If fd_norm is zero, let's use calc_norm as the divisor for the relative
-        # error check. That way we don't accidentally squelch a legitimate problem.
-        div_norm = fd_norm if fd_norm != 0. else calc_norm
+            if Jforward is not None:
+                if totals:
+                    mhatdotm, dhatdotd = derivative_info['directional_fd_fwd'][i]
+                    (abs_errs.forward, abs_vals.forward,
+                     rel_errs.forward, rel_vals.forward, didx) = get_errors(mhatdotm, dhatdotd)
+                else:
+                    (abs_errs.forward, abs_vals.forward,
+                     rel_errs.forward, rel_vals.forward, didx) = get_errors(Jforward, Jfd)
+                denom_idxs['fwd'] = didx
 
-        if div_norm == 0.:
-            derivative_info['rel error'].append(_ErrorTuple(None if fwd_error is None else nan,
-                                                            None if rev_error is None else nan,
-                                                            None if fwd_rev_error is None else nan))
+            if Jreverse is not None:
+                mhatdotm, dhatdotd = derivative_info['directional_fd_rev'][i]
+                (abs_errs.reverse, abs_vals.reverse, rel_errs.reverse, rel_vals.reverse, didx) = \
+                    get_errors(mhatdotm, dhatdotd)
+                denom_idxs['rev'] = didx
         else:
-            if matrix_free and not totals:
-                derivative_info['rel error'].append(_ErrorTuple(fwd_error / div_norm,
-                                                                rev_error / div_norm,
-                                                                fwd_rev_error / div_norm))
-            else:
-                derivative_info['rel error'].append(_ErrorTuple(
-                    None if fwd_error is None else fwd_error / div_norm,
-                    None if rev_error is None else rev_error / div_norm,
-                    None if fwd_rev_error is None else fwd_rev_error / div_norm))
+            if Jforward is not None:
+                (abs_errs.forward, abs_vals.forward, rel_errs.forward, rel_vals.forward, didx) = \
+                    get_errors(Jforward, Jfd)
+                denom_idxs['fwd'] = didx
+            if Jreverse is not None:
+                (abs_errs.reverse, abs_vals.reverse, rel_errs.reverse, rel_vals.reverse, didx) = \
+                    get_errors(Jreverse, Jfd)
+                denom_idxs['rev'] = didx
+
+        if Jfd is not None and Jforward is None and Jreverse is None:
+            (abs_errs.reverse, abs_vals.reverse, rel_errs.reverse, rel_vals.reverse, didx) = \
+                get_errors(np.zeros_like(Jfd), Jfd)
+            denom_idxs['rev'] = didx
+
+        if abs_errs_fwd_rev is not None:
+            abs_errs.fwd_rev = abs_errs_fwd_rev
+            abs_vals.fwd_rev = abs_vals_fwd_rev
+            rel_errs.fwd_rev = rel_errs_fwd_rev
+            rel_vals.fwd_rev = rel_vals_fwd_rev
+
+        derivative_info['abs error'].append(abs_errs)
+        derivative_info['rel error'].append(rel_errs)
+        derivative_info['denom_idx'].append(denom_idxs)
+        derivative_info['magnitude'].append(abs_mags)
+        derivative_info['vals_at_max_abs'].append(abs_vals)
+        derivative_info['vals_at_max_rel'].append(rel_vals)
+        derivative_info['steps'].append(step)
 
-    return np.max(fdnorms)
+    return max([err.max() for err in derivative_info['abs error']])
 
 
 def _errors_above_tol(deriv_info, abs_error_tol, rel_error_tol):
@@ -7486,8 +7599,6 @@ def _iter_derivs(derivatives, show_only_incorrect, all_fd_opts, totals, nondep_d
     ------
     tuple
         The (of, wrt) pair for the current derivatives being compared.
-    float
-        The FD norm.
     dict
         The FD options.
     bool
@@ -7503,7 +7614,6 @@ def _iter_derivs(derivatives, show_only_incorrect, all_fd_opts, totals, nondep_d
     keys = sorted(derivatives) if sort else derivatives
 
     for key in keys:
-        _, wrt = key
 
         inconsistent = False
         derivative_info = derivatives[key]
@@ -7511,6 +7621,7 @@ def _iter_derivs(derivatives, show_only_incorrect, all_fd_opts, totals, nondep_d
         if totals:
             fd_opts = all_fd_opts
         else:
+            _, wrt = key
             fd_opts = all_fd_opts[wrt]
 
         if key in incon_keys:
@@ -7518,10 +7629,10 @@ def _iter_derivs(derivatives, show_only_incorrect, all_fd_opts, totals, nondep_d
 
         directional = bool(fd_opts) and fd_opts.get('directional')
 
-        fd_norm = _compute_deriv_errors(derivative_info, matrix_free, directional, totals)
+        max_mag = _compute_deriv_errors(derivative_info, matrix_free, directional, totals)
 
         # Skip printing the non-dependent keys if the derivatives are fine.
-        if key in nondep_derivs and fd_norm < abs_error_tol:
+        if key in nondep_derivs and max_mag < abs_error_tol:
             del derivatives[key]
             continue
 
@@ -7530,7 +7641,7 @@ def _iter_derivs(derivatives, show_only_incorrect, all_fd_opts, totals, nondep_d
         if show_only_incorrect and not (above_abs or above_rel or inconsistent):
             continue
 
-        yield key, fd_norm, fd_opts, directional, above_abs, above_rel, inconsistent
+        yield key, fd_opts, directional, above_abs, above_rel, inconsistent
 
 
 def _format_error(error, tol):
diff --git a/openmdao/core/total_jac.py b/openmdao/core/total_jac.py
index 42f606f57..07dd566c8 100644
--- a/openmdao/core/total_jac.py
+++ b/openmdao/core/total_jac.py
@@ -1487,7 +1487,8 @@ class _TotalJacInfo(object):
 
                 # if some of the wrt vars are distributed in fwd mode, we bcast from the rank
                 # where each part of the distrib var exists
-                if self.get_remote and mode == 'fwd' and self.has_wrt_dist:
+                if self.get_remote and mode == 'fwd' and self.has_wrt_dist and \
+                        self.dist_input_range_map:
                     for start, stop, rank in self.dist_input_range_map[mode]:
                         contig = self.J[:, start:stop].copy()
                         model.comm.Bcast(contig, root=rank)
diff --git a/openmdao/docs/openmdao_book/features/building_blocks/solvers/scipy_iter_solver.ipynb b/openmdao/docs/openmdao_book/features/building_blocks/solvers/scipy_iter_solver.ipynb
index 6bbcf9ede..4ba2f4ed2 100644
--- a/openmdao/docs/openmdao_book/features/building_blocks/solvers/scipy_iter_solver.ipynb
+++ b/openmdao/docs/openmdao_book/features/building_blocks/solvers/scipy_iter_solver.ipynb
@@ -486,8 +486,8 @@
    },
    "outputs": [],
    "source": [
-    "assert_near_equal(prob.get_val('sub1.q1.x'), 1.996, .0001)\n",
-    "assert_near_equal(prob.get_val('sub2.q2.x'), 1.996, .0001)"
+    "assert_near_equal(prob.get_val('sub1.q1.x'), 0.99857318, .0001)\n",
+    "assert_near_equal(prob.get_val('sub2.q2.x'), 0.99857318, .0001)"
    ]
   }
  ],
diff --git a/openmdao/utils/array_utils.py b/openmdao/utils/array_utils.py
index baf08db0a..b72016d93 100644
--- a/openmdao/utils/array_utils.py
+++ b/openmdao/utils/array_utils.py
@@ -1104,3 +1104,56 @@ def safe_norm(arr):
         Norm of the array or 0 if the array is None or empty.
     """
     return 0. if arr is None or arr.size == 0 else np.linalg.norm(arr)
+
+
+def get_errors(x, y):
+    """
+    Compute the max absolute and relative errors of the difference in x and y.
+
+    Parameters
+    ----------
+    x : ndarray
+        The first array.
+    y : ndarray
+        The second array.
+
+    Returns
+    -------
+    tuple
+        Tuple of (max_abs_error, (abs_max_x, abs_max_y), max_rel_error, (rel_max_x, rel_max_y),
+        denom_idx), where ???_max_x and ???_max_y are the values of x and y at the locations where
+        absolute and relative errors are max, respectively, and denom_idx is the index specifying
+        which argument, 0 (x) or 1 (y), was used as the denominator in the relative error.
+    """
+    args = (x, y)
+    abs_error = np.abs(x - y)
+    if abs_error.size == 0:
+        return 0.0, (0.0, 0.0), 0.0, (0.0, 0.0), 0
+
+    max_abs_error_idx = np.argmax(abs_error)
+    max_abs_error = abs_error.flat[max_abs_error_idx]
+    abs_max_x = x.flat[max_abs_error_idx]
+    abs_max_y = y.flat[max_abs_error_idx]
+
+    # Filter values where the divisor would be zero
+    denom_idx = 1
+    nonzero = y != 0.
+    if not nonzero.any():
+        nonzero = x != 0.
+        if not nonzero.any():
+            return max_abs_error, (abs_max_x, abs_max_y), float('nan'), (0.0, 0.0), denom_idx
+        else:
+            denom_idx = 0
+
+    max_rel_error_nz = abs_error[nonzero] / np.abs(args[denom_idx][nonzero])
+    max_rel_error_idx = np.argmax(max_rel_error_nz)
+    max_rel_error = max_rel_error_nz.flat[max_rel_error_idx]
+    rel_max_x = x[nonzero].flat[max_rel_error_idx]
+    rel_max_y = y[nonzero].flat[max_rel_error_idx]
+
+    # if one value is zero the relative error doesn't really make sense, so just defer to whatever
+    # the absolute error check says.
+    if rel_max_x == 0.0 or rel_max_y == 0.0:
+        max_rel_error = float('nan')
+
+    return max_abs_error, (abs_max_x, abs_max_y), max_rel_error, (rel_max_x, rel_max_y), denom_idx
diff --git a/openmdao/utils/assert_utils.py b/openmdao/utils/assert_utils.py
index 7f8646008..d2dd9a760 100644
--- a/openmdao/utils/assert_utils.py
+++ b/openmdao/utils/assert_utils.py
@@ -221,13 +221,16 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
                 is the (output, input) tuple of strings;
             Third key:
                 is one of ['rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev',
-                           'directional_fd_fwd', 'directional_fd_rev', 'directional_fwd_rev',
-                           'rank_inconsistent', 'steps', 'matrix_free', 'directional']
+                           'vals_at_max_abs', 'vals_at_max_rel', 'directional_fd_fwd',
+                           'directional_fd_rev', 'directional_fwd_rev', 'rank_inconsistent',
+                           'matrix_free', 'directional', 'steps', and 'rank_inconsistent'].
 
-            For 'rel error', 'abs error', 'magnitude' the value is: A tuple containing norms for
-                forward - fd, adjoint - fd, forward - adjoint.
-            For 'J_fd', 'J_fwd', 'J_rev' the value is: A numpy array representing the computed
+                For 'J_fd', 'J_fwd', 'J_rev' the value is a numpy array representing the computed
                 Jacobian for the three different methods of computation.
+                For 'rel error', 'abs error', 'vals_at_max_abs' and 'vals_at_max_rel' the value is a
+                tuple containing values for forward - fd, reverse - fd, forward - reverse. For
+                'magnitude' the value is a tuple indicating the maximum magnitude of values found in
+                Jfwd, Jrev, and Jfd.
     atol : float
         Absolute error. Default is 1e-6.
     rtol : float
@@ -296,17 +299,24 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
                         analytic_found = True
                         try:
                             if fwd and dfwd is not None:
-                                dJfwd, dJfd = dfwd
-                                np.testing.assert_allclose(dJfwd, dJfd, atol=atol, rtol=rtol,
+                                J1, J2 = dfwd
+                                np.testing.assert_allclose(J1, J2, atol=atol, rtol=rtol,
                                                            verbose=False, equal_nan=False)
                             elif not fwd and drev is not None:
-                                dJrev, dJfd = drev
-                                np.testing.assert_allclose(dJrev, dJfd, atol=atol, rtol=rtol,
+                                J1, J2 = drev
+                                np.testing.assert_allclose(J1, J2, atol=atol, rtol=rtol,
                                                            verbose=False, equal_nan=False)
                             else:
-                                np.testing.assert_allclose(J, J_fd, atol=atol, rtol=rtol,
+                                J1, J2 = J, J_fd
+                                np.testing.assert_allclose(J1, J2, atol=atol, rtol=rtol,
                                                            verbose=False, equal_nan=False)
                         except Exception as err:
+                            abserr, relerr = _parse_assert_allclose_error(err.args[0])
+                            if abserr < atol and not (np.any(J1) or np.any(J2)):
+                                # if one array is all zeros and we don't violate the absolute
+                                # tolerance, then don't flag the relative error.
+                                continue
+
                             if verbose:
                                 bad_derivs.append(f"\n{direction} derivatives of '{key[0]}' wrt "
                                                   f"'{key[1]}' do not match finite "
@@ -317,7 +327,6 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
                                         bad_derivs[-1] += f'\nJ_fd - {Jname}:\n' + \
                                             np.array2string(J_fd - J)
                             else:
-                                abserr, relerr = _parse_assert_allclose_error(err.args[0])
                                 bad_derivs.append([f"{key[0]} wrt {key[1]}", "abs",
                                                    f"fd-{Jname[2:]}", f"{abserr}"])
                                 bad_derivs.append([f"{key[0]} wrt {key[1]}", "rel",
@@ -325,7 +334,8 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
 
                 if not analytic_found:
                     # check if J_fd is all zeros.  If not, then we have a problem.
-                    if np.linalg.norm(J_fd) > 1e-15:
+                    abserr = np.max(np.abs(J_fd))
+                    if abserr > atol:
                         if verbose:
                             bad_derivs.append(f"\nAnalytic deriv for '{key[0]}' wrt '{key[1]}' "
                                               f"is assumed zero, but finite difference{stepstr} "
@@ -343,12 +353,19 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
                 try:
                     if dir_fwd_rev is not None:
                         dJfwd, dJrev = dir_fwd_rev
+                        either_zero = not (np.any(dJfwd) or np.any(dJrev))
                         np.testing.assert_allclose(dJfwd, dJrev, atol=atol, rtol=rtol,
                                                    verbose=False, equal_nan=False)
                     else:
+                        either_zero = not (np.any(J_fwd) or np.any(J_rev))
                         np.testing.assert_allclose(J_fwd, J_rev, atol=atol, rtol=rtol,
                                                    verbose=False, equal_nan=False)
                 except Exception as err:
+                    abserr, relerr = _parse_assert_allclose_error(err.args[0])
+                    if abserr < atol and either_zero:
+                        # if one array is all zeros and we don't violate the absolute
+                        # tolerance, then don't flag the relative error.
+                        continue
                     if verbose:
                         bad_derivs.append(f"\nForward and Reverse derivatives of '{key[0]}' wrt "
                                           f"'{key[1]}' do not match.\n")
@@ -358,7 +375,6 @@ def assert_check_partials(data, atol=1e-6, rtol=1e-6, verbose=False, max_display
                                 bad_derivs[-1] += '\nJ_fwd - J_rev:\n' + \
                                     np.array2string(J_fwd - J_rev)
                     else:
-                        abserr, relerr = _parse_assert_allclose_error(err.args[0])
                         bad_derivs.append([f"{key[0]} wrt {key[1]}", "abs", "fwd-rev", f"{abserr}"])
                         bad_derivs.append([f"{key[0]} wrt {key[1]}", "rel", "fwd-rev", f"{relerr}"])
 
@@ -397,10 +413,17 @@ def assert_check_totals(totals_data, atol=1e-6, rtol=1e-6, max_display_shape=(20
         First key:
             is the (output, input) tuple of strings;
         Second key:
-            is one of ['rel error', 'abs error', 'magnitude', 'fdstep'];
-
-        For 'rel error', 'abs error', 'magnitude' the value is: A tuple containing norms for
-            forward - fd, adjoint - fd, forward - adjoint.
+            is one of ['rel error', 'abs error', 'magnitude', 'J_fd', 'J_fwd', 'J_rev',
+                       'vals_at_max_abs', 'vals_at_max_rel', 'directional_fd_fwd',
+                       'directional_fd_rev', 'directional_fwd_rev', 'rank_inconsistent',
+                       'matrix_free', 'directional', 'steps', and 'rank_inconsistent'].
+
+            For 'J_fd', 'J_fwd', 'J_rev' the value is a numpy array representing the computed
+            Jacobian for the three different methods of computation.
+            For 'rel error', 'abs error', 'vals_at_max_abs' and 'vals_at_max_rel' the value is a
+            tuple containing values for forward - fd, reverse - fd, forward - reverse. For
+            'magnitude' the value is a tuple indicating the maximum magnitude of values found in
+            Jfwd, Jrev, and Jfd.
     atol : float
         Absolute error. Default is 1e-6.
     rtol : float
@@ -441,6 +464,11 @@ def assert_check_totals(totals_data, atol=1e-6, rtol=1e-6, max_display_shape=(20
                     np.testing.assert_allclose(J, J_fd, atol=atol, rtol=rtol, verbose=False,
                                                equal_nan=False)
                 except Exception as err:
+                    abserr, relerr = _parse_assert_allclose_error(err.args[0])
+                    if abserr < atol and not (np.any(J) or np.any(J_fd)):
+                        # if one array is all zeros and we don't violate the absolute
+                        # tolerance, then don't flag the relative error.
+                        continue
                     fails.append(f"\n{direction} derivatives of '{key[0]}' w.r.t '{key[1]}' "
                                  "do not match finite difference.\n")
                     fails[-1] += _filter_np_err(err.args[0])
diff --git a/openmdao/utils/code_utils.py b/openmdao/utils/code_utils.py
index c99189865..cc32663b6 100644
--- a/openmdao/utils/code_utils.py
+++ b/openmdao/utils/code_utils.py
@@ -12,6 +12,7 @@ import importlib
 from types import LambdaType
 from collections import defaultdict, OrderedDict
 from tokenize import NAME, tokenize, untokenize
+from openmdao.utils.om_warnings import issue_warning
 
 import networkx as nx
 
@@ -485,6 +486,9 @@ def get_return_names(func):
 class _FuncGrapher(ast.NodeVisitor):
     """
     An ast.NodeVisitor that builds a graph between a function's inputs and outputs.
+
+    Note that this class assumes that all outputs of a called function are dependent on all inputs
+    to that function, which may introduce dependencies that don't actually exist.
     """
 
     def __init__(self, node):
@@ -567,11 +571,18 @@ def get_func_graph(func, outnames=None, display=False):
 
     Returns
     -------
-    networkx.DiGraph
-        A graph containing edges from inputs to outputs.
+    networkx.DiGraph or None
+        A graph containing edges from inputs to outputs.  Returns None if the function graph
+        couldn't be determined.
     """
     node = ast.parse(textwrap.dedent(inspect.getsource(func)), mode='exec')
-    visitor = _FuncGrapher(node)
+    try:
+        visitor = _FuncGrapher(node)
+    except RuntimeError as err:
+        issue_warning(f"Can't determine function graph for function '{func.__name__}' "
+                      f"due to: {err}")
+        return
+
     retnames = _get_return_names(visitor.outs)
     inputs = set(inspect.signature(func).parameters)
 
@@ -609,22 +620,21 @@ def get_func_graph(func, outnames=None, display=False):
     return visitor.graph
 
 
-def get_partials_deps(func, outputs=None):
+def get_function_deps(func, outputs=None):
     """
     Generate tuples of the form (output, input) for the given function.
 
-    Only tuples where the output depends on the input are yielded. This can be used to
-    determine which partials need to be declared.
+    Only tuples where the output depends on the input are yielded.
 
     Note that currently the function grapher doesn't recurse into functions and assumes that all
-    outputs of a sub-function are dependent on all inputs to that function. This may lead to
-    a conservative estimate of partials that need to be declared.
+    outputs of a called function are dependent on all inputs to that function. This may lead to
+    some dependencies being reported that don't actually exist.
 
     Parameters
     ----------
-    func : Callable
+    func : function or method with source available
         The function to be analyzed.
-    outputs : list or None
+    outputs : list of str or None
         The list of output variable names.
 
     Yields
@@ -633,6 +643,16 @@ def get_partials_deps(func, outputs=None):
         A tuple of the form (output, input).
     """
     graph = get_func_graph(func, outputs)
+    if graph is None:
+        if outputs is None:
+            return None
+
+        # assume full dependency
+        for inp in inspect.signature(func).parameters:
+            for out in outputs:
+                yield out, inp
+        return
+
     outs = graph.graph['outputs']
     successors = graph.successors
 
diff --git a/openmdao/utils/jax_utils.py b/openmdao/utils/jax_utils.py
index eff9c9ea1..de0101ae8 100644
--- a/openmdao/utils/jax_utils.py
+++ b/openmdao/utils/jax_utils.py
@@ -14,8 +14,7 @@ import importlib
 import numpy as np
 
 from openmdao.visualization.tables.table_builder import generate_table
-from openmdao.utils.code_utils import _get_long_name, remove_src_blocks, replace_src_block, \
-    get_partials_deps
+from openmdao.utils.code_utils import _get_long_name, remove_src_blocks, replace_src_block
 from openmdao.utils.file_utils import get_module_path, _load_and_exec
 
 
@@ -1171,6 +1170,7 @@ def to_compute_primal(inst, outfile='stdout', verbose=False):
 
 if __name__ == '__main__':
     import openmdao.api as om
+    from openmdao.utils.code_utils import get_function_deps
 
     def func(x, y):  # noqa: D103
         z = jnp.sin(x) * y
@@ -1178,7 +1178,7 @@ if __name__ == '__main__':
         zz = q + x * 1.5
         return z, zz
 
-    print('partials are:\n', list(get_partials_deps(func, ('z', 'zz'))))
+    print('partials are:\n', list(get_function_deps(func, ('z', 'zz'))))
 
     p = om.Problem()
     comp = p.model.add_subsystem('comp', om.ExecComp('y = 2.0*x', x=np.ones(3), y=np.ones(3)))
diff --git a/openmdao/utils/testing_utils.py b/openmdao/utils/testing_utils.py
index a3828021a..63b9e26b2 100644
--- a/openmdao/utils/testing_utils.py
+++ b/openmdao/utils/testing_utils.py
@@ -308,8 +308,9 @@ def _fix_comp_check_data(data):
     data : dict
         Dictionary containing derivative information keyed by subjac.
     """
-    names = ['J_fd', 'abs error', 'rel error', 'magnitude', 'directional_fd_fwd',
-             'directional_fd_rev']
+    names = ['J_fd', 'abs error', 'rel error', 'magnitude', 'magnitude_rel',
+             'directional_fd_fwd', 'directional_fd_rev', 'denom_idx',
+             'vals_at_max_abs', 'vals_at_max_rel']
 
     for name in names:
         if name in data:
@@ -367,7 +368,7 @@ def compare_prob_vs_comp_check_partials(probdata, compdata, comp):
             probval2 = probval[key2]
             compval2 = compval[key2]
 
-            if key2 == 'J_fd' or key2 == 'J_fwd':
+            if key2 in ('J_fd', 'J_fwd', 'denom_idx'):
                 assert_near_equal(probval2, compval2, 1e-6, 1e-6)
             else:
                 for i in range(3):


Test patch:
diff --git a/.github/workflows/openmdao_latest_workflow.yml b/.github/workflows/openmdao_latest_workflow.yml
index ff5b1b77a..9bf079226 100644
--- a/.github/workflows/openmdao_latest_workflow.yml
+++ b/.github/workflows/openmdao_latest_workflow.yml
@@ -162,7 +162,7 @@ jobs:
           if [[ "${{ matrix.NUMPY }}" == "<2" ]]; then
             conda install mpich mpi4py petsc4py=3.21.2 -q -y
           else
-            conda install mpich mpi4py petsc4py -q -y
+            conda install mpich mpi4py petsc4py=3.22.2 -q -y
           fi
 
           echo "============================================================="
diff --git a/openmdao/core/tests/test_check_partials.py b/openmdao/core/tests/test_check_partials.py
index f4e00656e..332c48e28 100644
--- a/openmdao/core/tests/test_check_partials.py
+++ b/openmdao/core/tests/test_check_partials.py
@@ -197,12 +197,12 @@ class TestProblemCheckPartials(unittest.TestCase):
 
         y_wrt_x1_line = lines.index("  comp: 'y' wrt 'x1'")
 
-        self.assertTrue(lines[y_wrt_x1_line+4].endswith('*'),
+        self.assertTrue(lines[y_wrt_x1_line+2].endswith('*'),
                         msg='Error flag expected in output but not displayed')
         self.assertTrue(lines[y_wrt_x1_line+6].endswith('*'),
                         msg='Error flag expected in output but not displayed')
         y_wrt_x2_line = lines.index("  comp: 'y' wrt 'x2'")
-        self.assertTrue(lines[y_wrt_x2_line+4].endswith('*'),
+        self.assertTrue(lines[y_wrt_x2_line+2].endswith('*'),
                         msg='Error flag not expected in output but displayed')
         self.assertTrue(lines[y_wrt_x2_line+6].endswith('*'),
                         msg='Error flag not expected in output but displayed')
@@ -353,7 +353,7 @@ class TestProblemCheckPartials(unittest.TestCase):
         abs_error = data['comp']['y', 'x2']['abs error']
         rel_error = data['comp']['y', 'x2']['rel error']
         self.assertAlmostEqual(abs_error.forward, 4.)
-        self.assertAlmostEqual(rel_error.forward, 1.)
+        self.assertTrue(np.isnan(rel_error.forward))
         self.assertAlmostEqual(np.linalg.norm(data['comp']['y', 'x2']['J_fd'] - 4.), 0.,
                                delta=1e-6)
 
@@ -649,9 +649,9 @@ class TestProblemCheckPartials(unittest.TestCase):
         data = prob.check_partials(out_stream=stream, compact_print=True)
         txt = stream.getvalue()
 
-        self.assertTrue("'g'             | 'z'" in txt)
+        self.assertTrue("g             | z" in txt)
         self.assertTrue(('g', 'z') in data['comp'])
-        self.assertTrue("'g'             | 'x'" in txt)
+        self.assertTrue("g             | x" in txt)
         self.assertTrue(('g', 'x') in data['comp'])
 
     def test_dependent_false_show(self):
@@ -972,9 +972,9 @@ class TestProblemCheckPartials(unittest.TestCase):
         prob.check_partials(out_stream=stream)
 
         lines = stream.getvalue().splitlines()
-        self.assertTrue('cs' in lines[6],
+        self.assertTrue('CS' in lines[17],
                         msg='Did you change the format for printing check derivs?')
-        self.assertTrue('fd' in lines[21],
+        self.assertTrue('FD' in lines[35],
                         msg='Did you change the format for printing check derivs?')
 
     def test_set_check_partial_options_invalid(self):
@@ -1203,13 +1203,13 @@ class TestProblemCheckPartials(unittest.TestCase):
         prob.check_partials(out_stream=stream, abs_err_tol=1.1e-6, compact_print=True)
 
         self.assertEqual(stream.getvalue().count('n/a'), 0)
-        self.assertEqual(stream.getvalue().count('rev'), 5)
+        self.assertEqual(stream.getvalue().count('rev'), 8)
         self.assertEqual(stream.getvalue().count('Component'), 2)
         self.assertEqual(len([ln for ln in stream.getvalue().splitlines() if ln.startswith('| ')]), 12) # counts rows (including headers)
 
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=False)
-        self.assertEqual(stream.getvalue().count('Reverse Magnitude'), 4)
+        self.assertEqual(stream.getvalue().count('rev value:'), 16)
         self.assertEqual(stream.getvalue().count('Raw Reverse Derivative'), 4)
         self.assertEqual(stream.getvalue().count('Jrev'), 20)
 
@@ -1242,8 +1242,8 @@ class TestProblemCheckPartials(unittest.TestCase):
         stream = StringIO()
         partials_data = prob.check_partials(out_stream=stream, compact_print=False)
         # So for this case, they do all provide them, so rev should not be shown
-        self.assertEqual(stream.getvalue().count('Forward Magnitude'), 2)
-        self.assertEqual(stream.getvalue().count('Reverse Magnitude'), 0)
+        self.assertEqual(stream.getvalue().count('fwd value'), 4)
+        self.assertEqual(stream.getvalue().count('rev value'), 0)
         self.assertEqual(stream.getvalue().count('Absolute Error'), 2)
         self.assertEqual(stream.getvalue().count('Relative Error'), 2)
         self.assertEqual(stream.getvalue().count('Raw Forward Derivative'), 2)
@@ -1264,11 +1264,11 @@ class TestProblemCheckPartials(unittest.TestCase):
         prob.run_model()
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=True)
-        self.assertEqual(stream.getvalue().count('rev'), 10)
+        self.assertEqual(stream.getvalue().count('rev'), 16)
 
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=False)
-        self.assertEqual(stream.getvalue().count('Reverse'), 4)
+        self.assertEqual(stream.getvalue().count('Reverse'), 2)
         self.assertEqual(stream.getvalue().count('Jrev'), 10)
 
         # 4: Mixed comps. Some with jacobians. Some not
@@ -1289,14 +1289,14 @@ class TestProblemCheckPartials(unittest.TestCase):
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=True)
         self.assertEqual(stream.getvalue().count('n/a'), 0)
-        self.assertEqual(stream.getvalue().count('rev'), 10)
+        self.assertEqual(stream.getvalue().count('rev'), 16)
         self.assertEqual(stream.getvalue().count('Component'), 2)
         self.assertEqual(len([ln for ln in stream.getvalue().splitlines() if ln.startswith('| ')]), 8)
 
         stream = StringIO()
         partials_data = prob.check_partials(out_stream=stream, compact_print=False)
-        self.assertEqual(stream.getvalue().count('Forward Magnitude'), 4)
-        self.assertEqual(stream.getvalue().count('Reverse Magnitude'), 2)
+        self.assertEqual(stream.getvalue().count('fwd value:'), 12)
+        self.assertEqual(stream.getvalue().count('rev value'), 8)
         self.assertEqual(stream.getvalue().count('Absolute Error'), 8)
         self.assertEqual(stream.getvalue().count('Relative Error'), 8)
         self.assertEqual(stream.getvalue().count('Raw Forward Derivative'), 4)
@@ -1330,7 +1330,7 @@ class TestProblemCheckPartials(unittest.TestCase):
 
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=True)
-        self.assertEqual(stream.getvalue().count("'z'             | 'y1'"), 2)
+        self.assertEqual(stream.getvalue().count("z             | y1"), 2)
 
     def test_check_partials_show_only_incorrect(self):
         # The second is adding an option to show only the incorrect subjacs
@@ -1356,7 +1356,7 @@ class TestProblemCheckPartials(unittest.TestCase):
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=True, show_only_incorrect=True)
         self.assertEqual(stream.getvalue().count("MyCompBadPartials"), 2)
-        self.assertEqual(stream.getvalue().count("'z'             | 'y1'"), 2)
+        self.assertEqual(stream.getvalue().count("z             | y1"), 2)
         self.assertEqual(stream.getvalue().count("MyCompGoodPartials"), 0)
 
         stream = StringIO()
@@ -1430,8 +1430,8 @@ class TestProblemCheckPartials(unittest.TestCase):
         stream = StringIO()
         prob.check_partials(out_stream=stream, compact_print=True)
         output = stream.getvalue()
-        self.assertTrue("(d)'x1'" in output)
-        self.assertTrue("(d)'x2'" in output)
+        self.assertTrue("(d) x1" in output)
+        self.assertTrue("(d) x2" in output)
 
     def test_directional_derivative_option_complex_step(self):
 
@@ -1485,8 +1485,8 @@ class TestProblemCheckPartials(unittest.TestCase):
         J = prob.check_partials(method='cs', out_stream=stream, compact_print=True)
         lines = stream.getvalue().splitlines()
 
-        self.assertEqual(lines[7][53:56], 'n/a')
-        assert_near_equal(float(lines[7][121:131]), 0.0, 1e-15)
+        entries = [s.strip() for s in lines[7].split('|') if s.strip()]
+        assert_near_equal(float(entries[19]), 0.0, 4e-15)
 
     def test_directional_mixed_matrix_free(self):
 
@@ -1813,7 +1813,7 @@ class TestProblemCheckPartials(unittest.TestCase):
         prob.check_partials(out_stream=stream)
         lines = stream.getvalue().splitlines()
 
-        self.assertTrue("Relative Error (Jfor - Jfd) / Jfor : 1." in lines[10])
+        self.assertTrue("Max Relative Error (Jfwd - Jfd) / Jfwd : nan" in lines[10])
 
     def test_directional_bug_implicit(self):
         # Test for bug in directional derivative direction for implicit var and matrix-free.
@@ -1969,6 +1969,37 @@ y wrt x                     | abs         | fd-fwd | 2.000000000279556
         for line1, line2 in zip_longest(ctx.exception.args[0].strip().split('\n'), expected.split('\n'), fillvalue=''):
             assert snum_equal(line1.strip(), line2.strip()), f"line1: {line1}, line2: {line2}"
 
+    def test_zero_analytic_zero_fd(self):
+        # Test that we can check a component that has a zero analytic and fd derivative.
+
+        class ZeroAnalyticComp(om.ExplicitComponent):
+
+            def setup(self):
+                self.add_input('x', val=3.0)
+                self.add_output('y', val=4.0)
+
+                self.declare_partials('*', '*')
+
+            def compute(self, inputs, outputs):
+                pass
+
+            def compute_partials(self, inputs, partials):
+                pass
+
+        prob = om.Problem()
+
+        prob.model.add_subsystem('p1', om.IndepVarComp('x', 3.5))
+        prob.model.add_subsystem('comp', ZeroAnalyticComp())
+        prob.model.connect('p1.x', 'comp.x')
+
+        prob.setup()
+        prob.run_model()
+
+        msg = "\nComponent 'comp' has zero derivatives for the following variable pairs that were declared as 'dependent': [('y', 'x')].\n"
+
+        with assert_warning(UserWarning, msg):
+            prob.check_partials(out_stream=None, compact_print=True)
+
 
 @unittest.skipUnless(MPI and PETScVector, "MPI and PETSc are required.")
 class TestCheckPartialsDistribDirectional(unittest.TestCase):
@@ -2792,10 +2823,10 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         nderivs = ncomps * 2
         self.assertEqual(contents.count("Component: CompGoodPartials 'good'"), 1)
         self.assertEqual(contents.count("Component: CompBadPartials 'bad'"), 1)
-        self.assertEqual(contents.count("Fd Magnitude:"), nderivs)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd), step="), 0)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd)"), nderivs)
-        self.assertEqual(contents.count("Relative Error (Jfor - Jfd) / Jf"), nderivs)
+        self.assertEqual(contents.count("fwd value:"), nderivs * 2)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd), step="), 0)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd)"), nderivs)
+        self.assertEqual(contents.count("Relative Error (Jfwd - Jfd) / Jf"), nderivs)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd), step="), 0)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd)"), nderivs)
 
@@ -2815,9 +2846,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         self.assertEqual(len(tables[1]), 7)
         self.assertEqual(len(tables[2]), 5)
         # check cols
-        self.assertEqual(tables[0][0].count('+'), 8)
-        self.assertEqual(tables[1][0].count('+'), 8)
-        self.assertEqual(tables[2][0].count('+'), 7)
+        self.assertEqual(tables[0][0].count('+'), 10)
+        self.assertEqual(tables[1][0].count('+'), 10)
+        self.assertEqual(tables[2][0].count('+'), 9)
 
     def test_single_cs_step_compact(self):
         p = self.setup_model()
@@ -2835,9 +2866,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         self.assertEqual(len(tables[1]), 7)
         self.assertEqual(len(tables[2]), 5)
         # check cols
-        self.assertEqual(tables[0][0].count('+'), 8)
-        self.assertEqual(tables[1][0].count('+'), 8)
-        self.assertEqual(tables[2][0].count('+'), 7)
+        self.assertEqual(tables[0][0].count('+'), 10)
+        self.assertEqual(tables[1][0].count('+'), 10)
+        self.assertEqual(tables[2][0].count('+'), 9)
 
     def test_multi_fd_steps(self):
         p = self.setup_model()
@@ -2848,9 +2879,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         nderivs = ncomps * 2
         self.assertEqual(contents.count("Component: CompGoodPartials 'good'"), 1)
         self.assertEqual(contents.count("Component: CompBadPartials 'bad'"), 1)
-        self.assertEqual(contents.count("Fd Magnitude:"), nderivs * 2)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd), step="), nderivs * 2)
-        self.assertEqual(contents.count("Relative Error (Jfor - Jfd) / Jf"), nderivs * 2)
+        self.assertEqual(contents.count("fwd value:"), nderivs * 4)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd), step="), nderivs * 2)
+        self.assertEqual(contents.count("Relative Error (Jfwd - Jfd) / Jf"), nderivs * 2)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd), step="), nderivs * 2)
 
     def test_multi_fd_steps_compact(self):
@@ -2869,9 +2900,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         self.assertEqual(len(tables[1]), 11)
         self.assertEqual(len(tables[2]), 5)
         # check cols
-        self.assertEqual(tables[0][0].count('+'), 9)
-        self.assertEqual(tables[1][0].count('+'), 9)
-        self.assertEqual(tables[2][0].count('+'), 8)
+        self.assertEqual(tables[0][0].count('+'), 11)
+        self.assertEqual(tables[1][0].count('+'), 11)
+        self.assertEqual(tables[2][0].count('+'), 10)
 
     def test_multi_cs_steps_compact(self):
         p = self.setup_model()
@@ -2889,9 +2920,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         self.assertEqual(len(tables[1]), 11)
         self.assertEqual(len(tables[2]), 5)
         # check cols
-        self.assertEqual(tables[0][0].count('+'), 9)
-        self.assertEqual(tables[1][0].count('+'), 9)
-        self.assertEqual(tables[2][0].count('+'), 8)
+        self.assertEqual(tables[0][0].count('+'), 11)
+        self.assertEqual(tables[1][0].count('+'), 11)
+        self.assertEqual(tables[2][0].count('+'), 10)
 
     def test_multi_fd_steps_compact_directional(self):
         p = self.setup_model(directional=True)
@@ -2909,9 +2940,9 @@ class TestCheckPartialsMultipleSteps(unittest.TestCase):
         self.assertEqual(len(tables[1]), 11)
         self.assertEqual(len(tables[2]), 5)
         # check cols
-        self.assertEqual(tables[0][0].count('+'), 9)
-        self.assertEqual(tables[1][0].count('+'), 9)
-        self.assertEqual(tables[2][0].count('+'), 8)
+        self.assertEqual(tables[0][0].count('+'), 11)
+        self.assertEqual(tables[1][0].count('+'), 11)
+        self.assertEqual(tables[2][0].count('+'), 10)
 
 
 if __name__ == "__main__":
diff --git a/openmdao/core/tests/test_check_totals.py b/openmdao/core/tests/test_check_totals.py
index e19561358..d032425ec 100644
--- a/openmdao/core/tests/test_check_totals.py
+++ b/openmdao/core/tests/test_check_totals.py
@@ -419,23 +419,23 @@ class TestProblemCheckTotals(unittest.TestCase):
         # Make sure auto-ivc sources are translated to promoted input names.
         self.assertTrue('x' in lines[4])
 
-        self.assertTrue('9.80614' in lines[5], "'9.80614' not found in '%s'" % lines[5])
-        self.assertTrue('9.80614' in lines[6], "'9.80614' not found in '%s'" % lines[6])
-        self.assertTrue('cs:None' in lines[6], "'cs:None not found in '%s'" % lines[6])
+        self.assertTrue('9.80614' in lines[7], "'9.80614' not found in '%s'" % lines[5])
+        self.assertTrue('9.80614' in lines[8], "'9.80614' not found in '%s'" % lines[6])
+        self.assertTrue('cs:None' in lines[8], "'cs:None not found in '%s'" % lines[6])
 
         assert_near_equal(totals['con2', 'x']['J_fwd'], [[0.09692762]], 1e-5)
         assert_near_equal(totals['con2', 'x']['J_fd'], [[0.09692762]], 1e-5)
 
         # Test compact_print output
         compact_stream = StringIO()
-        assert_check_totals(prob.check_totals(method='fd', out_stream=compact_stream, compact_print=True))
+        assert_check_totals(prob.check_totals(method='cs', out_stream=compact_stream, compact_print=True))
 
         compact_lines = compact_stream.getvalue().splitlines()
 
-        self.assertTrue("of '<variable>'" in compact_lines[5],
-            "of '<variable>' not found in '%s'" % compact_lines[5])
-        self.assertTrue('9.7743e+00' in compact_lines[-2],
-            "'9.7743e+00' not found in '%s'" % compact_lines[-2])
+        self.assertTrue("'of' variable" in compact_lines[5],
+            "'of' variable not found in '%s'" % compact_lines[5])
+        self.assertTrue('9.6100e+00' in compact_lines[-2],
+            "'9.6100e+00' not found in '%s'" % compact_lines[-2])
 
     def test_check_totals_show_progress(self):
         prob = om.Problem()
@@ -501,9 +501,9 @@ class TestProblemCheckTotals(unittest.TestCase):
 
         lines = stream.getvalue().splitlines()
 
-        self.assertTrue('1.000' in lines[5])
-        self.assertTrue('1.000' in lines[6])
-        self.assertTrue('0.000' in lines[8])
+        self.assertTrue('1.000' in lines[7])
+        self.assertTrue('1.000' in lines[8])
+        self.assertTrue('0.000' in lines[6])
         self.assertTrue('0.000' in lines[10])
 
         assert_near_equal(totals['x', 'x']['J_fwd'], [[1.0]], 1e-5)
@@ -1219,8 +1219,8 @@ class TestProblemCheckTotals(unittest.TestCase):
         lines = stream.getvalue().splitlines()
 
         self.assertTrue("Full Model: 'lcy' wrt 'x' (Linear constraint)" in lines[4])
-        self.assertTrue("Absolute Error (Jfor - Jfd)" in lines[8])
-        self.assertTrue("Relative Error (Jfor - Jfd) / Jfd" in lines[10])
+        self.assertTrue("Absolute Error (Jfwd - Jfd)" in lines[6])
+        self.assertTrue("Relative Error (Jfwd - Jfd) / Jfd" in lines[10])
 
     def test_alias_constraints(self):
         prob = om.Problem()
@@ -1377,13 +1377,13 @@ class TestProblemCheckTotals(unittest.TestCase):
         data = prob.check_totals(method='cs', out_stream=stream, directional=True)
         content = stream.getvalue()
 
-        self.assertEqual(content.count('Reverse Magnitude:'), 0)
-        self.assertEqual(content.count('Forward Magnitude:'), 1)
-        self.assertEqual(content.count('Fd Magnitude:'), 1)
-        self.assertEqual(content.count('Directional Derivative (Jfor)'), 1)
+        self.assertEqual(content.count('rev value:'), 0)
+        self.assertEqual(content.count('fwd value:'), 2)
+        self.assertEqual(content.count('fd value:'), 2)
+        self.assertEqual(content.count('Directional Derivative (Jfwd)'), 1)
         self.assertEqual(content.count('Directional CS Derivative (Jfd)'), 1)
-        self.assertTrue('Relative Error (Jfor - Jfd) / Jfd : ' in content)
-        self.assertTrue('Absolute Error (Jfor - Jfd) : ' in content)
+        self.assertTrue('Max Relative Error (Jfwd - Jfd) / Jfd : ' in content)
+        self.assertTrue('Max Absolute Error (Jfwd - Jfd) : ' in content)
         mhatdotm, dhatdotd =  data[(('comp.out',), 'comp.in')]['directional_fd_fwd']
         assert_near_equal(mhatdotm, dhatdotd, tolerance=2e-15)
 
@@ -1402,9 +1402,9 @@ class TestProblemCheckTotals(unittest.TestCase):
         content = stream.getvalue()
 
         self.assertEqual(content.count('comp.out (index size: 1)'), 1)
-        self.assertEqual(content.count('Reverse Magnitude:'), 1)
-        self.assertEqual(content.count('Forward Magnitude:'), 0)
-        self.assertEqual(content.count('Fd Magnitude:'), 1)
+        self.assertEqual(content.count('rev value:'), 2)
+        self.assertEqual(content.count('fwd value:'), 0)
+        self.assertEqual(content.count('fd value:'), 2)
         self.assertEqual(content.count('Directional Derivative (Jrev)'), 1)
         self.assertEqual(content.count('Directional CS Derivative (Jfd)'), 1)
         self.assertTrue('Relative Error ([rev, fd] Dot Product Test) / Jfd : ' in content)
@@ -1427,9 +1427,9 @@ class TestProblemCheckTotals(unittest.TestCase):
         content = stream.getvalue()
 
         self.assertEqual(content.count("'comp.out' wrt (d)('comp.in',)"), 1)
-        self.assertEqual(content.count('Reverse Magnitude:'), 1)
-        self.assertEqual(content.count('Forward Magnitude:'), 0)
-        self.assertEqual(content.count('Fd Magnitude:'), 1)
+        self.assertEqual(content.count('rev value:'), 2)
+        self.assertEqual(content.count('fwd value:'), 0)
+        self.assertEqual(content.count('fd value:'), 2)
         self.assertEqual(content.count('Directional Derivative (Jrev)'), 1)
         self.assertEqual(content.count('Directional CS Derivative (Jfd)'), 1)
         self.assertTrue('Relative Error ([rev, fd] Dot Product Test) / Jfd : ' in content)
@@ -1455,9 +1455,9 @@ class TestProblemCheckTotals(unittest.TestCase):
 
         self.assertEqual(content.count("'comp.out1' wrt (d)('comp.in1', 'comp.in2')"), 1)
         self.assertEqual(content.count("'comp.out2' wrt (d)('comp.in1', 'comp.in2')"), 1)
-        self.assertEqual(content.count('Reverse Magnitude:'), 2)
-        self.assertEqual(content.count('Forward Magnitude:'), 0)
-        self.assertEqual(content.count('Fd Magnitude:'), 2)
+        self.assertEqual(content.count('rev value:'), 4)
+        self.assertEqual(content.count('fwd value:'), 0)
+        self.assertEqual(content.count('fd value:'), 4)
         self.assertEqual(content.count('Directional Derivative (Jrev)'), 2)
         self.assertEqual(content.count('Directional CS Derivative (Jfd)'), 2)
         self.assertTrue(content.count('Relative Error ([rev, fd] Dot Product Test) / Jfd :'), 2)
@@ -1506,11 +1506,11 @@ class TestProblemCheckTotals(unittest.TestCase):
 
         self.assertEqual(content.count("('comp.out1', 'comp.out2') wrt (d)'comp.in1'"), 1)
         self.assertEqual(content.count("('comp.out1', 'comp.out2') wrt (d)'comp.in2'"), 1)
-        self.assertEqual(content.count('Reverse Magnitude:'), 0)
-        self.assertEqual(content.count('Forward Magnitude:'), 2)
-        self.assertEqual(content.count('Fd Magnitude:'), 2)
+        self.assertEqual(content.count('rev value:'), 0)
+        self.assertEqual(content.count('fwd value:'), 4)
+        self.assertEqual(content.count('fd value:'), 4)
         self.assertEqual(content.count('Directional Derivative (Jrev)'), 0)
-        self.assertEqual(content.count('Directional Derivative (Jfor)'), 2)
+        self.assertEqual(content.count('Directional Derivative (Jfwd)'), 2)
         self.assertEqual(content.count('Directional CS Derivative (Jfd)'), 2)
         self.assertEqual(content.count('Relative Error ([rev, fd] Dot Product Test) / Jfd :'), 0)
         self.assertEqual(content.count('Absolute Error ([rev, fd] Dot Product Test) :'), 0)
@@ -2019,10 +2019,10 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         contents = stream.getvalue()
         nsubjacs = 18
         self.assertEqual(contents.count("Full Model:"), nsubjacs)
-        self.assertEqual(contents.count("Fd Magnitude:"), nsubjacs)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd), step="), 0)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd)"), nsubjacs)
-        self.assertEqual(contents.count("Relative Error (Jfor - Jfd) / Jf"), nsubjacs)
+        self.assertEqual(contents.count("fd value:"), nsubjacs * 2)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd), step="), 0)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd)"), nsubjacs)
+        self.assertEqual(contents.count("Relative Error (Jfwd - Jfd) / Jf"), nsubjacs)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd), step="), 0)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd)"), nsubjacs)
 
@@ -2035,7 +2035,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         contents = stream.getvalue()
         nsubjacs = 18
         self.assertEqual(contents.count("Full Model:"), nsubjacs)
-        self.assertEqual(contents.count("Fd Magnitude:"), nsubjacs)
+        self.assertEqual(contents.count("fd value:"), nsubjacs * 2)
         self.assertEqual(contents.count("Absolute Error (Jrev - Jfd), step="), 0)
         self.assertEqual(contents.count("Absolute Error (Jrev - Jfd)"), nsubjacs)
         self.assertEqual(contents.count("Relative Error (Jrev - Jfd) / J"), nsubjacs)
@@ -2054,7 +2054,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
                 nsubjacs = 18
                 self.assertEqual(contents.count("step"), 0)
                 # check number of rows/cols
-                self.assertEqual(contents.count("+-------------------------------+------------------+-------------+-------------+-------------+-------------+--------------------+"), nsubjacs + 1)
+                self.assertEqual(contents.count("+-----------------------------+----------------+-------------+-------------+-------------+-------------+-------------+-------------+--------------------+"), nsubjacs + 1)
 
     def test_single_cs_step_compact(self):
         for mode in ('fwd', 'rev'):
@@ -2068,7 +2068,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
                 nsubjacs = 18
                 self.assertEqual(contents.count("step"), 0)
                 # check number of rows/cols
-                self.assertEqual(contents.count("+-------------------------------+------------------+-------------+-------------+-------------+-------------+------------+"), nsubjacs + 1)
+                self.assertEqual(contents.count("+-----------------------------+----------------+-------------+-------------+-------------+-------------+-------------+-------------+------------+"), nsubjacs + 1)
 
     def test_multi_fd_steps_fwd(self):
         p = om.Problem(model=CircleOpt(), driver=om.ScipyOptimizeDriver(optimizer='SLSQP', disp=False))
@@ -2079,9 +2079,9 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         contents = stream.getvalue()
         nsubjacs = 18
         self.assertEqual(contents.count("Full Model:"), nsubjacs)
-        self.assertEqual(contents.count("Fd Magnitude:"), nsubjacs * 2)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd), step="), nsubjacs * 2)
-        self.assertEqual(contents.count("Relative Error (Jfor - Jfd) / Jf"), nsubjacs * 2)
+        self.assertEqual(contents.count("fd value:"), nsubjacs * 4)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd), step="), nsubjacs * 2)
+        self.assertEqual(contents.count("Relative Error (Jfwd - Jfd) / Jf"), nsubjacs * 2)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd), step="), nsubjacs * 2)
 
     def test_multi_fd_steps_fwd_directional(self):
@@ -2092,9 +2092,9 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         p.check_totals(step=[1e-6, 1e-7], directional=True, out_stream=stream)
         contents = stream.getvalue()
         self.assertEqual(contents.count("Full Model:"), 3)
-        self.assertEqual(contents.count("Fd Magnitude:"), 6)
-        self.assertEqual(contents.count("Absolute Error (Jfor - Jfd), step="), 6)
-        self.assertEqual(contents.count("Relative Error (Jfor - Jfd) / Jf"), 6)
+        self.assertEqual(contents.count("fd value:"), 12)
+        self.assertEqual(contents.count("Absolute Error (Jfwd - Jfd), step="), 6)
+        self.assertEqual(contents.count("Relative Error (Jfwd - Jfd) / Jf"), 6)
         self.assertEqual(contents.count("Directional FD Derivative (Jfd), step="), 6)
 
     def test_multi_fd_steps_rev(self):
@@ -2106,7 +2106,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         contents = stream.getvalue()
         nsubjacs = 18
         self.assertEqual(contents.count("Full Model:"), nsubjacs)
-        self.assertEqual(contents.count("Fd Magnitude:"), nsubjacs * 2)
+        self.assertEqual(contents.count("fd value:"), nsubjacs * 4)
         self.assertEqual(contents.count("Absolute Error (Jrev - Jfd), step="), nsubjacs * 2)
         self.assertEqual(contents.count("Relative Error (Jrev - Jfd) / J"), nsubjacs * 2)
         self.assertEqual(contents.count("Raw FD Derivative (Jfd), step="), nsubjacs * 2)
@@ -2119,7 +2119,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
         p.check_totals(step=[1e-6, 1e-7], directional=True, out_stream=stream)
         contents = stream.getvalue()
         self.assertEqual(contents.count("Full Model:"), 6)
-        self.assertEqual(contents.count("Fd Magnitude:"), 12)
+        self.assertEqual(contents.count("fd value:"), 24)
         self.assertEqual(contents.count("Absolute Error ([rev, fd] Dot Product Test), step="), 12)
         self.assertEqual(contents.count("Relative Error ([rev, fd] Dot Product Test) / Jfd, step="), 12)
         self.assertEqual(contents.count("Directional FD Derivative (Jfd) Dot Product, step="), 12)
@@ -2131,12 +2131,12 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
                 p.setup(mode=mode)
                 p.run_model()
                 stream = StringIO()
-                p.check_totals(step=[1e-6, 1e-7], compact_print=True, out_stream=stream)
+                p.check_totals(step=[1e-6, 1e-7], compact_print=True, out_stream=stream, abs_err_tol=2e-6, rel_err_tol=3e-6)
                 contents = stream.getvalue()
                 nsubjacs = 18
                 self.assertEqual(contents.count("step"), 1)
                 # check number of rows/cols
-                self.assertEqual(contents.count("+-------------------------------+------------------+-------------+-------------+-------------+-------------+-------------+--------------------+"), (nsubjacs*2) + 1)
+                self.assertEqual(contents.count("+-----------------------------+----------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+------------+"), (nsubjacs*2) + 1)
 
     def test_multi_cs_steps_compact(self):
         for mode in ('fwd', 'rev'):
@@ -2150,7 +2150,7 @@ class TestCheckTotalsMultipleSteps(unittest.TestCase):
                 nsubjacs = 18
                 self.assertEqual(contents.count("step"), 1)
                 # check number of rows/cols
-                self.assertEqual(contents.count("+-------------------------------+------------------+-------------+-------------+-------------+-------------+-------------+------------+"), (nsubjacs*2) + 1)
+                self.assertEqual(contents.count("+-----------------------------+----------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+------------+"), (nsubjacs*2) + 1)
 
     def test_multi_fd_steps_compact_directional(self):
         expected_divs = {
diff --git a/openmdao/solvers/linear/tests/test_scipy_iter_solver.py b/openmdao/solvers/linear/tests/test_scipy_iter_solver.py
index bdb703b7b..3772c769f 100644
--- a/openmdao/solvers/linear/tests/test_scipy_iter_solver.py
+++ b/openmdao/solvers/linear/tests/test_scipy_iter_solver.py
@@ -353,6 +353,10 @@ class TestScipyKrylovFeature(unittest.TestCase):
         model.linear_solver = om.ScipyKrylov()
 
         prob.setup()
+        
+        # set input values to old defaults to get results to match
+        prob['sub1.q1.b'] = 1.
+        prob['sub2.q2.b'] = 1.
 
         model.sub1.linear_solver = om.DirectSolver()
         model.sub2.linear_solver = om.DirectSolver()
diff --git a/openmdao/test_suite/components/quad_implicit.py b/openmdao/test_suite/components/quad_implicit.py
index 465908d95..21dd5e5ef 100644
--- a/openmdao/test_suite/components/quad_implicit.py
+++ b/openmdao/test_suite/components/quad_implicit.py
@@ -13,9 +13,9 @@ class QuadraticComp(om.ImplicitComponent):
 
     def setup(self):
         self.add_input('a', val=1.)
-        self.add_input('b', val=1.)
+        self.add_input('b', val=5.)
         self.add_input('c', val=1.)
-        self.add_output('x', val=0.)
+        self.add_output('x', val=1.)
 
     def setup_partials(self):
         self.declare_partials(of='x', wrt='*')
diff --git a/openmdao/utils/tests/test_code_utils.py b/openmdao/utils/tests/test_code_utils.py
index bc27d3249..926899f28 100644
--- a/openmdao/utils/tests/test_code_utils.py
+++ b/openmdao/utils/tests/test_code_utils.py
@@ -7,7 +7,8 @@ import pickle
 import numpy as np
 
 from openmdao.utils.code_utils import get_nested_calls, LambdaPickleWrapper, get_return_names, \
-    get_func_graph, get_partials_deps
+    get_func_graph, get_function_deps
+from openmdao.utils.assert_utils import assert_warning
 from openmdao.core.group import Group
 
 
@@ -132,7 +133,7 @@ class TestGraphFunction(unittest.TestCase):
 
         graph = get_func_graph(func)
         self.assertEqual(0, len(graph.edges()))
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [])
 
     def test_unconected_output(self):
@@ -144,7 +145,7 @@ class TestGraphFunction(unittest.TestCase):
         self.assertIn(('a', 'c'), graph.edges())
         self.assertIn(('b', 'c'), graph.edges())
         self.assertEqual(0, len(graph.edges('out1')))
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('c', 'a'), ('c', 'b')])
 
     def test_complicated(self):
@@ -166,7 +167,7 @@ class TestGraphFunction(unittest.TestCase):
         self.assertEqual(sorted(graph.edges()), sorted(expected))
         self.assertEqual(sorted(graph.nodes()), sorted(['a', 'b', 'c', 'd', 'e', 'f', 'x']))
 
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('e', 'a'), ('e', 'b'), ('f', 'a'), ('f', 'b')])
 
     def test_multiple_returns(self):
@@ -176,7 +177,7 @@ class TestGraphFunction(unittest.TestCase):
         graph = get_func_graph(func)
         self.assertIn(('a', 'out0'), graph.edges())
         self.assertIn(('b', 'out1'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('out0', 'a'), ('out1', 'b')])
 
     def test_conditional_return(self):
@@ -188,7 +189,7 @@ class TestGraphFunction(unittest.TestCase):
 
         graph = get_func_graph(func)
         self.assertIn(('a', 'out0'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('out0', 'a')])
 
     def test_no_return(self):
@@ -197,7 +198,7 @@ class TestGraphFunction(unittest.TestCase):
 
         graph = get_func_graph(func)
         self.assertIn(('a', 'b'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [])
 
     def test_function_with_args(self):
@@ -208,7 +209,7 @@ class TestGraphFunction(unittest.TestCase):
         graph = get_func_graph(func)
         self.assertIn(('a', 'c'), graph.edges())
         self.assertIn(('b', 'c'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('c', 'a'), ('c', 'b')])
 
     def test_nested_function(self):
@@ -217,9 +218,9 @@ class TestGraphFunction(unittest.TestCase):
                 return b + 1
             return nested(a)
 
-        with self.assertRaises(RuntimeError) as cm:
+        msg = "Can't determine function graph for function 'func' due to: Function contains nested functions, which are not supported."
+        with assert_warning(UserWarning, msg):
             get_func_graph(func)
-        self.assertEqual(str(cm.exception), "Function contains nested functions, which are not supported.")
 
     def test_function_with_tuple_return(self):
         def func(a, b):
@@ -228,7 +229,7 @@ class TestGraphFunction(unittest.TestCase):
         graph = get_func_graph(func)
         self.assertIn(('a', 'out0'), graph.edges())
         self.assertIn(('b', 'out1'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('out0', 'a'), ('out1', 'b')])
 
     def test_function_with_mixed_return(self):
@@ -240,7 +241,7 @@ class TestGraphFunction(unittest.TestCase):
 
         graph = get_func_graph(func)
         self.assertIn(('a', 'out0'), graph.edges())
-        partials = sorted(get_partials_deps(func))
+        partials = sorted(get_function_deps(func))
         self.assertEqual(sorted(partials), [('out0', 'a')])