use std::{
fmt,
fmt::{Debug, Display, Formatter},
marker::PhantomData,
ops::{Add, Div, Mul, Neg, Sub},
};
use ndarray::{s, Array2};
use crate::{
enums::Time,
error::{Error, ErrorKind},
matrix::{ar_to_dm, identity, MatrixMul, ScalarMul, Trace},
polynomial_matrix::PolyMatrix,
transfer_function::TfGen,
wrappers::{PWrapper, PP},
Abs, Complex, Inv, NumCast, One, Pow, Sign, Sqrt, Zero,
};
pub mod continuous;
pub mod discrete;
pub mod solver;
#[derive(Clone, Debug, PartialEq)]
pub struct SsGen<T, U: Time> {
pub(super) a: Array2<T>,
pub(super) b: Array2<T>,
pub(super) c: Array2<T>,
pub(super) d: Array2<T>,
dim: Dim,
time: PhantomData<U>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Dim {
states: usize,
inputs: usize,
outputs: usize,
}
impl Dim {
#[must_use]
pub fn states(&self) -> usize {
self.states
}
#[must_use]
pub fn inputs(&self) -> usize {
self.inputs
}
#[must_use]
pub fn outputs(&self) -> usize {
self.outputs
}
}
impl<T, U: Time> SsGen<T, U>
where
T: Clone,
{
pub fn new_from_slice(
states: usize,
inputs: usize,
outputs: usize,
a: &[T],
b: &[T],
c: &[T],
d: &[T],
) -> Self {
let states_matrix = Array2::from_shape_vec((states, states), a.into()).unwrap();
debug_assert!(states_matrix.is_square());
Self {
a: states_matrix,
b: Array2::from_shape_vec((states, inputs), b.into()).unwrap(),
c: Array2::from_shape_vec((outputs, states), c.into()).unwrap(),
d: Array2::from_shape_vec((outputs, inputs), d.into()).unwrap(),
dim: Dim {
states,
inputs,
outputs,
},
time: PhantomData,
}
}
#[must_use]
pub fn dim(&self) -> Dim {
self.dim
}
}
impl<U> SsGen<f64, U>
where
U: Time,
{
#[must_use]
pub fn poles(&self) -> Vec<Complex<f64>> {
self.poles_impl()
}
}
impl<U> SsGen<f32, U>
where
U: Time,
{
#[must_use]
pub fn poles(&self) -> Vec<Complex<f32>> {
self.poles_impl()
}
}
impl<T, U> SsGen<T, U>
where
T: Abs
+ Copy
+ Inv<Output = T>
+ One
+ PartialOrd
+ Pow<T>
+ nalgebra::RealField
+ Sign
+ Sqrt
+ Zero,
U: Time,
{
#[must_use]
fn poles_impl(&self) -> Vec<Complex<T>> {
match self.dim().states() {
1 => vec![Complex::new(self.a[(0, 0)], <T as Zero>::zero())],
2 => {
let m00 = self.a[(0, 0)];
let m01 = self.a[(0, 1)];
let m10 = self.a[(1, 0)];
let m11 = self.a[(1, 1)];
let trace = m00 + m11;
let determinant = m00 * m11 - m01 * m10;
let (eig1, eig2) =
polynomen::complex_quadratic_roots(PWrapper(-trace), PWrapper(determinant));
vec![
Complex::new(eig1.0 .0, eig1.1 .0),
Complex::new(eig2.0 .0, eig2.1 .0),
]
}
_ => ar_to_dm(&self.a)
.complex_eigenvalues()
.as_slice()
.iter()
.map(|x| Complex::new(x.re, x.im))
.collect::<Vec<_>>(),
}
}
}
fn controllability_impl<T>(n: usize, m: usize, a: &Array2<T>, b: &Array2<T>) -> Array2<T>
where
T: Add<Output = T> + Clone + Mul<Output = T> + Zero,
{
let mut mr = Array2::<T>::from_elem((n, n * m), T::zero());
mr.slice_mut(s![.., 0..m]).assign(b);
let mut rhs = Array2::<T>::from_elem((n, m), T::zero());
for i in 1..n {
rhs.assign(&mr.slice(s![.., ((i - 1) * m)..(i * m)]));
let column = a.mmul(&rhs);
mr.slice_mut(s![.., (i * m)..((i + 1) * m)]).assign(&column);
}
mr
}
fn observability_impl<T>(n: usize, p: usize, a: &Array2<T>, c: &Array2<T>) -> Array2<T>
where
T: Add<Output = T> + Clone + Mul<Output = T> + Zero,
{
let mut mo = Array2::from_elem((n, n * p), T::zero());
mo.slice_mut(s![.., 0..p]).assign(&c.t());
let at = a.clone().reversed_axes();
let mut rhs = Array2::from_elem((n, p), T::zero());
for i in 1..n {
rhs.assign(&mo.slice(s![.., ((i - 1) * p)..(i * p)]));
let column = at.mmul(&rhs);
mo.slice_mut(s![.., (i * p)..((i + 1) * p)]).assign(&column);
}
mo
}
impl<T, U: Time> SsGen<T, U>
where
T: Add<Output = T> + Clone + Mul<Output = T> + Zero,
{
#[must_use]
pub fn controllability(&self) -> (usize, usize, Vec<T>) {
let n = self.dim.states;
let m = self.dim.inputs;
let mr = controllability_impl(n, m, &self.a, &self.b);
(n, n * m, mr.iter().cloned().collect())
}
#[must_use]
pub fn observability(&self) -> (usize, usize, Vec<T>) {
let n = self.dim.states;
let p = self.dim.outputs;
let mo = observability_impl(n, p, &self.a, &self.c);
(n, n * p, mo.iter().cloned().collect())
}
}
#[allow(non_snake_case)]
pub(super) fn leverrier<T>(A: &Array2<T>) -> Result<(PP<T>, PolyMatrix<T>), Error>
where
T: Add<Output = T>
+ Clone
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ NumCast
+ One
+ PartialEq
+ Zero,
{
let size = A.nrows(); let mut a = vec![T::one()];
let a1 = -A.trace();
a.insert(0, a1.clone());
let I = identity(size);
let B1 = I.clone();
let mut B = vec![B1.clone()];
if size == 1 {
return Ok((PP::new_from_coeffs(&a), PolyMatrix::new_from_coeffs(&B)));
}
let mut ak = a1;
let mut ABk = A.mmul(&B1);
for k in 2..=size {
let Bk = (&I).smul(ak) + &ABk;
ABk = A.mmul(&Bk);
ak = -T::from(k)
.ok_or_else(|| Error::new_internal(ErrorKind::NumericConversion))?
.inv()
* ABk.trace();
B.insert(0, Bk);
a.insert(0, ak.clone());
}
Ok((PP::new_from_coeffs(&a), PolyMatrix::new_from_coeffs(&B)))
}
impl<T, U> SsGen<T, U>
where
T: Add<Output = T>
+ Clone
+ Div<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialEq
+ Sub<Output = T>
+ Zero,
U: Time,
{
pub fn new_observability_realization(tf: &TfGen<T, U>) -> Result<Self, Error> {
let tf_norm = tf.normalize();
let order = match tf_norm.den_int().0.degree() {
Some(d) => d,
None => return Err(Error::new_internal(ErrorKind::ZeroPolynomialDenominator)),
};
let num = {
let mut num = tf_norm.num_int().clone();
num.0.extend(order);
num
};
let a = observability_canonical_form(&tf_norm)?;
let states = a.nrows();
let b_n = num.0[order].0.clone();
let b = Array2::from_shape_fn((states, 1), |(i, _j)| {
num.0[i].0.clone() - tf_norm.den_int().0[i].0.clone() * b_n.clone()
});
let c = {
let mut c = Array2::from_elem((1, states), T::zero());
c[(0, states - 1)] = T::one();
c
};
let d = Array2::from_elem((1, 1), b_n);
Ok(Self {
a,
b,
c,
d,
dim: Dim {
states,
inputs: 1,
outputs: 1,
},
time: PhantomData,
})
}
pub fn new_controllability_realization(tf: &TfGen<T, U>) -> Result<Self, Error> {
let tf_norm = tf.normalize();
let order = match tf_norm.den_int().0.degree() {
Some(d) => d,
None => return Err(Error::new_internal(ErrorKind::ZeroPolynomialDenominator)),
};
let num = {
let mut num = tf_norm.num_int().clone();
num.0.extend(order);
num
};
let a = controllability_canonical_form(&tf_norm)?;
let states = a.nrows();
let b_n = num.0[order].0.clone();
let b = {
let mut b = Array2::from_elem((states, 1), T::zero());
b[(states - 1, 0)] = T::one();
b
};
let c = Array2::from_shape_fn((1, states), |(_i, j)| {
num.0[j].0.clone() - tf_norm.den_int().0[j].0.clone() * b_n.clone()
});
let d = Array2::from_elem((1, 1), b_n);
Ok(Self {
a,
b,
c,
d,
dim: Dim {
states,
inputs: 1,
outputs: 1,
},
time: PhantomData,
})
}
}
fn observability_canonical_form<T, U>(tf: &TfGen<T, U>) -> Result<Array2<T>, Error>
where
T: Clone + Neg<Output = T> + One + PartialEq + Zero,
U: Time,
{
debug_assert!(One::is_one(&tf.den_int().0.leading_coeff().0));
match tf.den_int().0.degree() {
Some(degree) if degree > 0 => {
let comp = Array2::from_shape_fn((degree, degree), |(i, j)| {
if j == degree - 1 {
-tf.den_int().0[i].0.clone()
} else if i == j + 1 {
T::one()
} else {
T::zero()
}
});
debug_assert!(comp.is_square());
Ok(comp)
}
_ => Err(Error::new_internal(ErrorKind::NoPolesDenominator)),
}
}
fn controllability_canonical_form<T, U>(tf: &TfGen<T, U>) -> Result<Array2<T>, Error>
where
T: Clone + Neg<Output = T> + One + PartialEq + Zero,
U: Time,
{
debug_assert!(tf.den_int().0.leading_coeff().0.is_one());
match tf.den_int().0.degree() {
Some(degree) if degree > 0 => {
let comp = Array2::from_shape_fn((degree, degree), |(i, j)| {
if i == degree - 1 {
-tf.den_int().0[j].0.clone()
} else if j == i + 1 {
T::one()
} else {
T::zero()
}
});
debug_assert!(comp.is_square());
Ok(comp)
}
_ => Err(Error::new_internal(ErrorKind::NoPolesDenominator)),
}
}
impl<T, U: Time> Display for SsGen<T, U>
where
T: Display,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"A: {}\nB: {}\nC: {}\nD: {}",
self.a, self.b, self.c, self.d
)
}
}
#[derive(Clone, Debug)]
pub struct Equilibrium<T> {
x: Vec<T>,
y: Vec<T>,
}
impl<T> Equilibrium<T> {
fn new(x: Vec<T>, y: Vec<T>) -> Self {
Self { x, y }
}
#[must_use]
pub fn x(&self) -> &[T] {
self.x.as_slice()
}
#[must_use]
pub fn y(&self) -> &[T] {
self.y.as_slice()
}
}
impl<T: Display> Display for Equilibrium<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "x: [")?;
let mut first_x = true;
for i in &self.x {
if !first_x {
write!(f, ", ")?;
}
i.fmt(f)?;
first_x = false;
}
write!(f, "]\ny: [")?;
let mut first_y = true;
for i in &self.y {
if !first_y {
write!(f, ", ")?;
}
i.fmt(f)?;
first_y = false;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use crate::{polynomial_matrix::MatrixOfPoly, Continuous, Discrete};
use super::*;
proptest! {
#[test]
fn qc_dimensions(states: usize, inputs: usize, outputs: usize) {
let d = Dim {
states,
inputs,
outputs,
};
assert_eq!(states, d.states());
assert_eq!(inputs, d.inputs());
assert_eq!(outputs, d.outputs());
}
}
#[test]
fn system_dimensions() {
let states = 2;
let inputs = 1;
let outputs = 1;
let sys = SsGen::<f64, Continuous>::new_from_slice(
states,
inputs,
outputs,
&[-2., 0., 3., -7.],
&[1., 3.],
&[-1., 0.5],
&[0.1],
);
assert_eq!(
Dim {
states,
inputs,
outputs
},
sys.dim()
);
}
#[test]
#[should_panic]
fn poles_fail() {
let eig1 = -2.;
let eig2 = -7.;
let a = nalgebra::DMatrix::from_row_slice(2, 2, &[eig1, 0., 3., eig2]);
let schur = nalgebra::Schur::new(a);
let poles = schur.complex_eigenvalues();
assert_eq!((eig1, eig2), (poles[0].re, poles[1].re));
}
#[test]
fn poles_regression() {
let eig1 = -2.;
let eig2 = -7.;
let sys = SsGen::<f64, Discrete>::new_from_slice(
2,
1,
1,
&[eig1, 0., 3., eig2],
&[1., 3.],
&[-1., 0.5],
&[0.1],
);
let poles = sys.poles();
assert_eq!((eig1, eig2), (poles[0].re, poles[1].re));
}
proptest! {
#[test]
fn qc_poles_one(eig: f32) {
let sys = SsGen::<f32, Continuous>::new_from_slice(1, 1, 1, &[eig], &[1.], &[-1.], &[0.1]);
let poles = sys.poles();
let expected = (eig, 0.);
let actual = (poles[0].re, poles[0].im);
assert_relative_eq!(expected.0, actual.0, max_relative = 1e-10);
assert_relative_eq!(expected.1, actual.1, max_relative = 1e-10);
}
}
proptest! {
#[test]
fn qc_poles_two(eig1 in (-1e12..1e12), eig2 in (-1e12..1e12)) {
let sys = SsGen::<f64, Continuous>::new_from_slice(
2,
1,
1,
&[eig1, 0., 3., eig2],
&[1., 3.],
&[-1., 0.5],
&[0.1],
);
let poles = sys.poles();
let mut expected = [eig1, eig2];
expected.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
let mut actual = [poles[0].re, poles[1].re];
actual.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
assert_relative_eq!(expected[0], actual[0], max_relative = 1e-10);
assert_relative_eq!(expected[1], actual[1], max_relative = 1e-10);
}
}
#[test]
fn poles_three() {
let eig1 = -7.;
let eig2 = -2.;
let eig3 = 1.25;
let sys = SsGen::<f64, Discrete>::new_from_slice(
3,
1,
1,
&[eig1, 0., 0., 3., eig2, 0., 10., 0.8, eig3],
&[1., 3., -5.5],
&[-1., 0.5, -4.3],
&[0.],
);
let mut poles = sys.poles();
poles.sort_unstable_by(|a, b| a.re.partial_cmp(&b.re).unwrap());
assert_relative_eq!(eig1, poles[0].re, max_relative = 1e-10);
assert_relative_eq!(eig2, poles[1].re, max_relative = 1e-10);
assert_relative_eq!(eig3, poles[2].re, max_relative = 1e-10);
}
#[test]
#[allow(clippy::float_cmp)]
fn leverrier_algorithm_f64() {
let t = Array2::from_shape_vec((3, 3), vec![3., 1., 5., 3., 3., 1., 4., 6., 4.]).unwrap();
let expected_pc = [-40., 4., -10., 1.];
let expected_degree0 =
Array2::from_shape_vec((3, 3), vec![6., 26., -14., -8., -8., 12., 6., -14., 6.])
.unwrap();
let expected_degree1 =
Array2::from_shape_vec((3, 3), vec![-7., 1., 5., 3., -7., 1., 4., 6., -6.]).unwrap();
let expected_degree2 =
Array2::from_shape_vec((3, 3), vec![1., 0., 0., 0., 1., 0., 0., 0., 1.]).unwrap();
let (p, poly_matrix) = leverrier(&t).unwrap();
println!("T: {}\np: {}\n", &t, &p);
println!("B: {}", &poly_matrix);
assert_eq!(expected_pc, p.to_unwrapped_vec().as_slice());
assert_eq!(expected_degree0, poly_matrix[0]);
assert_eq!(expected_degree1, poly_matrix[1]);
assert_eq!(expected_degree2, poly_matrix[2]);
let mp = MatrixOfPoly::from(poly_matrix);
println!("mp {}", &mp);
let expected_result = "[[6 -7s +1s^2, 26 +1s, -14 +5s],\n \
[-8 +3s, -8 -7s +1s^2, 12 +1s],\n \
[6 +4s, -14 +6s, 6 -6s +1s^2]]";
assert_eq!(expected_result, format!("{}", &mp));
}
#[test]
#[allow(clippy::float_cmp)]
fn leverrier_algorithm_f32() {
let t =
Array2::from_shape_vec((3, 3), vec![3.0_f32, 1., 5., 3., 3., 1., 4., 6., 4.]).unwrap();
let expected_pc = [-40., 4., -10., 1.];
let expected_degree0 =
Array2::from_shape_vec((3, 3), vec![6., 26., -14., -8., -8., 12., 6., -14., 6.])
.unwrap();
let expected_degree1 =
Array2::from_shape_vec((3, 3), vec![-7., 1., 5., 3., -7., 1., 4., 6., -6.]).unwrap();
let expected_degree2 =
Array2::from_shape_vec((3, 3), vec![1., 0., 0., 0., 1., 0., 0., 0., 1.]).unwrap();
let (p, poly_matrix) = leverrier(&t).unwrap();
println!("T: {}\np: {}\n", &t, &p);
println!("B: {}", &poly_matrix);
assert_eq!(expected_pc, p.to_unwrapped_vec().as_slice());
assert_eq!(expected_degree0, poly_matrix[0]);
assert_eq!(expected_degree1, poly_matrix[1]);
assert_eq!(expected_degree2, poly_matrix[2]);
let mp = MatrixOfPoly::from(poly_matrix);
println!("mp {}", &mp);
let expected_result = "[[6 -7s +1s^2, 26 +1s, -14 +5s],\n \
[-8 +3s, -8 -7s +1s^2, 12 +1s],\n \
[6 +4s, -14 +6s, 6 -6s +1s^2]]";
assert_eq!(expected_result, format!("{}", &mp));
}
#[test]
#[allow(clippy::float_cmp)]
fn leverrier_1x1_matrix_f32() {
let t = Array2::from_elem((1, 1), 3.0_f32);
let expected_pc = [-3., 1.];
let expected_degree0 = Array2::from_elem((1, 1), 1.);
let (p, poly_matrix) = leverrier(&t).unwrap();
assert_eq!(expected_pc, p.to_unwrapped_vec().as_slice());
assert_eq!(expected_degree0, poly_matrix[0]);
let mp = MatrixOfPoly::from(poly_matrix);
let expected_result = "[[1]]";
assert_eq!(expected_result, format!("{}", &mp));
}
#[test]
#[allow(clippy::float_cmp)]
fn leverrier_1x1_matrix_f64() {
let t = Array2::from_elem((1, 1), 3.);
let expected_pc = [-3., 1.];
let expected_degree0 = Array2::from_elem((1, 1), 1.);
let (p, poly_matrix) = leverrier(&t).unwrap();
assert_eq!(expected_pc, p.to_unwrapped_vec().as_slice());
assert_eq!(expected_degree0, poly_matrix[0]);
let mp = MatrixOfPoly::from(poly_matrix);
let expected_result = "[[1]]";
assert_eq!(expected_result, format!("{}", &mp));
}
#[test]
fn convert_to_ss_continuous() {
use crate::transfer_function::continuous::Tf;
let tf = Tf::new([1.], [1., 1., 1.]);
let ss = SsGen::new_controllability_realization(&tf).unwrap();
assert_eq!(
Array2::from_shape_vec((2, 2), vec![0., 1., -1., -1.]).unwrap(),
ss.a
);
assert_eq!(Array2::from_shape_vec((2, 1), vec![0., 1.]).unwrap(), ss.b);
assert_eq!(Array2::from_shape_vec((1, 2), vec![1., 0.]).unwrap(), ss.c);
assert_eq!(Array2::from_shape_vec((1, 1), vec![0.]).unwrap(), ss.d);
}
#[test]
fn convert_to_ss_discrete() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new([1., 0., 1.], [3., 4., 1.]);
let ss = SsGen::new_observability_realization(&tf).unwrap();
assert_eq!(
Array2::from_shape_vec((2, 2), vec![0., -3., 1., -4.]).unwrap(),
ss.a
);
assert_eq!(
Array2::from_shape_vec((2, 1), vec![-2., -4.]).unwrap(),
ss.b
);
assert_eq!(Array2::from_shape_vec((1, 2), vec![0., 1.]).unwrap(), ss.c);
assert_eq!(Array2::from_shape_vec((1, 1), vec![1.]).unwrap(), ss.d);
}
#[test]
fn failed_observability_realization() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new([1.], [0.]);
let ss = SsGen::new_observability_realization(&tf);
assert!(ss.is_err());
}
#[test]
fn failed_observability_canonical_form() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new([1.], [1.]);
let matrix = observability_canonical_form(&tf);
assert!(matrix.is_err());
}
#[test]
fn failed_controllability_realization() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new([1.], [0.]);
let ss = SsGen::new_controllability_realization(&tf);
assert!(ss.is_err());
}
#[test]
fn failed_controllability_canonical_form() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new([1.], [1.]);
let matrix = controllability_canonical_form(&tf);
assert!(matrix.is_err());
}
#[test]
fn controllability() {
let a = [-1., 3., 0., 2.];
let b = [1., 2.];
let c = [1., 1.];
let d = [0.];
let sys = SsGen::<f64, Discrete>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let mr = sys.controllability();
assert_eq!((2, 2, vec![1., 5., 2., 4.]), mr);
}
#[test]
fn observability() {
let a = [-1., 3., 0., 2.];
let b = [1., 2.];
let c = [1., 1.];
let d = [0.];
let sys = SsGen::<f64, Continuous>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let mo = sys.observability();
assert_eq!((2, 2, vec![1., -1., 1., 5.]), mo);
}
#[test]
fn linear_system_display() {
let a = [-1., 3., 0., 2.];
let b = [1., 2.];
let c = [1., 1.];
let d = [0.];
let sys = SsGen::<f64, Continuous>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let string = format!("{}", &sys);
assert!(!string.is_empty());
}
}