Skip to content

Solver Options & Configuration

All ODE solvers share a common SolverOptions struct that controls tolerances, step size, output behavior, and event detection.

The most important settings are the relative and absolute tolerances. The solver controls the local error at each step to satisfy:

eiatol+rtolyi|e_i| \leq \text{atol} + \text{rtol} \cdot |y_i|

This is assessed per-component using a weighted RMS norm.

let options = SolverOptions::default()
.rtol(1e-8) // relative tolerance (default: 1e-6)
.atol(1e-10); // absolute tolerance (default: 1e-9)

Guidelines for choosing tolerances:

Accuracy neededrtolatolUse case
Quick survey1e-31e-6Exploring parameter space
Standard1e-61e-9Most engineering applications
High accuracy1e-81e-11Reference solutions, validation
Very high1e-101e-13Publication-quality, celestial mechanics
Near machine eps1e-131e-15Theoretical studies (use Vern7/Vern8)

The absolute tolerance matters when solution components pass through zero. If atol is too large, the solver won’t control error on small-valued components. If your components have very different scales, consider scaling your equations so all components are O(1).

let options = SolverOptions::default()
.h0(1e-3) // initial step size (default: auto-detected)
.h_max(0.1) // maximum step size (default: infinity)
.h_min(1e-14) // minimum step size (default: 1e-14)
.max_steps(50000); // step limit (default: 100,000)

By default, the solver automatically selects a starting step size based on the problem’s derivatives. Setting h0 explicitly is useful when:

  • You know the characteristic time scale of your problem
  • The automatic selection is too conservative or too aggressive
  • You want reproducible step sequences for testing

Limits how large a single step can be. Useful when:

  • The solution has features at known time scales that might be “stepped over”
  • You need sufficient time resolution in the output
  • The problem has discontinuous forcing at known times

If the step size drops below h_min, the solver returns a StepTooSmall error. This is a safety net for detecting problems that are too stiff for the chosen solver.

Prevents runaway integration. If the solver exceeds this count, it returns a MaxSteps error. Increase this for very long integrations or very tight tolerances.

By default, the solver saves the solution at every internal step. Use t_eval to get output at specific times instead:

let t_output: Vec<f64> = (0..=100).map(|i| i as f64 * 0.1).collect();
let options = SolverOptions::default()
.t_eval(t_output);

The solver uses interpolation to provide the solution at the requested times without reducing step sizes.

Enable dense output for continuous interpolation between steps:

let options = SolverOptions::default().dense();

When enabled, the solver stores interpolation coefficients at each step. This allows computing the solution at any time in the integration interval after the solve completes. Currently supported by DoPri5 (4th-order polynomial interpolant).

use numra::ode::{DoPri5, Solver, OdeProblem, SolverOptions};
let problem = OdeProblem::new(
|_t, y: &[f64], dydt: &mut [f64]| {
dydt[0] = -0.5 * y[0]; // slow exponential decay
},
0.0, 100.0, vec![1.0],
);
let options = SolverOptions::default()
.rtol(1e-8)
.atol(1e-10)
.h_max(1.0) // don't skip large regions
.max_steps(10000); // plenty of budget
let result = DoPri5::solve(&problem, 0.0, 100.0, &[1.0], &options).unwrap();
println!("Solved in {} steps", result.stats.n_accept);
println!("Final value: {:.6e}", result.y_final().unwrap()[0]);
OptionDefaultDescription
rtol1e-6Relative tolerance
atol1e-9Absolute tolerance
h0None (auto)Initial step size
h_maxinfinityMaximum step size
h_min1e-14Minimum step size
max_steps100,000Maximum number of steps
t_evalNone (all steps)Output time points
dense_outputfalseEnable dense interpolation
eventsemptyEvent functions