use crate::core::matrix::{matmul, matvec, vec_add, Matrix};
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct StateSpace<S: ControlScalar, const N: usize, const I: usize, const O: usize> {
pub a: Matrix<S, N, N>,
pub b: Matrix<S, N, I>,
pub c: Matrix<S, O, N>,
pub d: Matrix<S, O, I>,
state: [S; N],
}
impl<S: ControlScalar, const N: usize, const I: usize, const O: usize> StateSpace<S, N, I, O> {
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, O, N>,
d: Matrix<S, O, I>,
) -> Self {
Self {
a,
b,
c,
d,
state: core::array::from_fn(|_| S::ZERO),
}
}
pub fn step(&mut self, u: &[S; I]) -> [S; O] {
let ax = matvec(&self.a, &self.state);
let bu = matvec(&self.b, u);
let cx = matvec(&self.c, &self.state);
let du = matvec(&self.d, u);
self.state = vec_add(&ax, &bu);
vec_add(&cx, &du)
}
pub fn state(&self) -> &[S; N] {
&self.state
}
pub fn reset(&mut self) {
self.state = core::array::from_fn(|_| S::ZERO);
}
pub fn set_state(&mut self, x0: [S; N]) {
self.state = x0;
}
pub fn output(&self, u: &[S; I]) -> [S; O] {
let cx = matvec(&self.c, &self.state);
let du = matvec(&self.d, u);
vec_add(&cx, &du)
}
}
#[derive(Debug, Clone, Copy)]
pub struct ContinuousStateSpace<S: ControlScalar, const N: usize, const I: usize, const O: usize> {
pub a: Matrix<S, N, N>,
pub b: Matrix<S, N, I>,
pub c: Matrix<S, O, N>,
pub d: Matrix<S, O, I>,
}
impl<S: ControlScalar, const N: usize, const I: usize, const O: usize>
ContinuousStateSpace<S, N, I, O>
{
pub fn new(
a: Matrix<S, N, N>,
b: Matrix<S, N, I>,
c: Matrix<S, O, N>,
d: Matrix<S, O, I>,
) -> Self {
Self { a, b, c, d }
}
pub fn discretize_euler(&self, dt: S) -> StateSpace<S, N, I, O> {
let eye = Matrix::identity();
let ad = eye.add_mat(&self.a.scale(dt));
let bd = self.b.scale(dt);
StateSpace::new(ad, bd, self.c, self.d)
}
pub fn discretize_tustin(&self, dt: S) -> Option<StateSpace<S, N, I, O>> {
let half_dt = dt * S::HALF;
let eye = Matrix::<S, N, N>::identity();
let a_half = self.a.scale(half_dt);
let m_plus = eye.add_mat(&a_half); let m_minus = eye.sub_mat(&a_half);
let m_minus_inv = m_minus.inv()?;
let ad = matmul(&m_plus, &m_minus_inv);
let bd = matmul(&m_minus_inv, &self.b.scale(dt));
Some(StateSpace::new(ad, bd, self.c, self.d))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn first_order_system() -> ContinuousStateSpace<f64, 1, 1, 1> {
let a = Matrix { data: [[-1.0]] };
let b = Matrix { data: [[1.0]] };
let c = Matrix { data: [[1.0]] };
let d = Matrix { data: [[0.0]] };
ContinuousStateSpace::new(a, b, c, d)
}
#[test]
fn euler_step() {
let cont = first_order_system();
let mut sys = cont.discretize_euler(0.001);
let y = sys.step(&[1.0]);
assert_eq!(y[0], 0.0); let y2 = sys.step(&[1.0]);
assert!(y2[0] > 0.0); }
#[test]
fn tustin_step() {
let cont = first_order_system();
let mut sys = cont.discretize_tustin(0.01).unwrap();
sys.step(&[1.0]);
let y = sys.step(&[1.0]);
assert!(y[0] > 0.0);
}
#[test]
fn state_space_direct() {
let a = Matrix::<f64, 2, 2>::identity();
let b = Matrix::<f64, 2, 1>::zeros();
let c = Matrix::<f64, 1, 2>::zeros();
let d = Matrix::<f64, 1, 1>::zeros();
let mut sys = StateSpace::new(a, b, c, d);
sys.set_state([1.0, 2.0]);
let y = sys.step(&[0.0]);
assert_eq!(y[0], 0.0);
assert_eq!(sys.state(), &[1.0, 2.0]);
}
#[test]
fn reset_zeros_state() {
let a = Matrix::<f64, 1, 1>::identity();
let b = Matrix::<f64, 1, 1>::identity();
let c = Matrix::<f64, 1, 1>::identity();
let d = Matrix::<f64, 1, 1>::zeros();
let mut sys = StateSpace::new(a, b, c, d);
sys.step(&[1.0]);
sys.reset();
assert_eq!(sys.state(), &[0.0]);
}
#[test]
fn first_order_convergence_euler() {
let cont = first_order_system();
let mut sys = cont.discretize_euler(0.001);
for _ in 0..10_000 {
sys.step(&[1.0]);
}
assert!((sys.state()[0] - 1.0).abs() < 0.01);
}
#[test]
fn direct_feedthrough() {
let a = Matrix::<f64, 1, 1>::zeros();
let b = Matrix::<f64, 1, 1>::zeros();
let c = Matrix::<f64, 1, 1>::zeros();
let mut d = Matrix::<f64, 1, 1>::zeros();
d.data[0][0] = 5.0;
let mut sys = StateSpace::new(a, b, c, d);
let y = sys.step(&[3.0]);
assert_eq!(y[0], 15.0); }
}