Validation and scientific rigor
Reimplementing a diagnostics library invites a specific failure mode: silently diverging from the reference while claiming compatibility. wrf-rust addresses this with pinned reference implementations, a differential parity harness that compares real outputs field-by-field, and a science audit whose findings are published with their numerical magnitudes — including the cases where wrf-rust's previous values were wrong and the corrected values changed.
1 Pinned references
Every compatibility or correctness claim is made against a fixed, named authority. "Matches wrf-python" always means the pinned commit below, not whatever upstream happens to be today.
| Authority | Pin | Role |
|---|---|---|
| NCAR wrf-python 1.3.4.1 | 31c9233 |
Primary numerical oracle for compatible fields; source of the strict
*_wrfpython CAPE/SRH semantics (RIP Fortran,
psadilookup.dat) |
| SHARPpy 1.4.0a5 | a5405e2 |
Oracle for sounding-derived parameters (STP terms, SHIP, critical angle, mean-wind conventions, interpolation) |
| NOAA/SPC mesoanalysis definitions | current pages | Definitions for severe composites: SCP, effective STP, TEHI, DCP, MUCAPE, VTP |
| Primary literature | DOIs | Hampshire et al. (2018) for VTP; Evans and Doswell (2001) for DCP |
| ecape-rs | 414cac6 |
Entraining-CAPE implementation, pinned by revision in
wrf-core; upstream-golden test included |
2 Differential parity methodology
The parity framework deliberately separates three questions that are easy to conflate:
- Does the public API have compatible names, options, units, axes, component ordering, and missing-value behavior?
- Where wrf-python is a valid oracle, are fields numerically within an explicit per-field tolerance?
- Where wrf-rust intentionally goes beyond or corrects wrf-python, does it match the appropriate scientific authority instead?
Question 3 is never answered by loosening a wrf-python tolerance; such fields
are marked contract_only until they gain an independent oracle.
Mechanically: WRF fixtures are registered by exact byte length and SHA-256 and
never checked into the repository; wrf-python and wrf-rust run in
separate environments (both import as wrf, so the
extractors refuse to run if the other distribution is present); each produces an
implementation bundle; and a comparator checks provenance, shapes, missing-value
masks, and numerical tolerances against a resolved per-field contract. A second
contract, derived from a real downstream consumer (WRF-Runner), regression-tests
the literal call patterns that consumer makes.
Verification snapshot
Results of the most recent full run (2026-07-09, science-audit branch
codex/science-audit-wave2 at 59b380c, which extends
the released 0.3.0 line):
| Gate | Result | Notes |
|---|---|---|
| wrf-python differential (pinned 1.3.4.1) | 16 / 16 passed, 0 failures | All required comparisons in the 43-entry contract; one documented
non-gating native theta_e difference; 26
contract_only extension products |
| SHARPpy differential (pinned 1.4.0a5) | 23 / 23 passed, 0 failures | Maximum absolute error 3.47 × 10−18 |
| Rust workspace tests | 723 passed, 0 failed | 22 ignored (live/external); baseline at the 0.3.0 base was 693 passed, 0 failed |
| ECAPE goldens | 12 / 12 + 1 / 1 | Algorithm and goldens unchanged by the audit; pinned upstream-golden test passes |
Reproducibility hashes for that run — WRF fixture SHA-256
0d950786…28a77e6; wrf-python bundle
cd499cff…ef994af; wrf-rust bundle
40a34461…cc7d61f; comparison report
86136ada…54ec1e3 (full values in the audit record).
3 Corrections shipped in 0.3.0
The 0.3.0 release notes state it plainly: values change where the old ones were wrong. The corrections below shipped in 0.3.0; magnitudes are measured examples from the audit, not worst-case bounds.
| Correction | Reference | Example magnitude |
|---|---|---|
| Saturated parcel lifts above 1000 hPa no longer clamp to the 1000 hPa adiabat (satlift guard) | SHARPpy thermo |
Affects any parcel starting below the 1000 hPa surface |
| Sub-LCL CIN integrates every model level instead of one midpoint slab; parcel trace uses dry physics below the LCL | NCAR RIP CAPE | Column-dependent; changes low-level CIN on most soundings |
| CIN no longer accumulates negative buoyancy above the LFC; LFC/EL selection uses accumulated-energy minimum and highest valid EL under multiple crossings | NCAR RIP CAPE | Removes spurious CIN in multi-crossing profiles |
| AVO and PVO apply WRF map-scale factors; PVO retains full baroclinic terms | NCAR wrf_pvo |
Stretching-only vs full PVO sample delta −0.186 PVU (13.2%); AVO +3.15% at map factor 1.02 |
| theta-e and theta-w no longer double-add 273.15 | NCAR EQTHECALC,
SHARPpy thermo |
Regression pins 344.93 K (not 618.08 K) and 292.11 K (not 565.26 K) |
Theta diagnostics return their registered Kelvin units;
helicity alias maps to srh |
registry contract | Metadata/lookup correctness |
| Non-pressure-weighted mean wind resamples in pressure; 0–1 km fixed shear anchors at the 10 m wind; Southern-Hemisphere Bunkers movers mirror correctly | SHARPpy winds |
NPW mean-wind example 33.33 → 50.36 m/s; shear example 15 → 20 m/s; mirrored SRH +112.5 → −112.5 m²/s² |
| Storm-relative inflow sign for the critical angle (storm motion minus surface wind) | SHARPpy winds |
Canonical case 135° → 45° |
| Registered SHIP uses the most-unstable parcel's mixing ratio with SHARPpy bounds and corrections | SHARPpy params |
Example case 0.5 → 1.2 (2.4×) where environmental vs MU mixing ratio differ 5 vs 12 g/kg |
4 Wave-two audit corrections — shipped in 0.4.0
Status: the corrections in this section were developed on
codex/science-audit-wave2, merged to main via pull
request #2 (merge 6c5fbed), and shipped in
v0.4.0. The full audit closed every lead: 12 fixed, 7 already
resolved in the shipped line, 4 resolved by documentation, 2 refuted,
0 deferred.
| Finding | Reference | Example magnitude |
|---|---|---|
| Exported SHIP helper aligned with the registered (already-correct) implementation | SHARPpy SHIP | 0.5 → 1.2 (2.4×) in the canonical case |
| SCP: exported EBWD gate/cap fixed; registered SCP applies the current SPC MUCIN term | SPC SCP | MUCIN of −80 / −160 J/kg changes the old magnitude by −50% / −75% |
| Exported critical angle now requires the surface wind (arity change prevents silent stale use) | SHARPpy winds |
Calm-surface example 135° → 45° |
| Effective STP gains the elevated-inflow zero gate | SPC effective STP | An otherwise-normalized elevated case: 1.0 → 0.0 |
| TEHI assigns the high-ML3CAPE term exactly 1.0 per the literal SPC instruction | SPC TEHI | High-ML3CAPE fixture 2.4 → 1.6 (−33.3%) |
| Exported fixed-layer STP applies SHARPpy edge clamps | SHARPpy fixed STP | LCL 250 m term 1.75 → 1.0; 8 m/s shear term 0.4 → 0.0 |
| Exported DCP uses 0–6 km mean wind with knot conversions | SPC DCP, Evans–Doswell | Canonical case 1.09 → 2.36 (+116.5%) |
| Relative humidity computed as q/qs rather than e/es | NCAR DCOMPUTERH |
At 1000 hPa, 30 °C, q = 0.020: 73.38% → 74.52% (+1.14 points) |
| Gravity constant 9.81 m/s² (WRF's value) replaces 9.80665 | NCAR wrf_constants |
Heights decrease 0.0341% (−0.34 m per km) |
| Height-at-pressure uses log-pressure interpolation | SHARPpy interp |
Linear-p heights were high by 2.6 / 5.8 / 11.1 m in three layer examples |
| Dewpoint vapor-pressure floor matches NCAR | NCAR DCOMPUTETD |
At 100 hPa, q = 0: −128.5 → −80.4 °C |
Updraft-helicity boundary handling matches DCALCUH
(asymmetric zero halo) |
NCAR DCALCUH |
Halo covers 5.91% of a 100×100 grid, 1.99% of 300×300 |
| Southern-Hemisphere earth-rotation of winds matches wrf-python | NCAR wrf-python projection semantics | WPS alpha of 7° yields a 14° old-vs-reference separation |
5 Documented conventions, not silent forks
Some differences from the references are deliberate. They are named differently, documented, and pinned by regression oracles rather than hidden:
- Native MUCAPE vs strict NCAR.
mucapekeeps the SPC convention (maximum theta-e parcel in the lowest 300 hPa); the NCAR below-3-km / 500 m pressure-mean parcel is the separately namedmcape_wrfpython. Both are tested against their respective authorities. - SRH.
srh/srh1/srh3use modern Bunkers storm motion; the legacy RIP algorithm is the explicitsrh_wrfpython. - Updraft helicity integrates over true terrain-relative AGL rather than reproducing the pinned NCAR wrapper's fixed reference; the difference (about 0.7–1.7% for a linear 2–5 km integrand) is documented in the source.
vtp_modis deliberately not the published VTP; every normalization, layer, and gate difference from Hampshire et al. (2018) is enumerated in the source, and a reference oracle pins the published formula alongside it (illustrative published 1.889 vs modified 0.667).- Moist-adiabat accuracy. The Wobus-based lift carries a documented error envelope: measured deltas of 0.49 K at p ≥ 700 hPa and at most 1.18 K overall, within a stated 1.2 K envelope.
6 What this does and does not claim
- Parity is claimed against pinned references and explicit per-field tolerances, on registered fixtures — not against all WRF files or all option combinations.
- Corrections changed output values. If a workflow depended on the old (incorrect) SHIP, SCP, RH, or height values, 0.3.0 and the 0.4.0 wave-two release produce different numbers — deliberately.
- The wave-two corrections in section 4 shipped in the tagged
v0.4.0release. - External-oracle runs (multi-gigabyte wrf-python environments, live SHARPpy) are executed at audited commits rather than on every intermediate commit; the Rust workspace suite gates every commit.