Skip to content

Commit 03edd91

Browse files
Merge pull request #24 from martinjrobins/docs
#19 more docs on the front-page and odeequations
2 parents 6354696 + cc1afd9 commit 03edd91

File tree

6 files changed

+109
-15
lines changed

6 files changed

+109
-15
lines changed

src/lib.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! DiffSol is a library for solving differential equations. It provides a simple interface to solve ODEs and semi-explicit DAEs.
44
//!
5-
//! ## Getting Started
5+
//! ## Solving ODEs
66
//!
77
//! To create a new problem, use the [OdeBuilder] struct. You can set the initial time, initial step size, relative tolerance, absolute tolerance, and parameters,
88
//! or leave them at their default values. Then, call the [OdeBuilder::build_ode] method with the ODE equations, or the [OdeBuilder::build_ode_with_mass] method
@@ -11,8 +11,11 @@
1111
//! You will also need to choose a matrix type to use. DiffSol can use the [nalgebra](https://nalgebra.org) `DMatrix` type, or any other type that implements the
1212
//! [Matrix] trait. You can also use the [sundials](https://computation.llnl.gov/projects/sundials) library for the matrix and vector types (see [SundialsMatrix]).
1313
//!
14-
//! To solve the problem, you need to choose a solver. DiffSol provides a pure rust [Bdf] solver, or you can use the [SundialsIda] solver from the sundials library (requires the `sundials` feature).
15-
//! See the [OdeSolverMethod] trait for a more detailed description of the available methods on the solver.
14+
//! To solve the problem, you need to choose a solver. DiffSol provides the following solvers:
15+
//! - A pure rust Backwards Difference Formulae [Bdf] solver, suitable for stiff problems and singular mass matrices.
16+
//! - The IDA solver solver from the sundials library ([SundialsIda], requires the `sundials` feature).
17+
//!
18+
//! See the [OdeSolverMethod] trait for a more detailed description of the available methods on each solver.
1619
//!
1720
//! ```rust
1821
//! use diffsol::{OdeBuilder, Bdf, OdeSolverState, OdeSolverMethod};
@@ -45,6 +48,53 @@
4548
//! }
4649
//! let y = solver.interpolate(&state, t);
4750
//! ```
51+
//!
52+
//! ## DiffSL
53+
//!
54+
//! DiffSL is a domain-specific language for specifying differential equations <https://github.com/martinjrobins/diffsl>. It uses the LLVM compiler framwork
55+
//! to compile the equations to efficient machine code and uses the EnzymeAD library to compute the jacobian. You can use DiffSL with DiffSol by enabling one of the `diffsl-llvm*` features
56+
//! corresponding to the version of LLVM you have installed, and using the [OdeBuilder::build_diffsl] method.
57+
//!
58+
//! For more information on the DiffSL language, see the [DiffSL documentation](https://martinjrobins.github.io/diffsl/)
59+
//!
60+
//! ## Custom ODE problems
61+
//!
62+
//! The [OdeBuilder] struct can be used to create an ODE problem from a set of closures.
63+
//! If this is not suitable for your problem or you want more control over how your equations are implemented, you can also implement the [OdeEquations] trait manually.
64+
//!
65+
//! ## Nonlinear and linear solvers
66+
//!
67+
//! DiffSol provides generic nonlinear and linear solvers that are used internally by the ODE solver. You can use the solvers provided by DiffSol, or implement your own following the provided traits.
68+
//! The linear solver trait is [LinearSolver], and the nonlinear solver trait is [NonLinearSolver]. The [SolverProblem] struct is used to define the problem to solve.
69+
//!
70+
//! The provided linear solvers are:
71+
//! - [LU]: a direct solver that uses the LU decomposition implemented in the [nalgebra](https://nalgebra.org) library.
72+
//! - [SundialsLinearSolver]: a linear solver that uses the [sundials](https://computation.llnl.gov/projects/sundials) library (requires the `sundials` feature).
73+
//!
74+
//! The provided nonlinear solvers are:
75+
//! - [NewtonNonlinearSolver]: a nonlinear solver that uses the Newton method.
76+
//!
77+
//! ## Jacobian and Mass matrix calculation
78+
//!
79+
//! Via [OdeEquations], the user provides the action of the jacobian on a vector `J(x) v`. By default DiffSol uses this to generate a jacobian matrix for the ODE solver.
80+
//! Generally this requires `n` evaluations of the jacobian action for a system of size `n`, so it is often more efficient if the user can provide the jacobian matrix directly
81+
//! by implementing the [OdeEquations::jacobian_matrix] and the [OdeEquations::mass_matrix] (is applicable) functions.
82+
//!
83+
//! DiffSol also provides an experimental feature to calculate sparse jacobians more efficiently by automatically detecting the sparsity pattern of the jacobian and using
84+
//! colouring \[1\] to reduce the number of jacobian evaluations. You can enable this feature by enabling [OdeBuilder::use_coloring()] option when building the ODE problem.
85+
//!
86+
//! \[1\] Gebremedhin, A. H., Manne, F., & Pothen, A. (2005). What color is your Jacobian? Graph coloring for computing derivatives. SIAM review, 47(4), 629-705.
87+
//!
88+
//! ## Matrix and vector types
89+
//!
90+
//! When solving ODEs, you will need to choose a matrix and vector type to use. DiffSol uses the following types:
91+
//! - [nalgebra::DMatrix] and [nalgebra::DVector] from the [nalgebra](https://nalgebra.org) library.
92+
//! - [SundialsMatrix] and [SundialsVector] from the [sundials](https://computation.llnl.gov/projects/sundials) library (requires the `sundials` feature).
93+
//!
94+
//! If you wish to use your own matrix and vector types, you will need to implement the following traits:
95+
//! - For matrices: [Matrix], [MatrixView], [MatrixViewMut], [DenseMatrix], and [MatrixCommon].
96+
//! - For vectors: [Vector], [VectorIndex], [VectorView], [VectorViewMut], and [VectorCommon].
97+
//!
4898
4999
#[cfg(feature = "diffsl-llvm10")]
50100
pub extern crate diffsl10_0 as diffsl;
@@ -100,7 +150,7 @@ pub use linear_solver::sundials::SundialsLinearSolver;
100150
#[cfg(feature = "sundials")]
101151
pub use ode_solver::sundials::SundialsIda;
102152

103-
use matrix::{DenseMatrix, Matrix, MatrixViewMut};
153+
use matrix::{DenseMatrix, Matrix, MatrixCommon, MatrixView, MatrixViewMut};
104154
pub use nonlinear_solver::newton::NewtonNonlinearSolver;
105155
use nonlinear_solver::NonLinearSolver;
106156
pub use ode_solver::{
@@ -110,7 +160,7 @@ pub use ode_solver::{
110160
use op::NonLinearOp;
111161
use scalar::{IndexType, Scalar, Scale};
112162
use solver::SolverProblem;
113-
use vector::{Vector, VectorIndex, VectorRef, VectorView, VectorViewMut};
163+
use vector::{Vector, VectorCommon, VectorIndex, VectorRef, VectorView, VectorViewMut};
114164

115165
pub use scalar::scale;
116166

src/matrix/dense_serial.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use nalgebra::{DMatrix, DMatrixView, DMatrixViewMut, DVector, DVectorView, DVect
33

44
use crate::{IndexType, Scalar};
55

6-
use super::{DenseMatrix, Matrix, MatrixCommon, MatrixView, MatrixViewMut};
6+
use crate::{DenseMatrix, Matrix, MatrixCommon, MatrixView, MatrixViewMut};
77

88
impl<'a, T: Scalar> MatrixCommon for DMatrixViewMut<'a, T> {
99
type V = DVector<T>;

src/ode_solver/diffsl.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,19 @@ mod tests {
220220

221221
use super::DiffSl;
222222

223+
#[test]
224+
fn diffsl_expontential_decay() {
225+
// TODO: put this example into the docs once https://github.com/rust-lang/cargo/pull/13490 makes it to stable
226+
let code = "
227+
u { y = 1 }
228+
F { -y }
229+
out { y }
230+
";
231+
let problem = OdeBuilder::new().build_diffsl(code).unwrap();
232+
let mut solver = Bdf::default();
233+
let _y = solver.solve(&problem, 1.0).unwrap();
234+
}
235+
223236
#[test]
224237
fn diffsl_logistic_growth() {
225238
let text = "

src/ode_solver/equations.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,32 +36,47 @@ impl OdeEquationsStatistics {
3636
}
3737
}
3838

39+
/// this is the trait that defines the ODE equations of the form
40+
///
41+
/// $$
42+
/// M \frac{dy}{dt} = F(t, y, p)
43+
/// y(t_0) = y_0(t_0, p)
44+
/// $$
45+
///
46+
/// The ODE equations are defined by the right-hand side function $F(t, y, p)$, the initial condition $y_0(t_0, p)$, and the mass matrix $M$.
3947
pub trait OdeEquations: Op {
40-
/// This must be called first
48+
/// The parameters of the ODE equations are assumed to be constant. This function sets the parameters to the given value before solving the ODE.
49+
/// Note that `set_params` must always be called before calling any of the other functions in this trait.
4150
fn set_params(&mut self, p: Self::V);
4251

43-
/// calculates $F(y)$ where $y$ is given in `y` and stores the result in `rhs_y`
52+
/// calculates $F(t, y, p)$ where $y$ is given in `y` and stores the result in `rhs_y`. Note that the parameter vector $p$ is assumed to be
53+
/// already provided via [Self::set_params()]
4454
fn rhs_inplace(&self, t: Self::T, y: &Self::V, rhs_y: &mut Self::V);
4555

46-
/// calculates $y = J(x)v$
56+
/// calculates $y = J(x)v$, where $J(x)$ is the Jacobian matrix of the right-hand side function $F(t, y, p)$ at $y = x$. The result is stored in `y`.
4757
fn rhs_jac_inplace(&self, t: Self::T, x: &Self::V, v: &Self::V, y: &mut Self::V);
4858

49-
/// initializes `y` with the initial condition
59+
/// returns the initial condition, i.e. $y(t_0, p)$
5060
fn init(&self, t: Self::T) -> Self::V;
5161

62+
/// calculates the right-hand side function $F(t, y, p)$ where $y$ is given in `y`. The result is allocated and returned.
63+
/// The default implementation calls [Self::rhs_inplace()] and allocates a new vector for the result.
5264
fn rhs(&self, t: Self::T, y: &Self::V) -> Self::V {
5365
let mut rhs_y = Self::V::zeros(self.nstates());
5466
self.rhs_inplace(t, y, &mut rhs_y);
5567
rhs_y
5668
}
5769

70+
/// calculates $y = J(x)v$, where $J(x)$ is the Jacobian matrix of the right-hand side function $F(t, y, p)$ at $y = x$. The result is allocated and returned.
71+
/// The default implementation calls [Self::rhs_jac_inplace()] and allocates a new vector for the result.
5872
fn jac_mul(&self, t: Self::T, x: &Self::V, v: &Self::V) -> Self::V {
5973
let mut rhs_jac_y = Self::V::zeros(self.nstates());
6074
self.rhs_jac_inplace(t, x, v, &mut rhs_jac_y);
6175
rhs_jac_y
6276
}
6377

64-
/// rhs jacobian matrix J(x), re-use jacobian calculation from NonLinearOp
78+
/// calculate and return the jacobian matrix $J(x)$ of the right-hand side function $F(t, y, p)$ at $y = x$.
79+
/// The default implementation calls [Self::rhs_jac_inplace()] and uses the jacobian calculation in [NonLinearOp].
6580
fn jacobian_matrix(&self, x: &Self::V, t: Self::T) -> Self::M {
6681
let rhs_inplace = |x: &Self::V, _p: &Self::V, t: Self::T, y_rhs: &mut Self::V| {
6782
self.rhs_inplace(t, x, y_rhs);
@@ -81,28 +96,39 @@ pub trait OdeEquations: Op {
8196
closure.jacobian(x, t)
8297
}
8398

84-
/// mass matrix action: y = M(t)
99+
/// calculate the action of the mass matrix $M$ on the vector $v$ at time $t$, i,e. $y = M(t)v$.
100+
/// The default implementation assumes that the mass matrix is the identity matrix and returns $y = v$.
85101
fn mass_inplace(&self, _t: Self::T, v: &Self::V, y: &mut Self::V) {
86102
// assume identity mass matrix
87103
y.copy_from(v);
88104
}
89105

90-
/// returns the indices of the algebraic state variables
106+
/// For semi-explicit DAEs (with zeros on the diagonal of the mass matrix), this function
107+
/// returns the indices of the algebraic state variables. This is used to determine which
108+
/// components of the solution vector are algebraic, and therefore must be solved for
109+
/// when calculating the initial condition to make sure that the algebraic constraints are satisfied.
110+
/// The default implementation returns an empty vector, assuming that the mass matrix is the identity matrix.
91111
fn algebraic_indices(&self) -> <Self::V as Vector>::Index {
92112
// assume identity mass matrix
93113
<Self::V as Vector>::Index::zeros(0)
94114
}
95115

116+
/// calculate and return the mass matrix $M(t)$ at time $t$.
117+
/// The default implementation assumes that the mass matrix is the identity matrix and returns the identity matrix.
96118
fn mass_matrix(&self, _t: Self::T) -> Self::M {
97119
// assume identity mass matrix
98120
Self::M::from_diagonal(&Self::V::from_element(self.nstates(), Self::T::one()))
99121
}
100122

123+
/// calculate and return the statistics of the ODE equation object (i.e. how many times the right-hand side function was evaluated, how many times the jacobian was multiplied, etc.)
124+
/// The default implementation returns an empty statistics object.
101125
fn get_statistics(&self) -> OdeEquationsStatistics {
102126
OdeEquationsStatistics::new()
103127
}
104128
}
105129

130+
/// This struct implements the ODE equation trait [OdeEquations] for a given right-hand side function, jacobian function, mass matrix function, and initial condition function.
131+
/// These functions are provided as closures, and the parameters are assumed to be constant.
106132
pub struct OdeSolverEquations<M, F, G, H, I>
107133
where
108134
M: Matrix,
@@ -179,7 +205,6 @@ where
179205
}
180206
}
181207

182-
// impl Op
183208
impl<M, F, G, H, I> Op for OdeSolverEquations<M, F, G, H, I>
184209
where
185210
M: Matrix,
@@ -288,6 +313,9 @@ where
288313
}
289314
}
290315

316+
/// This struct implements the ODE equation trait [OdeEquations] for a given right-hand side function, jacobian function, and initial condition function.
317+
/// These functions are provided as closures, and the parameters are assumed to be constant.
318+
/// The mass matrix is assumed to be the identity matrix.
291319
pub struct OdeSolverEquationsMassI<M, F, G, I>
292320
where
293321
M: Matrix,

src/solver/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub struct SolverStatistics {
1010
pub nmaxiter: IndexType,
1111
}
1212

13+
/// A generic linear or nonlinear solver problem, containing the function to solve $f(t, y)$, the current time $t$, and the relative and absolute tolerances.
1314
pub struct SolverProblem<C: Op> {
1415
pub f: Rc<C>,
1516
pub t: C::T,
@@ -46,6 +47,8 @@ impl<C: Op> SolverProblem<C> {
4647
}
4748

4849
impl<C: NonLinearOp> SolverProblem<C> {
50+
/// Create a new solver problem from a nonlinear operator that solves for the linearised operator.
51+
/// That is, if the original function is $f(t, y)$, this function creates a new problem $f'$ that solves $f' = J(x) v$, where $J(x)$ is the Jacobian of $f$ at $x$.
4952
pub fn linearise(&self, x: &C::V) -> SolverProblem<LinearisedOp<C>> {
5053
let linearised_f = Rc::new(LinearisedOp::new(self.f.clone(), x));
5154
SolverProblem::new_from_problem(linearised_f, self)

src/vector/faer_serial.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use faer::{unzipped, zipped, Col, ColMut, ColRef};
44

55
use crate::{scalar::Scale, IndexType, Scalar};
66

7-
use super::{Vector, VectorCommon, VectorIndex, VectorView, VectorViewMut};
7+
use crate::{Vector, VectorCommon, VectorIndex, VectorView, VectorViewMut};
88

99
macro_rules! impl_op_for_faer_struct {
1010
($struct:ident, $trait_name:ident, $func_name:ident) => {

0 commit comments

Comments
 (0)