use super::{adaptive_step, integrate_interval, normalize_error, OdeError, OdeSolverConfig, OdeSystem};
#[derive(Debug, Clone)]
pub struct Euler {
pub config: OdeSolverConfig,
dydx0: Vec<f64>,
y_temp: Vec<f64>,
err: Vec<f64>,
}
impl Euler {
pub fn new(n: usize, abs_tol: f64, rel_tol: f64) -> Self {
let mut cfg = OdeSolverConfig::default();
cfg.abs_tol = abs_tol;
cfg.rel_tol = rel_tol;
Self {
config: cfg,
dydx0: vec![0.0; n],
y_temp: vec![0.0; n],
err: vec![0.0; n],
}
}
pub fn solve_step<Sys: OdeSystem + ?Sized>(
&mut self,
ode: &Sys,
x: &mut f64,
y: &mut Vec<f64>,
dx_try: &mut f64,
) -> Result<(), OdeError> {
let cfg = self.config.clone();
let err_buf = &mut self.err;
let abs_tol = cfg.abs_tol;
let rel_tol = cfg.rel_tol;
adaptive_step(
&cfg,
|_x0, y0, dydx0, dx, y_out| {
for i in 0..y0.len() {
err_buf[i] = dx * dydx0[i];
y_out[i] = y0[i] + err_buf[i];
}
normalize_error(y0, y_out, err_buf, abs_tol, rel_tol)
},
ode,
x,
y,
&mut self.dydx0,
&mut self.y_temp,
dx_try,
)
}
pub fn integrate<Sys: OdeSystem + ?Sized>(
&mut self,
ode: &Sys,
x_start: f64,
x_end: f64,
y: &mut Vec<f64>,
dx_est: &mut f64,
) -> Result<(), OdeError> {
let cfg = self.config.clone();
integrate_interval(
&cfg,
&mut |x, y, dx| self.solve_step(ode, x, y, dx),
x_start,
x_end,
y,
dx_est,
)
}
}
#[cfg(test)]
mod tests {
use petir::mathf::RealMath;
use super::*;
struct DecayOde;
impl OdeSystem for DecayOde {
fn n_eqns(&self) -> usize {
1
}
fn derivatives(&self, _x: f64, y: &[f64], dydx: &mut Vec<f64>) {
dydx[0] = -y[0];
}
}
struct RotationOde;
impl OdeSystem for RotationOde {
fn n_eqns(&self) -> usize {
2
}
fn derivatives(&self, _x: f64, y: &[f64], dydx: &mut Vec<f64>) {
dydx[0] = -y[1];
dydx[1] = y[0];
}
}
#[test]
fn euler_exponential_decay() {
let ode = DecayOde;
let mut solver = Euler::new(1, 1e-3, 1e-2);
let mut y = vec![1.0_f64];
let mut dx = 0.1;
solver.integrate(&ode, 0.0, 1.0, &mut y, &mut dx).unwrap();
let expected = (-1.0_f64).r_exp();
assert!(
(y[0] - expected).abs() < 1e-2,
"y={}, expected {}",
y[0],
expected
);
}
#[test]
fn euler_rotation() {
let ode = RotationOde;
let mut solver = Euler::new(2, 1e-3, 1e-2);
let mut y = vec![1.0_f64, 0.0];
let mut dx = 0.1;
solver
.integrate(&ode, 0.0, std::f64::consts::PI / 2.0, &mut y, &mut dx)
.unwrap();
assert!(y[0].abs() < 5e-2, "y0={}", y[0]);
assert!((y[1] - 1.0).abs() < 5e-2, "y1={}", y[1]);
}
}