au/transfer_function/
discrete.rs

1//! # Transfer functions for discrete time systems.
2//!
3//! Specialized struct and methods for discrete time transfer functions
4//! * time delay
5//! * initial value
6//! * static gain
7//! * ARMA (autoregressive moving average) time evaluation method
8//!
9//! This module contains the discretization struct of a continuous time
10//! transfer function
11//! * forward Euler mehtod
12//! * backward Euler method
13//! * Tustin (trapezoidal) method
14
15use nalgebra::RealField;
16use num_complex::Complex;
17use num_traits::{Float, Zero};
18
19use std::{
20    cmp::Ordering,
21    collections::VecDeque,
22    fmt::Debug,
23    iter::Sum,
24    ops::{Add, Div, Mul},
25};
26
27use crate::{enums::Discrete, plots::Plotter, transfer_function::TfGen};
28
29/// Discrete transfer function
30pub type Tfz<T> = TfGen<T, Discrete>;
31
32impl<T: Float> Tfz<T> {
33    /// Time delay for discrete time transfer function.
34    /// `y(k) = u(k - h)`
35    /// `G(z) = z^(-h)
36    ///
37    /// # Arguments
38    ///
39    /// * `h` - Time delay
40    ///
41    /// # Example
42    /// ```
43    /// use au::{num_complex::Complex, units::Seconds, Tfz};
44    /// let d = Tfz::delay(2);
45    /// assert_eq!(0.010000001, d(Complex::new(0., 10.0_f32)).norm());
46    /// ```
47    pub fn delay(k: i32) -> impl Fn(Complex<T>) -> Complex<T> {
48        move |z| z.powi(-k)
49    }
50
51    /// System inital value response to step input.
52    /// `y(0) = G(z->infinity)`
53    ///
54    /// # Example
55    /// ```
56    /// use au::{poly, Tfz};
57    /// let tf = Tfz::new(poly!(4.), poly!(1., 5.));
58    /// assert_eq!(0., tf.init_value());
59    /// ```
60    #[must_use]
61    pub fn init_value(&self) -> T {
62        let n = self.num().degree();
63        let d = self.den().degree();
64        match n.cmp(&d) {
65            Ordering::Less => T::zero(),
66            Ordering::Equal => self.num().leading_coeff() / self.den().leading_coeff(),
67            Ordering::Greater => T::infinity(),
68        }
69    }
70}
71
72impl<'a, T: 'a + Add<&'a T, Output = T> + Div<Output = T> + Zero> Tfz<T> {
73    /// Static gain `G(1)`.
74    /// Ratio between constant output and constant input.
75    /// Static gain is defined only for transfer functions of 0 type.
76    ///
77    /// Example
78    ///
79    /// ```
80    /// use au::{poly, Tfz};
81    /// let tf = Tfz::new(poly!(5., -3.),poly!(2., 5., -6.));
82    /// assert_eq!(2., tf.static_gain());
83    /// ```
84    #[must_use]
85    pub fn static_gain(&'a self) -> T {
86        let n = self
87            .num()
88            .as_slice()
89            .iter()
90            .fold(T::zero(), |acc, c| acc + c);
91        let d = self
92            .den()
93            .as_slice()
94            .iter()
95            .fold(T::zero(), |acc, c| acc + c);
96        n / d
97    }
98}
99
100impl<T: Float + RealField> Tfz<T> {
101    /// System stability. Checks if all poles are inside the unit circle.
102    ///
103    /// # Example
104    ///
105    /// ```
106    /// use au::{Poly, Tfz};
107    /// let tfz = Tfz::new(Poly::new_from_coeffs(&[1.]), Poly::new_from_roots(&[0.5, 1.5]));
108    /// assert!(!tfz.is_stable());
109    /// ```
110    #[must_use]
111    pub fn is_stable(&self) -> bool {
112        self.complex_poles().iter().all(|p| p.norm() < T::one())
113    }
114}
115
116/// Macro defining the common behaviour when creating the arma iterator.
117///
118/// # Arguments
119///
120/// * `self` - `self` parameter keyword
121/// * `y_coeffs` - vector containing the coefficients of the output
122/// * `u_coeffs` - vector containing the coefficients of the input
123/// * `y` - queue containing the calculated outputs
124/// * `u` - queue containing the supplied inputs
125macro_rules! arma {
126    ($self:ident, $y_coeffs:ident, $u_coeffs:ident, $y:ident, $u:ident) => {{
127        let g = $self.normalize();
128        let n_n = g.num().degree().unwrap_or(0);
129        let n_d = g.den().degree().unwrap_or(0);
130        let n = n_n.max(n_d);
131
132        // The front is the lowest order coefficient.
133        // The back is the higher order coefficient.
134        // The higher degree terms are the more recent.
135        // The last coefficient is always 1, because g is normalized.
136        // [a0, a1, a2, ..., a(n-1), 1]
137        let mut output_coefficients = g.den().coeffs();
138        // Remove the last coefficient by truncating the vector by one.
139        // This is done because the last coefficient of the denominator corresponds
140        // to the currently calculated output.
141        output_coefficients.truncate(n_d);
142        // [a0, a1, a2, ..., a(n-1)]
143        $y_coeffs = output_coefficients;
144        // [b0, b1, b2, ..., bm]
145        $u_coeffs = g.num().coeffs();
146
147        // The coefficients do not need to be extended with zeros,
148        // when the coffiecients are 'zipped' with the VecDeque, the zip stops at the
149        // shortest iterator.
150
151        let length = n + 1;
152        // The front is the oldest calculated output.
153        // [y(k-n), y(k-n+1), ..., y(k-1), y(k)]
154        $y = VecDeque::from(vec![T::zero(); length]);
155        // The front is the oldest input.
156        // [u(k-n), u(k-n+1), ..., u(k-1), u(k)]
157        $u = VecDeque::from(vec![T::zero(); length]);
158    }};
159}
160
161impl<T: Float + Mul<Output = T> + Sum> Tfz<T> {
162    /// Autoregressive moving average representation of a discrete transfer function
163    /// It transforms the transfer function into time domain input-output
164    /// difference equation.
165    /// ```text
166    ///                   b_n*z^n + b_(n-1)*z^(n-1) + ... + b_1*z + b_0
167    /// Y(z) = G(z)U(z) = --------------------------------------------- U(z)
168    ///                     z^n + a_(n-1)*z^(n-1) + ... + a_1*z + a_0
169    ///
170    /// y(k) = - a_(n-1)*y(k-1) - ... - a_1*y(k-n+1) - a_0*y(k-n) +
171    ///        + b_n*u(k) + b_(n-1)*u(k-1) + ... + b_1*u(k-n+1) + b_0*u(k-n)
172    /// ```
173    ///
174    /// # Arguments
175    ///
176    /// * `input` - Input function
177    ///
178    /// # Example
179    /// ```
180    /// use au::{poly, signals::discrete, Tfz};
181    /// let tfz = Tfz::new(poly!(1., 2., 3.), poly!(0., 0., 0., 1.));
182    /// let mut iter = tfz.arma_fn(discrete::step(1., 0));
183    /// assert_eq!(Some(0.), iter.next());
184    /// assert_eq!(Some(3.), iter.next());
185    /// assert_eq!(Some(5.), iter.next());
186    /// assert_eq!(Some(6.), iter.next());
187    /// ```
188    pub fn arma_fn<F>(&self, input: F) -> ArmaFn<F, T>
189    where
190        F: Fn(usize) -> T,
191    {
192        let y_coeffs: Vec<_>;
193        let u_coeffs: Vec<_>;
194        let y: VecDeque<_>;
195        let u: VecDeque<_>;
196        arma!(self, y_coeffs, u_coeffs, y, u);
197
198        ArmaFn {
199            y_coeffs,
200            u_coeffs,
201            y,
202            u,
203            input,
204            k: 0,
205        }
206    }
207
208    /// Autoregressive moving average representation of a discrete transfer function
209    /// It transforms the transfer function into time domain input-output
210    /// difference equation.
211    /// ```text
212    ///                   b_n*z^n + b_(n-1)*z^(n-1) + ... + b_1*z + b_0
213    /// Y(z) = G(z)U(z) = --------------------------------------------- U(z)
214    ///                     z^n + a_(n-1)*z^(n-1) + ... + a_1*z + a_0
215    ///
216    /// y(k) = - a_(n-1)*y(k-1) - ... - a_1*y(k-n+1) - a_0*y(k-n) +
217    ///        + b_n*u(k) + b_(n-1)*u(k-1) + ... + b_1*u(k-n+1) + b_0*u(k-n)
218    /// ```
219    ///
220    /// # Arguments
221    ///
222    /// * `iter` - Iterator supplying the input data to the model
223    ///
224    /// # Example
225    /// ```
226    /// use au::{poly, signals::discrete, Tfz};
227    /// let tfz = Tfz::new(poly!(1., 2., 3.), poly!(0., 0., 0., 1.));
228    /// let mut iter = tfz.arma_iter(std::iter::repeat(1.));
229    /// assert_eq!(Some(0.), iter.next());
230    /// assert_eq!(Some(3.), iter.next());
231    /// assert_eq!(Some(5.), iter.next());
232    /// assert_eq!(Some(6.), iter.next());
233    /// ```
234    pub fn arma_iter<I, II>(&self, iter: II) -> ArmaIter<I, T>
235    where
236        II: IntoIterator<Item = T, IntoIter = I>,
237        I: Iterator<Item = T>,
238    {
239        let y_coeffs: Vec<_>;
240        let u_coeffs: Vec<_>;
241        let y: VecDeque<_>;
242        let u: VecDeque<_>;
243        arma!(self, y_coeffs, u_coeffs, y, u);
244
245        ArmaIter {
246            y_coeffs,
247            u_coeffs,
248            y,
249            u,
250            iter: iter.into_iter(),
251        }
252    }
253}
254
255/// Iterator for the autoregressive moving average model of a discrete
256/// transfer function.
257/// The input is supplied through a function.
258#[derive(Debug)]
259pub struct ArmaFn<F, T>
260where
261    F: Fn(usize) -> T,
262{
263    /// y coefficients
264    y_coeffs: Vec<T>,
265    /// u coefficients
266    u_coeffs: Vec<T>,
267    /// y queue buffer
268    y: VecDeque<T>,
269    /// u queue buffer
270    u: VecDeque<T>,
271    /// input function
272    input: F,
273    /// step
274    k: usize,
275}
276
277/// Macro containing the common iteration steps of the ARMA model
278///
279/// # Arguments
280///
281/// * `self` - `self` keyword parameter
282macro_rules! arma_iter {
283    ($self:ident, $current_input:ident) => {{
284        // Push the current input into the most recent position of the input buffer.
285        $self.u.push_back($current_input);
286        // Discard oldest input.
287        $self.u.pop_front();
288        let input: T = $self
289            .u_coeffs
290            .iter()
291            .zip(&$self.u)
292            .map(|(&c, &u)| c * u)
293            .sum();
294
295        // Push zero in the last position shifting output values one step back
296        // in time, zero suppress last coefficient which shall be the current
297        // calculated output value.
298        $self.y.push_back(T::zero());
299        // Discard oldest output.
300        $self.y.pop_front();
301        let old_output: T = $self
302            .y_coeffs
303            .iter()
304            .zip(&$self.y)
305            .map(|(&c, &y)| c * y)
306            .sum();
307
308        // Calculate the output.
309        let new_y = input - old_output;
310        // Put the new calculated value in the last position of the buffer.
311        // `back_mut` returns None if the Deque is empty, this should never happen.
312        debug_assert!(!$self.y.is_empty());
313        *$self.y.back_mut()? = new_y;
314        Some(new_y)
315    }};
316}
317
318impl<F, T> Iterator for ArmaFn<F, T>
319where
320    F: Fn(usize) -> T,
321    T: Float + Mul<Output = T> + Sum,
322{
323    type Item = T;
324
325    fn next(&mut self) -> Option<Self::Item> {
326        let current_input = (self.input)(self.k);
327        self.k += 1;
328        arma_iter!(self, current_input)
329    }
330}
331
332/// Iterator for the autoregressive moving average model of a discrete
333/// transfer function.
334/// The input is supplied through an iterator.
335#[derive(Debug)]
336pub struct ArmaIter<I, T>
337where
338    I: Iterator,
339{
340    /// y coefficients
341    y_coeffs: Vec<T>,
342    /// u coefficients
343    u_coeffs: Vec<T>,
344    /// y queue buffer
345    y: VecDeque<T>,
346    /// u queue buffer
347    u: VecDeque<T>,
348    /// input iterator
349    iter: I,
350}
351
352impl<I, T> Iterator for ArmaIter<I, T>
353where
354    I: Iterator<Item = T>,
355    T: Float + Mul<Output = T> + Sum,
356{
357    type Item = T;
358
359    fn next(&mut self) -> Option<Self::Item> {
360        let current_input = self.iter.next()?;
361        arma_iter!(self, current_input)
362    }
363}
364
365impl<T: Float> Plotter<T> for Tfz<T> {
366    /// Evaluate the transfer function at the given value.
367    ///
368    /// # Arguments
369    ///
370    /// * `theta` - angle at which the function is evaluated.
371    /// Evaluation occurs at G(e^(i*theta)).
372    fn eval_point(&self, theta: T) -> Complex<T> {
373        self.eval(&Complex::from_polar(T::one(), theta))
374    }
375}
376
377#[cfg(test)]
378mod tests {
379    use super::*;
380    use crate::{poly, polynomial::Poly, signals::discrete, units::ToDecibel};
381    use num_complex::Complex64;
382
383    #[test]
384    fn tfz() {
385        let _ = Tfz::new(poly!(1.), poly!(1., 2., 3.));
386    }
387
388    #[test]
389    fn delay() {
390        let d = Tfz::delay(2);
391        assert_relative_eq!(0.010_000_001, d(Complex::new(0., 10.0_f32)).norm());
392    }
393
394    #[test]
395    fn initial_value() {
396        let tf = Tfz::new(poly!(4.), poly!(1., 5.));
397        assert_relative_eq!(0., tf.init_value());
398
399        let tf = Tfz::new(poly!(4., 10.), poly!(1., 5.));
400        assert_relative_eq!(2., tf.init_value());
401
402        let tf = Tfz::new(poly!(4., 1.), poly!(5.));
403        assert_relative_eq!(std::f32::INFINITY, tf.init_value());
404    }
405
406    #[test]
407    fn static_gain() {
408        let tf = Tfz::new(poly!(5., -3.), poly!(2., 5., -6.));
409        assert_relative_eq!(2., tf.static_gain());
410    }
411
412    #[test]
413    fn stability() {
414        let stable_den = Poly::new_from_roots(&[-0.3, 0.5]);
415        let stable_tf = Tfz::new(poly!(1., 2.), stable_den);
416        assert!(stable_tf.is_stable());
417
418        let unstable_den = Poly::new_from_roots(&[0., -2.]);
419        let unstable_tf = Tfz::new(poly!(1., 2.), unstable_den);
420        assert!(!unstable_tf.is_stable());
421    }
422
423    #[test]
424    fn eval() {
425        let tf = Tfz::new(
426            Poly::new_from_coeffs(&[2., 20.]),
427            Poly::new_from_coeffs(&[1., 0.1]),
428        );
429        let z = 0.5 * Complex64::i();
430        let g = tf.eval(&z);
431        assert_relative_eq!(20.159, g.norm().to_db(), max_relative = 1e-4);
432        assert_relative_eq!(75.828, g.arg().to_degrees(), max_relative = 1e-4);
433    }
434
435    #[test]
436    fn arma() {
437        let tfz = Tfz::new(poly!(0.5_f32), poly!(-0.5, 1.));
438        let mut iter = tfz.arma_fn(discrete::impulse(1., 0)).take(6);
439        assert_eq!(Some(0.), iter.next());
440        assert_eq!(Some(0.5), iter.next());
441        assert_eq!(Some(0.25), iter.next());
442        assert_eq!(Some(0.125), iter.next());
443        assert_eq!(Some(0.0625), iter.next());
444        assert_eq!(Some(0.03125), iter.next());
445        assert_eq!(None, iter.next());
446    }
447
448    #[test]
449    fn arma_iter() {
450        use std::iter;
451        let tfz = Tfz::new(poly!(0.5_f32), poly!(-0.5, 1.));
452        let mut iter = tfz.arma_iter(iter::once(1.).chain(iter::repeat(0.)).take(6));
453        assert_eq!(Some(0.), iter.next());
454        assert_eq!(Some(0.5), iter.next());
455        assert_eq!(Some(0.25), iter.next());
456        assert_eq!(Some(0.125), iter.next());
457        assert_eq!(Some(0.0625), iter.next());
458        assert_eq!(Some(0.03125), iter.next());
459        assert_eq!(None, iter.next());
460    }
461}