Skip to content

PRISM regression suite

The src/tests/prism/regression/ suite is the structural-change regression baseline for the PRISM Maxwell/EM solver — every step of the forest-of-trees migration must leave it green. Each case runs in roughly a minute per backend and validates one backend against committed reference outputs (or, for the AMR-seam cases, against a physics oracle).

This page is the conceptual overview. The authoritative operational reference — golden-capture workflow, CI-as-authority rules, tolerance rationale, adding a case — is the suite's own README.md; it is not duplicated here.

Design goals

  • Catch structural regressions, not physics changes. The suite is derived from research cases but CI-tuned: small grids (16³ or 8×16×16 per realm), it_max ≈ 5–10, per-step residual logging. It exists to catch a refactor that silently changes a reduction, a kernel chain, or a seam fill — not to validate the physics (that is the research cases' job).
  • Two backends, one baseline. Every case runs on prism-cpu-gnu (the CI-gated GNU/CPU baseline) and prism-fnl-nvf (the workstation-only OpenACC/GPU backend). A case is fully covered only when goldens exist for both.
  • Fixed two-rank runs. Every case runs at mpirun -np 2, so the golden is a two-rank golden and the suite exercises the MPI ghost-exchange and decomposition paths on every run. (A one-rank rerun will not match a two-rank golden.)
  • Deterministic across compilers. AMR runtime refinement is disabled (amr_iterations = 0, [amr] frequency = 999999) so no float-comparison-sensitive refinement decision happens mid-run; initial uniform refinement (iu_ref_levels = 2) still gives 64 leaf blocks to distribute across the two ranks.

The harness (run.sh)

bash
# CPU backend (also the CI job)
src/tests/prism/regression/run.sh cpu

# FNL backend — workstation only, via the wrapper that loads nvhpc + WSL UCX knobs
src/tests/prism/regression/run-fnl-local.sh

run.sh {cpu|fnl} [--no-build] [--varset <name>] builds the backend, then auto-discovers cases: every subdirectory with an input.ini is a case. A case with no golden/<backend>/ directory is skipped (it is not yet an anchor); set REGRESSION_RUN_GOLDENLESS=1 to run it anyway for the initial golden capture. Before any case runs, a GPU race-shape lint gate checks the FNL kernels.

Two reference files per case

A full PRISM checkpoint is ~100 MB/rank, far too large to commit. Instead each case commits two compact references, both compared tolerance-aware:

ReferenceWhat it isComparison
golden/<backend>/digest.txtper-variable field digestdigest.py comparertol=1e-6, atol=1e-3
golden/<backend>/*-residuals.datper-iteration residuals logdigest.py compare-residuals — same tolerance

The field digest reduces every HDF5 checkpoint to count, min, max, sum, sum_sq per field variable, aggregated across all blocks, ranks, and (for forests) realms at each saved iteration. div_* diagnostics are excluded (they are ~0 constraint residues, pure round-off). The tolerance is calibrated for cross-compiler runs: rtol=1e-6 absorbs FMA / libm / MPI-reduction-order noise (~9 significant digits still checked); atol=1e-3 is the cancellation-residue floor for antisymmetric fields whose sum is pure noise. Per-block metadata (dxdydz, origin, time_iteration) that differs only by block partitioning is auto-downgraded to SKIP_METADATA — this is what lets the β cross-config oracle compare a two-realm run against a single-realm golden.

Golden provenance

The CPU golden is captured in CI (the single source of truth for the CPU backend); a dev-box golden on a different compiler would drift. The FNL golden is captured on the GPU workstation. See the suite README for the exact capture/refresh workflow.

Case inventory

All cases share physical_model = EM, nv = 9, fdv_order = 6, ngc = 3, constrained_transport = no, divergence_correction = no (so the issue #11 nv/CT hazard is not exercised).

Digest/residuals-goldened cases

CaseRealmCadenceSchemeExercises
rmfsinglefdbaseline EM case (4 AC coils); the digest anchor and the β cross-config reference target
rmf-2realminter (x-split)αfdsingle-realm rmf split at x=0; aggregated digest vs the rmf golden (#13 Phase D)
rmf-2realm-asymKinterαfdthe asymmetric-K validation: realm_1 SSP-33 (K=3) ∥ realm_2 SSP-54 (K=5), the α K-equality-guard removal (#16)
rmf-2realm-stagesyncinterβfdthe load-bearing β oracle — digest must match the single-realm rmf golden bit-for-bit (#18)
rmf-fwlsinglefdthe only fWLayer case (C=6, six faces) + 4 coils — guards the FNL fWLayer host→device transfer fix (#31); FNL-goldened, CPU golden pending CI

Inter-realm 1:1 seam anchor

rmf-2realm-fd-pulse — a source-free Gaussian pulse split at x=0 into two same-resolution realms glued by a β mirror seam. It isolates the seam mechanism (inter-realm peer copy) from any resolution jump: a 1:1 seam must be div-free exactly like a 1:1 intra-block interface, and it holds div(B) = div(D) = 0but only under β. Under α the seam ghosts go unfilled during RK substages and div(B) leaks (this is the #31 diagnosis). For a 1:1 same-dt seam, β is correct and required.

AMR-seam cases (goldenless, check.sh-driven)

Three single-realm cases carry a static intra-realm 2:1 AMR jump (markers_number = 1, an AMR_GEO box marker covering the x<0 half). They are not digest-goldened; each carries a bespoke check.sh that asserts a physics oracle, run with a marker-off control leg for contrast. run.sh does not invoke them — they are run directly.

CaseSchemecheck.sh oracle
rmf-amrfv_centeredrefinement happens → 2:1 jump exists; the AMR seam registers (SEAM_KIND_INTRA_REALM_AMR); Berger-Colella reflux fires non-zero; np1 ≡ np2 register parity (#28)
rmf-amr-fdfd_centeredcontrol max|div(B)| ≤ 1e-13 (interior identity intact); seam max|div(B)| within ±5% of a pinned baseline; div(J) truthfulness within a factor-3 band (#26)
rmf-amr-fd-pulsefd_centeredsource-free two-invariant oracle (div(D) structural-zero, div(B) seam baseline ±5%); a --convergence p_obs ≥ 0.8 refinement ladder; and the RK-contract legs — leap-splitting refusal + SSP-11 instrument (#25)

The pulse case is the valid convergence instrument: with J = 0 the Maxwell system conserves both discrete divergences, so any seam div(B) is attributable to the 2:1 jump alone (no coil h⁻² contamination). Its --convergence leg is what pins the seam div(B) source as O(h^p) truncation-order (see div(B) at 2:1 seams).

Reproducers

src/tests/prism/regression/reproducers/ holds standalone minimal reproducers (not cases): fnl_mpi_rndv.sh for the FNL UCX-rendezvous SIGABRT on WSL (#12, the UCX_RNDV_THRESH=inf workaround) and a gfortran-16 debug-mode -fcheck=bounds false-positive reproducer.

See also

  • Forest (multi-realm) — the seam machinery these cases exercise.
  • Suite README.md — operational reference (golden capture, tolerances, adding a case).
  • kernel_inventory.md — the separate PRISM-FNL OpenACC kernel-class audit (#10 Step 3).