use super::DynamicalSystem;
pub struct MackeyGlass {
history: Vec<f64>,
head: usize, buf_len: usize,
current_x: f64,
pub beta: f64,
pub gamma: f64,
pub tau: f64,
pub n: f64,
dt: f64,
speed: f64,
prev_deriv: f64,
observable: Vec<f64>,
}
impl MackeyGlass {
pub fn new() -> Self {
let beta = 0.2;
let gamma = 0.1;
let tau = 17.0;
let n_param = 10.0;
let dt = 0.5;
let buf_len = ((tau / dt) as f64).ceil() as usize + 1;
let initial_x = 1.5;
let observable = vec![initial_x; 3];
Self {
history: vec![initial_x; buf_len],
head: 0,
buf_len,
current_x: initial_x,
beta,
gamma,
n: n_param,
tau,
dt,
speed: 0.0,
prev_deriv: 0.0,
observable,
}
}
fn delayed_x(&self) -> f64 {
self.history[self.head]
}
fn mg_deriv(&self, x: f64, x_delayed: f64) -> f64 {
self.beta * x_delayed / (1.0 + x_delayed.powf(self.n)) - self.gamma * x
}
fn update_observable(&mut self) {
let n = self.buf_len;
let offset_third = (n / 3).max(1);
let offset_two_thirds = (2 * n / 3).max(1);
let cur_idx = (self.head + n - 1) % n;
let third_idx = (self.head + n - 1 + n - offset_third) % n;
let two_thirds_idx = (self.head + n - 1 + n - offset_two_thirds) % n;
self.observable[0] = self.history[cur_idx];
self.observable[1] = self.history[third_idx];
self.observable[2] = self.history[two_thirds_idx];
}
}
impl DynamicalSystem for MackeyGlass {
fn state(&self) -> &[f64] {
&self.observable
}
fn dimension(&self) -> usize {
3
}
fn name(&self) -> &str {
"mackey_glass"
}
fn speed(&self) -> f64 {
self.speed
}
fn deriv_at(&self, _state: &[f64]) -> Vec<f64> {
vec![0.0; 3]
}
fn step(&mut self, _dt: f64) {
let x_delayed = self.delayed_x();
let deriv_curr = self.mg_deriv(self.current_x, x_delayed);
let x_pred = self.current_x + self.dt * (1.5 * deriv_curr - 0.5 * self.prev_deriv);
let deriv_pred = self.mg_deriv(x_pred, x_delayed);
let new_x = self.current_x + self.dt * 0.5 * (deriv_curr + deriv_pred);
self.prev_deriv = deriv_curr;
self.history[self.head] = new_x;
self.head = (self.head + 1) % self.buf_len;
self.speed = deriv_curr.abs();
self.current_x = new_x;
self.update_observable();
}
fn set_state(&mut self, s: &[f64]) {
if let Some(&v) = s.first() {
if v.is_finite() {
self.current_x = v;
self.prev_deriv = 0.0;
for slot in &mut self.history {
*slot = v;
}
for o in &mut self.observable {
*o = v;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::DynamicalSystem;
#[test]
fn test_mackey_glass_initial_state() {
let sys = MackeyGlass::new();
let s = sys.state();
assert_eq!(s.len(), 3);
assert!(s.iter().all(|v| v.is_finite()), "Initial state has non-finite values");
assert_eq!(sys.name(), "mackey_glass");
assert_eq!(sys.dimension(), 3);
}
#[test]
fn test_mackey_glass_step_changes_state() {
let mut sys = MackeyGlass::new();
for _ in 0..50 {
sys.step(0.5);
}
let before: Vec<f64> = sys.state().to_vec();
sys.step(0.5);
let after = sys.state();
assert!(
before.iter().zip(after.iter()).any(|(a, b)| (a - b).abs() > 1e-15),
"State did not change after step"
);
}
#[test]
fn test_mackey_glass_state_stays_finite() {
let mut sys = MackeyGlass::new();
for _ in 0..500 {
sys.step(0.5);
}
for v in sys.state().iter() {
assert!(v.is_finite(), "State became non-finite: {}", v);
}
}
#[test]
fn test_mackey_glass_set_state() {
let mut sys = MackeyGlass::new();
sys.set_state(&[2.0]);
let s = sys.state();
for v in s.iter() {
assert!((*v - 2.0).abs() < 1e-10, "Observable not reset to new x: {}", v);
}
}
#[test]
fn test_mackey_glass_set_state_nan_ignored() {
let mut sys = MackeyGlass::new();
let original_x = sys.state()[0];
sys.set_state(&[f64::NAN]);
assert!(
(sys.state()[0] - original_x).abs() < 1e-10,
"NaN set_state should be ignored"
);
}
#[test]
fn test_mackey_glass_speed_positive_after_step() {
let mut sys = MackeyGlass::new();
for _ in 0..50 {
sys.step(0.1);
}
let speed_before = sys.speed();
sys.step(0.1);
assert!(sys.speed() >= 0.0, "speed should be non-negative: {}", speed_before);
}
#[test]
fn test_mackey_glass_state_finite_after_long_run() {
let mut sys = MackeyGlass::new();
for _ in 0..3000 {
sys.step(0.1);
}
assert!(
sys.state().iter().all(|v| v.is_finite()),
"State should stay finite: {:?}", sys.state()
);
}
}