---
title: Linearisation and gradients
summary: Jacobian products, preconditioner blocks, adjoint and direct total derivatives are verified against finite differences, against each other and against dense reference Jacobians. Agreement with finite differences is limited by finite-difference accuracy; adjoint and direct mode agree to the tolerance of their linear solves.
section: verification
order: 4
level: 2
study: adjoint_gradients
studies: [linearization, mesh_deformation]
keywords: [adjoint, finite differences, dot product test, Jacobian, shape derivative]
---
<!-- gyre docs: expanded Markdown of verification/derivatives.md (generated tables, verification numbers and chart data are written out). Base level of this page: L2 Practice; passages at another level are bracketed by 'begin level N' / 'end level N' comments and start with [LN Name]. Built from revision ceb1f4b0be. -->

For an MDO foundation the derivative code is as important as the flow solver. It is verified
at three levels: products of the residual Jacobian, total derivatives of outputs, and the
geometric chain behind shape derivatives.

## Exact linearisation

**Verification status: Exact linearization by automatic differentiation** (study `linearization`): **PASSED**, 40 of 40 checks passed, 198.1 s.

Jacobian-vector products (forward mode) are compared with central differences of the residual; vector-Jacobian products (reverse mode) are checked through the dot-product identity; and the element-block-Jacobi inverses obtained from split-argument JVPs are multiplied with the diagonal blocks of a dense AD Jacobian. Random admissible states on curved meshes. Boundary conditions covered: farfield, noslip_adiabatic, noslip_isothermal, pressure_outlet, slip_wall, slip_wall_mirror, symmetry, total_inlet; interface fluxes: es_roe, hllc, llf, roe.

| Check | Value | Expected | Result |
|---|---|---|---|
| Euler, slip wall: JVP vs central difference | 5.3652e-10 | < 1e-6 | pass |
| Euler, slip wall: <v,Ju> = <J^T v,u> | 7.4656e-16 | < 1e-12 | pass |
| Euler, slip wall: block inverse times dense diagonal block | 6.7502e-14 | < 1e-8 | pass |
| Euler, slip wall: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| NS, adiabatic wall: JVP vs central difference | 3.7139e-10 | < 1e-6 | pass |
| NS, adiabatic wall: <v,Ju> = <J^T v,u> | 7.5418e-15 | < 1e-12 | pass |
| NS, adiabatic wall: block inverse times dense diagonal block | 1.2397e-14 | < 1e-8 | pass |
| NS, adiabatic wall: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| NS, isothermal wall: JVP vs central difference | 3.1571e-10 | < 1e-6 | pass |
| NS, isothermal wall: <v,Ju> = <J^T v,u> | 1.0274e-13 | < 1e-12 | pass |
| NS, isothermal wall: block inverse times dense diagonal block | 1.6348e-14 | < 1e-8 | pass |
| NS, isothermal wall: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| NS, mixed channel: JVP vs central difference | 5.2611e-10 | < 1e-6 | pass |
| NS, mixed channel: <v,Ju> = <J^T v,u> | 4.3341e-16 | < 1e-12 | pass |
| NS, mixed channel: block inverse times dense diagonal block | 1.4949e-14 | < 1e-8 | pass |
| NS, mixed channel: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| Euler, mirrored slip wall: JVP vs central difference | 1.9949e-10 | < 1e-6 | pass |
| Euler, mirrored slip wall: <v,Ju> = <J^T v,u> | 1.0578e-13 | < 1e-12 | pass |
| Euler, mirrored slip wall: block inverse times dense diagonal block | 5.6843e-14 | < 1e-8 | pass |
| Euler, mirrored slip wall: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| Euler, mirrored slip wall, HLLC flux: JVP vs central difference | 5.6037e-10 | < 1e-6 | pass |
| Euler, mirrored slip wall, HLLC flux: <v,Ju> = <J^T v,u> | 7.5694e-14 | < 1e-12 | pass |
| Euler, mirrored slip wall, HLLC flux: block inverse times dense diagonal block | 2.4869e-14 | < 1e-8 | pass |
| Euler, mirrored slip wall, HLLC flux: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| Euler, mirrored slip wall, LLF flux: JVP vs central difference | 1.3770e-10 | < 1e-6 | pass |
| Euler, mirrored slip wall, LLF flux: <v,Ju> = <J^T v,u> | 1.4703e-15 | < 1e-12 | pass |
| Euler, mirrored slip wall, LLF flux: block inverse times dense diagonal block | 1.1713e-14 | < 1e-8 | pass |
| Euler, mirrored slip wall, LLF flux: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| Euler, inlet/outlet channel: JVP vs central difference | 2.1088e-10 | < 1e-6 | pass |
| Euler, inlet/outlet channel: <v,Ju> = <J^T v,u> | 1.2508e-13 | < 1e-12 | pass |
| Euler, inlet/outlet channel: block inverse times dense diagonal block | 5.3846e-15 | < 1e-8 | pass |
| Euler, inlet/outlet channel: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| Euler, mirrored slip wall, ES-Roe flux: JVP vs central difference | 1.8848e-10 | < 1e-6 | pass |
| Euler, mirrored slip wall, ES-Roe flux: <v,Ju> = <J^T v,u> | 1.3925e-14 | < 1e-12 | pass |
| Euler, mirrored slip wall, ES-Roe flux: block inverse times dense diagonal block | 3.8192e-14 | < 1e-8 | pass |
| Euler, mirrored slip wall, ES-Roe flux: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |
| NS, adiabatic wall, ES-Roe flux: JVP vs central difference | 3.0017e-10 | < 1e-6 | pass |
| NS, adiabatic wall, ES-Roe flux: <v,Ju> = <J^T v,u> | 2.9248e-15 | < 1e-12 | pass |
| NS, adiabatic wall, ES-Roe flux: block inverse times dense diagonal block | 1.6784e-14 | < 1e-8 | pass |
| NS, adiabatic wall, ES-Roe flux: split-argument residual with identical arguments vs ordinary residual | 0 | < 1e-12 | pass |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T10:27:54-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [linearization.json](../data/verification/linearization.json).

**Table: Linearization defects** (Exact linearization by automatic differentiation)

| configuration | p | DOF | JVP vs FD (rel.) | dot-product (rel.) | \|B⁻¹ J_ee - I\| | split residual defect |
|---|---|---|---|---|---|---|
| Euler, slip wall | 2 | 432 | 5.3652e-10 | 7.4656e-16 | 6.7502e-14 | 0 |
| NS, adiabatic wall | 2 | 432 | 3.7139e-10 | 7.5418e-15 | 1.2397e-14 | 0 |
| NS, isothermal wall | 2 | 432 | 3.1571e-10 | 1.0274e-13 | 1.6348e-14 | 0 |
| NS, mixed channel | 3 | 576 | 5.2611e-10 | 4.3341e-16 | 1.4949e-14 | 0 |
| Euler, mirrored slip wall | 2 | 432 | 1.9949e-10 | 1.0578e-13 | 5.6843e-14 | 0 |
| Euler, mirrored slip wall, HLLC flux | 2 | 432 | 5.6037e-10 | 7.5694e-14 | 2.4869e-14 | 0 |
| Euler, mirrored slip wall, LLF flux | 2 | 432 | 1.3770e-10 | 1.4703e-15 | 1.1713e-14 | 0 |
| Euler, inlet/outlet channel | 2 | 324 | 2.1088e-10 | 1.2508e-13 | 5.3846e-15 | 0 |
| Euler, mirrored slip wall, ES-Roe flux | 2 | 432 | 1.8848e-10 | 1.3925e-14 | 3.8192e-14 | 0 |
| NS, adiabatic wall, ES-Roe flux | 2 | 432 | 3.0017e-10 | 2.9248e-15 | 1.6784e-14 | 0 |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T10:27:54-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [linearization.json](../data/verification/linearization.json).

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] The four columns**

* **JVP vs FD** — forward-mode product $\mathbf J\mathbf u$ against a central difference of the
  residual; the residual difference is $\mathcal O(10^{-10})$, the accuracy of the finite
  difference itself.
* **dot-product** — $\langle\mathbf v, \mathbf J\mathbf u\rangle = \langle\mathbf J^{\mathsf T}\mathbf v,
  \mathbf u\rangle$ for random vectors: forward and reverse mode agree to round-off, so the adjoint
  operator is the exact transpose.
* **$|\mathbf B^{-1}\mathbf J_{ee} - \mathbf I|$** — the block-Jacobi inverses from split-argument JVPs
  multiplied with the diagonal blocks of a dense AD Jacobian.
* **split residual defect** — the split-argument residual evaluated with identical arguments
  must equal the ordinary residual; the check requires a difference below $10^{-12}$.

Random admissible states are used, on curved meshes, for slip, adiabatic and isothermal walls,
for a channel combining far-field, pressure-outlet, no-slip and slip boundaries, for the
mirrored slip wall (`slip_wall_mirror`) with the Roe, HLLC, LLF and ES-Roe interface fluxes, for
an inlet/outlet channel (`total_inlet`, `pressure_outlet`, `symmetry`, `slip_wall_mirror`), and
for a viscous case with the ES-Roe flux. The mirrored slip wall is the only wall that hands two
different states to the interface Riemann flux, which is why it is run with each flux; the
ES-Roe flux is the one whose dissipation contains logarithmic means and entropy variables.

<!-- end level 3 -->

## Adjoint gradients

**Verification status: Adjoint gradients versus finite differences** (study `adjoint_gradients`): **PASSED**, 18 of 18 checks passed, 1176 s.

Total derivatives of force coefficients with respect to angle of attack, Mach number, Reynolds number and a smooth surface-shape perturbation are computed with one transposed (adjoint) solve per output and compared with central finite differences of fully re-converged flows. For the scalar inputs of the Euler case the derivatives are also computed in direct (forward) mode, an independent linear solve of the same linearization, and compared with the adjoint values.

| Check | Value | Expected | Result |
|---|---|---|---|
| Euler: dcl/dalpha adjoint vs FD (rel.) | 9.8180e-11 | < 1e-6 | pass |
| Euler: dcl/dalpha direct vs adjoint mode (rel.) | 2.7336e-09 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcd/dalpha adjoint vs FD (rel.) | 2.0356e-08 | < 1e-6 | pass |
| Euler: dcd/dalpha direct vs adjoint mode (rel.) | 2.8444e-08 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcm/dalpha adjoint vs FD (rel.) | 1.1288e-08 | < 1e-6 | pass |
| Euler: dcm/dalpha direct vs adjoint mode (rel.) | 4.6115e-07 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcl/dmach adjoint vs FD (rel.) | 1.9822e-09 | < 1e-6 | pass |
| Euler: dcl/dmach direct vs adjoint mode (rel.) | 2.6393e-06 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcd/dmach adjoint vs FD (rel.) | 2.1349e-08 | < 1e-6 | pass |
| Euler: dcd/dmach direct vs adjoint mode (rel.) | 1.1047e-05 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcm/dmach adjoint vs FD (rel.) | 7.8285e-09 | < 1e-6 | pass |
| Euler: dcm/dmach direct vs adjoint mode (rel.) | 6.9061e-05 | < 1e-4 (limited by the linear-solve tolerance 1e-10) | pass |
| Euler: dcl/dshape (directional) adjoint vs FD (rel.) | 6.7282e-09 | < 1e-6 | pass |
| Euler: dcd/dshape (directional) adjoint vs FD (rel.) | 1.6480e-08 | < 1e-6 | pass |
| Euler: dcm/dshape (directional) adjoint vs FD (rel.) | 3.8413e-08 | < 1e-6 | pass |
| Navier–Stokes: dcd/dre adjoint vs FD (rel.) | 4.7607e-10 | < 1e-6 | pass |
| Navier–Stokes: dcd/dmach adjoint vs FD (rel.) | 1.3254e-09 | < 1e-6 | pass |
| step sweep: best FD agreement with adjoint (rel.) | 1.1864e-10 | < 1e-8 | pass |

Run notes: Largest relative difference in this run: adjoint against finite differences 3.8e-08, adjoint against direct mode 6.9e-05.

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T11:18:25-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [adjoint_gradients.json](../data/verification/adjoint_gradients.json).

**Table: Adjoint, finite-difference and direct derivatives** (Adjoint gradients versus finite differences)

| case | derivative | adjoint | finite difference | rel. diff. (adjoint, FD) | rel. diff. (adjoint, direct) |
|---|---|---|---|---|---|
| Euler NACA 0012 | dcl/dalpha | 7.59626 | 7.59626 | 9.8180e-11 | 2.7336e-09 |
| Euler NACA 0012 | dcd/dalpha | 0.0222355 | 0.0222355 | 2.0356e-08 | 2.8444e-08 |
| Euler NACA 0012 | dcm/dalpha | 0.0123702 | 0.0123702 | 1.1288e-08 | 4.6115e-07 |
| Euler NACA 0012 | dcl/dmach | 0.218262 | 0.218262 | 1.9822e-09 | 2.6393e-06 |
| Euler NACA 0012 | dcd/dmach | 3.8734e-04 | 3.8734e-04 | 2.1349e-08 | 1.1047e-05 |
| Euler NACA 0012 | dcm/dmach | 0.00128519 | 0.00128519 | 7.8285e-09 | 6.9061e-05 |
| Euler NACA 0012 | dcl/dshape (directional) | 0.0130491 | 0.0130491 | 6.7282e-09 | — |
| Euler NACA 0012 | dcd/dshape (directional) | -2.4501e-05 | -2.4501e-05 | 1.6480e-08 | — |
| Euler NACA 0012 | dcm/dshape (directional) | 3.9401e-04 | 3.9401e-04 | 3.8413e-08 | — |
| Cylinder Re = 40 | dcd/dre | -0.0125001 | -0.0125001 | 4.7607e-10 | — |
| Cylinder Re = 40 | dcd/dmach | 0.270411 | 0.270411 | 1.3254e-09 | — |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T11:18:25-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [adjoint_gradients.json](../data/verification/adjoint_gradients.json).

<!-- begin level 2 (Practice) -->
**[L2 Practice] What the table shows**

Total derivatives of $c_l$, $c_d$ and $c_m$ with respect to angle of attack, Mach number and a
smooth shape perturbation (Euler, NACA 0012), and of $c_d$ with respect to Reynolds and Mach
number (laminar cylinder). The adjoint value is compared with a central finite difference of
fully re-converged flows (check: relative difference below $10^{-6}$); that column is limited
by finite-difference error. For the scalar inputs of the Euler case the derivative is also
computed in direct mode, an independent linear solve of the same linearisation, and must agree
with the adjoint value to a relative $10^{-4}$. That agreement is limited by the $10^{-10}$
tolerance of the two Krylov solves times the conditioning of the derivative, which is large
for derivatives that are small through cancellation; it is not a round-off comparison.

<!-- end level 2 -->

**Chart: Finite-difference error of dcl/dα versus step size** (Adjoint gradients versus finite differences). Line chart of |FD − adjoint| / |adjoint| against step size, x axis log, y axis log; 1 series. The table holds the plotted data. Reference slopes drawn: 2 (ε² truncation). Smallest difference in this run: 1.2e-10 at a step of 1e-05. The reference line is the ε² truncation error of a central difference; round-off of the re-converged flows grows as 1/ε.

| step size | central difference |
|---|---|
| 0.01 | 1.0052e-04 |
| 0.001 | 9.7679e-07 |
| 1.0000e-04 | 9.6958e-09 |
| 1.0000e-05 | 1.1864e-10 |
| 1.0000e-06 | 1.8974e-10 |
| 1.0000e-07 | 2.3724e-10 |
| 1.0000e-08 | 2.7589e-09 |
| 1.0000e-09 | 3.9518e-07 |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T11:18:25-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [adjoint_gradients.json](../data/verification/adjoint_gradients.json).

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] The V-curve**

Sweeping the finite-difference step shows truncation error falling as $\varepsilon^2$ on the
right and round-off growing as $1/\varepsilon$ on the left. The adjoint value sits at the bottom
of the V: the best any finite difference can do is approach it.

<!-- end level 3 -->

## Geometry chain

**Verification status: Differentiable mesh deformation** (study `mesh_deformation`): **PASSED**, 9 of 9 checks passed, 2.45 s.

Surface displacements are propagated to all high-order geometry nodes by inverse-distance weighting. The study deforms a NACA 0012 O-mesh with a thickness bump of increasing amplitude and checks that elements stay valid, that wall nodes follow the prescription exactly while far-field nodes stay fixed, and that the derivative of a geometric quantity through deformation and metric evaluation matches finite differences.

| Check | Value | Expected | Result |
|---|---|---|---|
| scale 0.0: mesh valid (min J > 0) | 4.3328e-05 | > 0 | pass |
| scale 0.0: surface/far-field prescription defect | 0 | < 1e-13 | pass |
| scale 0.5: mesh valid (min J > 0) | 4.3327e-05 | > 0 | pass |
| scale 0.5: surface/far-field prescription defect | 0 | < 1e-13 | pass |
| scale 1.0: mesh valid (min J > 0) | 4.3327e-05 | > 0 | pass |
| scale 1.0: surface/far-field prescription defect | 0 | < 1e-13 | pass |
| scale 2.0: mesh valid (min J > 0) | 4.3325e-05 | > 0 | pass |
| scale 2.0: surface/far-field prescription defect | 0 | < 1e-13 | pass |
| d(weighted area)/d(surface displacement): AD vs FD | 1.0828e-08 | < 1e-7 | pass |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T10:22:42-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [mesh_deformation.json](../data/verification/mesh_deformation.json).

**Table: Mesh validity under deformation** (Differentiable mesh deformation)

| bump scale | min J | min scaled Jacobian | surface defect | far-field motion |
|---|---|---|---|---|
| 0 | 4.3328e-05 | 0.125712 | 0 | 0 |
| 0.5 | 4.3327e-05 | 0.125627 | 0 | 0 |
| 1 | 4.3327e-05 | 0.125541 | 0 | 0 |
| 2 | 4.3325e-05 | 0.125371 | 0 | 0 |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T10:22:42-0500; device cpu; quick suite. **Revision check: Same code as this build.** Produced by 39979fcf902ad6116eb550b881b4278d4599c8e2. Every commit between it and the commit this site was built from (ceb1f4b0bed41de53e586df10858079f5a2bcdd8) changes only documentation text, evidence records or bookkeeping: the code is the same. Full record: [mesh_deformation.json](../data/verification/mesh_deformation.json).

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] What is being checked**

A thickness bump of increasing amplitude is applied to the aerofoil surface. Elements remain
valid, wall nodes follow the prescription exactly, far-field nodes do not move, and the
derivative of a weighted area through deformation and metric evaluation matches a finite
difference. The same chain carries every shape derivative Gyre returns.

<!-- end level 3 -->

## Coupled derivatives

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] Beyond the suite: integration tests**

Two tests in the regular test suite exercise the coupling contract:

* `test_coupled_aerostructural_adjoint_matches_finite_differences` builds a coupled adjoint for
  a flow coupled to an elastic skin *outside* Gyre, using only loads, displacements and reverse
  total products, and matches a finite difference of the re-converged coupled system;
* `test_openmdao_component_totals` wraps the discipline as an OpenMDAO component and compares the
  framework's reverse- and forward-mode totals with Gyre's own.

<!-- end level 3 -->
