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}