Checking Partial Derivatives with Finite Difference#

In addition to using approximations to estimate partial derivatives, you can also use approximations to check your implementations of the partial derivatives for a component.

Problem has a method, check_partials, that checks partial derivatives comprehensively for all Components in your model. To do this check, the framework compares the analytic result against a finite difference result. This means that the check_partials function can be quite computationally expensive. So use it to check your work, but don’t leave the call in your production run scripts.

Problem.check_partials(out_stream=DEFAULT_OUT_STREAM, includes=None, excludes=None, compact_print=False, abs_err_tol=1e-06, rel_err_tol=1e-06, method='fd', step=None, form='forward', step_calc='abs', minimum_step=1e-12, force_dense=True, show_only_incorrect=False)[source]

Check partial derivatives comprehensively for all components in your model.

Parameters:
out_streamfile-like object

Where to send human readable output. By default it goes to stdout. Set to None to suppress.

includesNone or list_like

List of glob patterns for pathnames to include in the check. Default is None, which includes all components in the model.

excludesNone or list_like

List of glob patterns for pathnames to exclude from the check. Default is None, which excludes nothing.

compact_printbool

Set to True to just print the essentials, one line per input-output pair.

abs_err_tolfloat

Threshold value for absolute error. Errors about this value will have a ‘*’ displayed next to them in output, making them easy to search for. Default is 1.0E-6.

rel_err_tolfloat

Threshold value for relative error. Errors about this value will have a ‘*’ displayed next to them in output, making them easy to search for. Note at times there may be a significant relative error due to a minor absolute error. Default is 1.0E-6.

methodstr

Method, ‘fd’ for finite difference or ‘cs’ for complex step. Default is ‘fd’.

stepfloat

Step size for approximation. Default is None, which means 1e-6 for ‘fd’ and 1e-40 for ‘cs’.

formstr

Form for finite difference, can be ‘forward’, ‘backward’, or ‘central’. Default ‘forward’.

step_calcstr

Step type for computing the size of the finite difference step. It can be ‘abs’ for absolute, ‘rel_avg’ for a size relative to the absolute value of the vector input, or ‘rel_element’ for a size relative to each value in the vector input. In addition, it can be ‘rel_legacy’ for a size relative to the norm of the vector. For backwards compatibilty, it can be ‘rel’, which is now equivalent to ‘rel_avg’. Defaults to None, in which case the approximation method provides its default value.

minimum_stepfloat

Minimum step size allowed when using one of the relative step_calc options.

force_densebool

If True, analytic derivatives will be coerced into arrays. Default is True.

show_only_incorrectbool, optional

Set to True if output should print only the subjacs found to be incorrect.

Returns:
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 boolean ‘rank_inconsistent’ indicates if the derivative wrt a serial variable is inconsistent across MPI ranks.

Note

For components that provide their partials directly (from the compute_partials or linearize methods, only information about the forward derivatives are shown. For components that are matrix-free, both forward and reverse derivative information is shown.

Implicit components are matrix-free if they define a apply_linear method. Explicit components are matrix-free if they define either compute_jacvec_product or compute_multi_jacvec_product methods.

Basic Usage#

When the difference between the FD derivative and the provided derivative is larger (in either a relative or absolute sense) than 1e-6, that partial derivative will be marked with a ‘*’.

import numpy as np

import openmdao.api as om

class MyComp(om.ExplicitComponent):
    def setup(self):
        self.add_input('x1', 3.0)
        self.add_input('x2', 5.0)

        self.add_output('y', 5.5)

        self.declare_partials(of='*', wrt='*')

    def compute(self, inputs, outputs):
        outputs['y'] = 3.0*inputs['x1'] + 4.0*inputs['x2']

    def compute_partials(self, inputs, partials):
        """Intentionally incorrect derivative."""
        J = partials
        J['y', 'x1'] = np.array([4.0])
        J['y', 'x2'] = np.array([40])

prob = om.Problem()

prob.model.add_subsystem('comp', MyComp())

prob.set_solver_print(level=0)

prob.setup()
prob.run_model()

data = prob.check_partials()
------------------------
Component: MyComp 'comp'
------------------------

  comp: 'y' wrt 'x1'
    Analytic Magnitude: 4.000000e+00
          Fd Magnitude: 3.000000e+00 (fd:forward)
    Absolute Error (Jan - Jfd) : 1.000000e+00 *

    Relative Error (Jan - Jfd) / Jfd : 3.333333e-01 *

    Raw Analytic Derivative (Jfor)
[[4.]]

    Raw FD Derivative (Jfd)
[[3.]]
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  comp: 'y' wrt 'x2'
    Analytic Magnitude: 4.000000e+01
          Fd Magnitude: 4.000000e+00 (fd:forward)
    Absolute Error (Jan - Jfd) : 3.600000e+01 *

    Relative Error (Jan - Jfd) / Jfd : 9.000000e+00 *

    Raw Analytic Derivative (Jfor)
[[40.]]

    Raw FD Derivative (Jfd)
[[4.]]
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
x1_error = data['comp']['y', 'x1']['abs error']

print(x1_error.forward)
1.0000000004688445
x2_error = data['comp']['y', 'x2']['rel error']

print(x2_error.forward)
8.99999999860222

Turn off standard output and just view the derivatives in the return:

import numpy as np

import openmdao.api as om

class MyComp(om.ExplicitComponent):
    def setup(self):
        self.add_input('x1', 3.0)
        self.add_input('x2', 5.0)

        self.add_output('y', 5.5)

        self.declare_partials(of='*', wrt='*')

    def compute(self, inputs, outputs):
        outputs['y'] = 3.0*inputs['x1'] + 4.0*inputs['x2']

    def compute_partials(self, inputs, partials):
        """Intentionally incorrect derivative."""
        J = partials
        J['y', 'x1'] = np.array([4.0])
        J['y', 'x2'] = np.array([40])

prob = om.Problem()

prob.model.add_subsystem('comp', MyComp())

prob.set_solver_print(level=0)

prob.setup()
prob.run_model()
data = prob.check_partials(out_stream=None, compact_print=True)
print(data)
{'comp': {('y', 'x1'): {'J_fwd': array([[4.]]), 'J_fd': array([[3.]]), 'abs error': ErrorTuple(forward=1.0000000004688445, reverse=None, forward_reverse=None), 'magnitude': MagnitudeTuple(forward=4.0, reverse=None, fd=2.9999999995311555), 'rel error': ErrorTuple(forward=0.3333333335417087, reverse=None, forward_reverse=None)}, ('y', 'x2'): {'J_fwd': array([[40.]]), 'J_fd': array([[4.]]), 'abs error': ErrorTuple(forward=35.99999999944089, reverse=None, forward_reverse=None), 'magnitude': MagnitudeTuple(forward=40.0, reverse=None, fd=4.000000000559112), 'rel error': ErrorTuple(forward=8.99999999860222, reverse=None, forward_reverse=None)}}}

Show Only Incorrect Printing Option#

If you are only concerned with seeing the partials calculations that are incorrect, set show_only_incorrect to True. This applies to both compact_print equal to True and False.

import numpy as np
import openmdao.api as om

class MyCompGoodPartials(om.ExplicitComponent):
    def setup(self):
        self.add_input('x1', 3.0)
        self.add_input('x2', 5.0)
        self.add_output('y', 5.5)
        self.declare_partials(of='*', wrt='*')

    def compute(self, inputs, outputs):
        outputs['y'] = 3.0 * inputs['x1'] + 4.0 * inputs['x2']

    def compute_partials(self, inputs, partials):
        """Correct derivative."""
        J = partials
        J['y', 'x1'] = np.array([3.0])
        J['y', 'x2'] = np.array([4.0])

class MyCompBadPartials(om.ExplicitComponent):
    def setup(self):
        self.add_input('y1', 3.0)
        self.add_input('y2', 5.0)
        self.add_output('z', 5.5)
        self.declare_partials(of='*', wrt='*')

    def compute(self, inputs, outputs):
        outputs['z'] = 3.0 * inputs['y1'] + 4.0 * inputs['y2']

    def compute_partials(self, inputs, partials):
        """Intentionally incorrect derivative."""
        J = partials
        J['z', 'y1'] = np.array([33.0])
        J['z', 'y2'] = np.array([40.0])

prob = om.Problem()
prob.model.add_subsystem('good', MyCompGoodPartials())
prob.model.add_subsystem('bad', MyCompBadPartials())
prob.model.connect('good.y', 'bad.y1')

prob.set_solver_print(level=0)
prob.setup()
prob.run_model()

prob.check_partials(compact_print=True, show_only_incorrect=True)
prob.check_partials(compact_print=False, show_only_incorrect=True)
** Only writing information about components with incorrect Jacobians **

----------------------------------
Component: MyCompBadPartials 'bad'
----------------------------------

'<output>' wrt '<variable>' | calc mag.  | check mag. | a(cal-chk) | r(cal-chk)
-------------------------------------------------------------------------------

'z'        wrt 'y1'         | 3.3000e+01 | 3.0000e+00 | 3.0000e+01 | 1.0000e+01 >ABS_TOL >REL_TOL
'z'        wrt 'y2'         | 4.0000e+01 | 4.0000e+00 | 3.6000e+01 | 9.0000e+00 >ABS_TOL >REL_TOL

#################################################################
Sub Jacobian with Largest Relative Error: MyCompBadPartials 'bad'
#################################################################

'<output>' wrt '<variable>' | calc mag.  | check mag. | a(cal-chk) | r(cal-chk)
-------------------------------------------------------------------------------
'z'        wrt 'y1'         | 3.3000e+01 | 3.0000e+00 | 3.0000e+01 | 1.0000e+01

** Only writing information about components with incorrect Jacobians **

----------------------------------
Component: MyCompBadPartials 'bad'
----------------------------------

  bad: 'z' wrt 'y1'
    Analytic Magnitude: 3.300000e+01
          Fd Magnitude: 3.000000e+00 (fd:forward)
    Absolute Error (Jan - Jfd) : 3.000000e+01 *

    Relative Error (Jan - Jfd) / Jfd : 1.000000e+01 *

    Raw Analytic Derivative (Jfor)
[[33.]]

    Raw FD Derivative (Jfd)
[[3.00000001]]
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  bad: 'z' wrt 'y2'
    Analytic Magnitude: 4.000000e+01
          Fd Magnitude: 4.000000e+00 (fd:forward)
    Absolute Error (Jan - Jfd) : 3.600000e+01 *

    Relative Error (Jan - Jfd) / Jfd : 9.000000e+00 *

    Raw Analytic Derivative (Jfor)
[[40.]]

    Raw FD Derivative (Jfd)
[[4.]]
 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{'good': {('y', 'x1'): {'J_fwd': array([[3.]]),
   'J_fd': array([[3.]]),
   'abs error': ErrorTuple(forward=4.688445187639445e-10, reverse=None, forward_reverse=None),
   'magnitude': MagnitudeTuple(forward=3.0, reverse=None, fd=2.9999999995311555),
   'rel error': ErrorTuple(forward=1.5628150627907208e-10, reverse=None, forward_reverse=None)},
  ('y', 'x2'): {'J_fwd': array([[4.]]),
   'J_fd': array([[4.]]),
   'abs error': ErrorTuple(forward=5.591118679149076e-10, reverse=None, forward_reverse=None),
   'magnitude': MagnitudeTuple(forward=4.0, reverse=None, fd=4.000000000559112),
   'rel error': ErrorTuple(forward=1.3977796695918903e-10, reverse=None, forward_reverse=None)}},
 'bad': {('z', 'y1'): {'J_fwd': array([[33.]]),
   'J_fd': array([[3.00000001]]),
   'abs error': ErrorTuple(forward=29.999999993363417, reverse=None, forward_reverse=None),
   'magnitude': MagnitudeTuple(forward=33.0, reverse=None, fd=3.000000006636583),
   'rel error': ErrorTuple(forward=9.999999975665864, reverse=None, forward_reverse=None)},
  ('z', 'y2'): {'J_fwd': array([[40.]]),
   'J_fd': array([[4.]]),
   'abs error': ErrorTuple(forward=35.999999995888174, reverse=None, forward_reverse=None),
   'magnitude': MagnitudeTuple(forward=40.0, reverse=None, fd=4.0000000041118255),
   'rel error': ErrorTuple(forward=8.999999989720436, reverse=None, forward_reverse=None)}}}