1use crate::butcher_tableau::dopri54;
4use crate::continuous_output_model::ContinuousOutputModel;
5use crate::controller::Controller;
6use crate::dop_shared::*;
7use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, MatrixSum, OVector, U1};
8
9trait DefaultController<T: FloatNumber> {
10 fn default(x: T, x_end: T) -> Self;
11}
12
13impl<T: FloatNumber> DefaultController<T> for Controller<T> {
14 fn default(x: T, x_end: T) -> Self {
15 let alpha = T::from(0.2 - 0.04 * 0.75).unwrap();
16 Controller::new(
17 alpha,
18 T::from(0.04).unwrap(),
19 T::from(10.0).unwrap(),
20 T::from(0.2).unwrap(),
21 x_end - x,
22 T::from(0.9).unwrap(),
23 sign(T::one(), x_end - x),
24 )
25 }
26}
27
28pub struct Dopri5<T, V, F>
30where
31 T: FloatNumber,
32 F: System<T, V>,
33{
34 f: F,
35 x: T,
36 x_old: T,
37 x_end: T,
38 xd: T,
39 dx: T,
40 y: V,
41 rtol: T,
42 atol: T,
43 results: SolverResult<T, V>,
44 uround: T,
45 h: T,
46 h_old: T,
47 n_max: u32,
48 n_stiff: u32,
49 controller: Controller<T>,
50 out_type: OutputType,
51 rcont: [V; 5],
52 stats: Stats,
53}
54
55impl<T, D: Dim, F> Dopri5<T, OVector<T, D>, F>
56where
57 f64: From<T>,
58 T: FloatNumber,
59 F: System<T, OVector<T, D>>,
60 OVector<T, D>: std::ops::Mul<T, Output = OVector<T, D>>,
61 DefaultAllocator: Allocator<D>,
62{
63 pub fn new(f: F, x: T, x_end: T, dx: T, y: OVector<T, D>, rtol: T, atol: T) -> Self {
76 let (rows, cols) = y.shape_generic();
77 Self {
78 f,
79 x,
80 xd: x,
81 dx,
82 x_old: x,
83 x_end,
84 y,
85 rtol,
86 atol,
87 results: SolverResult::default(),
88 uround: T::epsilon(),
89 h: T::zero(),
90 h_old: T::zero(),
91 n_max: 100000,
92 n_stiff: 1000,
93 controller: Controller::default(x, x_end),
94 out_type: OutputType::Dense,
95 rcont: [
96 OVector::zeros_generic(rows, cols),
97 OVector::zeros_generic(rows, cols),
98 OVector::zeros_generic(rows, cols),
99 OVector::zeros_generic(rows, cols),
100 OVector::zeros_generic(rows, cols),
101 ],
102 stats: Stats::new(),
103 }
104 }
105
106 #[allow(clippy::too_many_arguments)]
128 pub fn from_param(
129 f: F,
130 x: T,
131 x_end: T,
132 dx: T,
133 y: OVector<T, D>,
134 rtol: T,
135 atol: T,
136 safety_factor: T,
137 beta: T,
138 fac_min: T,
139 fac_max: T,
140 h_max: T,
141 h: T,
142 n_max: u32,
143 n_stiff: u32,
144 out_type: OutputType,
145 ) -> Self {
146 let alpha = T::from(0.2).unwrap() - beta * T::from(0.75).unwrap();
147 let (rows, cols) = y.shape_generic();
148 Self {
149 f,
150 x,
151 xd: x,
152 x_old: T::zero(),
153 x_end,
154 dx,
155 y,
156 rtol,
157 atol,
158 results: SolverResult::default(),
159 uround: T::from(f64::EPSILON).unwrap(),
160 h,
161 h_old: T::zero(),
162 n_max,
163 n_stiff,
164 controller: Controller::new(
165 alpha,
166 beta,
167 fac_max,
168 fac_min,
169 h_max,
170 safety_factor,
171 sign(T::one(), x_end - x),
172 ),
173 out_type,
174 rcont: [
175 OVector::zeros_generic(rows, cols),
176 OVector::zeros_generic(rows, cols),
177 OVector::zeros_generic(rows, cols),
178 OVector::zeros_generic(rows, cols),
179 OVector::zeros_generic(rows, cols),
180 ],
181 stats: Stats::new(),
182 }
183 }
184
185 fn hinit(&self) -> T {
187 let (rows, cols) = self.y.shape_generic();
188 let mut f0 = OVector::zeros_generic(rows, cols);
189 self.f.system(self.x, &self.y, &mut f0);
190 let posneg = sign(T::from(1.0).unwrap(), self.x_end - self.x);
191
192 let dim = rows.value();
194 let mut d0 = T::zero();
195 let mut d1 = T::zero();
196 for i in 0..dim {
197 let y_i = T::from(self.y[i]).unwrap();
198 let sci = self.atol + y_i.abs() * self.rtol;
199 d0 += (y_i / sci) * (y_i / sci);
200 let f0_i = T::from(f0[i]).unwrap();
201 d1 += (f0_i / sci) * (f0_i / sci);
202 }
203
204 let tol = T::from(1.0E-10).unwrap();
206 let mut h0 = if d0 < tol || d1 < tol {
207 T::from(1.0E-6).unwrap()
208 } else {
209 T::from(0.01).unwrap() * (d0 / d1).sqrt()
210 };
211
212 h0 = h0.min(self.controller.h_max());
213 h0 = sign(h0, posneg);
214
215 let y1 = &self.y + &f0 * h0;
216 let mut f1 = OVector::zeros_generic(rows, cols);
217 self.f.system(self.x + h0, &y1, &mut f1);
218
219 let mut d2 = T::zero();
221 for i in 0..dim {
222 let f0_i = f0[i];
223 let f1_i = f1[i];
224 let y_i = self.y[i];
225 let sci = self.atol + y_i.abs() * self.rtol;
226 d2 += ((f1_i - f0_i) / sci) * ((f1_i - f0_i) / sci);
227 }
228 d2 = d2.sqrt() / h0;
229
230 let h1 = if d1.sqrt().max(d2.abs()) <= T::from(1.0E-15).unwrap() {
231 T::from(1.0E-6_f64)
232 .unwrap()
233 .max(h0.abs() * T::from(1.0E-3).unwrap())
234 } else {
235 (T::from(0.01).unwrap() / (d1.sqrt().max(d2))).powf(T::one() / T::from(5.0).unwrap())
236 };
237
238 sign(
239 (T::from(100.0).unwrap() * h0.abs()).min(h1.min(self.controller.h_max())),
240 posneg,
241 )
242 }
243
244 pub fn integrate_with_continuous_output_model(
246 &mut self,
247 continuous_output: &mut ContinuousOutputModel<T, OVector<T, D>>,
248 ) -> Result<Stats, IntegrationError> {
249 self.out_type = OutputType::Continuous;
250 self.integrate_core(Some(continuous_output))
251 }
252
253 pub fn integrate(&mut self) -> Result<Stats, IntegrationError> {
255 if self.out_type == OutputType::Continuous {
256 panic!("Please use `integrate_with_continuous_output_model` to compute a continuous output model.");
257 }
258 self.integrate_core(None)
259 }
260
261 fn integrate_core(
263 &mut self,
264 mut continuous_output_model: Option<&mut ContinuousOutputModel<T, OVector<T, D>>>,
265 ) -> Result<Stats, IntegrationError> {
266 let (rows, cols) = self.y.shape_generic();
268 self.x_old = self.x;
269 let mut n_step = 0;
270 let mut last = false;
271 let mut h_new = T::zero();
272 let dim = rows.value();
273 let mut non_stiff = 0;
274 let mut iasti = 0;
275 let posneg = sign(T::one(), self.x_end - self.x);
276
277 if let Some(output) = continuous_output_model.as_deref_mut() {
279 output.set_lower_bound(self.x);
280 }
281
282 if self.h == T::zero() {
283 self.h = self.hinit();
284 self.stats.num_eval += 2;
285 }
286 self.h_old = self.h;
287
288 if self.out_type == OutputType::Sparse {
290 self.results.push(self.x, self.y.clone());
291 }
292
293 let mut k = vec![OVector::zeros_generic(rows, cols); 7];
294 self.f.system(self.x, &self.y, &mut k[0]);
295 self.stats.num_eval += 1;
296
297 while !last {
299 if n_step > self.n_max {
301 self.h_old = self.h;
302 return Err(IntegrationError::MaxNumStepReached {
303 x: f64::from(self.x),
304 n_step,
305 });
306 }
307
308 if T::from(0.1).unwrap() * self.h.abs() <= self.uround * self.x.abs() {
310 self.h_old = self.h;
311 return Err(IntegrationError::StepSizeUnderflow {
312 x: f64::from(self.x),
313 });
314 }
315
316 if (self.x + T::from(1.01).unwrap() * self.h - self.x_end) * posneg > T::zero() {
318 self.h = self.x_end - self.x;
319 last = true;
320 }
321 n_step += 1;
322
323 let h = self.h;
324 let mut y_next = OVector::zeros_generic(rows, cols);
326 let mut y_stiff = OVector::zeros_generic(rows, cols);
327 for s in 1..7 {
328 y_next = self.y.clone();
329 for (j, k_value) in k.iter().enumerate().take(s) {
330 y_next += k_value * h * dopri54::a(s + 1, j + 1);
331 }
332 self.f
333 .system(self.x + self.h * dopri54::c::<T>(s + 1), &y_next, &mut k[s]);
334 if s == 5 {
335 y_stiff = y_next.clone();
336 }
337 }
338 k[1] = k[6].clone();
339 self.stats.num_eval += 6;
340
341 if self.out_type == OutputType::Dense || self.out_type == OutputType::Continuous {
343 self.rcont[4] = (&k[0] * dopri54::d::<T>(1)
344 + &k[2] * dopri54::d::<T>(3)
345 + &k[3] * dopri54::d::<T>(4)
346 + &k[4] * dopri54::d::<T>(5)
347 + &k[5] * dopri54::d::<T>(6)
348 + &k[1] * dopri54::d::<T>(7))
349 * h;
350 }
351
352 k[3] = (&k[0] * dopri54::e::<T>(1)
354 + &k[2] * dopri54::e::<T>(3)
355 + &k[3] * dopri54::e::<T>(4)
356 + &k[4] * dopri54::e::<T>(5)
357 + &k[5] * dopri54::e::<T>(6)
358 + &k[1] * dopri54::e::<T>(7))
359 * h;
360
361 let mut err = T::zero();
363 for i in 0..dim {
364 let y_i = T::from(self.y[i]).unwrap();
365 let y_next_i = T::from(y_next[i]).unwrap();
366 let sc_i: T = self.atol + y_i.abs().max(y_next_i.abs()) * self.rtol;
367 let err_est_i = T::from(k[3][i]).unwrap();
368 err += (err_est_i / sc_i) * (err_est_i / sc_i);
369 }
370 err = (err / T::from(dim).unwrap()).sqrt();
371
372 if self.controller.accept(err, self.h, &mut h_new) {
374 self.stats.accepted_steps += 1;
375
376 if self.stats.accepted_steps % self.n_stiff == 0 || iasti > 0 {
378 let num = T::from((&k[1] - &k[5]).dot(&(&k[1] - &k[5]))).unwrap();
379 let den = T::from((&y_next - &y_stiff).dot(&(&y_next - &y_stiff))).unwrap();
380 let h_lamb = if den > T::zero() {
381 self.h * (num / den).sqrt()
382 } else {
383 T::zero()
384 };
385
386 if h_lamb > T::from(3.25).unwrap() {
387 iasti += 1;
388 non_stiff = 0;
389 if iasti == 15 {
390 self.h_old = self.h;
391 return Err(IntegrationError::StiffnessDetected {
392 x: f64::from(self.x),
393 });
394 }
395 } else {
396 non_stiff += 1;
397 if non_stiff == 6 {
398 iasti = 0;
399 }
400 }
401 }
402
403 if self.out_type == OutputType::Dense || self.out_type == OutputType::Continuous {
405 let h = self.h;
406
407 let ydiff = &y_next - &self.y;
408 let bspl = &k[0] * h - &ydiff;
409 self.rcont[0] = self.y.clone();
410 self.rcont[1] = ydiff.clone();
411 self.rcont[2] = bspl.clone();
412 self.rcont[3] = -&k[1] * h + ydiff - bspl;
413 }
414
415 k[0] = k[1].clone();
416 self.y = y_next.clone();
417 self.x_old = self.x;
418 self.x += self.h;
419 self.h_old = self.h;
420
421 self.solution_output(y_next, &mut continuous_output_model, &k[0]);
422
423 if self
424 .f
425 .solout(self.x, self.results.get().1.last().unwrap(), &k[0])
426 {
427 last = true;
428 }
429
430 if last {
432 self.h_old = posneg * h_new;
433 return Ok(self.stats);
434 }
435 } else {
436 last = false;
437 if self.stats.accepted_steps >= 1 {
438 self.stats.rejected_steps += 1;
439 }
440 }
441 self.h = h_new;
442 }
443 Ok(self.stats)
444 }
445
446 fn solution_output(
447 &mut self,
448 y_next: OVector<T, D>,
449 continuous_output_model: &mut Option<&mut ContinuousOutputModel<T, OVector<T, D>>>,
450 dy: &OVector<T, D>,
451 ) {
452 if self.out_type == OutputType::Dense {
453 while self.xd.abs() <= self.x.abs() {
454 if self.x_old.abs() <= self.xd.abs() && self.x.abs() >= self.xd.abs() {
455 let theta = (self.xd - self.x_old) / self.h_old;
456 let theta1 = T::one() - theta;
457 let y_out = self.compute_y_out(theta, theta1);
458 self.results.push(self.xd, y_out);
459 self.xd += self.dx;
460 }
461
462 if self
463 .f
464 .solout(self.xd, self.results.get().1.last().unwrap(), dy)
465 {
466 break;
467 }
468 }
469
470 if (self.xd - self.x_end).abs() < T::from(1e-9).unwrap() {
472 let theta = (self.x_end - self.x_old) / self.h_old;
473 let theta1 = T::one() - theta;
474 let y_out = self.compute_y_out(theta, theta1);
475 self.results.push(self.x_end, y_out);
476 self.xd += self.dx;
477 }
478 } else {
479 self.results.push(self.x, y_next)
480 }
481
482 if let Some(output) = continuous_output_model.as_deref_mut() {
484 output.add_interval(self.x.abs(), self.rcont.to_vec(), self.h_old);
485 }
486 }
487
488 fn compute_y_out(&mut self, theta: T, theta1: T) -> MatrixSum<T, D, U1, D, U1> {
490 &self.rcont[0]
491 + (&self.rcont[1]
492 + (&self.rcont[2] + (&self.rcont[3] + &self.rcont[4] * theta1) * theta) * theta1)
493 * theta
494 }
495
496 pub fn x_out(&self) -> &Vec<T> {
498 self.results.get().0
499 }
500
501 pub fn y_out(&self) -> &Vec<OVector<T, D>> {
503 self.results.get().1
504 }
505
506 pub fn results(&self) -> &SolverResult<T, OVector<T, D>> {
508 &self.results
509 }
510}
511
512impl<T, D: Dim, F> From<Dopri5<T, OVector<T, D>, F>> for SolverResult<T, OVector<T, D>>
513where
514 T: FloatNumber,
515 F: System<T, OVector<T, D>>,
516 DefaultAllocator: Allocator<D>,
517{
518 fn from(val: Dopri5<T, OVector<T, D>, F>) -> Self {
519 val.results
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::{OVector, System, Vector1};
527 use nalgebra::{allocator::Allocator, DefaultAllocator, Dim};
528
529 struct Test1 {}
531 impl<D: Dim> System<f64, OVector<f64, D>> for Test1
532 where
533 DefaultAllocator: Allocator<D>,
534 {
535 fn system(&self, x: f64, y: &OVector<f64, D>, dy: &mut OVector<f64, D>) {
536 dy[0] = (5. * x * x - y[0]) / (x + y[0]).exp();
537 }
538
539 fn solout(&mut self, x: f64, _y: &OVector<f64, D>, _dy: &OVector<f64, D>) -> bool {
540 x >= 0.5
541 }
542 }
543
544 #[test]
545 fn test_integrate_test1_svector() {
546 let system = Test1 {};
547 let mut stepper = Dopri5::new(system, 0., 1., 0.1, Vector1::new(1.), 1e-12, 1e-6);
548 let _ = stepper.integrate();
549
550 let x = stepper.x_out();
551 assert!((*x.last().unwrap() - 0.5).abs() < 1.0E-9);
552
553 let out = stepper.y_out();
554 assert!((&out[5][0] - 0.9130611474392001).abs() < 1.0E-9);
555 }
556
557 #[test]
558 #[should_panic]
559 fn test_integrate_when_continuous_output_type_panic() {
560 let system = Test1 {};
561 let mut stepper = Dopri5::from_param(
562 system,
563 0.,
564 1.,
565 0.1,
566 Vector1::new(1.),
567 1e-12,
568 1e-6,
569 0.1,
570 0.2,
571 0.3,
572 2.0,
573 5.0,
574 0.1,
575 10000,
576 1000,
577 OutputType::Continuous,
578 );
579 let _ = stepper.integrate();
580 }
581}