use crate::{polynomial::Poly, transfer_function::continuous::Tf};
use num_traits::Float;
#[derive(Debug)]
pub struct Pid<T: Float> {
kp: T,
ti: T,
td: T,
n: Option<T>,
}
impl<T: Float> Pid<T> {
pub fn new_ideal(kp: T, ti: T, td: T) -> Self {
Self {
kp,
ti,
td,
n: None,
}
}
pub fn new(kp: T, ti: T, td: T, n: T) -> Self {
Self {
kp,
ti,
td,
n: Some(n),
}
}
pub fn tf(&self) -> Tf<T> {
self.n
.map_or_else(|| self.tf_from_ideal_pid(), |n| self.tf_from_real_pid(n))
}
fn tf_from_real_pid(&self, n: T) -> Tf<T> {
let a0 = self.kp * n;
let a1 = self.kp * (self.ti * n + self.td);
let a2 = self.kp * self.ti * self.td * (T::one() + n);
let b0 = T::zero();
let b1 = self.ti * n;
let b2 = self.ti * self.td;
Tf::new(
Poly::new_from_coeffs(&[a0, a1, a2]),
Poly::new_from_coeffs(&[b0, b1, b2]),
)
}
fn tf_from_ideal_pid(&self) -> Tf<T> {
Tf::new(
Poly::new_from_coeffs(&[T::one(), self.ti, self.ti * self.td]),
Poly::new_from_coeffs(&[T::zero(), self.ti / self.kp]),
)
}
}
#[cfg(test)]
mod pid_tests {
use super::*;
use crate::units::ToDecibel;
use num_complex::Complex64;
#[test]
fn ideal_pid_creation() {
let pid = Pid::new_ideal(10., 5., 2.);
let tf = pid.tf();
let c = tf.eval(&Complex64::new(0., 1.));
assert_eq!(Complex64::new(10., 18.), c);
}
#[test]
fn real_pid_creation() {
let g = Tf::new(
Poly::new_from_coeffs(&[1.]),
Poly::new_from_roots(&[-1., -1., -1.]),
);
let pid = Pid::new(2., 2., 0.5, 5.);
let r = pid.tf();
assert_eq!(Some(vec![-10., 0.]), r.real_poles());
let l = &g * &r;
let critical_freq = 0.8;
let c = l.eval(&Complex64::new(0., critical_freq));
assert_abs_diff_eq!(0., c.norm().to_db(), epsilon = 0.1);
}
}