Gyredocs
Depth Practice
API

Gyre/Examples

Coupled aero-structural adjoint

In one minuteA flow coupled to an elastic skin, with the coupled derivative assembled outside Gyre from loads, displacements and reverse total products — the pattern an MDO framework uses, in sixty lines.

Written at L2 Practice goes to L3 MechanicsMDO integrators 538 words Markdown

This example exists to show an interface, not a structural model. The structure is a toy — a smooth linear compliance mapping surface loads to surface displacements — but the coupling is real: loads deform the surface, the deformed surface changes the loads.

PassedExample: coupled aero-structural adjoint2 of 2 checks passed · 823 s · quick suite

The flow is coupled to a linear elastic skin (loads in, displacements out) by a fixed-point iteration written outside Gyre. The coupled derivative of lift with respect to angle of attack is assembled from Gyre's reduced reverse products and compared with a finite difference of the fully re-converged coupled system.

  • coupled adjoint vs finite difference (rel.)9.63e-08expected < 1e-5pass
  • coupling is active: max deflection [chords]0.005559expected > 1e-5pass

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

Coupled derivativeExample: coupled aero-structural adjoint
cl (flexible)max deflection [c]dcl/dα coupled adjointdcl/dα finite differencerelative differencedcl/dα frozen shapeaeroelastic effect [%]
0.2796980.005559197.765027.765029.6349e-087.715740.638621

gyre 0.4.0 · 39979fcf90 · · cpu · quick suite · record (JSON)Same code as this build

The derivative of lift with respect to angle of attack for the coupled system differs from the frozen-shape value by the aeroelastic effect in the last column. The coupled adjoint reproduces a finite difference of the fully re-converged coupled system to the relative difference shown — using only operations any MDO framework can call.

The coupled analysis#

python
def coupled_solve(flow, C, tol=1e-9, relax=1.0, max_iter=40):
    d = np.asarray(flow.d_surface).copy()
    for it in range(max_iter):
        flow.set_inputs(x_surface=d)                 # displacements in
        converge(flow)                               # warm-started flow solve; raises if it does not converge
        f = flow.outputs(("f_surface",))["f_surface"]    # consistent nodal loads out
        d_new = (C @ f.reshape(-1)).reshape(d.shape)     # the "structure"
        ...

converge is a small helper of the example: it calls flow.solve() and raises if the history says the solve did not converge. A failed solve leaves the discipline’s last converged state in place, the flow of the previous shape, so loads read after it would belong to another design.

The coupled adjoint#

python
tot = flow.totals(of=("cl",), wrt=("alpha", "x_surface"))["derivatives"]["cl"]
dJ_dd = tot["x_surface"].reshape(-1)
lam = dJ_dd.copy()
for _ in range(max_iter):                            # transposed fixed-point iteration
    seed = (C.T @ lam).reshape(shape)
    back = flow.adjoint_product({"f_surface": seed}, wrt=("x_surface",))["derivatives"]
    lam_new = dJ_dd + back["x_surface"].reshape(-1)
    ...
cross = flow.adjoint_product({"f_surface": (C.T @ lam).reshape(shape)}, wrt=("alpha",))
dJ_dalpha = tot["alpha"] + cross["derivatives"]["alpha"]

Three things, all part of the discipline contract:

  1. f_surface, loads that are conservative and work-conjugate to x_surface;
  2. totals(…, wrt=["alpha", "x_surface"]), the reduced gradient of a scalar;
  3. adjoint_product({"f_surface": seed}, …), the reverse product (df/dx)Tseed(\mathrm d\mathbf f/\mathrm d\mathbf x)^{\mathsf T}\mathbf{seed} — one flow-adjoint solve regardless of the number of load components.

The loop, the structure and the choice of solver for the coupled system live outside. The derivation is in Adjoints and sensitivities; the full script is examples/aerostructural_coupling.py, and the same logic is exercised by tests/test_integrations.py.

The same calls exist on a remote session: flow.adjoint({"f_surface": seed}, wrt=[...]) on a RemoteDiscipline, or POST /api/v1/sessions/{id}/adjoint. A structural code on another tailnet machine can therefore own the coupled iteration.

    ↑ ↓ moveEnter open[ ] change depthResults show section and depth level