scivex_optim/ode/
euler.rs1use scivex_core::Float;
7
8use super::{OdeOptions, OdeResult};
9use crate::error::Result;
10
11pub fn euler<T, F>(f: F, t_span: [T; 2], y0: &[T], options: &OdeOptions<T>) -> Result<OdeResult<T>>
30where
31 T: Float,
32 F: Fn(T, &[T]) -> Vec<T>,
33{
34 let t0 = t_span[0];
35 let tf = t_span[1];
36 let h = options
37 .first_step
38 .unwrap_or_else(|| (tf - t0) / T::from_f64(100.0));
39 let n = y0.len();
40
41 let mut t = t0;
42 let mut y = y0.to_vec();
43 let mut t_values = vec![t];
44 let mut y_values = vec![y.clone()];
45 let mut n_evals: usize = 0;
46
47 while t < tf {
48 let step = if t + h > tf { tf - t } else { h };
50
51 let dy = f(t, &y);
52 n_evals += 1;
53
54 for i in 0..n {
55 y[i] += step * dy[i];
56 }
57 t += step;
58
59 t_values.push(t);
60 y_values.push(y.clone());
61
62 if let Some(ref event_fn) = options.event_fn {
64 let val = event_fn(t, &y);
65 if val.abs() < T::from_f64(1e-12)
66 || (t_values.len() > 1 && {
67 let prev_y = &y_values[y_values.len() - 2];
68 let prev_t = t_values[t_values.len() - 2];
69 let prev_val = event_fn(prev_t, prev_y);
70 (prev_val > T::zero()) != (val > T::zero())
71 })
72 {
73 break;
74 }
75 }
76 }
77
78 let n_steps = y_values.len() - 1;
79 Ok(OdeResult {
80 t: t_values,
81 y: y_values,
82 n_evals,
83 n_steps,
84 success: true,
85 })
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_euler_exponential_decay() {
94 let result = euler(
96 |_t: f64, y: &[f64]| vec![-y[0]],
97 [0.0, 1.0],
98 &[1.0],
99 &OdeOptions::default(),
100 )
101 .unwrap();
102
103 let y_final = result.y.last().unwrap()[0];
104 let expected = (-1.0_f64).exp();
105 assert!(
107 (y_final - expected).abs() < 0.02,
108 "y_final={y_final}, expected={expected}"
109 );
110 }
111
112 #[test]
113 fn test_euler_linear() {
114 let result = euler(
116 |_t: f64, _y: &[f64]| vec![1.0],
117 [0.0, 2.0],
118 &[0.0],
119 &OdeOptions::default(),
120 )
121 .unwrap();
122
123 let y_final = result.y.last().unwrap()[0];
124 assert!(
125 (y_final - 2.0).abs() < 1e-10,
126 "y_final={y_final}, expected=2.0"
127 );
128 }
129
130 #[test]
131 fn test_euler_system() {
132 let opts = OdeOptions {
135 first_step: Some(0.001),
136 ..OdeOptions::default()
137 };
138 let result = euler(
139 |_t: f64, y: &[f64]| vec![y[1], -y[0]],
140 [0.0, 1.0],
141 &[1.0, 0.0],
142 &opts,
143 )
144 .unwrap();
145
146 let y_final = &result.y.last().unwrap();
147 let expected_y0 = 1.0_f64.cos();
148 let expected_y1 = -(1.0_f64.sin());
149 assert!(
150 (y_final[0] - expected_y0).abs() < 0.01,
151 "y0={}, expected={}",
152 y_final[0],
153 expected_y0
154 );
155 assert!(
156 (y_final[1] - expected_y1).abs() < 0.01,
157 "y1={}, expected={}",
158 y_final[1],
159 expected_y1
160 );
161 }
162
163 #[test]
164 fn test_euler_stores_trajectory() {
165 let result = euler(
166 |_t: f64, _y: &[f64]| vec![1.0],
167 [0.0, 1.0],
168 &[0.0],
169 &OdeOptions::default(),
170 )
171 .unwrap();
172
173 assert!(result.t.len() > 2);
174 assert_eq!(result.t.len(), result.y.len());
175 assert!((result.t[0] - 0.0).abs() < 1e-12);
176 assert!((*result.t.last().unwrap() - 1.0).abs() < 1e-12);
177 }
178}