use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AdrcError {
NonPositiveObserverBandwidth,
NonPositiveControllerBandwidth,
NonPositiveAlpha,
NonPositiveDt,
}
impl core::fmt::Display for AdrcError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AdrcError::NonPositiveObserverBandwidth => {
f.write_str("observer bandwidth must be positive")
}
AdrcError::NonPositiveControllerBandwidth => {
f.write_str("controller bandwidth must be positive")
}
AdrcError::NonPositiveAlpha => f.write_str("alpha must be positive"),
AdrcError::NonPositiveDt => f.write_str("dt must be positive"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SecondOrderAdrc<S: ControlScalar> {
pub z1: S,
pub z2: S,
pub z3: S,
omega_o: S,
omega_c: S,
b: S,
beta1: S,
beta2: S,
beta3: S,
kp: S,
kd: S,
dt: S,
}
impl<S: ControlScalar> SecondOrderAdrc<S> {
pub fn new(omega_o: S, omega_c: S, b: S, dt: S) -> Result<Self, AdrcError> {
if omega_o <= S::ZERO {
return Err(AdrcError::NonPositiveObserverBandwidth);
}
if omega_c <= S::ZERO {
return Err(AdrcError::NonPositiveControllerBandwidth);
}
if b <= S::ZERO {
return Err(AdrcError::NonPositiveAlpha);
}
if dt <= S::ZERO {
return Err(AdrcError::NonPositiveDt);
}
let three = S::from_f64(3.0);
let beta1 = three * omega_o;
let beta2 = three * omega_o * omega_o;
let beta3 = omega_o * omega_o * omega_o;
let kp = omega_c * omega_c;
let kd = S::TWO * omega_c;
Ok(Self {
z1: S::ZERO,
z2: S::ZERO,
z3: S::ZERO,
omega_o,
omega_c,
b,
beta1,
beta2,
beta3,
kp,
kd,
dt,
})
}
pub fn reset(&mut self) {
self.z1 = S::ZERO;
self.z2 = S::ZERO;
self.z3 = S::ZERO;
}
pub fn reset_to(&mut self, y0: S, dy0: S) {
self.z1 = y0;
self.z2 = dy0;
self.z3 = S::ZERO;
}
pub fn update(&mut self, y: S, r: S, dr: S, u_prev: S) -> S {
let e_obs = self.z1 - y;
let dz1 = self.z2 - self.beta1 * e_obs;
let dz2 = self.z3 + self.b * u_prev - self.beta2 * e_obs;
let dz3 = -self.beta3 * e_obs;
self.z1 += dz1 * self.dt;
self.z2 += dz2 * self.dt;
self.z3 += dz3 * self.dt;
let u0 = self.kp * (r - self.z1) + self.kd * (dr - self.z2);
(u0 - self.z3) / self.b
}
pub fn omega_o(&self) -> S {
self.omega_o
}
pub fn omega_c(&self) -> S {
self.omega_c
}
pub fn disturbance_estimate(&self) -> S {
self.z3
}
}
#[derive(Debug, Clone, Copy)]
pub struct FirstOrderAdrc<S: ControlScalar> {
pub z1: S,
pub z2: S,
omega_o: S,
omega_c: S,
b: S,
beta1: S,
beta2: S,
dt: S,
}
impl<S: ControlScalar> FirstOrderAdrc<S> {
pub fn new(omega_o: S, omega_c: S, b: S, dt: S) -> Result<Self, AdrcError> {
if omega_o <= S::ZERO {
return Err(AdrcError::NonPositiveObserverBandwidth);
}
if omega_c <= S::ZERO {
return Err(AdrcError::NonPositiveControllerBandwidth);
}
if b <= S::ZERO {
return Err(AdrcError::NonPositiveAlpha);
}
if dt <= S::ZERO {
return Err(AdrcError::NonPositiveDt);
}
let two = S::TWO;
let beta1 = two * omega_o;
let beta2 = omega_o * omega_o;
Ok(Self {
z1: S::ZERO,
z2: S::ZERO,
omega_o,
omega_c,
b,
beta1,
beta2,
dt,
})
}
pub fn reset(&mut self) {
self.z1 = S::ZERO;
self.z2 = S::ZERO;
}
pub fn reset_to(&mut self, y0: S) {
self.z1 = y0;
self.z2 = S::ZERO;
}
pub fn update(&mut self, y: S, r: S, u_prev: S) -> S {
let e_obs = self.z1 - y;
let dz1 = self.z2 + self.b * u_prev - self.beta1 * e_obs;
let dz2 = -self.beta2 * e_obs;
self.z1 += dz1 * self.dt;
self.z2 += dz2 * self.dt;
let u0 = self.omega_c * (r - self.z1);
(u0 - self.z2) / self.b
}
pub fn omega_o(&self) -> S {
self.omega_o
}
pub fn omega_c(&self) -> S {
self.omega_c
}
pub fn disturbance_estimate(&self) -> S {
self.z2
}
}
#[derive(Debug, Clone, Copy)]
pub struct ExtendedStateObserver<S: ControlScalar> {
pub z1: S,
pub z2: S,
pub z3: S,
beta1: S,
beta2: S,
beta3: S,
b: S,
dt: S,
}
impl<S: ControlScalar> ExtendedStateObserver<S> {
pub fn new(omega_o: S, b: S, dt: S) -> Result<Self, AdrcError> {
if omega_o <= S::ZERO {
return Err(AdrcError::NonPositiveObserverBandwidth);
}
if b <= S::ZERO {
return Err(AdrcError::NonPositiveAlpha);
}
if dt <= S::ZERO {
return Err(AdrcError::NonPositiveDt);
}
let three = S::from_f64(3.0);
Ok(Self {
z1: S::ZERO,
z2: S::ZERO,
z3: S::ZERO,
beta1: three * omega_o,
beta2: three * omega_o * omega_o,
beta3: omega_o * omega_o * omega_o,
b,
dt,
})
}
pub fn update(&mut self, y: S, u: S) {
let e = self.z1 - y;
let dz1 = self.z2 - self.beta1 * e;
let dz2 = self.z3 + self.b * u - self.beta2 * e;
let dz3 = -self.beta3 * e;
self.z1 += dz1 * self.dt;
self.z2 += dz2 * self.dt;
self.z3 += dz3 * self.dt;
}
pub fn states(&self) -> (S, S, S) {
(self.z1, self.z2, self.z3)
}
pub fn reset(&mut self) {
self.z1 = S::ZERO;
self.z2 = S::ZERO;
self.z3 = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
const DT: f64 = 0.001;
fn step_first_order(y: f64, u: f64, d: f64) -> f64 {
y + DT * (-y + u + d)
}
fn step_second_order(state: [f64; 2], u: f64, d: f64) -> [f64; 2] {
let dy = state[1];
let ddy = -0.5 * state[1] + u + d;
[state[0] + DT * dy, state[1] + DT * ddy]
}
#[test]
fn first_order_adrc_invalid_params() {
assert!(FirstOrderAdrc::<f64>::new(0.0, 10.0, 1.0, DT).is_err());
assert!(FirstOrderAdrc::<f64>::new(50.0, 0.0, 1.0, DT).is_err());
assert!(FirstOrderAdrc::<f64>::new(50.0, 10.0, 0.0, DT).is_err());
assert!(FirstOrderAdrc::<f64>::new(50.0, 10.0, 1.0, 0.0).is_err());
}
#[test]
fn second_order_adrc_invalid_params() {
assert!(SecondOrderAdrc::<f64>::new(0.0, 10.0, 1.0, DT).is_err());
assert!(SecondOrderAdrc::<f64>::new(100.0, 0.0, 1.0, DT).is_err());
assert!(SecondOrderAdrc::<f64>::new(100.0, 10.0, -1.0, DT).is_err());
assert!(SecondOrderAdrc::<f64>::new(100.0, 10.0, 1.0, -DT).is_err());
}
#[test]
fn first_order_adrc_tracks_reference_with_disturbance() {
let mut ctrl = FirstOrderAdrc::<f64>::new(50.0, 10.0, 1.0, DT).expect("valid params");
let r = 1.0_f64;
let disturbance = 0.3_f64;
let mut y = 0.0_f64;
let mut u = 0.0_f64;
for _ in 0..5000 {
let u_new = ctrl.update(y, r, u);
y = step_first_order(y, u, disturbance);
u = u_new;
}
assert!(
(y - r).abs() < 0.05,
"output={:.4} should be near reference={}",
y,
r
);
}
#[test]
fn second_order_adrc_tracks_reference() {
let mut ctrl = SecondOrderAdrc::<f64>::new(80.0, 10.0, 1.0, DT).expect("valid params");
let r = 1.0_f64;
let disturbance = 0.5_f64;
let mut state = [0.0_f64; 2];
let mut u = 0.0_f64;
for _ in 0..8000 {
let u_new = ctrl.update(state[0], r, 0.0, u);
state = step_second_order(state, u, disturbance);
u = u_new;
}
assert!(
(state[0] - r).abs() < 0.05,
"output={:.4} should converge to reference={}",
state[0],
r
);
}
#[test]
fn eso_disturbance_estimate_converges() {
let omega_o = 80.0_f64;
let omega_c = 20.0_f64;
let b = 1.0_f64;
let d = 2.0_f64;
let mut ctrl = FirstOrderAdrc::<f64>::new(omega_o, omega_c, b, DT).expect("valid params");
let mut y = 0.0_f64;
let mut u = 0.0_f64;
let r = 1.0_f64;
for _ in 0..6000 {
let u_new = ctrl.update(y, r, u);
y += DT * (b * u + d);
u = u_new;
}
assert!(
(y - r).abs() < 0.1,
"output y={:.4} should be near r={} despite disturbance d={}",
y,
r,
d
);
let d_est = ctrl.disturbance_estimate();
assert!(
d_est.abs() > 0.5,
"disturbance estimate z2={:.4} should be significantly non-zero",
d_est
);
}
#[test]
fn second_order_adrc_reset() {
let mut ctrl = SecondOrderAdrc::<f64>::new(100.0, 20.0, 1.0, DT).expect("valid params");
for i in 0..100 {
let _ = ctrl.update(i as f64 * 0.01, 1.0, 0.0, 0.0);
}
ctrl.reset();
assert_eq!(ctrl.z1, 0.0);
assert_eq!(ctrl.z2, 0.0);
assert_eq!(ctrl.z3, 0.0);
}
#[test]
fn first_order_adrc_f32() {
let mut ctrl =
FirstOrderAdrc::<f32>::new(50.0, 10.0, 1.0, 0.001).expect("valid f32 params");
let u = ctrl.update(0.0_f32, 1.0_f32, 0.0_f32);
assert!(u.is_finite());
}
}