This vignette walks through the minimal setup for solving an ODE
system with odelia, using the classic Lorenz system as
the worked example. The Lorenz system is built into the package, so
everything below runs against library(odelia) directly — no
C++ compilation needed.
The core abstractions
A simulation in odelia is assembled from a few
pieces:
- a system — the ODE model itself (parameters, state,
and a rule for the rates
dy/dt); - a control object (
OdeControl) — the solver’s tolerances and step sizes; - a solver (or runner) — which drives the adaptive RK4-5 stepper forward over the times you request and collects the solution history.
Define the system
The Lorenz system has three state variables and three parameters
(sigma, R, b). We create it and
set an initial state. The Lorenz system is autonomous (its rates do not
depend on time directly), so no time is required.
lz <- LorenzSystem$new(sigma = 10, R = 28, b = 8 / 3)
# Inspect stored parameters
lz$pars()
#> [1] 10.000000 28.000000 2.666667
# Set the initial state (x, y, z)
lz$set_state(c(1, 1, 1))
# We can query the current state and rates directly
lz$state()
#> [1] 1 1 1
lz$rates()
#> [1] 0.000000 26.000000 -1.666667Solve the system
Create a control object (the defaults are sensible) and a solver, then advance with adaptive time stepping over the times we want output at.
ctrl <- OdeControl$new()
runner <- Lorenz_Solver$new(lz$ptr, ctrl$ptr)
runner$advance_adaptive(seq(0, 100, by = 0.01))
out <- runner$history()
head(out)
#> # A tibble: 6 × 7
#> time x y z dxdt dydt dzdt
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 0 1 1 1 0 26 -1.67
#> 2 0.01 1.01 1.26 0.985 2.47 26.1 -1.35
#> 3 0.02 1.05 1.52 0.973 4.75 26.8 -0.997
#> 4 0.03 1.11 1.80 0.965 6.91 28.1 -0.583
#> 5 0.04 1.19 2.09 0.962 9.02 30.0 -0.0858
#> 6 0.05 1.29 2.40 0.964 11.1 32.4 0.520history() returns a tibble with one row per requested
output time. Because the stepper is adaptive, the solver may take more
internal steps than there are output rows — the internal evaluation
times are available via runner$times().
Plot the attractor
plot(out$x, out$z,
type = "l",
xlab = "x", ylab = "z",
main = "Lorenz attractor (via odelia)"
)
Controlling the solver
OdeControl exposes the solver’s tuning knobs —
absolute/relative tolerances, state and derivative scaling, and
minimum/maximum/initial step sizes. You can inspect the current settings
or tighten them:
ctrl$get_controls()
#> $tol_abs
#> [1] 1e-08
#>
#> $tol_rel
#> [1] 1e-08
#>
#> $a_y
#> [1] 1
#>
#> $a_dydt
#> [1] 0
#>
#> $step_size_min
#> [1] 1e-08
#>
#> $step_size_max
#> [1] 10
#>
#> $step_size_initial
#> [1] 1e-06
# Tighten tolerances for a more accurate (but slower) solve
ctrl$set_tol_abs(1e-8)
ctrl$set_tol_rel(1e-8)Where next
- For models with external time-varying forcing, and how to define your own ODE system in C++, see Building your own model with external drivers.
- To recover parameters from data using exact gradients, see
vignette("parameter-fitting", package = "odelia").