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}