Skip to content

Spectral fidelity: what relative L2 does not see

Relative L2 error rewards smooth predictions. A blurred field can have a small L2 error and still miss the small scales that carry heat transfer, mixing or loads, and neural operators are known to under-predict them (spectral bias). pinneapple_analysis.validation.spectral measures what L2 hides. It is NumPy only and works on any field on a regular grid, from any code.

import numpy as np
from pinneapple_analysis.validation.spectral import spectral_fidelity

report = spectral_fidelity(pred, ref, ndim=2)          # arrays (..., ny, nx): leading axes are averaged
print(report.summary())                                # rel. L2, high-band energy ratio and bias, per-band table
print(report.passed)                                   # False when |bias| > tol (default 0.25)

What is measured

Quantity Function Meaning
Energy spectrum E(k) energy_spectrum(u, ndim, periodic=..., window="hann") Shell-summed, integer radial wavenumbers k = rint(|m|), sum E = mean(u^2) for a periodic field. Non-periodic fields are mean-removed and Hann-windowed (unit mean square).
Energy ratio per band, high-wavenumber bias spectral_comparison, high_wavenumber_bias E_pred / E_ref - 1 over the top third of the shells where the reference has energy: about -1 for a smoothed prediction, 0 for the reference itself, positive for added noise.
Phase error and coherence per band spectral_comparison Energy-weighted mean phase difference [rad]; |sum P R*|^2 / (sum |P|^2 sum |R|^2) (1 = the same modes).
Structure functions structure_functions(u, ndim, orders=(2, 3)) S_p(r) = <|u(x + r) - u(x)|^p> per axis, signed S_3, flatness S_4 / S_2^2 (3 for Gaussian increments; larger at small r for intermittent fields).
Gradient errors gradient_metrics(pred, ref, ndim, spacing, vector_axis=..., walls=...) Error in grad u and |grad u|; for a velocity field, vorticity and strain-rate magnitude; for a scalar, the mean wall flux -k du/dn on chosen faces.
Power-law slope fit_power_law(k, E, kmin, kmax) Least-squares slope in log-log.

Where it is reported

  • pinneapple check: rule PC704 spectral-bias on every grid result with a grid reference of the same shape (tolerance hf-bias, default 0.25).
  • pp.benchmark: field problems (Burgers, Darcy, Navier-Stokes) add an "HF energy bias" column next to the relative L2 error.
  • pp.validate: part of the validation report (once it lands, #696).

How it was checked

tests/test_spectral_fidelity.py:

  • a synthetic field with a prescribed k^-5/3 spectrum: every shell within 1 % (the construction makes it exact to round-off) and the fitted slope within 1 %;
  • a 2-D decaying-turbulence velocity field from the pseudo-spectral solver (pinneapple_physics.multiscale.ns2d, 64², nu = 5e-4, t = 3, spectrum slope about -3.3): the field against itself has zero bias, zero phase error and unit coherence; a copy through a Gaussian filter of half a cell has a relative L2 error of 2.5 % and a high-wavenumber bias of -0.68 (flagged; -0.92 at 0.75 cell, where L2 is 4.9 %); 5 % white noise gives a positive bias above 0.25;
  • closed forms: S_2(r) of a linear field, flatness 3 of Gaussian noise, vorticity and strain of an analytic velocity field, and the wall flux of x^2 + y.

Limits

  • Small added noise is below the default tolerance: 2 % white noise on the turbulence field gives a bias of +0.19 (not flagged at 0.25), 5 % gives +1.09. Lower tol if noise matters for the use.
  • The bias compares energies, not positions: a field shifted by a few cells keeps a zero bias. The phase error and coherence catch that.
  • The band edges are fractions of the resolved range of the reference. A reference with almost no energy at high wavenumbers (smooth solutions) gives a short high band; the check is most informative for multi-scale fields.
  • Non-periodic fields are windowed, which spreads energy across neighbouring shells (leakage). Ratios are robust to it because prediction and reference get the same window; absolute spectra of non-periodic fields are approximate.