7 releases
Uses new Rust 2024
| 0.1.4 | Nov 12, 2025 |
|---|---|
| 0.1.3 | Oct 26, 2025 |
| 0.1.1 | May 22, 2025 |
| 0.0.3 | May 20, 2025 |
| 0.0.2 | Apr 27, 2025 |
#393 in Algorithms
79KB
1.5K
SLoC
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
Rust
To build the core Rust library:
cd mini-ode
cargo build --release
Python
To build and install the Python package (in a virtual environment or Conda environment):
cd mini-ode-python
LIBTORCH_USE_PYTORCH=1 maturin develop
This builds the Python bindings using
maturinand installs the package locally.
π 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):
import torch
import mini_ode
# 1. Define derivative function using PyTorch
def f(x: torch.Tensor, y: torch.Tensor):
return y.flip(0) - torch.tensor([0, 1]) * (y.flip(0) ** 3)
# 2. Trace the function to TorchScript
traced_f = torch.jit.trace(f, (torch.tensor(0.), torch.tensor([0., 0.])))
# 3. Create a solver instance
solver = mini_ode.RK4MethodSolver(step=0.01)
# 4. Solve the ODE
xs, ys = solver.solve(traced_f, (0., 5.), torch.tensor([1.0, 0.0]))
π§ Using Optimizers (Implicit Solvers Only)
Some solvers like GLRK4MethodSolver or ImplicitEulerMethodSolver require an optimizer for nonlinear system solving:
optimizer = mini_ode.optimizers.CG(
max_steps=5,
gtol=1e-8,
)
solver = mini_ode.GLRK4MethodSolver(step=0.2, optimizer=optimizer)
π¦ 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 mini_ode::Solver;
use tch::{Tensor, CModule};
fn main() -> anyhow::Result<()> {
let solver = Solver::Euler { step: 0.01 };
let model = CModule::load("my_traced_function.pt")?;
let x_span = (0.0, 2.0);
let y0 = Tensor::from_slice(&[1.0f64, 0.0]);
let (xs, ys) = solver.solve(model, x_span, y0)?;
println!("{:?}", xs);
Ok(())
}
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 mini_ode::Solver;
use tch::{Tensor, CModule};
fn main() -> anyhow::Result<()> {
// Initial value for tracing
let y0 = Tensor::from_slice(&[1.0f64, 0.0]);
// Define the derivative function closure
let mut closure = |inputs: &[Tensor]| {
let x = &inputs[0];
let y = &inputs[1];
let flipped = y.flip(0);
let dy = &flipped - &(&flipped.pow_tensor_scalar(3.0) * Tensor::from_slice(&[0.0, 1.0]));
vec![dy]
};
// Trace the model directly in Rust
let model = CModule::create_by_tracing(
"ode_fn",
"forward",
&[Tensor::from(0.0), y0.shallow_clone()],
&mut closure,
)?;
// Use an adaptive solver, for example
let solver = Solver::RKF45 {
rtol: 0.00001,
atol: 0.00001,
min_step: 1e-9,
safety_factor: 0.9
};
let x_span = Tensor::from_slice(&[0.0f64, 5.0]);
let (xs, ys) = solver.solve(model, x_span, y0)?;
println!("Final state: {:?}", ys);
Ok(())
}
π 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
Dependencies
~14MB
~277K SLoC