use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct Mrac<S: ControlScalar> {
pub a_m: S,
pub b_m: S,
pub gamma: S,
pub theta_r: S,
pub theta_y: S,
y_m: S,
error: S,
}
impl<S: ControlScalar> Mrac<S> {
pub fn new(a_m: S, b_m: S, gamma: S) -> Self {
Self {
a_m,
b_m,
gamma,
theta_r: b_m, theta_y: S::ZERO, y_m: S::ZERO,
error: S::ZERO,
}
}
pub fn update(&mut self, r: S, y_p: S) -> S {
self.y_m = self.a_m * self.y_m + self.b_m * r;
self.error = y_p - self.y_m;
self.theta_r -= self.gamma * self.error * r;
self.theta_y -= self.gamma * self.error * y_p;
self.theta_r * r + self.theta_y * y_p
}
pub fn reference_output(&self) -> S {
self.y_m
}
pub fn tracking_error(&self) -> S {
self.error
}
pub fn reset(&mut self) {
self.y_m = S::ZERO;
self.error = S::ZERO;
self.theta_r = self.b_m;
self.theta_y = S::ZERO;
}
}
#[derive(Debug, Clone, Copy)]
pub struct MracNormalized<S: ControlScalar> {
inner: Mrac<S>,
epsilon: S,
}
impl<S: ControlScalar> MracNormalized<S> {
pub fn new(a_m: S, b_m: S, gamma: S) -> Self {
Self {
inner: Mrac::new(a_m, b_m, gamma),
epsilon: S::from_f64(1e-4),
}
}
pub fn update(&mut self, r: S, y_p: S) -> S {
self.inner.y_m = self.inner.a_m * self.inner.y_m + self.inner.b_m * r;
self.inner.error = y_p - self.inner.y_m;
let norm = S::ONE + r * r + y_p * y_p + self.epsilon;
self.inner.theta_r -= self.inner.gamma * self.inner.error * r / norm;
self.inner.theta_y -= self.inner.gamma * self.inner.error * y_p / norm;
self.inner.theta_r * r + self.inner.theta_y * y_p
}
pub fn reference_output(&self) -> S {
self.inner.y_m
}
pub fn tracking_error(&self) -> S {
self.inner.error
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mrac_tracks_reference_model() {
let mut mrac = Mrac::<f64>::new(0.8, 0.2, 0.01);
let mut y_p = 0.0_f64;
for _ in 0..2000 {
let r = 1.0;
let u = mrac.update(r, y_p);
y_p = 0.7 * y_p + 2.0 * u;
}
let y_m_ss = mrac.reference_output();
assert!(
(y_p - y_m_ss).abs() < 0.5,
"y_p={:.4}, y_m={:.4}",
y_p,
y_m_ss
);
}
#[test]
fn mrac_normalized_stable() {
let mut mrac = MracNormalized::<f64>::new(0.8, 0.2, 0.1);
let mut y_p = 0.0_f64;
let mut bounded = true;
for _ in 0..5000 {
let r = 1.0;
let u = mrac.update(r, y_p);
y_p = 0.7 * y_p + 2.0 * u;
if y_p.abs() > 1000.0 {
bounded = false;
break;
}
}
assert!(bounded, "Output should remain bounded");
}
#[test]
fn zero_input_stays_zero() {
let mut mrac = Mrac::<f64>::new(0.8, 0.2, 0.01);
let u = mrac.update(0.0, 0.0);
assert_eq!(u, 0.0);
assert_eq!(mrac.reference_output(), 0.0);
}
#[test]
fn reset_returns_to_initial() {
let mut mrac = Mrac::<f64>::new(0.8, 0.2, 0.01);
for _ in 0..100 {
mrac.update(1.0, 0.5);
}
mrac.reset();
assert_eq!(mrac.reference_output(), 0.0);
assert_eq!(mrac.tracking_error(), 0.0);
}
}