#![allow(unused)]
use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
pub type EconomicCostFn<S, const N: usize, const I: usize> =
fn(&Matrix<S, N, 1>, &Matrix<S, I, 1>) -> S;
pub struct EconomicStage<S: ControlScalar, const N: usize, const I: usize> {
pub cost_fn: EconomicCostFn<S, N, I>,
pub gradient_step: S,
}
impl<S: ControlScalar, const N: usize, const I: usize> EconomicStage<S, N, I> {
pub fn new(cost_fn: EconomicCostFn<S, N, I>, gradient_step: S) -> Self {
Self {
cost_fn,
gradient_step,
}
}
pub fn stage_cost(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>) -> S {
(self.cost_fn)(x, u)
}
pub fn gradient_u(&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_plus = *u;
let mut u_minus = *u;
u_plus.data[i][0] += eps;
u_minus.data[i][0] -= eps;
let c_plus = (self.cost_fn)(x, &u_plus);
let c_minus = (self.cost_fn)(x, &u_minus);
grad.data[i][0] = (c_plus - c_minus) / (S::TWO * eps);
}
grad
}
pub fn gradient_x(&self, x: &Matrix<S, N, 1>, u: &Matrix<S, I, 1>, eps: S) -> Matrix<S, N, 1> {
let mut grad = Matrix::<S, N, 1>::zeros();
for i in 0..N {
let mut x_plus = *x;
let mut x_minus = *x;
x_plus.data[i][0] += eps;
x_minus.data[i][0] -= eps;
let c_plus = (self.cost_fn)(&x_plus, u);
let c_minus = (self.cost_fn)(&x_minus, u);
grad.data[i][0] = (c_plus - c_minus) / (S::TWO * eps);
}
grad
}
}
pub struct EconomicMpc<S: ControlScalar, const N: usize, const I: usize, const H: usize> {
pub a: Matrix<S, N, N>,
pub b: Matrix<S, N, I>,
pub stage: EconomicStage<S, N, I>,
pub x: Matrix<S, N, 1>,
pub horizon: usize,
pub iterations: usize,
pub terminal_constraint: bool,
pub turnpike_thresh: S,
}
impl<S: ControlScalar, const N: usize, const I: usize, const H: usize> EconomicMpc<S, N, I, H> {
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
stage: EconomicStage<S, N, I>,
iterations: usize,
) -> Self {
Self {
a,
b,
stage,
x: Matrix::zeros(),
horizon: H,
iterations,
terminal_constraint: false,
turnpike_thresh: S::from_f64(1e-4),
}
}
pub fn total_cost(&self, u_seq: &[Matrix<S, I, 1>; H]) -> S {
let mut total = S::ZERO;
let mut x = self.x;
let horizon = self.horizon.min(H);
for u_k in u_seq.iter().take(horizon) {
let c = self.stage.stage_cost(&x, u_k);
total += c;
let ax = matmul(&self.a, &x);
let bu = matmul(&self.b, u_k);
x = ax.add_mat(&bu);
}
total
}
pub fn near_turnpike(&self, u_seq: &[Matrix<S, I, 1>; H]) -> bool {
let horizon = self.horizon.min(H);
if horizon < 2 {
return false;
}
let mut x = self.x;
let mut prev_cost = self.stage.stage_cost(&x, &u_seq[0]);
let ax = matmul(&self.a, &x);
let bu = matmul(&self.b, &u_seq[0]);
x = ax.add_mat(&bu);
for u_k in u_seq.iter().take(horizon).skip(1) {
let c = self.stage.stage_cost(&x, u_k);
let diff = if c > prev_cost {
c - prev_cost
} else {
prev_cost - c
};
if diff > self.turnpike_thresh {
return false;
}
prev_cost = c;
let ax2 = matmul(&self.a, &x);
let bu2 = matmul(&self.b, u_k);
x = ax2.add_mat(&bu2);
}
true
}
pub fn step(&mut self) -> Matrix<S, I, 1> {
let eps = S::from_f64(1e-5);
let step = self.stage.gradient_step;
let horizon = self.horizon.min(H);
let max_iter = self.iterations;
let mut u_seq: [Matrix<S, I, 1>; H] = [Matrix::zeros(); H];
for _iter in 0..max_iter {
let mut xs: [Matrix<S, N, 1>; H] = [Matrix::zeros(); H];
let mut x = self.x;
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.stage.gradient_u(&xs[k], &u_seq[k], eps);
for i in 0..I {
u_seq[k].data[i][0] -= step * g.data[i][0];
}
}
if self.terminal_constraint {
}
}
u_seq[0]
}
pub fn set_state(&mut self, x: Matrix<S, N, 1>) {
self.x = x;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn quadratic_cost<const N: usize, const I: usize>(
x: &Matrix<f64, N, 1>,
u: &Matrix<f64, I, 1>,
) -> f64 {
let x_cost: f64 = x.data.iter().map(|r| r[0] * r[0]).sum();
let u_cost: f64 = u.data.iter().map(|r| r[0] * r[0]).sum();
x_cost + u_cost
}
#[test]
fn economic_stage_cost_evaluates() {
let stage = EconomicStage::<f64, 2, 1>::new(quadratic_cost, 1e-3);
let mut x = Matrix::<f64, 2, 1>::zeros();
x.data[0][0] = 1.0;
x.data[1][0] = 2.0;
let mut u = Matrix::<f64, 1, 1>::zeros();
u.data[0][0] = 0.5;
let cost = stage.stage_cost(&x, &u);
assert!((cost - 5.25).abs() < 1e-12, "Cost: {}", cost);
}
#[test]
fn gradient_u_numerical() {
let stage = EconomicStage::<f64, 2, 1>::new(quadratic_cost, 1e-3);
let x = Matrix::<f64, 2, 1>::zeros();
let mut u = Matrix::<f64, 1, 1>::zeros();
u.data[0][0] = 1.0;
let g = stage.gradient_u(&x, &u, 1e-5);
assert!(
(g.data[0][0] - 2.0).abs() < 1e-6,
"Gradient: {}",
g.data[0][0]
);
}
#[test]
fn economic_mpc_step_reduces_cost() {
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 stage = EconomicStage::<f64, 2, 1>::new(quadratic_cost, 1e-3);
let mut mpc = EconomicMpc::<f64, 2, 1, 5>::new(a, b, stage, 100);
let mut x0 = Matrix::<f64, 2, 1>::zeros();
x0.data[0][0] = 1.0;
mpc.set_state(x0);
let u_zero: [Matrix<f64, 1, 1>; 5] = [Matrix::zeros(); 5];
let cost_before = mpc.total_cost(&u_zero);
let u_opt = mpc.step();
let mut u_opt_seq: [Matrix<f64, 1, 1>; 5] = [u_opt; 5];
let cost_after = mpc.total_cost(&u_opt_seq);
assert!(
cost_after <= cost_before + 1e-6,
"Cost should not increase: before={}, after={}",
cost_before,
cost_after
);
}
#[test]
fn total_cost_zero_at_origin() {
let a = Matrix::<f64, 2, 2>::identity();
let b = Matrix::<f64, 2, 1>::zeros();
let stage = EconomicStage::<f64, 2, 1>::new(quadratic_cost, 1e-3);
let mpc = EconomicMpc::<f64, 2, 1, 5>::new(a, b, stage, 10);
let u_seq = [Matrix::<f64, 1, 1>::zeros(); 5];
let cost = mpc.total_cost(&u_seq);
assert!(
cost.abs() < 1e-12,
"Cost at origin should be zero: {}",
cost
);
}
}