Gyredocs
Depth Practice
API

Gyre/Mathematics & Numerical Methods

Shock capturing

In one minuteFlows with shocks are stabilised by an artificial viscosity that a smoothness sensor switches on only in the elements containing a shock. The term lives inside the differentiated residual, so Newton convergence and adjoint derivatives stay exact.

Written at L2 Practice goes to L3 Mechanics 946 words Markdown

A polynomial cannot represent a discontinuity. Entropy-conservative volume terms and a dissipative interface flux remove the aliasing instability, but at a shock the solution still oscillates, and nothing in the scheme keeps density and pressure positive. Gyre adds a small amount of physical-looking diffusion exactly where the solution stops being resolved, and nowhere else.

bash
.venv/bin/gyre run naca0012_transonic            # preset: M = 0.8, α = 1.25°
.venv/bin/gyre run naca0012_euler --set flow.mach=0.8 --set discretization.shock_capturing=true

discretization.shock_capturing enables the term; discretization.av_coef scales it (default 1). Smooth flows are unaffected: the sensor is exactly zero for a uniform state and far below threshold for resolved solutions.

  1. Detect. In each element the density is expanded in Legendre modes. If a noticeable part of its energy sits in the highest modes, the element is not resolving the solution — the signature of a shock. This gives one smooth sensor value per element.
  2. Diffuse. Where the sensor exceeds a threshold, a Laplacian artificial viscosity is switched on, scaled with the element size divided by the polynomial degree and with the local wave speed, and with av_coef. It spreads the shock over a few nodes inside the element instead of letting the polynomial oscillate.
  3. Differentiate. Sensor and viscosity are smooth functions of the state and part of the residual, so the Newton linearisation and the adjoint include them exactly. This is why a transonic case converges to machine precision and why its gradients agree with finite differences (transonic example).

What it is not: a limiter, a positivity guarantee or an entropy-stable shock treatment. It is verified on a transonic aerofoil with weak shocks and untested for strong ones; the limits are listed at the end of this page.

In every element the density is transformed to an orthonormal Legendre basis. The sensor is the fraction of its energy carried by the highest polynomial shell (all modes with at least one index equal to pp):

Se=∑modes with max⁡(i,j,k)=pρ^ 2∑all modesρ^ 2,se=log⁡10Se.S_e = \frac{\sum_{\text{modes with } \max(i,j,k) = p} \hat\rho^{\,2}}{\sum_{\text{all modes}} \hat\rho^{\,2}}, \qquad s_e = \log_{10} S_e .

For a smooth, resolved solution the spectrum decays rapidly and ses_e is very negative; across a shock the spectrum is flat and ses_e approaches zero. The numerator is evaluated as a direct sum of squares — computing it as “total minus low modes” lets round-off make it negative in near-uniform far-field elements, and the logarithm of a negative number poisons the whole linearisation.

The element-wise coefficient ramps smoothly from zero to its maximum,

εe=εmax⁡⋅{0se≤s0−κ12(1+sin⁡π(se−s0)2κ)∣se−s0∣<κ1se≥s0+κεmax⁡=cav hep (∣u∣+c)‾e,\varepsilon_e = \varepsilon_{\max}\cdot \begin{cases} 0 & s_e \le s_0 - \kappa \\ \tfrac12\left(1 + \sin\dfrac{\pi (s_e - s_0)}{2\kappa}\right) & |s_e - s_0| < \kappa \\ 1 & s_e \ge s_0 + \kappa \end{cases} \qquad \varepsilon_{\max} = c_\text{av}\,\frac{h_e}{p}\,\overline{(|\mathbf u| + c)}_e ,

with threshold s0=−(1.5+4log⁡10p)s_0 = -(1.5 + 4\log_{10} p), half-width κ=1\kappa = 1, heh_e the element size and the bar an element mean. The element size is the square root of the element area in 2-D and the cube root of the volume in 3-D, both taken from the nodal mean of the Jacobian. On a spanwise extrusion of a 2-D section it is the square root of the in-plane area, so that an extruded case carries, for the same state, exactly the viscosity of its section, independent of span and nz. The scaling makes the shock about one element wide. The diffusive flux εe∇U\varepsilon_e \nabla\mathbf U acts on all conserved variables and is discretized with the same BR2 machinery as the physical viscous terms: lifted gradients, weak divergence, averaged interface flux. No flux crosses domain boundaries.

The sensor, the ramp (which is C1C^1) and the viscosity are ordinary JAX code inside the residual. Therefore:

  • the Newton linearisation includes the dependence of εe\varepsilon_e on the solution — the solver reduces the residual of a transonic case by ten orders, at the linear rate of its inexact Newton iteration (implicit solver), rather than stalling in the limit cycle that frozen or non-smooth limiters are known for;
  • the element blocks of the preconditioner remain exact (the viscosity of an element depends only on its own state; the neighbour’s enters through the neighbour-role argument);
  • the discrete adjoint contains the shock-capturing term, so total derivatives are those of the function that is actually computed.
Adjoint derivatives through the shockExample: transonic aerofoil with shock capturing
derivativeadjointfinite differencerelative difference
dcl/dalpha17.621917.62191.4558e-06
dcd/dalpha1.314591.314599.9160e-07
dcl/dmach4.469924.469889.9365e-06
dcd/dmach0.6887270.6887253.7501e-06

gyre 0.4.0 · 39979fcf90 · · gpu · NVIDIA GeForce RTX 3060 Ti · quick suite · record (JSON)Same code as this build

Unit tests assert that the viscosity is exactly zero for uniform and smooth resolved data and non-zero only in elements at a steep profile; that free-stream preservation and conservation hold with the term active (2-D and 3-D); that it increases entropy dissipation; that a spanwise extrusion reproduces the viscosity of its 2-D section for different spans, and its residual for a state that is continuous across elements (where the state jumps between elements the two residuals differ through the default penalty, 6 for hexahedra against 4 for quadrilaterals); and that block inverses, JVPs and VJPs remain exact with the sensor partially ramped (tests/test_operator.py). The transonic example checks the flow and its derivatives.

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