use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct LyapunovMrac<S: ControlScalar> {
pub a_m: S,
pub b_m: S,
pub gamma: S,
pub p: S,
pub theta: [S; 2],
y_m: S,
error: S,
pub y_m_out: S,
}
impl<S: ControlScalar> LyapunovMrac<S> {
pub fn new(a_m: S, b_m: S, gamma: S, p: S) -> Self {
Self {
a_m,
b_m,
gamma,
p,
theta: [b_m, S::ZERO], y_m: S::ZERO,
error: S::ZERO,
y_m_out: S::ZERO,
}
}
pub fn lyapunov_value(&self, theta_star: [S; 2]) -> S {
let e2 = self.error * self.error;
let dt0 = self.theta[0] - theta_star[0];
let dt1 = self.theta[1] - theta_star[1];
let theta_err2 = dt0 * dt0 + dt1 * dt1;
self.p * e2 + theta_err2 / self.gamma
}
pub fn update(&mut self, r: S, y: S) -> S {
self.y_m = self.a_m * self.y_m + self.b_m * r;
self.y_m_out = self.y_m;
self.error = y - self.y_m;
let adapt = self.gamma * self.p * self.error;
self.theta[0] -= adapt * r;
self.theta[1] -= adapt * y;
self.theta[0] * r + self.theta[1] * y
}
pub fn error(&self) -> S {
self.error
}
pub fn reset(&mut self) {
self.y_m = S::ZERO;
self.error = S::ZERO;
self.y_m_out = S::ZERO;
self.theta = [self.b_m, S::ZERO];
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simulate(n: usize, a_p: f64, b_p: f64) -> f64 {
let a_m = 0.7_f64;
let b_m = 0.3_f64;
let gamma = 0.05_f64;
let p = 1.0 / (1.0 - a_m * a_m); let mut ctrl = LyapunovMrac::new(a_m, b_m, gamma, p);
let mut y = 0.0_f64;
let r = 1.0_f64;
for _ in 0..n {
let u = ctrl.update(r, y);
y = a_p * y + b_p * u;
}
ctrl.error()
}
#[test]
fn test_tracking_error_decreases() {
let early = simulate(20, 0.8, 1.0).abs();
let late = simulate(500, 0.8, 1.0).abs();
assert!(late < early, "early={early}, late={late}");
}
#[test]
fn test_lyapunov_value_non_negative() {
let a_m = 0.7_f64;
let b_m = 0.3_f64;
let gamma = 0.05_f64;
let p = 1.0 / (1.0 - a_m * a_m);
let mut ctrl = LyapunovMrac::new(a_m, b_m, gamma, p);
let mut y = 0.0_f64;
let theta_star = [1.0_f64, 0.0_f64];
for _ in 0..100 {
let u = ctrl.update(1.0, y);
y = 0.8 * y + u;
let v = ctrl.lyapunov_value(theta_star);
assert!(v >= 0.0, "V={v}");
}
}
#[test]
fn test_reset_clears_state() {
let mut ctrl = LyapunovMrac::new(0.7_f64, 0.3_f64, 0.05_f64, 2.0_f64);
for _ in 0..50 {
ctrl.update(1.0, 0.5);
}
ctrl.reset();
assert_eq!(ctrl.error(), 0.0);
assert_eq!(ctrl.y_m_out, 0.0);
assert_eq!(ctrl.theta[1], 0.0);
}
#[test]
fn test_reference_model_output() {
let a_m = 0.8_f64;
let b_m = 0.2_f64;
let mut ctrl = LyapunovMrac::new(a_m, b_m, 0.01_f64, 5.0_f64);
let r = 1.0_f64;
let mut expected_ym = 0.0_f64;
for _ in 0..10 {
ctrl.update(r, expected_ym); expected_ym = a_m * expected_ym + b_m * r;
}
let diff = (ctrl.y_m_out - expected_ym).abs();
assert!(
diff < 0.5,
"y_m_out={} expected={}",
ctrl.y_m_out,
expected_ym
);
}
}