Approximating SemiTotal Derivatives¶
There are times where it makes sense to approximate the derivatives for an entire group in one shot. You can turn on
the approximation by calling approx_totals
on any Group
.

Group.
approx_totals
(method='fd', step=None, form=None, step_calc=None)[source] Approximate derivatives for a Group using the specified approximation method.
Parameters:  method : str
The type of approximation that should be used. Valid options include: ‘fd’: Finite Difference, ‘cs’: Complex Step
 step : float
Step size for approximation. Defaults to None, in which case, the approximation method provides its default value.
 form : string
Form for finite difference, can be ‘forward’, ‘backward’, or ‘central’. Defaults to None, in which case, the approximation method provides its default value.
 step_calc : string
Step type for finite difference, can be ‘abs’ for absolute’, or ‘rel’ for relative. Defaults to None, in which case, the approximation method provides its default value.
The default method for approximating semitotal derivatives is the finite difference method. When you call the approx_totals
method on a group, OpenMDAO will
generate an approximate Jacobian for the entire group during the linearization step before derivatives are calculated. OpenMDAO automatically figures out
which inputs and output pairs are needed in this Jacobian. When solve_linear
is called from any system that contains this system, the approximated Jacobian
is used for the derivatives in this system.
The derivatives approximated in this manner are total derivatives of outputs of the group with respect to inputs. If any components in the group contain
implicit states, then you must have an appropriate solver (such as NewtonSolver
) inside the group to solve the implicit relationships.
Here is a classic example of where you might use an approximation like finite difference. In this example, we could just approximate the partials on components CompOne and CompTwo separately. However, CompTwo has a vector input that is 25 wide, so it would require 25 separate executions under finite difference. If we instead approximate the total derivatives on the whole group, we only have one input, so just one extra execution.
import numpy as np
from openmdao.api import Problem, Group, IndepVarComp, ScipyKrylov, ExplicitComponent
class CompOne(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_output('y', val=np.zeros(25))
self._exec_count = 0
def compute(self, inputs, outputs):
x = inputs['x']
outputs['y'] = np.arange(25) * x
self._exec_count += 1
class CompTwo(ExplicitComponent):
def setup(self):
self.add_input('y', val=np.zeros(25))
self.add_output('z', val=0.0)
self._exec_count = 0
def compute(self, inputs, outputs):
y = inputs['y']
outputs['z'] = np.sum(y)
self._exec_count += 1
prob = Problem()
model = prob.model = Group()
model.add_subsystem('p1', IndepVarComp('x', 0.0), promotes=['x'])
model.add_subsystem('comp1', CompOne(), promotes=['x', 'y'])
comp2 = model.add_subsystem('comp2', CompTwo(), promotes=['y', 'z'])
model.linear_solver = ScipyKrylov()
model.approx_totals()
prob.setup()
prob.run_model()
of = ['z']
wrt = ['x']
derivs = prob.compute_totals(of=of, wrt=wrt)
print(derivs['z', 'x'])
[[ 300.]]
print(comp2._exec_count)
2
The same arguments are used for both partial and total derivative approximation specifications. Here we set the finite difference step size, the form to central differences, and the step_calc to relative instead of absolute.
import numpy as np
from openmdao.api import Problem, Group, IndepVarComp, ScipyKrylov, ExplicitComponent
class CompOne(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_output('y', val=np.zeros(25))
self._exec_count = 0
def compute(self, inputs, outputs):
x = inputs['x']
outputs['y'] = np.arange(25) * x
self._exec_count += 1
class CompTwo(ExplicitComponent):
def setup(self):
self.add_input('y', val=np.zeros(25))
self.add_output('z', val=0.0)
self._exec_count = 0
def compute(self, inputs, outputs):
y = inputs['y']
outputs['z'] = np.sum(y)
self._exec_count += 1
prob = Problem()
model = prob.model = Group()
model.add_subsystem('p1', IndepVarComp('x', 1.0), promotes=['x'])
model.add_subsystem('comp1', CompOne(), promotes=['x', 'y'])
comp2 = model.add_subsystem('comp2', CompTwo(), promotes=['y', 'z'])
model.linear_solver = ScipyKrylov()
model.approx_totals(method='fd', step=1e7, form='central', step_calc='rel')
prob.setup()
prob.run_model()
of = ['z']
wrt = ['x']
derivs = prob.compute_totals(of=of, wrt=wrt)
print(derivs['z', 'x'])
[[ 300.00000048]]
Complex Step¶
You can also complex step your model or group, though there are some important restrictions.
 All components must support complex calculations in solve_nonlinear:
 Under complex step, a component’s inputs are complex, all stages of the calculation will operate on complex inputs to produce
complex outputs, and the final value placed into outputs is complex. Most Python functions already support complex numbers, so pure
Python components will generally satisfy this requirement. Take care with functions like
abs
, which effectively squelches the complex part of the argument.  Solvers like Newton that require gradients are not supported:
 Complex stepping a model causes it to run with complex inputs. When there is a nonlinear solver at some level, the solver must be
able to converge. Some solvers such as
NonlinearBlockGS
can handle this. However, the Newton solver must linearize and initiate a gradient solve about a complex point. This is not possible to do at present (though we are working on some ideas to make this work.)
import numpy as np
from openmdao.api import Problem, Group, IndepVarComp, ScipyKrylov, ExplicitComponent
class CompOne(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_output('y', val=np.zeros(25))
self._exec_count = 0
def compute(self, inputs, outputs):
x = inputs['x']
outputs['y'] = np.arange(25) * x
self._exec_count += 1
class CompTwo(ExplicitComponent):
def setup(self):
self.add_input('y', val=np.zeros(25))
self.add_output('z', val=0.0)
self._exec_count = 0
def compute(self, inputs, outputs):
y = inputs['y']
outputs['z'] = np.sum(y)
self._exec_count += 1
prob = Problem()
model = prob.model = Group()
model.add_subsystem('p1', IndepVarComp('x', 0.0), promotes=['x'])
model.add_subsystem('comp1', CompOne(), promotes=['x', 'y'])
comp2 = model.add_subsystem('comp2', CompTwo(), promotes=['y', 'z'])
model.linear_solver = ScipyKrylov()
model.approx_totals(method='cs')
prob.setup()
prob.run_model()
of = ['z']
wrt = ['x']
derivs = prob.compute_totals(of=of, wrt=wrt)
print(derivs['z', 'x'])
[[ 300.]]