use crate::core::matrix::{matmul, matvec, Matrix};
use crate::core::scalar::ControlScalar;
use crate::state_feedback::lqr::{solve_dare, Lqr};
pub struct Lqg<S: ControlScalar, const N: usize, const M: usize, const I: usize> {
pub k: Matrix<S, I, N>,
pub l: Matrix<S, N, M>,
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, M, N>,
x_hat: [S; N],
}
impl<S: ControlScalar, const N: usize, const M: usize, const I: usize> Lqg<S, N, M, I> {
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, M, N>,
k: Matrix<S, I, N>,
l: Matrix<S, N, M>,
) -> Self {
Self {
k,
l,
a,
b,
c,
x_hat: [S::ZERO; N],
}
}
pub fn design(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, M, N>,
q_lqr: &Matrix<S, N, N>,
r_lqr: &Matrix<S, I, I>,
q_kf: &Matrix<S, N, N>,
r_kf: &Matrix<S, M, M>,
) -> Option<Self> {
let lqr = Lqr::design(&a, &b, q_lqr, r_lqr)?;
let at = a.transpose();
let ct = c.transpose(); let kf_sol = solve_dare(&at, &ct, q_kf, r_kf, 1000, S::from_f64(1e-8))?;
let r_inv = r_kf.inv()?;
let p_ct = matmul(&kf_sol.p, &ct); let l = matmul(&p_ct, &r_inv);
Some(Self::new(a, b, c, lqr.gain, l))
}
pub fn update(&mut self, y: &[S; M], reference: &[S; N]) -> [S; I] {
let u_prev = self.control_from_estimate(reference);
let y_hat = matvec(&self.c, &self.x_hat);
let innov: [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_prev);
let le = matvec(&self.l, &innov);
self.x_hat = core::array::from_fn(|i| ax[i] + bu[i] + le[i]);
self.control_from_estimate(reference)
}
fn control_from_estimate(&self, reference: &[S; N]) -> [S; I] {
let error: [S; N] = core::array::from_fn(|i| self.x_hat[i] - reference[i]);
let ku = matvec(&self.k, &error);
core::array::from_fn(|i| -ku[i])
}
pub fn state_estimate(&self) -> &[S; N] {
&self.x_hat
}
pub fn reset(&mut self) {
self.x_hat = [S::ZERO; N];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lqg_basic_construction() {
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 k = Matrix::<f64, 1, 1> { data: [[0.5]] };
let l = Matrix::<f64, 1, 1> { data: [[0.7]] };
let lqg = Lqg::new(a, b, c, k, l);
assert_eq!(lqg.state_estimate()[0], 0.0);
}
#[test]
fn lqg_design_works() {
let a = Matrix::<f64, 2, 2> {
data: [[1.0, 0.01], [0.0, 1.0]],
};
let b = Matrix::<f64, 2, 1> {
data: [[0.0], [0.01]],
};
let c = Matrix::<f64, 1, 2> { data: [[1.0, 0.0]] };
let q_lqr = Matrix::<f64, 2, 2>::identity().scale(1.0);
let r_lqr = Matrix::<f64, 1, 1> { data: [[0.1]] };
let q_kf = Matrix::<f64, 2, 2>::identity().scale(0.01);
let r_kf = Matrix::<f64, 1, 1> { data: [[0.1]] };
let lqg = Lqg::design(a, b, c, &q_lqr, &r_lqr, &q_kf, &r_kf);
assert!(lqg.is_some(), "LQG design should succeed");
}
#[test]
fn lqg_stabilizes_integrator() {
let a = Matrix::<f64, 1, 1> { data: [[0.9]] }; let b = Matrix::<f64, 1, 1> { data: [[1.0]] };
let c = Matrix::<f64, 1, 1> { data: [[1.0]] };
let k = Matrix::<f64, 1, 1> { data: [[0.5]] }; let l = Matrix::<f64, 1, 1> { data: [[0.8]] }; let mut lqg = Lqg::new(a, b, c, k, l);
let mut x_true = 5.0_f64;
for _ in 0..200 {
let u = lqg.update(&[x_true], &[0.0]);
x_true = 0.9 * x_true + u[0];
}
assert!(x_true.abs() < 0.5, "Should converge: x={:.4}", x_true);
}
}