Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The dynamic dispatch API

The diffsol-c crate has two APIs that let you use diffsol from other languages:

  1. The dynamic dispatch API, which this page describes. It is a Rust API. It wraps the core diffsol solver 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.
  2. 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:

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, &[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.