#![allow(unused)]
use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
use crate::state_feedback::lqr::solve_dare;
pub fn solve_h2_dare<S: ControlScalar, const N: usize, const I: usize>(
a: &Matrix<S, N, N>,
b: &Matrix<S, N, I>,
q: &Matrix<S, N, N>,
r: &Matrix<S, I, I>,
) -> Option<(Matrix<S, I, N>, Matrix<S, N, N>)> {
let sol = solve_dare(a, b, q, r, 2000, S::from_f64(1e-10))?;
Some((sol.k, sol.p))
}
pub fn h2_norm_bound<S: ControlScalar, const N: usize, const I: usize>(
p: &Matrix<S, N, N>,
b_disturbance: &Matrix<S, N, I>,
) -> S {
let bt = b_disturbance.transpose();
let pb = matmul(p, b_disturbance);
let btpb = matmul(&bt, &pb);
btpb.trace()
}
#[derive(Debug, Clone, Copy)]
pub struct H2Controller<S: ControlScalar, const N: usize, const I: usize> {
pub gain: Matrix<S, I, N>,
pub h2_norm: S,
pub p_matrix: Matrix<S, N, N>,
}
impl<S: ControlScalar, const N: usize, const I: usize> H2Controller<S, N, I> {
pub fn new(gain: Matrix<S, I, N>, h2_norm: S) -> Self {
Self {
gain,
h2_norm,
p_matrix: Matrix::zeros(),
}
}
pub fn design(
a: &Matrix<S, N, N>,
b: &Matrix<S, N, I>,
q: &Matrix<S, N, N>,
r: &Matrix<S, I, I>,
b_disturbance: &Matrix<S, N, I>,
) -> Option<Self> {
let (gain, p) = solve_h2_dare(a, b, q, r)?;
let norm = h2_norm_bound(&p, b_disturbance);
Some(Self {
gain,
h2_norm: norm,
p_matrix: p,
})
}
pub fn control(&self, x: &Matrix<S, N, 1>) -> Matrix<S, I, 1> {
let kx = matmul(&self.gain, x);
kx.neg()
}
pub fn control_arr(&self, x: &[S; N]) -> [S; I] {
let xm = Matrix {
data: core::array::from_fn(|r| [x[r]]),
};
let u = self.control(&xm);
core::array::from_fn(|i| u.data[i][0])
}
pub fn cost_from_state(&self, x: &Matrix<S, N, 1>) -> S {
let px = matmul(&self.p_matrix, x);
let xt = x.transpose();
let cost = matmul(&xt, &px);
cost.data[0][0]
}
}
#[cfg(test)]
mod tests {
use super::*;
fn double_integrator() -> (Matrix<f64, 2, 2>, Matrix<f64, 2, 1>) {
let mut a = Matrix::<f64, 2, 2>::identity();
a.data[0][1] = 0.1;
let mut b = Matrix::<f64, 2, 1>::zeros();
b.data[0][0] = 0.005;
b.data[1][0] = 0.1;
(a, b)
}
#[test]
fn h2_dare_converges() {
let (a, b) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let mut r = Matrix::<f64, 1, 1>::zeros();
r.data[0][0] = 0.1;
let result = solve_h2_dare(&a, &b, &q, &r);
assert!(result.is_some(), "H2 DARE should converge");
let (k, _p) = result.unwrap();
assert!(k.data[0][0].abs() > 0.0 || k.data[0][1].abs() > 0.0);
}
#[test]
fn h2_controller_stabilizes() {
let (a, b) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let mut r = Matrix::<f64, 1, 1>::zeros();
r.data[0][0] = 0.1;
let ctrl = H2Controller::design(&a, &b, &q, &r, &b).unwrap();
let mut x = Matrix::<f64, 2, 1>::zeros();
x.data[0][0] = 1.0;
for _ in 0..300 {
let u = ctrl.control(&x);
let ax = matmul(&a, &x);
let bu = matmul(&b, &u);
x = ax.add_mat(&bu);
}
assert!(
x.data[0][0].abs() < 0.01,
"Position should converge: {}",
x.data[0][0]
);
assert!(
x.data[1][0].abs() < 0.01,
"Velocity should converge: {}",
x.data[1][0]
);
}
#[test]
fn h2_norm_bound_nonneg() {
let (a, b) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let mut r = Matrix::<f64, 1, 1>::zeros();
r.data[0][0] = 0.1;
let (_k, p) = solve_h2_dare(&a, &b, &q, &r).unwrap();
let norm = h2_norm_bound(&p, &b);
assert!(norm >= 0.0, "H2 norm bound must be non-negative: {}", norm);
}
#[test]
fn h2_cost_from_state() {
let (a, b) = double_integrator();
let q = Matrix::<f64, 2, 2>::identity();
let mut r = Matrix::<f64, 1, 1>::zeros();
r.data[0][0] = 0.1;
let ctrl = H2Controller::design(&a, &b, &q, &r, &b).unwrap();
let mut x0 = Matrix::<f64, 2, 1>::zeros();
x0.data[0][0] = 1.0;
let cost = ctrl.cost_from_state(&x0);
assert!(
cost > 0.0,
"Cost from non-zero state should be positive: {}",
cost
);
let x_zero = Matrix::<f64, 2, 1>::zeros();
let cost_zero = ctrl.cost_from_state(&x_zero);
assert!(
cost_zero.abs() < 1e-12,
"Cost from zero state should be zero: {}",
cost_zero
);
}
}