mini-ode
A minimalistic, multi-language library for solving Ordinary Differential Equations (ODEs). mini-ode is designed with a shared Rust core and a consistent interface for both Rust and Python users. It supports explicit, implicit, fixed step and adaptive step algorithms.
✨ Features
- Dual interface: call the same solvers from Rust or Python
- PyTorch-compatible: define the derivative function using PyTorch
- Multiple solver methods: includes explicit, implicit, and adaptive-step solvers
- Modular optimizers: implicit solvers allow flexible optimizer configuration
🧠 Supported Solvers
| Solver Class | Method | Suitable For | Implicit | Adaptive Step |
|---|---|---|---|---|
EulerMethodSolver |
Euler | Simple, fast, and educational use. | ❌ | ❌ |
RK4MethodSolver |
Runge-Kutta 4th Order (RK4) | General-purpose with fixed step size. | ❌ | ❌ |
ImplicitEulerMethodSolver |
Implicit Euler | Stiff or ill-conditioned problems. | ✅ | ❌ |
GLRK4MethodSolver |
Gauss-Legendre RK (Order 4) | High-accuracy, stiff problems. | ✅ | ❌ |
RKF45MethodSolver |
Runge-Kutta-Fehlberg 4(5) | Adaptive step size control. | ❌ | ✅ |
ROW1MethodSolver |
Rosenbrock-Wanner (Order 1) | Fast semi-implicit method for stiff systems. | semi | ❌ |
📦 Building the Library
mini-ode can be built against two different libtorch installations:
- a system-installed libtorch, or
- the libtorch bundled with a PyTorch installation.
It is important that the headers used at compile time and the shared libraries loaded at runtime come from the same installation.
Rust (system libtorch)
To build the Rust library against your system-installed libtorch:
This is the recommended approach if you are developing a pure Rust application.
Python
To build the Python bindings using the PyTorch installation from your active Python environment:
LIBTORCH_USE_PYTORCH=1
This builds the Python bindings using maturin and installs the package locally. LIBTORCH_USE_PYTORCH=1 tells torch-sys to obtain the include directories and libraries from the currently active Python installation.
Building Rust code against a PyTorch installation
If you want to build the Rust version of mini-ode against the libtorch that ships with your Conda or virtual environment PyTorch installation, you must configure both the compiler and the runtime linker.
Using only
LIBTORCH_USE_PYTORCH=1
is not enough.
While this causes torch-sys to compile against the headers from your Python installation, system will typically still load the system libtorch (for example /usr/lib/libtorch.so) at runtime. Simply activating a Conda environment does not guarantee that the Conda libtorch will be used. Mixing the compile-time headers from one PyTorch installation with the runtime libraries from another would lead to runtime failures.
Correct way to build and run:
This ensures that both the compiler and the runtime use the same libtorch installation.
Tip
If your Rust code is loaded as a Python extension, importing
torchbefore importing your extension ensures that Python has already loaded the correct libtorch libraries.
🐍 Python Usage Overview
To use mini-ode from Python:
- Define the derivative function
f(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:xis a scalar tensor (rank 0, shape()).yis a 1D tensor (rank 1, shape(n,)wherenis the dimension of the state vector).- The function must return a 1D tensor of the same shape as
y(i.e.,(n,)).
- Trace the function using
torch.jit.traceto convert it to TorchScript. Provide example inputs matching the shapes (e.g.,torch.tensor(0.)forxandtorch.tensor([0.] * n)fory). - Create a solver instance, configuring parameters like step size or optimizer as needed.
- Call
solver.solve(traced_f, x_span, y0):x_span: A tuple(start, end)for the integration interval.y0: Initial state as a 1D tensor (shape(n,)).- Returns:
(xs, ys)wherexsis a 1D tensor of x-values (shape(num_points,)), andysis a 2D tensor of y-values (shape(num_points, n)).- For fixed-step solvers,
xsis evenly spaced (e.g., viatorch.linspace). - For adaptive-step solvers,
xshas a variable number of points based on error control.
- For fixed-step solvers,
Example usage flow (not full code):
# 1. Define derivative function using PyTorch
return - *
# 2. Trace the function to TorchScript
=
# 3. Create a solver instance
=
# 4. Solve the ODE
, =
🔧 Using Optimizers (Implicit Solvers Only)
Some solvers like GLRK4MethodSolver or ImplicitEulerMethodSolver require an optimizer for nonlinear system solving:
=
=
🦀 Rust Usage Overview
In Rust, solvers use the same logic as in Python - but you pass in a tch::CModule representing the TorchScripted derivative function.
Example 1: Load a TorchScript model from file
This approach uses a model traced in Python (e.g., with torch.jit.trace) and saved to disk.
use Solver;
use ;
Example 2: Trace the derivative function directly in Rust
You can also define and trace the derivative function in Rust using CModule::create_by_tracing.
use Solver;
use ;
📁 Project Structure
mini-ode/ # Core Rust implementation of solvers
mini-ode-python/ # Python bindings using PyO3 + maturin
example.ipynb # Jupyter notebook demonstrating usage
📄 License
This project is licensed under the GPL-2.0 License.
👤 Author
Antoni Michał Przybylik
📧 antoni@taon.io
🔗 https://github.com/antoniprzybylik