use crate::core::matrix::{matvec, Matrix};
use crate::core::scalar::ControlScalar;
pub struct DisturbanceObserver<S: ControlScalar, const N: usize, const M: usize> {
a: Matrix<S, N, N>,
b_col: [S; N],
c: Matrix<S, M, N>,
l_x: Matrix<S, N, M>,
l_d: [S; M],
x_hat: [S; N],
d_hat: S,
}
impl<S: ControlScalar, const N: usize, const M: usize> DisturbanceObserver<S, N, M> {
pub fn new(
a: Matrix<S, N, N>,
b_col: [S; N],
c: Matrix<S, M, N>,
l_x: Matrix<S, N, M>,
l_d: [S; M],
) -> Self {
Self {
a,
b_col,
c,
l_x,
l_d,
x_hat: [S::ZERO; N],
d_hat: S::ZERO,
}
}
pub fn update(&mut self, u: S, y: &[S; M]) -> (&[S; N], S) {
let y_hat = matvec(&self.c, &self.x_hat);
let innovation: [S; M] = core::array::from_fn(|i| y[i] - y_hat[i]);
let ax = matvec(&self.a, &self.x_hat);
let bu_plus_d: S = u + self.d_hat;
let le = matvec(&self.l_x, &innovation);
self.x_hat = core::array::from_fn(|i| ax[i] + self.b_col[i] * bu_plus_d + le[i]);
let l_d_e: S = self
.l_d
.iter()
.zip(innovation.iter())
.map(|(&ld, &e)| ld * e)
.fold(S::ZERO, |a, b| a + b);
self.d_hat += l_d_e;
(&self.x_hat, self.d_hat)
}
pub fn state(&self) -> &[S; N] {
&self.x_hat
}
pub fn disturbance(&self) -> S {
self.d_hat
}
pub fn reset(&mut self) {
self.x_hat = [S::ZERO; N];
self.d_hat = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_dob() -> DisturbanceObserver<f64, 1, 1> {
let a = Matrix::<f64, 1, 1> { data: [[1.0]] };
let b = [1.0_f64];
let c = Matrix::<f64, 1, 1> { data: [[1.0]] };
let l_x = Matrix::<f64, 1, 1> { data: [[0.6]] };
let l_d = [0.4_f64];
DisturbanceObserver::new(a, b, c, l_x, l_d)
}
#[test]
fn estimates_constant_disturbance() {
let mut dob = build_dob();
let true_dist = 2.0_f64;
let mut x_true = 0.0_f64;
for _ in 0..200 {
let u = 0.0;
x_true += u + true_dist;
dob.update(u, &[x_true]);
}
assert!((dob.disturbance() - true_dist).abs() < 0.5);
}
#[test]
fn no_disturbance_zero_estimate() {
let mut dob = build_dob();
let mut x_true = 0.0_f64;
for _ in 0..50 {
let u = 0.1;
x_true += u;
dob.update(u, &[x_true]);
}
assert!(dob.disturbance().abs() < 0.5);
}
#[test]
fn reset_clears_estimates() {
let mut dob = build_dob();
for _ in 0..20 {
dob.update(1.0, &[5.0]);
}
dob.reset();
assert_eq!(dob.disturbance(), 0.0);
}
}