1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//! Russell - Rust Scientific Library
//!
//! `russell_ode`: Solvers for ordinary differential equations and differential algebraic equations
//!
//! **Important:** This crate depends on external libraries (non-Rust). Thus, please check the [Installation Instructions on the GitHub Repository](https://github.com/cpmech/russell).
//!
//! 
//!
//! The figure above is the solution of the Arenstorf Orbit obtained with [russell_ode](https://github.com/cpmech/russell/blob/main/russell_ode/examples/arenstorf_dopri8.rs).
//!
//! # Introduction
//!
//! This library implements (**natively**) numerical solver solvers for systems of ordinary equations (ODEs) and differential-algebraic equation systems (DAEs) of Index-1. One advantage of a native implementation is the "safety aspects" enforced by Rust. Moreover, we implement thread-safe code. For example, the performance is improved when the real-based linear and complex-based linear systems are factorized concurrently, as in our Radau5.
//!
//! The principal structs are:
//!
//! * [System] defines the ODE or DAE system
//! * [OdeSolver] implements the "time-stepping" loop and calls the *actual* numerical solver
//! * [Params] holds numeric parameters needed by all methods
//! * (optional) [Output] holds the results from accepted steps (all methods) or the *dense output* (DoPri5, DoPri8, and Radau5 only)
//! * (optional) [Stats] holds statistics and benchmarking data
//!
//! The [System] struct holds the number of equations (system dimension), the right-hand side function `f(x, y)`, an optional function to compute the Jacobian matrix, and, also optionally, the mass matrix.
//!
//! The [OdeSolver] approximates the solution of the ODE/DAE using either fixed or variable steps. Some methods can only be run with fixed steps (this is automatically detected). In addition to the system struct, the solver takes [Params] as input.
//!
//! A set of default (~optimal) parameters are allocated by [Params::new()]. If needed, the user may *tweak* the parameters by accessing each parameter subgroup:
//!
//! * [ParamsNewton] parameters for Newton's iterations' for the methods that use iterations such asBwEuler and Radau5
//! * [ParamsStep] parameters for the variable-step control
//! * [ParamsStiffness] parameters to control and enable the stiffness detection (DoPri5 and DoPri8 only)
//! * [ParamsBwEuler] parameters for the BwEuler solver
//! * [ParamsRadau5] parameters for the Radau5 solver
//! * [ParamsERK] parameters for all explicit Runge-Kutta methods (e.g., DoPri5, DoPri8)
//!
//! The ODE and DAE systems are represented as follows:
//!
//! ```text
//! d{y}
//! [M] ———— = {f}(x, {y})
//! dx
//! ```
//!
//! where `x` is the independent scalar variable (e.g., time), `{y}` is the solution vector,
//! `{f}` is the right-hand side vector, and `[M]` is the so-called "mass matrix".
//!
//! **Note:** The mass matrix is optional and need not be specified (unless the DAE under study requires it).
//!
//! The Jacobian is defined by:
//!
//! ```text
//! ∂{f}
//! [J](x, {y}) = ————
//! ∂{y}
//! ```
//!
//! where `[J]` is the Jacobian matrix.
//!
//! **Note:** The Jacobian function is not required for explicit Runge-Kutta methods (see [Method] and [Information]). Thus, one may simply pass the [no_jacobian] function and set [HasJacobian::No] in the system.
//!
//! The flag [ParamsNewton::use_numerical_jacobian] may be set to true to compute the Jacobian matrix numerically. This option works with or without specifying the analytical Jacobian function.
//!
//! ## Recommended methods
//!
//! * [Method::DoPri5] for ODE systems and non-stiff problems using moderate tolerances
//! * [Method::DoPri8] for ODE systems and non-stiff problems using strict tolerances
//! * [Method::Radau5] for ODE and DAE systems, possibly stiff, with moderate to strict tolerances
//!
//! **Note:** A *Stiff problem* arises due to a combination of conditions, such as
//! the ODE system equations, the initial values, the stepsize, and the numerical method.
//!
//! ## Limitations
//!
//! * Currently, the only method that can solve DAE systems is [Method::Radau5]
//! * Currently, *dense output* is only available for [Method::DoPri5], [Method::DoPri8], and [Method::Radau5]
//!
//! ## References
//!
//! 1. E. Hairer, S. P. Nørsett, G. Wanner (2008) Solving Ordinary Differential Equations I.
//! Non-stiff Problems. Second Revised Edition. Corrected 3rd printing 2008. Springer Series
//! in Computational Mathematics, 528p
//! 2. E. Hairer, G. Wanner (2002) Solving Ordinary Differential Equations II.
//! Stiff and Differential-Algebraic Problems. Second Revised Edition.
//! Corrected 2nd printing 2002. Springer Series in Computational Mathematics, 614p
//!
//! # Examples
//!
//! See also [the examples on GitHub page](https://github.com/cpmech/russell/tree/main/russell_ode)
//!
//! ### Simple system with mass matrix
//!
//! Solve with Radau5:
//!
//! ```text
//! y0' + y1' = -y0 + y1
//! y0' - y1' = y0 + y1
//! y2' = 1/(1 + x)
//!
//! y0(0) = 1, y1(0) = 0, y2(0) = 0
//! ```
//!
//! Thus:
//!
//! ```text
//! M y' = f(x, y)
//! ```
//!
//! with:
//!
//! ```text
//! ┌ ┐ ┌ ┐
//! │ 1 1 0 │ │ -y0 + y1 │
//! M = │ 1 -1 0 │ f = │ y0 + y1 │
//! │ 0 0 1 │ │ 1/(1 + x) │
//! └ ┘ └ ┘
//! ```
//!
//! The Jacobian matrix is:
//!
//! ```text
//! ┌ ┐
//! df │ -1 1 0 │
//! J = —— = │ 1 1 0 │
//! dy │ 0 0 0 │
//! └ ┘
//! ```
//!
//! Code:
//!
//! ```
//! use russell_lab::{vec_max_abs_diff, StrError, Vector};
//! use russell_ode::prelude::*;
//! use russell_sparse::CooMatrix;
//!
//! fn main() -> Result<(), StrError> {
//! // DAE system
//! let ndim = 3;
//! let jac_nnz = 4; // number of non-zero values in the Jacobian
//! let mut system = System::new(
//! ndim,
//! |f: &mut Vector, x: f64, y: &Vector, _args: &mut NoArgs| {
//! f[0] = -y[0] + y[1];
//! f[1] = y[0] + y[1];
//! f[2] = 1.0 / (1.0 + x);
//! Ok(())
//! },
//! move |jj: &mut CooMatrix, alpha: f64, _x: f64, _y: &Vector, _args: &mut NoArgs| {
//! jj.reset();
//! jj.put(0, 0, alpha * (-1.0)).unwrap();
//! jj.put(0, 1, alpha * (1.0)).unwrap();
//! jj.put(1, 0, alpha * (1.0)).unwrap();
//! jj.put(1, 1, alpha * (1.0)).unwrap();
//! Ok(())
//! },
//! HasJacobian::Yes,
//! Some(jac_nnz),
//! None,
//! );
//!
//! // mass matrix
//! let mass_nnz = 5; // number of non-zero values in the mass matrix
//! system.init_mass_matrix(mass_nnz).unwrap();
//! system.mass_put(0, 0, 1.0).unwrap();
//! system.mass_put(0, 1, 1.0).unwrap();
//! system.mass_put(1, 0, 1.0).unwrap();
//! system.mass_put(1, 1, -1.0).unwrap();
//! system.mass_put(2, 2, 1.0).unwrap();
//!
//! // solver
//! let params = Params::new(Method::Radau5);
//! let mut solver = OdeSolver::new(params, &system)?;
//!
//! // initial values
//! let x = 0.0;
//! let mut y = Vector::from(&[1.0, 0.0, 0.0]);
//!
//! // solve from x = 0 to x = 2
//! let x1 = 2.0;
//! let mut args = 0;
//! solver.solve(&mut y, x, x1, None, None, &mut args)?;
//! println!("y =\n{}", y);
//! Ok(())
//! }
//! ```
/// Defines the error output as a static string
pub type StrError = &'static str;
pub use crate*;
use crate*;
pub use crate*;
use crate*;
use crate*;
use crate*;
use crate*;
pub use crate*;
use crate*;
pub use crate*;
pub use crate*;
pub use crate*;
use crate*;
pub use crate*;
pub use crate*;
pub use crate*;
use crate*;
// run code from README file