pub mod continuous;
pub mod discrete;
pub mod solver;
use nalgebra::{ComplexField, DMatrix, DVector, RealField, Scalar};
use num_complex::Complex;
use num_traits::Float;
use std::{
fmt,
fmt::{Debug, Display, Formatter},
marker::PhantomData,
};
use crate::{
enums::Time,
error::{Error, ErrorKind},
polynomial,
polynomial::Poly,
polynomial_matrix::PolyMatrix,
transfer_function::TfGen,
};
#[derive(Clone, Debug, PartialEq)]
pub struct SsGen<T: Scalar, U: Time> {
pub(super) a: DMatrix<T>,
pub(super) b: DMatrix<T>,
pub(super) c: DMatrix<T>,
pub(super) d: DMatrix<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: Scalar, U: Time> SsGen<T, U> {
pub fn new_from_slice(
states: usize,
inputs: usize,
outputs: usize,
a: &[T],
b: &[T],
c: &[T],
d: &[T],
) -> Self {
let states_matrix = DMatrix::from_row_slice(states, states, a);
debug_assert!(states_matrix.is_square());
Self {
a: states_matrix,
b: DMatrix::from_row_slice(states, inputs, b),
c: DMatrix::from_row_slice(outputs, states, c),
d: DMatrix::from_row_slice(outputs, inputs, d),
dim: Dim {
states,
inputs,
outputs,
},
time: PhantomData,
}
}
#[must_use]
pub fn dim(&self) -> Dim {
self.dim
}
}
impl<T: ComplexField + Float + RealField, U: Time> SsGen<T, U> {
#[must_use]
pub fn poles(&self) -> Vec<Complex<T>> {
match self.dim().states() {
1 => vec![Complex::new(self.a[(0, 0)], T::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) = polynomial::complex_quadratic_roots(-trace, determinant);
vec![eig1, eig2]
}
_ => self.a.complex_eigenvalues().as_slice().to_vec(),
}
}
}
fn controllability_impl<T: RealField + Scalar>(
n: usize,
m: usize,
a: &DMatrix<T>,
b: &DMatrix<T>,
) -> DMatrix<T> {
let mut mr = DMatrix::<T>::zeros(n, n * m);
mr.columns_range_mut(0..m).copy_from(b);
let mut rhs = DMatrix::<T>::zeros(n, m);
for i in 1..n {
rhs.copy_from(&mr.columns_range(((i - 1) * m)..(i * m)));
a.mul_to(&rhs, &mut mr.columns_range_mut((i * m)..((i + 1) * m)))
}
mr
}
fn observability_impl<T: RealField + Scalar>(
n: usize,
p: usize,
a: &DMatrix<T>,
c: &DMatrix<T>,
) -> DMatrix<T> {
let mut mo = DMatrix::<T>::zeros(n, n * p);
mo.columns_range_mut(0..p).copy_from(&c.transpose());
let mut rhs = DMatrix::<T>::zeros(n, p);
for i in 1..n {
rhs.copy_from(&mo.columns_range(((i - 1) * p)..(i * p)));
a.tr_mul_to(&rhs, &mut mo.columns_range_mut((i * p)..((i + 1) * p)))
}
mo
}
impl<T: RealField + Scalar, U: Time> SsGen<T, U> {
#[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.data.as_vec().clone())
}
#[must_use]
pub fn osservability(&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.data.as_vec().clone())
}
}
macro_rules! leverrier {
($ty:ty, $name:ident) => {
#[allow(non_snake_case, clippy::cast_precision_loss)]
pub(super) fn $name(A: &DMatrix<$ty>) -> (Poly<$ty>, PolyMatrix<$ty>) {
let size = A.nrows(); let mut a = vec![1.0];
let a1 = -A.trace();
a.insert(0, a1);
let B1 = DMatrix::identity(size, size); let mut B = vec![B1.clone()];
if size == 1 {
return (Poly::new_from_coeffs(&a), PolyMatrix::new_from_coeffs(&B));
}
let mut Bk = B1.clone();
let mut ak = a1;
for k in 2..=size {
Bk = ak * &B1 + A * &Bk;
B.insert(0, Bk.clone());
let ABk = A * &Bk;
ak = -(k as $ty).recip() * ABk.trace();
a.insert(0, ak);
}
(Poly::new_from_coeffs(&a), PolyMatrix::new_from_coeffs(&B))
}
};
}
leverrier!(f64, leverrier_f64);
leverrier!(f32, leverrier_f32);
impl<T: ComplexField + Float + RealField, U: Time> SsGen<T, U> {
pub fn new_observability_realization(tf: &TfGen<T, U>) -> Result<Self, Error> {
let tf_norm = tf.normalize();
let order = match tf_norm.den().degree() {
Some(d) => d,
None => return Err(Error::new_internal(ErrorKind::ZeroPolynomialDenominator)),
};
let num = {
let mut num = tf_norm.num().clone();
num.extend(order);
num
};
let a = observability_canonical_form(&tf_norm)?;
let states = a.nrows();
let b_n = num[order];
let b = DMatrix::from_fn(states, 1, |i, _j| num[i] - tf_norm.den()[i] * b_n);
let c = {
let mut c = DMatrix::zeros(1, states);
c[states - 1] = T::one();
c
};
let d = DMatrix::from_element(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().degree() {
Some(d) => d,
None => return Err(Error::new_internal(ErrorKind::ZeroPolynomialDenominator)),
};
let num = {
let mut num = tf_norm.num().clone();
num.extend(order);
num
};
let a = controllability_canonical_form(&tf_norm)?;
let states = a.nrows();
let b_n = num[order];
let b = {
let mut b = DMatrix::zeros(states, 1);
b[states - 1] = T::one();
b
};
let c = DMatrix::from_fn(1, states, |_i, j| num[j] - tf_norm.den()[j] * b_n);
let d = DMatrix::from_element(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<DMatrix<T>, Error>
where
T: ComplexField + Float + RealField,
U: Time,
{
debug_assert!(tf.den().leading_coeff().is_one());
match tf.den().degree() {
Some(degree) if degree > 0 => {
let comp = DMatrix::from_fn(degree, degree, |i, j| {
if j == degree - 1 {
-tf.den()[i]
} 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<DMatrix<T>, Error>
where
T: ComplexField + Float + RealField,
U: Time,
{
debug_assert!(tf.den().leading_coeff().is_one());
match tf.den().degree() {
Some(degree) if degree > 0 => {
let comp = DMatrix::from_fn(degree, degree, |i, j| {
if i == degree - 1 {
-tf.den()[j]
} 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: Scalar + Display, U: Time> Display for SsGen<T, U> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"A: {}\nB: {}\nC: {}\nD: {}",
self.a, self.b, self.c, self.d
)
}
}
#[derive(Debug)]
pub struct Equilibrium<T: Scalar> {
x: DVector<T>,
y: DVector<T>,
}
impl<T: Scalar> Equilibrium<T> {
fn new(x: DVector<T>, y: DVector<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 + Scalar> Display for Equilibrium<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "x: {}\ny: {}", self.x, self.y)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{polynomial_matrix::MatrixOfPoly, Continuous, Discrete};
use nalgebra::DMatrix;
use proptest::prelude::*;
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::<_, 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 = 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::<_, 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::<_, 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::<_, 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::<_, 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]
fn leverrier_algorythm_f64() {
let t = DMatrix::from_row_slice(3, 3, &[3., 1., 5., 3., 3., 1., 4., 6., 4.]);
let expected_pc = Poly::new_from_coeffs(&[-40., 4., -10., 1.]);
let expected_degree0 =
DMatrix::from_row_slice(3, 3, &[6., 26., -14., -8., -8., 12., 6., -14., 6.]);
let expected_degree1 =
DMatrix::from_row_slice(3, 3, &[-7., 1., 5., 3., -7., 1., 4., 6., -6.]);
let expected_degree2 = DMatrix::from_row_slice(3, 3, &[1., 0., 0., 0., 1., 0., 0., 0., 1.]);
let (p, poly_matrix) = leverrier_f64(&t);
println!("T: {}\np: {}\n", &t, &p);
println!("B: {}", &poly_matrix);
assert_eq!(expected_pc, p);
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]
fn leverrier_algorythm_f32() {
let t = DMatrix::from_row_slice(3, 3, &[3., 1., 5., 3., 3., 1., 4., 6., 4.]);
let expected_pc = Poly::new_from_coeffs(&[-40., 4., -10., 1.]);
let expected_degree0 =
DMatrix::from_row_slice(3, 3, &[6., 26., -14., -8., -8., 12., 6., -14., 6.]);
let expected_degree1 =
DMatrix::from_row_slice(3, 3, &[-7., 1., 5., 3., -7., 1., 4., 6., -6.]);
let expected_degree2 = DMatrix::from_row_slice(3, 3, &[1., 0., 0., 0., 1., 0., 0., 0., 1.]);
let (p, poly_matrix) = leverrier_f32(&t);
println!("T: {}\np: {}\n", &t, &p);
println!("B: {}", &poly_matrix);
assert_eq!(expected_pc, p);
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]
fn leverrier_1x1_matrix_f32() {
let t = DMatrix::from_row_slice(1, 1, &[3.]);
let expected_pc = Poly::new_from_coeffs(&[-3., 1.]);
let expected_degree0 = DMatrix::from_row_slice(1, 1, &[1.]);
let (p, poly_matrix) = leverrier_f32(&t);
assert_eq!(expected_pc, p);
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 leverrier_1x1_matrix_f64() {
let t = DMatrix::from_row_slice(1, 1, &[3.]);
let expected_pc = Poly::new_from_coeffs(&[-3., 1.]);
let expected_degree0 = DMatrix::from_row_slice(1, 1, &[1.]);
let (p, poly_matrix) = leverrier_f64(&t);
assert_eq!(expected_pc, p);
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(
Poly::new_from_coeffs(&[1.]),
Poly::new_from_coeffs(&[1., 1., 1.]),
);
let ss = SsGen::new_controllability_realization(&tf).unwrap();
assert_eq!(DMatrix::from_row_slice(2, 2, &[0., 1., -1., -1.]), ss.a);
assert_eq!(DMatrix::from_row_slice(2, 1, &[0., 1.]), ss.b);
assert_eq!(DMatrix::from_row_slice(1, 2, &[1., 0.]), ss.c);
assert_eq!(DMatrix::from_row_slice(1, 1, &[0.]), ss.d);
}
#[test]
fn convert_to_ss_discrete() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new(
Poly::new_from_coeffs(&[1., 0., 1.]),
Poly::new_from_coeffs(&[3., 4., 1.]),
);
let ss = SsGen::new_observability_realization(&tf).unwrap();
assert_eq!(DMatrix::from_row_slice(2, 2, &[0., -3., 1., -4.]), ss.a);
assert_eq!(DMatrix::from_row_slice(2, 1, &[-2., -4.]), ss.b);
assert_eq!(DMatrix::from_row_slice(1, 2, &[0., 1.]), ss.c);
assert_eq!(DMatrix::from_row_slice(1, 1, &[1.]), ss.d);
}
#[test]
fn failed_observability_realization() {
use crate::transfer_function::discrete::Tfz;
let tf = Tfz::new(Poly::new_from_coeffs(&[1.]), Poly::new_from_coeffs(&[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(Poly::new_from_coeffs(&[1.]), Poly::new_from_coeffs(&[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(Poly::new_from_coeffs(&[1.]), Poly::new_from_coeffs(&[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(Poly::new_from_coeffs(&[1.]), Poly::new_from_coeffs(&[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::<_, Discrete>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let mr = sys.controllability();
assert_eq!((2, 2, vec![1., 2., 5., 4.]), mr);
}
#[test]
fn osservability() {
let a = [-1., 3., 0., 2.];
let b = [1., 2.];
let c = [1., 1.];
let d = [0.];
let sys = SsGen::<_, Continuous>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let mo = sys.osservability();
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::<_, Continuous>::new_from_slice(2, 1, 1, &a, &b, &c, &d);
let string = format!("{}", &sys);
assert!(!string.is_empty());
}
}