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