use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DelayError {
InvalidParameter,
ZeroDelay,
}
pub struct DelayBuffer<S, const D: usize> {
buffer: [S; D],
head: usize,
}
impl<S: ControlScalar, const D: usize> DelayBuffer<S, D> {
pub fn new(initial: S) -> Self {
Self {
buffer: [initial; D],
head: 0,
}
}
pub fn push(&mut self, value: S) -> S {
let delayed = self.buffer[self.head];
self.buffer[self.head] = value;
self.head = (self.head + 1) % D;
delayed
}
pub fn peek_delayed(&self) -> S {
self.buffer[self.head]
}
}
pub struct PadeDelay<S> {
b0: S,
b1: S,
a1: S,
x_prev: S,
y_prev: S,
tau: S,
dt: S,
}
impl<S: ControlScalar> PadeDelay<S> {
pub fn new(tau: S, dt: S) -> Result<Self, DelayError> {
if tau <= S::ZERO || dt <= S::ZERO {
return Err(DelayError::InvalidParameter);
}
let r = tau / (S::TWO * dt);
let one_plus_r = S::ONE + r;
let one_minus_r = S::ONE - r;
let b0 = one_minus_r / one_plus_r;
let b1 = S::ONE;
let a1 = one_minus_r / one_plus_r;
Ok(Self {
b0,
b1,
a1,
x_prev: S::ZERO,
y_prev: S::ZERO,
tau,
dt,
})
}
pub fn filter(&mut self, u: S) -> S {
let y = self.b0 * u + self.b1 * self.x_prev - self.a1 * self.y_prev;
self.x_prev = u;
self.y_prev = y;
y
}
pub fn group_delay(&self) -> S {
self.tau / S::TWO
}
pub fn tau(&self) -> S {
self.tau
}
pub fn dt(&self) -> S {
self.dt
}
pub fn reset(&mut self) {
self.x_prev = S::ZERO;
self.y_prev = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn delay_buffer_d1_first_output_is_initial() {
let mut buf = DelayBuffer::<f64, 1>::new(0.0);
let out = buf.push(5.0);
assert!(
(out - 0.0).abs() < 1e-12,
"Expected 0.0 (initial), got {out}"
);
}
#[test]
fn delay_buffer_d1_output_is_previous_input() {
let mut buf = DelayBuffer::<f64, 1>::new(0.0);
buf.push(5.0); let out = buf.push(9.0); assert!((out - 5.0).abs() < 1e-12, "Expected 5.0, got {out}");
}
#[test]
fn delay_buffer_d3_correct_delay() {
let mut buf = DelayBuffer::<f64, 3>::new(-1.0);
let o0 = buf.push(10.0);
let o1 = buf.push(20.0);
let o2 = buf.push(30.0);
assert!((o0 - (-1.0)).abs() < 1e-12, "o0 expected -1, got {o0}");
assert!((o1 - (-1.0)).abs() < 1e-12, "o1 expected -1, got {o1}");
assert!((o2 - (-1.0)).abs() < 1e-12, "o2 expected -1, got {o2}");
let o3 = buf.push(40.0);
assert!((o3 - 10.0).abs() < 1e-12, "o3 expected 10, got {o3}");
let o4 = buf.push(50.0);
assert!((o4 - 20.0).abs() < 1e-12, "o4 expected 20, got {o4}");
}
#[test]
fn delay_buffer_d2_initial_fill() {
let mut buf = DelayBuffer::<f64, 2>::new(7.0);
let o0 = buf.push(1.0); let o1 = buf.push(2.0); let o2 = buf.push(3.0); assert!((o0 - 7.0).abs() < 1e-12, "o0={o0}");
assert!((o1 - 7.0).abs() < 1e-12, "o1={o1}");
assert!((o2 - 1.0).abs() < 1e-12, "o2={o2}");
}
#[test]
fn delay_buffer_peek_delayed_matches_push_output() {
let mut buf = DelayBuffer::<f64, 2>::new(0.0);
buf.push(1.0);
buf.push(2.0);
let peeked = buf.peek_delayed();
let pushed = buf.push(3.0);
assert!(
(peeked - pushed).abs() < 1e-12,
"peek={peeked} != push={pushed}"
);
}
#[test]
fn pade_invalid_parameters_rejected() {
assert!(PadeDelay::<f64>::new(0.0, 0.001).is_err());
assert!(PadeDelay::<f64>::new(-0.1, 0.001).is_err());
assert!(PadeDelay::<f64>::new(0.01, 0.0).is_err());
assert!(PadeDelay::<f64>::new(0.01, -0.001).is_err());
}
#[test]
fn pade_unit_step_response_final_value_is_one() {
let tau = 0.05_f64;
let dt = 0.001_f64;
let mut pade = PadeDelay::new(tau, dt).unwrap();
let mut y = 0.0_f64;
for _ in 0..5000 {
y = pade.filter(1.0);
}
assert!(
(y - 1.0).abs() < 1e-6,
"Final value of step response = {y}, expected ≈ 1.0"
);
}
#[test]
fn pade_group_delay_is_half_tau() {
let tau = 0.1_f64;
let dt = 0.001_f64;
let pade = PadeDelay::new(tau, dt).unwrap();
let gd = pade.group_delay();
assert!(
(gd - tau / 2.0).abs() < 1e-12,
"Group delay = {gd}, expected {}",
tau / 2.0
);
}
#[test]
fn pade_step_response_delayed_rise() {
let tau = 0.1_f64;
let dt = 0.001_f64;
let mut pade = PadeDelay::new(tau, dt).unwrap();
let first = pade.filter(1.0);
assert!(
first < 0.0,
"First Padé output should be negative (non-min-phase), got {first}"
);
let mut y = first;
for _ in 1..10_000 {
y = pade.filter(1.0);
}
assert!(
(y - 1.0).abs() < 1e-5,
"Steady-state should be 1.0, got {y}"
);
}
#[test]
fn pade_reset_clears_state() {
let tau = 0.01_f64;
let dt = 0.001_f64;
let mut pade = PadeDelay::new(tau, dt).unwrap();
for _ in 0..100 {
pade.filter(1.0);
}
pade.reset();
let y = pade.filter(0.0);
assert!(y.abs() < 1e-12, "After reset, y(0) should be 0, got {y}");
}
}