#![allow(unused)]
use crate::core::matrix::{matmul, Matrix};
use crate::core::scalar::ControlScalar;
pub fn compute_feed_forward_gain<S: ControlScalar, const N: usize, const I: usize>(
a: &Matrix<S, N, N>,
b: &Matrix<S, N, I>,
c: &Matrix<S, 1, N>,
k: &Matrix<S, I, N>,
) -> Option<S> {
let bk = matmul(b, k);
let a_cl = a.sub_mat(&bk);
let i_minus_acl = Matrix::<S, N, N>::identity().sub_mat(&a_cl);
let i_minus_acl_inv = i_minus_acl.inv()?;
let ci = matmul(c, &i_minus_acl_inv);
let dc_gain_mat = matmul(&ci, b);
let dc_gain = dc_gain_mat.data[0][0];
if dc_gain.abs() < S::EPSILON * S::from_f64(1e6) {
return None; }
Some(S::ONE / dc_gain)
}
pub struct Prescaler<S: ControlScalar, const N: usize, const I: usize> {
pub n_bar: S,
pub k_gain: Matrix<S, I, N>,
pub integrator: S,
pub ki: S,
}
impl<S: ControlScalar, const N: usize, const I: usize> Prescaler<S, N, I> {
pub fn new(n_bar: S, k_gain: Matrix<S, I, N>, ki: S) -> Self {
Self {
n_bar,
k_gain,
integrator: S::ZERO,
ki,
}
}
pub fn update(&mut self, x: &Matrix<S, N, 1>, r: S, y: S, dt: S) -> S {
let kx = matmul(&self.k_gain, x);
let u_fb = -kx.data[0][0];
let u_ff = self.n_bar * r;
let u_int = self.ki * self.integrator;
let u = u_fb + u_ff + u_int;
let error = r - y;
self.integrator += error * dt;
u
}
pub fn reset(&mut self) {
self.integrator = S::ZERO;
}
pub fn set_integrator(&mut self, val: S) {
self.integrator = val;
}
pub fn feed_forward(&self, r: S) -> S {
self.n_bar * r
}
}
#[cfg(test)]
mod tests {
use super::*;
fn siso_plant() -> (
Matrix<f64, 1, 1>,
Matrix<f64, 1, 1>,
Matrix<f64, 1, 1>,
Matrix<f64, 1, 1>,
) {
let mut a = Matrix::<f64, 1, 1>::zeros();
a.data[0][0] = 0.9;
let mut b = Matrix::<f64, 1, 1>::zeros();
b.data[0][0] = 1.0;
let mut c = Matrix::<f64, 1, 1>::zeros();
c.data[0][0] = 1.0;
let mut k = Matrix::<f64, 1, 1>::zeros();
k.data[0][0] = 0.5;
(a, b, c, k)
}
#[test]
fn feed_forward_gain_computed() {
let (a, b, c, k) = siso_plant();
let n_bar = compute_feed_forward_gain(&a, &b, &c, &k);
assert!(n_bar.is_some(), "N_bar should be computable");
let n_bar = n_bar.unwrap();
assert!(
(n_bar - 0.6).abs() < 1e-8,
"N_bar should be ~0.6: {}",
n_bar
);
}
#[test]
fn prescaler_zero_ref_zero_output() {
let (a, b, c, k) = siso_plant();
let n_bar = compute_feed_forward_gain(&a, &b, &c, &k).unwrap();
let mut ps = Prescaler::new(n_bar, k, 0.0_f64);
let x = Matrix::<f64, 1, 1>::zeros();
let u = ps.update(&x, 0.0, 0.0, 0.01);
assert!(u.abs() < 1e-12, "Control should be zero: {}", u);
}
#[test]
fn prescaler_tracking_with_integral() {
let (a, b, c, k) = siso_plant();
let n_bar = compute_feed_forward_gain(&a, &b, &c, &k).unwrap();
let ki = 0.1_f64;
let mut ps = Prescaler::new(n_bar, k, ki);
let mut x = Matrix::<f64, 1, 1>::zeros();
let r = 1.0_f64;
let dt = 0.01_f64;
for _ in 0..500 {
let y = x.data[0][0];
let u = ps.update(&x, r, y, dt);
x.data[0][0] = a.data[0][0] * x.data[0][0] + b.data[0][0] * u;
}
assert!(
(x.data[0][0] - r).abs() < 0.05,
"Output should track reference with integral: y={}, r={}",
x.data[0][0],
r
);
}
#[test]
fn prescaler_reset_clears_integrator() {
let (a, b, c, k) = siso_plant();
let n_bar = compute_feed_forward_gain(&a, &b, &c, &k).unwrap();
let mut ps = Prescaler::new(n_bar, k, 0.1_f64);
let x = Matrix::<f64, 1, 1>::zeros();
for _ in 0..10 {
ps.update(&x, 1.0, 0.0, 0.01);
}
assert!(ps.integrator.abs() > 0.0);
ps.reset();
assert!(
ps.integrator.abs() < 1e-15,
"Integrator should be zero after reset"
);
}
}