#![allow(unused)]
use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
pub struct SubsystemMpc<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 x: Matrix<S, N, 1>,
pub rho: S,
dual: Matrix<S, I, 1>,
z: Matrix<S, I, 1>,
neighbor_u: Matrix<S, I, 1>,
}
impl<S: ControlScalar, const N: usize, const I: usize, const H: usize> SubsystemMpc<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>,
rho: S,
) -> Self {
Self {
a,
b,
q,
r,
x: Matrix::zeros(),
rho,
dual: Matrix::zeros(),
z: Matrix::zeros(),
neighbor_u: Matrix::zeros(),
}
}
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 cx = matmul(&xt, &qx).data[0][0];
let ru = matmul(&self.r, u);
let ut = u.transpose();
let cu = matmul(&ut, &ru).data[0][0];
cx + cu
}
fn augmented_cost(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>) -> S {
let base = self.stage_cost(x, u);
let mut aug = S::ZERO;
for i in 0..I {
let diff = u.data[i][0] - self.z.data[i][0] + self.dual.data[i][0];
aug += diff * diff;
}
aug *= self.rho * S::HALF;
base + aug
}
fn grad_augmented(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>, eps: S) -> Matrix<S, I, 1> {
let mut g = 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;
g.data[i][0] =
(self.augmented_cost(x, &u_p) - self.augmented_cost(x, &u_m)) / (S::TWO * eps);
}
g
}
pub fn primal_update(&mut self) -> Matrix<S, I, 1> {
let step = S::from_f64(1e-3);
let eps = S::from_f64(1e-5);
let max_iter = 30_usize;
let horizon = H;
let mut u_seq: [Matrix<S, I, 1>; H] = [Matrix::zeros(); H];
for _it in 0..max_iter {
let mut x = self.x;
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;
}
let g = self.grad_augmented(&xs[0], &u_seq[0], eps);
for i in 0..I {
u_seq[0].data[i][0] -= step * g.data[i][0];
}
for k in 1..horizon {
let g2 = self.grad_stage(&xs[k], &u_seq[k], eps);
for i in 0..I {
u_seq[k].data[i][0] -= step * g2.data[i][0];
}
}
}
u_seq[0]
}
fn grad_stage(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>, eps: S) -> Matrix<S, I, 1> {
let mut g = 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;
g.data[i][0] = (self.stage_cost(x, &u_p) - self.stage_cost(x, &u_m)) / (S::TWO * eps);
}
g
}
pub fn dual_update(&mut self, u_local: &Matrix<S, I, 1>) {
for i in 0..I {
self.dual.data[i][0] += self.rho * (u_local.data[i][0] - self.z.data[i][0]);
}
}
pub fn consensus_update(&mut self, u_local: &Matrix<S, I, 1>) {
for i in 0..I {
self.z.data[i][0] = (u_local.data[i][0] + self.neighbor_u.data[i][0]) * S::HALF;
}
}
pub fn share_prediction(&self) -> Matrix<S, I, 1> {
self.z
}
pub fn receive_neighbor(&mut self, u_neighbor: Matrix<S, I, 1>) {
self.neighbor_u = u_neighbor;
}
pub fn primal_residual(&self, u: &Matrix<S, I, 1>) -> S {
let diff = u.sub_mat(&self.z);
diff.frob_norm()
}
pub fn dual_residual(&self) -> S {
self.dual.frob_norm()
}
pub fn admm_iter(&mut self) -> Matrix<S, I, 1> {
let u = self.primal_update();
self.consensus_update(&u);
self.dual_update(&u);
u
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_subsystem() -> SubsystemMpc<f64, 2, 1, 5> {
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;
SubsystemMpc::new(a, b, q, r, 1.0_f64)
}
#[test]
fn subsystem_construction() {
let sys = make_subsystem();
assert_eq!(sys.rho, 1.0);
assert_eq!(sys.z.data[0][0], 0.0);
}
#[test]
fn primal_residual_zero_at_init() {
let sys = make_subsystem();
let u = Matrix::<f64, 1, 1>::zeros();
let res = sys.primal_residual(&u);
assert!(
res.abs() < 1e-12,
"Initial primal residual should be zero: {}",
res
);
}
#[test]
fn admm_iter_produces_control() {
let mut sys = make_subsystem();
sys.x.data[0][0] = 1.0;
let u = sys.admm_iter();
let _ = u;
}
#[test]
fn consensus_averages_predictions() {
let mut sys = make_subsystem();
let mut u_local = Matrix::<f64, 1, 1>::zeros();
u_local.data[0][0] = 2.0;
let mut u_neighbor = Matrix::<f64, 1, 1>::zeros();
u_neighbor.data[0][0] = 4.0;
sys.receive_neighbor(u_neighbor);
sys.consensus_update(&u_local);
assert!(
(sys.z.data[0][0] - 3.0).abs() < 1e-12,
"z = {}",
sys.z.data[0][0]
);
}
#[test]
fn dual_update_accumulates() {
let mut sys = make_subsystem();
let mut u_local = Matrix::<f64, 1, 1>::zeros();
u_local.data[0][0] = 1.0;
sys.dual_update(&u_local);
assert!(
(sys.dual.data[0][0] - 1.0).abs() < 1e-12,
"Dual: {}",
sys.dual.data[0][0]
);
sys.dual_update(&u_local);
assert!(
(sys.dual.data[0][0] - 2.0).abs() < 1e-12,
"Dual: {}",
sys.dual.data[0][0]
);
}
}