use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UdeError {
NonPositiveFilterBandwidth,
NonPositiveDt,
}
impl core::fmt::Display for UdeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
UdeError::NonPositiveFilterBandwidth => {
f.write_str("UDE filter bandwidth a_f must be strictly positive")
}
UdeError::NonPositiveDt => f.write_str("sampling period dt must be strictly positive"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct UdeController<S: ControlScalar> {
d_hat: S,
y_prev: S,
af: S,
a: S,
b: S,
dt: S,
initialized: bool,
}
impl<S: ControlScalar> UdeController<S> {
pub fn new(af: S, a: S, b: S, dt: S) -> Result<Self, UdeError> {
if af <= S::ZERO {
return Err(UdeError::NonPositiveFilterBandwidth);
}
if dt <= S::ZERO {
return Err(UdeError::NonPositiveDt);
}
Ok(Self {
d_hat: S::ZERO,
y_prev: S::ZERO,
af,
a,
b,
dt,
initialized: false,
})
}
pub fn update(&mut self, y: S, u: S) -> Result<S, UdeError> {
if !self.initialized {
self.y_prev = y;
self.initialized = true;
return Ok(self.d_hat);
}
let y_dot = (y - self.y_prev) / self.dt;
self.y_prev = y;
let residual = y_dot - self.a * y - self.b * u;
let d_hat_dot = -(self.af * self.d_hat) + self.af * residual;
self.d_hat += d_hat_dot * self.dt;
Ok(self.d_hat)
}
#[inline]
pub fn estimate(&self) -> S {
self.d_hat
}
pub fn reset(&mut self) {
self.d_hat = S::ZERO;
self.y_prev = S::ZERO;
self.initialized = false;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simulate_plant(a: f64, b: f64, dt: f64, u: f64, d: f64, y0: f64, steps: usize) -> Vec<f64> {
let mut y = y0;
let mut ys = Vec::with_capacity(steps + 1);
ys.push(y);
for _ in 0..steps {
y += dt * (a * y + b * (u + d));
ys.push(y);
}
ys
}
#[test]
fn test_step_disturbance() {
let a = -1.0_f64;
let b = 1.0_f64;
let af = 10.0_f64;
let dt = 0.001_f64;
let u = 0.0_f64;
let d = 1.0_f64;
let y0 = 0.0_f64;
let steps = 3000usize;
let ys = simulate_plant(a, b, dt, u, d, y0, steps);
let mut ude = UdeController::new(af, a, b, dt).expect("valid");
let mut d_hat = 0.0_f64;
for &y_k in &ys[..steps] {
d_hat = ude.update(y_k, u).expect("update ok");
}
let err = (d_hat - d).abs();
assert!(
err < 0.2 * d,
"UDE: expected d_hat ≈ {d}, got {d_hat} (err={err})"
);
}
#[test]
fn test_zero_disturbance() {
let a = -1.0_f64;
let b = 1.0_f64;
let af = 10.0_f64;
let dt = 0.001_f64;
let u = 0.0_f64;
let d = 0.0_f64;
let y0 = 0.5_f64;
let steps = 600usize;
let ys = simulate_plant(a, b, dt, u, d, y0, steps);
let mut ude = UdeController::new(af, a, b, dt).expect("valid");
let mut d_hat = 0.0_f64;
for &y_k in &ys[..steps] {
d_hat = ude.update(y_k, u).expect("update ok");
}
assert!(d_hat.abs() < 0.05, "UDE: expected d_hat ≈ 0, got {d_hat}");
}
#[test]
fn test_bandwidth_effect() {
let a = -1.0_f64;
let b = 1.0_f64;
let dt = 0.001_f64;
let u = 0.0_f64;
let d = 1.0_f64;
let y0 = 0.0_f64;
let steps = 600usize;
let ys = simulate_plant(a, b, dt, u, d, y0, steps);
let mut ude_slow = UdeController::new(5.0, a, b, dt).expect("slow valid");
let mut ude_fast = UdeController::new(20.0, a, b, dt).expect("fast valid");
let mut d_slow = 0.0_f64;
let mut d_fast = 0.0_f64;
for &y_k in &ys[..steps] {
d_slow = ude_slow.update(y_k, u).expect("slow update ok");
d_fast = ude_fast.update(y_k, u).expect("fast update ok");
}
let err_slow = (d_slow - d).abs();
let err_fast = (d_fast - d).abs();
assert!(
err_fast < err_slow,
"Higher bandwidth (af=20) should converge faster: err_fast={err_fast} err_slow={err_slow}"
);
}
#[test]
fn test_reset() {
let a = -1.0_f64;
let b = 1.0_f64;
let dt = 0.001_f64;
let mut ude = UdeController::new(10.0, a, b, dt).expect("valid");
let ys = simulate_plant(a, b, dt, 0.0, 2.0, 0.0, 300);
for &y_k in &ys[..300] {
let _ = ude.update(y_k, 0.0);
}
ude.reset();
assert_eq!(ude.estimate(), 0.0);
let first = ude.update(0.0, 0.0).expect("post-reset update ok");
assert_eq!(first, 0.0);
}
#[test]
fn test_invalid_af() {
let result = UdeController::<f64>::new(0.0, -1.0, 1.0, 0.01);
assert_eq!(result.unwrap_err(), UdeError::NonPositiveFilterBandwidth);
let result2 = UdeController::<f64>::new(-5.0, -1.0, 1.0, 0.01);
assert_eq!(result2.unwrap_err(), UdeError::NonPositiveFilterBandwidth);
}
#[test]
fn test_invalid_dt() {
let result = UdeController::<f64>::new(10.0, -1.0, 1.0, 0.0);
assert_eq!(result.unwrap_err(), UdeError::NonPositiveDt);
}
#[test]
fn test_estimate_accessor() {
let mut ude = UdeController::new(10.0_f64, -1.0, 1.0, 0.001).expect("valid");
let _ = ude.update(0.0, 0.0).expect("init ok");
let ret = ude.update(0.1, 0.05).expect("update ok");
assert_eq!(ret, ude.estimate());
}
}