use super::rhs_fn::{Integrator, IntegratorOutput, RhsFn};
use crate::error::{Error, Result};
use crate::vector3::Vector3;
pub struct AdaptiveIntegrator<I: Integrator> {
pub integrator: I,
pub tolerance: f64,
pub safety: f64,
pub dt_min: f64,
pub dt_max: f64,
pub max_factor: f64,
pub min_factor: f64,
pub order: f64,
prev_error: f64,
}
impl<I: Integrator> AdaptiveIntegrator<I> {
pub fn new(integrator: I, tolerance: f64, order: f64) -> Self {
Self {
integrator,
tolerance,
safety: 0.9,
dt_min: 1e-20,
dt_max: 1e10,
max_factor: 5.0,
min_factor: 0.2,
order,
prev_error: tolerance,
}
}
pub fn with_dt_min(mut self, dt_min: f64) -> Self {
self.dt_min = dt_min;
self
}
pub fn with_dt_max(mut self, dt_max: f64) -> Self {
self.dt_max = dt_max;
self
}
pub fn with_safety(mut self, safety: f64) -> Self {
self.safety = safety;
self
}
pub fn adaptive_step(
&mut self,
state: &[Vector3<f64>],
t: f64,
dt: f64,
f: &RhsFn<'_>,
) -> Result<(IntegratorOutput, f64)> {
let mut current_dt = dt.clamp(self.dt_min, self.dt_max);
loop {
let output = self.integrator.step(state, t, current_dt, f)?;
let error = match output.error_estimate {
Some(e) if e > 0.0 => e,
Some(_) => {
let new_dt = (current_dt * self.max_factor).clamp(self.dt_min, self.dt_max);
self.prev_error = self.tolerance;
let mut accepted = output;
accepted.suggested_dt = Some(new_dt);
return Ok((accepted, current_dt));
},
None => {
return Ok((output, current_dt));
},
};
let alpha_pi = 0.7 / (self.order + 1.0);
let beta_pi = 0.4 / (self.order + 1.0);
let factor = self.safety
* (self.tolerance / error).powf(alpha_pi)
* (self.prev_error / self.tolerance).powf(beta_pi);
let factor = factor.clamp(self.min_factor, self.max_factor);
if error <= self.tolerance {
self.prev_error = error;
let new_dt = (current_dt * factor).clamp(self.dt_min, self.dt_max);
let mut accepted = output;
accepted.suggested_dt = Some(new_dt);
return Ok((accepted, current_dt));
}
current_dt = (current_dt * factor).clamp(self.dt_min, self.dt_max);
if current_dt <= self.dt_min {
return Err(Error::NumericalError {
description: format!(
"Adaptive integrator: step size {:.2e} reached minimum {:.2e} \
without achieving tolerance {:.2e} (error = {:.2e})",
current_dt, self.dt_min, self.tolerance, error
),
});
}
}
}
}