Scope, validated regime, and known limitations¶
This page states, in one place, what aeolus1d is validated for, what it is not, and what it is not qualified for — so you can decide whether to trust a number before you rely on it. It is deliberately conservative. The detailed, reproducible evidence behind every claim here lives in the validation dossier; this page is the honest summary and the caveats.
aeolus1d is a 1D compressible-Euler pipe-network solver with engine,
powertrain, and aftertreatment modelling. It is pre-1.0, alpha-to-beta
software (PyPA Development Status :: 4 – Beta, since 0.4.0; it was
3 – Alpha before). We do not claim general
"parity" with GT-POWER / AVL / Ricardo WAVE as a product. Where the term
"parity" appears in project docs it means a specific, auditable thing —
validated competence on a named workflow at a stated tolerance, reproducible
from a checked-in case — never feature-count parity with a 30-year funded
commercial code.
Verification — are the equations solved correctly?¶
Dataset-independent checks that the numerics converge at their design rates:
- Spatial order (flux): HLLC + MUSCL/MP5 checked against the analytic Sod
solution (
tests/validation/test_sod_against_analytical.py) and an observed-order test on a smooth density wave (MUSCL ≥ 1.8;tests/integration/test_smooth_advection.py). The order is a lower bound away from smooth extrema, where a TVD limiter must clip. The order is also verified per field, not just density — a running-solver Richardson self-convergence on a smooth pulse where ρ, u, and p all vary independently recovers order 2 for each of density, velocity, and pressure (measured ≈1.98 / 1.94 / 2.01;tests/integration/test_running_order_multivar.py). - Classic high-order / strong-shock benchmarks: the literature reference
problems the basic tubes can't exercise —
Shu-Osher (MP5 must preserve the post-shock entropy-wave amplitude a
dissipative scheme erases; observed ~3.8× first-order),
Woodward-Colella interacting blast waves (density and pressure stay
strictly positive through the violent collision; the ρ≈6 spike forms at
x≈0.78), and the Abgrall moving γ-interface (see the variable-γ caveat
under Known limitations).
bench/{shu_osher,woodward_colella,abgrall_interface}.py, gated bytests/integration/test_euler_verification_benchmarks.py. - Species mass-flux scheme-consistency (telescoping invariant): a passive
scalar must advect on the same per-face mass flux the density was advanced
with, recovered by telescoping the scheme's own mass residual
(\(\dot m_i = \dot m_0 - \Delta x \sum_{j<i} R_{j,0}\)) from a boundary-face
anchor. A consistent anchor telescopes exactly to the scheme's true
right-boundary flux; the check pins
\(|\dot m_N - F_N| < 10^{-9}\) across all seven flux/EOS variants
(
first_order, MUSCL and MP5 each in scalar-γ / variable-γ / real-gas-EOS), and a teeth-test confirms the pre-#719 first-order anchor violated it by \(\mathcal O(1)\) (tests/integration/test_species_scheme_consistency.py). This is a structural-consistency check, not a convergence-rate one — it guarantees a uniform composition stays uniform across sharp gradients on every scheme. - Temporal order (each integrator): a per-
rkself-convergence study pins the formal order of all seven time integrators — IE ≈ 1, SSP-RK2 / BDF2 ≈ 2, SSP-RK3 ≈ 3, RK4 ≈ 4, RK5 / Radau-IIA(3) ≈ 5 (tests/integration/test_temporal_order.py). - Source-term discretisation (MMS): Method-of-Manufactured-Solutions proofs
that the source closures converge at the formal order (2 for MUSCL) and do
not without the source. The base energy-source path
(
tests/integration/test_mms_source.py) plus area variation, wall friction, and wall heat (tests/integration/test_mms_closures.py) — each re-derives the closure by hand so that a wrong-signed / wrong-coefficient / mis-coupled closure leaves an O(1) residual that fails to converge (asserted by a load-bearing control per closure). The catalyst reaction source is stiff, operator-split chemistry — its integrator order is verified separately intest_temporal_order.py, not by a spatial MMS. - Regression net: a golden-snapshot suite (
tests/regression/) freezes the final state of canonical cases and fails on any silent physics drift ≳ 1e-4.
The GCI machinery ships in aeolus1d.validation — grid_convergence_index
/ mesh_convergence_study (Roache / ASME V&V-20 three-grid apparent order +
Richardson extrapolation + a numerical-uncertainty band), and ScalarComparison
carries u_num / u_data (RSS'd into validation_uncertainty, with a
within_validation_uncertainty verdict). It is now run end-to-end on real
suites via avl_compare_common.gci_mesh_study (run any AVL case's headline
scalar at three mesh densities). Two contrasting results, both on committed data:
- DOC light-off T50 (C02001) — mesh-converged. A 25/50/100-cell study gives
GCI ≈ 0.07 %(u_num ≈ ±0.3 K); the reportedU_val ≈ ±5 Kis dominated by the digitization termu_data, and the model–AVL error (~2 K) sits withinU_val.run_validation.py --gcireports it; a@slowtest gates thatu_numstays small. - DPF regen peak substrate T (C02006) — mesh-converged, cleanly. A
15/25/40-cell study gives peak substrate T 662.1 → 662.8 → 663.2 K with
observed order
p = 1.0, in the asymptotic range (GCI ≈ 0.13 %,u_num ≈ ±0.8 K) — so the regen thermal peak is a trustworthy number (run_validation.py --gci; slow ~12 min). - SCR With-NO NO-conversion (C02002) — NOT mesh-converged. The same study
gives NO conversion 21.5 % → 12.6 % → 7.2 % at 25 / 50 / 100 cells:
GCI ≈ 140 %(u_num ≈ ±10 conversion-points) with an unphysical negative Richardsonh→0value. The coarse-mesh agreement is a numerical-diffusion artefact, so this quantity is reported, not quoted as validated (run_validation.py --gci; slow ~10 min).
Pending (tracked): rolling the --gci pattern out to the remaining quoted
numbers (the flux + area/friction/heat source paths are now MMS-covered).
Treat any single quoted number as mesh-specific unless it carries a U_val
— as the SCR case shows, a value without its mesh can be a pure diffusion
artefact.
When GCI does not apply. For the calibrated lumped-closure aftertreatment
cases (DOC Voltz light-off, SCR Temkin storage, C02006 DPF/CSF regen), a
grid-convergence index on the discretisation uncertainty u_num is
misleadingly small and not the honest error bar: per ASME V&V-20 the total
validation uncertainty u_val = √(u_num² + u_input² + u_D²) is dominated by the
model-form/data term u_D (the lumped rate constants were fitted to the AVL
trace), not by mesh. These cases are therefore labelled GCI-not-applicable —
calibrated in their pass_criteria.toml, and GCI is reserved for the handful
of numbers actually quoted in the docs where u_num is the meaningful term.
Validation — does it agree with reality?¶
The reference type matters as much as the number. We distinguish three:
| Workflow | Status | Reference type | Honest verdict |
|---|---|---|---|
| Engine breathing / manifold tuning (SI) | breathing verified; quantitative match re-cal owed | mixed — mendeley_ethanol_water is experiment; ricardo_ktm is code-to-code (reclassified 2026-08-30: its reference is Ricardo WAVE's own OEM baseline, not the chassis dyno — see that suite's README) |
The valve-coupling breathing physics is verified correct + conservation-pinned (ADR-0008, now default). The published-data quantitative match was calibrated against the legacy coupling and is mid-re-establishment: Ricardo-WAVE KTM 510 cc is 7/10 with the recommended --throttle --wall-heat invocation, re-measured 2026-08-30 on 9222699d (per-point table in that suite's README; the 6/10 recorded on 2026-06-09 stood here until then, and which point flipped is unrecoverable because that record kept only the aggregate) — 3/10 bare; was 8/10 legacy same-invocation; originally re-validated 2026-06-09: the single-point-calibrated idealised cam over-admits air across RPM (every point over-predicts torque), a per-RPM re-cal owed, gated on proprietary OEM cam data. Mendeley ethanol/water is 39/60 — the honest score: the earlier 44/60 (2026-06-09) was flattered by a knock-integral bug that silently retarded combustion 10° on this scaffold, bisected 2026-08-15 to the 2026-07-01 fix 643fc3fb (closure in BACKLOG_HISTORY.md; fixed-fuel suite, so the orifice coupling is ~1 %/near-neutral, no re-tuning) — the residual concentrates at 40–50 % water, where the measured gap is combustion deterioration under water dilution; the earlier "charge-cooling / constant-γ" diagnosis was implemented, measured, and retracted 2026-08-15 (charge cooling moves the prediction the wrong way — see the suite README). (An earlier note that the data was "local-only / not re-runnable in CI" was wrong; operating_points.csv is committed and runs in CI.) |
| Acoustics / NVH | validated | analytic + code-to-code | transmission-loss vs analytic + a GT-Suite TMM cross-check. Not yet experiment-validated. |
| Aftertreatment (DOC / SCR / TWC) | partly bench-validated | experiment (open synthetic-gas-bench) + code-to-code vs AVL Boost digitized traces | Corrected 2026-08-30. This row said "Not validated against lab / synthetic-gas-bench data", which had been false since heliyon_twc_co_oxidation landed: that suite reproduces the measured Table-11 CO-step OSC discharge durations of an open-access (CC BY) Heliyon 2024 synthetic-gas-bench study across three ageing states — and is predictive across two of them, since Omega_OSC_brnk is calibrated on the fresh state only while mild and severe follow from the paper's independent Table-13 site-density ratio. ornl_passive_scr adds measured NO→NH₃ selectivity-vs-λ (ORNL CLEERS 2014 / SAE 2016-01-0934) at trend level. Both are CI-gated and pass. What remains true: several rate constants are calibrated to AVL rather than predictive, doc_propylene_lightoff is code-to-literature (a T50 band, not a dataset), and the SCR NO-conversion mesh-calibration caveat below still stands. See docs/validation-dossier.md |
| Turbo matching | consistency check | self-consistency | shaft power-balance + map fidelity — the code agrees with itself, not with experiment. |
| Electrified drive cycle | consistency check | self-consistency | energy bookkeeping — again a self-check, not external validation. |
See the validation dossier for the per-operating-point numbers, the checked-in case behind each row, and how to reproduce them.
Blind-predictable vs structurally-blocked gates¶
A gate is only worth aiming a blind (un-tuned) prediction at if the physics it needs is actually present. We do not claim un-tuned predictive closure of any gate that is structurally missing physics; for those, the honest target is a calibrated match, labelled as such. Each v0.3.0-era gate is classified:
| Gate | Blind-predictable today? | Why |
|---|---|---|
| W1 breathing physics (mass conservation across the valve) | Yes — verified | Conservation-pinned by test_cylinder_valve_conservation.py (ADR-0008); no tuning involved. |
| W1 KTM / Mendeley quantitative match | No — calibrated | Single-point-calibrated cases; a blind per-RPM match needs OEM cam geometry (KTM) or charge-cooling physics (high-water Mendeley) not in the shipped case. |
| W2 turbo matching | n/a — self-consistency | Conservation/map-fidelity check, not a prediction of external data. |
W3 C02006 co_conv_cat |
No — CO calibration gap | The CSF CO/HC oxidation kinetics do exist (aeolus1d.closures.filter.csf_into) and are active on the Cat brick: c3h6_conv_cat matches AVL's Cat trace within ~16 % (gated at 30 %), which disproves the earlier "no CSF kinetics / structural" claim. Only co_conv_cat remains off (~77 % mean) — aeolus1d under-converts CO because the mass-transfer cap that keeps the QS steady-energy exotherm stable also caps CO conversion at AVL's lower band. A CO-oxidation calibration/stability gap, not missing physics; recalibration is deferred (pass_criteria.toml FOLLOWUP). |
| W3 C02006 Non-Cat channels (T, soot, Δp, conv) | partly — calibrated | The passive brick is calibrated to the AVL Non-Cat trace (cake-Darcy, A_o2 ×4); the soot burn-down is bi-stable (cliff vs MT-limited plateau) until an MT-limited rate cap lands. |
| EPW diesel breathing / IMEP_High / PFP | Yes — near-exact | With the orifice coupling (ADR-0008), vol-eff ~74 % and IMEP_High/PFP land within a few % at the anchor points; the blind 2nd-operating-point T_EXH check no longer generalises. |
| ↳ CORRECTED 2026-09-10 | T_EXH: no | The row above read "one blind 2nd-operating-point T_EXH check generalises (−5.6 % / −1.8 %)" until this date. Those figures predate 2026-09-06. test_blind_operating_point_T_EXH_generalizes is now xfail(strict=True) at −9.91 % against a 6 % gate (AVL 605.4 °C vs aeolus1d 545.44 °C), re-measured 2026-09-07 and reproducing the 2026-09-06 run to 0.05 °C — deterministic, so the drift is real. Two of the three calibration anchors are also outside the gated 40 K band (−47.09 K and −43.09 K). A refit was measured and REFUSED: the two blind ops want h = 57.1 and 196.3, a 109.9 % spread against a 25 % bound pre-registered in BACKLOG.md before any number existed. The breathing / IMEP_High / PFP claims in the row above are unaffected — only T_EXH is withdrawn. |
| EPW diesel BMEP / NOx | No — residual gaps | Pumping-loop residual leaves BMEP ~30 % low. The two former NOx blockers have since landed — the Zeldovich post-processor no longer raises (it escalates BDF→LSODA→Radau→equilibrium-envelope, aeolus1d.analysis.nox_post) and an opt-in dissociation-aware burnt-zone-T cap ([cylinder.combustion] burnt_dissociation) tames the constant-γ peak-T — but NOx itself is still uncalibrated/unvalidated. Neither residual is a conservation bug. |
| W4 acoustics | Yes — analytic | Reproduces the closed-form resonance + GT TMM in the plane-wave regime; not an external-experiment claim. |
| W5 electrified | n/a — self-consistency | Energy-bookkeeping check, not a prediction. |
The coupled DOC + DPF + SCR aftertreatment train remains a named v0.3.0 gate that is not yet closed (single-component aftertreatment is demonstrative; the coupled train is unvalidated).
Known limitations / not validated / known-wrong¶
Do not rely on aeolus1d for these without independent verification:
- The caloric model is a constant by default, and drifts at engine temperatures. Two separate paths, both measured 2026-08-01:
Ideal-gas path (203 of the 212 bundled cases). A single scalar gamma. For
N2 against NASA-7, gamma falls from 1.4005 at 300 K to 1.3139 at 1500 K,
so a constant 1.4 is 6.6 % high there. The consequence is larger than
that sounds: pressure is recovered as p = (gamma - 1) rho e, and the error
in gamma - 1 reaches 11 % at 800 K, 22 % at 1200 K and 31 % at 1800 K.
aeolus1d already has the fix — [pipe.species] per_cell_gamma = true
evaluates gamma per cell from NASA-7 at the cell's own temperature, cutting
the 1500 K error from 6.6 % to 0.9 %. It is off by default and used by
three bundled cases, and it requires a [pipe.species] block: a single-gas
pipe with no species tracking cannot use it at all.
The EPW diesel validation suite is one of the three: since 2026-08-10 its
exhaust pipes ship per_cell_gamma = true, after an A/B measured on one
commit came back net-better in both anchored and predictive mode. It is
paired with a re-fitted anchored-mode plenum h*A (550 -> 377 W/K) and the
pair must move together — see the suite README. The DD13 suite still
declares no species columns, so its exhaust-side numbers carry this error.
See tests/integration/test_caloric_model_accuracy.py.
Until 2026-08-01 simply adding the columns did not help on an engine,
which is why no engine case had them. A cylinder routed only its fuel
column; every other column fell back to the pipe's zero-gradient ghost
(which mirrors the runner back at itself) and, under the orifice valve
model, blowback mass entered the runner with no species credit at all. The
runner's composition was therefore diluted toward zero, and a zero-sum
composition makes mixture.gamma return exactly 1.0 — which zeroes the
pressure and collapses the HLLC fan into a ZeroDivisionError. The cylinder
now carries a general composition (next entry), so the columns can be added;
doing so on the validated diesel suites is a separate exercise, because it
changes their exhaust-side results.
- The cylinder's composition is a well-mixed single zone with
complete-combustion products. Since 2026-08-01 a cylinder keeps a mass
inventory of every non-fuel species column its ports touch, exchanges it
with the runners through both valve couplings, and converts burnt fuel into
stoichiometric CO2 + H2O with the matching O2 debit
(
aeolus1d.chemistry.fuel_stoichiometry, driven by the fuel's H/C atom ratio). What that does not model: no CO, no soot, no NO formed in the cylinder, no dissociation, and no burnt/unburnt zone split — the charge is one well-mixed volume, so the exhaust composition is a cycle-averaged product mixture rather than a resolved blowdown history. Rich operation is not modelled either: when the tracked O2 runs out the conversion scales back and reports the shortfall inCylinder.last_products_unconvertedinstead of forming CO. The inventory is advanced by an operator-split forward-Euler step on the stage-mean port rate — exact for SSP-RK2, first-order for SSP-RK3. Inert on any case whose pipes carry no non-fuel column, which is every bundled engine case. Seeexamples/91_cylinder_species_transport.toml.
The same stage-mean treatment applies to a plenum's fuel and species
inventories (Junction, EHRJunction). Until 2026-08-01 both integrated
the last RK stage's rate, which lost 6.6e-05 (N2) / 1.5e-04 (O2) / 6.7e-05
(fuel) of the tracked mass over 4 ms on a closed network — see
tests/integration/test_plenum_species_conservation.py. Volume-side
inventories are the recurring home of this mistake, so any new one should
use species_coupling.StageRateAccumulator rather than caching a single
rate.
Real-gas EOS path. The scalar cv has the same weakness (N2: 741 J/(kg K)
at 300 K rising to 946 at 1500 K, so a constant tuned near ambient is 27 %
low). Since 2026-08-01 [pipe.eos] accepts an optional cv_poly /
cv_T_min / cv_T_max polynomial; omitting it keeps the constant and is
bit-identical. Note the narrow reach: ideal-gas pipes never take the EOS
residual (has_real_gas is eos_kind != EOS_IDEAL), so cv_poly only
affects pipes configured with redlich_kwong / peng_robinson /
tabulated — 6 bundled cases, none of the validated suites.
- Choosing an EOS: cubics have an accuracy ceiling at high density.
Measured against NIST Chemistry WebBook nitrogen
(
tests/integration/test_eos_vs_nist_reference.py), max relative error:
| pressure @200 K | sound speed @200 K | pressure @300 K | |
|---|---|---|---|
ideal |
19.1 % | — | 0.52 % |
redlich_kwong |
0.80 % | 6.96 % | 1.55 % |
peng_robinson |
4.85 % | 9.53 % | 1.67 % |
tabulated (CoolProp) |
0.037 % | 0.025 % | 0.037 % |
Three things worth taking from that. Near the critical region a cubic is a
large win over ideal (0.8 % vs 19 %). In the near-ideal regime the cubics
are slightly worse than ideal, which is also cheaper — "real gas" does
not mean "more accurate everywhere". And the residual sound-speed error of
the cubics is the p-v-T form at liquid-like density, not the caloric
model: it grows with density and no cv(T) can remove it. When density
accuracy matters, use kind = "tabulated".
- A non-ideal EOS cannot be combined with species transport, and is now
refused at load time (2026-09-15).
Pipe.eos_paramsis one array for the whole pipe — oneR_gas, oneTc, onePc, oneomega— so a pipe whose composition varies cell to cell is evaluated with the wrong substance everywhere but one cell. Measured with Peng-Robinson in molar units and van der Waals one-fluid mixing (k_ij = 0), pressure at fixed(T, ρ):
| condition | error today | if only R were per-cell |
real-gas worth |
|---|---|---|---|
| exhaust, 700 K, 0.75 kg/m³ | 36.4 % | 0.05 % | 0.05 % |
| EGR cooler, 400 K, 1.6 kg/m³ | 36.5 % | 0.42 % | 0.25 % |
| wet exhaust H₂O/N₂, 700 K | 55.0 % | 0.21 % | 0.26 % |
| dense CO₂/N₂, 320 K, 50 kg/m³ | 43.8 % | 17.96 % | 13.99 % |
This sharpens the "real gas is not more accurate everywhere" point above
into a rule you can apply. Compare the last two columns: at every condition
an engine runs, the real-gas correction is worth 0.05–0.26 % while a
wrong composition costs 36–55 % — so on a compositionally graded exhaust
duct a cubic EOS is not merely unhelpful, it is two orders of magnitude the
wrong thing to spend accuracy on. Use kind = "ideal" there: its
p = (γ−1)ρe is composition-correct already, because gamma_pad is built
per cell from the mixture's own cp and R_mix.
Lifting the restriction needs per-cell EOS parameters — eos_params (P,)
→ (N, P) across the 28 @njit kernels that take a params array, plus
k_ij as a new input surface. Filed in BACKLOG.md with this measurement.
Note that per-cell R alone does not do it: it is a ~99 % win only in
the regime where the cubic buys nothing, and leaves 18 % in the dense regime
where the cubic is the point.
- Diesel exhaust temperature is no longer ~10× high — that was an
artefact of the under-breathing valve bug, closed by the orifice coupling
(ADR-0008). With the corrected default + a physical exhaust wall-heat
coefficient, EPW
T_EXHis no longer 10x high. CORRECTED 2026-09-10: this bullet read "lands within ~±13 % mid-range and a blind 2nd-operating-point check generalises (−5.6 % / −1.8 %)" until this date, on figures that predate 2026-09-06. Re-measured, the blind op reads −9.91 % against a 6 % gate and the test isxfail(strict=True); in predictive mode — the mode a user without the vendor CSV actually runs —T_EXHis report-only and NOT VALIDATED, reading −44.9 / −113.4 / −80.9 K at the 1200 / 1500 / 1800 anchors (measured 2026-09-07; −38.6 / −109.0 / −73.2 K re-measured 2026-10-11,--ops 3predictive onmain56dad407, with the 1500 rpm op not plateau-certified). Treat dieselT_EXHas indicative. The residual diesel gaps are the pumping loop (BMEP ~30 % low) and NOx (a frozen constant-γ burnt-zone back-solve over-predicts peak T at high load, inflating the Zeldovich estimate — mitigated but not closed by the opt-in[cylinder.combustion] burnt_dissociationcap and the two-zonegamma_burnt/gamma_unburntpath); treat diesel NOx and BMEP as indicative, not validated. - EPW diesel: cyclic convergence / gas-exchange under-convergence is the
open driver. The EPW steady suite runs a short settle protocol
(
N_CYCLES_SETTLE + 1 = 2cycles, last cycle read) — adequate for the mechanical outputs since the orifice coupling (breathing converges by ~cycle 4–5, trapped mass matching AVLMF_IAto ~10 %, vol-eff ~74 %), but the gas-exchange loop remains under-converged:IMEP_Lowplateaus several bar too negative (−1.7 / −3.3 / −4.4 bar at the anchors vs AVL ≈ −0.5), which is precisely the residual behind the ~30 % low BMEP above. Quoted EPW numbers are protocol-specific until a cycle-convergence detector + converged-state wall-heat re-tune land (tracked inBACKLOG.md). - Quasi-steady solver (
solver = "quasi_steady") operating limits. The QS SIMPLE low-Mach mode is validated on DPF/filter aftertreatment pipes (the C02006 keystone) — not across all pipe kinds and regimes:- Catalyst pipes can crash the QS flow solve. A DOC-style catalyst
pipe (two-phase catalyst + Darcy-Forchheimer porous + DOC oxidation
kinetics) can drive the SIMPLE solve to a degenerate outlet state —
a
ZeroDivisionErrorin the pressure-outlet kernel with ρ/p ≈ 0 at the outlet cell. Only DPF/filter pipes are QS-validated today. - Strong exotherms can crash the QS SIMPLE kernel. A
high-temperature regeneration runaway (peak inlet ≳ 740 K combined
with heavy soot loading) drives the SIMPLE solve to a
ZeroDivisionErroras a cell's density → 0 when the steady-energy temperature spikes. Gentle passive regen (C02006, ≤ 693 K) is stable; high-soot active-regen drive cycles can crash until the kernel guards the singular regime. - DPF cake soot-drag is absent from the QS momentum equation — only the pipe's porous Darcy-Forchheimer drag is carried, so QS pressure drop under heavy cake loading reads low.
- No mixed engine + aftertreatment multi-rate network — QS is whole-network: an engine-breathing section cannot run compressible while the aftertreatment section runs QS in the same case.
- Species mass conservation under a fast light-off (transient). Reaction products absent from the inlet feed are correctly flushed at steady state (ΣY ≈ 1; the inflow-ghost fix, ADR-0009), but a sharp gas-phase light-off shows a bounded transient ΣY dip (≈ 0.96) before recovering to 1.0 — an operator-split transient. QS validation metrics are steady-state, so this is a numerical note, not a result caveat.
- Catalyst pipes can crash the QS flow solve. A DOC-style catalyst
pipe (two-phase catalyst + Darcy-Forchheimer porous + DOC oxidation
kinetics) can drive the SIMPLE solve to a degenerate outlet state —
a
- Coupled aftertreatment train. A cross-coupled DOC + DPF + SCR drive-cycle is a named v0.3.0 release gate that is not yet closed. Single-component aftertreatment is demonstrative (above); the coupled train is not validated.
- SCR NO-conversion is mesh-calibrated, not mesh-converged. The AVL C02002 With-NO conversion matches AVL (30.4 % vs 30.6 %) only at its 50-cell calibration mesh; refining monotonically lowers it (45 % @ 25 → 30 % @ 50 → 19 % @ 100, change not shrinking — numerical diffusion on a coarse mesh over-predicts conversion). The converged value is lower than AVL's, so the agreement is a calibration artefact. See the C02002 suite README.
- Any temperature-critical claim outside the validated SI-breathing regime (light-off prediction, wall/solid temperatures, heat rejection) should be treated as indicative until a per-quantity uncertainty is published.
- Real-gas (PR/RK/tabulated) and network acoustic eigenfrequencies are exercised by tests but not validated against external data.
- Variable-γ pressure equilibrium — default recovery is Karni-consistent
(convergent). The per-cell-γ HLLC (
use_per_cell_gamma) recovers each cell's temperature self-consistently from the conserved internal energy (fixed point onT = (γ(T)-1)·e/R) — thegamma_T_from_energypath, now the default. Per-cell γ is opt-in per pipe via[pipe.species] per_cell_gamma = true(the recovery mode is the companiongamma_T_from_energyflag, default true); seeexamples/87_multicomponent_variable_gamma_shock_tube.toml. With it, an advected γ-jump interface at uniform p, u stays near pressure equilibrium: the moving-contact oscillation on an N₂|CO₂ contact is <0.1 % pressure / sub-% velocity and converges under mesh refinement (p-error ≈1.3e-3 → 2.4e-4 from N=100 → 800) — i.e. genuine truncation error, not a consistency defect. Settinggamma_T_from_energy=Falserestores the legacy scalar-mean-γ T recovery, which exhibits the classic Karni (1994) failure mode: a mesh-independent ~1.3 % / ~3.3 % oscillation (bounded, no blow-up). For uniform composition the two recoveries are algebraically identical (e = p/((γ−1)ρ)), so scalar-γ and uniform-mixture results — including every golden snapshot — are bit-identical either way; only sharp multicomponent γ-contrasts shift. Pinned both ways bytest_euler_verification_benchmarks.py::test_abgrall_interface_*. A fully quasi-conservative (Abgrall 1996) flux that would drive the residual to exact round-off is deliberately not pursued: it requires advecting1/(γ−1)as an independent, non-thermodynamic field, which is incompatible with aeolus1d's physicalγ = mixture_γ(T,Y)wherever composition mixes or temperature changes (i.e. every reacting cell) — the standard pressure-equilibrium-vs- thermodynamic-consistency trade-off, resolved here in favour of thermodynamic consistency with a convergent residual. - Calibration vs prediction: a coefficient tuned to reproduce one dataset
is calibration, not predictive validation. Aftertreatment rate constants
and some wall-heat coefficients are calibrated; the KTM
flow_nuresult (no tuned heat-transfer coefficient) is the predictive gold standard. - Bit-reproducibility is scoped to x86-64. (Stated here 2026-08-29; the
decision itself dates from 2026-07-31.) The golden regression net asserts
bit-identity under
AEOLUS_FASTMATH=0, and that guarantee is claimed on x86-64 only. Cross-architecture equality was never attainable for the chemistry and EOS rows — libmexp/logdiffer by ~1 ulp between architectures — so it was scoped out rather than pursued. aeolus1d is expected to run correctly elsewhere; what does not carry across architectures is the bit-for-bit reproduction of a stored snapshot. The decision was recorded inMATURITY.md(R1) anddocs/ci-runner-runbook.md, neither of which a user reads — so the limitation R1 was closed on the strength of being "stated" was, until this entry, not stated anywhere in the user-facing documentation.
Experimental / research seams (not part of the supported surface)¶
These ship in-tree as research scaffolding. They are not validated, not covered by the regression net, and may change or be removed without a deprecation cycle:
- the
mlextra (scikit-learn/torch) and the learned-closure NPZ seams (py_junction_bc = "learned_npz",turbo_map = "learned_npz"); - the PINN closure adapter (
aeolus1d.closures.pinn, Python-API only); - the ISAT in-situ tabulation prototype (
aeolus1d.closures.isat_chem).
The full aftertreatment closure set (DOC / DPF / SCR / SCRF / TWC) is demonstrative / calibrated as noted above — usable for exploration, not for regulatory or certification work.
Deprecation & stability policy¶
What you may rely on staying stable, and how it changes when it must.
The supported surface is:
- the
aeolus1dCLI — command names, their documented flags, and exit codes (CLI reference); - the TOML case format — every field documented in
run settings and the closures reference, versioned by the
schema_versionmarker (the first key of the[case]block) with an explicit migration path (ADR-0007); - the documented Python API — exactly the symbols rendered under
API reference (driving the solver, runtime objects, the
closure registry). Undocumented modules,
_-prefixed names, and internal kernels are not part of it and may change at any time; - the closed closure / component registry names a case file selects by
string (
aeolus1d.closures.registry).
Everything under Experimental / research seams above is explicitly outside this surface and may change or vanish without notice.
Pre-1.0 (current). aeolus1d is 0.x; under SemVer, 0.x grants no
API-stability guarantee. In practice we hold a stronger line than that minimum:
- Breaking changes to the supported surface happen only between minor
versions (
0.2 → 0.3), never in a patch (0.3.0 → 0.3.1), and every one is called out indocs/changelog.md. - Where a change can be made backward-compatible, it is. A renamed or
moved public symbol keeps a working alias that emits a
DeprecationWarningfor at least one minor cycle before removal; the warning names the replacement. A removed CLI flag is accepted-and-warned for the same window. - The TOML case format never breaks silently. An older
[case].schema_versionis either auto-migrated on load or rejected with a clear, actionable error — it is never mis-parsed against the current schema (ADR-0007). A field's meaning does not change under a fixedschema_version. (An unversioned deck is treated as version 1.)
How a deprecation is surfaced — all three, so it is impossible to miss:
- a runtime
DeprecationWarningat the call/parse site, naming the replacement; - a Deprecated entry in
docs/changelog.mdfor the release that introduces it, and again for the release that removes it; - a note in the affected symbol's / field's documentation.
Post-1.0 (intended). Once 1.0 ships, aeolus1d follows
SemVer strictly: breaking changes to the supported
surface only on a major bump, and a deprecated name warns for at least one
minor release before removal in the next major. The experimental-seam
carve-out persists — those never enter the compatibility contract until they
are explicitly promoted (documented + regression-covered).
Fitness for use¶
aeolus1d is provided for research and engineering exploration. It is not
qualified for safety-critical, certification, regulatory, or homologation
use, and carries no warranty (it is MIT-licensed — see the LICENSE
file at the repository root). You are
responsible for independently verifying any result you depend on. If you find
a case where a documented-validated workflow disagrees with experiment, please
open an issue — that is
exactly the feedback the validation dossier exists to absorb.