4. Ordinary differential equations
State makes history matter
Section titled “State makes history matter”An ordinary differential equation relates quantities to derivatives with respect to one independent variable, here time. A first-order initial-value problem needs both an evolution relation and an initial state. Without the initial value, the relation describes a family of trajectories rather than one trajectory.
Learning outcomes
Section titled “Learning outcomes”After this chapter, you should be able to:
- identify state, parameter, derivative relation, and initial value;
- derive the closed form for constant-rate scalar decay;
- keep the time method and output schedule outside the Model;
- execute the example with the installed Python package and select a series by
its exact
FieldRef; and - diagnose a physically wrong sign that remains dimensionally consistent.
Derive the bounded problem
Section titled “Derive the bounded problem”Let dimensionless state decay with constant rate :
Separating variables and integrating from to gives
so the independently stated comparison is
The executable lesson uses and observes , , and . Those choices bound this lesson; they do not define general ODE support.
Model source
Section titled “Model source”// A minimal implicit ODE: x decays at the rate supplied by the user.model decay() { // `x` is dimensionless and starts at 1. `rate` has inverse-time units. state x: 1; initial { x = 1; } parameter rate: 1 / s = 1;
// Eqiora writes the evolution law as a residual equal to zero. relation flow { derivative(x) + rate * x = 0; }}The initial value and rate belong to the Model. The adaptive method, its tolerances, the final time, and requested observations belong to the Realization or Run.
Execute the model
Section titled “Execute the model”Follow Get started to install the source build and download the same
decay.eqi and run.py used here. Set rate to 1, then run from their folder:
$ uv run --no-project --python .venv/bin/python python run.pyThe complete script uses the public compile → resolve → run path:
"""Run the decay model shared by Get started and the ODE lesson."""
from __future__ import annotations
import argparsefrom pathlib import Path
import eqiora
OUTPUT_TIMES_S = (0.25, 0.5, 1.0)
def solve(source_path: Path) -> tuple[tuple[float, float], ...]: """Return the requested (time in seconds, dimensionless value) samples."""
model = eqiora.compile(path=source_path) field = model.field(model.field_ids[0]) plan = eqiora.resolve( model, temporal=eqiora.time.Tsitouras45( initial_step_s=0.01, relative_tolerance=1.0e-9, absolute_tolerances={field: 1.0e-11}, ), ) result = eqiora.run( plan, state=eqiora.State.initial(plan), until_s=OUTPUT_TIMES_S[-1], output_times_s=OUTPUT_TIMES_S, ) series = result.series(field) times = series.time.numpy() values = series.values.numpy() return tuple( (float(time_s), float(value)) for time_s, value in zip(times, values, strict=True) )
def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("model", nargs="?", type=Path, default=Path("decay.eqi")) args = parser.parse_args() for time_s, value in solve(args.model): print(f"t={time_s:.2f}, x={value:.10f}")
if __name__ == "__main__": main()View the maintained run script.
The program prints the time and computed value. Compare each value with independently. The focused installed-package test requires the absolute difference to remain at most for exactly these three samples. That is adequate for this lesson; it is not a tolerance for a different model, interval, backend, or method.
Observation boundary
Section titled “Observation boundary”The Result owns an immutable series selected by the exact field handle. This
chapter observes only three scalar values. It does not inspect internal adaptive
steps, infer values between requested times, or treat plotting choices as model
meaning.
The closed form supplies the comparison independently of the program’s output. If the comparison fails, the expected expression is not replaced with the computed values; the model, realization, execution, and test are investigated.
Deliberate failure: the wrong sign still compiles
Section titled “Deliberate failure: the wrong sign still compiles”Change the residual to:
relation flow { derivative(x) - rate * x = 0;}Both terms still have inverse-time dimension, so compilation can succeed. But the relation now implies : growth instead of decay. Comparing it with the pre-stated decay solution must fail. The repair is to revisit the physical assumption and residual sign, not to loosen the comparison tolerance.
Exercises
Section titled “Exercises”- Derive the solution for an arbitrary positive initial value .
- Predict the three observed values when
rateis , then state which part of the source and which comparison expression must change. - Run the wrong-sign mutant. Explain why dimensional checking accepts it and the closed-form comparison rejects it.
- Add one requested output time without changing the Model. Explain why this changes a Run request rather than mathematical meaning.
- List two reasons the three passing samples do not establish a convergence rate or suitability for stiff systems.
Previous: Algebraic relations and networks · Back to the series map · Next: Fields and spatial domains