Repository navigation
ReactionNetworkBenchmark with packaged FAIR cards (#65) - #74
Merged
Merged
Conversation
equilibrium() clamps Newton iterates to c >= 0. On the converted Cantera h2o2 system at 1200 K it stopped at a near-boundary point (H, O ~ 0) where every flux is tiny and the absolute residual (~5e-11) looked converged, but net production equalled gross turnover: flux ran one way, so it was not a steady state. Cantera's thermochemical equilibrium has net/gross ~ 4e-15 there. After convergence, require max_s |dc_s/dt| / gross turnover_s <= 1e-6 and raise RuntimeError otherwise (suggesting a better guess). Exact boundary equilibria (e.g. Michaelis-Menten, zero flux) still pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Refs #65. Fills in koopman_graph.datasets.reaction.benchmark: - Six FAIR JSON cards under reaction/data/ define the frozen datasets (chain, Michaelis-Menten, Brusselator, random network, Van de Vusse CSTR with controls, Cantera h2o2). Cards record generator, simulator, grid, seed, initial ranges, log floor, graph encoding, oracle references, units, license, limitations, and Cantera provenance. - ReactionNetworkBenchmark.generate -> ReactionDataset (float64 concentrations, controls, per-trajectory GraphSnapshotSequence with species nodes, timestamps, control_inputs), optional SSA simulator, deterministic trajectory split, versioned save/load (weights_only). - compute_card_oracle + scripts/build_reaction_cards.py (--check) write the oracle sections; tests recompute them. - eigenvalue_timescales maps numerically zero Re(λ) to inf; the stationarity check gains a flux-scaled absolute tolerance so boundary steady states with vanishing leftovers are accepted. - Exported from koopman_graph.datasets (root __all__ unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- build_reaction_cards.py --check compares card numbers with the test tolerances instead of byte-exact text, so platform/BLAS last-digit differences no longer fail CI, and flags reaction_*.json files that no spec produces. - system_from_card (and so generate / compute_card_oracle) refuses a Cantera card whose recorded mechanism SHA-256 differs from the installed file. - eigenvalue_timescales treats only round-off-level real parts (100 * eps * max|lambda|) as zero, so slow modes in stiff spectra stay finite. - split rejects zero ratios instead of silently assigning one trajectory. - SSA path seeds are drawn from the dataset generator (no aliasing across dataset seeds). - ReactionDataset.features bounds-checks its index like sequence. - compute_card_oracle builds the CSTR reactor once. - CHANGELOG: move the extras / marker / CI / import-hygiene entries back under Added. - Tests: equilibrium raises on a forced Newton stall, stiff-spectrum timescales, tolerant card comparison, orphan cards, SHA mismatch, index checks, split zero ratios, SSA seed independence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
tjkessler
added a commit
that referenced
this pull request
Oct 15, 2026
Carries the reviewed #69-#74 fixes up to #75 and clears its conflict. benchmark.py: keep the graph_encoding validation in ReactionDataset.sequence and use #74's _check_index helper. CHANGELOG: keep the notebook / graph_encoding entries and #74's eigenvalue_timescales and Cantera SHA-256 entry (updated for the optional zero_tolerance). The notebook re-executed on the merged code prints the same numbers as its committed outputs, so the outputs are left unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
After f20605b, Linux CI still failed test_cantera_card_oracle_and_provenance and the card builder --check on reaction_cantera_h2o2_v1, again on oracle['conservation_laws']. The basis itself now matches across platforms; what differed were entries that are exactly zero in theory but come out as round-off (1.07e-16, 5.37e-17 on macOS). Their values depend on the LAPACK build, and the Cantera card is compared with atol=1e-18 (appropriate for its ~1e-14 concentrations, not for O(1) basis entries). _canonical_row_basis now sets entries below 1e-12 to exactly zero, so the stored basis is identical across platforms rather than merely close. Cards regenerated: changes are those zeros plus rounding-level shifts (<= 1.8e-15). Test asserts that injected 1e-16 noise canonicalizes to an exact zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #65. Seventh PR for the reaction-network datasets. Stacked on #73 (→ #72 → #71 → #70 → #69). Merge in order #69 → … → #73 → this one. Each PR retargets to
mainwhen the one below it merges, and CI starts then.Changes
FAIR cards (the source of truth). Six JSON cards under
koopman_graph/datasets/reaction/data/are loaded withimportlib.resources. I checked that they ship in the built wheel.dataset_idreaction_chain_v1first_order_chain(4)expm{−0.25, −0.5, −1}+ 1 conservation zeroreaction_michaelis_menten_v1reaction_brusselator_v1reaction_random_network_v1sample_random_network(seed=0)reaction_van_de_vusse_v1(T, F/V)stored as globalcontrol_inputsreaction_cantera_h2o2_v1h2o2.yamlReactorNet[cantera]; isothermal const-volume at 1200 K; SHA-256 recordedEach card records:
null= infinite).ReactionNetworkBenchmarkis exported fromkoopman_graph.datasets, like the other*Benchmarkclasses; the root__all__is unchanged.dataset_ids()andload_card(id)list and read the packaged cards.generate(id, *, num_trajectories, num_timesteps, seed, simulator="card"|"ssa", volume)draws seeded initial conditions (and control schedules) and integrates them. It returns aReactionDatasetwith rawfloat64concentrations(N, T, S), optional controls(N, T, C), and the card.simulator="ssa"swaps in Gillespie paths for the uncontrolled, non-Cantera cards.ReactionDataset.sequence(i)returns aGraphSnapshotSequencewith one node per species. Features are the (log-)concentration as a(S, 1)tensor,edge_index/edge_weightcome fromspecies_influence_graph(plushyperedge_indexforreaction_hyperedge), and it carriestimestampsand globalcontrol_inputs.splitdoes a deterministic trajectory-level 70/10/20 split by index (trajectories are i.i.d.).save/loadwrite and read versionedtorch.savepayloads (format_version = 1), loaded withweights_only=True.Keeping cards honest.
compute_card_oracle(card)computes the oracle section. It usesequilibriumandoracle_spectrumfor catalog and random cards, the steady state andJ − D·Iat the control midpoints for the CSTR, and Cantera'sequilibrate("TV")for h2o2.scripts/build_reaction_cards.pywrites the cards, and--checkexits 1 if a committed card is stale.--check.Fixes found while building the cards (separate commit e51a327, plus a follow-up)
equilibriumcould return a false equilibrium. On the converted Cantera h2o2 system, the clamped Newton solve stopped at a near-boundary point (H, O ≈ 0). The absolute residual there was about 5e-11, but net production equalled gross turnover, so flux was one-way and it wasn't a steady state. Cantera's equilibrium has net/gross ≈ 4e-15.equilibriumnow requires|dc_s/dt| ≤ 1e-6·gross_s + 1e-12·(peak gross turnover at initial)and raises otherwise.eigenvalue_timescales(new, used byOracleSpectrum.timescales) maps|Re λ| ≤ 1e-12·max|λ|toinf. Before, the random network's drained mode showed up as an 8e16 timescale instead of infinite.Tests (
tests/datasets/test_reaction_benchmark.py)hold_steps, in range, and exposed ascontrol_inputs.build_reaction_cards.py --checkis clean (skipped without Cantera).Verification (macOS arm64, Python 3.13, Cantera 3.2.0)
ruff checkandruff format --checkonsrc/ tests/ scripts/pass.pytest -n auto -m "not slow and not distributed and not ray" --cov=koopman_graph --cov-fail-under=90: 3516 passed, 26 skipped, 97.30%.benchmark.pyis at 100% andoracles.pyat 99%.sphinx-build -W --keep-goingpasses. The class is documented once, underkoopman_graph.datasets, because the reaction automodule excludes it to avoid a duplicate-object error.python -m build --wheelincludes all 6 cards.Only the example notebook remains for #65 after this.
🤖 Generated with Claude Code