use numra_core::Scalar;
#[derive(Clone, Debug)]
pub struct StepProposal<S: Scalar> {
pub h_new: S,
pub accept: bool,
}
pub trait StepController<S: Scalar> {
fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S>;
fn accept(&mut self, h: S, err: S);
fn reject(&mut self, h: S, err: S);
fn reset(&mut self);
}
#[derive(Clone, Debug)]
pub struct PIController<S: Scalar> {
pub safety: S,
pub fac_min: S,
pub fac_max: S,
pub beta1: S,
pub beta2: S,
err_old: Option<S>,
h_old: Option<S>,
}
impl<S: Scalar> Default for PIController<S> {
fn default() -> Self {
Self::new()
}
}
impl<S: Scalar> PIController<S> {
pub fn new() -> Self {
Self {
safety: S::from_f64(0.9),
fac_min: S::from_f64(0.2),
fac_max: S::from_f64(10.0),
beta1: S::from_f64(0.7),
beta2: S::from_f64(0.4),
err_old: None,
h_old: None,
}
}
pub fn with_params(safety: S, fac_min: S, fac_max: S, beta1: S, beta2: S) -> Self {
Self {
safety,
fac_min,
fac_max,
beta1,
beta2,
err_old: None,
h_old: None,
}
}
pub fn for_order(order: usize) -> Self {
let p = S::from_usize(order);
Self {
safety: S::from_f64(0.9),
fac_min: S::from_f64(0.2),
fac_max: S::from_f64(10.0),
beta1: S::from_f64(0.7) / p,
beta2: S::from_f64(0.4) / p,
err_old: None,
h_old: None,
}
}
}
impl<S: Scalar> StepController<S> for PIController<S> {
fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S> {
let p = S::from_usize(order);
let err_safe = err.max(S::EPSILON * S::from_f64(100.0));
let fac = match self.err_old {
Some(err_old) if err_old > S::ZERO => {
let err_old_safe = err_old.max(S::EPSILON * S::from_f64(100.0));
self.safety
* err_safe.powf(-self.beta1)
* (err_old_safe / err_safe).powf(self.beta2)
}
_ => {
self.safety * err_safe.powf(-S::ONE / p)
}
};
let fac = fac.clamp(self.fac_min, self.fac_max);
let h_new = h * fac;
let accept = err <= S::ONE;
StepProposal { h_new, accept }
}
fn accept(&mut self, h: S, err: S) {
self.err_old = Some(err);
self.h_old = Some(h);
}
fn reject(&mut self, _h: S, _err: S) {
self.err_old = None;
}
fn reset(&mut self) {
self.err_old = None;
self.h_old = None;
}
}
#[derive(Clone, Debug)]
pub struct IController<S: Scalar> {
pub safety: S,
pub fac_min: S,
pub fac_max: S,
}
impl<S: Scalar> Default for IController<S> {
fn default() -> Self {
Self {
safety: S::from_f64(0.9),
fac_min: S::from_f64(0.2),
fac_max: S::from_f64(10.0),
}
}
}
impl<S: Scalar> StepController<S> for IController<S> {
fn propose(&mut self, h: S, err: S, order: usize) -> StepProposal<S> {
let p = S::from_usize(order);
let err_safe = err.max(S::EPSILON * S::from_f64(100.0));
let fac = self.safety * err_safe.powf(-S::ONE / p);
let fac = fac.clamp(self.fac_min, self.fac_max);
let h_new = h * fac;
let accept = err <= S::ONE;
StepProposal { h_new, accept }
}
fn accept(&mut self, _h: S, _err: S) {}
fn reject(&mut self, _h: S, _err: S) {}
fn reset(&mut self) {}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pi_controller_accept() {
let mut ctrl = PIController::<f64>::for_order(5);
let prop = ctrl.propose(0.1, 0.5, 5);
assert!(prop.accept);
assert!(prop.h_new > 0.1); }
#[test]
fn test_pi_controller_reject() {
let mut ctrl = PIController::<f64>::for_order(5);
let prop = ctrl.propose(0.1, 2.0, 5);
assert!(!prop.accept);
assert!(prop.h_new < 0.1); }
#[test]
fn test_pi_controller_limits() {
let mut ctrl = PIController::<f64>::for_order(5);
let prop = ctrl.propose(0.1, 1e-10, 5);
assert!(prop.accept);
assert!(prop.h_new <= 0.1 * ctrl.fac_max);
let prop = ctrl.propose(0.1, 1e10, 5);
assert!(!prop.accept);
assert!(prop.h_new >= 0.1 * ctrl.fac_min);
}
#[test]
fn test_i_controller() {
let mut ctrl = IController::<f64>::default();
let prop = ctrl.propose(0.1, 0.5, 5);
assert!(prop.accept);
assert!(prop.h_new > 0.1);
let prop = ctrl.propose(0.1, 2.0, 5);
assert!(!prop.accept);
assert!(prop.h_new < 0.1);
}
#[test]
fn test_pi_controller_exact_one_error() {
let mut ctrl = PIController::<f64>::for_order(5);
let prop = ctrl.propose(0.1, 1.0, 5);
assert!(prop.accept);
assert!((prop.h_new - 0.1).abs() < 0.05);
}
#[test]
fn test_pi_controller_zero_error() {
let mut ctrl = PIController::<f64>::for_order(5);
let prop = ctrl.propose(0.1, 1e-15, 5);
assert!(prop.accept);
assert!(prop.h_new <= 0.1 * ctrl.fac_max);
}
#[test]
fn test_pi_controller_reset() {
let mut ctrl = PIController::<f64>::for_order(5);
let _ = ctrl.propose(0.1, 0.5, 5);
ctrl.accept(0.1, 0.5);
ctrl.reset();
let prop = ctrl.propose(0.1, 0.5, 5);
assert!(prop.accept);
}
#[test]
fn test_pi_controller_consecutive_accepts() {
let mut ctrl = PIController::<f64>::for_order(5);
for _ in 0..5 {
let prop = ctrl.propose(0.1, 0.5, 5);
assert!(prop.accept);
ctrl.accept(0.1, 0.5);
}
}
#[test]
fn test_pi_controller_reject_clears_memory() {
let mut ctrl = PIController::<f64>::for_order(5);
let _ = ctrl.propose(0.1, 0.5, 5);
ctrl.accept(0.1, 0.5);
let _ = ctrl.propose(0.1, 5.0, 5);
ctrl.reject(0.1, 5.0);
assert!(ctrl.err_old.is_none());
}
#[test]
fn test_i_controller_stateless() {
let mut ctrl = IController::<f64>::default();
let prop1 = ctrl.propose(0.1, 0.5, 5);
ctrl.accept(0.1, 0.5);
let prop2 = ctrl.propose(0.1, 0.5, 5);
assert!((prop1.h_new - prop2.h_new).abs() < 1e-15);
}
#[test]
fn test_controller_with_order_1() {
let mut ctrl = PIController::<f64>::for_order(1);
let prop = ctrl.propose(0.1, 0.5, 1);
assert!(prop.accept);
}
#[test]
fn test_controller_large_error_multiple() {
let mut ctrl = PIController::<f64>::for_order(5);
for _ in 0..3 {
let prop = ctrl.propose(0.1, 100.0, 5);
assert!(!prop.accept);
assert!(prop.h_new >= 0.1 * ctrl.fac_min);
ctrl.reject(0.1, 100.0);
}
}
#[test]
fn test_step_proposal_fields() {
let prop = StepProposal {
h_new: 0.05,
accept: false,
};
assert!((prop.h_new - 0.05).abs() < 1e-15);
assert!(!prop.accept);
}
#[test]
fn test_custom_params() {
let ctrl = PIController::<f64>::with_params(
0.8, 0.1, 5.0, 0.5, 0.3, );
assert!((ctrl.safety - 0.8).abs() < 1e-15);
assert!((ctrl.fac_min - 0.1).abs() < 1e-15);
assert!((ctrl.fac_max - 5.0).abs() < 1e-15);
}
}