use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct MracSecondOrder<S: ControlScalar> {
pub omega_n: S,
pub zeta: S,
pub dt: S,
pub gamma: S,
pub theta_max: S,
pub theta1: S,
pub theta2: S,
pub theta3: S,
y_m: S,
yd_m: S,
error: S,
epsilon: S,
}
impl<S: ControlScalar> MracSecondOrder<S> {
pub fn new(omega_n: S, zeta: S, dt: S, gamma: S, theta_max: S) -> Self {
let two = S::from_f64(2.0);
Self {
omega_n,
zeta,
dt,
gamma,
theta_max,
theta1: omega_n * omega_n,
theta2: -(omega_n * omega_n),
theta3: -(two * zeta * omega_n),
y_m: S::ZERO,
yd_m: S::ZERO,
error: S::ZERO,
epsilon: S::from_f64(1e-4),
}
}
pub fn update(&mut self, r: S, y: S, yd: S) -> S {
let wn = self.omega_n;
let wn2 = wn * wn;
let two = S::from_f64(2.0);
let ydd_m = wn2 * r - two * self.zeta * wn * self.yd_m - wn2 * self.y_m;
self.yd_m += self.dt * ydd_m;
self.y_m += self.dt * self.yd_m;
self.error = y - self.y_m;
let phi_r = r;
let phi_y = y;
let phi_yd = yd;
let norm = S::ONE + phi_r * phi_r + phi_y * phi_y + phi_yd * phi_yd + self.epsilon;
let grad_scale = self.gamma * self.error / norm;
self.theta1 -= grad_scale * phi_r;
self.theta2 -= grad_scale * phi_y;
self.theta3 -= grad_scale * phi_yd;
self.theta1 = self.theta1.clamp_val(-self.theta_max, self.theta_max);
self.theta2 = self.theta2.clamp_val(-self.theta_max, self.theta_max);
self.theta3 = self.theta3.clamp_val(-self.theta_max, self.theta_max);
self.theta1 * r + self.theta2 * y + self.theta3 * yd
}
pub fn reference_output(&self) -> S {
self.y_m
}
pub fn reference_velocity(&self) -> S {
self.yd_m
}
pub fn tracking_error(&self) -> S {
self.error
}
pub fn reset_states(&mut self) {
self.y_m = S::ZERO;
self.yd_m = S::ZERO;
self.error = S::ZERO;
}
pub fn reset(&mut self) {
self.reset_states();
let two = S::from_f64(2.0);
self.theta1 = self.omega_n * self.omega_n;
self.theta2 = -(self.omega_n * self.omega_n);
self.theta3 = -(two * self.zeta * self.omega_n);
}
}
#[derive(Debug, Clone, Copy)]
pub struct LyapunovMracFirstOrder<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,
lyapunov_v: S,
}
impl<S: ControlScalar> LyapunovMracFirstOrder<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,
lyapunov_v: S::ZERO,
}
}
pub fn update(&mut self, r: S, y: S) -> S {
self.y_m = self.a_m * self.y_m + self.b_m * r;
self.error = y - self.y_m;
let delta_theta_r = -self.gamma * self.error * r;
let delta_theta_y = -self.gamma * self.error * y;
self.theta_r += delta_theta_r;
self.theta_y += delta_theta_y;
let inv_gamma = if self.gamma.abs() > S::EPSILON {
S::ONE / self.gamma
} else {
S::from_f64(1e6)
};
self.lyapunov_v = self.error * self.error
+ inv_gamma * (self.theta_r * self.theta_r + self.theta_y * self.theta_y);
self.theta_r * r + self.theta_y * y
}
pub fn reference_output(&self) -> S {
self.y_m
}
pub fn tracking_error(&self) -> S {
self.error
}
pub fn lyapunov_value(&self) -> S {
self.lyapunov_v
}
pub fn reset(&mut self) {
self.y_m = S::ZERO;
self.error = S::ZERO;
self.lyapunov_v = S::ZERO;
self.theta_r = self.b_m;
self.theta_y = S::ZERO;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn second_order_reference_model_advances() {
let mut mrac = MracSecondOrder::<f64>::new(5.0, 0.7, 0.01, 0.001, 100.0);
let y_prev = mrac.reference_output();
mrac.update(1.0, 0.0, 0.0);
let _ = y_prev; assert!(mrac.reference_velocity().abs() >= 0.0); }
#[test]
fn second_order_tracks_step_reference() {
let dt = 0.01_f64;
let mut mrac = MracSecondOrder::<f64>::new(3.0, 0.8, dt, 0.005, 50.0);
let mut y = 0.0_f64;
let mut yd = 0.0_f64;
for _ in 0..3000 {
let u = mrac.update(1.0, y, yd);
let ydd = 2.0 * u - 3.0 * yd - y;
yd += dt * ydd;
y += dt * yd;
if y.abs() > 1e4 {
break; }
}
assert!(
mrac.tracking_error().abs() < 5.0,
"error={}",
mrac.tracking_error()
);
}
#[test]
fn second_order_projection_bounds_parameters() {
let mut mrac = MracSecondOrder::<f64>::new(5.0, 0.7, 0.01, 10.0, 20.0);
for _ in 0..1000 {
mrac.update(100.0, 50.0, 10.0);
}
assert!(mrac.theta1.abs() <= 20.0 + 1e-9);
assert!(mrac.theta2.abs() <= 20.0 + 1e-9);
assert!(mrac.theta3.abs() <= 20.0 + 1e-9);
}
#[test]
fn second_order_reset_clears_states() {
let mut mrac = MracSecondOrder::<f64>::new(5.0, 0.7, 0.01, 0.01, 100.0);
for _ in 0..100 {
mrac.update(1.0, 0.5, 0.1);
}
mrac.reset();
assert_eq!(mrac.reference_output(), 0.0);
assert_eq!(mrac.tracking_error(), 0.0);
}
#[test]
fn lyapunov_mrac_stays_bounded() {
let mut mrac = LyapunovMracFirstOrder::<f64>::new(0.8, 0.2, 0.005);
let mut y = 0.0_f64;
let mut bounded = true;
for _ in 0..5000 {
let u = mrac.update(1.0, y);
y = 0.7 * y + 1.5 * u;
if y.abs() > 1000.0 {
bounded = false;
break;
}
}
assert!(bounded, "Lyapunov MRAC output diverged");
}
#[test]
fn lyapunov_mrac_value_nonnegative() {
let mut mrac = LyapunovMracFirstOrder::<f64>::new(0.8, 0.2, 0.01);
let mut y = 0.0_f64;
for _ in 0..100 {
let u = mrac.update(1.0, y);
y = 0.7 * y + 1.5 * u;
}
assert!(mrac.lyapunov_value() >= 0.0);
}
#[test]
fn lyapunov_mrac_reset() {
let mut mrac = LyapunovMracFirstOrder::<f64>::new(0.8, 0.2, 0.01);
let mut y = 0.0_f64;
for _ in 0..100 {
let u = mrac.update(1.0, y);
y = 0.7 * y + u;
}
mrac.reset();
assert_eq!(mrac.reference_output(), 0.0);
assert_eq!(mrac.tracking_error(), 0.0);
assert_eq!(mrac.theta_y, 0.0);
}
}