---
title: Output error estimation
summary: The adjoint that provides gradients also measures how much each element's unresolved residual contaminates an output. One extra linear solve yields an error estimate, a corrected output and per-element indicators.
section: theory
order: 9
level: 3
keywords: [dual-weighted residual, adjoint-weighted residual, error estimate, superconvergence, adaptation]
---
<!-- gyre docs: expanded Markdown of theory/error-estimation.md (generated tables, verification numbers and chart data are written out). Base level of this page: L3 Mechanics; passages at another level are bracketed by 'begin level N' / 'end level N' comments and start with [LN Name]. Built from revision ceb1f4b0be. -->

A design optimizer exploits whatever errors the analysis has. Knowing the discretization error
of the *objective* — not of the solution in some norm — is therefore the relevant quality
measure, and the adjoint provides it.

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

Let $h$ denote the working space (degree $p$) and $H$ an enriched space (degree $p+1$ on the same
mesh). Inject the converged coarse solution, $\mathbf U_H^h = \mathbf I\,\mathbf U_h$. It does not
satisfy the fine equations; the fine residual $\mathbf R_{w,H}(\mathbf U_H^h)$ measures by how much.
Linearising the fine output about the injected state gives

$$
\delta J \;\equiv\; J_H(\mathbf U_H) - J_H(\mathbf U_H^h) \;\approx\; -\boldsymbol\psi_H^{\mathsf T}\,\mathbf R_{w,H}(\mathbf U_H^h),
$$

with $\boldsymbol\psi_H$ the fine-space discrete adjoint linearised about $\mathbf U_H^h$. Gyre solves
that one linear system with the p-multigrid hierarchy $(p+1, p, \dots, 0)$ and returns

* `value` $= J_h$, `estimate` $=\delta J$, `corrected` $= J_H(\mathbf U_H^h) + \delta J$, and
  `error` $= J_h - $ `corrected`;
* `indicators`: $\eta_e = \big|\sum_{i\in e}\boldsymbol\psi_{H,i}\cdot\mathbf R_{w,H,i}\big|$, the share of
  each element — the quantity an adaptation strategy would act on.

No fine *nonlinear* solve is needed.

<!-- end level 3 -->

<!-- begin level 4 (Theory) -->
**[L4 Theory] Why it works, and when**

The estimate is the first-order term of a Taylor expansion, so its error is quadratic in the
distance between $\mathbf U_H^h$ and $\mathbf U_H$. Its quality hinges on $\boldsymbol\psi_H$ being a good
approximation of the continuous adjoint, i.e. on **adjoint consistency** of the discretization
and of the output definition (see [BR2](viscous-br2.md) and
[boundary conditions](boundary-conditions.md)). For an adjoint-consistent scheme, outputs
converge at up to twice the rate of the solution in the energy norm, and the corrected output
converges faster still. Singularities (a sharp trailing edge) reduce these rates, as they reduce
everything else.

Effectivity is assessed against the actually converged $p+1$ solution — an exact test of the
estimator, independent of any "true" solution:

**Table: Adjoint-weighted-residual estimate versus the p + 1 solution** (Output error estimation)

| mesh | p | cd (p) | estimate δ | corrected | cd (p + 1, converged) | effectivity | remaining / original error |
|---|---|---|---|---|---|---|---|
| 12x4 | 1 | 0.0177275 | -0.00857008 | 0.00634203 | 0.00569478 | 0.929779 | 0.0537907 |
| 24x8 | 1 | 0.012233 | -0.00806656 | 9.2333e-04 | 3.4443e-04 | 0.93304 | 0.0486938 |
| 12x4 | 2 | 0.00569478 | -0.0026344 | 5.0612e-04 | 1.4077e-04 | 0.878207 | 0.065781 |
| 24x8 | 2 | 3.4443e-04 | -2.0704e-04 | 2.1391e-05 | 1.4254e-05 | 0.966676 | 0.0216164 |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T11:33:17-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: [output_error.json](../data/verification/output_error.json).

**Chart: Drag error: raw and adjoint-corrected** (Output error estimation). Convergence chart of |cd − reference| against h (1 / nx), x axis log, y axis log; 4 series. The table holds the plotted data. Reference: finest mesh at degree 4, a solution used by no plotted point and no estimate. It differs from the degree-3 output on the same mesh by 1.4e-05; the reference is itself a computed value, and differences of that size or below are not resolved. Corrected outputs are the injected p + 1 output plus the adjoint estimate.

| h (1 / nx) | p = 1: cd | p = 2: cd | p = 1: corrected | p = 2: corrected |
|---|---|---|---|---|
| 0.0833333 | 0.0177271 | 0.00569443 | 0.00634168 | 5.0577e-04 |
| 0.0416667 | 0.0122327 | 3.4408e-04 | 9.2298e-04 | 2.1043e-05 |

Provenance: gyre 0.4.0; revision `39979fcf90` (commit `39979fcf902ad6116eb550b881b4278d4599c8e2`); 2026-11-12T11:33:17-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: [output_error.json](../data/verification/output_error.json).

<!-- end level 4 -->

<!-- begin level 2 (Practice) -->
**[L2 Practice] How to use it**

```bash
gyre run naca0012_euler --error cd
```

or request `error_estimate: ["cd"]` in a job, or call `session_error_estimate` from an agent.
The command prints `value`, `estimate`, `corrected` and `error` for each output. To keep the
per-element `indicators`, add `-o <dir>` (the full record is then in `result.json`) or use a job
or a session (the agent tool `session_error_estimate` returns them summarised).

In a design loop the estimate answers three practical questions: whether the current mesh/order
is adequate for the objective at the current design, whether a change in the objective between
two designs is larger than its numerical uncertainty, and where resolution is missing.

The estimate exists for the built-in scalar outputs, which can be rebuilt in the enriched
space with the same reference quantities; a user-defined functional or `f_surface` raises
`KeyError`. It is computed at a converged state only: a job, `run_case()` or `gyre run` whose
solve did not converge returns no estimate. The job result and the result of `run_case()` list
it under `skipped`; for `gyre run` that entry is in `result.json` (with `-o <dir>`), not in the
printed summary.

<!-- end level 2 -->

<!-- begin level 2 (Practice) -->
**[L2 Practice] Choosing the degree from the estimate**

Order selection turns the estimate into a stopping rule. Given an output and an absolute
tolerance, the case is solved at its own degree $p_0$, the error of the output is estimated,
and the degree is raised by one until

$$
|J_h - (J_H(\mathbf U_H^h) + \delta J)| \le \text{tolerance},
$$

that is, until `|error|` of the estimate above is within the tolerance, or the largest allowed
degree is reached. Each new degree starts from the interpolated solution of the one below.
Usage is in [Gradients and error estimates](../guide/gradients.md#letting-the-error-estimate-choose-the-order);
the [order-selection study](../verification/order-selection.md) checks the rule.

<!-- end level 2 -->

<!-- begin level 3 (Mechanics) -->
**[L3 Mechanics] What the selected degree does and does not guarantee**

* **The lowest sufficient degree among those tried.** Degrees are tried in ascending order
  from the case's own, and the first whose estimate meets the tolerance is returned. Degrees
  below the starting one are never tried.
* **Relative to the next degree.** The estimate at degree $p$ predicts the output at $p + 1$ on
  the same mesh. The tolerance therefore bounds the estimated distance to the $p + 1$ output,
  not to the exact one. Where enrichment in $p$ captures little of the true error (a
  singularity, an under-resolved shock, a geometry that the mesh itself misrepresents) the
  estimate, and with it the selection, is optimistic.
* **An estimate, not a bound.** $\delta J$ is a linearisation; on a mesh too coarse for it to
  hold, `met: true` says that the estimate is within the tolerance, nothing more.
* **Only a certified estimate counts.** The tolerance is met only by a finite estimate whose
  adjoint solve converged. If a degree does not converge, after it has also been tried with
  the full continuation and from the free stream, the selection stops with `met: false` and
  the last converged degree.
* **Uniform enrichment.** Every element gets the same degree. The indicators $\eta_e$ are
  reported with the estimate but not used; the mesh is not changed.
* **One output.** The degree that suffices for one output need not suffice for another; other
  outputs of the result carry no accuracy statement unless their own estimate is requested.
* **A discrete choice.** The selected degree can change between two nearby designs, and the
  output then changes by the difference between two discretizations. Inside an optimization,
  select once and keep the degree fixed.

<!-- end level 3 -->
