use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
pub struct HinfSolution<S: ControlScalar, const N: usize, const I: usize> {
pub k: Matrix<S, I, N>,
pub p: Matrix<S, N, N>,
pub converged: bool,
pub iterations: usize,
}
#[allow(clippy::too_many_arguments)]
pub fn solve_hinf_dare<S: ControlScalar, const N: usize, const I: usize, const D: usize>(
a: &Matrix<S, N, N>,
b_u: &Matrix<S, N, I>,
b_w: &Matrix<S, N, D>,
q: &Matrix<S, N, N>,
r: &Matrix<S, I, I>,
gamma: S,
max_iter: usize,
tol: S,
) -> Option<HinfSolution<S, N, I>> {
let mut p = *q;
let at = a.transpose();
let bt_u = b_u.transpose(); let bt_w = b_w.transpose(); let gamma2_inv = S::ONE / (gamma * gamma);
let id_d = Matrix::<S, D, D>::identity();
for iter in 0..max_iter {
let p_old = p;
let pb_u = matmul(&p, b_u); let s_u = matmul(&bt_u, &pb_u).add_mat(r); let s_u_inv = s_u.inv()?;
let pb_w = matmul(&p, b_w); let btwpbw = matmul(&bt_w, &pb_w); let m_w = id_d.add_mat(&btwpbw.scale(-gamma2_inv));
let m_w_inv = m_w.inv()?;
let pa = matmul(&p, a); let atpa = matmul(&at, &pa);
let atpbu = matmul(&at, &pb_u); let btu_pa = matmul(&bt_u, &pa); let ctrl = matmul(&matmul(&atpbu, &s_u_inv), &btu_pa);
let atpbw = matmul(&at, &pb_w); let btw_pa = matmul(&bt_w, &pa); let dist = matmul(&matmul(&atpbw, &m_w_inv), &btw_pa).scale(gamma2_inv);
p = q.add_mat(&atpa).sub_mat(&ctrl).add_mat(&dist);
let err = p.sub_mat(&p_old).frob_norm();
if err < tol {
let pb_u_f = matmul(&p, b_u);
let btu_pa_f = matmul(&bt_u, &matmul(&p, a));
let s_u_f = matmul(&bt_u, &pb_u_f).add_mat(r);
let k = matmul(&s_u_f.inv()?, &btu_pa_f);
return Some(HinfSolution {
k,
p,
converged: true,
iterations: iter + 1,
});
}
}
let pb_u_f = matmul(&p, b_u);
let btu_pa_f = matmul(&bt_u, &matmul(&p, a));
let s_u_f = matmul(&bt_u, &pb_u_f).add_mat(r);
let k = matmul(&s_u_f.inv()?, &btu_pa_f);
Some(HinfSolution {
k,
p,
converged: false,
iterations: max_iter,
})
}
#[derive(Debug, Clone, Copy)]
pub struct HinfController<S: ControlScalar, const N: usize, const I: usize> {
pub gain: Matrix<S, I, N>,
}
impl<S: ControlScalar, const N: usize, const I: usize> HinfController<S, N, I> {
pub fn new(gain: Matrix<S, I, N>) -> Self {
Self { gain }
}
pub fn control(&self, x: &[S; N]) -> [S; I] {
use crate::core::matrix::matvec;
let kx = matvec(&self.gain, x);
core::array::from_fn(|i| -kx[i])
}
}
#[cfg(test)]
mod tests {
use super::*;
fn double_integrator() -> (Matrix<f64, 2, 2>, Matrix<f64, 2, 1>, Matrix<f64, 2, 1>) {
let a = Matrix::<f64, 2, 2> {
data: [[1.0, 0.1], [0.0, 1.0]],
};
let b_u = Matrix::<f64, 2, 1> {
data: [[0.005], [0.1]],
};
let b_w = Matrix::<f64, 2, 1> {
data: [[0.001], [0.01]],
};
(a, b_u, b_w)
}
#[test]
fn hinf_dare_converges() {
let (a, b_u, b_w) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let r = Matrix::<f64, 1, 1> { data: [[0.1]] };
let gamma = 10.0_f64;
let sol = solve_hinf_dare(&a, &b_u, &b_w, &q, &r, gamma, 1000, 1e-8);
assert!(sol.is_some(), "DARE should converge for γ=10");
let sol = sol.unwrap();
assert!(sol.converged, "Should converge within 1000 iterations");
}
#[test]
fn hinf_gain_stabilizes_system() {
let (a, b_u, b_w) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity().scale(10.0);
let r = Matrix::<f64, 1, 1> { data: [[0.1]] };
let gamma = 5.0_f64;
let sol = solve_hinf_dare(&a, &b_u, &b_w, &q, &r, gamma, 2000, 1e-8)
.expect("H-inf DARE should succeed");
let ctrl = HinfController::new(sol.k);
let mut x = [1.0_f64, 0.5];
for _ in 0..200 {
let u = ctrl.control(&x);
let x_new: [f64; 2] = core::array::from_fn(|i| {
a.data[i][0] * x[0] + a.data[i][1] * x[1] + b_u.data[i][0] * u[0]
});
x = x_new;
}
assert!(
x[0].abs() < 0.1 && x[1].abs() < 0.1,
"State should converge to origin: x={:?}",
x
);
}
#[test]
fn larger_gamma_accepts_more_disturbance() {
let (a, b_u, b_w) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let r = Matrix::<f64, 1, 1> { data: [[1.0]] };
let sol_large = solve_hinf_dare(&a, &b_u, &b_w, &q, &r, 1e6_f64, 1000, 1e-8);
assert!(sol_large.is_some(), "Large γ should always converge");
}
#[test]
fn too_small_gamma_may_fail() {
let (a, b_u, b_w) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let r = Matrix::<f64, 1, 1> { data: [[1.0]] };
let sol = solve_hinf_dare(&a, &b_u, &b_w, &q, &r, 0.001_f64, 100, 1e-8);
let _ = sol;
}
}