use num_complex::Complex;
use num_traits::{Float, Num};
use std::fmt::Debug;
use crate::{
complex,
enums::Discretization,
polynomial::Poly,
transfer_function::{continuous::Tf, discrete::Tfz},
units::{RadiansPerSecond, Seconds},
};
#[derive(Debug)]
pub struct TfDiscretization<T: Num> {
tf: Tf<T>,
ts: Seconds<T>,
conversion: fn(Complex<T>, Seconds<T>) -> Complex<T>,
}
impl<T: Num> TfDiscretization<T> {
fn new_from_cont(
tf: Tf<T>,
ts: Seconds<T>,
conversion: fn(Complex<T>, Seconds<T>) -> Complex<T>,
) -> Self {
Self { tf, ts, conversion }
}
}
impl<T: Float> TfDiscretization<T> {
pub fn discretize(tf: Tf<T>, ts: Seconds<T>, method: Discretization) -> Self {
let conv = match method {
Discretization::ForwardEuler => fe,
Discretization::BackwardEuler => fb,
Discretization::Tustin => tu,
};
Self::new_from_cont(tf, ts, conv)
}
}
impl<T: Float> Tf<T> {
pub fn discretize(&self, ts: Seconds<T>, method: Discretization) -> Tfz<T> {
match method {
Discretization::ForwardEuler => {
let t = ts.0.recip();
let s = Poly::new_from_coeffs(&[-t, t]);
let num = self.num().eval_by_val(s.clone());
let den = self.den().eval_by_val(s);
Tfz::new(num, den)
}
Discretization::BackwardEuler => {
let s_num = Poly::new_from_coeffs(&[-T::one(), T::one()]);
let s_den = Poly::new_from_coeffs(&[T::zero(), ts.0]);
discr_impl(self, &s_num, &s_den)
}
Discretization::Tustin => {
let k = (T::one() + T::one()) / ts.0;
let s_num = Poly::new_from_coeffs(&[-T::one(), T::one()]) * k;
let s_den = Poly::new_from_coeffs(&[T::one(), T::one()]);
discr_impl(self, &s_num, &s_den)
}
}
}
pub fn discretize_with_warp(&self, ts: Seconds<T>, warp_freq: RadiansPerSecond<T>) -> Tfz<T> {
let two = T::one() + T::one();
let k = warp_freq.0 / (warp_freq.0 * ts.0 / two).tan();
let s_num = Poly::new_from_coeffs(&[-T::one(), T::one()]) * k;
let s_den = Poly::new_from_coeffs(&[T::one(), T::one()]);
discr_impl(self, &s_num, &s_den)
}
}
#[allow(clippy::cast_sign_loss)]
fn discr_impl<T: Float>(tf: &Tf<T>, s_num: &Poly<T>, s_den: &Poly<T>) -> Tfz<T> {
let s = Tf::new(s_num.clone(), s_den.clone());
let num = tf.num().eval(&s).num().clone();
let den = tf.den().eval(&s).num().clone();
match tf.relative_degree() {
g if g > 0 => {
let num = num * s_den.powi(g as u32);
Tfz::new(num, den)
}
g if g < 0 => {
let den = den * s_num.powi(-g as u32);
Tfz::new(num, den)
}
_ => Tfz::new(num, den),
}
}
fn fe<T: Float>(z: Complex<T>, ts: Seconds<T>) -> Complex<T> {
(z - T::one()) / ts.0
}
fn fb<T: Float>(z: Complex<T>, ts: Seconds<T>) -> Complex<T> {
(z - T::one()) / (z * ts.0)
}
fn tu<T: Float>(z: Complex<T>, ts: Seconds<T>) -> Complex<T> {
let float = (T::one() + T::one()) / ts.0;
let complex = complex::compdiv(z - T::one(), z + T::one());
complex * float
}
impl<T: Float> TfDiscretization<T> {
pub fn eval(&self, z: Complex<T>) -> Complex<T> {
let s = (self.conversion)(z, self.ts);
self.tf.eval(&s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{polynomial::Poly, units::ToDecibel};
use num_complex::Complex64;
#[test]
fn new_tfz() {
let _t = TfDiscretization {
tf: Tf::new(
Poly::new_from_coeffs(&[0., 1.]),
Poly::new_from_coeffs(&[1., 1., 1.]),
),
ts: Seconds(0.1),
conversion: |_z, _ts| 1.0 * Complex64::i(),
};
}
#[test]
fn eval_forward_euler() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let z = 0.5 * Complex64::i();
let ts = Seconds(1.);
let tfz = TfDiscretization::discretize(tf, ts, Discretization::ForwardEuler);
let s = (tfz.conversion)(z, ts);
assert_relative_eq!(-1.0, s.re);
assert_relative_eq!(0.5, s.im);
let gz = tfz.eval(z);
assert_relative_eq!(27.175, gz.norm().to_db(), max_relative = 1e-4);
assert_relative_eq!(147.77, gz.arg().to_degrees(), max_relative = 1e-4);
}
#[test]
fn eval_backward_euler() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let z = 0.5 * Complex64::i();
let ts = Seconds(1.);
let tfz = TfDiscretization::discretize(tf, ts, Discretization::BackwardEuler);
let s = (tfz.conversion)(z, ts);
assert_relative_eq!(1.0, s.re);
assert_relative_eq!(2.0, s.im);
let gz = tfz.eval(z);
assert_relative_eq!(32.220, gz.norm().to_db(), max_relative = 1e-4);
assert_relative_eq!(50.884, gz.arg().to_degrees(), max_relative = 1e-4);
}
#[test]
fn eval_tustin() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let z = 0.5 * Complex64::i();
let ts = Seconds(1.);
let tfz = TfDiscretization::discretize(tf, ts, Discretization::Tustin);
let s = (tfz.conversion)(z, ts);
assert_relative_eq!(-1.2, s.re);
assert_relative_eq!(1.6, s.im);
let gz = tfz.eval(z);
assert_relative_eq!(32.753, gz.norm().to_db(), max_relative = 1e-4);
assert_relative_eq!(114.20, gz.arg().to_degrees(), max_relative = 1e-4);
}
#[test]
fn discretization_forward_euler() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let tfz = tf
.discretize(Seconds(1.), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[-180., 200.]),
Poly::new_from_coeffs(&[9., 1.]),
);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_forward_euler2() {
let tf = Tf::new(
Poly::new_from_coeffs(&[1., 2., 3.]),
Poly::new_from_coeffs(&[4., 5.]),
);
let tfz = tf
.discretize(Seconds(0.1), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[5.62, -11.6, 6.]),
Poly::new_from_coeffs(&[-0.92, 1.]),
);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_forward_euler3() {
let tf = Tf::new(
Poly::new_from_coeffs(&[4.0, 5.]),
Poly::new_from_coeffs(&[3., 2., 1.]),
);
let tfz = tf
.discretize(Seconds(0.1), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[-0.46, 0.5]),
Poly::new_from_coeffs(&[0.83, -1.8, 1.]),
);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_backward_euler() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let tfz = tf
.discretize(Seconds(1.), Discretization::BackwardEuler)
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[-20. / 1.1, 20.]),
Poly::new_from_coeffs(&[-0.1 / 1.1, 1.]),
);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_tustin() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2., 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let tfz = tf
.discretize(Seconds(1.), Discretization::Tustin)
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[-38. / 1.2, 35.]),
Poly::new_from_coeffs(&[0.8 / 1.2, 1.]),
);
assert_eq!(expected, tfz);
}
#[test]
fn frequency_warping() {
let tf = Tf::new(
Poly::new_from_coeffs(&[2.0_f32, 20.]),
Poly::new_from_coeffs(&[1., 0.1]),
);
let tfz = tf
.discretize_with_warp(Seconds(1.), RadiansPerSecond(0.1))
.normalize();
let expected = Tfz::new(
Poly::new_from_coeffs(&[-31.643_282, 34.977_077]),
Poly::new_from_coeffs(&[0.666_898_2, 1.]),
);
assert_eq!(expected, tfz);
}
}