use crate::{dka_method, Continuous, Discrete, Polynomial};
use num_complex::Complex;
use num_traits::{Float, Zero};
use serde::Serialize;
use std::marker::PhantomData;
use std::ops::AddAssign;
mod display;
mod ops;
mod parser;
mod partial_fraction;
mod with_delay;
pub use partial_fraction::{PartialFraction, PoleTerm};
pub use with_delay::{SimulationError, TransferFunctionWithDelay};
pub(crate) use partial_fraction::{fmt_num, principal_part, push_term};
pub use parser::TransferFunctionParseError;
#[doc(hidden)]
pub use parser::{__detect_domain, __DomainTag, __SelectDomain};
#[derive(Clone, Debug, Serialize)]
pub struct TransferFunction<T, D = Continuous> {
pub numerator: Polynomial<T>,
pub denominator: Polynomial<T>,
#[serde(skip)]
_domain: PhantomData<D>,
}
#[derive(Clone, Debug, Serialize)]
pub struct PzMap<T, D = Continuous> {
pub poles: Vec<Complex<T>>,
pub zeros: Vec<Complex<T>>,
#[serde(skip)]
_domain: PhantomData<D>,
}
impl<T, D> TransferFunction<T, D> {
pub fn from_polynomials(numerator: Polynomial<T>, denominator: Polynomial<T>) -> Self {
Self { numerator, denominator, _domain: PhantomData }
}
}
impl<T: Float + AddAssign, D> TransferFunction<T, D> {
pub fn reduced(&self) -> Self {
self.reduced_with_tolerance(T::from(1e-6).unwrap())
}
pub fn reduced_with_tolerance(&self, rel_tol: T) -> Self {
let mut numer = trim_leading_zeros(&self.numerator);
let mut denom = trim_leading_zeros(&self.denominator);
let origin = trailing_zeros(&numer).min(trailing_zeros(&denom));
let (numer_len, denom_len) = (numer.len(), denom.len());
numer.truncate(numer_len - origin);
denom.truncate(denom_len - origin);
let numer_roots = grouped_roots(&numer);
let denom_roots = grouped_roots(&denom);
let mut common: Vec<(Complex<T>, usize)> = Vec::new();
for &(z, mz) in &numer_roots {
let threshold = rel_tol * z.norm().max(T::one());
if let Some(&(p, mp)) = denom_roots.iter().find(|&&(p, _)| (p - z).norm() <= threshold) {
common.push(((z + p) / T::from(2.0).unwrap(), mz.min(mp)));
}
}
if common.is_empty() {
return Self::from_polynomials(numer, denom);
}
Self::from_polynomials(deflate(&numer, &common), deflate(&denom, &common))
}
}
fn trim_leading_zeros<T: Float>(p: &Polynomial<T>) -> Polynomial<T> {
let coeffs: Vec<T> = p.iter().copied().skip_while(|c| c.is_zero()).collect();
if coeffs.is_empty() { Polynomial(vec![T::zero()]) } else { Polynomial(coeffs) }
}
fn trailing_zeros<T: Float>(p: &Polynomial<T>) -> usize {
if p.iter().all(|c| c.is_zero()) {
return 0;
}
p.iter().rev().take_while(|c| c.is_zero()).count()
}
fn grouped_roots<T: Float>(p: &Polynomial<T>) -> Vec<(Complex<T>, usize)> {
let tol = T::from(1e-4).unwrap();
roots_with_multiplicity(p, tol, tol)
}
pub(crate) fn roots_with_multiplicity<T: Float>(p: &Polynomial<T>, cluster_tol: T, snap_tol: T) -> Vec<(Complex<T>, usize)> {
let complex_poly = Polynomial(p.iter().map(|&c| Complex::from(c)).collect());
let roots = dka_method(&complex_poly).unwrap_or_default();
partial_fraction::group_roots(&complex_poly, &roots, cluster_tol, snap_tol)
}
fn deflate<T: Float>(p: &Polynomial<T>, factors: &[(Complex<T>, usize)]) -> Polynomial<T> {
let mut work: Vec<Complex<T>> = p.iter().map(|&c| Complex::from(c)).collect();
for &(c, k) in factors {
for _ in 0..k {
if work.len() <= 1 {
break;
}
let mut acc = Complex::zero();
let mut quotient = Vec::with_capacity(work.len());
for &a in &work {
acc = acc * c + a;
quotient.push(acc);
}
quotient.pop();
work = quotient;
}
}
Polynomial(work.iter().map(|c| c.re).collect())
}
impl<T: Float + AddAssign> TransferFunction<T, Continuous> {
pub fn continuous(numerator: &[T], denominator: &[T]) -> Self {
Self::from_polynomials(Polynomial(numerator.to_vec()), Polynomial(denominator.to_vec())).reduced()
}
}
impl<T: Float + AddAssign> TransferFunction<T, Discrete> {
pub fn discrete(numerator: &[T], denominator: &[T]) -> Self {
Self::from_polynomials(Polynomial(numerator.to_vec()), Polynomial(denominator.to_vec())).reduced()
}
}
impl<T: Float + AddAssign, D> TransferFunction<T, D> {
pub fn pz_map(&self) -> PzMap<T, D> {
let tf = self.reduced();
let (denom, numer) = {
let denom_complex = tf.denominator
.iter()
.map(|&x| Complex::from(x))
.collect::<Vec<Complex<T>>>();
let numer_complex = tf.numerator
.iter()
.map(|&x| Complex::from(x))
.collect::<Vec<Complex<T>>>();
(Polynomial(denom_complex), Polynomial(numer_complex))
};
PzMap {
poles: dka_method(&denom).unwrap_or(vec![]),
zeros: dka_method(&numer).unwrap_or(vec![]),
_domain: PhantomData,
}
}
}