Skip to content

v0.16.0

Latest

Choose a tag to compare

@tjkessler tjkessler released this 15 Oct 22:05
c2f9c34

Mass-action reaction-network datasets under
koopman_graph.datasets.reaction, with exact oracle references
(equilibria, conservation laws, Jacobian spectra and timescales) for
checking whether a learned latent operator recovers kinetic timescales
and respects conservation. ReactionNetworkBenchmark is the only new
name in koopman_graph.datasets; the root __all__, homogeneous
scientific defaults, and the linear latent operator contract are
unchanged (koopman=None still selects "pernode"). SciPy and Cantera
stay optional behind the new [reaction] / [cantera] extras.
FORMAT_VERSION stays 1.

Added

  • koopman_graph.datasets.reaction subpackage (pure PyTorch unless an
    optional extra is used) with the modules below and packaged FAIR cards
    under reaction/data/. Its names are exported from the subpackage;
    only ReactionNetworkBenchmark is added to koopman_graph.datasets,
    and the root __all__ is unchanged.

  • koopman_graph.datasets.reaction.system: frozen ReactionSystem
    (reactant / product stoichiometry, mass-action rates and right-hand side,
    stoichiometric matrix, linearity check, optional element matrix and
    per-reaction ArrheniusParams, at_temperature, dict round trip) and
    check_element_balance, which reports per-reaction element residuals
    (open-system source / sink reactions show up as unbalanced). Stored
    tensors are owned copies but not write-protected; treat them as
    read-only.

  • koopman_graph.datasets.reaction.transform: LogConcentrationTransform
    (floored log c, its inverse, a below-floor mask, and a serializable
    record of the floor) and element_balance_violation for element-total
    drift from a reference state.

  • koopman_graph.datasets.reaction.graphs: species_influence_graph
    (directed species graph matching the mass-action Jacobian's sparsity,
    weighted by linking-reaction count), reaction_hyperedge_index (one
    hyperedge per reaction), reaction_directed_hyperedges (reactant tail /
    product head incidences), and species_reaction_bipartite (PyG
    HeteroData with stoichiometric edge weights).

  • koopman_graph.datasets.reaction.integrate: exact augmented
    matrix-exponential propagation for linear systems (integrate_expm,
    linear_rate_equations), batched and differentiable torch RK4
    (integrate_rk4), SciPy BDF / Radau / LSODA with an exact mass-action
    Jacobian behind a lazy require_scipy() guard (integrate_stiff;
    [reaction] extra), the integrate_reaction_system dispatcher
    (method="auto"), and mass_action_jacobian.

  • koopman_graph.datasets.reaction.catalog: reference systems with frozen
    default parameters (first_order_chain, reversible_dimerization,
    michaelis_menten, brusselator, lotka_volterra). Closed systems carry
    an element matrix.

  • koopman_graph.datasets.reaction.oracles: conservation_laws (orthonormal
    left nullspace of N), stoichiometric_rank, equilibrium (exact for
    linear systems, damped non-negative Newton within the compatibility
    class otherwise), and oracle_spectrum / oracle_timescales /
    OracleSpectrum. These give the Jacobian, dynamic eigenvalues with the
    conservation zero modes removed, timescales -1/Re(λ), and flow-map
    eigenvalues exp(λ Δt) to compare with a learned discrete operator.

  • koopman_graph.datasets.reaction.random_network: sample_random_network
    builds seeded, element-balanced mass-action networks. Species
    compositions are sampled first, so every candidate reaction conserves
    elements; reaction order is capped at 2 (max_order=1 gives linear
    dissociation networks); rates are log-uniform. The returned
    SampledReactionNetwork records the seed and every sampler parameter.

  • koopman_graph.datasets.reaction.cstr: van_de_vusse_cstr /
    VanDeVusseCSTR, a Van de Vusse CSTR with Klatt & Engell (1998)
    Arrhenius kinetics. Reactor temperature and dilution rate are inputs,
    either constant or piecewise-constant schedules. It provides the
    controlled right-hand side, RK4 simulation, and a Newton steady state; the
    nominal point (114.2 °C, F/V = 14.19 1/h) gives c_A ≈ 2.14,
    c_B ≈ 1.09.

  • koopman_graph.datasets.reaction.gillespie: seeded Gillespie direct-method
    SSA (gillespie_ssa, SSATrajectory) with mass-action propensities
    (falling factorials, volume scaling) that match the ODE rates, plus
    last-value-hold binning onto a grid (bin_to_grid).

  • koopman_graph.datasets.reaction.cantera_io (lazy [cantera] boundary):

    • load_cantera_mechanism converts a Cantera gas mechanism (default the
      bundled h2o2.yaml, read from the installed package) to a mass-action
      ReactionSystem at a reference (T, P, X). Reversible reactions are
      split into forward and reverse. Elementary and mass-action three-body
      reactions convert exactly: [M] is expanded into one reaction per
      collider with rate k ε_s.
    • Falloff and other state-dependent rates are frozen at the reference
      state, skipped, or rejected (state_dependent=); frozen rates with a
      mass-action third body keep the per-collider [M] expansion.
      Reactions with non-mass-action orders are skipped or rejected
      (orders equal to the stoichiometry convert normally). Rate constants
      are read with Cantera's legacy rate-constant mode off, whatever the
      process setting. Duplicate reactions get unique names. Every frozen or
      skipped reaction is listed in CanteraProvenance, which also records
      the Cantera version, mechanism path and SHA-256, the reference state,
      the "illustration only" caveat, and the Cantera citation DOI.
    • simulate_cantera_reactor runs constant-pressure or constant-volume
      ideal-gas reactors, adiabatic or isothermal.
    • Also: require_cantera, resolve_mechanism_path (bare names prefer
      Cantera's bundled data over the working directory), and
      mechanism_sha256.
  • [reaction] extra (scipy>=1.11.0) for stiff reaction-network solvers
    and [cantera] extra (cantera>=3.2,<3.3) for Cantera mechanisms.

  • cantera pytest marker; marked tests are skipped automatically when the
    [cantera] extra is not installed.

  • CI test jobs and the release verify job install [reaction] and
    [cantera], so cantera-marked tests run there.

  • tests/test_import_hygiene.py: import koopman_graph must not load
    SciPy, Cantera, or torchdiffeq.

  • ReactionNetworkBenchmark (exported from koopman_graph.datasets and
    koopman_graph.datasets.reaction): card-defined, seeded reaction-network
    datasets.

    • Six packaged FAIR JSON cards under reaction/data/:
      reaction_chain_v1, reaction_michaelis_menten_v1,
      reaction_brusselator_v1, reaction_random_network_v1,
      reaction_van_de_vusse_v1 (piecewise-constant temperature /
      dilution-rate controls), and reaction_cantera_h2o2_v1 ([cantera]).
      Each card records the generator, simulator, time grid, seed,
      initial-condition ranges, log floor, graph encoding, oracle references
      (equilibrium, dynamic eigenvalues, timescales, conservation laws),
      units, license, limitations, and Cantera provenance.
    • generate returns a ReactionDataset (raw float64 concentrations,
      optional controls, per-trajectory GraphSnapshotSequence with species
      nodes, timestamps, and global control_inputs), with an optional
      Gillespie simulator="ssa". Also: deterministic trajectory split, and
      versioned save / load payloads (weights_only loading).
    • compute_card_oracle and scripts/build_reaction_cards.py (with
      --check) regenerate the oracle sections; tests recompute them to catch
      drift.
  • Example 55_reaction_network_oracles.ipynb compares estimators against the
    ReactionNetworkBenchmark oracles:

    • exact DMD recovers the chain's exp(λ Δt) (errors ~1e-7) and conserves
      mass in rollouts;
    • DMD on the card's log features misses by up to ~0.13;
    • a short koopman="graph" fit is approximate, and
      element_balance_violation flags its rollout drift;
    • a late-window DMD recovers the Michaelis-Menten slow mode against each
      trajectory's own equilibrium linearization.

    Registered in the PR notebook smoke set and the full 27-55 shard.

  • ReactionDataset.sequence(..., graph_encoding=) overrides the card's
    encoding (e.g. drop hyperedge_index for DMDBaseline).

  • eigenvalue_timescales: -1/Re(λ) with numerical zeros mapped to inf
    (an optional absolute zero_tolerance, else eigensolver round-off
    100 · eps · max|λ|); OracleSpectrum.timescales uses it with its own
    zero_tolerance. ReactionNetworkBenchmark.generate refuses a Cantera
    card whose recorded mechanism SHA-256 differs from the installed file.

Changed

  • STGODEBaseline now imports torchdiffeq on first fit / predict instead
    of at module import, so import koopman_graph with [baselines-ode]
    installed no longer loads SciPy. A missing torchdiffeq is no longer
    cached: installing the extra mid-session takes effect on the next call.
  • uv.lock relocked; it was missing extras already declared in
    pyproject.toml (export, control-lmi, ...).

Fixed

  • conservation_laws returns a canonical basis (Gram-Schmidt in species
    order on the basis-independent projector) instead of whatever an SVD
    returns. With two or more conservation laws the SVD basis differed
    between LAPACK builds, so the Michaelis-Menten, random-network, and
    Cantera cards did not reproduce on Linux CI. Card oracle values change
    only at rounding level (<= 3e-14) apart from the rotated laws.

  • equilibrium no longer returns a clamped Newton stall that is not
    stationary. It now requires |dc_s/dt| <= 1e-6 · gross turnover_s + 1e-12 · (peak initial turnover) and otherwise raises RuntimeError.
    Previously the converted Cantera h2o2 system could report a
    near-boundary point with one-way flux as its equilibrium.