use crate::core::scalar::ControlScalar;
pub struct SelfTuningController<S: ControlScalar, const N_PARAMS: usize> {
pub theta: [S; N_PARAMS],
p: [S; N_PARAMS],
pub lambda: S,
pub desired_poles: [S; 2],
regressor: [S; N_PARAMS],
prev_y: S,
prev_u: S,
pub output_limit: S,
u: S,
}
impl<S: ControlScalar, const N_PARAMS: usize> SelfTuningController<S, N_PARAMS> {
pub fn new(lambda: S, p0: S, desired_poles: [S; 2], output_limit: S) -> Self {
Self {
theta: [S::ZERO; N_PARAMS],
p: [p0; N_PARAMS],
lambda,
desired_poles,
regressor: [S::ZERO; N_PARAMS],
prev_y: S::ZERO,
prev_u: S::ZERO,
output_limit,
u: S::ZERO,
}
}
pub fn update(&mut self, y: S, r: S) -> S {
if N_PARAMS >= 1 {
self.regressor[0] = self.prev_y;
}
if N_PARAMS >= 2 {
self.regressor[1] = self.prev_u;
}
let phi = &self.regressor;
let y_hat: S = phi
.iter()
.zip(self.theta.iter())
.map(|(&p, &t)| p * t)
.fold(S::ZERO, |a, b| a + b);
let error = y - y_hat;
let denom = self.lambda
+ phi
.iter()
.zip(self.p.iter())
.map(|(&ph, &pi)| ph * ph * pi)
.fold(S::ZERO, |a, b| a + b);
if denom.abs() > S::EPSILON {
for (i, &ph) in phi.iter().enumerate().take(N_PARAMS) {
let k_i = self.p[i] * ph / denom;
self.theta[i] += k_i * error;
self.p[i] = (self.p[i] - k_i * ph * self.p[i]) / self.lambda;
self.p[i] = self.p[i].clamp_val(S::EPSILON, S::from_f64(1e8));
}
}
let a_est = if N_PARAMS >= 1 {
self.theta[0]
} else {
S::ZERO
};
let b_est = if N_PARAMS >= 2 { self.theta[1] } else { S::ONE };
let p_d = self.desired_poles[0];
let control = if b_est.abs() > S::from_f64(0.001) {
let feedback_gain = (a_est - p_d) / b_est;
let reference_gain = (S::ONE - p_d) / b_est;
reference_gain * r - feedback_gain * y
} else {
S::from_f64(0.5) * (r - y)
};
self.u = control.clamp_val(-self.output_limit, self.output_limit);
self.prev_y = y;
self.prev_u = self.u;
self.u
}
pub fn control_output(&self) -> S {
self.u
}
pub fn reset(&mut self) {
self.theta = [S::ZERO; N_PARAMS];
self.p = [S::from_f64(1e4); N_PARAMS];
self.regressor = [S::ZERO; N_PARAMS];
self.prev_y = S::ZERO;
self.prev_u = S::ZERO;
self.u = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifies_and_tracks_setpoint() {
let mut stc = SelfTuningController::<f64, 2>::new(1.0, 1e4, [0.3, 0.1], 10.0);
let mut y = 0.0_f64;
let r = 1.0_f64;
for _ in 0..500 {
let u = stc.update(y, r);
y = 0.8 * y + 0.5 * u;
}
assert!((y - r).abs() < 0.2, "y={:.4}, r={:.4}", y, r);
}
#[test]
fn control_is_clamped() {
let mut stc = SelfTuningController::<f64, 2>::new(1.0, 1e4, [0.5, 0.5], 5.0);
let u = stc.update(0.0, 100.0); assert!(u.abs() <= 5.0 + 1e-10);
}
#[test]
fn reset_clears_state() {
let mut stc = SelfTuningController::<f64, 2>::new(0.99, 1e4, [0.5, 0.5], 10.0);
for _ in 0..100 {
stc.update(1.0, 2.0);
}
stc.reset();
assert_eq!(stc.theta[0], 0.0);
assert_eq!(stc.theta[1], 0.0);
}
}