use crate::embedded::error::EmbeddedError;
use crate::embedded::fixed::linalg::{
Matrix as MatrixStorage, Vector as VectorStorage, mat_vec_mul, vec_add,
};
use num_traits::Float;
pub type Matrix<T, const R: usize, const C: usize> = MatrixStorage<T, R, C>;
pub type Vector<T, const N: usize> = VectorStorage<T, N>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct DiscreteStateSpace<T, const NX: usize, const NU: usize, const NY: usize> {
a: Matrix<T, NX, NX>,
b: Matrix<T, NX, NU>,
c: Matrix<T, NY, NX>,
d: Matrix<T, NY, NU>,
sample_time: T,
}
impl<T, const NX: usize, const NU: usize, const NY: usize> DiscreteStateSpace<T, NX, NU, NY>
where
T: Float,
{
pub fn new(
a: Matrix<T, NX, NX>,
b: Matrix<T, NX, NU>,
c: Matrix<T, NY, NX>,
d: Matrix<T, NY, NU>,
sample_time: T,
) -> Result<Self, EmbeddedError> {
if !sample_time.is_finite() || sample_time <= T::zero() {
return Err(EmbeddedError::InvalidSampleTime);
}
Ok(Self {
a,
b,
c,
d,
sample_time,
})
}
#[must_use]
pub fn a(&self) -> &Matrix<T, NX, NX> {
&self.a
}
#[must_use]
pub fn b(&self) -> &Matrix<T, NX, NU> {
&self.b
}
#[must_use]
pub fn c(&self) -> &Matrix<T, NY, NX> {
&self.c
}
#[must_use]
pub fn d(&self) -> &Matrix<T, NY, NU> {
&self.d
}
#[must_use]
pub fn sample_time(&self) -> T {
self.sample_time
}
#[must_use]
pub fn output(&self, x: &Vector<T, NX>, u: &Vector<T, NU>) -> Vector<T, NY> {
vec_add(&mat_vec_mul(&self.c, x), &mat_vec_mul(&self.d, u))
}
#[must_use]
pub fn next_state(&self, x: &Vector<T, NX>, u: &Vector<T, NU>) -> Vector<T, NX> {
vec_add(&mat_vec_mul(&self.a, x), &mat_vec_mul(&self.b, u))
}
pub fn step(&self, x: &mut Vector<T, NX>, u: Vector<T, NU>) -> Vector<T, NY> {
let y = self.output(x, &u);
*x = self.next_state(x, &u);
y
}
}
impl<T, const NX: usize, const NU: usize, const NY: usize> DiscreteStateSpace<T, NX, NU, NY>
where
T: Float,
{
pub fn dc_gain(&self) -> Result<Matrix<T, NY, NU>, EmbeddedError> {
let lhs = core::array::from_fn(|row| {
core::array::from_fn(|col| {
if row == col {
T::one() - self.a[row][col]
} else {
-self.a[row][col]
}
})
});
let state_gain = crate::embedded::fixed::linalg::solve_linear_system(
&lhs,
&self.b,
"state_space.dc_gain",
)?;
Ok(crate::embedded::fixed::linalg::mat_add(
&crate::embedded::fixed::linalg::mat_mul(&self.c, &state_gain),
&self.d,
))
}
}
#[cfg(feature = "alloc")]
impl<T, const NX: usize, const NU: usize, const NY: usize>
TryFrom<&crate::control::lti::DiscreteStateSpace<T>> for DiscreteStateSpace<T, NX, NU, NY>
where
T: Float + faer_traits::RealField,
{
type Error = EmbeddedError;
fn try_from(value: &crate::control::lti::DiscreteStateSpace<T>) -> Result<Self, Self::Error> {
if value.nstates() != NX {
return Err(EmbeddedError::DimensionMismatch {
which: "embedded.fixed.state_space.nstates",
expected_rows: NX,
expected_cols: 1,
actual_rows: value.nstates(),
actual_cols: 1,
});
}
if value.ninputs() != NU {
return Err(EmbeddedError::DimensionMismatch {
which: "embedded.fixed.state_space.ninputs",
expected_rows: NU,
expected_cols: 1,
actual_rows: value.ninputs(),
actual_cols: 1,
});
}
if value.noutputs() != NY {
return Err(EmbeddedError::DimensionMismatch {
which: "embedded.fixed.state_space.noutputs",
expected_rows: NY,
expected_cols: 1,
actual_rows: value.noutputs(),
actual_cols: 1,
});
}
let mut a = [[T::zero(); NX]; NX];
let mut b = [[T::zero(); NU]; NX];
let mut c = [[T::zero(); NX]; NY];
let mut d = [[T::zero(); NU]; NY];
for i in 0..NX {
for j in 0..NX {
a[i][j] = value.a()[(i, j)];
}
for j in 0..NU {
b[i][j] = value.b()[(i, j)];
}
}
for i in 0..NY {
for j in 0..NX {
c[i][j] = value.c()[(i, j)];
}
for j in 0..NU {
d[i][j] = value.d()[(i, j)];
}
}
Self::new(a, b, c, d, value.sample_time())
}
}
#[cfg(test)]
mod tests {
use super::DiscreteStateSpace;
use crate::embedded::error::EmbeddedError;
#[test]
fn dc_gain_rejects_unit_pole() {
let system =
DiscreteStateSpace::<f64, 1, 1, 1>::new([[1.0]], [[1.0]], [[1.0]], [[0.0]], 0.1)
.unwrap();
let err = system.dc_gain().unwrap_err();
assert_eq!(
err,
EmbeddedError::SingularMatrix {
which: "state_space.dc_gain"
}
);
}
#[test]
fn dc_gain_matches_scalar_finite_reference() {
let system =
DiscreteStateSpace::<f64, 1, 1, 1>::new([[0.5]], [[2.0]], [[3.0]], [[4.0]], 0.1)
.unwrap();
let gain = system.dc_gain().unwrap();
assert!((gain[0][0] - 16.0).abs() < 1.0e-12);
}
}