Skip to content

Cantera mechanism I/O and reactor simulation (#65) - #73

Merged
tjkessler merged 3 commits into
mainfrom
feature/reaction-cantera-io
Oct 15, 2026
Merged

tjkessler merged 3 commits into
mainfrom
feature/reaction-cantera-io

Conversation

@tjkessler

Copy link
Copy Markdown
Owner

Refs #65. Sixth PR for the reaction-network datasets. Stacked on #72 (→ #71 → #70 → #69). Merge in order #69 → #70 → #71 → #72 → this one. Each PR retargets to main when the one below it merges, and CI starts then.

Changes

koopman_graph.datasets.reaction.cantera_io is the lazy [cantera] boundary. Importing it doesn't import Cantera.

load_cantera_mechanism(name_or_path="h2o2.yaml", *, temperature_k, pressure_pa, mole_fractions, state_dependent="freeze") converts a gas mechanism into a mass-action ReactionSystem at a reference state (T, P, X):

  • Reversible reactions become a forward row and a reverse row, using Cantera's forward_rate_constants / reverse_rate_constants at T. Reverse constants come from thermochemistry and aren't Arrhenius, so arrhenius=None.
  • Elementary reactions convert exactly.
  • Mass-action three-body reactions also convert exactly. In Cantera 3.2 the rate constants leave out [M], and [M] = Σ ε_s c_s, so each +M reaction expands into one reaction per collider s (A + B + s → … + s, k ε_s). Explicit colliders (+ O2) are just the case with a single collider.
  • State-dependent rates (falloff, PLOG, Chebyshev, …) aren't mass action. state_dependent="freeze" uses the effective constant at the reference state, which is exact there and approximate elsewhere. "skip" drops them and "error" raises.
  • Non-mass-action orders are always skipped (or raise under "error").
  • Nothing is dropped silently. CanteraProvenance lists every frozen and skipped equation, and records the Cantera version, resolved path, mechanism SHA-256, reference state, Cantera's "bundled inputs are for illustration only" caveat, and the Cantera 3.2.0 citation DOI. It also has a JSON-safe to_dict.
  • Units: kmol/m³ equals mol/L, so concentrations, rate constants and time carry over unchanged.

Other functions:

  • simulate_cantera_reactor(...) runs an IdealGasConstPressureReactor or IdealGasReactor, adiabatic or isothermal (energy="off"), and returns concentrations, T, and P at the output times plus provenance. It passes clone=True explicitly, which becomes Cantera's default after 3.2.
  • require_cantera() raises ImportError naming [cantera]. resolve_mechanism_path takes an explicit path, which needs no Cantera, or searches Cantera's data directories. mechanism_sha256 hashes the file.

CI: the Ubuntu and macOS test jobs and the release verify job now install --extra reaction --extra cantera. #66 deferred this until cantera-marked tests existed. Without it, cantera_io.py would be mostly uncovered in CI and fail the 90% Codecov patch target. Cantera 3.2.0 has wheels for the whole matrix (CPython 3.10–3.14, manylinux x86_64, macOS arm64), and uv sync --frozen with the combined extras resolves from the existing lock.

Tests

  • The bundled h2o2.yaml SHA-256 equals the value in [Feature]: Mass-action reaction-network datasets (koopman_graph.datasets.reaction) #65 (0efc6c52…c8d4).
  • At the reference state (1200 K, radical-rich mix), system.time_derivative equals Cantera's net_production_rates to 1e-12 relative. The system is element-balanced, has 1 frozen reaction (the Troe falloff) and 0 skipped.
  • With the falloff removed, the conversion stays exact (≤1e-12) at a different composition and pressure, which shows the three-body expansion is exact. With the falloff frozen, the error away from the reference state is nonzero but under 5%.
  • skip / error policies work. A custom mechanism with orders: {H2: 0.5} has that reaction skipped (or rejected under error).
  • An isothermal constant-volume reactor agrees with integrate_stiff on the converted system to 1e-3 of the concentration scale. The adiabatic run heats past 1500 K at constant pressure.
  • Without Cantera, simulated via sys.modules, every entry point raises the [cantera] hint. Hashing and explicit-path resolution work without it.
  • Locally the 9 Cantera tests skip cleanly when Cantera is hidden, and the 2 missing-extra tests pass.

Verification (macOS arm64, Python 3.13, Cantera 3.2.0)

  • ruff check and ruff format --check on src/ tests/ pass.
  • pytest -n auto -m "not slow and not distributed and not ray" --cov=koopman_graph --cov-fail-under=90: 3480 passed, 26 skipped, 97.27%. cantera_io.py is at 98%.
  • sphinx-build -W --keep-going passes.
  • Not checked: the Linux CI install with [cantera]. It will run for the first time when this PR retargets to main.

🤖 Generated with Claude Code

Refs #65. Fills in koopman_graph.datasets.reaction.cantera_io (lazy
[cantera] boundary):

- load_cantera_mechanism converts a Cantera gas mechanism (default the
  bundled h2o2.yaml) to a mass-action ReactionSystem at a reference
  (T, P, X). Reversible reactions split into forward/reverse rows with
  Cantera's rate constants. Elementary and mass-action three-body
  reactions are exact ([M] = sum eps_s c_s expanded into one reaction per
  collider). Falloff and other state-dependent rates are frozen, skipped,
  or rejected; non-mass-action orders are skipped or rejected.
  CanteraProvenance lists every frozen/skipped reaction and records the
  Cantera version, path, SHA-256, reference state, the illustration-only
  caveat, and the Cantera citation DOI.
- simulate_cantera_reactor: const-pressure / const-volume ideal-gas
  ReactorNet, adiabatic or isothermal.
- require_cantera, resolve_mechanism_path, mechanism_sha256.

CI test jobs and the release verify job now install [reaction] and
[cantera], so the cantera-marked tests run (and cover this module) there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Frozen state-dependent rates with a mass-action third body (e.g.
  three-body Blowers-Masel) now keep [M] via the per-collider expansion;
  Cantera's rate constants leave it out. Non-mass-action (+M) third
  bodies with Arrhenius rates convert exactly with no [M] factor.
- Read rate constants with legacy rate-constant mode off (detected with
  a probe mechanism) and restore the process setting afterwards.
- Orders equal to the reactant stoichiometry are mass action and are no
  longer skipped.
- Duplicate reactions get unique reaction names (#index suffix).
- Bare mechanism names prefer Cantera's bundled data directories over the
  working directory; Paths and names with a directory are explicit.
- simulate_cantera_reactor rejects non-finite times and records
  state_dependent=None instead of the out-of-policy "exact".
- Share solution loading in _open_solution; evaluate each collider
  efficiency once.
- Tests for explicit/frozen/non-mass-action third bodies, legacy mode,
  matching orders, duplicate names, cwd shadowing, and infinite times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.55769% with 3 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/koopman_graph/datasets/reaction/cantera_io.py 98.55% 2 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
src/koopman_graph/datasets/reaction/__init__.py 100.00% <100.00%> (ø)
src/koopman_graph/datasets/reaction/cantera_io.py 98.55% <98.55%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

tjkessler added a commit that referenced this pull request Oct 15, 2026
Carries the reviewed #69-#73 fixes up to #74. Conflict in
datasets/reaction/oracles.py resolved by taking #71's hardened version
(exact projector linear equilibrium, residual-checked Newton with
line-search stall detection, OracleSpectrum.zero_tolerance, one SVD for
rank / range / conservation laws) and re-applying #74's additions:

- _require_stationary flux check on both Newton return paths, so a
  clamped one-way boundary stall is still refused;
- eigenvalue_timescales as the single timescale implementation, now with
  an optional absolute zero_tolerance (OracleSpectrum.timescales passes
  its own; 0 compares exactly) and the round-off default otherwise.

test_equilibrium_rejects_one_way_boundary_point accepts either guard's
message, since both refuse the stalled point. Committed cards are
unchanged (build_reaction_cards.py --check passes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from feature/reaction-random-cstr-gillespie to main October 15, 2026 21:04
@tjkessler
tjkessler merged commit ecb4958 into main Oct 15, 2026
13 checks passed
@tjkessler
tjkessler deleted the feature/reaction-cantera-io branch October 15, 2026 21:05
tjkessler added a commit that referenced this pull request Oct 15, 2026
Ubuntu CI failed test_card_oracle_matches_recomputation (Michaelis-Menten,
random network), test_cantera_card_oracle_and_provenance, and the card
builder --check: oracle['conservation_laws'] drifted. With two or more
conservation laws any rotation of an orthonormal basis is valid, and
torch.linalg.svd returns a different one under Linux LAPACK than under
macOS Accelerate, so the committed cards held a platform-specific basis.
The single-law chain card was unaffected.

_stoichiometric_basis now canonicalizes the conservation laws:
Gram-Schmidt in species order on the columns of the projector P = W^T W,
which is basis independent, so every platform gets the same rows (to
rounding) with a fixed sign. Cards regenerated; apart from the rotated
laws, oracle values move only at rounding level (<= 3e-14). Adds a test
that a rotated, sign-flipped basis canonicalizes back to the same rows.

Also merges main (#70-#73) into the branch, which GitHub reported as
behind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant