Skip to main content

mini_ode/
lib.rs

1//! # mini-ode
2//!
3//! **mini-ode** is a minimalistic library for solving Ordinary Differential Equations (ODEs).
4//!
5//! The library supports explicit, implicit, fixed-step, and adaptive-step algorithms. The library
6//! uses libtorch through [`tch`] bindings.
7//!
8//! ## Quick Start
9//!
10//! To begin, you'll need the [`tch`] crate (Rust bindings for PyTorch):
11//!
12//! ```toml
13//! [dependencies]
14//! mini-ode = "0.1"
15//! tch = "0.15"
16//! ```
17//!
18//! ### Defining Your ODE
19//!
20//! The derivative function must be defined using PyTorch operations and converted to TorchScript.
21//! As an example, consider the undamped Duffing oscillator `y0'' = y0 - y0^3`, rewritten as a
22//! first-order system `y0' = y1`, `y1' = y0 - y0^3`:
23//!
24//! ```rust
25//! use tch::{Tensor, CModule};
26//!
27//! // Define the derivative function f(x, y) -> dy/dx
28//! // x: scalar tensor (shape ())
29//! // y: 1D tensor (shape (n,)) where n is state dimension
30//! let y0 = Tensor::from_slice(&[1f64, 0f64]);
31//! let mut closure = |inputs: &[Tensor]| {
32//!     let _x = &inputs[0];
33//!     let y = &inputs[1];
34//!     let y0 = y.get(0);
35//!     let y1 = y.get(1);
36//!
37//!     let dy0 = y1;
38//!     let dy1 = &y0 - &y0.pow_tensor_scalar(3.0);
39//!
40//!     vec![Tensor::stack(&[dy0, dy1], 0)]
41//! };
42//!
43//! // Trace the function
44//! let model = CModule::create_by_tracing(
45//!     "ode_fn",
46//!     "forward",
47//!     &[Tensor::from(0.0f64), y0.shallow_clone()],
48//!     &mut closure,
49//! )?;
50//! # use std::error::Error;
51//! # Ok::<(), Box<dyn Error>>(())
52//! ```
53//!
54//! ### Solving an ODE
55//!
56//! Once you have your TorchScript model, solve the ODE:
57//!
58//! ```rust
59//! use mini_ode::Solver;
60//! use tch::Tensor;
61//!
62//!# use tch::CModule;
63//!#
64//!# // Define the derivative function f(x, y) -> dy/dx
65//!# // x: scalar tensor (shape ())
66//!# // y: 1D tensor (shape (n,)) where n is state dimension
67//!# let y0 = Tensor::from_slice(&[1f64, 0f64]);
68//!# let mut closure = |inputs: &[Tensor]| {
69//!#     let _x = &inputs[0];
70//!#     let y = &inputs[1];
71//!#     let y0 = y.get(0);
72//!#     let y1 = y.get(1);
73//!#
74//!#     let dy0 = y1;
75//!#     let dy1 = &y0 - &y0.pow_tensor_scalar(3.0);
76//!#
77//!#     vec![Tensor::stack(&[dy0, dy1], 0)]
78//!# };
79//!#
80//!# // Trace the function
81//!# let model = CModule::create_by_tracing(
82//!#     "ode_fn",
83//!#     "forward",
84//!#     &[Tensor::from(0.0f64), y0.shallow_clone()],
85//!#     &mut closure,
86//!# )?;
87//!
88//! // Create a solver with fixed step size
89//! let solver = Solver::RK4 { step: 0.01 };
90//!
91//! // Define integration interval and initial condition
92//! let x_span = (0.0, 5.0);
93//! let y0 = Tensor::from_slice(&[1.0f64, 0.0]);
94//!
95//! // Solve and get results
96//! let (xs, ys) = solver.solve(model, x_span, y0)?;
97//! // xs: 1D tensor of x-values, shape (num_points,)
98//! // ys: 2D tensor of y-values, shape (num_points, n)
99//! # use std::error::Error;
100//! # Ok::<(), Box<dyn Error>>(())
101//! ```
102//!
103//! For adaptive-step solvers, configure tolerances:
104//!
105//! ```rust
106//! use mini_ode::Solver;
107//!
108//! let solver = Solver::RKF45 {
109//!     rtol: 1e-5,
110//!     atol: 1e-5,
111//!     min_step: 1e-9,
112//!     safety_factor: 0.9,
113//! };
114//! ```
115//!
116//! For implicit solvers ([`Solver::ImplicitEuler`], [`Solver::GLRK4`]), you need to configure an optimizer:
117//!
118//! ```rust
119//! use mini_ode::Solver;
120//! use mini_ode::optimizers;
121//! use std::sync::Arc;
122//!
123//! let optimizer = optimizers::CG::new(5, None, Some(1e-8));
124//!
125//! let solver = Solver::GLRK4 {
126//!     step: 0.2,
127//!     optimizer: Arc::new(optimizer),
128//! };
129//! ```
130//!
131//! ## Supported Solvers
132//!
133//! The library provides multiple solver implementations for different use cases:
134//!
135//! | Solver | Method | Implicit | Adaptive | Best For |
136//! |--------|--------|----------|----------|----------|
137//! | [`Solver::Euler`] | Euler | ❌ | ❌ | Simple, educational use |
138//! | [`Solver::RK4`] | Runge-Kutta 4th Order | ❌ | ❌ | General-purpose, fixed step |
139//! | [`Solver::ImplicitEuler`] | Implicit Euler | ✅ | ❌ | Stiff problems |
140//! | [`Solver::GLRK4`] | Gauss-Legendre RK (Order 4) | ✅ | ❌ | High-accuracy, stiff systems |
141//! | [`Solver::RKF45`] | Runge-Kutta-Fehlberg 4(5) | ❌ | ✅ | Adaptive step control |
142//! | [`Solver::ROW1`] | Rosenbrock-Wanner (Order 1) | semi | ❌ | Fast semi-implicit, stiff |
143
144#[cfg(test)]
145mod tests;
146
147pub(crate) mod utils;
148
149pub mod optimizers;
150
151mod solvers;
152pub use solvers::Solver;