Skip to main content

ordinary_diffeq/
problem.rs

1use nalgebra::SVector;
2use roots::{find_root_brent, SimpleConvergency};
3
4use super::callback::Callback;
5use super::controller::{Controller, PIController, TryStep};
6use super::integrator::Integrator;
7use super::ode::ODE;
8
9#[derive(Clone)]
10pub struct Problem<'a, const D: usize, S, P>
11where
12    S: Integrator<D>,
13{
14    ode: ODE<'a, D, P>,
15    integrator: S,
16    controller: PIController,
17    callbacks: Vec<Callback<'a, D, P>>,
18}
19
20impl<'a, const D: usize, S, P> Problem<'a, D, S, P>
21where
22    S: Integrator<D> + Copy,
23{
24    pub fn new(ode: ODE<'a, D, P>, integrator: S, controller: PIController) -> Self {
25        Problem {
26            ode,
27            integrator,
28            controller,
29            callbacks: Vec::new(),
30        }
31    }
32    pub fn solve(&mut self) -> Solution<S, D> {
33        let mut convergency = SimpleConvergency {
34            eps: 1e-12,
35            max_iter: 1000,
36        };
37        let mut times: Vec<f64> = vec![self.ode.t];
38        let mut states: Vec<SVector<f64, D>> = vec![self.ode.y];
39        let mut dense_coefficients: Vec<Vec<SVector<f64, D>>> = Vec::new();
40        while self.ode.t < self.ode.t_end {
41            if self.ode.t + self.controller.next_step_guess.extract() > self.ode.t_end {
42                // If the next step would go past the end, then just set it to the end
43                self.controller.next_step_guess = TryStep::NotYetAccepted(
44                    self.ode.t_end - self.ode.t,
45                );
46            }
47            let (mut new_y, mut curr_step, mut dense_option) = if S::ADAPTIVE {
48                // First, we try stepping with the "next step guess" to get the error
49                let (mut trial_y, mut err_option, mut dense_option) =
50                    self.integrator.step(&self.ode, self.controller.next_step_guess.extract());
51                let mut err = err_option.unwrap();
52                // Then we determine whether we need to reduce the step size or not
53                // If successful, we get the next step guess
54                let initial_guess = self.controller.next_step_guess.extract();
55                let mut next_step_guess = <PIController as Controller<D>>::determine_step(
56                    &mut self.controller,
57                    initial_guess,
58                    err,
59                );
60                while !next_step_guess.is_accepted() {
61                    // If that step isn't acceptable, then change the step until it is
62                    (trial_y, err_option, dense_option) =
63                        self.integrator.step(&self.ode, next_step_guess.extract());
64                    next_step_guess = <PIController as Controller<D>>::determine_step(
65                        &mut self.controller,
66                        next_step_guess.extract(),
67                        err,
68                    );
69                    err = err_option.unwrap();
70                }
71                // So at this point we can safely assume we have an accepted step
72                self.controller.next_step_guess = next_step_guess.reset().unwrap();
73                (trial_y, next_step_guess.extract(), dense_option)
74            } else {
75                // If fixed time step just step forward one step
76                let (trial_y, _, dense_option) = self.integrator.step(&self.ode, self.controller.next_step_guess.extract());
77                (trial_y, self.controller.next_step_guess.extract(), dense_option)
78            };
79            if !self.callbacks.is_empty() {
80                // Check for events occurring
81                for callback in &self.callbacks {
82                    if (callback.event)(self.ode.t, self.ode.y, &self.ode.params)
83                        * (callback.event)(self.ode.t + curr_step, new_y, &self.ode.params)
84                        < 0.0
85                    {
86                        // If the event crossed zero, then find the root
87                        let f = |test_t| {
88                            let test_y = self.integrator.step(&self.ode, test_t).0;
89                            (callback.event)(self.ode.t + test_t, test_y, &self.ode.params)
90                        };
91                        let root = find_root_brent(0.0, curr_step, &f, &mut convergency).unwrap();
92                        curr_step = root;
93                        (new_y, _, dense_option) = self.integrator.step(&self.ode, curr_step);
94                        (callback.effect)(&mut self.ode);
95                    }
96                }
97            }
98            self.ode.y = new_y;
99            self.ode.t += curr_step;
100            times.push(self.ode.t);
101            states.push(self.ode.y);
102            // TODO: Implement third order interpolation for non-dense algorithms
103            dense_coefficients.push(dense_option.unwrap());
104        }
105        Solution {
106            integrator: self.integrator,
107            times,
108            states,
109            dense: dense_coefficients,
110        }
111    }
112
113    pub fn with_callback(mut self, callback: Callback<'a, D, P>) -> Self {
114        self.callbacks.push(callback);
115        Self {
116            ode: self.ode,
117            integrator: self.integrator,
118            controller: self.controller,
119            callbacks: self.callbacks,
120        }
121    }
122}
123
124pub struct Solution<S, const D: usize>
125where
126    S: Integrator<D>,
127{
128    pub integrator: S,
129    pub times: Vec<f64>,
130    pub states: Vec<SVector<f64, D>>,
131    pub dense: Vec<Vec<SVector<f64, D>>>,
132}
133
134impl<S, const D: usize> Solution<S, D>
135where
136    S: Integrator<D>,
137{
138    pub fn interpolate(&self, t: f64) -> SVector<f64, D> {
139        // First check that the t is within bounds
140        let last = self.times.last().unwrap();
141        let first = self.times.first().unwrap();
142
143        // TODO: Improve these errors
144        let mut times = self.times.clone();
145        if *first > *last {
146            times.reverse();
147        }
148        if t < *first || t > *last {
149            panic!();
150        }
151
152        // Then find the two t values closest to the desired t
153        match times.binary_search_by(|x| x.total_cmp(&t)) {
154            Ok(index) => self.states[index],
155            Err(end_index) => {
156                // Then send that to the integrator
157                let t_start = times[end_index - 1];
158                let t_end = times[end_index];
159                self.integrator
160                    .interpolate(t_start, t_end, &self.dense[end_index - 1], t)
161            }
162        }
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::callback::stop;
170    use crate::controller::PIController;
171    use crate::integrator::dormand_prince::DormandPrince45;
172    use approx::assert_relative_eq;
173    use nalgebra::Vector3;
174
175    #[test]
176    fn test_problem() {
177        type Params = ();
178        fn derivative(_t: f64, y: Vector3<f64>, _p: &Params) -> Vector3<f64> {
179            y
180        }
181        let y0 = Vector3::new(1.0, 1.0, 1.0);
182
183        let ode = ODE::new(&derivative, 0.0, 1.0, y0, ());
184        let dp45 = DormandPrince45::new().a_tol(1e-12).r_tol(1e-5);
185        let controller = PIController::default();
186
187        let mut problem = Problem::new(ode, dp45, controller);
188
189        let solution = problem.solve();
190        solution
191            .times
192            .iter()
193            .zip(solution.states.iter())
194            .for_each(|(time, state)| {
195                assert_relative_eq!(state[0], time.exp(), max_relative = 1e-2);
196            })
197    }
198
199    #[test]
200    fn test_with_callback() {
201        type Params = ();
202        fn derivative(_t: f64, y: Vector3<f64>, _p: &Params) -> Vector3<f64> {
203            y
204        }
205        let y0 = Vector3::new(1.0, 1.0, 1.0);
206
207        let ode = ODE::new(&derivative, 0.0, 10.0, y0, ());
208        let dp45 = DormandPrince45::new().a_tol(1e-12).r_tol(1e-5);
209        let controller = PIController::default();
210
211        let value_too_high = Callback {
212            event: &|_: f64, y: SVector<f64, 3>, _: &Params| 10.0 - y[0],
213            effect: &stop,
214        };
215
216        let mut problem = Problem::new(ode, dp45, controller).with_callback(value_too_high);
217        let solution = problem.solve();
218
219        assert_relative_eq!(
220            solution.states.last().unwrap()[0],
221            10.0,
222            max_relative = 1e-11
223        );
224    }
225
226    #[test]
227    fn test_with_interpolation() {
228        type Params = ();
229        fn derivative(_t: f64, y: Vector3<f64>, _p: &Params) -> Vector3<f64> {
230            y
231        }
232        let y0 = Vector3::new(1.0, 1.0, 1.0);
233
234        let ode = ODE::new(&derivative, 0.0, 10.0, y0, ());
235        let dp45 = DormandPrince45::new().a_tol(1e-12).r_tol(1e-6);
236        let controller = PIController::default();
237
238        let mut problem = Problem::new(ode, dp45, controller);
239        let solution = problem.solve();
240
241        assert_relative_eq!(
242            solution.interpolate(8.8)[0],
243            8.8_f64.exp(),
244            max_relative = 1e-6
245        );
246    }
247}