use std::{
fmt::Debug,
ops::{Add, Div, Mul, Neg, Sub},
};
use polynomen::Poly;
use crate::{
enums::Discretization,
transfer_function::{continuous::Tf, discrete::Tfz},
units::{RadiansPerSecond, Seconds},
wrappers::{CWrapper, PWrapper},
Abs, Complex, Inv, One, Tan, Zero,
};
#[derive(Debug)]
pub struct TfDiscretization<T> {
tf: Tf<T>,
ts: Seconds<T>,
conversion: fn(Complex<T>, Seconds<T>) -> Complex<T>,
}
impl<T> TfDiscretization<T>
where
T: Clone + PartialEq,
{
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> TfDiscretization<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialEq
+ PartialOrd
+ Sub<Output = T>
+ Zero,
{
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> Tf<T>
where
T: Add<Output = T>
+ Clone
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialEq
+ Tan
+ Zero,
{
pub fn discretize(&self, ts: Seconds<T>, method: Discretization) -> Tfz<T> {
use polynomen::{One, Zero};
match method {
Discretization::ForwardEuler => {
let t = ts.0.inv();
let s = Poly::new_from_coeffs(&[PWrapper(-t.clone()), PWrapper(t)]);
let num = self.num_int().0.eval_by_val(s.clone());
let den = self.den_int().0.eval_by_val(s);
Tfz::new_from_poly(num, den)
}
Discretization::BackwardEuler => {
let s_num = Poly::new_from_coeffs(&[-PWrapper::one(), PWrapper::one()]);
let s_den = Poly::new_from_coeffs(&[PWrapper::zero(), PWrapper(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(&[-PWrapper::one(), PWrapper::one()]) * PWrapper(k);
let s_den = Poly::new_from_coeffs(&[PWrapper::one(), PWrapper::one()]);
discr_impl(self, &s_num, &s_den)
}
}
}
pub fn discretize_with_warp(&self, ts: Seconds<T>, warp_freq: RadiansPerSecond<T>) -> Tfz<T> {
use polynomen::One;
let two = T::one() + T::one();
let k = warp_freq.0.clone() / (warp_freq.0 * ts.0 / two).tan();
let s_num = Poly::new_from_coeffs(&[-PWrapper::one(), PWrapper::one()]) * PWrapper(k);
let s_den = Poly::new_from_coeffs(&[PWrapper::one(), PWrapper::one()]);
discr_impl(self, &s_num, &s_den)
}
}
#[allow(clippy::cast_sign_loss)]
fn discr_impl<T>(tf: &Tf<T>, s_num: &Poly<PWrapper<T>>, s_den: &Poly<PWrapper<T>>) -> Tfz<T>
where
T: Add<Output = T> + Clone + Mul<Output = T> + One + PartialEq + Zero,
{
let s = Tf::new_from_poly(s_num.clone(), s_den.clone());
let num = tf
.num_int()
.0
.eval_by_val(PWrapper(s.clone()))
.0
.num_int()
.0
.clone();
let den = tf
.den_int()
.0
.eval_by_val(PWrapper(s))
.0
.num_int()
.0
.clone();
match tf.relative_degree() {
g if g > 0 => {
let num = num * s_den.powi(g as u32);
Tfz::new_from_poly(num, den)
}
g if g < 0 => {
let den = den * s_den.powi(-g as u32);
Tfz::new_from_poly(num, den)
}
_ => Tfz::new_from_poly(num, den),
}
}
fn fe<T>(z: Complex<T>, ts: Seconds<T>) -> Complex<T>
where
T: Clone + Div<Output = T> + One + Sub<Output = T>,
{
(z - T::one()) / ts.0
}
fn fb<T>(z: Complex<T>, ts: Seconds<T>) -> Complex<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialOrd
+ Sub<Output = T>
+ Zero,
{
(z.clone() - T::one()) / (z * ts.0)
}
fn tu<T>(z: Complex<T>, ts: Seconds<T>) -> Complex<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ One
+ PartialOrd
+ Sub<Output = T>
+ Zero,
{
let float = (T::one() + T::one()) / ts.0;
let num = z.clone() - T::one();
let den = z + T::one();
let (re, im) = complex_division::compdiv(
CWrapper(num.re),
CWrapper(num.im),
CWrapper(den.re),
CWrapper(den.im),
);
let complex = Complex::new(re.0, im.0);
complex * float
}
impl<T> TfDiscretization<T>
where
T: Abs
+ Add<Output = T>
+ Clone
+ Div<Output = T>
+ Inv<Output = T>
+ Mul<Output = T>
+ Neg<Output = T>
+ PartialEq
+ PartialOrd
+ Sub<Output = T>
+ Zero,
{
pub fn eval(&self, z: Complex<T>) -> Complex<T> {
let s = (self.conversion)(z, self.ts.clone());
self.tf.eval(&s)
}
}
#[cfg(test)]
mod tests {
use crate::{units::ToDecibel, Complex};
use super::*;
#[test]
fn new_tfz() {
let _t = TfDiscretization {
tf: Tf::new([0., 1.], [1., 1., 1.]),
ts: Seconds(0.1),
conversion: |_z, _ts| Complex::new(0., 1.) * 1.,
};
}
#[test]
fn eval_forward_euler() {
let tf = Tf::new([2., 20.], [1., 0.1]);
let z = Complex::<f64>::new(0., 1.) * 0.5;
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([2., 20.], [1., 0.1]);
let z = Complex::<f64>::new(0., 1.) * 0.5;
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([2., 20.], [1., 0.1]);
let z = Complex::<f64>::new(0., 1.) * 0.5;
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([2., 20.], [1., 0.1]);
let tfz = tf
.discretize(Seconds(1.), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new([-180., 200.], [9., 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_forward_euler2() {
let tf = Tf::new([1., 2., 3.], [4., 5.]);
let tfz = tf
.discretize(Seconds(0.1), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new([5.62, -11.6, 6.], [-0.92, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_forward_euler3() {
let tf = Tf::new([4.0, 5.], [3., 2., 1.]);
let tfz = tf
.discretize(Seconds(0.1), Discretization::ForwardEuler)
.normalize();
let expected = Tfz::new([-0.46, 0.5], [0.83, -1.8, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_backward_euler() {
let tf = Tf::new([2., 20.], [1., 0.1]);
let tfz = tf
.discretize(Seconds(1.), Discretization::BackwardEuler)
.normalize();
let expected = Tfz::new([-20. / 1.1, 20.], [-0.1 / 1.1, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_backward_euler2() {
let tf = Tf::new([2.], [1., 0.1]);
let tfz = tf
.discretize(Seconds(1.), Discretization::BackwardEuler)
.normalize();
let expected = Tfz::new([0., 2. / 1.1], [-0.1 / 1.1, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_backward_euler3() {
let tf = Tf::new([1., 2., 3.], [1., 0.1]);
let tfz = tf
.discretize(Seconds(1.), Discretization::BackwardEuler)
.normalize();
let expected = Tfz::new([3. / 1.1, -8. / 1.1, 6. / 1.1], [0., -0.1 / 1.1, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn discretization_tustin() {
let tf = Tf::new([2., 20.], [1., 0.1]);
let tfz = tf
.discretize(Seconds(1.), Discretization::Tustin)
.normalize();
let expected = Tfz::new([-38. / 1.2, 35.], [0.8 / 1.2, 1.]);
assert_eq!(expected, tfz);
}
#[test]
fn frequency_warping() {
let tf = Tf::new([2.0_f32, 20.], [1., 0.1]);
let tfz = tf
.discretize_with_warp(Seconds(1.), RadiansPerSecond(0.1))
.normalize();
let expected = Tfz::new([-31.643_282, 34.977_077], [0.666_898_2, 1.]);
assert_eq!(expected, tfz);
}
}