Inside OpenMDAO
In one minuteWrap the flow discipline as a matrix-free OpenMDAO component, in reduced space or at residual level, and let the framework assemble total derivatives in forward or reverse mode. No optimizer involved.
import numpy as np
import openmdao.api as om
from gyre.cases import Case, preset
from gyre.integrations.openmdao import GyreFlowComp
flow = Case(preset("naca0012_euler", **{"solver.tol_rel": 1e-12})).discipline
prob = om.Problem()
prob.model.add_subsystem(
"flow", GyreFlowComp(discipline=flow, inputs=("alpha", "mach"), outputs=("cl", "cd")),
promotes=["*"])
prob.setup(mode="rev")
prob.set_val("alpha", np.radians(2.0))
prob.run_model()
totals = prob.compute_totals(of=["cl", "cd"], wrt=["alpha", "mach", "x_surface"])
examples/openmdao_wrapper.py is a runnable script built on this adapter
(.venv/bin/python examples/openmdao_wrapper.py).
The adapter needs OpenMDAO, which the mdo extra installs (pip install -e ".[mdo]"); the
all extra, which make bootstrap installs, includes it.
| OpenMDAO calls | Adapter does |
|---|---|
compute |
set_inputs, warm-started solve (both skipped when the discipline already holds the converged solution for these inputs), outputs; raises om.AnalysisError if the flow does not converge or the inputs are outside the range of the flow model; after a failed point the next call always solves again, also for inputs that had converged before |
compute_jacvec_product(mode="rev") |
one adjoint_product for all seeded outputs |
compute_jacvec_product(mode="fwd") |
one tangent_product for the perturbed inputs |
Options: inputs (scalar flow parameters), outputs (scalar functionals), shape=True
(expose x_surface), loads=True (expose f_surface), linear_tol.
from gyre.integrations.openmdao import GyreFlowGroup
prob.model.add_subsystem(
"flow", GyreFlowGroup(discipline=flow, inputs=("alpha", "mach"), outputs=("cl", "cd"),
loads=True),
promotes=["*"])
GyreFlowGroup contains two components. The implicit one has the flow state as its output:
OpenMDAO asks it for the residual (apply_nonlinear), for products with the linearised
residual in either mode (apply_linear) and for linear solves (solve_linear), each about
the state and inputs OpenMDAO holds; its solve_nonlinear is the flow solve. The second
component evaluates the functionals and the load field from inputs and state. The framework
then owns the coupled linear system, which is what a hierarchical or monolithic coupled
adjoint needs.
| OpenMDAO calls | Adapter does |
|---|---|
solve_nonlinear |
solve_nonlinear(inputs) of the discipline; the state becomes the component’s output |
apply_nonlinear |
apply_nonlinear(inputs, state): the weak-form residual at OpenMDAO’s values |
apply_linear (fwd / rev) |
jvp / vjp about that point |
solve_linear (fwd / rev) |
solve_linear; a solve that misses linear_tol raises om.AnalysisError |
| quantity | value |
|---|---|
| check_partials flow.solver: 4 (of, wrt) pairs, directional | 3.0315e-09 |
| check_partials flow.functionals: 12 (of, wrt) pairs, directional | 5.6471e-09 |
| totals through the implicit component (reverse) vs FlowDiscipline.totals | 4.1434e-16 |
| coupled dcl/dα, OpenMDAO reverse mode | 7.54117 |
| coupled dcl/dα, OpenMDAO forward mode | 7.54117 |
| coupled dcl/dα, central difference of re-converged coupled analyses | 7.54117 |
| relative difference, reverse mode vs finite difference | 1.2307e-07 |
| dcl/dα of the rigid aerofoil | 7.51815 |
| largest deflection [chords] | 0.00555541 |
gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build
The table is from the discipline contract study:
OpenMDAO’s own check_partials of both components, totals through the implicit component
against the discipline’s adjoint totals, and the coupled derivative of a flow–structure
cycle built on the group (examples/openmdao_wrapper.py) against a finite difference of
re-converged coupled analyses.
tests/test_integrations.py::test_openmdao_component_totals checks that the framework’s
reverse-mode totals equal Gyre’s own adjoint totals (including the shape sensitivity field),
that forward mode agrees with reverse mode, and that a finite difference through the
framework’s model evaluation matches.
tests/test_integrations.py::test_openmdao_component_solves_again_after_a_failed_point checks
that a point that does not converge raises AnalysisError and that a return to the previous,
converged inputs solves again instead of reusing the cached point. For the implicit form,
test_openmdao_implicit_component_passes_check_partials,
test_openmdao_implicit_group_totals_match_the_reduced_space and
test_openmdao_coupled_adjoint_through_the_implicit_component check the partial products,
the totals mode for mode against the discipline’s own, and the coupled adjoint. The tests skip themselves when OpenMDAO is not installed;
make bootstrap and the hosted CI workflow install all, so it runs there.
With loads=True and shape=True the component exposes the two coupling fields. Connected to
a structural component in an OpenMDAO group with a nonlinear block solver, the framework’s
linear solver assembles the coupled adjoint from the same compute_jacvec_product calls —
the framework-owned version of the hand-written coupled adjoint.