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', 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’.

formstring

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

step_calcstring

Step type for finite difference, can be ‘abs’ for absolute’, or ‘rel’ for relative. Default is ‘abs’.

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’];

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

Jacobian for the three different methods of computation.

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)}}}