1use std::fmt;
27use std::ops::ControlFlow;
28
29use serde::{Deserialize, Serialize};
30use thiserror::Error;
31
32use crate::events::{Direction, EVENT_TIME_RESOLUTION_S, RootError, find_root};
33
34pub trait OdeSystem<const N: usize> {
41 type Error;
43
44 fn derivative(&mut self, t_s: f64, y: &[f64; N]) -> Result<[f64; N], Self::Error>;
53
54 fn absolute_tolerance_weights(&self) -> [f64; N] {
58 [1.0; N]
59 }
60
61 fn event_count(&self) -> usize {
63 0
64 }
65
66 fn event_direction(&self, _index: usize) -> Direction {
68 Direction::Either
69 }
70
71 fn event_value(&mut self, _index: usize, _t_s: f64, _y: &[f64; N]) -> f64 {
74 f64::NAN
75 }
76
77 fn accept_step(&mut self, _step: &Step<N>) -> ControlFlow<()> {
81 ControlFlow::Continue(())
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
87#[serde(default, deny_unknown_fields)]
88pub struct Adaptive {
89 pub relative_tolerance: f64,
91 pub absolute_tolerance: f64,
94 pub initial_step_s: Option<f64>,
96 pub max_step_s: Option<f64>,
99}
100
101impl Default for Adaptive {
102 fn default() -> Self {
104 Self {
105 relative_tolerance: 1e-8,
106 absolute_tolerance: 1e-8,
107 initial_step_s: None,
108 max_step_s: None,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
115#[serde(tag = "method", deny_unknown_fields)]
116#[non_exhaustive]
117pub enum Method {
118 #[serde(rename = "dopri5")]
120 DormandPrince54(Adaptive),
121 #[serde(rename = "rk4")]
123 Rk4 {
124 step_s: f64,
126 },
127}
128
129impl Default for Method {
130 fn default() -> Self {
132 Self::DormandPrince54(Adaptive::default())
133 }
134}
135
136impl Method {
137 pub fn validate(&self) -> Result<(), SettingsError> {
143 match self {
144 Self::DormandPrince54(adaptive) => {
145 positive("relative tolerance", adaptive.relative_tolerance)?;
146 positive("absolute tolerance", adaptive.absolute_tolerance)?;
147 if let Some(h) = adaptive.initial_step_s {
148 positive("initial step", h)?;
149 }
150 if let Some(h) = adaptive.max_step_s {
151 positive("maximum step", h)?;
152 }
153 Ok(())
154 }
155 Self::Rk4 { step_s } => positive("RK4 step", *step_s),
156 }
157 }
158}
159
160fn positive(what: &'static str, value: f64) -> Result<(), SettingsError> {
161 if value.is_finite() && value > 0.0 {
162 Ok(())
163 } else {
164 Err(SettingsError::NotPositive { what, value })
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Error)]
170#[non_exhaustive]
171pub enum SettingsError {
172 #[error("the {what} must be finite and positive, not {value}")]
174 NotPositive {
175 what: &'static str,
177 value: f64,
179 },
180 #[error("the {what} must be finite, not {value}")]
182 NotFinite {
183 what: &'static str,
185 value: f64,
187 },
188}
189
190#[derive(Debug, Clone, PartialEq, Error)]
193#[non_exhaustive]
194pub enum IntegrationError<E> {
195 #[error("the derivative failed at t = {t_s} s")]
197 Derivative {
198 t_s: f64,
200 source: E,
202 },
203 #[error("the step fell to {step_s} s at t = {t_s} s")]
206 StepTooSmall {
207 t_s: f64,
209 step_s: f64,
211 },
212 #[error("the solution is not finite after t = {t_s} s")]
214 NotFinite {
215 t_s: f64,
217 },
218 #[error("the step limit ({limit}) was reached at t = {t_s} s")]
220 StepLimit {
221 t_s: f64,
223 limit: u64,
225 },
226 #[error("event {index} failed at t = {t_s} s")]
228 EventNotFinite {
229 index: usize,
231 t_s: f64,
233 },
234 #[error("cannot integrate from t = {t_s} s to {t_stop_s} s")]
236 Backward {
237 t_s: f64,
239 t_stop_s: f64,
241 },
242 #[error(transparent)]
244 Settings(#[from] SettingsError),
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249#[non_exhaustive]
250pub enum Advance {
251 Reached,
253 Events,
256 Stopped,
258}
259
260#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
262pub struct Stats {
263 pub evaluations: u64,
265 pub accepted_steps: u64,
267 pub rejected_steps: u64,
269}
270
271#[derive(Clone, PartialEq)]
273pub struct Step<const N: usize> {
274 start_s: f64,
275 end_s: f64,
276 span_s: f64,
278 start: [f64; N],
279 end: [f64; N],
280 dense: Dense<N>,
281}
282
283#[derive(Clone, PartialEq)]
284enum Dense<const N: usize> {
285 Quartic {
287 delta: [f64; N],
288 r2: [f64; N],
289 r3: [f64; N],
290 r4: [f64; N],
291 },
292 Cubic {
294 delta: [f64; N],
295 r2: [f64; N],
296 r3: [f64; N],
297 },
298}
299
300impl<const N: usize> fmt::Debug for Step<N> {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 f.debug_struct("Step")
303 .field("start_s", &self.start_s)
304 .field("end_s", &self.end_s)
305 .field("start", &self.start)
306 .field("end", &self.end)
307 .finish_non_exhaustive()
308 }
309}
310
311fn hermite_terms<const N: usize>(
313 h: f64,
314 start: &[f64; N],
315 end: &[f64; N],
316 f0: &[f64; N],
317 f1: &[f64; N],
318) -> ([f64; N], [f64; N], [f64; N]) {
319 let mut delta = [0.0; N];
320 let mut r2 = [0.0; N];
321 let mut r3 = [0.0; N];
322 for i in 0..N {
323 delta[i] = end[i] - start[i];
324 r2[i] = h * f0[i] - delta[i];
325 r3[i] = delta[i] - h * f1[i] - r2[i];
326 }
327 (delta, r2, r3)
328}
329
330impl<const N: usize> Step<N> {
331 fn quartic(
332 start_s: f64,
333 end_s: f64,
334 start: [f64; N],
335 end: [f64; N],
336 k1: &[f64; N],
337 k7: &[f64; N],
338 r4: [f64; N],
339 ) -> Self {
340 let h = end_s - start_s;
341 let (delta, r2, r3) = hermite_terms(h, &start, &end, k1, k7);
342 Self {
343 start_s,
344 end_s,
345 span_s: h,
346 start,
347 end,
348 dense: Dense::Quartic { delta, r2, r3, r4 },
349 }
350 }
351
352 fn cubic(
353 start_s: f64,
354 end_s: f64,
355 start: [f64; N],
356 end: [f64; N],
357 f0: &[f64; N],
358 f1: &[f64; N],
359 ) -> Self {
360 let h = end_s - start_s;
361 let (delta, r2, r3) = hermite_terms(h, &start, &end, f0, f1);
362 Self {
363 start_s,
364 end_s,
365 span_s: h,
366 start,
367 end,
368 dense: Dense::Cubic { delta, r2, r3 },
369 }
370 }
371
372 fn truncated(&self, t_s: f64) -> Self {
375 let end = self.state_at(t_s);
376 Self {
377 end_s: t_s,
378 end,
379 ..self.clone()
380 }
381 }
382
383 #[must_use]
385 pub fn start_s(&self) -> f64 {
386 self.start_s
387 }
388
389 #[must_use]
391 pub fn end_s(&self) -> f64 {
392 self.end_s
393 }
394
395 #[must_use]
397 pub fn start(&self) -> &[f64; N] {
398 &self.start
399 }
400
401 #[must_use]
403 pub fn end(&self) -> &[f64; N] {
404 &self.end
405 }
406
407 #[must_use]
410 pub fn state_at(&self, t_s: f64) -> [f64; N] {
411 if t_s == self.end_s {
412 return self.end;
413 }
414 if self.span_s == 0.0 {
415 return self.start;
416 }
417 let theta = (t_s - self.start_s) / self.span_s;
418 let theta1 = 1.0 - theta;
419 let mut y = [0.0; N];
420 match &self.dense {
421 Dense::Quartic { delta, r2, r3, r4 } => {
422 for i in 0..N {
423 y[i] = self.start[i]
424 + theta * (delta[i] + theta1 * (r2[i] + theta * (r3[i] + theta1 * r4[i])));
425 }
426 }
427 Dense::Cubic { delta, r2, r3 } => {
428 for i in 0..N {
429 y[i] = self.start[i] + theta * (delta[i] + theta1 * (r2[i] + theta * r3[i]));
430 }
431 }
432 }
433 y
434 }
435}
436
437mod dp {
440 pub const C2: f64 = 1.0 / 5.0;
441 pub const C3: f64 = 3.0 / 10.0;
442 pub const C4: f64 = 4.0 / 5.0;
443 pub const C5: f64 = 8.0 / 9.0;
444 pub const A21: f64 = 1.0 / 5.0;
445 pub const A31: f64 = 3.0 / 40.0;
446 pub const A32: f64 = 9.0 / 40.0;
447 pub const A41: f64 = 44.0 / 45.0;
448 pub const A42: f64 = -56.0 / 15.0;
449 pub const A43: f64 = 32.0 / 9.0;
450 pub const A51: f64 = 19372.0 / 6561.0;
451 pub const A52: f64 = -25360.0 / 2187.0;
452 pub const A53: f64 = 64448.0 / 6561.0;
453 pub const A54: f64 = -212.0 / 729.0;
454 pub const A61: f64 = 9017.0 / 3168.0;
455 pub const A62: f64 = -355.0 / 33.0;
456 pub const A63: f64 = 46732.0 / 5247.0;
457 pub const A64: f64 = 49.0 / 176.0;
458 pub const A65: f64 = -5103.0 / 18656.0;
459 pub const B1: f64 = 35.0 / 384.0;
461 pub const B3: f64 = 500.0 / 1113.0;
462 pub const B4: f64 = 125.0 / 192.0;
463 pub const B5: f64 = -2187.0 / 6784.0;
464 pub const B6: f64 = 11.0 / 84.0;
465 pub const E1: f64 = 71.0 / 57600.0;
467 pub const E3: f64 = -71.0 / 16695.0;
468 pub const E4: f64 = 71.0 / 1920.0;
469 pub const E5: f64 = -17253.0 / 339200.0;
470 pub const E6: f64 = 22.0 / 525.0;
471 pub const E7: f64 = -1.0 / 40.0;
472 pub const D1: f64 = -12715105075.0 / 11282082432.0;
474 pub const D3: f64 = 87487479700.0 / 32700410799.0;
475 pub const D4: f64 = -10690763975.0 / 1880347072.0;
476 pub const D5: f64 = 701980252875.0 / 199316789632.0;
477 pub const D6: f64 = -1453857185.0 / 822651844.0;
478 pub const D7: f64 = 69997945.0 / 29380423.0;
479}
480
481const SAFETY: f64 = 0.9;
483const MAX_SHRINK: f64 = 5.0;
485const MIN_DIVISOR: f64 = 0.1;
487const BETA: f64 = 0.04;
489const EXPONENT: f64 = 0.2 - BETA * 0.75;
491const ROUNDING: f64 = 2.3e-16;
493
494fn negligible_step(t: f64, h: f64) -> bool {
497 0.1 * h <= t.abs().max(1.0) * ROUNDING
498}
499
500pub const DEFAULT_STEP_LIMIT: u64 = 1_000_000;
502
503#[derive(Debug, Clone)]
505pub struct Integrator<const N: usize> {
506 method: Method,
507 t_s: f64,
508 y: [f64; N],
509 next_step_s: f64,
511 previous_error: f64,
513 stats: Stats,
514 step_limit: u64,
515 g_start: Vec<f64>,
516 g_end: Vec<f64>,
517 fired: Vec<usize>,
518}
519
520impl<const N: usize> Integrator<N> {
521 pub fn new(method: Method, t0_s: f64, y0: [f64; N]) -> Result<Self, SettingsError> {
527 method.validate()?;
528 let mut integrator = Self {
529 method,
530 t_s: 0.0,
531 y: [0.0; N],
532 next_step_s: 0.0,
533 previous_error: 1e-4,
534 stats: Stats::default(),
535 step_limit: DEFAULT_STEP_LIMIT,
536 g_start: Vec::new(),
537 g_end: Vec::new(),
538 fired: Vec::new(),
539 };
540 integrator.reset(t0_s, y0)?;
541 Ok(integrator)
542 }
543
544 #[must_use]
547 pub fn with_step_limit(mut self, steps: u64) -> Self {
548 self.step_limit = steps;
549 self
550 }
551
552 pub fn set_step_limit(&mut self, steps: u64) {
554 self.step_limit = steps;
555 }
556
557 pub fn reset(&mut self, t_s: f64, y: [f64; N]) -> Result<(), SettingsError> {
564 if !t_s.is_finite() {
565 return Err(SettingsError::NotFinite {
566 what: "time",
567 value: t_s,
568 });
569 }
570 if let Some(bad) = y.iter().find(|v| !v.is_finite()) {
571 return Err(SettingsError::NotFinite {
572 what: "state",
573 value: *bad,
574 });
575 }
576 self.t_s = t_s;
577 self.y = y;
578 self.fired.clear();
579 Ok(())
580 }
581
582 #[must_use]
584 pub fn method(&self) -> Method {
585 self.method
586 }
587
588 #[must_use]
590 pub fn time_s(&self) -> f64 {
591 self.t_s
592 }
593
594 #[must_use]
596 pub fn state(&self) -> &[f64; N] {
597 &self.y
598 }
599
600 #[must_use]
602 pub fn stats(&self) -> Stats {
603 self.stats
604 }
605
606 #[must_use]
608 pub fn next_step_s(&self) -> Option<f64> {
609 (self.next_step_s > 0.0).then_some(self.next_step_s)
610 }
611
612 #[must_use]
615 pub fn fired_events(&self) -> &[usize] {
616 &self.fired
617 }
618
619 pub fn advance<S>(
639 &mut self,
640 system: &mut S,
641 t_stop_s: f64,
642 ) -> Result<Advance, IntegrationError<S::Error>>
643 where
644 S: OdeSystem<N> + ?Sized,
645 {
646 self.fired.clear();
647 if t_stop_s.is_nan() || t_stop_s < self.t_s {
648 return Err(IntegrationError::Backward {
649 t_s: self.t_s,
650 t_stop_s,
651 });
652 }
653 if negligible_step(self.t_s, t_stop_s - self.t_s) {
654 self.t_s = t_stop_s;
655 return Ok(Advance::Reached);
656 }
657 let weights = system.absolute_tolerance_weights();
658 for w in weights {
659 positive("absolute tolerance weight", w)?;
660 }
661 self.g_start.clear();
662 for index in 0..system.event_count() {
663 let g = system.event_value(index, self.t_s, &self.y);
664 if !g.is_finite() {
665 return Err(IntegrationError::EventNotFinite {
666 index,
667 t_s: self.t_s,
668 });
669 }
670 self.g_start.push(g);
671 }
672 match self.method {
673 Method::DormandPrince54(adaptive) => {
674 self.advance_dp(system, &adaptive, &weights, t_stop_s)
675 }
676 Method::Rk4 { step_s } => self.advance_rk4(system, step_s, t_stop_s),
677 }
678 }
679
680 fn evaluate<S: OdeSystem<N> + ?Sized>(
681 &mut self,
682 system: &mut S,
683 t_s: f64,
684 y: &[f64; N],
685 ) -> Result<[f64; N], IntegrationError<S::Error>> {
686 self.stats.evaluations += 1;
687 system
688 .derivative(t_s, y)
689 .map_err(|source| IntegrationError::Derivative { t_s, source })
690 }
691
692 fn check_step_limit<X>(&self) -> Result<(), IntegrationError<X>> {
693 if self.stats.accepted_steps + self.stats.rejected_steps >= self.step_limit {
694 Err(IntegrationError::StepLimit {
695 t_s: self.t_s,
696 limit: self.step_limit,
697 })
698 } else {
699 Ok(())
700 }
701 }
702
703 fn dp_stages<S: OdeSystem<N> + ?Sized>(
706 &mut self,
707 system: &mut S,
708 t: f64,
709 y: &[f64; N],
710 k1: &[f64; N],
711 h: f64,
712 t1: f64,
713 ) -> Result<DpStages<N>, IntegrationError<S::Error>> {
714 use dp::*;
715 let k2 = self.evaluate(system, t + C2 * h, &lin(y, h, &[(A21, k1)]))?;
716 let k3 = self.evaluate(system, t + C3 * h, &lin(y, h, &[(A31, k1), (A32, &k2)]))?;
717 let k4 = self.evaluate(
718 system,
719 t + C4 * h,
720 &lin(y, h, &[(A41, k1), (A42, &k2), (A43, &k3)]),
721 )?;
722 let k5 = self.evaluate(
723 system,
724 t + C5 * h,
725 &lin(y, h, &[(A51, k1), (A52, &k2), (A53, &k3), (A54, &k4)]),
726 )?;
727 let k6 = self.evaluate(
728 system,
729 t1,
730 &lin(
731 y,
732 h,
733 &[(A61, k1), (A62, &k2), (A63, &k3), (A64, &k4), (A65, &k5)],
734 ),
735 )?;
736 let y1 = lin(
737 y,
738 h,
739 &[(B1, k1), (B3, &k3), (B4, &k4), (B5, &k5), (B6, &k6)],
740 );
741 let k7 = self.evaluate(system, t1, &y1)?;
742 Ok(DpStages {
743 k3,
744 k4,
745 k5,
746 k6,
747 k7,
748 y1,
749 })
750 }
751
752 fn advance_dp<S: OdeSystem<N> + ?Sized>(
753 &mut self,
754 system: &mut S,
755 adaptive: &Adaptive,
756 weights: &[f64; N],
757 t_stop_s: f64,
758 ) -> Result<Advance, IntegrationError<S::Error>> {
759 use dp::*;
760 let rtol = adaptive.relative_tolerance;
761 let atol = adaptive.absolute_tolerance;
762 let max_step = adaptive.max_step_s.unwrap_or(f64::INFINITY);
763 let scale = |i: usize, a: f64, b: f64| weights[i] * atol + rtol * a.abs().max(b.abs());
764
765 let (t0, y0) = (self.t_s, self.y);
766 let mut k1 = self.evaluate(system, t0, &y0)?;
767 let mut h = if self.next_step_s > 0.0 {
768 self.next_step_s
769 } else if let Some(h0) = adaptive.initial_step_s {
770 h0
771 } else {
772 self.starting_step(system, &k1, weights, rtol, atol, t_stop_s, max_step)?
773 };
774 h = h.min(max_step);
775 let mut rejected = false;
776 let mut failure: Option<IntegrationError<S::Error>> = None;
779 loop {
780 self.check_step_limit()?;
781 let t = self.t_s;
782 let y = self.y;
783 if !h.is_finite() || h <= 0.0 {
784 return Err(failure.unwrap_or(IntegrationError::NotFinite { t_s: t }));
785 }
786 let proposed = h;
787 let last = t + 1.01 * h >= t_stop_s && t_stop_s - t <= max_step * (1.0 + 1e-9);
790 if last {
791 h = t_stop_s - t;
792 }
793 if negligible_step(t, h) {
794 if last {
795 self.t_s = t_stop_s;
797 return Ok(Advance::Reached);
798 }
799 return Err(failure.unwrap_or(IntegrationError::StepTooSmall { t_s: t, step_s: h }));
800 }
801 let t1 = if last { t_stop_s } else { t + h };
802 let stages = match self.dp_stages(system, t, &y, &k1, h, t1) {
803 Ok(stages) => stages,
804 Err(error) => {
805 self.stats.rejected_steps += 1;
807 failure = Some(error);
808 rejected = true;
809 h /= MAX_SHRINK;
810 continue;
811 }
812 };
813
814 let mut sum = 0.0;
815 for i in 0..N {
816 let e = h
817 * (E1 * k1[i]
818 + E3 * stages.k3[i]
819 + E4 * stages.k4[i]
820 + E5 * stages.k5[i]
821 + E6 * stages.k6[i]
822 + E7 * stages.k7[i]);
823 sum += (e / scale(i, y[i], stages.y1[i])).powi(2);
824 }
825 let error = (sum / N.max(1) as f64).sqrt();
826 if !error.is_finite() {
827 self.stats.rejected_steps += 1;
828 failure = Some(IntegrationError::NotFinite { t_s: t });
829 rejected = true;
830 h /= MAX_SHRINK;
831 continue;
832 }
833 let fac11 = error.powf(EXPONENT);
834 if error > 1.0 {
835 self.stats.rejected_steps += 1;
836 failure = None;
837 rejected = true;
838 h /= (fac11 / SAFETY).min(MAX_SHRINK);
839 continue;
840 }
841
842 let fac =
844 (fac11 / self.previous_error.powf(BETA) / SAFETY).clamp(MIN_DIVISOR, MAX_SHRINK);
845 let mut h_new = (h / fac).min(max_step);
846 if rejected {
847 h_new = h_new.min(h);
848 }
849 let carry = if last {
851 h_new.max(proposed.min(max_step))
852 } else {
853 h_new
854 };
855 let r4 = lin(
856 &[0.0; N],
857 h,
858 &[
859 (D1, &k1),
860 (D3, &stages.k3),
861 (D4, &stages.k4),
862 (D5, &stages.k5),
863 (D6, &stages.k6),
864 (D7, &stages.k7),
865 ],
866 );
867 let step = Step::quartic(t, t1, y, stages.y1, &k1, &stages.k7, r4);
868 let located = self.locate_event(system, &step)?;
869 self.previous_error = error.max(1e-4);
870 self.next_step_s = carry;
871 if let Some(outcome) = self.commit(system, step, located)? {
872 return Ok(outcome);
873 }
874 if last {
875 return Ok(Advance::Reached);
876 }
877 k1 = stages.k7;
878 rejected = false;
879 failure = None;
880 h = h_new;
881 }
882 }
883
884 fn commit<S: OdeSystem<N> + ?Sized>(
887 &mut self,
888 system: &mut S,
889 step: Step<N>,
890 event: Option<(usize, f64)>,
891 ) -> Result<Option<Advance>, IntegrationError<S::Error>> {
892 let Some((first, t_event)) = event else {
893 self.stats.accepted_steps += 1;
894 self.t_s = step.end_s;
895 self.y = step.end;
896 std::mem::swap(&mut self.g_start, &mut self.g_end);
897 return Ok(system
898 .accept_step(&step)
899 .is_break()
900 .then_some(Advance::Stopped));
901 };
902 let shortened = step.truncated(t_event);
903 self.fired.clear();
904 for index in 0..self.g_start.len() {
905 let g = system.event_value(index, t_event, &shortened.end);
906 if !g.is_finite() {
907 self.fired.clear();
908 return Err(IntegrationError::EventNotFinite {
909 index,
910 t_s: t_event,
911 });
912 }
913 if system
914 .event_direction(index)
915 .crosses(self.g_start[index], g)
916 {
917 self.fired.push(index);
918 }
919 }
920 if let Err(position) = self.fired.binary_search(&first) {
923 self.fired.insert(position, first);
924 }
925 self.stats.accepted_steps += 1;
926 self.t_s = t_event;
927 self.y = shortened.end;
928 let _ = system.accept_step(&shortened);
930 Ok(Some(Advance::Events))
931 }
932
933 #[expect(
936 clippy::too_many_arguments,
937 reason = "private helper sharing `advance_dp`'s validated inputs"
938 )]
939 fn starting_step<S: OdeSystem<N> + ?Sized>(
940 &mut self,
941 system: &mut S,
942 f0: &[f64; N],
943 weights: &[f64; N],
944 rtol: f64,
945 atol: f64,
946 t_stop_s: f64,
947 max_step: f64,
948 ) -> Result<f64, IntegrationError<S::Error>> {
949 let (mut dnf, mut dny) = (0.0, 0.0);
950 for i in 0..N {
951 let sk = weights[i] * atol + rtol * self.y[i].abs();
952 dnf += (f0[i] / sk).powi(2);
953 dny += (self.y[i] / sk).powi(2);
954 }
955 let mut h = if dnf <= 1e-10 || dny <= 1e-10 {
956 1e-6
957 } else {
958 (dny / dnf).sqrt() * 0.01
959 };
960 h = h.min(max_step).min(t_stop_s - self.t_s);
961 let y1 = lin(&self.y, h, &[(1.0, f0)]);
962 let f1 = self.evaluate(system, self.t_s + h, &y1)?;
963 let mut der2 = 0.0;
964 for i in 0..N {
965 let sk = weights[i] * atol + rtol * self.y[i].abs();
966 der2 += ((f1[i] - f0[i]) / sk).powi(2);
967 }
968 let der2 = der2.sqrt() / h;
969 let der12 = der2.abs().max(dnf.sqrt());
970 let h1 = if der12 <= 1e-15 {
971 (h * 1e-3).max(1e-6)
972 } else {
973 (0.01 / der12).powf(0.2)
974 };
975 let h = (100.0 * h).min(h1).min(max_step);
976 if h.is_finite() && h > 0.0 {
977 Ok(h)
978 } else {
979 Err(IntegrationError::NotFinite { t_s: self.t_s })
980 }
981 }
982
983 fn advance_rk4<S: OdeSystem<N> + ?Sized>(
984 &mut self,
985 system: &mut S,
986 step_s: f64,
987 t_stop_s: f64,
988 ) -> Result<Advance, IntegrationError<S::Error>> {
989 let (t0, y0) = (self.t_s, self.y);
990 let mut k1 = self.evaluate(system, t0, &y0)?;
991 loop {
992 self.check_step_limit()?;
993 let t = self.t_s;
994 let y = self.y;
995 let last = t + 1.01 * step_s >= t_stop_s;
996 let (h, t1) = if last {
997 (t_stop_s - t, t_stop_s)
998 } else {
999 (step_s, t + step_s)
1000 };
1001 if negligible_step(t, h) {
1002 if last {
1003 self.t_s = t_stop_s;
1004 return Ok(Advance::Reached);
1005 }
1006 return Err(IntegrationError::StepTooSmall { t_s: t, step_s: h });
1007 }
1008 let y1 = self.rk4_step(system, t, &y, &k1, h, t1)?;
1009 let f1 = self.evaluate(system, t1, &y1)?;
1010 if y1.iter().chain(&f1).any(|v| !v.is_finite()) {
1011 return Err(IntegrationError::NotFinite { t_s: t });
1012 }
1013 let step = Step::cubic(t, t1, y, y1, &k1, &f1);
1014 let located = self.locate_event(system, &step)?;
1015 if let Some(outcome) = self.commit(system, step, located)? {
1016 return Ok(outcome);
1017 }
1018 if last {
1019 return Ok(Advance::Reached);
1020 }
1021 k1 = f1;
1022 }
1023 }
1024
1025 fn rk4_step<S: OdeSystem<N> + ?Sized>(
1027 &mut self,
1028 system: &mut S,
1029 t: f64,
1030 y: &[f64; N],
1031 k1: &[f64; N],
1032 h: f64,
1033 t1: f64,
1034 ) -> Result<[f64; N], IntegrationError<S::Error>> {
1035 let k2 = self.evaluate(system, t + 0.5 * h, &lin(y, h, &[(0.5, k1)]))?;
1036 let k3 = self.evaluate(system, t + 0.5 * h, &lin(y, h, &[(0.5, &k2)]))?;
1037 let k4 = self.evaluate(system, t1, &lin(y, h, &[(1.0, &k3)]))?;
1038 Ok(lin(
1039 y,
1040 h,
1041 &[
1042 (1.0 / 6.0, k1),
1043 (2.0 / 6.0, &k2),
1044 (2.0 / 6.0, &k3),
1045 (1.0 / 6.0, &k4),
1046 ],
1047 ))
1048 }
1049
1050 fn locate_event<S: OdeSystem<N> + ?Sized>(
1053 &mut self,
1054 system: &mut S,
1055 step: &Step<N>,
1056 ) -> Result<Option<(usize, f64)>, IntegrationError<S::Error>> {
1057 self.g_end.clear();
1058 let mut earliest: Option<(usize, f64)> = None;
1059 for index in 0..self.g_start.len() {
1060 let g1 = system.event_value(index, step.end_s, &step.end);
1061 if !g1.is_finite() {
1062 return Err(IntegrationError::EventNotFinite {
1063 index,
1064 t_s: step.end_s,
1065 });
1066 }
1067 self.g_end.push(g1);
1068 let g0 = self.g_start[index];
1069 if !system.event_direction(index).crosses(g0, g1) {
1070 continue;
1071 }
1072 let root = find_root(
1073 |t| system.event_value(index, t, &step.state_at(t)),
1074 step.start_s,
1075 step.end_s,
1076 g0,
1077 g1,
1078 EVENT_TIME_RESOLUTION_S,
1079 )
1080 .map_err(|error| IntegrationError::EventNotFinite {
1081 index,
1082 t_s: match error {
1083 RootError::NotFinite { x } => x,
1084 _ => step.end_s,
1085 },
1086 })?;
1087 if earliest.is_none_or(|(_, t)| root < t) {
1088 earliest = Some((index, root));
1089 }
1090 }
1091 Ok(earliest)
1092 }
1093}
1094
1095struct DpStages<const N: usize> {
1097 k3: [f64; N],
1098 k4: [f64; N],
1099 k5: [f64; N],
1100 k6: [f64; N],
1101 k7: [f64; N],
1102 y1: [f64; N],
1103}
1104
1105#[inline]
1107fn lin<const N: usize>(y: &[f64; N], h: f64, terms: &[(f64, &[f64; N])]) -> [f64; N] {
1108 let mut out = *y;
1109 for (a, k) in terms {
1110 let ha = h * a;
1111 for i in 0..N {
1112 out[i] += ha * k[i];
1113 }
1114 }
1115 out
1116}
1117
1118#[cfg(test)]
1119mod tests;