use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct DualController<S: ControlScalar> {
pub k_ce: S,
pub probe_amplitude: S,
pub trace_p: S,
pub probe_threshold: S,
pub alpha: S,
probe_counter: u32,
probe_period: u32,
}
impl<S: ControlScalar> DualController<S> {
pub fn new(k_ce: S, probe_amplitude: S, probe_threshold: S, probe_period: u32) -> Self {
Self {
k_ce,
probe_amplitude,
trace_p: S::ZERO,
probe_threshold,
alpha: S::from_f64(0.99),
probe_counter: 0,
probe_period,
}
}
pub fn alpha_probing(&self) -> S {
let ratio = self.trace_p / (self.probe_threshold + S::EPSILON);
ratio.clamp_val(S::ZERO, S::ONE)
}
pub fn update_uncertainty(&mut self, trace_p: S) {
self.trace_p = self.alpha * self.trace_p + (S::ONE - self.alpha) * trace_p;
}
pub fn ce_control(&self, r: S, y: S) -> S {
self.k_ce * (r - y)
}
pub fn probing_signal(&mut self) -> S {
let half = self.probe_period;
let period = half * 2;
let sign = if self.probe_counter < half {
self.probe_amplitude
} else {
-self.probe_amplitude
};
self.probe_counter += 1;
if self.probe_counter >= period {
self.probe_counter = 0;
}
sign
}
pub fn update(&mut self, r: S, y: S) -> S {
let u_ce = self.ce_control(r, y);
let alpha = self.alpha_probing();
let u_probe = self.probing_signal();
u_ce + alpha * u_probe
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_probing_when_certain() {
let mut ctrl = DualController::new(1.0_f64, 5.0_f64, 10.0_f64, 4);
let u = ctrl.update(1.0, 0.0);
assert!((u - 1.0).abs() < 1e-9, "u={u}");
}
#[test]
fn test_full_probing_when_uncertain() {
let mut ctrl = DualController::new(0.0_f64, 5.0_f64, 1.0_f64, 4);
ctrl.trace_p = 100.0;
let u = ctrl.update(0.0, 0.0);
assert!((u.abs() - 5.0).abs() < 1e-9, "u={u}");
}
#[test]
fn test_probing_square_wave_period() {
let mut ctrl = DualController::new(0.0_f64, 1.0_f64, 0.0_f64, 3);
ctrl.trace_p = 1.0; let signals: Vec<f64> = (0..6).map(|_| ctrl.update(0.0, 0.0)).collect();
assert!(signals[0] > 0.0);
assert!(signals[1] > 0.0);
assert!(signals[2] > 0.0);
assert!(signals[3] < 0.0);
assert!(signals[4] < 0.0);
assert!(signals[5] < 0.0);
}
#[test]
fn test_update_uncertainty_smoothing() {
let mut ctrl = DualController::new(1.0_f64, 1.0_f64, 1.0_f64, 4);
ctrl.alpha = 0.9;
ctrl.update_uncertainty(10.0);
assert!(
(ctrl.trace_p - 1.0).abs() < 1e-9,
"trace_p={}",
ctrl.trace_p
);
}
#[test]
fn test_alpha_probing_clamped() {
let mut ctrl = DualController::new(1.0_f64, 1.0_f64, 5.0_f64, 4);
ctrl.trace_p = 0.0;
assert_eq!(ctrl.alpha_probing(), 0.0);
ctrl.trace_p = 100.0;
assert_eq!(ctrl.alpha_probing(), 1.0);
}
}