outram_foam_basic_lib/ode/
euler.rs1use super::{
23 adaptive_step, integrate_interval, normalize_error, OdeError, OdeSolverConfig, OdeSystem,
24};
25
26pub struct Euler {
29 pub config: OdeSolverConfig,
30 dydx0: Vec<f64>,
32 y_temp: Vec<f64>,
33 err: Vec<f64>,
34}
35
36impl Euler {
37 pub fn new(n: usize, abs_tol: f64, rel_tol: f64) -> Self {
38 let mut cfg = OdeSolverConfig::default();
39 cfg.abs_tol = abs_tol;
40 cfg.rel_tol = rel_tol;
41 Self {
42 config: cfg,
43 dydx0: vec![0.0; n],
44 y_temp: vec![0.0; n],
45 err: vec![0.0; n],
46 }
47 }
48
49 pub fn solve_step(
52 &mut self,
53 ode: &dyn OdeSystem,
54 x: &mut f64,
55 y: &mut Vec<f64>,
56 dx_try: &mut f64,
57 ) -> Result<(), OdeError> {
58 let cfg = self.config.clone();
59 let err_buf = &mut self.err;
60 let abs_tol = cfg.abs_tol;
61 let rel_tol = cfg.rel_tol;
62
63 adaptive_step(
64 &cfg,
65 |_x0, y0, dydx0, dx, y_out| {
66 for i in 0..y0.len() {
67 err_buf[i] = dx * dydx0[i];
68 y_out[i] = y0[i] + err_buf[i];
69 }
70 normalize_error(y0, y_out, err_buf, abs_tol, rel_tol)
71 },
72 ode,
73 x,
74 y,
75 &mut self.dydx0,
76 &mut self.y_temp,
77 dx_try,
78 )
79 }
80
81 pub fn integrate(
83 &mut self,
84 ode: &dyn OdeSystem,
85 x_start: f64,
86 x_end: f64,
87 y: &mut Vec<f64>,
88 dx_est: &mut f64,
89 ) -> Result<(), OdeError> {
90 let cfg = self.config.clone();
91 integrate_interval(
92 &cfg,
93 &mut |x, y, dx| self.solve_step(ode, x, y, dx),
94 x_start,
95 x_end,
96 y,
97 dx_est,
98 )
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 struct DecayOde;
107 impl OdeSystem for DecayOde {
108 fn n_eqns(&self) -> usize {
109 1
110 }
111 fn derivatives(&self, _x: f64, y: &[f64], dydx: &mut Vec<f64>) {
112 dydx[0] = -y[0];
113 }
114 }
115
116 struct RotationOde;
117 impl OdeSystem for RotationOde {
118 fn n_eqns(&self) -> usize {
119 2
120 }
121 fn derivatives(&self, _x: f64, y: &[f64], dydx: &mut Vec<f64>) {
122 dydx[0] = -y[1];
123 dydx[1] = y[0];
124 }
125 }
126
127 #[test]
128 fn euler_exponential_decay() {
129 let ode = DecayOde;
132 let mut solver = Euler::new(1, 1e-3, 1e-2);
133 let mut y = vec![1.0_f64];
134 let mut dx = 0.1;
135 solver.integrate(&ode, 0.0, 1.0, &mut y, &mut dx).unwrap();
136 let expected = (-1.0_f64).exp();
137 assert!(
138 (y[0] - expected).abs() < 1e-2,
139 "y={}, expected {}",
140 y[0],
141 expected
142 );
143 }
144
145 #[test]
146 fn euler_rotation() {
147 let ode = RotationOde;
149 let mut solver = Euler::new(2, 1e-3, 1e-2);
150 let mut y = vec![1.0_f64, 0.0];
151 let mut dx = 0.1;
152 solver
153 .integrate(&ode, 0.0, std::f64::consts::PI / 2.0, &mut y, &mut dx)
154 .unwrap();
155 assert!(y[0].abs() < 5e-2, "y0={}", y[0]);
156 assert!((y[1] - 1.0).abs() < 5e-2, "y1={}", y[1]);
157 }
158}