Run / case settings (all CaseHeader fields)
Run / case settings — exhaustive [case] reference¶
(Path: docs/run-settings.md)
Every key the TOML loader accepts under the top-level
[case]table, grouped by concern. Mirrors the GUI's Basic / Advanced split: the routine knobs inCaseHeader.BASIC_FIELDSare documented first in each section; everything else lives under collapsed "Advanced settings…" callouts so the casual reader can skim and the power user can drill.Source of truth:
src/aeolus1d/io/schema.py. If a row here disagrees with the dataclass docstring, the dataclass wins — please open a docs PR with the fix.
0. Conventions¶
- Default is the schema default applied when the key is absent. A
[case]block need only list the knobs that differ from default — the loader fills the rest. (aeolus1d expand --fullmaterialises every key explicitly if you want the fully-specified form;aeolus1d expandalone rewrites a case down to its minimal compact block.) Misspelled keys are still rejected. - Type uses Python notation;
Literal[...]enumerates the allowed string arms. - Touch when describes the realistic trigger to change the value, not its theoretical meaning.
- Banner-surfaced = the value appears in the one-shot solver-settings banner at run start
(suppressed by
AEOLUS_QUIET_SETTINGS=1). - Inert when = combinations that silently no-op the flag (e.g. QS knobs ignored under explicit RK). See Section 12 for the full table.
CaseHeader.BASIC_FIELDS (the GUI's "always-shown" set) is:
name, t_end, cfl, scheme, limiter, rk, solver, adaptive_dt, dt_fixed, chem_solver.
1. Identity and time horizon (BASIC)¶
| Field | Type | Default | Touch when |
|---|---|---|---|
name |
str |
"case" |
Always — the output HDF5 and run banner echo it. |
t_end |
float (s) |
0.0 |
Always — solver no-ops at 0.0. |
2. Numerical scheme — flux, limiter, time integrator (BASIC)¶
| Field | Type | Default | Touch when |
|---|---|---|---|
scheme |
Literal["first_order", "muscl", "mp5"] |
"first_order" |
"muscl" for the standard 2nd-order baseline; "mp5" for spectral-quality smooth runs. Applies to single-pipe and network cases — until 2026-08-03 build_network read this key only to size the ghost width and never passed it to the pipes, so every junction-network case silently ran first-order while the banner reported the requested scheme. |
limiter |
Literal["minmod", "van_leer", "none"] |
"minmod" |
"van_leer" for slightly more dissipative shocked runs; "none" only for smooth flow. |
rk |
Literal["ssp_rk2","ssp_rk3","rk4","rk5","implicit_euler","bdf2","radau_iia"] |
"ssp_rk2" |
rk4/rk5 for smooth-flow accuracy (NOT TVD — never on shocked runs); implicit flavours (implicit_euler, bdf2, radau_iia) to unlock CFL > 1. |
cfl |
float |
0.5 |
Lower for stability; raise toward 0.9 once converged; ≫1 only under an implicit rk. |
hllc_low_mach |
bool |
False |
Phase-24b low-Mach corrected HLLC flux. First-order ideal-gas uniform-area path only. |
Advanced settings…
Field Type Default Touch when cfl_ramp_startfloat1.0Soft-start CFL — an absolute CFL (not a multiplier), interpolated linearly from cfl_ramp_startatt_startup tocflatt_start + cfl_ramp_duration. Checked likecfl(positive; ≤ 1 with an explicit rk). Apply to transient-mass-flow-inlet kicks where the configured CFL would overshoot on step 0.cfl_ramp_durationfloat(s)0.0> 0enables the ramp;0.0keeps step-0 behaviour unchanged. While the ramp is active its elapsed fraction (0 → 1) scalesadaptive_cfl_max, clamped up toadaptive_cfl_min.dt_fixedfloat(s)0.0BASIC — > 0overrides CFL-driven Δt every step. No stability guard; user is responsible for keeping below the acoustic CFL on explicit RK. Use for fixed-step studies (Cruise-M / GT-Suite imports), FFT-locked sensitivity sweeps, or FMU co-sim with an external master clock.dt_hrr_fractionfloat0.0~0.1bounds the per-step fractional change in gas energy / soot loading / cake temperature; min'd with the acoustic-CFL Δt. Turn on for DPF regen and catalyst light-off transients — kills the heat-release runaway that diverges high-CFL updates (AVL Cruise-M C02006 regen ignition). On the quasi-steady driver the meaning of the bounds shifts:0.0applies a0.1safety floor (an un-capped QS macro step can stride over a light-off / regen transient), and a negative value disables the cap entirely so Δt rises to the advective-CFL /qs_dt_maxceiling — use it for a case known to be lit-off and steady (e.g. a hot TWC brick held at temperature), where the floor would otherwise pin Δt at ~µs the whole run.dt_heat_fractionfloat0.0> 0caps Δt so a constant-T_wallcell's explicit gas-temperature relaxation can't overshoot the wall in one step (Δt ≤ dt_heat_fraction·ρ·cv·D_h/(4h)) — a cheap stability guard for low-ρ / high-hcells the acoustic CFL under-resolves.1.0is the exact no-overshoot limit;0.5–1.0adds margin. Only the[pipe.heat] kind = "constant_twall"closure is bounded (flow-Nu / EGR / CAC closures are self-limiting). Explicit network + Strang drivers only — the QS (steady energy) and implicit (unconditionally stable) drivers ignore it.
3. Adaptive Δt and error control¶
| Field | Type | Default | Touch when |
|---|---|---|---|
adaptive_dt |
bool |
False |
BASIC — flip on for implicit rk runs that span large CFL ranges. Inert under explicit RK. |
adaptive_cfl_min |
float |
0.5 |
Lower bound the controller can shrink to. |
adaptive_cfl_max |
float |
50.0 |
Upper bound the controller can grow to; raise when the case can tolerate large CFL (long pipes, slow-transient flows). |
adaptive_target_iters |
int |
4 |
Newton-difficulty target for the heuristic controller — controller grows Δt when Newton finishes in fewer iters, shrinks when it needs more. |
adaptive_error_control |
bool |
False |
True local-error Δt control via Richardson step-doubling (one step vs two half-steps), held near adaptive_atol + adaptive_rtol·|U|. Only valid for one-step implicit stencils (implicit_euler / radau_iia); raises on BDF2. |
adaptive_atol |
float |
1.0e-6 |
Absolute LTE tolerance for the error-control path. |
adaptive_rtol |
float |
1.0e-4 |
Relative LTE tolerance for the error-control path. |
dt_fixed |
float (s) |
0.0 |
BASIC — see Section 2 Advanced. |
dt_hrr_fraction |
float |
0.0 |
See Section 2 Advanced. |
dt_heat_fraction |
float |
0.0 |
See Section 2 Advanced. |
4. Implicit Newton — tolerances + chord + parallelism¶
Cross-link: Implicit-solver reference for the path-selection decision table; numerics.md for the chord / JFNK negative-result story.
| Field | Type | Default | Touch when |
|---|---|---|---|
newton_tol |
float |
1.0e-8 |
Loosen for transient stability; tighten for accuracy-critical runs. |
newton_max_iters |
int |
8 |
Hitting the cap triggers a Δt-halving retry. |
chord_max_age |
int |
2 |
0 recovers the legacy full-Newton path bit-exactly. |
chord_rate_threshold |
float |
0.5 |
Convergence-rate ceiling above which the next iter forces a Jacobian refresh. Loosen to ~0.7 for stiffer cases where the chord ages faster than expected. |
parallel_pipes |
bool |
False |
Per-pipe Newton on a thread pool. The Newton kernels are not nogil, so threads serialize on the GIL — not a speedup today; explicit opt-in only. Ignored on single-pipe networks. |
parallel_pipes_auto_threshold |
int |
0 |
Networks with at least this many pipes auto-enable threading even when parallel_pipes=False. 0 (default) disables the auto path — the kernels hold the GIL, so the old auto-enable at 12 added thread overhead with no parallelism. |
5. Picard outer loop (implicit network)¶
Cross-link: Phase I-Y Picard outer loop.
| Field | Type | Default | Touch when |
|---|---|---|---|
picard_max_iter |
int |
3 |
1 is block-Gauss-Seidel without iteration (the pre-I-Y behaviour); raise on coupled-junction networks. Inert on plenum-only networks (Picard exits at iter 2 either way). |
picard_tol |
float |
1.0e-6 |
Ghost-state convergence on pipe ↔ component coupling. |
adaptive_picard |
bool |
False |
Per-step controller — grows budget when the budget saturates, shrinks after adaptive_picard_n_easy easy steps. |
adaptive_picard_min |
int |
1 |
Lower bound on the controller. |
adaptive_picard_max |
int |
8 |
Upper bound on the controller. |
adaptive_picard_n_easy |
int |
5 |
Easy-step count (steps that exited at iter 1) before the controller shrinks the budget. |
6. Solver routing — explicit / Strang / quasi-steady (BASIC: solver)¶
| Field | Type | Default | Touch when |
|---|---|---|---|
solver |
Literal["explicit","strang","quasi_steady"] |
"explicit" |
"strang" for Strang-split chemistry (intended for pipes carrying a py_reaction — without one it degenerates to pure transport); "quasi_steady" for low-Mach AT drive cycles (SIMPLE pressure-projection flow + forward-Euler slow states, Δt bounded by HRR instead of acoustic CFL). |
"quasi_steady" targets reacting AT networks, but the hard gate is topology, not
chemistry: the driver needs a prescribed low-Mach layout with a pressure anchor
(a far pressure_outlet) and raises NotImplementedError at run start if none is
found (the plenum pressures would float). It does not reject a case merely because no
pipe carries a py_reaction, and "strang" has no reaction gate at all — build succeeds
either way. See low-mach-at-plan.md for the driver architecture.
The QS driver also refuses cylinders and EHR junctions by name, at run
start. A Cylinder duck-types as a junction — it has ports that resolve — so a
combined engine + aftertreatment case switched to quasi_steady used to connect
up and run to completion. But the driver deliberately never applies the fuel /
valve step, so piston motion, valve events and combustion all vanished and the
cylinder acted as a constant-pressure node: meaningless engine-side numbers,
exit code 0. Use an explicit rk for anything containing a cylinder. (EHR
junctions carry an integrated per-port neck velocity the driver never advances,
so they are refused for the same reason.)
A QS macro step is also capped at the next transient-BC breakpoint. The steps
are the largest of any driver — qs_dt_max defaults to 1 s, and qs_dt_max = 0
means "the whole run" — and the BCs are sampled once per step, so a step that
spanned a schedule transition simply never saw it.
7. Quasi-steady (QS) driver — flow + chemistry sub-cycling¶
Cross-link: low-mach-at-plan.md, phase-iy-picard.md for the sign-bracket-bisection guard against the secant-Newton fixed-point bias.
7.1 Step controls¶
| Field | Type | Default | Touch when |
|---|---|---|---|
qs_dt_init |
float (s) |
1.0e-4 |
Bootstrap macro step before the HRR controller (dt_hrr_fraction) engages. |
qs_dt_max |
float (s) |
0.0 |
Hard cap on the macro Δt. 0.0 = no cap beyond t_end. |
qs_cfl_adv |
float |
0.5 |
Advective CFL ceiling: macro Δt ≤ qs_cfl_adv · min(dx / |u|). 0.5 is safe for explicit species advection; 1.0–2.0 typically works for reactive-AT (species step is 1st-order upwind, stable up to CFL = 1). Bigger means fewer macro steps and proportionally fewer chemistry calls — a ~4× wall-time lever on chemistry-dominated cases. Validate species conservation past 1.0. |
qs_steady_energy |
bool |
True |
Steady spatial energy balance (∂E/∂t→0). Removes the regen-ignition Δt collapse that blocked long drive cycles. Set False only to restore the legacy explicit enthalpy advection + temperature floor. |
qs_surface_q_rxn_into_energy |
bool |
True |
Surface-chem HRR (pipe.q_rxn) lagged into the steady-energy source. Safe by construction via the semi-implicit catalyst gas-side fold + a capacitance limiter (QSNetworkConfig.q_rxn_max_dT_per_step, a fixed 100 K internal cap — not a [case] key). Set False only as a diagnostic A/B. |
qs_surface_q_rxn_into_substrate |
bool |
False |
Route the surface-chem HRR (pipe.q_rxn) onto the catalyst substrate T_solid (dT_s += q_rxn/(ρ_s·c_s)) instead of the gas. The gas then picks it up through the semi-implicit gas↔solid fold, bounded by the substrate heat capacity — so the gas-side capacitance limiter (the fixed 100 K q_rxn_max_dT_per_step cap) isn't needed for this term. Only applies to pipes with a two-phase catalyst (two_phase_heat / two_phase_heat_var_nu); a q_rxn pipe without one falls back to the gas overlay (warned once). Default False = bit-identical gas-overlay behaviour. |
7.2 Junction Picard (QS)¶
| Field | Type | Default | Touch when |
|---|---|---|---|
qs_picard_tol |
float |
1.0e-6 |
Strong-drag (porous catalyst / DPF) networks converge to ~1e-8 relative ṁ noise; bare-duct networks float at ~1e-5. Loosen to 1e-4 for networks with bare ducts upstream of porous anchors. |
qs_picard_max_iter |
int |
100 |
Raise for larger networks where the FD-Newton's linear off-diagonal coupling needs more outer iterations. |
qs_picard_relax |
float |
0.7 |
Newton-damping in jp_new = jp − relax · net / slope. 0.7 is the original design value (near-steady cases); reduce to 0.3 on sharp inlet transients (TWC ṁ step-change), 0.1 on stiff regimes. Belt-and-braces with the sign-bracket-bisection guard (MR !131). For 2-cycle diagnosis use scripts/diagnose_qs_picard_2cycle.py; for post-mortem trace analysis use scripts/analyze_qs_picard_trace.py. |
qs_junction_coupled_newton |
bool |
False |
The default scalar Picard solves each junction pressure independently — fine for serial chains, but it diverges on parallel branches / strongly-coupled multi-junction networks (it ignores the off-diagonal ∂net_j/∂p_k coupling). True switches to a coupled finite-difference Newton that solves the whole junction-pressure vector at once (parallel SCR banks, split/merge collectors, dense junction chains). Off by default so validated cases stay bit-exact; flip on for imported networks with parallel branches. Superseded by qs_junction_loop_flow when both are set. |
qs_junction_loop_flow |
bool |
False |
EXPERIMENTAL (drag-model loop head-loss). Loop-flow / null-space junction solve (Abraham & Stoianov 2016; Hardy Cross): solves the flows in the incidence matrix's cycle space (well-conditioned SPD loop-Newton) and recovers junction pressures, then re-solves each pipe ṁ-driven at its loop flow. Robust on looped / parallel-branch networks with extreme resistance contrast where the nodal coupled-Newton stalls (the Cruise-T cas1 dual SCR-bank import). Takes precedence over qs_junction_coupled_newton. Off by default. |
qs_flow_reg_velocity |
float (m/s) |
0.0 |
Low-flow head-loss regularization threshold u_reg (Gorev 2013; Elhay & Simpson 2011). A purely quadratic (Forchheimer) pipe has dΔp/dṁ → 0 as ṁ → 0, so the junction Jacobian goes singular on networks with low-/zero-resistance elements (plenums, restrictions). u_reg > 0 adds a small LINEAR drag below this velocity, giving a finite non-zero slope at zero flow (negligible at physical u ≫ u_reg) and restoring a well-conditioned Newton step. 0.0 (default) is bit-identical. |
qs_flow_reg_alpha_floor |
float (1/m) |
1.0 |
Forchheimer-equivalent α floor applied during regularization (when qs_flow_reg_velocity > 0) so genuinely zero-drag pipes also get a non-singular slope at zero flow. |
7.3 QS chemistry super-cycle¶
| Field | Type | Default | Touch when |
|---|---|---|---|
qs_chem_settle_tol |
float |
0.0 |
Energy-side gate for the QS chemistry super-cycle: forces chem step while lagged HRR per-step fractional change > tol. Typical ~1e-2 (1%/step). Only consulted when chem_every_n > 1. |
qs_chem_coverage_settle_tol |
float |
0.0 |
Coverage-side gate: forces chem step while max|Δθ| over the last chem fire > tol (θ ∈ [0,1], absolute per-fire change). Independent of the energy gate — fires on EITHER. Needed for hot-catalyst regimes where HRR ≈ 0 but oxygen-storage θ is still relaxing (energy gate alone would degrade θ / NOx fidelity). |
qs_chem_fused_rhs |
bool |
True |
NEW v0.9 default ON — @njit fused Brinkmeier-TWC LSODA RHS in _qs_surface_chem_step. Bit-equivalent to the Python rhs (LSODA takes the same step sequence on identical RHS values). 2.08× measured speedup on AVL Cruise-M MFR_transition_SC630 (97.7 s → 47.0 s on a 12 s sim; 8.15× → 3.92× realtime). Non-Brinkmeier closures auto-fall-back to the Python rhs (no per-case opt-out needed). To confirm the fused path is firing on a given run, use scripts/profile_post_fused.py. Set False only to fall back if a regression surfaces. Cross-link: perf-history.md, changelog v0.9. |
8. Chemistry solver — integrator + tolerances (BASIC: chem_solver)¶
| Field | Type | Default | Touch when |
|---|---|---|---|
chem_solver |
Literal["auto","lsoda","bs_jit","bs_brinkmeier","linop","fixed_step","numbalsoda"] |
"auto" |
"auto" routes Arrhenius-only single-species pipes (py_reaction_params set, no multi/surface/extras) to bs_jit; everything else to lsoda. Override only when profiling. bs_jit is the @njit Bader-Deuflhard semi-implicit Bulirsch-Stoer kernel; bs_brinkmeier is the cell-parallel variant; linop is the experimental linearised surrogate; fixed_step is QS-only (non-stiff mechanisms only); numbalsoda runs the general reaction="mechanism" surface path per-cell through numbalsoda's nogil LSODA in a prange (GIL-free alternative to the batched scipy LSODA; needs the optional numbalsoda package, falls back to scipy for any pipe it doesn't handle). |
chem_rtol |
float |
1.0e-6 |
Tighten only for stiff oscillating mechanisms (Brinkmeier OSC). |
chem_atol |
float |
1.0e-9 |
Companion to chem_rtol. |
chem_fixed_dt |
float (s) |
1.0e-3 |
Only with chem_solver="fixed_step" (QS driver, non-stiff mechs only — not for stiff oxygen-storage TWC). Sub-step size; n_sub = round(macro Δt / chem_fixed_dt) ≥ 1. |
chem_be_iters |
int |
3 |
Backward-Euler Newton iterations per chem_fixed_dt sub-step (fixed-step path). |
chem_every_n |
int |
1 |
Lie super-cycle shared by every chemistry driver (explicit / Strang / implicit-network / quasi-steady). Final super-cycle is always closed before the loop returns. Catastrophic at light-off without a settle gate on the QS driver (measured: chem_every_n=4 at light-off took 2.25M steps) — pair with qs_chem_settle_tol / qs_chem_coverage_settle_tol there. |
chem_interval |
float (s) |
0.0 |
Wall-clock companion to chem_every_n, shared by every chemistry driver. Forces a chem fire every N simulated seconds regardless of macro-step count — decouples chemistry from macro Δt growth. Fires on EITHER (step counter OR interval). Typical 1e-3–1e-1 s; track the chemistry timescale. |
chem_parallel |
bool |
False |
prange-dispatch BS-Brinkmeier across cells. Bit-exact regardless of thread count (Strang frozen-state). Flip ON only for large pipes (≥ ~50 cells) on idle hardware — Numba thread-launch overhead beats per-cell work on smaller pipes and CI / runner contention masks the parallel win. |
chem_linop_model |
str |
"" |
EXPERIMENTAL — path to a trained two-stage linearised surrogate .npz (from ml_chemistry/train_linop.py). QS driver, Brinkmeier-TWC pipes only. Empty disables (banded LSODA). See tutorials/chem-surrogate-training.md. |
chem_linop_tol |
float |
0.4 |
Surrogate per-step relaxation cap 1 - exp(-k dt); cells above this fall the pipe back to LSODA so the step is bounded-error. |
9. Implicit "folds" — closures inside the Newton residual¶
These flags fold source terms historically applied by the post-Newton Lie split into the
implicit Newton residual + Jacobian. Trade per-iteration cost for cleaner convergence and
bit-stable high-ΔT startups. All inert under explicit RK or when solver = "quasi_steady".
| Field | Type | Default | Touch when |
|---|---|---|---|
catalyst_in_newton_residual |
bool |
True (v0.9 flip, Tier A) |
Constant-h two_phase_heat only (variable-Nu still routes through the post-Newton Lie split). Removes the implicit/explicit ringing that produces reverse-flow artefacts on high-ΔT-gas startups (973 K gas, 298 K wall). Set False per case to recover the pre-v0.9 Lie-split behaviour. |
catalyst_nu_laminar_floor |
bool |
False (validation-sensitive) |
Floors the variable-Nu (two_phase_heat_var_nu) Sieder-Tate heat-transfer Nusselt at the square-channel laminar asymptote (2.98) so the gas↔solid coupling doesn't vanish at low Re (idle / soak / cool-down), where Nu = 0.027·Re^0.8·Pr^(1/3) → 0. The Hawthorn correlation already carries this floor additively. Reference: Millo et al. (2020), 1D Simulation Models for Aftertreatment Components, Table 11.1 (Graetz-Nusselt asymptotes: square 2.977 const-T / 3.089 const-flux). Default False — 15 shipped cases use sieder_tate (every validated c02xxx AVL case + the Heliyon OSC gate), so flooring shifts their light-off / cool-down timing; flip per gating suite, re-run c02001/c02002, record before/after. |
dpf_cake_in_newton_residual |
bool |
True (v0.9 flip, Tier A) |
Folds the linear-in-u s_mom = −μ·u·soot·R_cake + quadratic-in-u Forchheimer s_E = u·s_mom. The regen heat injection folds separately (dpf_regen_in_newton_residual). Set False per case to recover the pre-v0.9 Lie-split path. |
dpf_regen_in_newton_residual |
bool |
False (opt-in) |
Folds the direct-path DPF regen gas-energy source dH·rate (Phase-8f.5 direct-to-gas Arrhenius soot oxidation) into the implicit Newton residual + a finite-difference Jacobian (the rate carries an Arrhenius × NO-inhibition × MT-cap × two-fraction chain — FD is sign-correct by construction, exact-to-2nd-order, vs a hand-derived analytic form). soot_loading + species frozen during Newton; the post-step Lie split then skips its direct-path gas-energy add. Only the direct path folds — the thermal (T_cake) path's linear cake↔gas cooling stays on the Lie split. Implicit-driver only; explicit / QS always Lie-split. Default False ⇒ bit-identical; turn on for implicit DPF-regen runs where Arrhenius self-heating drives Newton ringing. |
substrate_wall_in_newton_residual |
bool |
True (v0.9 — re-affirmed 2026-05-31 by the !137 audit; bit-identical on non-DPF cases) |
Folds the gas-side energy source q_wall = h_wall·(T_wall − T_gas). Both residual and Jacobian augmenters early-return when pipe.filter is None, so non-DPF cases are bit-identical to False. Set False only as a diagnostic A/B (e.g. to confirm the Newton path is bit-stable). A warn-diagnostic fires if True but no pipe carries a substrate wall. |
shaft_implicit_advance |
bool |
False |
Tier B-2. Shaft / EHR-neck port_u_n ODE I·dω/dt = τ_net advanced implicitly per macro step. Shaft timescale (~50–500 ms) is already physically decoupled from acoustic Δt, so the gain is transient sharpness on fast step-changes / surge transitions, not stability. Ignored by explicit + QS drivers. |
species_implicit_upwind |
bool |
False |
Tier C-1. Replaces explicit acoustic-CFL-bound upwind species sub-stepping (O(100) sub-steps at idle) with the QS driver's backward-Euler march. Unconditionally stable for any Δt; ~100× cheaper on low-velocity transients. Chemistry stays operator-split. Differs semantically from explicit sub-stepping for fast-reaction cases (per-sweep transport + per-sweep chemistry vs per-sub-step transport + chemistry-per-macro-step) — both converge to the same steady state but transient trajectories differ. Validate trajectory match per case before opting in. |
junction_volume_implicit_advance |
bool |
False |
Tier C-2. Junction volume (m, E_internal) ODE solved implicitly per macro step via the same fixed-point pattern. Timescale (~50 ms) is ~10⁴× decoupled from acoustic Δt so explicit Euler is stable by physics; gain here is per-step accuracy on fast plenum transients (load steps, breathing). Combines with shaft_implicit_advance in a single fixed-point loop. |
junction_bdf2_history |
bool |
True (2026-09-11 — a conservation fix, not a tuning knob) |
Advances the junction volume on the same time discretisation as the pipe interiors. The implicit driver runs an implicit-pipes / explicit-plenum split: under rk = "bdf2" the pipes integrate U = β₁U_n + β₂U_{n-1} + dt_eff·R while the volume across the same port face integrated m = m_n + dt·ṁ, so the two sides of one face solved different ODEs. Measured on the closed two-pipe + plenum network (tests/integration/test_discrete_balance.py): relative energy drift 4.1e-05 → 3.6e-07, which puts bdf2 below implicit_euler's 1.4e-06. Set False only to recover the pre-2026-09-11 forward-Euler volume advance bit-for-bit. Inert for every other rk — implicit_euler and radau_iia carry no volume-side history to read and are bit-identical either way, so this cannot be used to A/B them. |
multilayer_wall_implicit |
bool |
False |
Tier C-3. CN semi-implicit MultiLayerWall radial diffusion (per-layer cap, inter-layer UA, gas-side h·A, external h_ext·A_ext). Radiation (T⁴) and axial conduction stay explicit. Kills the radial-conduction stability constraint that forces sub-stepping for thick insulation + aggressive Δt. Routed through the explicit network driver's advance_standalone_walls (implicit / QS drivers do not currently call the wall advance). |
10. Cylinder breathing — valve coupling + discharge¶
Knobs that govern how a [[cylinder]]'s ports exchange mass/energy with their
runner pipes. All inert for cases with no cylinders.
| Field | Type | Default | Touch when |
|---|---|---|---|
cylinder_orifice_valve_flow |
bool |
True |
Compressible isentropic-orifice valve coupling (ADR-0008), applied symmetrically so mass/energy conserve across the valve. False = the legacy F·A_eff-credit / F·A_pipe-drain path, which leaks (1−Cd·lift) of the flow per valve (starves breathing to ~6% vol-eff). Honoured by the explicit driver only — an implicit rk with this True raises at build (see §11). Leave True; flip only for a bit-identical A/B against the pre-ADR-0008 baseline. |
binary_valve_apply_cd |
bool |
False |
Only affects BINARY valve ports — a port with open_deg/close_deg but no lift_cad/lift_y schedule. True applies the port's discharge coefficient port.Cd to the curtain area, matching the lift-curve branch (which always multiplies by Cd) and the orifice A_eff-already-includes-Cd contract. Legacy False uses Cd=1.0 on binary ports, so a binary valve over-breathes by ~1/Cd (≈1.4× at Cd=0.7) versus an equivalent lift-table spec. A provable no-op for every port that has a lift schedule — all bundled engine cases (KTM 510, EPW diesel, examples 31/38/39/50/52/63) ship lift curves, so this is bit-identical for the whole shipped corpus. Set True for hand-authored binary-valve cases (and the scaffold_single_cylinder_engine builder) to remove the discontinuity. Kept opt-in: net-neutral on the only suite it touches (Mendeley, scaffold-built, 44→43/60 gate-edge; evaluated 2026-06-13). The earlier "KTM/EPW over-breathing cause" framing was disproven — both use lift curves. |
valve_stagnation_recovery |
bool |
False |
Only affects orifice cylinder-valve flow (cylinder_orifice_valve_flow = true, the default). True drives the poppet-valve orifice from the runner cell's stagnation (total) state — T0 = T + u²/2cp, p0 = p·(T0/T)^(γ/(γ-1)) — instead of its static state, the physically-correct condition a valve sees. The neglected u²/2 is a ~6% p0/p lift at ~100 m/s intake-runner speed, so this raises high-flow filling (and the total enthalpy the flow carries, edot = ṁ·cp·T0). The 0-D cylinder plenum is quiescent (u≈0) so only the runner side gains the lift. Validation-sensitive (shifts volumetric efficiency), so default False = legacy static-state model — bit-identical for the whole validated + regression corpus (u=0 ⇒ stagnation = static). A candidate lever for the EPW high-load under-breathing gap; flip it and re-run the KTM/Mendeley/EPW gates before adopting. |
urea_spray_conserve_energy |
bool |
False |
Only affects cases with a [pipe.urea] kind="urea_spray" injector (examples 42/43/45/48, c02021). True injects the AdBlue decomposition products at the local gas specific internal energy (energy-consistent) instead of as zero-energy mass (which spuriously cools the gas). No-op on c02002 (the SCR ads/des suite feeds gaseous NH₃ via a species inflow BC — no spray) and on every golden, so a default flip is bit-identical for the validated + regression corpus. The artifact it removes is tiny at realistic dosing (~0.2% of exhaust mass): measured ΔT ≤ 0.17 K → immaterial to kinetics. Kept opt-in (evaluated 2026-06-13); set True for energy-consistent urea-spray runs. Default False = legacy. |
urea_spray_conserve_mass |
bool |
False |
Only affects [pipe.urea] kind="urea_spray" cases. True grows the gas density by the TOTAL tracked-product mass the spray adds to the species columns (NH₃ + CO₂ + HNCO — the surplus over the urea mass being the AdBlue carrier / reaction water), so ΣY stays 1. Default False keeps the legacy behaviour where only the urea mass grows ρ while ~1.30× that lands in the species columns (ΣY drifts > 1) — a trace bookkeeping error for scalar-γ SCR but one that perturbs mixture-γ / R pipes. Opt-in keeps the validated c02002 / ex42–48 baselines bit-identical (mirrors urea_spray_conserve_energy). |
11. Output and GUI sidecar¶
| Field | Type | Default | Touch when |
|---|---|---|---|
gui_layout |
str |
"" |
Sidecar layout reference written by aeolus1d-gui on save. CLI ignores. Empty for hand-authored / CLI-only cases. |
handshake |
Literal["warn","strict","off"] |
"warn" |
The connection handshake: after build, before the time loop, every gas connection is checked for what each side carries (species by name; gamma, R and EOS). "warn" prints each connection that drops a species or reads the state with a different gas model, once per process; "strict" refuses to build (CaseValidationError); "off" prints nothing. Set "strict" for a case whose transfers must be lossless. aeolus1d handshake CASE.toml prints the full ledger; the GUI draws it under Run → Connections…. |
12. Inert-combination matrix¶
A practical cheat-sheet for what each flag is silently ignored by. Keeps you from chasing ghost knobs.
| Flag | Silently inert when |
|---|---|
newton_*, picard_*, chord_*, adaptive_dt family |
rk is an explicit scheme (ssp_rk*, rk4, rk5). |
adaptive_error_control (raises) |
rk = "bdf2" — explicitly rejected (BDF2 is not a one-step stencil). |
cylinder_orifice_valve_flow (raises at build, default-on) |
rk is implicit (implicit_euler / bdf2 / radau_iia) — the orifice valve mass/energy sources are applied only by the explicit driver, so an implicit cylinder run would silently not conserve mass; build_network refuses it. Use an explicit rk, or set cylinder_orifice_valve_flow = false for the legacy coupling. |
qs_* (everything in Section 7) |
solver is not "quasi_steady". |
catalyst_in_newton_residual / dpf_cake_in_newton_residual / dpf_regen_in_newton_residual / substrate_wall_in_newton_residual |
rk is explicit OR solver = "quasi_steady". |
substrate_wall_in_newton_residual (no-op, bit-identical) |
No pipe carries a FilterSpec (substrate wall) — augmenters early-return. |
shaft_implicit_advance / junction_volume_implicit_advance |
rk is explicit OR solver = "quasi_steady". |
junction_bdf2_history |
rk is not bdf2 (no volume-side history exists to read), OR rk is explicit, OR solver = "quasi_steady", OR the network has no junction. |
multilayer_wall_implicit |
No pipe carries a MultiLayerWall closure. |
chem_* family |
No pipe carries a py_reaction (chemistry path not entered). |
chem_fixed_dt / chem_be_iters |
chem_solver != "fixed_step". |
chem_linop_model / chem_linop_tol |
chem_solver != "linop" OR pipe is not Brinkmeier-TWC. |
chem_parallel |
chem_solver is neither "bs_brinkmeier" nor "numbalsoda" (the two cell-parallel chem paths). |
parallel_pipes / parallel_pipes_auto_threshold |
Single-pipe network. |
hllc_low_mach |
Variable-area, non-ideal-gas, or higher-order (MUSCL / MP5) paths — first-order uniform-area ideal-gas only. 2026-09-13: this row describes which kernel carries the correction and is unchanged. What was additionally true until that date, and undocumented, is that the flag was inert on the single-pipe driver entirely — accepted, printed by the startup banner, and ignored by the residual. Both explicit drivers now honour it. |
solver = "quasi_steady" (raises at run start, not build) |
No far pressure_outlet anchor in the network (unsupported low-Mach topology — the driver needs a prescribed inlet ṁ + outlet p). Missing chemistry is not itself a rejection. |
solver = "strang" (never rejected for lack of chemistry) |
A "strang" run with no reacting pipe degenerates to the pure-transport T-operator (no chemistry sub-step) — it runs, it just does nothing chemistry does. |
13. Common-pattern recipes¶
Five short "I want X — set these" blocks; each is a copy-pasteable TOML [case] snippet.
1. Smooth-flow accuracy bench — temporal-error floor matters, no shocks.
[case]
scheme = "mp5"
limiter = "none"
rk = "rk4"
cfl = 0.5
2. Stable shocked run — defaults are correct, no overrides needed.
[case]
scheme = "first_order"
limiter = "minmod"
rk = "ssp_rk2"
cfl = 0.5
3. Large-network implicit — many pipes, large CFL, threaded Newton.
[case]
rk = "bdf2"
cfl = 5.0
adaptive_dt = true
parallel_pipes = true
4. AVL Cruise-M aftertreatment drive cycle — low-Mach QS driver, super-cycled chemistry, fused RHS.
[case]
solver = "quasi_steady"
dt_hrr_fraction = 0.1
chem_every_n = 4
qs_chem_settle_tol = 1.0e-2
qs_chem_fused_rhs = true # v0.9 default
5. Sharp-transient TWC — Recipe 4 plus damped Picard and coverage gate for fast inlet changes.
[case]
solver = "quasi_steady"
dt_hrr_fraction = 0.1
chem_every_n = 4
qs_chem_settle_tol = 1.0e-2
qs_chem_coverage_settle_tol = 1.0e-3
qs_picard_relax = 0.3
qs_chem_fused_rhs = true
14. Pointers — nested specs with their own BASIC_FIELDS split¶
The Basic / Advanced presentation extends to nine nested closure dataclasses; the field-by-field
references live in closures-reference.md. This chapter only covers [case].
[[pipe]]→PipeSpec(5 basic:id,length,n_cells,diameter,init).[[junction]]→JunctionSpec.[[bc]]→BCSpec.- Per-pipe closures:
FrictionSpec,HeatSpec,PorousSpec,CatalystSpec,FilterSpec,UreaSpec,SpeciesSpec,EOSSpec,ThermalWallSpec— each with its ownBASIC_FIELDS.
15. Diagnostics shipped with QS hardening (v0.9)¶
Scripts under scripts/ are the permanent debug surface for QS Picard non-convergence and
chemistry profiling — they print structured traces that map directly to the knobs in
Sections 7 and 8.
scripts/diagnose_qs_picard_2cycle.py— detects the bit-exact stale-secant damped-Newton 2-cycle that motivated the sign-bracket-bisection guard (MR !131). First stop on aqs_picard_relaxsymptom.scripts/analyze_qs_picard_trace.py— post-mortem trace analysis for QS Picard runs.scripts/profile_post_fused.py— chemistry-side profile to confirm the fused-RHS path (qs_chem_fused_rhs = true) is firing on Brinkmeier pipes (MR !132). First stop when a measured QS perf number doesn't match the 2.08× headline.
Reach for them on any QS Picard non-convergence or unexplained chemistry slowdown.