Version: dev

derive_odes

Stable function · Reactions

Tier: stable · Kind: function · Topic: Reactions

JuliaTypeScriptPythonRustGo
derive_odesderiveODEs, deriveOdesderive_odesderive_odesDeriveODEs

Note: spelling differs from the §2 rule: go=DeriveODEs (expected DeriveOdes); typescript=deriveODEs (expected deriveOdes); multiple spellings exported by: typescript

Julia

derive_odes(rxn_sys::ReactionSystem)
derive_odes(rxn_sys::ReactionSystem) -> Model

Generate an ODE model from a reaction system using standard mass action kinetics.

Creates state variables for each species, parameters for rate constants, and equations with d[species]/dt = net_stoichiometry * rate_law. Handles source reactions (null substrates), sink reactions (null products), and constraint equations.

Arguments

  • rxn_sys::ReactionSystem: Input reaction system with species, reactions, and parameters

Returns

  • Model: ODE model with variables, equations, and optional events/subsystems

Algorithm (Libraries Spec Section 7.4)

  1. Create state variables for all species in the reaction system, except reservoir species (constant: true), which become parameters
  2. Create parameter variables for all parameters (rate constants)
  3. For each reaction, compute the mass action rate expression
  4. For each species, compute the net rate of change from all reactions
  5. Create differential equations d[species]/dt = net_rate, except for reservoir species, which are held fixed and get none

Examples

# Simple A + B -> C reaction
species = [Species("A"), Species("B"), Species("C")]
reactions = [Reaction(Dict("A"=>1, "B"=>1), Dict("C"=>1), VarExpr("k1"))]
parameters = [Parameter("k1", 0.1)]
rxn_sys = ReactionSystem(species, reactions, parameters=parameters)

model = derive_odes(rxn_sys)
# Returns Model with:
# - State variables: A, B, C
# - Parameter: k1
# - Equations: d[A]/dt = -k1*A*B, d[B]/dt = -k1*A*B, d[C]/dt = k1*A*B

Source

Example (reactions)

doc = load_path(joinpath(FIXTURES, "ozone.esm"))
rs = doc.reaction_systems["Ozone"]
@assert rs isa ReactionSystem
@assert length(rs.reactions) == 2

odes = derive_odes(rs)  # a Model with one D(species)/dt per species
for eq in odes.equations
    println(to_ascii(eq))
end
@assert [to_ascii(eq) for eq in odes.equations] == [
    "D(O3)/Dt = -1 * k1 * NO * O3 + jNO2 * NO2",
    "D(NO)/Dt = -1 * k1 * NO * O3 + jNO2 * NO2",
    "D(NO2)/Dt = k1 * NO * O3 + -1 * jNO2 * NO2",
]

# Rows are species (O3, NO, NO2), columns are reactions (R1, R2).
@assert stoichiometric_matrix(rs) == [-1.0 1.0; -1.0 1.0; 1.0 -1.0]

From docs/api/examples/julia/reactions.jl

TypeScript

Exported as deriveODEs and deriveOdes. Both names refer to the same operation.

deriveODEs

function deriveODEs(system: ReactionSystem): Model

Deprecated. Use deriveOdes.


Deprecated. Spelling alias for deriveOdes , kept for one minor per API_SPEC.md §10. The canonical name is derive_odes, whose TypeScript transliteration (API_SPEC §2/§2.1) is deriveOdes, matching the siblings odeStates / isOdeState. Removed at the next major.

Source

deriveOdes

function deriveOdes(system: ReactionSystem): Model

Derive ODEs from a reaction system using mass action kinetics

Generates an ODE model from reaction stoichiometry and rate laws. For each reaction with rate k, substrates {Si} with stoichiometries {ni}, products {Pj} with stoichiometries {mj}:

  • rate law: v = k * prod(Si^ni)
  • ODE contribution: dX/dt += net_stoich_X * v

Handles:

  • Source reactions (null substrates): rate is the direct production term
  • Sink reactions (null products): rate is the direct loss term
  • Constraint equations are appended as additional equations

Parameters

  • system — ReactionSystem to derive ODEs from

Returns

Model with species as state variables, derived ODEs plus constraints

Source

Example (reactions)

const doc = loadPath(fixture('ozone.esm'))
const system = doc.reaction_systems!.Ozone
assert.ok(!('ref' in system)) // narrows away an unresolved `{ ref }` mount

// Mass-action kinetics: one ODE per species.
const odes = deriveOdes(system)
assert.deepEqual(odeStates(odes).sort(), ['NO', 'NO2', 'O3'])
assert.deepEqual(
  odes.equations.map((eq) => toAscii(eq)),
  [
    'D(O3)/Dt = -1 * k1 * NO * O3 + jNO2 * NO2',
    'D(NO)/Dt = -1 * k1 * NO * O3 + jNO2 * NO2',
    'D(NO2)/Dt = k1 * NO * O3 + -1 * jNO2 * NO2',
  ],
)

// Net stoichiometry: rows are species, columns are reactions.
const { matrix, species, reactions } = stoichiometricMatrix(system)
assert.deepEqual(species, ['O3', 'NO', 'NO2'])
assert.deepEqual(reactions, ['R1', 'R2'])
assert.deepEqual(matrix, [
  [-1, 1],
  [-1, 1],
  [1, -1],
])

From docs/api/examples/typescript/reactions.ts

Python

derive_odes(system: ReactionSystem) -> Model

Derive ODEs from a reaction system using mass action kinetics.

Thin wrapper around lower_reactions_to_equations that bundles the resulting equations with a variables dict (state variables for species, parameter variables for rate constants) into a Model . Handles source reactions (null substrates) and sink reactions (null products).

Parameters

  • system (ReactionSystem) — ReactionSystem containing species, reactions, and parameters

Returns

  • Model Model — Mathematical model with ODE equations for each species

Raises

  • ValueError — If system is empty or contains invalid reactions

Source

Example (reactions)

doc = earthsci_ast.load_path(FIXTURES / "ozone.esm")
system = doc.reaction_systems["Ozone"]
assert [s.name for s in system.species] == ["O3", "NO", "NO2"]
assert [r.id for r in system.reactions] == ["R1", "R2"]

odes = earthsci_ast.derive_odes(system)
for eq in odes.equations:
    print(earthsci_ast.to_ascii(eq))

# One equation per species, in declaration order; rate constants become parameters.
assert earthsci_ast.ode_states(odes) == ["NO", "NO2", "O3"]
assert [eq.lhs.args[0] for eq in odes.equations] == ["O3", "NO", "NO2"]
assert odes.variables["k1"].type == "parameter"
assert earthsci_ast.to_ascii(odes.equations[2]) == "D(NO2)/Dt = k1 * NO * O3 + -1 * jNO2 * NO2"

From docs/api/examples/python/reactions.py

Rust

pub fn derive_odes(system: &ReactionSystem) -> Result<Model, DeriveError>

Generate ODE model from a reaction system

Converts a reaction system into an ODE model with species as state variables and reactions contributing to their derivatives using mass action kinetics.

Mass action kinetics: rate law = k * product(substrates^stoichiometry) Net stoichiometry = products - substrates d[species]/dt = sum(net_stoichiometry * rate_law)

Arguments

  • system - The reaction system to convert

Returns

  • Result<Model, DeriveError> - ODE model with species as state variables, or error

Errors

Returns DeriveError for invalid stoichiometry, missing rate laws, or unit conversion issues.

Source · Rust docs

Example (reactions)

let doc = load_path(fixture("ozone.esm"))?;
let system = &doc.reaction_systems.as_ref().unwrap()["Ozone"];

// derive_odes turns the reactions into an ordinary model: one
// differential equation per species, with mass-action rate laws.
let model = derive_odes(system)?;
for eq in &model.equations {
    println!("{}", to_ascii(eq));
}
let mut states = ode_states(&model);
states.sort();
assert_eq!(states, ["NO", "NO2", "O3"]);
let no2 = model.equations.iter().find(|eq| to_ascii(&eq.lhs) == "D(NO2)/Dt").unwrap();
assert_eq!(to_ascii(&no2.rhs), "k1 * NO * O3 + -1 * jNO2 * NO2");
assert_eq!(model.equations.len(), 3);

From docs/api/examples/rust/reactions.rs

Go

func DeriveODEs(system *ReactionSystem) (*Model, error)

DeriveODEs converts a reaction network into a Model whose equations are the species’ ODEs under mass-action kinetics (esm-spec §7.4).

Species become UNKNOWNS — the D(s,t) equation emitted below is what makes one an ODE state under the §6.3.1 derived classification — except reservoir species (constant: true), which are held fixed and get no ODE, so they lower to PARAMETERS carrying their declared default. Rate parameters become parameters. Subsystems are derived recursively, as in the Julia reference.

The equations come from the same lowerReactionsToEquations the flatten path uses, so a system’s ODEs are identical whether reached through here or through Flatten. That is deliberate and mirrors how Julia and Rust are arranged — both have derive_odes call their own shared lowering helper rather than reimplementing mass action beside it. The helper is not itself exported: it takes an explicit species order that only a caller which has already decided the document’s ordering (flatten) can supply meaningfully, and exporting that argument would export a decision this entry point is here to make.

Source · Go docs

Example (reactions)

doc, err := esm.LoadPath(fixture("ozone.esm"))
if err != nil {
	log.Fatal(err)
}
system := doc.ReactionSystems["Ozone"]
odes, err := esm.DeriveODEs(&system)
if err != nil {
	log.Fatal(err)
}
for _, eq := range odes.Equations {
	fmt.Println(esm.ToASCII(eq.LHS), "=", esm.ToASCII(eq.RHS))
}

// One row per species, one column per reaction.
fmt.Println(esm.StoichiometricMatrix(&system))

From docs/api/examples/go/reactions_test.go