Computing analytic derivatives
Contents
Computing analytic derivatives¶
Level: Beginner-Intermediate
Topics: Model differentiation
Here you will compute and provide the analytic derivatives for an OpenMDAO component, then check the results using check_partials
.
Definition of the first system¶
Here is the simple component. It has two inputs (x is an array, y is a scalar) and one output (z is an array). Your goal is to add the compute_partials()
method to the component so that your analytic derivatives match the finite difference checks. I’d suggest trying to differentiate these expressions by hand, but feel free to use a symbolic solver (like Wolfram Alpha or Mathematica) if you prefer.
Because we are using finite difference to check the partial derivatives, you should expect your analytic derivatives to match to about 1.e-6. This is due to the approximation error introduced by FD.
Click here to reveal a small hint
The shapes of your computed Jacobians should be 2x3 and 2x1. What do these rows and columns correspond to? Where do the shapes come from? How can you fill them in?
import numpy as np
import openmdao.api as om
class SimpleComponent(om.ExplicitComponent):
def setup(self):
self.add_input('x', shape=3)
self.add_input('y')
self.add_output('z', shape=2)
self.declare_partials('z', 'x')
self.declare_partials('z', 'y')
def compute(self, inputs, outputs):
x = inputs['x']
y = inputs['y']
outputs['z'][0] = np.sum(x) * y
outputs['z'][1] = x[2] - y**2 * x[0]
prob = om.Problem()
model = prob.model
model.add_subsystem('comp', SimpleComponent(), promotes=['*'])
prob.setup()
prob.set_val('x', [2.2, 5.3, 4.1])
prob.set_val('y', [1.5])
prob.run_model()
data = prob.check_partials()
---------------------------------
Component: SimpleComponent 'comp'
---------------------------------
comp: 'z' wrt 'x'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 3.579455e+00 (fd:forward)
Absolute Error (Jan - Jfd) : 3.579455e+00 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[ 1.5 1.5 1.5 ]
[-2.25 0. 1. ]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
comp: 'z' wrt 'y'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.334616e+01 (fd:forward)
Absolute Error (Jan - Jfd) : 1.334616e+01 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0.]
[0.]]
Raw FD Derivative (Jfd)
[[11.6 ]
[-6.6000022]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Another simple case – derivatives of a fluid pump model¶
This example component is a real engineering model of a simple pump, taken from OpenConcept. The component looks a bit more complicated than the previous one, but that’s because there are multiple inputs and outputs, each corresponding to a meaningful engineering value. Additionally, the variable naming and descriptions are much more explicit here.
Again, please write a compute_partials()
method that correctly computes the derivatives. Here we are checking against a complex-step approximation so we should be able to match it to machine precision.
Click here to reveal a small hint
Pay close attention to the shape of the Jacobians here and where the non-zero derivatives should be.
class SimplePump(om.ExplicitComponent):
def initialize(self):
self.options.declare("num_nodes", default=1, desc="Number of flight/control conditions")
self.options.declare("efficiency", default=0.35, desc="Efficiency (dimensionless)")
self.options.declare("weight_base", default=0.0, desc="Pump base weight")
self.options.declare("weight_inc", default=1 / 450, desc="Incremental pump weight (kg/W)")
def setup(self):
nn = self.options["num_nodes"]
weight_inc = self.options["weight_inc"]
self.add_input("power_rating", units="W", desc="Pump electrical power rating")
self.add_input("mdot_coolant", units="kg/s", desc="Coolant mass flow rate", val=np.ones((nn,)))
self.add_input("delta_p", units="Pa", desc="Pump pressure rise", val=np.ones((nn,)))
self.add_input("rho_coolant", units="kg/m**3", desc="Coolant density", val=np.ones((nn,)))
self.add_output("elec_load", units="W", desc="Pump electrical load", val=np.ones((nn,)))
self.add_output("component_weight", units="kg", desc="Pump weight")
self.add_output("component_sizing_margin", units=None, val=np.ones((nn,)), desc="Comp sizing margin")
self.declare_partials(
["elec_load", "component_sizing_margin"],
["rho_coolant", "delta_p", "mdot_coolant"],
rows=np.arange(nn),
cols=np.arange(nn),
)
self.declare_partials(["component_sizing_margin"], ["power_rating"], rows=np.arange(nn), cols=np.zeros(nn))
self.declare_partials(["component_weight"], ["power_rating"], val=weight_inc)
def compute(self, inputs, outputs):
eta = self.options["efficiency"]
weight_inc = self.options["weight_inc"]
weight_base = self.options["weight_base"]
outputs["component_weight"] = weight_base + weight_inc * inputs["power_rating"]
# compute the fluid power
vol_flow_rate = inputs["mdot_coolant"] / inputs["rho_coolant"] # m3/s
fluid_power = vol_flow_rate * inputs["delta_p"]
outputs["elec_load"] = fluid_power / eta
outputs["component_sizing_margin"] = outputs["elec_load"] / inputs["power_rating"]
prob = om.Problem()
nn = 3
rho_coolant = 1020 * np.ones(nn)
mdot_coolant = np.linspace(0.6, 1.2, nn)
delta_p = np.linspace(2e4, 4e4, nn)
power_rating = 1000
prob.model.add_subsystem("pump", SimplePump(num_nodes=nn), promotes_inputs=["*"])
prob.setup(force_alloc_complex=True)
prob.set_val("power_rating", power_rating, units="W")
prob.set_val("delta_p", delta_p, units="Pa")
prob.set_val("mdot_coolant", mdot_coolant, units="kg/s")
prob.set_val("rho_coolant", rho_coolant, units="kg/m**3")
prob.run_model()
data = prob.check_partials(method='cs')
----------------------------
Component: SimplePump 'pump'
----------------------------
pump: 'component_sizing_margin' wrt 'delta_p'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 4.525349e-06 (cs:None)
Absolute Error (Jan - Jfd) : 4.525349e-06 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[1.68067227e-06 0.00000000e+00 0.00000000e+00]
[0.00000000e+00 2.52100840e-06 0.00000000e+00]
[0.00000000e+00 0.00000000e+00 3.36134454e-06]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'component_sizing_margin' wrt 'mdot_coolant'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.508450e-01 (cs:None)
Absolute Error (Jan - Jfd) : 1.508450e-01 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[0.05602241 0. 0. ]
[0. 0.08403361 0. ]
[0. 0. 0.11204482]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'component_sizing_margin' wrt 'power_rating'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.578848e-04 (cs:None)
Absolute Error (Jan - Jfd) : 1.578848e-04 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0.]
[0.]
[0.]]
Raw FD Derivative (Jfd)
[[-3.36134454e-05]
[-7.56302521e-05]
[-1.34453782e-04]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'component_sizing_margin' wrt 'rho_coolant'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.547890e-04 (cs:None)
Absolute Error (Jan - Jfd) : 1.547890e-04 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[-3.29543582e-05 0.00000000e+00 0.00000000e+00]
[ 0.00000000e+00 -7.41473060e-05 0.00000000e+00]
[ 0.00000000e+00 0.00000000e+00 -1.31817433e-04]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'component_weight' wrt 'power_rating'
Analytic Magnitude: 2.222222e-03
Fd Magnitude: 2.222222e-03 (cs:None)
Absolute Error (Jan - Jfd) : 0.000000e+00
Relative Error (Jan - Jfd) / Jfd : 0.000000e+00
Raw Analytic Derivative (Jfor)
[[0.00222222]]
Raw FD Derivative (Jfd)
[[0.00222222]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'elec_load' wrt 'delta_p'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 4.525349e-03 (cs:None)
Absolute Error (Jan - Jfd) : 4.525349e-03 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[0.00168067 0. 0. ]
[0. 0.00252101 0. ]
[0. 0. 0.00336134]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'elec_load' wrt 'mdot_coolant'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.508450e+02 (cs:None)
Absolute Error (Jan - Jfd) : 1.508450e+02 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[ 56.02240896 0. 0. ]
[ 0. 84.03361345 0. ]
[ 0. 0. 112.04481793]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
pump: 'elec_load' wrt 'rho_coolant'
Analytic Magnitude: 0.000000e+00
Fd Magnitude: 1.547890e-01 (cs:None)
Absolute Error (Jan - Jfd) : 1.547890e-01 *
Relative Error (Jan - Jfd) / Jfd : 1.000000e+00 *
Raw Analytic Derivative (Jfor)
[[0. 0. 0.]
[0. 0. 0.]
[0. 0. 0.]]
Raw FD Derivative (Jfd)
[[-0.03295436 0. 0. ]
[ 0. -0.07414731 0. ]
[ 0. 0. -0.13181743]]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -