Skip to content

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 in CaseHeader.BASIC_FIELDS are 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 --full materialises every key explicitly if you want the fully-specified form; aeolus1d expand alone 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_start float 1.0 Soft-start CFL — an absolute CFL (not a multiplier), interpolated linearly from cfl_ramp_start at t_start up to cfl at t_start + cfl_ramp_duration. Checked like cfl (positive; ≤ 1 with an explicit rk). Apply to transient-mass-flow-inlet kicks where the configured CFL would overshoot on step 0.
cfl_ramp_duration float (s) 0.0 > 0 enables the ramp; 0.0 keeps step-0 behaviour unchanged. While the ramp is active its elapsed fraction (0 → 1) scales adaptive_cfl_max, clamped up to adaptive_cfl_min.
dt_fixed float (s) 0.0 BASIC — > 0 overrides 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_fraction float 0.0 ~0.1 bounds 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.0 applies a 0.1 safety 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_max ceiling — 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_fraction float 0.0 > 0 caps Δt so a constant-T_wall cell'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-h cells the acoustic CFL under-resolves. 1.0 is the exact no-overshoot limit; 0.5–1.0 adds 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 own BASIC_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 a qs_picard_relax symptom.
  • 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.