use crate::core::matrix::matvec;
use crate::core::matrix::Matrix;
use crate::core::scalar::ControlScalar;
pub struct Lqi<S: ControlScalar, const N: usize, const I: usize> {
pub k_x: Matrix<S, I, N>,
pub k_z: [S; I],
integral: S,
integral_limit: S,
}
impl<S: ControlScalar, const N: usize, const I: usize> Lqi<S, N, I> {
pub fn new(k_x: Matrix<S, I, N>, k_z: [S; I]) -> Self {
Self {
k_x,
k_z,
integral: S::ZERO,
integral_limit: S::from_f64(1e6),
}
}
pub fn with_integral_limit(mut self, limit: S) -> Self {
self.integral_limit = limit;
self
}
pub fn update(&mut self, x: &[S; N], r: S, y: S, dt: S) -> [S; I] {
self.integral =
(self.integral + dt * (r - y)).clamp_val(-self.integral_limit, self.integral_limit);
let u_x = matvec(&self.k_x, x);
core::array::from_fn(|i| -u_x[i] - self.k_z[i] * self.integral)
}
pub fn reset(&mut self) {
self.integral = S::ZERO;
}
pub fn integral_state(&self) -> S {
self.integral
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lqi_eliminates_steady_state_error() {
let k_x = Matrix::<f64, 1, 1> { data: [[0.4]] };
let k_z = [-6.0_f64];
let mut lqi = Lqi::new(k_x, k_z);
let mut x = 0.0_f64;
let r = 1.0_f64;
let dt = 0.01;
for _ in 0..2000 {
let u = lqi.update(&[x], r, x, dt);
x = 0.9 * x + u[0];
}
assert!(
(x - r).abs() < 0.05,
"LQI should eliminate SS error: x={:.4}",
x
);
}
#[test]
fn reset_clears_integral() {
let k_x = Matrix::<f64, 1, 1> { data: [[0.3]] };
let k_z = [0.1_f64];
let mut lqi = Lqi::new(k_x, k_z);
for _ in 0..100 {
lqi.update(&[0.0], 1.0, 0.0, 0.01);
}
assert!(lqi.integral_state().abs() > 0.1);
lqi.reset();
assert_eq!(lqi.integral_state(), 0.0);
}
#[test]
fn integral_limit_clamps_windup() {
let k_x = Matrix::<f64, 1, 1> { data: [[0.0]] };
let k_z = [0.0_f64];
let mut lqi = Lqi::new(k_x, k_z).with_integral_limit(5.0);
for _ in 0..10000 {
lqi.update(&[0.0], 100.0, 0.0, 0.01);
}
assert!(lqi.integral_state().abs() <= 5.0 + 1e-10);
}
}