#![allow(dead_code)]
use crate::core::scalar::ControlScalar;
use crate::core::signal::{Feedback, Setpoint};
use crate::core::traits::Controller;
use crate::pid::Pid;
pub struct SmithPredictor<S: ControlScalar, const D: usize> {
pub inner_pid: Pid<S>,
delay_buffer: [S; D],
buffer_idx: usize,
pub plant_a: S,
pub plant_b: S,
model_output: S,
last_u: S,
}
impl<S: ControlScalar, const D: usize> SmithPredictor<S, D> {
pub fn new(inner_pid: Pid<S>, plant_a: S, plant_b: S) -> Self {
Self {
inner_pid,
delay_buffer: [S::ZERO; D],
buffer_idx: 0,
plant_a,
plant_b,
model_output: S::ZERO,
last_u: S::ZERO,
}
}
pub fn update(&mut self, sp: S, fb: S, dt: S) -> S {
let new_model = self.plant_a * self.model_output + self.plant_b * self.last_u;
let delayed_model = if D == 0 {
new_model
} else {
self.delay_buffer[self.buffer_idx]
};
if D > 0 {
self.delay_buffer[self.buffer_idx] = new_model;
self.buffer_idx = (self.buffer_idx + 1) % D;
}
self.model_output = new_model;
let fb_corrected = fb + (new_model - delayed_model);
let sp_sig = Setpoint::new(sp);
let fb_sig = Feedback::new(fb_corrected);
let u = self.inner_pid.update(&sp_sig, &fb_sig, dt).value();
self.last_u = u;
u
}
pub fn model_output(&self) -> S {
self.model_output
}
pub fn reset(&mut self) {
self.inner_pid.reset();
self.delay_buffer = [S::ZERO; D];
self.buffer_idx = 0;
self.model_output = S::ZERO;
self.last_u = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pid::PidConfig;
fn make_predictor() -> SmithPredictor<f64, 5> {
let pid = PidConfig::pi(1.0_f64, 5.0).build();
SmithPredictor::new(pid, 0.9_f64, 0.1_f64)
}
#[test]
fn output_nonzero_for_nonzero_setpoint() {
let mut sp_val = make_predictor();
let u = sp_val.update(1.0, 0.0, 0.01);
assert!(u > 0.0, "u={u}");
}
#[test]
fn zero_setpoint_zero_feedback_zero_output() {
let mut sp_val = make_predictor();
let u = sp_val.update(0.0, 0.0, 0.01);
assert!((u).abs() < 1e-12, "u={u}");
}
#[test]
fn reset_clears_state() {
let mut pred = make_predictor();
for _ in 0..10 {
pred.update(1.0, 0.5, 0.01);
}
pred.reset();
assert!((pred.model_output()).abs() < 1e-12);
let u = pred.update(0.0, 0.0, 0.01);
assert!(u.abs() < 1e-12, "u after reset={u}");
}
#[test]
fn delay_buffer_shifts_correctly() {
let pid = PidConfig::pi(0.5_f64, 1.0).build();
let mut pred: SmithPredictor<f64, 3> = SmithPredictor::new(pid, 0.8_f64, 0.2_f64);
for _ in 0..20 {
pred.update(1.0, 0.0, 0.01);
}
assert!(pred.model_output() > 0.0);
}
}