differential_equations/dde/problem.rs
1//! Initial Value Problem Struct and Constructors for Delay Differential Equations (DDEs)
2
3use crate::{
4 dde::{DDE, numerical_method::DelayNumericalMethod, solve_dde},
5 error::Error,
6 interpolate::Interpolation,
7 solout::*,
8 solution::Solution,
9 traits::{Real, State},
10};
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<'a, const L: usize, T, Y, F, H>
79where
80 T: Real,
81 Y: State<T>,
82 F: DDE<L, T, Y>,
83 H: Fn(T) -> Y,
84{
85 // Initial Value Problem Fields
86 pub dde: &'a F, // DDE containing the Differential Equation and Optional Terminate Function.
87 pub t0: T, // Initial Time.
88 pub tf: T, // Final Time.
89 pub y0: Y, // Initial State Vector.
90 pub phi: H, // Initial History Function.
91}
92
93impl<'a, const L: usize, T, Y, F, H> DDEProblem<'a, L, T, Y, F, H>
94where
95 T: Real,
96 Y: State<T>,
97 F: DDE<L, T, Y>,
98 H: Fn(T) -> Y + Clone, // Require Clone for H if DDEProblem needs Clone
99{
100 /// Create a new Initial Value Problem for DDEs
101 ///
102 /// # Arguments
103 /// * `dde` - DDE containing the Differential Equation and Optional Terminate Function.
104 /// * `t0` - Initial Time.
105 /// * `tf` - Final Time.
106 /// * `y0` - Initial State Vector at `t0`.
107 /// * `phi` - Initial History Function `phi(t)` for `t <= t0`.
108 ///
109 /// # Returns
110 /// * DDEProblem Problem ready to be solved.
111 ///
112 pub fn new(dde: &'a F, t0: T, tf: T, y0: Y, phi: H) -> Self {
113 DDEProblem {
114 dde,
115 t0,
116 tf,
117 y0,
118 phi,
119 }
120 }
121
122 /// Solve the DDEProblem using a default solout, e.g. outputting solutions at calculated steps.
123 ///
124 /// # Returns
125 /// * `Result<Solution<T, Y>, Error<T, Y>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
126 ///
127 pub fn solve<S>(&self, solver: &mut S) -> Result<Solution<T, Y>, Error<T, Y>>
128 where
129 S: DelayNumericalMethod<L, T, Y, H> + Interpolation<T, Y>,
130 H: Clone, // phi needs to be cloneable for solve_dde
131 {
132 let mut default_solout = DefaultSolout::new(); // Default solout implementation
133 solve_dde(
134 solver,
135 self.dde,
136 self.t0,
137 self.tf,
138 &self.y0,
139 self.phi.clone(),
140 &mut default_solout,
141 )
142 }
143
144 /// Returns a DDEProblem DelayNumericalMethod with the provided solout function for outputting points.
145 ///
146 /// # Returns
147 /// * DDEProblem DelayNumericalMethod with the provided solout function ready for .solve() method.
148 ///
149 pub fn solout<O: Solout<T, Y>>(
150 &'a self,
151 solout: &'a mut O,
152 ) -> DDEProblemMutRefSoloutPair<'a, L, T, Y, F, H, O> {
153 DDEProblemMutRefSoloutPair::new(self, solout)
154 }
155
156 /// Uses the an Even Solout implementation to output evenly spaced points between the initial and final time.
157 /// Note that this does not include the solution of the calculated steps.
158 ///
159 /// # Arguments
160 /// * `dt` - Interval between each output point.
161 ///
162 /// # Returns
163 /// * DDEProblem DelayNumericalMethod with Even Solout function ready for .solve() method.
164 ///
165 pub fn even(&self, dt: T) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, EvenSolout<T>> {
166 let even_solout = EvenSolout::new(dt, self.t0, self.tf); // Even solout implementation
167 DDEProblemSoloutPair::new(self, even_solout)
168 }
169
170 /// Uses the Dense Output method to output n number of interpolation points between each step.
171 /// Note this includes the solution of the calculated steps.
172 ///
173 /// # Arguments
174 /// * `n` - Number of interpolation points between each step.
175 ///
176 /// # Returns
177 /// * DDEProblem DelayNumericalMethod with Dense Output function ready for .solve() method.
178 ///
179 pub fn dense(&self, n: usize) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, DenseSolout> {
180 let dense_solout = DenseSolout::new(n); // Dense solout implementation
181 DDEProblemSoloutPair::new(self, dense_solout)
182 }
183
184 /// Uses the provided time points for evaluation instead of the default method.
185 /// Note this does not include the solution of the calculated steps.
186 ///
187 /// # Arguments
188 /// * `points` - Custom output points.
189 ///
190 /// # Returns
191 /// * DDEProblem DelayNumericalMethod with Custom Time Evaluation function ready for .solve() method.
192 ///
193 pub fn t_eval(
194 &self,
195 points: impl AsRef<[T]>,
196 ) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, TEvalSolout<T>> {
197 let t_eval_solout = TEvalSolout::new(points, self.t0, self.tf); // Custom time evaluation solout implementation
198 DDEProblemSoloutPair::new(self, t_eval_solout)
199 }
200
201 /// Uses the CrossingSolout method to output points when a specific component crosses a threshold.
202 /// Note this does not include the solution of the calculated steps.
203 ///
204 /// # Arguments
205 /// * `component_idx` - Index of the component to monitor for crossing.
206 /// * `threshhold` - Value to cross.
207 /// * `direction` - Direction of crossing (positive or negative).
208 ///
209 /// # Returns
210 /// * DDEProblem DelayNumericalMethod with CrossingSolout function ready for .solve() method.
211 ///
212 pub fn crossing(
213 &self,
214 component_idx: usize,
215 threshhold: T,
216 direction: CrossingDirection,
217 ) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, CrossingSolout<T>> {
218 let crossing_solout =
219 CrossingSolout::new(component_idx, threshhold).with_direction(direction); // Crossing solout implementation
220 DDEProblemSoloutPair::new(self, crossing_solout)
221 }
222
223 /// Uses the HyperplaneCrossingSolout method to output points when a specific hyperplane is crossed.
224 /// Note this does not include the solution of the calculated steps.
225 ///
226 /// # Arguments
227 /// * `point` - Point on the hyperplane.
228 /// * `normal` - Normal vector of the hyperplane.
229 /// * `extractor` - Function to extract the component from the state vector.
230 /// * `direction` - Direction of crossing (positive or negative).
231 ///
232 /// # Returns
233 /// * DDEProblem DelayNumericalMethod with HyperplaneCrossingSolout function ready for .solve() method.
234 ///
235 pub fn hyperplane_crossing<Y1>(
236 &self,
237 point: Y1,
238 normal: Y1,
239 extractor: fn(&Y) -> Y1,
240 direction: CrossingDirection,
241 ) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, HyperplaneCrossingSolout<T, Y1, Y>>
242 where
243 Y1: State<T>,
244 {
245 let solout =
246 HyperplaneCrossingSolout::new(point, normal, extractor).with_direction(direction);
247
248 DDEProblemSoloutPair::new(self, solout)
249 }
250
251 /// Uses an `EventSolout` to detect zero crossings g(t,y)=0 of a user-defined event function.
252 pub fn event<E>(
253 &self,
254 event: &'a E,
255 ) -> DDEProblemSoloutPair<'_, L, T, Y, F, H, EventSolout<'_, T, Y, E>>
256 where
257 E: Event<T, Y>,
258 {
259 let solout = EventSolout::new(event, self.t0, self.tf);
260 DDEProblemSoloutPair::new(self, solout)
261 }
262}
263
264/// DDEProblemMutRefSoloutPair serves as an intermediate between the DDEProblem struct and a custom solout provided by the user.
265pub struct DDEProblemMutRefSoloutPair<'a, const L: usize, T, Y, F, H, O>
266where
267 T: Real,
268 Y: State<T>,
269 F: DDE<L, T, Y>,
270 H: Fn(T) -> Y,
271 O: Solout<T, Y>,
272{
273 pub problem: &'a DDEProblem<'a, L, T, Y, F, H>, // Reference to the DDEProblem struct
274 pub solout: &'a mut O, // Reference to the solout implementation
275}
276
277impl<'a, const L: usize, T, Y, F, H, O> DDEProblemMutRefSoloutPair<'a, L, T, Y, F, H, O>
278where
279 T: Real,
280 Y: State<T>,
281 F: DDE<L, T, Y>,
282 H: Fn(T) -> Y + Clone, // Require Clone for H
283 O: Solout<T, Y>,
284{
285 /// Create a new DDEProblemMutRefSoloutPair
286 ///
287 /// # Arguments
288 /// * `problem` - Reference to the DDEProblem struct
289 /// * `solout` - Mutable reference to the solout implementation
290 ///
291 pub fn new(problem: &'a DDEProblem<L, T, Y, F, H>, solout: &'a mut O) -> Self {
292 DDEProblemMutRefSoloutPair { problem, solout }
293 }
294
295 /// Solve the DDEProblem using the provided solout
296 ///
297 /// # Arguments
298 /// * `solver` - DelayNumericalMethod to use for solving the DDEProblem
299 ///
300 /// # Returns
301 /// * `Result<Solution<T, Y>, Error<T, Y>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
302 ///
303 pub fn solve<S>(&mut self, solver: &mut S) -> Result<Solution<T, Y>, Error<T, Y>>
304 where
305 S: DelayNumericalMethod<L, T, Y, H> + Interpolation<T, Y>,
306 {
307 solve_dde(
308 solver,
309 self.problem.dde,
310 self.problem.t0,
311 self.problem.tf,
312 &self.problem.y0,
313 self.problem.phi.clone(),
314 self.solout,
315 )
316 }
317}
318
319/// DDEProblemSoloutPair serves as an intermediate between the DDEProblem struct and solve_dde when a predefined solout is used.
320#[derive(Clone, Debug)]
321pub struct DDEProblemSoloutPair<'a, const L: usize, T, Y, F, H, O>
322where
323 T: Real,
324 Y: State<T>,
325 F: DDE<L, T, Y>,
326 H: Fn(T) -> Y,
327 O: Solout<T, Y>,
328{
329 pub problem: &'a DDEProblem<'a, L, T, Y, F, H>,
330 pub solout: O,
331}
332
333impl<'a, const L: usize, T, Y, F, H, O> DDEProblemSoloutPair<'a, L, T, Y, F, H, O>
334where
335 T: Real,
336 Y: State<T>,
337 F: DDE<L, T, Y>,
338 H: Fn(T) -> Y + Clone,
339 O: Solout<T, Y>,
340{
341 /// Create a new DDEProblemSoloutPair
342 ///
343 /// # Arguments
344 /// * `problem` - Reference to the DDEProblem struct
345 /// * `solout` - Solout implementation
346 ///
347 pub fn new(problem: &'a DDEProblem<L, T, Y, F, H>, solout: O) -> Self {
348 DDEProblemSoloutPair { problem, solout }
349 }
350
351 /// Solve the DDEProblem using the provided solout
352 ///
353 /// # Arguments
354 /// * `solver` - DelayNumericalMethod to use for solving the DDEProblem
355 ///
356 /// # Returns
357 /// * `Result<Solution<T, Y>, Error<T, Y>>` - `Ok(Solution)` if successful or interrupted by events, `Err(Error)` if errors or issues are encountered.
358 ///
359 pub fn solve<S>(mut self, solver: &mut S) -> Result<Solution<T, Y>, Error<T, Y>>
360 where
361 S: DelayNumericalMethod<L, T, Y, H> + Interpolation<T, Y>,
362 {
363 solve_dde(
364 solver,
365 self.problem.dde,
366 self.problem.t0,
367 self.problem.tf,
368 &self.problem.y0,
369 self.problem.phi.clone(),
370 &mut self.solout,
371 )
372 }
373
374 /// Wrap current solout with event detection while preserving original output strategy.
375 pub fn event<E>(
376 self,
377 event: &'a E,
378 ) -> DDEProblemSoloutPair<'a, L, T, Y, F, H, EventWrappedSolout<'a, T, Y, O, E>>
379 where
380 E: Event<T, Y>,
381 {
382 let wrapped = EventWrappedSolout::new(self.solout, event, self.problem.t0, self.problem.tf);
383 DDEProblemSoloutPair {
384 problem: self.problem,
385 solout: wrapped,
386 }
387 }
388}