#![allow(unused)]
use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
pub struct TubeMpc<S: ControlScalar, const N: usize, const I: usize, const H: usize> {
pub a: Matrix<S, N, N>,
pub b: Matrix<S, N, I>,
pub q: Matrix<S, N, N>,
pub r: Matrix<S, I, I>,
pub k_lqr: Matrix<S, I, N>,
pub tube_size: S,
pub x_nominal: Matrix<S, N, 1>,
pub x_actual: Matrix<S, N, 1>,
pub horizon: usize,
}
impl<S: ControlScalar, const N: usize, const I: usize, const H: usize> TubeMpc<S, N, I, H> {
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
q: Matrix<S, N, N>,
r: Matrix<S, I, I>,
k_lqr: Matrix<S, I, N>,
tube_size: S,
) -> Self {
Self {
a,
b,
q,
r,
k_lqr,
tube_size,
x_nominal: Matrix::zeros(),
x_actual: Matrix::zeros(),
horizon: H,
}
}
fn stage_cost(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>) -> S {
let qx = matmul(&self.q, x);
let xt = x.transpose();
let cost_x = matmul(&xt, &qx).data[0][0];
let ru = matmul(&self.r, u);
let ut = u.transpose();
let cost_u = matmul(&ut, &ru).data[0][0];
cost_x + cost_u
}
fn grad_u_cost(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>, eps: S) -> Matrix<S, I, 1> {
let mut grad = Matrix::<S, I, 1>::zeros();
for i in 0..I {
let mut u_p = *u;
let mut u_m = *u;
u_p.data[i][0] += eps;
u_m.data[i][0] -= eps;
grad.data[i][0] =
(self.stage_cost(x, &u_p) - self.stage_cost(x, &u_m)) / (S::TWO * eps);
}
grad
}
fn solve_nominal(&self) -> Matrix<S, I, 1> {
let step = S::from_f64(5e-4);
let eps = S::from_f64(1e-4);
let max_iter = 60_usize;
let horizon = self.horizon.min(H);
let mut u_seq: [Matrix<S, I, 1>; H] = [Matrix::zeros(); H];
for _it in 0..max_iter {
let mut x = self.x_nominal;
let mut xs: [Matrix<S, N, 1>; H] = [Matrix::zeros(); H];
for k in 0..horizon {
let ax = matmul(&self.a, &x);
let bu = matmul(&self.b, &u_seq[k]);
x = ax.add_mat(&bu);
xs[k] = x;
}
for k in 0..horizon {
let g = self.grad_u_cost(&xs[k], &u_seq[k], eps);
for i in 0..I {
u_seq[k].data[i][0] -= step * g.data[i][0];
}
}
}
u_seq[0]
}
pub fn nominal_cost(&self, u_seq: &[Matrix<S, I, 1>; H]) -> S {
let mut total = S::ZERO;
let mut x = self.x_nominal;
for u_k in u_seq.iter().take(self.horizon.min(H)) {
let ax = matmul(&self.a, &x);
let bu = matmul(&self.b, u_k);
x = ax.add_mat(&bu);
total += self.stage_cost(&x, u_k);
}
total
}
pub fn step(&mut self, x_actual: Matrix<S, N, 1>) -> Matrix<S, I, 1> {
self.x_actual = x_actual;
let u_nom = self.solve_nominal();
let e = self.x_nominal.sub_mat(&self.x_actual);
let u_correction = matmul(&self.k_lqr, &e);
let u_total = u_nom.add_mat(&u_correction);
let ax = matmul(&self.a, &self.x_nominal);
let bu = matmul(&self.b, &u_nom);
self.x_nominal = ax.add_mat(&bu);
u_total
}
pub fn tighten_constraint(&self, constraint: S) -> S {
constraint - self.tube_size
}
pub fn tube_error(&self) -> S {
let diff = self.x_actual.sub_mat(&self.x_nominal);
diff.frob_norm()
}
pub fn reset_nominal(&mut self, x: Matrix<S, N, 1>) {
self.x_nominal = x;
}
}
#[cfg(test)]
mod tests {
use super::*;
type Mat2x2 = Matrix<f64, 2, 2>;
type Mat2x1 = Matrix<f64, 2, 1>;
type Mat1x1 = Matrix<f64, 1, 1>;
type Mat1x2 = Matrix<f64, 1, 2>;
fn simple_system() -> (Mat2x2, Mat2x1, Mat2x2, Mat1x1, Mat1x2) {
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;
let q = Matrix::<f64, 2, 2>::identity();
let mut r = Matrix::<f64, 1, 1>::zeros();
r.data[0][0] = 0.1;
let mut k = Matrix::<f64, 1, 2>::zeros();
k.data[0][0] = 3.0;
k.data[0][1] = 1.5;
(a, b, q, r, k)
}
#[test]
fn tube_mpc_construction() {
let (a, b, q, r, k) = simple_system();
let mpc = TubeMpc::<f64, 2, 1, 5>::new(a, b, q, r, k, 0.1_f64);
assert_eq!(mpc.tube_size, 0.1);
assert_eq!(mpc.horizon, 5);
}
#[test]
fn tighten_constraint() {
let (a, b, q, r, k) = simple_system();
let mpc = TubeMpc::<f64, 2, 1, 5>::new(a, b, q, r, k, 0.1_f64);
let tightened = mpc.tighten_constraint(1.0_f64);
assert!((tightened - 0.9).abs() < 1e-12, "Tightened: {}", tightened);
}
#[test]
fn tube_mpc_step_produces_control() {
let (a, b, q, r, k) = simple_system();
let mut mpc = TubeMpc::<f64, 2, 1, 5>::new(a, b, q, r, k, 0.05_f64);
let mut xn = Matrix::<f64, 2, 1>::zeros();
xn.data[0][0] = 1.0;
mpc.reset_nominal(xn);
let xa = Matrix::<f64, 2, 1>::zeros();
let u = mpc.step(xa);
assert!(
u.data[0][0].abs() > 0.0,
"Control should be nonzero for nonzero tube error: {}",
u.data[0][0]
);
}
#[test]
fn tube_error_is_zero_when_states_equal() {
let (a, b, q, r, k) = simple_system();
let mut mpc = TubeMpc::<f64, 2, 1, 5>::new(a, b, q, r, k, 0.05_f64);
let x = Matrix::<f64, 2, 1>::zeros();
mpc.x_actual = x;
mpc.x_nominal = x;
assert!(
mpc.tube_error() < 1e-12,
"Error should be zero: {}",
mpc.tube_error()
);
}
}