The dynamic dispatch API
The diffsol-c crate has two APIs that let you use
diffsol from other languages:
- The dynamic dispatch API, which this page describes. It is a Rust API. It wraps the core
diffsolsolver in types that select their implementation at runtime. Thus you can select the matrix backend, the scalar type, the linear solver, the ODE solver method and the JIT backend at runtime, not at compile time. - The C API. It gives the dynamic dispatch API as
extern "C"functions, so that C code, or a language that can call C, can use it. See The C API.
Key types
The central type is
OdeWrapper. It keeps
the solver state in an Arc<Mutex<...>>, so that threads and FFI code can share it safely. Its
methods create, configure, solve and serialize the solver.
You configure the solver with these runtime enum types:
- OdeSolverType: the ODE integration method.
- MatrixType: the linear algebra backend.
- ScalarType: the floating-point precision of the solver.
- LinearSolverType: the linear solver of the implicit methods.
- JitBackendType: the JIT compilation backend for DiffSL code.
A solve returns a
SolutionWrapper.
From it, you can get the time points, the state values and the sensitivities as
Tensor
objects. A Tensor shares the data of the solution, with no copy, and keeps the solution alive,
so it stays valid after the SolutionWrapper is dropped.
The inputs of the solve methods, for example the parameters and the times t_eval, are
TensorView objects.
A TensorView borrows the data of the caller, with no copy. It is small and only on the stack,
and it has at most 3 axes. Make one from a slice with TensorView::from_slice, or from a
Tensor with Tensor::view.
The values on the first axis of a tensor or a view are always adjacent (column-major, or Fortran,
order), so each column [:, j, k] is one slice: column and columns give these slices with no
copy. A view of a row-major array is not possible: make a column-major copy first, for example
with numpy.asfortranarray.
The arrays use the column-major order of diffsol, and the last axis of an array of lanes is the
lane. For example, the parameters are an nparams x nlanes
array, and the state values of a solution are an nout x nt x nbatch array.
You can give the equations as DiffSL code (OdeWrapper::new_jit, new_external and
new_external_dynamic), or as C callbacks (OdeWrapper::new_callbacks, see
The C API).
Example: the logistic equation
This example uses the dynamic dispatch API to solve the logistic ODE
\(dy/dt = r \cdot y \cdot (1 - y)\) from Rust. All of the solver configuration occurs at
runtime, with the OdeWrapper API.
The DiffSL code of the logistic equation:
let code = r#"
in_i { r = 2.0 }
u_i { y = 0.1 }
dudt_i { dydt = 0 }
F_i { (r * y) * (1 - y) }
out_i { y }
"#;
Create the OdeWrapper with the Cranelift JIT backend, f64 precision and the BDF solver:
let ode = OdeWrapper::new_jit(
code,
JitBackendType::Cranelift,
ScalarType::F64,
MatrixType::NalgebraDense,
LinearSolverType::Default,
OdeSolverType::Bdf,
)
.unwrap();
Set the solver tolerances:
ode.set_rtol(1e-6).unwrap();
ode.set_atol(1e-8).unwrap();
Solve on a fixed time grid:
let times = vec![0.0, 0.25, 0.5, 0.75, 1.0];
// the inputs are views of Rust slices, with no copy
// params is a `[nparams, nlanes]` array: here one lane with one parameter
let params = TensorView::from_slice(&[2.0], &[1, 1]);
// t_eval is a `[nt]` array
let t_eval = TensorView::from_slice(×, &[times.len()]);
let solution = ode.solve_dense(params, t_eval).unwrap();
Get the solution data from the SolutionWrapper:
// ys is a `[nout, nt, nbatch]` array; ts is a `[nt]` array that all lanes share.
let ys = solution.get_ys().unwrap();
let ts = solution.get_ts().unwrap();
The full example is in examples/diffsol-c-logistic.