Metadata-Version: 2.5
Name: aeolus1d
Version: 0.3.0rc2
Summary: 1D compressible gas-dynamics pipe-network solver
Author: Claude
Author-email: Niklas Nordin <niklas.nordin@nequam.se>
License-Expression: MIT
License-File: AUTHORS.md
License-File: LICENSE
Keywords: 1d,cfd,compressible,engine-simulation,gas-dynamics,numba
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Python: >=3.9
Requires-Dist: h5py<4,>=3.8
Requires-Dist: numba<0.68,>=0.58
Requires-Dist: numpy<3,>=1.24
Requires-Dist: scipy<2,>=1.10
Requires-Dist: tomli<3,>=2.0; python_version < '3.11'
Provides-Extra: build
Requires-Dist: pyinstaller>=6.0; extra == 'build'
Provides-Extra: chem-fast
Requires-Dist: numbalsoda>=0.3; extra == 'chem-fast'
Provides-Extra: chemistry-yaml
Requires-Dist: pyyaml>=6.0; extra == 'chemistry-yaml'
Provides-Extra: dev
Requires-Dist: hypothesis; extra == 'dev'
Requires-Dist: matplotlib; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pip-tools>=7; extra == 'dev'
Requires-Dist: pre-commit>=3; extra == 'dev'
Requires-Dist: pytest-timeout; extra == 'dev'
Requires-Dist: pytest-xdist; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: types-markdown; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs; extra == 'docs'
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Provides-Extra: gui
Requires-Dist: markdown>=3.4; extra == 'gui'
Requires-Dist: matplotlib>=3.4; extra == 'gui'
Requires-Dist: pyarrow>=10.0; extra == 'gui'
Requires-Dist: pyside6>=6.5; extra == 'gui'
Requires-Dist: pyvista>=0.42; extra == 'gui'
Requires-Dist: pyvistaqt>=0.11; extra == 'gui'
Provides-Extra: interop
Requires-Dist: defusedxml>=0.7; extra == 'interop'
Requires-Dist: fmpy>=0.3; extra == 'interop'
Requires-Dist: lxml>=5.0; extra == 'interop'
Requires-Dist: pycgns>=6.2; extra == 'interop'
Requires-Dist: pythonfmu>=0.6; extra == 'interop'
Provides-Extra: ml
Requires-Dist: scikit-learn>=1.0; extra == 'ml'
Requires-Dist: torch>=2.0; extra == 'ml'
Provides-Extra: real-gas
Requires-Dist: coolprop>=6.4; extra == 'real-gas'
Description-Content-Type: text/markdown

# aeolus1d

<!-- Badges must survive being rendered on PyPI, where this file is the
     project's front page and relative links do not resolve. The pipeline
     badge and the version/python badges' link targets used to point into
     the GitLab project, which is private — they rendered as a broken image
     and three dead links for anyone who is not a member. Only badges whose
     image AND link are publicly reachable belong here. -->

<!-- This badge tracks the latest FINAL release, not the pyproject line, and
     is pinned to it by tests/unit/test_version_agreement.py alongside
     CITATION.cff and SECURITY.md. 0.3.0rc1 is a pre-release; it does not
     move this until 0.3.0 ships. -->
[![version](https://img.shields.io/badge/version-0.2.0-informational)](https://pypi.org/project/aeolus1d/)
[![python](https://img.shields.io/badge/python-3.9%2B-blue)](https://pypi.org/project/aeolus1d/)
[![license](https://img.shields.io/badge/license-MIT-yellow)](https://opensource.org/licenses/MIT)
[![code style: ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![type-checked: mypy --strict](https://img.shields.io/badge/type--checked-mypy%20--strict-blueviolet)](https://mypy-lang.org/)

A 1D compressible gas-dynamics pipe-network solver — open-source, pure
Python (NumPy / SciPy / Numba). Foundation for an open engine and
powertrain simulator in the tradition of 1D wave-action codes.

Named after Αἴολος, keeper of the winds.

---

## What's in the box

aeolus1d is a working 1D Euler solver with engine- and
aftertreatment-grade modelling depth, validated within a **stated
regime** — see **[Scope & limitations](docs/scope-and-limitations.md)**
for what is validated, what is demonstrative, and what it is not
qualified for. The codebase ships ~3900 unit + integration tests,
229 bundled examples, a golden-snapshot
regression net, a Qt / PyVista visual editor, and a TOML case format
with full schema validation.

### Solver core

- **HLLC Riemann solver** (Toro Ch. 10) with closed-form analytic
  Jacobians end-to-end (frozen-MUSCL ideal-gas + variable-γ + real-
  gas EOS-PR / RK / tabulated). Optional **HLLC-LM** low-Mach correction
  (Thornber).
- **Reconstruction**: 1st-order Godunov, MUSCL (van-Leer / minmod),
  or **MP5** (Suresh-Huynh, 5th-order monotonicity-preserving).
- **Time integration**: explicit SSP-RK2 / SSP-RK3, **plus** implicit
  Euler / BDF2 / Radau IIA(3) with block-tridiagonal Newton, adaptive
  Δt, and **Picard** outer iteration over network ↔ component coupling
  (`picard_max_iter`, adaptive controller). (A matrix-free JFNK path
  was benchmarked, found slower than the direct block-Thomas solve at
  every tested N, and removed — see `docs/numerics.md`.)
- **Quasi-1D Euler** with variable area and a per-cell closure stack
  (friction, wall heat transfer, porous-media drag, two-phase catalyst,
  reactive chemistry, surface kinetics).
- **Real-gas EOS**: ideal / Redlich-Kwong / Peng-Robinson /
  CoolProp-backed tabulated lookup, plumbed through the HLLC kernel
  and the network MUSCL path. Closed-form first- and second-derivative
  Jacobians for the cubic-EOS Newton path.
- **Variable-γ stack**: cell-averaged scalar, per-cell γ in HLLC, and
  per-cell γ in MUSCL / MP5.
- **Multi-species transport** with a `(Y, Y_extras)` stack; per-cell
  species columns travel with the gas via a JIT'd 1st-order upwind on
  the HLLC mass flux.
- **Strang-split chemistry**: scipy LSODA per cell or a hand-tuned
  `@njit` Bader-Deuflhard semi-implicit Bulirsch-Stoer kernel.
  Mechanism-driven kinetics from CHEMKIN / Cantera YAML / JANAF
  imports plus an in-tree species thermo DB (NASA-7 polynomials).

### Components

- **Pipe** — finite-volume grid with ghost cells (Ng = 1 or 2),
  variable area, optional per-cell γ + species columns, optional
  friction / heat / porous / catalyst / filter sub-state. Multiple
  init modes (uniform, Riemann, regional, mode-shape).
- **Junctions** — plenum (multi-port + species transport),
  Corberán CP+h (lossless or with per-port `K_loss`), Extended
  Helmholtz Resonator (per-port `u_n` ODE state), passthrough
  (transparent area-preserving connector). Phase-23 LODI /
  Poinsot-Lele characteristic port BC available on plenum ports.
- **Cylinder** — slider-crank V(θ), Wiebe combustion, two-zone
  burnt / unburnt diagnostic with per-zone γ tracking, continuous
  lift schedules on every port, Livengood-Wu knock prediction +
  closed-loop θ_spark retard controller. Engine-cycle accumulator
  (`EngineMetrics` / `cycle_summary` / `fleet_summary`) for IMEP /
  BSFC / knock-fired-rate post-processing. **Phase E5 VVT**: per-port
  `phase_offset_deg` (cam-phaser advance / retard) and `lift_scale`
  (variable-lift multiplier, 0.0 = effective deactivation) — both
  TOML-configurable and mutable at runtime by any controller.
  Carries a well-mixed **charge composition** — an inventory of every
  species column its ports touch, exchanged with the runners through
  both valve couplings, with burnt fuel converted to stoichiometric
  CO₂ + H₂O and the matching O₂ debit from the fuel's H/C ratio.
- **Turbomachinery** — quasi-steady map-driven Compressor + Turbine
  paired through a 1-DOF Shaft inertia ODE. 3-D `TurboMap` with a
  `vane_open` axis for VGT / IGV. CSV / NPZ map loaders.
  `bypass_open` actuator on Turbine for wastegate use. Compressor
  surge / choke / idle / backflow regime diagnostics. **v0.5
  robustness pack**: opt-in γ/R off-test-gas correction
  (`gas_property_correction`, GT Flow §3.10) for EGR / rich-SI /
  diesel part-load maps, near-surge mass-flow damping
  (`surge_damping`), and shaft mechanical efficiency (`eta_mech`) +
  viscous bearing / windage drag (`bearing_friction`).
- **Volume** — 0-D plenum with mass / energy / optional species
  state. Used by junctions and as a generic 0-D building block.
- **Helmholtz resonator** — 1-D neck + cavity component with a
  closed-form natural-frequency match against the 0-D textbook
  formula on the quasi-2D bench.
- **Coolant pipe + thermal wall** — Phase Coolant: a 1-D coolant
  pipe carrying per-cell ``T_cool(x)`` paired with a per-cell
  solid-wall thermal mass (``T_wall(x)``) coupled to a gas pipe
  through ``h_gas`` / ``h_cool``. Closes the engine-cooling story
  alongside the 0-D ``aeolus1d.coolant.CoolantState`` lumped model
  (Phase E3) — pick the 0-D model for fast bulk-block warm-up
  studies, the 1-D pipe for cylinder-head / jacket-channel spatial
  resolution. Wired into ``advance_network`` via the
  ``coolant_couplings`` parameter (Coolant-2); the standalone
  forward-Euler ``advance_coolant_system`` step driver remains for
  coolant-only studies.

### Closures

- **Friction**: NONE / `DARCY_CONST` / `COLEBROOK` (Haaland turbulent +
  laminar / transition) / `MINOR_LOSS` (K-method lumped bend / fitting
  loss, additive to wall friction; `K` either user-supplied or derived
  from bend geometry via the Idelchik smooth-bend correlation).
- **Wall heat**: NONE / `CONSTANT_TWALL` / `FLOW_NU` (flow-correlation
  Nusselt → h: Dittus-Boelter / Colburn / Gnielinski / Sieder-Tate over
  a laminar Nu = 3.66 floor).
- **Dynamic wall thermal mass**: per-cell `T_wall(x)` ODE, coolant-jacket
  coupled or standalone with external convection + radiation loss
  (engine warm-up / underbody heat rejection), two-way energy-coupled to
  the gas; optional axial conduction and **radial multi-layer** walls
  (steel + insulation + shield, series half-thickness conductance).
- **Porous (Darcy-Forchheimer)**: constant μ or Sutherland μ(T);
  ideal-gas + real-gas analytic Jacobian path.
- **Catalyst**: two-phase heat (`h · a_v · (T_s − T_g)`) with constant
  or Re/Pr-dependent Nusselt (Hawthorn monolith correlation).
  Mechanism-driven surface kinetics (Phase-46d).
- **Aftertreatment**: full DOC (HC + CO Arrhenius branches; optional
  sulfate-poisoning + active-regen via `DOCSulfurController`),
  DPF (passive trap + cake Δp + active O₂ regen + cake thermal mass +
  opt-in soot-ox heat-release stability clamp (`hrr_clamp`) +
  **substrate-wall thermal inertia** (`Omega_wall`/`h_wall`: the
  SiC/cordierite monolith as a thermal-inertia element for WHTC /
  cold-start, v0.5) + closed-loop heater PI), SCR (Cu-zeolite NH₃ ads/des / standard SCR /
  oxidation), SCRF, two-stage SCR, urea-water injector + HNCO
  hydrolysis, TWC (Voltz-Morgan-Liederman 3-pathway and Brinkmeier
  11-reaction with Ce O₂ storage, plus two opt-in channels: passive-SCR
  NH₃ generation `A_nh3_form_brnk` and C₃H₆ total oxidation
  `A_c3h6_ox_brnk`)).
- **Equation of state**: ideal / RK / PR / tabulated, with closed-form
  `∂a/∂T`, `∂a/∂ρ` for the cubic EOSs.
- **ML seam**: `py_friction` / `py_heat` / `py_porous` / `py_reaction`
  / `py_reaction_multi` / `py_reaction_surface` / `py_junction_bc` /
  `py_turbo_map` callables drop in at the per-cell or per-port level
  for learned closures. Bundled NN port-state and turbo-map surrogates
  (`LearnedTurboMap` MLP, PINN closure adapter).

### Throttle body

- **Throttle body inlet BC** (`aeolus1d.bc.throttle`) — Phase E1:
  motor-driven intake throttle modelled as a controllable
  isentropic orifice. ``Throttle.open ∈ [0, 1]`` mutable state
  field that any controller (or hand-written closure) can drive on
  every step. Same Saint-Venant choked / subsonic dispatch as the
  EGR-valve BC; sized for SI gasoline intake workflows
  (``Cd ≈ 0.7`` default, fresh-air composition write-through).
- **Charge-air-cooler closure** (`aeolus1d.closures.heat.make_charge_air_cooler_py_heat`)
  — Phase E2: counterpart to the EGR cooler for the intake side.
  Same ε-NTU model, just named for intake-side workflows so
  callers don't have to alias an exhaust-side name. Pairs with
  the throttle BC for a complete boosted-SI intake plumbing
  stack.

### Boundary conditions

`stagnation_inlet` / `pressure_outlet` / `mass_flow_inlet` /
`velocity_inlet` / `valve` (Chalet-Chesse-Hetet effective-area) /
`valve_orifice` (Winterbone-Pearson; forward/reverse `Cd`, area-ratio
default `Cd`, Borda-Carnot recovery, perforated-plate `n_holes`) /
`wall` / `transmissive` /
`nonreflecting` (LODI / Poinsot-Lele). Every static BC has a
`transient_*` counterpart driven by a `Clock` schedule (mass flow,
velocity, stagnation, pressure-outlet, plus per-extras-species
schedules for inflow `Y` mass fractions).

### Controllers

`BoostController` (VGT vane PI on manifold pressure),
`WastegateController` (sign-inverted PI on `Turbine.bypass_open`),
`DPFHeaterController` (heater PI on mean cake temperature),
`DOCSulfurController` (lumped sulfate storage / reduction integrator),
`EGRValveController` (PI on intake-pipe O₂ mass fraction, actuating
a controllable orifice BC's open fraction — Phase 8m-ctrl-2 +
Phase 8m-ctrl-3 added the `[[controller]] kind = "egr_valve"` +
`[[bc]] kind = "egr_valve"` TOML schema integration),
`FuelInjectorController` (PI on exhaust-side O₂ mass fraction —
the canonical lambda feedback signal — actuating
`Cylinder.m_fuel_per_cycle`; multi-injector cylinders preserve
per-injector ratios — Phase 8m-ctrl-4 + Phase 8m-ctrl-5 added the
`[[controller]] kind = "fuel_injector"` TOML schema integration),
`SparkTimingController` (PI on Livengood-Wu knock integral, actuating
`WiebeCombustion.theta_start_deg`; complements the existing reactive
knock-event retard with preventive margin control — Phase 8m-ctrl-6 +
Phase 8m-ctrl-7 added the `[[controller]] kind = "spark_timing"` TOML
schema integration).
All implemented as velocity-form PI; pluggable as `on_step=` callbacks
or wired via `[[controller]]` blocks in TOML — six controllers, full
TOML symmetry.

A general **control / signal toolbox** (`aeolus1d.control.signal_toolbox`)
provides reusable blocks the domain controllers and a driver model build
on: first-order low/high-pass + moving-average + exponential filters,
rate limiter, saturation, Schmitt relay, discrete delay, discrete-IIR
transfer function, signal sources (step/ramp/sine/sawtooth/exponential),
and RMS/RMSE.

### Vehicle & powertrain

- **Longitudinal vehicle** (`vehicle.py`): 1-DOF road load (rolling +
  aero + grade), rotating-mass factor.
- **Driveline** (`driveline.py`): N-speed gearbox, torque converter,
  clutch, auto-shifter; **AMT shift transients**
  (`transmission_shift.py`) — the four-phase disengage→sync→engage→end
  state machine + a torsional elastic flange.
- **Drive cycles & driver**: `drive_cycles.DriveCycle` (speed-time
  profiles from CSV / segments / illustrative) + a closed-loop
  `driver` (velocity-tracking PI + road-load feed-forward →
  engine-torque + brake demand), with `analysis.vehicle_tasks`
  (coastdown, full-load acceleration, elasticity, gradeability, cycle
  KPIs) and an engine **BSFC fuel map** (`fuel_map.py`).
- **Hybrid / electric**: parallel hybrid (`hybrid.py`); a multi-RC
  **ECM battery** (`battery_ecm.py`, with pack scaling), a map-based
  **e-motor** + **inverter** + **DC-DC** loss models (`emotor.py`), a
  **planetary** power-split (`planetary.py`), **regen-braking** split
  (`regen.py`), and **energy management** (`energy_management.py`:
  series-hybrid with thermostat + ECMS charge-sustaining strategies).

### Tooling

- **TOML case format** with full schema validation (Phase B10
  two-pass validator with 21 sub-validators + TOML source-position
  lookup for traceable error messages + unknown-key detection with
  ``difflib`` did-you-mean hints so typos like ``[[pipe.initt]]``
  surface as a warning instead of silently defaulting).
- **HDF5 output** with Phase-A17 checkpoint / resume.
- **Visual editor** (`aeolus1d-gui`) — Qt / PyVista drag-and-drop
  palette, auto-generated inspector forms from the schema's
  `KIND_FIELDS` table, run-as-subprocess controller, 3-D network
  view with HDF5 trace overlay, 2-D plot dock with probe time-series,
  acoustic-mode FEM eigensolver dock, Solver Internals diagnostic
  view (live analytic-vs-FD routing per closure).
- **CLI** — `aeolus1d run`, `aeolus1d bench`, `aeolus1d optimize`,
  `aeolus1d examples`, `aeolus1d gui`, `aeolus1d audio`,
  `aeolus1d acoustics` (NVH metrics — SPL / dB(A) / dB(C) +
  octave bands + transmission loss from a stored pressure-probe
  HDF5 trace), `aeolus1d turbo-map`
  (renders a `.cmp` / `.trb` / `.npz` turbo map as a
  manufacturer-style compressor or turbine plot — iso-speed
  lines, η contours, surge / choke envelope, optional
  operating-point overlay). Bench harness ships canonical
  literature cases with closed-form pass criteria
  (`examples/benchmarks/`).
- **Interop** — Cantera YAML import, CGNS export
  (Tecplot / Paraview-readable), CHEMKIN mechanism import +
  native JSON-per-mechanism persistence, AVL Cruise M `.cm` import,
  GT-Suite native `.gtm` import (Phase B1 inspect + B3 read for
  pipes / junctions / BCs + **Phase 9a** recursive `.gtsub`
  sub-assembly resolution + **Phase 9b** engine breathing-path
  templates — cylinder + cylgeom, compressor, turbine,
  shaft, exhaust valveconn — wired through the orifice-
  passthrough chain + **Phase 9c** EndEnvironment auto-attach
  emitting real `stagnation_inlet` / `pressure_outlet` BCs at
  the breathing-path termini + **Phase 9e** cylinder port
  wiring through the valveconn-flowsplit-pipe chain so the
  imported cylinders actually breathe + **Phase 9d** GT
  thermal lookup-table dispatch (HTC + T_wall stamped onto
  pipes by longest-substring name match — F2 intake ports
  get h=3000 W/m²K, bellows get h=35 W/m²K) + **Phase 9f**
  GT-Wide ASCII `.cmp` / `.trb` map readers so compressors /
  turbines carry their real performance data instead of
  synthetic defaults + **Phase 9g** controller sub-assembly
  translation (wastegate wires to `WastegateController`; the
  other 6+ F2 controllers silent-skip with named lossy entries
  pointing at FMI co-sim) + **Phase 9h Tier-1** 3-zone cylinder
  wall heat transfer (head / liner / piston each get their own
  prescribed T; Q̇ splits across the matching surfaces while
  gas-side h stays the bulk Woschni correlation; legacy
  single-T behaviour bit-exact when zone Ts collapse) +
  **Phase 9i** an
  end-to-end integration test pinning that the F2-BWC9-100
  driver case loads, builds, and steps the timeloop +
  **Phase 9j neutral schematic**
  carrying cylinders / turbos / shafts / heat-exchangers /
  controllers end-to-end so the importer lands directly on a
  runnable `Case`), FMU export scaffolding.
- **Optimisation + sensitivity** — `aeolus1d optimize` minimises a
  trace-vs-CSV metric over user-declared parameter handles;
  `aeolus1d.sensitivity` does parameter sweeps.
- **Quality bar** — `mypy --strict` coverage across all 196
  non-`__init__` source modules (148 in `src/aeolus1d/`, 48 in
  `aeolus1d_gui/`); ruff pre-commit hook on every staged file
  (with CI re-run on push); ~3900 unit + integration tests under
  parallel `pytest-xdist`. GitLab CI is mirrored on GitHub Actions.

### Post-processing

GT-POST / Cruise-M-grade analysis on top of any tracer / HDF5
output. Full reference in
**[docs/post-processing.md](docs/post-processing.md)**.

- **Engine cycle diagnostics** (`aeolus1d.engine_post`) — IMEP /
  PMEP / IMEP_gross / p-max, **heat-release inversion** with
  CA10 / CA50 / CA90 + polytropic exponents, **volumetric
  efficiency**, **COV(IMEP)** combustion-stability indicator,
  BSFC + η_thermal.
- **Live cycle accumulator** (`aeolus1d.analysis.engine_metrics`)
  — `CycleAccumulator` + `MultiCycleAccumulator` for multi-
  cylinder fleet rollup during a run via the `on_step=` hook
  (knock-fired rate, mean retard, max knock-integral).
- **Acoustics / NVH** (`aeolus1d.analysis.acoustics`) — SPL /
  **dB(A)/dB(C)/dB(Z)** per IEC 61672, power spectrum, IEC 61260
  **1/1 and 1/3 octave bands**, **engine-order analysis**,
  **transmission-loss** — the `aeolus1d acoustics --probe-out` CLI
  reports the naive amplitude-ratio TL (anechoic terminations
  only); the reflection-correct **two-microphone wave
  decomposition** (`transmission_loss_decomposed`) and insertion
  loss are in the Python API — **free-field monopole
  radiation** (in-pipe → listener SPL), and **combustion-noise**
  prediction from a cylinder-pressure trace. A frequency-domain
  **four-pole transfer-matrix** muffler solver
  (`aeolus1d.analysis.acoustics_tm`) gives transmission loss directly
  from chained ABCD duct elements (validated against the analytic
  expansion-chamber TL).
- **System-level reports** (`aeolus1d.analysis.system_reports`) —
  **turbo operating-point overlay** on the compressor map (with
  surge / choke envelope), Sankey-ready **energy balance** (fuel
  → work + pump + exhaust + wall + residual), **cumulative
  emissions** integration past a probe, **drive-cycle**
  (NEDC / WLTP / FTP / user) integration harness, and
  **steady-state / periodic-limit-cycle convergence** detection.
- **WAV export** (`aeolus1d.audio`) for listening to the engine
  note at audio rate.
- **State-space linearization** (`aeolus1d.analysis.linearize`) —
  Phase L1: build a continuous-time ``(A, B, C, D)`` model around a
  steady operating point by central finite-difference on the
  pipe-residual kernel. Plugs straight into ``scipy.signal.StateSpace``
  / ``python-control`` for Bode plots, pole / stability analysis,
  LQR / H∞ controller synthesis, and Kalman observer design.
  Phase L2 extends this to multi-pipe + junction-coupled networks:
  ``linearize_network`` packs every pipe's interior cells into one
  state vector, calls ``junction.apply_port_bcs()`` between FD
  perturbations so junctions cross-couple their connected pipes.
  Phase L2b (default) also packs each junction's ``(m, E_internal)``
  into the state vector and reads ``Junction.accumulate_rates()``
  for the corresponding time derivatives, giving full bidirectional
  pipe ↔ plenum coupling. Phase L2c additionally packs port-level
  ``u_n`` state — turbo shaft ω, extended-Helmholtz-resonator slug
  velocity, anything matching the network timeloop's
  ``port_u_rates`` / ``ports[i].u_n`` duck-typing contract. Two
  opt-out flags (``frozen_volumes`` / ``frozen_shafts``) collapse
  back to L2 (pipes-only) or L2b (pipes + volumes) when partial
  linearization is more honest — e.g. when a network contains a
  cylinder whose slider-crank angle deliberately stays out of the
  pack (kinematic clock, not an integrable ODE — cycle-averaged
  analysis is the right tool there).
  Phase L3 adds a ``method="analytic"`` opt-in that pulls A
  directly from the implicit Newton solver's closed-form
  block-tridiagonal Jacobian assembly (the same one Phases
  B16 → B22 ship for every closure kind). For pure pipe-and-far-BC
  networks (``junctions=()``) one assembly per pipe replaces
  ``2n`` residual sweeps, machine-precision-exact rather than
  FD-truncated. Phase L3b extends this to junction-coupled
  networks via a hybrid: pipe-on-pipe diagonal blocks are
  overlaid analytically onto the FD-derived A; cross-blocks
  (pipe ↔ volume, pipe ↔ u_n, pipe-pipe-through-junction) stay
  FD. Pure-analytic cross-blocks are BACKLOG L3c.

---

## Install

Requires Python ≥ 3.9. Canonical instructions live in
[docs/installation.md](docs/installation.md); the short form:

```bash
python -m venv .venv
source .venv/bin/activate            # Windows: .venv\Scripts\activate

pip install aeolus1d                 # solver + CLI
pip install "aeolus1d[gui]"          # + desktop GUI (optional)
```

Working **from a clone** instead (contributors), the same three lines
become editable installs:

```bash
pip install -e .
pip install -e ".[gui]"
pip install -e ".[dev]"              # + tests / lint / type-check
```

After this, the `aeolus1d` (and, with `[gui]`, `aeolus1d-gui`) console
scripts are on PATH. Verify:

```bash
aeolus1d warmup                        # one-time kernel compile, ~19 s
aeolus1d examples                      # lists the bundled cases; line 1 is their directory
aeolus1d run "$(aeolus1d examples | head -1)/01_single_pipe_sod.toml" --out sod.h5
aeolus1d-gui                           # opens the desktop GUI ([gui] extra)
```

> The bundled cases ship **inside the package**, so `examples/…` is not a
> path that exists in your working directory after `pip install`. Use
> `aeolus1d examples` to find them (it prints the directory), or run from
> a clone. Earlier versions of this README showed
> `aeolus1d run examples/01_single_pipe_sod.toml`, which only worked from
> a source checkout and failed with `case file not found` for everyone
> who installed from PyPI.

The first solver run on a fresh environment pays a one-time Numba kernel
compile — **~19 s** measured on x86-64 with numba 0.65.1, and
hardware-dependent. `aeolus1d warmup` does it out loud up front and
caches the kernels on disk so every later run starts in seconds.

Two loosely-pinned requirements files are shipped as an alternative
to the extras: `requirements.txt` (simulator core + the desktop GUI
runtime) and `requirements-dev.txt` (adds tests — pytest + xdist +
timeout + hypothesis — lint (ruff), type-check (mypy), plotting
tools, and the docs builder).

More optional extras through `pyproject.toml`:

```bash
pip install -e ".[real-gas]"       # CoolProp — cubic / multi-parameter EOS
pip install -e ".[chemistry-yaml]" # pyyaml — Cantera mechanism import
pip install -e ".[interop]"        # pycgns / pythonfmu / fmpy / lxml
pip install -e ".[build]"          # PyInstaller — end-user binaries
```

If you're going to commit code, install the pre-commit hook so
ruff issues fail locally before they reach the CI gate:

```bash
pre-commit install                  # one-time per checkout
```

After that, every `git commit` runs ruff (with `--fix`) on the
staged files. The same rules run in CI on push, so a clean
local commit means a green CI run.

---

## Quick start

```bash
pytest -q
aeolus1d bench nozzle
aeolus1d run examples/03_two_pipes_junction.toml --out run.h5
aeolus1d run examples/05_catalyst_plug.toml --out plug.h5
```

See `examples/` for the 229 bundled cases (`aeolus1d examples` lists
them all, grouped by subdirectory) and
`examples/benchmarks/` for canonical literature benches with
reference-solution pass criteria. Most top-level cases have a matching
`examples/plots/plot_NN_*.py` that renders the expected physics
visualisation; `examples/plots/README.md` marks the ones that do not.

The **[Tutorials](docs/tutorials/)** are the intended on-ramp for new
users — three guided walkthroughs (Sod shock tube → networks +
junctions → engine end-to-end), each anchored on a bundled example so
you can run the same commands and modify-then-rerun.

---

## GUI

`aeolus1d-gui` (or `aeolus1d gui` / `python -m aeolus1d_gui`) opens the
Qt / PyVista visual editor + post-processor. Drop pipes / junctions /
cylinders / BCs from the left palette onto the canvas; drag yellow
port markers to wire components together. Press F5 to run; the 3-D
network view colours pipes by ρ / u / p / T and a time slider scrubs
through traced samples. The GUI keeps a `*.layout.json` sidecar next
to each case file with node positions + probes so reopening restores
exactly what you had on screen.

Full walkthrough in **[docs/gui.md](docs/gui.md)**.

---

## Documentation

Built with MkDocs + Material theme. To serve locally:

```bash
pip install -e ".[docs]"
mkdocs serve              # http://127.0.0.1:8000
```

Pages of note:

- **[index](docs/index.md)** — headline-feature summary
- **[quickstart](docs/quickstart.md)** — first run with a guided example
- **[scope & limitations](docs/scope-and-limitations.md)** — what's
  validated, what's demonstrative, and what it's *not* qualified for
  (read this before trusting a number)
- **[tutorials/](docs/tutorials/)** — three narrative walkthroughs
- **[concepts](docs/concepts.md)** — runtime data flow end-to-end
- **[theory](docs/theory.md)** — governing equations + flux scheme +
  closures
- **[architecture](docs/architecture.md)** — module tree + design
  conventions
- **[closures](docs/closures.md)** — friction / heat / porous /
  catalyst / EOS protocols and ML-seam attachment points
- **[numerics](docs/numerics.md)** — implicit Newton, chord /
  Jacobian reuse, block-tridiag structure (incl. the JFNK
  removal story)
- **[implicit-solver-reference](docs/implicit-solver-reference.md)** —
  every `NewtonConfig` knob, decision table for picking the right path
- **[phase-iy-picard](docs/phase-iy-picard.md)** — outer Picard
  iteration on network coupling
- **[modes](docs/modes.md)** — FEM eigensolver for acoustic-mode
  analysis
- **[interop](docs/interop.md)** — Cantera YAML, CGNS, FMI,
  AVL Cruise M, GT-Suite
- **[gui](docs/gui.md)** — desktop visual editor walkthrough
- **[post-processing](docs/post-processing.md)** — IMEP /
  CA10/50/90 / SPL / dB(A) / order analysis / muffler TL /
  turbo overlay / energy balance / drive cycle
- **[examples](docs/examples.md)** — bundled cases by topic
- **[cli](docs/cli.md)** — full subcommand reference
- **[changelog](docs/changelog.md)** — milestone-by-milestone summary
- **[phase-log](docs/phase-log.md)** — detailed per-phase technical
  history (the historical record this README used to carry)
- **[perf-history](docs/perf-history.md)** — Phase Perf-1 → Perf-17
  before/after numbers and the empirical-vs-speculative pattern
- **[roadmap](docs/roadmap.md)** — what's planned next
- **[CLAUDE](CLAUDE.md)** — working rules: which document owns which
  question, and what must be updated alongside a change
- **[STATUS](STATUS.md)** — **start a session here**: where the code is,
  what is in flight, what to do next, and which document owns which
  question. One screen, no plans

The same Markdown content is reachable from the GUI (Help →
Documentation, Ctrl+H) — built-in viewer with a tree-of-contents
on the left and the formatted page on the right. Useful when you
want to skim the theory page while editing a case.

---

## Design notes

Hot kernels (Riemann flux, reconstruction, pipe residual, RK stages,
junction solve) are plain `@njit` functions operating on
struct-of-arrays — `jitclass` is deliberately avoided. Physics
closures are exposed as integer-kind-coded dispatch protocols so the
JIT can inline cheaply, and so learned surrogates can be attached
per-pipe without changes to the solver. All schema-driven closure
families register themselves at import time via
`aeolus1d.closures.registry.register_closure(...)` — the schema's
`Literal[...]` arms still need hand-maintenance, but a unit test
catches drift between the registry and the schema.

The full data flow — TOML → `Case` → live network → timeloop → HDF5
→ resume → GUI — is documented end-to-end in
**[docs/concepts.md](docs/concepts.md)**.

---

## Authors

- Niklas Nordin (`niklas.nordin@nequam.se`)
- Claude (Anthropic) — pair-programming assistant

---

## Community

- **[CONTRIBUTING.md](CONTRIBUTING.md)** — dev setup, local gates,
  branch + MR conventions, phase-workflow rule.
- **[SUPPORT.md](SUPPORT.md)** — how to get help and file a good bug
  report (best-effort, single maintainer, no SLA pre-1.0).
- **[MAINTAINERS.md](MAINTAINERS.md)** — who maintains aeolus1d, the
  bus-factor-1 reality, and how to become a second maintainer.
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)** — Contributor Covenant
  v2.1; report concerns to `niklas.nordin@nequam.se`.
- **[SECURITY.md](SECURITY.md)** — responsible-disclosure policy
  for case-file parsers and the install path.
- **[CITATION.cff](CITATION.cff)** — how to cite aeolus1d in academic
  work (Zenodo DOI added at the first archived release).

---

## License

MIT.
