use std::ops::Neg;
use std::{
cmp::Ordering,
collections::VecDeque,
fmt::Debug,
iter::Sum,
ops::{Add, Div, Mul, Sub},
};
use crate::{
enums::Discrete, plots::Plotter, transfer_function::TfGen, Abs, Atan2, Complex, Cos, Hypot,
Infinity, Inv, One, Pow, Sin, Zero,
};
pub type Tfz<T> = TfGen<T, Discrete>;
impl<T> Tfz<T>
where
T: Abs
+ Add<Output = T>
+ Atan2
+ Clone
+ Cos
+ Div<Output = T>
+ Hypot
+ Infinity
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialEq
+ PartialOrd
+ Pow<T>
+ Sin
+ Sub<Output = T>
+ Zero,
{
pub fn delay(k: i32) -> impl Fn(Complex<T>) -> Complex<T> {
move |z| z.powi(-k)
}
#[must_use]
pub fn init_value(&self) -> T {
let n = self.num_int().0.degree();
let d = self.den_int().0.degree();
match n.cmp(&d) {
Ordering::Less => T::zero(),
Ordering::Equal => {
self.num_int().0.leading_coeff().0 / self.den_int().0.leading_coeff().0
}
Ordering::Greater => T::infinity(),
}
}
}
impl<'a, T> Tfz<T>
where
T: 'a + Add<&'a T, Output = T> + Clone + Div<Output = T> + PartialEq + Zero,
{
#[must_use]
pub fn static_gain(&'a self) -> T {
let n = self
.num_int()
.0
.as_slice()
.iter()
.fold(T::zero(), |acc, c| acc + &c.0);
let d = self
.den_int()
.0
.as_slice()
.iter()
.fold(T::zero(), |acc, c| acc + &c.0);
n / d
}
}
macro_rules! tfz_stable {
($ty:ty) => {
impl Tfz<$ty> {
#[doc = "System stability. Checks if all poles are inside the unit circle.\n"]
#[doc = "# Example\n```\nuse polynomen::Poly;\nuse automatica::Tfz;"]
#[doc = concat!("let tfz = Tfz::new([1.0_",stringify!($ty), "], Poly::new_from_roots(&[0.5, 1.5]));")]
#[doc = "assert!(!tfz.is_stable());\n```"]
#[must_use]
pub fn is_stable(&self) -> bool {
self.complex_poles().iter().all(|p| p.norm() < <$ty>::one())
}
}
};
}
tfz_stable!(f32);
tfz_stable!(f64);
macro_rules! arma {
($self:ident, $y_coeffs:ident, $u_coeffs:ident, $y:ident, $u:ident) => {{
let g = $self.normalize();
let n_n = g.num_int().0.degree().unwrap_or(0);
let n_d = g.den_int().0.degree().unwrap_or(0);
let n = n_n.max(n_d);
let mut output_coefficients: Vec<_> =
g.den_int().0.coeffs().iter().map(|x| x.0.clone()).collect();
output_coefficients.truncate(n_d);
$y_coeffs = output_coefficients;
$u_coeffs = g.num_int().0.coeffs().iter().map(|x| x.0.clone()).collect();
let length = n + 1;
$y = VecDeque::from(vec![T::zero(); length]);
$u = VecDeque::from(vec![T::zero(); length]);
}};
}
impl<T> Tfz<T>
where
T: Add<Output = T>
+ Clone
+ Div<Output = T>
+ Mul<Output = T>
+ One
+ PartialEq
+ Sub<Output = T>
+ Sum
+ Zero,
{
pub fn arma_fn<F>(&self, input: F) -> ArmaFn<F, T>
where
F: Fn(usize) -> T,
{
let y_coeffs: Vec<_>;
let u_coeffs: Vec<_>;
let y: VecDeque<_>;
let u: VecDeque<_>;
arma!(self, y_coeffs, u_coeffs, y, u);
ArmaFn {
y_coeffs,
u_coeffs,
y,
u,
input,
k: 0,
}
}
pub fn arma_iter<I, II>(&self, iter: II) -> ArmaIter<I, T>
where
II: IntoIterator<Item = T, IntoIter = I>,
I: Iterator<Item = T>,
{
let y_coeffs: Vec<_>;
let u_coeffs: Vec<_>;
let y: VecDeque<_>;
let u: VecDeque<_>;
arma!(self, y_coeffs, u_coeffs, y, u);
ArmaIter {
y_coeffs,
u_coeffs,
y,
u,
iter: iter.into_iter(),
}
}
}
#[derive(Debug)]
pub struct ArmaFn<F, T>
where
F: Fn(usize) -> T,
{
y_coeffs: Vec<T>,
u_coeffs: Vec<T>,
y: VecDeque<T>,
u: VecDeque<T>,
input: F,
k: usize,
}
macro_rules! arma_iter {
($self:ident, $current_input:ident) => {{
$self.u.push_back($current_input);
$self.u.pop_front();
let input: T = $self
.u_coeffs
.iter()
.zip(&$self.u)
.map(|(c, u)| c.clone() * u.clone())
.sum();
$self.y.push_back(T::zero());
$self.y.pop_front();
let old_output: T = $self
.y_coeffs
.iter()
.zip(&$self.y)
.map(|(c, y)| c.clone() * y.clone())
.sum();
let new_y = input - old_output;
debug_assert!(!$self.y.is_empty());
*$self.y.back_mut()? = new_y.clone();
Some(new_y)
}};
}
impl<F, T> Iterator for ArmaFn<F, T>
where
F: Fn(usize) -> T,
T: Clone + Mul<Output = T> + Sub<Output = T> + Sum + Zero,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let current_input = (self.input)(self.k);
self.k += 1;
arma_iter!(self, current_input)
}
}
#[derive(Debug)]
pub struct ArmaIter<I, T>
where
I: Iterator,
{
y_coeffs: Vec<T>,
u_coeffs: Vec<T>,
y: VecDeque<T>,
u: VecDeque<T>,
iter: I,
}
impl<I, T> Iterator for ArmaIter<I, T>
where
I: Iterator<Item = T>,
T: Clone + Mul<Output = T> + Sub<Output = T> + Sum + Zero,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let current_input = self.iter.next()?;
arma_iter!(self, current_input)
}
}
impl<T> Plotter<T> for Tfz<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Cos
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialEq
+ PartialOrd
+ Sin
+ Sub<Output = T>
+ Zero,
{
fn eval_point(&self, theta: T) -> Complex<T> {
self.eval(&Complex::from_polar(T::one(), theta))
}
}
#[cfg(test)]
mod tests {
use polynomen::Poly;
use crate::{signals::discrete, units::ToDecibel, Complex};
use super::*;
#[test]
fn tfz() {
let _tfz = Tfz::new([1.], [1., 2., 3.]);
}
#[test]
fn delay() {
let d = Tfz::delay(2);
assert_relative_eq!(0.010_000_001, d(Complex::new(0., 10.0_f32)).norm());
}
#[test]
fn initial_value() {
let tf = Tfz::new([4.], [1., 5.]);
assert_relative_eq!(0., tf.init_value());
let tf = Tfz::new([4., 10.], [1., 5.]);
assert_relative_eq!(2., tf.init_value());
let tf = Tfz::new([4., 1.], [5.]);
assert_relative_eq!(std::f32::INFINITY, tf.init_value());
}
#[test]
fn static_gain() {
let tf = Tfz::new([5., -3.], [2., 5., -6.]);
assert_relative_eq!(2., tf.static_gain());
}
#[test]
fn stability() {
let stable_den = Poly::new_from_roots(&[-0.3_f64, 0.5]);
let stable_tf = Tfz::new([1., 2.], stable_den);
assert!(stable_tf.is_stable());
let unstable_den = Poly::new_from_roots(&[0.0_f64, -2.]);
let unstable_tf = Tfz::new([1., 2.], unstable_den);
assert!(!unstable_tf.is_stable());
}
#[test]
fn eval() {
let tf = Tfz::new([2., 20.], [1., 0.1]);
let z = Complex::<f64>::new(0., 1.) * 0.5;
let g = tf.eval(&z);
assert_relative_eq!(20.159, g.norm().to_db(), max_relative = 1e-4);
assert_relative_eq!(75.828, g.arg().to_degrees(), max_relative = 1e-4);
}
#[test]
fn arma() {
let tfz = Tfz::new([0.5_f32], [-0.5, 1.]);
let mut iter = tfz.arma_fn(discrete::impulse(1., 0)).take(6);
assert_eq!(Some(0.), iter.next());
assert_eq!(Some(0.5), iter.next());
assert_eq!(Some(0.25), iter.next());
assert_eq!(Some(0.125), iter.next());
assert_eq!(Some(0.0625), iter.next());
assert_eq!(Some(0.03125), iter.next());
assert_eq!(None, iter.next());
}
#[test]
fn arma_iter() {
use std::iter;
let tfz = Tfz::new([0.5_f32], [-0.5, 1.]);
let mut iter = tfz.arma_iter(iter::once(1.).chain(iter::repeat(0.)).take(6));
assert_eq!(Some(0.), iter.next());
assert_eq!(Some(0.5), iter.next());
assert_eq!(Some(0.25), iter.next());
assert_eq!(Some(0.125), iter.next());
assert_eq!(Some(0.0625), iter.next());
assert_eq!(Some(0.03125), iter.next());
assert_eq!(None, iter.next());
}
}