1use crate::common::IntegrateFloat;
8use crate::error::IntegrateResult;
9use crate::ode::types::{ODEMethod, ODEOptions, ODEResult};
10use scirs2_core::ndarray::{Array1, ArrayView1};
11
12#[allow(dead_code)]
28pub fn rk45_method<F, Func>(
29 f: Func,
30 t_span: [F; 2],
31 y0: Array1<F>,
32 opts: ODEOptions<F>,
33) -> IntegrateResult<ODEResult<F>>
34where
35 F: IntegrateFloat,
36 Func: Fn(F, ArrayView1<F>) -> Array1<F>,
37{
38 let [t_start, t_end] = t_span;
40 let n_dim = y0.len();
41
42 let h0 = opts.h0.unwrap_or_else(|| {
44 let _span = t_end - t_start;
46 _span / F::from_usize(100).expect("Operation failed")
47 });
48
49 let min_step = opts.min_step.unwrap_or_else(|| {
51 let _span = t_end - t_start;
52 _span * F::from_f64(1e-8).expect("Operation failed") });
54
55 let max_step = opts.max_step.unwrap_or_else(|| {
56 t_end - t_start });
58
59 let mut t = t_start;
61 let mut y = y0.clone();
62 let mut h = h0;
63
64 let mut t_values = vec![t_start];
66 let mut y_values = vec![y0.clone()];
67
68 let mut func_evals = 0;
70 let mut step_count = 0;
71 let mut accepted_steps = 0;
72 let mut rejected_steps = 0;
73
74 let c2 = F::from_f64(1.0 / 5.0).expect("Operation failed");
77 let c3 = F::from_f64(3.0 / 10.0).expect("Operation failed");
78 let c4 = F::from_f64(4.0 / 5.0).expect("Operation failed");
79 let c5 = F::from_f64(8.0 / 9.0).expect("Operation failed");
80 let c6 = F::one();
81
82 while t < t_end && step_count < opts.max_steps {
84 if t + h > t_end {
86 h = t_end - t;
87 }
88
89 h = h.min(max_step).max(min_step);
91
92 let k1 = f(t, y.view());
94
95 let mut y_stage = y.clone();
97 for i in 0..n_dim {
98 y_stage[i] = y[i] + h * F::from_f64(1.0 / 5.0).expect("Operation failed") * k1[i];
99 }
100 let k2 = f(t + c2 * h, y_stage.view());
101
102 let mut y_stage = y.clone();
103 for i in 0..n_dim {
104 y_stage[i] = y[i]
105 + h * (F::from_f64(3.0 / 40.0).expect("Operation failed") * k1[i]
106 + F::from_f64(9.0 / 40.0).expect("Operation failed") * k2[i]);
107 }
108 let k3 = f(t + c3 * h, y_stage.view());
109
110 let mut y_stage = y.clone();
111 for i in 0..n_dim {
112 y_stage[i] = y[i]
113 + h * (F::from_f64(44.0 / 45.0).expect("Operation failed") * k1[i]
114 + F::from_f64(-56.0 / 15.0).expect("Operation failed") * k2[i]
115 + F::from_f64(32.0 / 9.0).expect("Operation failed") * k3[i]);
116 }
117 let k4 = f(t + c4 * h, y_stage.view());
118
119 let mut y_stage = y.clone();
120 for i in 0..n_dim {
121 y_stage[i] = y[i]
122 + h * (F::from_f64(19372.0 / 6561.0).expect("Operation failed") * k1[i]
123 + F::from_f64(-25360.0 / 2187.0).expect("Operation failed") * k2[i]
124 + F::from_f64(64448.0 / 6561.0).expect("Operation failed") * k3[i]
125 + F::from_f64(-212.0 / 729.0).expect("Operation failed") * k4[i]);
126 }
127 let k5 = f(t + c5 * h, y_stage.view());
128
129 let mut y_stage = y.clone();
130 for i in 0..n_dim {
131 y_stage[i] = y[i]
132 + h * (F::from_f64(9017.0 / 3168.0).expect("Operation failed") * k1[i]
133 + F::from_f64(-355.0 / 33.0).expect("Operation failed") * k2[i]
134 + F::from_f64(46732.0 / 5247.0).expect("Operation failed") * k3[i]
135 + F::from_f64(49.0 / 176.0).expect("Operation failed") * k4[i]
136 + F::from_f64(-5103.0 / 18656.0).expect("Operation failed") * k5[i]);
137 }
138 let k6 = f(t + c6 * h, y_stage.view());
139
140 let mut y_stage = y.clone();
141 for i in 0..n_dim {
142 y_stage[i] = y[i]
143 + h * (F::from_f64(35.0 / 384.0).expect("Operation failed") * k1[i]
144 + F::zero() * k2[i]
145 + F::from_f64(500.0 / 1113.0).expect("Operation failed") * k3[i]
146 + F::from_f64(125.0 / 192.0).expect("Operation failed") * k4[i]
147 + F::from_f64(-2187.0 / 6784.0).expect("Operation failed") * k5[i]
148 + F::from_f64(11.0 / 84.0).expect("Operation failed") * k6[i]);
149 }
150 let k7 = f(t + h, y_stage.view());
151
152 func_evals += 7;
153
154 let mut y5 = y.clone();
156 for i in 0..n_dim {
157 y5[i] = y[i]
158 + h * (F::from_f64(35.0 / 384.0).expect("Operation failed") * k1[i]
159 + F::zero() * k2[i]
160 + F::from_f64(500.0 / 1113.0).expect("Operation failed") * k3[i]
161 + F::from_f64(125.0 / 192.0).expect("Operation failed") * k4[i]
162 + F::from_f64(-2187.0 / 6784.0).expect("Operation failed") * k5[i]
163 + F::from_f64(11.0 / 84.0).expect("Operation failed") * k6[i]
164 + F::zero() * k7[i]);
165 }
166
167 let mut y4 = y.clone();
169 for i in 0..n_dim {
170 y4[i] = y[i]
171 + h * (F::from_f64(5179.0 / 57600.0).expect("Operation failed") * k1[i]
172 + F::zero() * k2[i]
173 + F::from_f64(7571.0 / 16695.0).expect("Operation failed") * k3[i]
174 + F::from_f64(393.0 / 640.0).expect("Operation failed") * k4[i]
175 + F::from_f64(-92097.0 / 339200.0).expect("Operation failed") * k5[i]
176 + F::from_f64(187.0 / 2100.0).expect("Operation failed") * k6[i]
177 + F::from_f64(1.0 / 40.0).expect("Operation failed") * k7[i]);
178 }
179
180 let mut err_norm = F::zero();
182 for i in 0..n_dim {
183 let sc = opts.atol + opts.rtol * y5[i].abs();
184 let err = (y5[i] - y4[i]).abs() / sc;
185 err_norm = err_norm.max(err);
186 }
187
188 let order = F::from_f64(5.0).expect("Operation failed"); let exponent = F::one() / (order + F::one());
191 let safety = F::from_f64(0.9).expect("Operation failed");
192 let factor = safety * (F::one() / err_norm).powf(exponent);
193 let factor_min = F::from_f64(0.2).expect("Operation failed");
194 let factor_max = F::from_f64(5.0).expect("Operation failed");
195 let factor = factor.min(factor_max).max(factor_min);
196
197 if err_norm <= F::one() {
198 t += h;
200 y = y5; t_values.push(t);
204 y_values.push(y.clone());
205
206 if err_norm <= F::from_f64(0.1).expect("Operation failed") {
208 h *= factor.max(F::from_f64(2.0).expect("Operation failed"));
210 } else {
211 h *= factor;
212 }
213
214 step_count += 1;
215 accepted_steps += 1;
216 } else {
217 h *= factor.min(F::one());
219 rejected_steps += 1;
220
221 if h < min_step {
223 return Err(crate::error::IntegrateError::StepSizeTooSmall(format!(
224 "Step size {h} too small at t {t}"
225 )));
226 }
227 }
228 }
229
230 let success = t >= t_end;
232 let message = if !success {
233 Some(format!(
234 "Maximum number of steps ({}) reached",
235 opts.max_steps
236 ))
237 } else {
238 None
239 };
240
241 Ok(ODEResult {
243 t: t_values,
244 y: y_values,
245 success,
246 message,
247 n_eval: func_evals,
248 n_steps: step_count,
249 n_accepted: accepted_steps,
250 n_rejected: rejected_steps,
251 n_lu: 0, n_jac: 0, method: ODEMethod::RK45,
254 })
255}
256
257#[allow(clippy::too_many_arguments)]
276fn pi_step_factor<F: IntegrateFloat>(
277 err_norm: F,
278 err_prev: F,
279 accepted: bool,
280 just_recovered_from_rejection: bool,
281 alpha: F,
282 beta_gain: F,
283 safety: F,
284 growth_min: F,
285 growth_max: F,
286) -> (F, F) {
287 let tiny = F::from_f64(1e-300).expect("Operation failed");
288 let err_eff = err_norm.max(tiny);
289 let fac_elementary = err_eff.powf(alpha);
290
291 if accepted {
292 let history = err_prev.max(tiny).powf(beta_gain);
293 let mut factor = safety / (fac_elementary * history);
294 factor = factor.min(growth_max).max(growth_min);
295 if just_recovered_from_rejection {
296 factor = factor.min(F::one());
298 }
299 let updated_err_prev = err_norm.max(F::from_f64(1e-10).expect("Operation failed"));
300 (factor, updated_err_prev)
301 } else {
302 let factor = (safety / fac_elementary).min(F::one()).max(growth_min);
305 (factor, err_prev)
306 }
307}
308
309#[allow(dead_code)]
336pub fn rk23_method<F, Func>(
337 f: Func,
338 t_span: [F; 2],
339 y0: Array1<F>,
340 opts: ODEOptions<F>,
341) -> IntegrateResult<ODEResult<F>>
342where
343 F: IntegrateFloat,
344 Func: Fn(F, ArrayView1<F>) -> Array1<F>,
345{
346 let [t_start, t_end] = t_span;
348 let n_dim = y0.len();
349
350 let h0 = opts.h0.unwrap_or_else(|| {
352 let _span = t_end - t_start;
354 _span / F::from_usize(100).expect("Operation failed")
355 });
356
357 let min_step = opts.min_step.unwrap_or_else(|| {
359 let _span = t_end - t_start;
360 _span * F::from_f64(1e-8).expect("Operation failed") });
362
363 let max_step = opts.max_step.unwrap_or_else(|| {
364 t_end - t_start });
366
367 let mut t = t_start;
369 let mut y = y0.clone();
370 let mut h = h0;
371
372 let mut t_values = vec![t_start];
374 let mut y_values = vec![y0.clone()];
375
376 let mut func_evals = 0;
378 let mut step_count = 0;
379 let mut accepted_steps = 0;
380 let mut rejected_steps = 0;
381
382 let c2 = F::from_f64(0.5).expect("Operation failed");
384 let c3 = F::from_f64(0.75).expect("Operation failed");
385 let a21 = F::from_f64(0.5).expect("Operation failed");
386 let a32 = F::from_f64(0.75).expect("Operation failed");
387 let b1 = F::from_f64(2.0 / 9.0).expect("Operation failed");
388 let b2 = F::from_f64(1.0 / 3.0).expect("Operation failed");
389 let b3 = F::from_f64(4.0 / 9.0).expect("Operation failed");
390 let e1 = F::from_f64(5.0 / 72.0).expect("Operation failed");
392 let e2 = F::from_f64(-1.0 / 12.0).expect("Operation failed");
393 let e3 = F::from_f64(-1.0 / 9.0).expect("Operation failed");
394 let e4 = F::from_f64(0.125).expect("Operation failed");
395
396 let alpha = F::one() / F::from_f64(3.0).expect("Operation failed");
400 let beta_gain = F::from_f64(0.08).expect("Operation failed");
401 let safety = F::from_f64(0.9).expect("Operation failed");
402 let growth_min = F::from_f64(0.2).expect("Operation failed");
403 let growth_max = F::from_f64(10.0).expect("Operation failed");
404 let mut err_prev = F::one();
405 let mut just_rejected = false;
406
407 while t < t_end && step_count < opts.max_steps {
409 if t + h > t_end {
411 h = t_end - t;
412 }
413
414 h = h.min(max_step).max(min_step);
416
417 let k1 = f(t, y.view());
419
420 let mut y_stage = y.clone();
422 for i in 0..n_dim {
423 y_stage[i] = y[i] + h * a21 * k1[i];
424 }
425 let k2 = f(t + c2 * h, y_stage.view());
426
427 let mut y_stage = y.clone();
429 for i in 0..n_dim {
430 y_stage[i] = y[i] + h * a32 * k2[i];
431 }
432 let k3 = f(t + c3 * h, y_stage.view());
433
434 let mut y3 = y.clone();
437 for i in 0..n_dim {
438 y3[i] = y[i] + h * (b1 * k1[i] + b2 * k2[i] + b3 * k3[i]);
439 }
440
441 let k4 = f(t + h, y3.view());
443 func_evals += 4;
444
445 let mut err_norm = F::zero();
447 for i in 0..n_dim {
448 let sc = opts.atol + opts.rtol * y3[i].abs().max(y[i].abs());
449 let err_i = e1 * k1[i] + e2 * k2[i] + e3 * k3[i] + e4 * k4[i];
450 err_norm = err_norm.max((h * err_i / sc).abs());
451 }
452
453 let accepted = err_norm <= F::one();
454 let (factor, new_err_prev) = pi_step_factor(
455 err_norm,
456 err_prev,
457 accepted,
458 just_rejected,
459 alpha,
460 beta_gain,
461 safety,
462 growth_min,
463 growth_max,
464 );
465
466 if accepted {
467 t += h;
469 y = y3;
470
471 t_values.push(t);
473 y_values.push(y.clone());
474
475 err_prev = new_err_prev;
476 just_rejected = false;
477 h *= factor;
478
479 step_count += 1;
480 accepted_steps += 1;
481 } else {
482 h *= factor;
484 just_rejected = true;
485 rejected_steps += 1;
486
487 if h < min_step {
489 return Err(crate::error::IntegrateError::StepSizeTooSmall(format!(
490 "Step size {h} too small at t {t}"
491 )));
492 }
493 }
494 }
495
496 let success = t >= t_end;
498 let message = if !success {
499 Some(format!(
500 "Maximum number of steps ({}) reached",
501 opts.max_steps
502 ))
503 } else {
504 None
505 };
506
507 Ok(ODEResult {
509 t: t_values,
510 y: y_values,
511 success,
512 message,
513 n_eval: func_evals,
514 n_steps: step_count,
515 n_accepted: accepted_steps,
516 n_rejected: rejected_steps,
517 n_lu: 0, n_jac: 0, method: ODEMethod::RK23,
520 })
521}
522
523const DOP853_STAGES: usize = 12;
538
539const DOP853_C: [f64; 12] = [
542 0.0,
543 0.05260015195876773,
544 0.0789002279381516,
545 0.1183503419072274,
546 0.2816496580927726,
547 0.3333333333333333,
548 0.25,
549 0.3076923076923077,
550 0.6512820512820513,
551 0.6,
552 0.8571428571428571,
553 1.0,
554];
555
556const DOP853_A: [[f64; 12]; 12] = [
560 [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
561 [
562 0.05260015195876773,
563 0.0,
564 0.0,
565 0.0,
566 0.0,
567 0.0,
568 0.0,
569 0.0,
570 0.0,
571 0.0,
572 0.0,
573 0.0,
574 ],
575 [
576 0.0197250569845379,
577 0.0591751709536137,
578 0.0,
579 0.0,
580 0.0,
581 0.0,
582 0.0,
583 0.0,
584 0.0,
585 0.0,
586 0.0,
587 0.0,
588 ],
589 [
590 0.02958758547680685,
591 0.0,
592 0.08876275643042054,
593 0.0,
594 0.0,
595 0.0,
596 0.0,
597 0.0,
598 0.0,
599 0.0,
600 0.0,
601 0.0,
602 ],
603 [
604 0.2413651341592667,
605 0.0,
606 -0.8845494793282861,
607 0.924834003261792,
608 0.0,
609 0.0,
610 0.0,
611 0.0,
612 0.0,
613 0.0,
614 0.0,
615 0.0,
616 ],
617 [
618 0.037037037037037035,
619 0.0,
620 0.0,
621 0.17082860872947386,
622 0.12546768756682242,
623 0.0,
624 0.0,
625 0.0,
626 0.0,
627 0.0,
628 0.0,
629 0.0,
630 ],
631 [
632 0.037109375,
633 0.0,
634 0.0,
635 0.17025221101954405,
636 0.06021653898045596,
637 -0.017578125,
638 0.0,
639 0.0,
640 0.0,
641 0.0,
642 0.0,
643 0.0,
644 ],
645 [
646 0.03709200011850479,
647 0.0,
648 0.0,
649 0.17038392571223998,
650 0.10726203044637328,
651 -0.015319437748624402,
652 0.008273789163814023,
653 0.0,
654 0.0,
655 0.0,
656 0.0,
657 0.0,
658 ],
659 [
660 0.6241109587160757,
661 0.0,
662 0.0,
663 -3.3608926294469414,
664 -0.868219346841726,
665 27.59209969944671,
666 20.154067550477894,
667 -43.48988418106996,
668 0.0,
669 0.0,
670 0.0,
671 0.0,
672 ],
673 [
674 0.47766253643826434,
675 0.0,
676 0.0,
677 -2.4881146199716677,
678 -0.590290826836843,
679 21.230051448181193,
680 15.279233632882423,
681 -33.28821096898486,
682 -0.020331201708508627,
683 0.0,
684 0.0,
685 0.0,
686 ],
687 [
688 -0.9371424300859873,
689 0.0,
690 0.0,
691 5.186372428844064,
692 1.0914373489967295,
693 -8.149787010746927,
694 -18.52006565999696,
695 22.739487099350505,
696 2.4936055526796523,
697 -3.0467644718982196,
698 0.0,
699 0.0,
700 ],
701 [
702 2.273310147516538,
703 0.0,
704 0.0,
705 -10.53449546673725,
706 -2.0008720582248625,
707 -17.9589318631188,
708 27.94888452941996,
709 -2.8589982771350235,
710 -8.87285693353063,
711 12.360567175794303,
712 0.6433927460157636,
713 0.0,
714 ],
715];
716
717const DOP853_B: [f64; 12] = [
719 0.054293734116568765,
720 0.0,
721 0.0,
722 0.0,
723 0.0,
724 4.450312892752409,
725 1.8915178993145003,
726 -5.801203960010585,
727 0.3111643669578199,
728 -0.1521609496625161,
729 0.20136540080403034,
730 0.04471061572777259,
731];
732
733const DOP853_E5: [f64; 12] = [
735 0.01312004499419488,
736 0.0,
737 0.0,
738 0.0,
739 0.0,
740 -1.2251564463762044,
741 -0.4957589496572502,
742 1.6643771824549864,
743 -0.35032884874997366,
744 0.3341791187130175,
745 0.08192320648511571,
746 -0.022355307863886294,
747];
748
749const DOP853_E3_ADJUST: [(usize, f64); 3] = [
753 (0, 0.2440944881889764),
754 (8, 0.7338466882816118),
755 (11, 0.022058823529411766),
756];
757
758#[allow(dead_code)]
787pub fn dop853_method<F, Func>(
788 f: Func,
789 t_span: [F; 2],
790 y0: Array1<F>,
791 opts: ODEOptions<F>,
792) -> IntegrateResult<ODEResult<F>>
793where
794 F: IntegrateFloat,
795 Func: Fn(F, ArrayView1<F>) -> Array1<F>,
796{
797 let [t_start, t_end] = t_span;
799 let n_dim = y0.len();
800
801 let h0 = opts.h0.unwrap_or_else(|| {
803 let _span = t_end - t_start;
805 _span / F::from_usize(100).expect("Operation failed")
806 });
807
808 let min_step = opts.min_step.unwrap_or_else(|| {
810 let _span = t_end - t_start;
811 _span * F::from_f64(1e-8).expect("Operation failed") });
813
814 let max_step = opts.max_step.unwrap_or_else(|| {
815 t_end - t_start });
817
818 let mut t = t_start;
820 let mut y = y0.clone();
821 let mut h = h0;
822
823 let mut t_values = vec![t_start];
825 let mut y_values = vec![y0.clone()];
826
827 let mut func_evals = 0;
829 let mut step_count = 0;
830 let mut accepted_steps = 0;
831 let mut rejected_steps = 0;
832
833 let mut e3 = DOP853_B;
836 for &(idx, adjust) in DOP853_E3_ADJUST.iter() {
837 e3[idx] -= adjust;
838 }
839
840 let alpha = F::from_f64(0.125).expect("Operation failed");
844 let beta_gain = F::zero();
845 let safety = F::from_f64(0.9).expect("Operation failed");
846 let growth_min = F::from_f64(1.0 / 3.0).expect("Operation failed");
847 let growth_max = F::from_f64(6.0).expect("Operation failed");
848 let mut err_prev = F::one();
849 let mut just_rejected = false;
850
851 let n_dim_f = F::from_usize(n_dim).expect("Operation failed");
852 let err3_weight = F::from_f64(0.01).expect("Operation failed");
853 let non_finite_penalty = F::from_f64(1e30).expect("Operation failed");
854
855 while t < t_end && step_count < opts.max_steps {
857 if t + h > t_end {
859 h = t_end - t;
860 }
861
862 h = h.min(max_step).max(min_step);
864
865 let mut k_stages: Vec<Array1<F>> = Vec::with_capacity(DOP853_STAGES);
867 k_stages.push(f(t, y.view()));
868 for s in 1..DOP853_STAGES {
869 let mut y_stage = y.clone();
870 for (j, k_j) in k_stages.iter().enumerate().take(s) {
871 let a_sj = DOP853_A[s][j];
872 if a_sj != 0.0 {
873 let a_sj = F::from_f64(a_sj).expect("Operation failed");
874 for d in 0..n_dim {
875 y_stage[d] += h * a_sj * k_j[d];
876 }
877 }
878 }
879 let c_s = F::from_f64(DOP853_C[s]).expect("Operation failed");
880 k_stages.push(f(t + c_s * h, y_stage.view()));
881 }
882 func_evals += DOP853_STAGES;
883
884 let mut y8 = y.clone();
886 for (j, k_j) in k_stages.iter().enumerate() {
887 let b_j = DOP853_B[j];
888 if b_j != 0.0 {
889 let b_j = F::from_f64(b_j).expect("Operation failed");
890 for d in 0..n_dim {
891 y8[d] += h * b_j * k_j[d];
892 }
893 }
894 }
895
896 let mut err5_sq = F::zero();
898 let mut err3_sq = F::zero();
899 for d in 0..n_dim {
900 let scale = opts.atol + opts.rtol * y[d].abs().max(y8[d].abs());
901 let mut e5_d = F::zero();
902 let mut e3_d = F::zero();
903 for (j, k_j) in k_stages.iter().enumerate() {
904 let kjd = k_j[d];
905 let e5j = DOP853_E5[j];
906 if e5j != 0.0 {
907 e5_d += F::from_f64(e5j).expect("Operation failed") * kjd;
908 }
909 let e3j = e3[j];
910 if e3j != 0.0 {
911 e3_d += F::from_f64(e3j).expect("Operation failed") * kjd;
912 }
913 }
914 let e5s = e5_d / scale;
915 let e3s = e3_d / scale;
916 err5_sq += e5s * e5s;
917 err3_sq += e3s * e3s;
918 }
919 let denom = err5_sq + err3_weight * err3_sq;
920 let err_norm = if denom <= F::zero() {
921 F::zero()
922 } else {
923 let raw = h.abs() * err5_sq / (denom * n_dim_f).sqrt();
924 if raw.is_finite() {
925 raw
926 } else {
927 non_finite_penalty
932 }
933 };
934
935 let accepted = err_norm <= F::one();
936 let (factor, new_err_prev) = pi_step_factor(
937 err_norm,
938 err_prev,
939 accepted,
940 just_rejected,
941 alpha,
942 beta_gain,
943 safety,
944 growth_min,
945 growth_max,
946 );
947
948 if accepted {
949 t += h;
951 y = y8;
952
953 t_values.push(t);
955 y_values.push(y.clone());
956
957 err_prev = new_err_prev;
958 just_rejected = false;
959 h *= factor;
960
961 step_count += 1;
962 accepted_steps += 1;
963 } else {
964 h *= factor;
966 just_rejected = true;
967 rejected_steps += 1;
968
969 if h < min_step {
971 return Err(crate::error::IntegrateError::StepSizeTooSmall(format!(
972 "Step size {h} too small at t {t}"
973 )));
974 }
975 }
976 }
977
978 let success = t >= t_end;
980 let message = if !success {
981 Some(format!(
982 "Maximum number of steps ({}) reached",
983 opts.max_steps
984 ))
985 } else {
986 None
987 };
988
989 Ok(ODEResult {
991 t: t_values,
992 y: y_values,
993 success,
994 message,
995 n_eval: func_evals,
996 n_steps: step_count,
997 n_accepted: accepted_steps,
998 n_rejected: rejected_steps,
999 n_lu: 0, n_jac: 0, method: ODEMethod::DOP853,
1002 })
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use super::*;
1008
1009 #[test]
1018 fn rk23_empirical_convergence_order_is_three() {
1019 let k = 3.0_f64;
1020 let f =
1021 move |_t: f64, y: ArrayView1<f64>| -> Array1<f64> { Array1::from_vec(vec![-k * y[0]]) };
1022 let t_end = 1.0_f64;
1023 let y0 = 2.0_f64;
1024 let y_exact = y0 * (-k * t_end).exp();
1025
1026 let mut prev_err: Option<f64> = None;
1027 let mut min_ratio = f64::INFINITY;
1028 let mut h = t_end / 8.0;
1029 for _ in 0..3 {
1030 let opts = ODEOptions {
1031 method: ODEMethod::RK23,
1032 rtol: 1.0,
1033 atol: 1.0,
1034 h0: Some(h),
1035 min_step: Some(h),
1036 max_step: Some(h),
1037 max_steps: 1_000_000,
1038 ..Default::default()
1039 };
1040 let result = rk23_method(f, [0.0, t_end], Array1::from_vec(vec![y0]), opts)
1041 .expect("RK23 fixed-step solve failed");
1042 assert!(
1043 result.success,
1044 "RK23 fixed-step integration did not complete"
1045 );
1046 let y_final = result.y.last().expect("empty result")[0];
1047 let err = (y_final - y_exact).abs();
1048
1049 if let Some(prev) = prev_err {
1050 min_ratio = min_ratio.min(prev / err);
1051 }
1052 prev_err = Some(err);
1053 h /= 2.0;
1054 }
1055
1056 assert!(
1060 min_ratio > 5.0,
1061 "RK23 empirical convergence order too low: min ratio={min_ratio} (expected ~8)"
1062 );
1063 }
1064
1065 #[test]
1066 fn dop853_empirical_convergence_order_is_eight() {
1067 let k = 3.0_f64;
1068 let f =
1069 move |_t: f64, y: ArrayView1<f64>| -> Array1<f64> { Array1::from_vec(vec![-k * y[0]]) };
1070 let t_end = 1.0_f64;
1071 let y0 = 2.0_f64;
1072 let y_exact = y0 * (-k * t_end).exp();
1073
1074 let mut prev_err: Option<f64> = None;
1075 let mut min_ratio = f64::INFINITY;
1076 let mut h = t_end / 2.0;
1077 for _ in 0..3 {
1078 let opts = ODEOptions {
1079 method: ODEMethod::DOP853,
1080 rtol: 1.0,
1081 atol: 1.0,
1082 h0: Some(h),
1083 min_step: Some(h),
1084 max_step: Some(h),
1085 max_steps: 1_000_000,
1086 ..Default::default()
1087 };
1088 let result = dop853_method(f, [0.0, t_end], Array1::from_vec(vec![y0]), opts)
1089 .expect("DOP853 fixed-step solve failed");
1090 assert!(
1091 result.success,
1092 "DOP853 fixed-step integration did not complete"
1093 );
1094 let y_final = result.y.last().expect("empty result")[0];
1095 let err = (y_final - y_exact).abs();
1096
1097 if let Some(prev) = prev_err {
1098 min_ratio = min_ratio.min(prev / err);
1099 }
1100 prev_err = Some(err);
1101 h /= 2.0;
1102 }
1103
1104 assert!(
1107 min_ratio > 50.0,
1108 "DOP853 empirical convergence order too low: min ratio={min_ratio} (expected ~256)"
1109 );
1110 }
1111
1112 #[test]
1117 fn rk23_dop853_agree_with_rk45_van_der_pol() {
1118 let mu = 1.0_f64;
1119 let f = move |_t: f64, y: ArrayView1<f64>| -> Array1<f64> {
1120 Array1::from_vec(vec![y[1], mu * (1.0 - y[0] * y[0]) * y[1] - y[0]])
1121 };
1122 let y0 = Array1::from_vec(vec![0.5_f64, 0.0]);
1123 let t_span = [0.0_f64, 3.0];
1124
1125 let opts_of = |method: ODEMethod| ODEOptions {
1126 method,
1127 rtol: 1e-10,
1128 atol: 1e-12,
1129 max_steps: 500_000,
1130 ..Default::default()
1131 };
1132
1133 let rk45 = rk45_method(f, t_span, y0.clone(), opts_of(ODEMethod::RK45))
1134 .expect("RK45 Van der Pol solve failed");
1135 let rk23 = rk23_method(f, t_span, y0.clone(), opts_of(ODEMethod::RK23))
1136 .expect("RK23 Van der Pol solve failed");
1137 let dop853 = dop853_method(f, t_span, y0, opts_of(ODEMethod::DOP853))
1138 .expect("DOP853 Van der Pol solve failed");
1139
1140 assert!(rk45.success && rk23.success && dop853.success);
1141
1142 let y_rk45 = rk45.y.last().expect("empty result");
1143 let y_rk23 = rk23.y.last().expect("empty result");
1144 let y_dop853 = dop853.y.last().expect("empty result");
1145
1146 for i in 0..2 {
1147 assert!(
1148 (y_rk23[i] - y_rk45[i]).abs() < 1e-5,
1149 "RK23 vs RK45 disagreement at index {i}: {} vs {}",
1150 y_rk23[i],
1151 y_rk45[i]
1152 );
1153 assert!(
1154 (y_dop853[i] - y_rk45[i]).abs() < 1e-5,
1155 "DOP853 vs RK45 disagreement at index {i}: {} vs {}",
1156 y_dop853[i],
1157 y_rk45[i]
1158 );
1159 }
1160 }
1161}