differential_equations/dde/
problem.rs

1//! Initial Value Problem Struct and Constructors for Delay Differential Equations (DDEs)
2
3use crate::{
4    Error, Solution,
5    dde::{DDE, numerical_method::DelayNumericalMethod, solve_dde},
6    interpolate::Interpolation,
7    solout::*,
8    traits::{CallBackData, Real, State},
9};
10use std::marker::PhantomData;
11
12/// Initial Value Problem for Delay Differential Equations (DDEs)
13///
14/// The Initial Value Problem takes the form:
15/// y'(t) = f(t, y(t), y(t-tau1), ...),  t ∈ [t0, tf],  y(t) = phi(t) for t <= t0
16///
17/// # Overview
18///
19/// The DDEProblem struct provides a simple interface for solving DDEs:
20///
21/// # Example
22///
23/// ```
24/// use differential_equations::prelude::*;
25/// use nalgebra::{Vector2, vector};
26///
27/// let mut rkf45 = ExplicitRungeKutta::rkf45()
28///    .rtol(1e-6)
29///    .atol(1e-6);
30///
31/// let t0 = 0.0;
32/// let tf = 10.0;
33/// let y0 = vector![1.0, 0.0];
34/// let phi = |t| {
35///     y0
36/// };
37/// struct Example;
38/// impl DDE<1, f64, Vector2<f64>> for Example {
39///     fn diff(&self, t: f64, y: &Vector2<f64>, yd: &[Vector2<f64>; 1], dydt: &mut Vector2<f64>) {
40///        dydt[0] = y[1] + yd[0][0];
41///        dydt[1] = -y[0] + yd[0][1];
42///     }
43///
44///     fn lags(&self, _t: f64, _y: &Vector2<f64>, lags: &mut [f64; 1]) {
45///        lags[0] = 1.0; // Fixed delay of 1.0
46///     }
47/// }
48/// let solution = DDEProblem::new(Example, t0, tf, y0, phi).solve(&mut rkf45).unwrap();
49///
50/// let (t, y) = solution.last().unwrap();
51/// println!("Solution: ({}, {})", t, y);
52/// ```
53///
54/// # Fields
55///
56/// * `dde` - DDE implementing the differential equation
57/// * `t0` - Initial time
58/// * `tf` - Final time
59/// * `y0` - Initial state vector at `t0`
60/// * `phi` - Initial history function `phi(t)` for `t <= t0`
61///
62/// # Basic Usage
63///
64/// * `new(dde, t0, tf, y0, phi)` - Create a new DDEProblem
65/// * `solve(&mut solver)` - Solve using default output (solver step points)
66///
67/// # Output Control Methods
68///
69/// These methods configure how solution points are generated and returned:
70///
71/// * `even(dt)` - Generate evenly spaced output points with interval `dt`
72/// * `dense(n)` - Include `n` interpolated points between each solver step
73/// * `t_eval(points)` - Evaluate solution at specific time points
74/// * `solout(custom_solout)` - Use a custom output handler
75///
76/// Each returns a solver configuration that can be executed with `.solve(&mut solver)`.
77#[derive(Clone, Debug)] // Note: Clone requires F and H to be Clone
78pub struct DDEProblem<const L: usize, T, V, D, F, H>
79where
80    T: Real,
81    V: State<T>,
82    D: CallBackData,
83    F: DDE<L, T, V, D>,
84    H: Fn(T) -> V,
85{
86    // Initial Value Problem Fields
87    pub dde: F, // DDE containing the Differential Equation and Optional Terminate Function.
88    pub t0: T,  // Initial Time.
89    pub tf: T,  // Final Time.
90    pub y0: V,  // Initial State Vector.
91    pub phi: H, // Initial History Function.
92
93    // Phantom Data for Users event output
94    _event_output_type: PhantomData<D>,
95}
96
97impl<const L: usize, T, V, D, F, H> DDEProblem<L, T, V, D, F, H>
98where
99    T: Real,
100    V: State<T>,
101    D: CallBackData,
102    F: DDE<L, T, V, D>,
103    H: Fn(T) -> V + Clone, // Require Clone for H if DDEProblem needs Clone
104{
105    /// Create a new Initial Value Problem for DDEs
106    ///
107    /// # Arguments
108    /// * `dde`  - DDE containing the Differential Equation and Optional Terminate Function.
109    /// * `t0`   - Initial Time.
110    /// * `tf`   - Final Time.
111    /// * `y0`   - Initial State Vector at `t0`.
112    /// * `phi`  - Initial History Function `phi(t)` for `t <= t0`.
113    ///
114    /// # Returns
115    /// * DDEProblem Problem ready to be solved.
116    ///
117    pub fn new(dde: F, t0: T, tf: T, y0: V, phi: H) -> Self {
118        DDEProblem {
119            dde,
120            t0,
121            tf,
122            y0,
123            phi,
124            _event_output_type: PhantomData,
125        }
126    }
127
128    /// Solve the DDEProblem using a default solout, e.g. outputting solutions at calculated steps.
129    ///
130    /// # Returns
131    /// * `Result<Solution<T, V, D>, Error<T, V>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
132    ///
133    pub fn solve<S>(&self, solver: &mut S) -> Result<Solution<T, V, D>, Error<T, V>>
134    where
135        S: DelayNumericalMethod<L, T, V, H, D> + Interpolation<T, V>,
136        H: Clone, // phi needs to be cloneable for solve_dde
137    {
138        let mut default_solout = DefaultSolout::new(); // Default solout implementation
139        solve_dde(
140            solver,
141            &self.dde,
142            self.t0,
143            self.tf,
144            &self.y0,
145            self.phi.clone(), // Clone phi here
146            &mut default_solout,
147        )
148    }
149
150    /// Returns a DDEProblem DelayNumericalMethod with the provided solout function for outputting points.
151    ///
152    /// # Returns
153    /// * DDEProblem DelayNumericalMethod with the provided solout function ready for .solve() method.
154    ///
155    pub fn solout<'a, O: Solout<T, V, D>>(
156        &'a self,
157        solout: &'a mut O,
158    ) -> DDEProblemMutRefSoloutPair<'a, L, T, V, D, F, H, O> {
159        DDEProblemMutRefSoloutPair::new(self, solout)
160    }
161
162    /// Uses the an Even Solout implementation to output evenly spaced points between the initial and final time.
163    /// Note that this does not include the solution of the calculated steps.
164    ///
165    /// # Arguments
166    /// * `dt` - Interval between each output point.
167    ///
168    /// # Returns
169    /// * DDEProblem DelayNumericalMethod with Even Solout function ready for .solve() method.
170    ///
171    pub fn even(&self, dt: T) -> DDEProblemSoloutPair<'_, L, T, V, D, F, H, EvenSolout<T>> {
172        let even_solout = EvenSolout::new(dt, self.t0, self.tf); // Even solout implementation
173        DDEProblemSoloutPair::new(self, even_solout)
174    }
175
176    /// Uses the Dense Output method to output n number of interpolation points between each step.
177    /// Note this includes the solution of the calculated steps.
178    ///
179    /// # Arguments
180    /// * `n` - Number of interpolation points between each step.
181    ///
182    /// # Returns
183    /// * DDEProblem DelayNumericalMethod with Dense Output function ready for .solve() method.
184    ///
185    pub fn dense(&self, n: usize) -> DDEProblemSoloutPair<'_, L, T, V, D, F, H, DenseSolout> {
186        let dense_solout = DenseSolout::new(n); // Dense solout implementation
187        DDEProblemSoloutPair::new(self, dense_solout)
188    }
189
190    /// Uses the provided time points for evaluation instead of the default method.
191    /// Note this does not include the solution of the calculated steps.
192    ///
193    /// # Arguments
194    /// * `points` - Custom output points.
195    ///
196    /// # Returns
197    /// * DDEProblem DelayNumericalMethod with Custom Time Evaluation function ready for .solve() method.
198    ///
199    pub fn t_eval(
200        &self,
201        points: Vec<T>,
202    ) -> DDEProblemSoloutPair<'_, L, T, V, D, F, H, TEvalSolout<T>> {
203        let t_eval_solout = TEvalSolout::new(points, self.t0, self.tf); // Custom time evaluation solout implementation
204        DDEProblemSoloutPair::new(self, t_eval_solout)
205    }
206
207    /// Uses the CrossingSolout method to output points when a specific component crosses a threshold.
208    /// Note this does not include the solution of the calculated steps.
209    ///
210    /// # Arguments
211    /// * `component_idx` - Index of the component to monitor for crossing.
212    /// * `threshhold` - Value to cross.
213    /// * `direction` - Direction of crossing (positive or negative).
214    ///
215    /// # Returns
216    /// * DDEProblem DelayNumericalMethod with CrossingSolout function ready for .solve() method.
217    ///
218    pub fn crossing(
219        &self,
220        component_idx: usize,
221        threshhold: T,
222        direction: CrossingDirection,
223    ) -> DDEProblemSoloutPair<'_, L, T, V, D, F, H, CrossingSolout<T>> {
224        let crossing_solout =
225            CrossingSolout::new(component_idx, threshhold).with_direction(direction); // Crossing solout implementation
226        DDEProblemSoloutPair::new(self, crossing_solout)
227    }
228
229    /// Uses the HyperplaneCrossingSolout method to output points when a specific hyperplane is crossed.
230    /// Note this does not include the solution of the calculated steps.
231    ///
232    /// # Arguments
233    /// * `point` - Point on the hyperplane.
234    /// * `normal` - Normal vector of the hyperplane.
235    /// * `extractor` - Function to extract the component from the state vector.
236    /// * `direction` - Direction of crossing (positive or negative).
237    ///
238    /// # Returns
239    /// * DDEProblem DelayNumericalMethod with HyperplaneCrossingSolout function ready for .solve() method.
240    ///
241    pub fn hyperplane_crossing<V1>(
242        &self,
243        point: V1,
244        normal: V1,
245        extractor: fn(&V) -> V1,
246        direction: CrossingDirection,
247    ) -> DDEProblemSoloutPair<'_, L, T, V, D, F, H, HyperplaneCrossingSolout<T, V1, V>>
248    where
249        V1: State<T>,
250    {
251        let solout =
252            HyperplaneCrossingSolout::new(point, normal, extractor).with_direction(direction);
253
254        DDEProblemSoloutPair::new(self, solout)
255    }
256}
257
258/// DDEProblemMutRefSoloutPair serves as an intermediate between the DDEProblem struct and a custom solout provided by the user.
259pub struct DDEProblemMutRefSoloutPair<'a, const L: usize, T, V, D, F, H, O>
260where
261    T: Real,
262    V: State<T>,
263    D: CallBackData,
264    F: DDE<L, T, V, D>,
265    H: Fn(T) -> V,
266    O: Solout<T, V, D>,
267{
268    pub problem: &'a DDEProblem<L, T, V, D, F, H>, // Reference to the DDEProblem struct
269    pub solout: &'a mut O,                         // Reference to the solout implementation
270}
271
272impl<'a, const L: usize, T, V, D, F, H, O> DDEProblemMutRefSoloutPair<'a, L, T, V, D, F, H, O>
273where
274    T: Real,
275    V: State<T>,
276    D: CallBackData,
277    F: DDE<L, T, V, D>,
278    H: Fn(T) -> V + Clone, // Require Clone for H
279    O: Solout<T, V, D>,
280{
281    /// Create a new DDEProblemMutRefSoloutPair
282    ///
283    /// # Arguments
284    /// * `problem` - Reference to the DDEProblem struct
285    /// * `solout` - Mutable reference to the solout implementation
286    ///
287    pub fn new(problem: &'a DDEProblem<L, T, V, D, F, H>, solout: &'a mut O) -> Self {
288        DDEProblemMutRefSoloutPair { problem, solout }
289    }
290
291    /// Solve the DDEProblem using the provided solout
292    ///
293    /// # Arguments
294    /// * `solver` - DelayNumericalMethod to use for solving the DDEProblem
295    ///
296    /// # Returns
297    /// * `Result<Solution<T, V, D>, Error<T, V>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
298    ///
299    pub fn solve<S>(&mut self, solver: &mut S) -> Result<Solution<T, V, D>, Error<T, V>>
300    where
301        S: DelayNumericalMethod<L, T, V, H, D> + Interpolation<T, V>,
302    {
303        solve_dde(
304            solver,
305            &self.problem.dde,
306            self.problem.t0,
307            self.problem.tf,
308            &self.problem.y0,
309            self.problem.phi.clone(), // Clone phi here
310            self.solout,
311        )
312    }
313}
314
315/// DDEProblemSoloutPair serves as an intermediate between the DDEProblem struct and solve_dde when a predefined solout is used.
316#[derive(Clone, Debug)]
317pub struct DDEProblemSoloutPair<'a, const L: usize, T, V, D, F, H, O>
318where
319    T: Real,
320    V: State<T>,
321    D: CallBackData,
322    F: DDE<L, T, V, D>,
323    H: Fn(T) -> V,
324    O: Solout<T, V, D>,
325{
326    pub problem: &'a DDEProblem<L, T, V, D, F, H>,
327    pub solout: O,
328}
329
330impl<'a, const L: usize, T, V, D, F, H, O> DDEProblemSoloutPair<'a, L, T, V, D, F, H, O>
331where
332    T: Real,
333    V: State<T>,
334    D: CallBackData,
335    F: DDE<L, T, V, D>,
336    H: Fn(T) -> V + Clone,
337    O: Solout<T, V, D>,
338{
339    /// Create a new DDEProblemSoloutPair
340    ///
341    /// # Arguments
342    /// * `problem` - Reference to the DDEProblem struct
343    /// * `solout` - Solout implementation
344    ///
345    pub fn new(problem: &'a DDEProblem<L, T, V, D, F, H>, solout: O) -> Self {
346        DDEProblemSoloutPair { problem, solout }
347    }
348
349    /// Solve the DDEProblem using the provided solout
350    ///
351    /// # Arguments
352    /// * `solver` - DelayNumericalMethod to use for solving the DDEProblem
353    ///
354    /// # Returns
355    /// * `Result<Solution<T, V, D>, Error<T, V>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
356    ///
357    pub fn solve<S>(mut self, solver: &mut S) -> Result<Solution<T, V, D>, Error<T, V>>
358    where
359        S: DelayNumericalMethod<L, T, V, H, D> + Interpolation<T, V>,
360    {
361        solve_dde(
362            solver,
363            &self.problem.dde,
364            self.problem.t0,
365            self.problem.tf,
366            &self.problem.y0,
367            self.problem.phi.clone(), // Clone phi here
368            &mut self.solout,
369        )
370    }
371}