use crate::core::matrix::{matvec, Matrix};
use crate::core::scalar::ControlScalar;
pub struct LuenbergerObserver<S: ControlScalar, const N: usize, const M: usize, const I: usize> {
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, M, N>,
l: Matrix<S, N, M>,
x_hat: [S; N],
}
impl<S: ControlScalar, const N: usize, const M: usize, const I: usize>
LuenbergerObserver<S, N, M, I>
{
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, M, N>,
l: Matrix<S, N, M>,
) -> Self {
Self {
a,
b,
c,
l,
x_hat: [S::ZERO; N],
}
}
pub fn with_initial_state(mut self, x0: [S; N]) -> Self {
self.x_hat = x0;
self
}
pub fn update(&mut self, u: &[S; I], y: &[S; M]) -> &[S; N] {
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 = matvec(&self.b, u);
let le = matvec(&self.l, &innovation);
self.x_hat = core::array::from_fn(|i| ax[i] + bu[i] + le[i]);
&self.x_hat
}
pub fn state(&self) -> &[S; N] {
&self.x_hat
}
pub fn reset(&mut self) {
self.x_hat = [S::ZERO; N];
}
pub fn set_state(&mut self, x: [S; N]) {
self.x_hat = x;
}
pub fn residual(&self, y: &[S; M]) -> [S; M] {
let y_hat = matvec(&self.c, &self.x_hat);
core::array::from_fn(|i| y[i] - y_hat[i])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_integrator_observer() -> LuenbergerObserver<f64, 1, 1, 1> {
let a = Matrix::<f64, 1, 1> { data: [[1.0]] };
let b = Matrix::<f64, 1, 1> { data: [[1.0]] };
let c = Matrix::<f64, 1, 1> { data: [[1.0]] };
let l = Matrix::<f64, 1, 1> { data: [[0.5]] };
LuenbergerObserver::new(a, b, c, l)
}
#[test]
fn observer_converges_to_true_state() {
let mut obs = build_integrator_observer();
let mut x_true = 0.0_f64;
for _ in 0..100 {
let u = 0.1_f64;
obs.update(&[u], &[x_true]);
x_true += u; }
assert!((obs.state()[0] - x_true).abs() < 0.01);
}
#[test]
fn zero_input_zero_output() {
let mut obs = build_integrator_observer();
for _ in 0..10 {
obs.update(&[0.0], &[0.0]);
}
assert!(obs.state()[0].abs() < 1e-10);
}
#[test]
fn reset_clears_state() {
let mut obs = build_integrator_observer();
for _ in 0..10 {
obs.update(&[1.0], &[5.0]);
}
obs.reset();
assert_eq!(obs.state()[0], 0.0);
}
#[test]
fn residual_is_output_error() {
let mut obs = build_integrator_observer();
obs.set_state([3.0]);
let r = obs.residual(&[5.0]);
assert!((r[0] - 2.0).abs() < 1e-10);
}
}