use crate::traits::FloatScalar;
use super::ControlError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PidGains<T> {
pub kp: T,
pub ki: T,
pub kd: T,
}
#[derive(Debug, Clone, Copy)]
pub struct FopdtModel<T> {
k: T,
tau: T,
l: T,
}
impl<T: FloatScalar> FopdtModel<T> {
pub fn new(gain: T, tau: T, delay: T) -> Result<Self, ControlError> {
let zero = T::zero();
if gain == zero || !gain.is_finite() {
return Err(ControlError::InvalidFrequency);
}
if tau <= zero || !tau.is_finite() {
return Err(ControlError::InvalidFrequency);
}
if delay < zero || !delay.is_finite() {
return Err(ControlError::InvalidFrequency);
}
Ok(Self { k: gain, tau, l: delay })
}
pub fn gain(&self) -> T { self.k }
pub fn tau(&self) -> T { self.tau }
pub fn delay(&self) -> T { self.l }
pub fn ziegler_nichols(&self) -> PidGains<T> {
let two = T::one() + T::one();
assert!(self.l > T::zero(), "Ziegler-Nichols requires non-zero dead time");
let r = self.tau / (self.k * self.l);
let kp = T::from(1.2).unwrap() * r;
let ti = two * self.l;
let td = self.l / two;
PidGains {
kp,
ki: kp / ti,
kd: kp * td,
}
}
pub fn cohen_coon(&self) -> PidGains<T> {
assert!(self.l > T::zero(), "Cohen-Coon requires non-zero dead time");
let three = T::from(3.0).unwrap();
let four = T::from(4.0).unwrap();
let r = self.l / self.tau;
let base = self.tau / (self.k * self.l);
let kp = base * (four / three + r / four);
let ti = self.l * (T::from(32.0).unwrap() + T::from(6.0).unwrap() * r)
/ (T::from(13.0).unwrap() + T::from(8.0).unwrap() * r);
let td = self.l * four / (T::from(11.0).unwrap() + T::from(2.0).unwrap() * r);
PidGains {
kp,
ki: kp / ti,
kd: kp * td,
}
}
pub fn simc(&self, tau_c: T) -> PidGains<T> {
assert!(tau_c > T::zero(), "closed-loop time constant must be positive");
let two = T::one() + T::one();
let kp = self.tau / (self.k * (tau_c + self.l));
let four = two + two;
let ti_candidate = four * (tau_c + self.l);
let ti = if self.tau < ti_candidate { self.tau } else { ti_candidate };
let td = self.l / two;
PidGains {
kp,
ki: kp / ti,
kd: kp * td,
}
}
}
pub fn ziegler_nichols_ultimate<T: FloatScalar>(
ku: T,
tu: T,
) -> Result<PidGains<T>, ControlError> {
let zero = T::zero();
if ku <= zero || !ku.is_finite() {
return Err(ControlError::InvalidFrequency);
}
if tu <= zero || !tu.is_finite() {
return Err(ControlError::InvalidFrequency);
}
let two = T::one() + T::one();
let eight = T::from(8.0).unwrap();
let kp = T::from(0.6).unwrap() * ku;
let ti = tu / two;
let td = tu / eight;
Ok(PidGains {
kp,
ki: kp / ti,
kd: kp * td,
})
}