1use numra_core::Scalar;
11
12pub trait OdeSystem<S: Scalar> {
16 fn dim(&self) -> usize;
18
19 fn rhs(&self, t: S, y: &[S], dydt: &mut [S]);
21
22 fn jacobian(&self, t: S, y: &[S], jac: &mut [S]) {
25 let n = self.dim();
26 let eps = S::from_f64(1e-8);
27 let mut y_pert = y.to_vec();
28 let mut f0 = vec![S::ZERO; n];
29 let mut f1 = vec![S::ZERO; n];
30
31 self.rhs(t, y, &mut f0);
32
33 for j in 0..n {
34 let yj_save = y_pert[j];
35 let h = eps * (S::ONE + yj_save.abs());
36 y_pert[j] = yj_save + h;
37 self.rhs(t, &y_pert, &mut f1);
38 y_pert[j] = yj_save;
39
40 for i in 0..n {
41 jac[i * n + j] = (f1[i] - f0[i]) / h;
42 }
43 }
44 }
45
46 fn is_autonomous(&self) -> bool {
48 false
49 }
50
51 fn has_mass_matrix(&self) -> bool {
53 false
54 }
55
56 fn mass_matrix(&self, mass: &mut [S]) {
63 let n = self.dim();
64 for i in 0..n {
65 for j in 0..n {
66 mass[i * n + j] = if i == j { S::ONE } else { S::ZERO };
67 }
68 }
69 }
70
71 fn is_singular_mass(&self) -> bool {
73 false
74 }
75
76 fn algebraic_indices(&self) -> Vec<usize> {
80 Vec::new()
81 }
82}
83
84pub struct OdeProblem<S: Scalar, F>
88where
89 F: Fn(S, &[S], &mut [S]),
90{
91 pub f: F,
93 pub t0: S,
95 pub tf: S,
97 pub y0: Vec<S>,
99}
100
101impl<S: Scalar, F> OdeProblem<S, F>
102where
103 F: Fn(S, &[S], &mut [S]),
104{
105 pub fn new(f: F, t0: S, tf: S, y0: Vec<S>) -> Self {
107 Self { f, t0, tf, y0 }
108 }
109
110 pub fn dim(&self) -> usize {
112 self.y0.len()
113 }
114
115 pub fn tspan(&self) -> (S, S) {
117 (self.t0, self.tf)
118 }
119
120 pub fn direction(&self) -> S {
122 if self.tf >= self.t0 {
123 S::ONE
124 } else {
125 -S::ONE
126 }
127 }
128}
129
130impl<S: Scalar, F> OdeSystem<S> for OdeProblem<S, F>
131where
132 F: Fn(S, &[S], &mut [S]),
133{
134 fn dim(&self) -> usize {
135 self.y0.len()
136 }
137
138 fn rhs(&self, t: S, y: &[S], dydt: &mut [S]) {
139 (self.f)(t, y, dydt);
140 }
141}
142
143pub struct DaeProblem<S: Scalar, F, M>
169where
170 F: Fn(S, &[S], &mut [S]),
171 M: Fn(&mut [S]),
172{
173 pub f: F,
175 pub mass: M,
177 pub t0: S,
179 pub tf: S,
181 pub y0: Vec<S>,
183 pub alg_indices: Vec<usize>,
185}
186
187impl<S: Scalar, F, M> DaeProblem<S, F, M>
188where
189 F: Fn(S, &[S], &mut [S]),
190 M: Fn(&mut [S]),
191{
192 pub fn new(f: F, mass: M, t0: S, tf: S, y0: Vec<S>, alg_indices: Vec<usize>) -> Self {
202 Self {
203 f,
204 mass,
205 t0,
206 tf,
207 y0,
208 alg_indices,
209 }
210 }
211
212 pub fn dim(&self) -> usize {
214 self.y0.len()
215 }
216}
217
218impl<S: Scalar, F, M> OdeSystem<S> for DaeProblem<S, F, M>
219where
220 F: Fn(S, &[S], &mut [S]),
221 M: Fn(&mut [S]),
222{
223 fn dim(&self) -> usize {
224 self.y0.len()
225 }
226
227 fn rhs(&self, t: S, y: &[S], dydt: &mut [S]) {
228 (self.f)(t, y, dydt);
229 }
230
231 fn has_mass_matrix(&self) -> bool {
232 true
233 }
234
235 fn mass_matrix(&self, mass: &mut [S]) {
236 (self.mass)(mass);
237 }
238
239 fn is_singular_mass(&self) -> bool {
240 !self.alg_indices.is_empty()
241 }
242
243 fn algebraic_indices(&self) -> Vec<usize> {
244 self.alg_indices.clone()
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_ode_problem_creation() {
254 let f = |_t: f64, y: &[f64], dydt: &mut [f64]| {
255 dydt[0] = -y[0];
256 };
257 let problem = OdeProblem::new(f, 0.0, 1.0, vec![1.0]);
258 assert_eq!(problem.dim(), 1);
259 assert_eq!(problem.tspan(), (0.0, 1.0));
260 }
261
262 #[test]
263 fn test_rhs_evaluation() {
264 let f = |_t: f64, y: &[f64], dydt: &mut [f64]| {
265 dydt[0] = -2.0 * y[0];
266 };
267 let problem = OdeProblem::new(f, 0.0, 1.0, vec![1.0]);
268
269 let mut dydt = vec![0.0];
270 problem.rhs(0.0, &[1.0], &mut dydt);
271 assert!((dydt[0] + 2.0).abs() < 1e-10);
272 }
273
274 #[test]
275 fn test_jacobian_finite_diff() {
276 let f = |_t: f64, y: &[f64], dydt: &mut [f64]| {
278 dydt[0] = -2.0 * y[0];
279 };
280 let problem = OdeProblem::new(f, 0.0, 1.0, vec![1.0]);
281
282 let mut jac = vec![0.0];
283 problem.jacobian(0.0, &[1.0], &mut jac);
284 assert!((jac[0] + 2.0).abs() < 1e-5);
285 }
286}