use core::ops::{Add, AddAssign, Div, Mul, SubAssign};
use num_traits::{AsPrimitive, Float};
use crate::{
Build,
iir::{Biquad, BiquadClamp, Error},
};
#[derive(Clone, Debug, Copy, Default, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub enum Order {
P = 2,
#[default]
I = 1,
I2 = 0,
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Builder<T> {
order: Order,
gain: [T; 5],
limit: [T; 5],
}
impl<T: Float> Default for Builder<T> {
fn default() -> Self {
Self {
order: Order::default(),
gain: [T::zero(); 5],
limit: [T::infinity(); 5],
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub enum Action {
I2 = 0,
I = 1,
P = 2,
D = 3,
D2 = 4,
}
impl<T: Float> Builder<T> {
pub fn order(mut self, order: Order) -> Self {
self.order = order;
self
}
pub fn gain(mut self, action: Action, gain: T) -> Self {
self.gain[action as usize] = gain;
self
}
pub fn kp(self, gain: T) -> Self {
self.gain(Action::P, gain)
}
pub fn ki(self, gain: T) -> Self {
self.gain(Action::I, gain)
}
pub fn ki2(self, gain: T) -> Self {
self.gain(Action::I2, gain)
}
pub fn kd(self, gain: T) -> Self {
self.gain(Action::D, gain)
}
pub fn kd2(self, gain: T) -> Self {
self.gain(Action::D2, gain)
}
pub fn limit(mut self, action: Action, limit: T) -> Self {
self.limit[action as usize] = limit;
self
}
pub fn limit_i(self, limit: T) -> Self {
self.limit(Action::I, limit)
}
pub fn limit_i2(self, limit: T) -> Self {
self.limit(Action::I2, limit)
}
pub fn limit_d(self, limit: T) -> Self {
self.limit(Action::D, limit)
}
pub fn limit_d2(self, limit: T) -> Self {
self.limit(Action::D2, limit)
}
pub fn validate(&self, period: &T) -> Result<(), Error> {
if !period.is_finite() {
return Err(Error::NonFinite("period"));
}
if *period <= T::zero() {
return Err(Error::NonPositive("period"));
}
for (name, values) in [("gain", &self.gain), ("limit", &self.limit)] {
for value in values {
if !value.is_finite() && !value.is_infinite() {
return Err(Error::NonFinite(name));
}
}
}
for action in [Action::I2, Action::I, Action::D, Action::D2] {
let gain = self.gain[action as usize];
let limit = self.limit[action as usize];
if limit.is_finite() {
if limit == T::zero() {
return Err(Error::NonPositive("limit"));
}
if gain != T::zero() && gain.signum() != limit.signum() {
return Err(Error::SignMismatch("gain/limit"));
}
}
}
Ok(())
}
pub fn try_build<C>(&self, period: &T) -> Result<C, Error>
where
Self: Build<C, Context = T>,
{
self.validate(period)?;
Ok(Build::build(self, period))
}
}
impl<T, C> Build<[C; 5]> for Builder<T>
where
C: 'static + Copy + SubAssign + AddAssign,
T: Float + AsPrimitive<C>,
{
type Context = T;
fn build(&self, period: &T) -> [C; 5] {
let mut z = period.powi(-(self.order as i32));
let mut gl = [[T::zero(); 2]; 3];
for (gl, (i, (gain, limit))) in gl
.iter_mut()
.zip(
self.gain
.iter()
.zip(self.limit.iter())
.enumerate()
.skip(self.order as usize),
)
.rev()
{
gl[0] = *gain * z;
gl[1] = if i == Action::P as usize {
T::one()
} else {
gl[0] / *limit
};
z = z * *period;
}
let a0i = (gl[0][1] + gl[1][1] + gl[2][1]).recip();
let kernels = [[1, 0, 0], [1, -1, 0], [1, -2, 1]];
let mut ba = [[T::zero().as_(); 2]; 3];
for (gli, ki) in gl.into_iter().zip(kernels) {
let gli = gli.map(|c| (c * a0i).as_());
for (baj, kij) in ba.iter_mut().zip(ki) {
if kij > 0 {
for _ in 0..kij {
baj[0] += gli[0];
baj[1] -= gli[1];
}
} else {
for _ in 0..-kij {
baj[0] -= gli[0];
baj[1] += gli[1];
}
}
}
}
[ba[0][0], ba[1][0], ba[2][0], ba[1][1], ba[2][1]]
}
}
impl<C, T> Build<Biquad<C>> for Builder<T>
where
Self: Build<[C; 5]>,
Biquad<C>: From<[C; 5]>,
{
type Context = <Self as Build<[C; 5]>>::Context;
fn build(&self, ctx: &Self::Context) -> Biquad<C> {
self.build(ctx).into()
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Gains<T> {
pub value: [T; 5],
}
impl<T> Gains<T> {
pub fn new(value: [T; 5]) -> Self {
Self { value }
}
}
#[derive(Clone, Debug)]
pub struct Units<T> {
pub t: T,
pub x: T,
pub y: T,
}
impl<T: Float> Default for Units<T> {
fn default() -> Self {
Self {
t: T::one(),
x: T::one(),
y: T::one(),
}
}
}
impl<T> Units<T> {
pub const fn new(t: T, x: T, y: T) -> Self {
Self { t, x, y }
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(::serde::Serialize, ::serde::Deserialize))]
pub struct Pid<T> {
pub order: Order,
pub gain: Gains<T>,
pub limit: Gains<T>,
pub setpoint: T,
pub min: T,
pub max: T,
}
impl<T: Float + Default> Default for Pid<T> {
fn default() -> Self {
Self {
order: Order::default(),
gain: Gains::default(),
limit: Gains::new([T::infinity(); 5]),
setpoint: T::zero(),
min: T::neg_infinity(),
max: T::infinity(),
}
}
}
impl<T: Float> Pid<T> {
pub fn kp(mut self, gain: T) -> Self {
self.gain.value[Action::P as usize] = gain;
self
}
pub fn ki(mut self, gain: T) -> Self {
self.gain.value[Action::I as usize] = gain;
self
}
pub fn ki2(mut self, gain: T) -> Self {
self.gain.value[Action::I2 as usize] = gain;
self
}
pub fn kd(mut self, gain: T) -> Self {
self.gain.value[Action::D as usize] = gain;
self
}
pub fn kd2(mut self, gain: T) -> Self {
self.gain.value[Action::D2 as usize] = gain;
self
}
pub fn setpoint(mut self, setpoint: T) -> Self {
self.setpoint = setpoint;
self
}
pub fn output_limits(mut self, min: T, max: T) -> Self {
self.min = min;
self.max = max;
self
}
pub fn limit_i(mut self, limit: T) -> Self {
self.limit.value[Action::I as usize] = limit;
self
}
pub fn limit_i2(mut self, limit: T) -> Self {
self.limit.value[Action::I2 as usize] = limit;
self
}
pub fn limit_d(mut self, limit: T) -> Self {
self.limit.value[Action::D as usize] = limit;
self
}
pub fn limit_d2(mut self, limit: T) -> Self {
self.limit.value[Action::D2 as usize] = limit;
self
}
pub fn validate(&self, units: &Units<T>) -> Result<(), Error> {
if self.min > self.max {
return Err(Error::InvertedRange("output_limits"));
}
for (name, value) in [("t", units.t), ("x", units.x), ("y", units.y)] {
if !value.is_finite() {
return Err(Error::NonFinite(name));
}
if value <= T::zero() {
return Err(Error::NonPositive(name));
}
}
Builder {
order: self.order,
gain: self.gain.value,
limit: self.limit.value,
}
.validate(&units.t)
}
pub fn try_build<C>(&self, units: &Units<T>) -> Result<C, Error>
where
Self: Build<C, Context = Units<T>>,
{
self.validate(units)?;
Ok(Build::build(self, units))
}
}
impl<T, Y, C> Build<BiquadClamp<C, Y>> for Pid<T>
where
Y: 'static + Copy + Mul<C, Output = Y> + Div<C, Output = Y>,
C: Add<Output = C> + Copy,
T: AsPrimitive<Y> + Float,
BiquadClamp<C, Y>: From<[C; 5]>,
Builder<T>: Build<[C; 5], Context = T>,
{
type Context = Units<T>;
fn build(&self, units: &Units<T>) -> BiquadClamp<C, Y> {
let yu = units.y.recip();
let yx = units.x * yu;
let p = self.gain.value[Action::P as usize];
let mut biquad: BiquadClamp<C, Y> = Builder {
gain: self.gain.value.map(|g| yx * g.copysign(p)),
limit: self.limit.value.map(|mut l| {
if l.is_nan() {
l = T::infinity()
}
yx * l.copysign(p)
}),
order: self.order,
}
.build(&units.t)
.into();
biquad.set_input_offset((-self.setpoint * units.x.recip()).as_());
biquad.min = (self.min * yu).as_();
biquad.max = (self.max * yu).as_();
biquad
}
}
#[cfg(test)]
mod test {
use dsp_process::SplitProcess;
use crate::iir::{pid::Build, *};
#[test]
fn pid() {
let b: Biquad<f32> = pid::Builder::default()
.gain(pid::Action::I, 1e-3)
.gain(pid::Action::P, 1.0)
.gain(pid::Action::D, 1e2)
.limit(pid::Action::I, 1e3)
.limit(pid::Action::D, 1e1)
.build(&1.0);
let want = [9.181_909, -18.272_726, 9.090_908, 1.909_090_8, -0.909_090_8];
for (ba_have, ba_want) in b.ba.iter().zip(want.iter()) {
assert!(
(ba_have / ba_want - 1.0).abs() < 2.0 * f32::EPSILON,
"have {:?} != want {want:?}",
b.ba,
);
}
}
#[test]
fn pid_i32() {
use dsp_fixedpoint::Q32;
let b: Biquad<Q32<29>> = pid::Builder::<f32>::default()
.gain(pid::Action::I, 1e-5)
.gain(pid::Action::P, 1e-2)
.gain(pid::Action::D, 1e0)
.limit(pid::Action::I, 1e1)
.limit(pid::Action::D, 1e-1)
.build(&1.0);
println!("{b:?}");
}
#[test]
fn units() {
let ki = 5e-2;
let tau = 3e-3;
let b: Biquad<f32> = pid::Builder::default().gain(pid::Action::I, ki).build(&tau);
let mut xy = DirectForm1::default();
for i in 1..10 {
let y_have = b.process(&mut xy, 1.0);
let y_want = (i as f32) * tau * ki;
assert!(
(y_have / y_want - 1.0).abs() < 3.0 * f32::EPSILON,
"{i}: have {y_have} != {y_want}"
);
}
}
}