mod error;
pub use error::StateSpaceError;
use nalgebra::{ComplexField, DMatrix, RealField};
use num_traits::Float;
use std::marker::PhantomData;
use crate::{Continuous, Polynomial, TransferFunction};
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct StateSpaceOrder {
pub system: usize,
pub input: usize,
pub output: usize,
}
#[derive(Clone, Debug)]
pub struct StateSpace<T, D = Continuous> {
pub a: DMatrix<T>,
pub b: DMatrix<T>,
pub c: DMatrix<T>,
pub d: DMatrix<T>,
pub order: StateSpaceOrder,
_domain: PhantomData<D>,
}
impl<T, D> StateSpace<T, D> {
pub fn new(a: DMatrix<T>, b: DMatrix<T>, c: DMatrix<T>, d: DMatrix<T>) -> Result<Self, StateSpaceError> {
let state_order = a.nrows();
let input_order = b.ncols();
let output_order = c.nrows();
if a.ncols() != state_order {
return Err(StateSpaceError::SystemMatrix {
rows: a.nrows(),
cols: a.ncols(),
})
}
if b.nrows() != state_order {
return Err(StateSpaceError::InputMatrix {
expected_row: state_order,
expected_col: input_order,
actual_rows: b.nrows(),
actual_cols: b.ncols(),
})
}
if c.ncols() != state_order {
return Err(StateSpaceError::OutputMatrix {
expected_row: output_order,
expected_col: state_order,
actual_rows: c.nrows(),
actual_cols: c.ncols(),
})
}
if (d.nrows(), d.ncols()) != (output_order, input_order) {
return Err(StateSpaceError::FeedthroughMatrix {
expected_row: output_order,
expected_col: input_order,
actual_rows: d.nrows(),
actual_cols: d.ncols(),
})
}
let order = StateSpaceOrder {
system: a.nrows(),
input: b.ncols(),
output: c.nrows(),
};
Ok(Self {a, b, c, d, order, _domain: PhantomData})
}
}
impl<T: Float + ComplexField + RealField, D> StateSpace<T, D> {
pub fn transfer_function(&self) -> Result<TransferFunction<T, D>, StateSpaceError> {
if self.order.input != 1 || self.order.output != 1 {
return Err(StateSpaceError::NotSiso { inputs: self.order.input, outputs: self.order.output });
}
let n = self.order.system;
let identity = DMatrix::<T>::identity(n, n);
let mut m = identity.clone();
let mut denominator = vec![T::one()];
let mut adjugate = Vec::with_capacity(n);
for k in 1..=n {
adjugate.push((&self.c * &m * &self.b)[(0, 0)]);
let am = &self.a * &m;
let ck = -am.trace() / T::from(k).unwrap();
denominator.push(ck);
m = am + &identity * ck;
}
let d = self.d[(0, 0)];
let numerator: Vec<T> = std::iter::once(T::zero())
.chain(adjugate)
.zip(&denominator)
.map(|(cab, &det)| cab + d * det)
.collect();
Ok(TransferFunction::from_polynomials(Polynomial(numerator), Polynomial(denominator)))
}
}
impl<T: Float + ComplexField + RealField> StateSpace<T, Continuous> {
pub fn controllable_canonical(tf: &TransferFunction<T, Continuous>) -> Result<Self, StateSpaceError> {
let trim = |p: &Polynomial<T>| p.iter().copied().skip_while(|c| c.is_zero()).collect::<Vec<T>>();
let numer = trim(&tf.numerator);
let denom = trim(&tf.denominator);
let n = denom.len().saturating_sub(1);
if numer.len() > denom.len() {
return Err(StateSpaceError::Improper { numerator: numer.len() - 1, denominator: n });
}
if n == 0 {
return Err(StateSpaceError::EmptySystem);
}
let lead = denom[0];
let denom: Vec<T> = denom.iter().map(|&c| c / lead).collect();
let numer: Vec<T> = numer.iter().map(|&c| c / lead).collect();
let (d, numer) = if numer.len() == denom.len() {
let d = numer[0];
(d, numer.iter().zip(&denom).skip(1).map(|(&r, &a)| r - d * a).collect())
} else {
(T::zero(), numer)
};
let remainder: Vec<T> = numer.iter().rev().copied().collect();
let mut a = DMatrix::<T>::zeros(n, n);
for i in 0..n - 1 {
a[(i, i + 1)] = T::one();
}
for j in 0..n {
a[(n - 1, j)] = -denom[n - j];
}
let mut b = DMatrix::<T>::zeros(n, 1);
b[(n - 1, 0)] = T::one();
let mut c = DMatrix::<T>::zeros(1, n);
for (j, &r) in remainder.iter().enumerate() {
c[(0, j)] = r;
}
Self::new(a, b, c, DMatrix::from_element(1, 1, d))
}
pub fn normalized_controllable_canonical(tf: &TransferFunction<T, Continuous>) -> Result<Self, StateSpaceError> {
let mut ssr = Self::controllable_canonical(tf)?;
let lead = tf.denominator.iter().copied().find(|c| !c.is_zero()).unwrap_or_else(T::one);
if let Some(&lowest) = tf.numerator.iter().rev().find(|c| !c.is_zero()) {
let scale = lowest / lead;
ssr.b *= scale;
ssr.c /= scale;
}
Ok(ssr)
}
}