use nalgebra::RealField;
use num_complex::Complex;
use num_traits::Float;
use std::{cmp::Ordering, marker::PhantomData, ops::Div};
use crate::{
enums::Continuous,
plots::{root_locus::RootLocus, Plotter},
rational_function::Rf,
transfer_function::TfGen,
units::Seconds,
};
pub type Tf<T> = TfGen<T, Continuous>;
impl<T: Float> Tf<T> {
pub fn delay(tau: Seconds<T>) -> impl Fn(Complex<T>) -> Complex<T> {
move |s| (-s * tau.0).exp()
}
#[must_use]
pub fn init_value(&self) -> T {
let n = self.num().degree();
let d = self.den().degree();
match n.cmp(&d) {
Ordering::Less => T::zero(),
Ordering::Equal => self.num().leading_coeff() / self.den().leading_coeff(),
Ordering::Greater => T::infinity(),
}
}
#[must_use]
pub fn init_value_der(&self) -> T {
let n = self.num().degree();
let d = self.den().degree().map(|d| d - 1);
match n.cmp(&d) {
Ordering::Less => T::zero(),
Ordering::Equal => self.num().leading_coeff() / self.den().leading_coeff(),
Ordering::Greater => T::infinity(),
}
}
#[must_use]
pub fn sensitivity(&self, r: &Self) -> Self {
let n = self.num() * r.num();
let d = self.den() * r.den();
Self {
rf: Rf::new(d.clone(), n + d),
time: PhantomData,
}
}
#[must_use]
pub fn compl_sensitivity(&self, r: &Self) -> Self {
let l = self * r;
l.feedback_n()
}
#[must_use]
pub fn control_sensitivity(&self, r: &Self) -> Self {
Self {
rf: Rf::new(
r.num() * self.den(),
r.num() * self.num() + r.den() * self.den(),
),
time: PhantomData,
}
}
}
impl<T: Float + RealField> Tf<T> {
#[must_use]
pub fn is_stable(&self) -> bool {
self.complex_poles().iter().all(|p| p.re.is_negative())
}
pub fn root_locus(&self, k: T) -> Vec<Complex<T>> {
let p = &(self.num() * k) + self.den();
p.complex_roots()
}
pub fn root_locus_plot(self, min_k: T, max_k: T, step: T) -> RootLocus<T> {
RootLocus::new(self, min_k, max_k, step)
}
}
impl<T> Tf<T> {
#[must_use]
pub fn static_gain<'a>(&'a self) -> T
where
&'a T: 'a + Div<&'a T, Output = T>,
{
&self.num()[0] / &self.den()[0]
}
}
impl<T: Float> Plotter<T> for Tf<T> {
fn eval_point(&self, s: T) -> Complex<T> {
self.eval(&Complex::new(T::zero(), s))
}
}
#[cfg(test)]
mod tests {
use num_traits::One;
use proptest::prelude::*;
use std::str::FromStr;
use super::*;
use crate::{
plots::{bode::Bode, polar::Polar},
poly,
polynomial::Poly,
units::RadiansPerSecond,
};
#[test]
fn delay() {
let d = Tf::delay(Seconds(2.));
assert_relative_eq!(1., d(Complex::new(0., 10.)).norm());
assert_relative_eq!(-1., d(Complex::new(0., 0.5)).arg());
}
proptest! {
#[test]
fn qc_static_gain(g: f32) {
let tf = Tf::new(poly!(g, -3.), poly!(1., 5., -0.5));
assert_relative_eq!(g, tf.static_gain());
}
}
#[test]
fn stability() {
let stable_den = Poly::new_from_roots(&[-1., -2.]);
let stable_tf = Tf::new(poly!(1., 2.), stable_den);
assert!(stable_tf.is_stable());
let unstable_den = Poly::new_from_roots(&[0., -2.]);
let unstable_tf = Tf::new(poly!(1., 2.), unstable_den);
assert!(!unstable_tf.is_stable());
}
#[test]
fn bode() {
let tf = Tf::new(Poly::<f64>::one(), Poly::new_from_roots(&[-1.]));
let b = Bode::new(tf, RadiansPerSecond(0.1), RadiansPerSecond(100.0), 0.1);
for g in b.into_iter().into_db_deg() {
assert!(g.magnitude() < 0.);
assert!(g.phase() < 0.);
}
}
#[test]
fn polar() {
let tf = Tf::new(poly!(5.), Poly::new_from_roots(&[-1., -10.]));
let p = Polar::new(tf, RadiansPerSecond(0.1), RadiansPerSecond(10.0), 0.1);
for g in p {
assert!(g.magnitude() < 1.);
assert!(g.phase() < 0.);
}
}
#[test]
fn initial_value() {
let tf = Tf::new(poly!(4.), poly!(1., 5.));
assert_relative_eq!(0., tf.init_value());
let tf = Tf::new(poly!(4., -12.), poly!(1., 5.));
assert_relative_eq!(-2.4, tf.init_value());
let tf = Tf::new(poly!(-3., 4.), poly!(5.));
assert_relative_eq!(std::f32::INFINITY, tf.init_value());
}
#[test]
fn derivative_initial_value() {
let tf = Tf::new(poly!(1., -3.), poly!(1., 3., 2.));
assert_relative_eq!(-1.5, tf.init_value_der());
let tf = Tf::new(poly!(1.), poly!(1., 3., 2.));
assert_relative_eq!(0., tf.init_value_der());
let tf = Tf::new(poly!(1., 0.5, -3.), poly!(1., 3., 2.));
assert_relative_eq!(std::f32::INFINITY, tf.init_value_der());
}
#[test]
fn complementary_sensitivity() {
let g = Tf::new(poly!(1.), poly!(0., 1.));
let r = Tf::new(poly!(4.), poly!(1., 1.));
let f = g.compl_sensitivity(&r);
assert_eq!(Tf::new(poly!(4.), poly!(4., 1., 1.)), f);
}
#[test]
fn sensitivity() {
let g = Tf::new(poly!(1.), poly!(0., 1.));
let r = Tf::new(poly!(4.), poly!(1., 1.));
let s = g.sensitivity(&r);
assert_eq!(Tf::new(poly!(0., 1., 1.), poly!(4., 1., 1.)), s);
}
#[test]
fn control_sensitivity() {
let g = Tf::new(poly!(1.), poly!(0., 1.));
let r = Tf::new(poly!(4.), poly!(1., 1.));
let q = g.control_sensitivity(&r);
assert_eq!(Tf::new(poly!(0., 4.), poly!(4., 1., 1.)), q);
}
#[test]
fn root_locus() {
let l = Tf::new(poly!(1.), Poly::new_from_roots(&[-1., -2.]));
let locus1 = l.root_locus(0.25);
assert_eq!(Complex::from_str("-1.5").unwrap(), locus1[0]);
let locus2 = l.root_locus(-2.);
assert_eq!(Complex::from_str("-3.").unwrap(), locus2[0]);
assert_eq!(Complex::from_str("0.").unwrap(), locus2[1]);
}
#[test]
fn root_locus_iterations() {
let l = Tf::new(poly!(1.0_f32), Poly::new_from_roots(&[0., -3., -5.]));
let loci = l.root_locus_plot(1., 130., 1.).into_iter();
let last = loci.last().unwrap();
assert_relative_eq!(130., last.k());
assert_eq!(3, last.output().len());
assert!(last.output().iter().any(|r| r.re > 0.));
}
}