use crate::systems::DynamicalSystem;
use std::collections::VecDeque;
pub struct FractionalLorenz {
state: Vec<f64>,
history_x: VecDeque<f64>,
history_y: VecDeque<f64>,
history_z: VecDeque<f64>,
pub alpha: f64,
pub sigma: f64,
pub rho: f64,
pub beta: f64,
memory_len: usize,
gl_coeffs: Vec<f64>,
}
fn gl_coefficients(alpha: f64, n: usize) -> Vec<f64> {
let mut coeffs = vec![0.0f64; n];
if n == 0 {
return coeffs;
}
coeffs[0] = 1.0;
for k in 1..n {
coeffs[k] = coeffs[k - 1] * ((k as f64 - 1.0 - alpha) / k as f64);
}
coeffs
}
impl FractionalLorenz {
pub fn new(alpha: f64, sigma: f64, rho: f64, beta: f64) -> Self {
let memory_len = 64;
let gl_coeffs = gl_coefficients(alpha, memory_len);
let mut history_x = VecDeque::with_capacity(memory_len);
let mut history_y = VecDeque::with_capacity(memory_len);
let mut history_z = VecDeque::with_capacity(memory_len);
let x0 = 1.0f64;
let y0 = 1.0f64;
let z0 = 1.0f64;
history_x.push_back(x0);
history_y.push_back(y0);
history_z.push_back(z0);
Self {
state: vec![x0, y0, z0],
history_x,
history_y,
history_z,
alpha,
sigma,
rho,
beta,
memory_len,
gl_coeffs,
}
}
fn gl_sum(history: &VecDeque<f64>, coeffs: &[f64]) -> f64 {
history
.iter()
.zip(coeffs.iter().skip(1))
.map(|(&h, &c)| c * h)
.sum::<f64>()
}
}
impl DynamicalSystem for FractionalLorenz {
fn state(&self) -> &[f64] {
&self.state
}
fn step(&mut self, dt: f64) {
let n = self.memory_len;
if (self.gl_coeffs[1] - (-(self.alpha))).abs() > 1e-10 {
self.gl_coeffs = gl_coefficients(self.alpha, n);
}
let x = self.state[0];
let y = self[1];
let z = self[2];
let h_alpha = dt.powf(self.alpha);
let sum_x = Self::gl_sum(&self.history_x, &self.gl_coeffs);
let sum_y = Self::gl_sum(&self.history_y, &self.gl_coeffs);
let sum_z = Self::gl_sum(&self.history_z, &self.gl_coeffs);
let dx = h_alpha * (self.sigma * (y - x)) - sum_x;
let dy = h_alpha * (x * (self.rho - z) - y) - sum_y;
let dz = h_alpha * (x * y - self.beta * z) - sum_z;
let new_x = dx;
let new_y = dy;
let new_z = dz;
self.history_x.push_front(new_x);
self.history_y.push_front(new_y);
self.history_z.push_front(new_z);
if self.history_x.len() > self.memory_len {
self.history_x.pop_back();
}
if self.history_y.len() > self.memory_len {
self.history_y.pop_back();
}
if self.history_z.len() > self.memory_len {
self.history_z.pop_back();
}
if new_x.abs() > 1000.0
|| new_y.abs() > 1000.0
|| new_z.abs() > 1000.0
|| !new_x.is_finite()
|| !new_y.is_finite()
|| !new_z.is_finite()
{
self.state[0] = 0.1;
self.state[1] = 0.0;
self.state[2] = 0.1;
self.history_x.clear();
self.history_x.push_back(0.1);
self.history_y.clear();
self.history_y.push_back(0.0);
self.history_z.clear();
self.history_z.push_back(0.1);
return;
}
self.state[0] = new_x;
self.state[1] = new_y;
self.state[2] = new_z;
}
fn dimension(&self) -> usize {
3
}
fn name(&self) -> &str {
"fractional_lorenz"
}
fn speed(&self) -> f64 {
let dx = self.sigma * (self.state[1] - self.state[0]);
let dy = self.state[0] * (self.rho - self.state[2]) - self.state[1];
let dz = self.state[0] * self.state[1] - self.beta * self.state[2];
(dx * dx + dy * dy + dz * dz).sqrt()
}
fn set_state(&mut self, s: &[f64]) {
let n = self.state.len().min(s.len());
for i in 0..n {
if s[i].is_finite() {
self.state[i] = s[i];
}
}
self.history_x.clear();
self.history_x.push_back(self.state[0]);
self.history_y.clear();
self.history_y.push_back(self.state[1]);
self.history_z.clear();
self.history_z.push_back(self.state[2]);
}
fn deriv_at(&self, state: &[f64]) -> Vec<f64> {
if state.len() < 3 {
return vec![0.0; 3];
}
vec![
self.sigma * (state[1] - state[0]),
state[0] * (self.rho - state[2]) - state[1],
state[0] * state[1] - self.beta * state[2],
]
}
}
impl std::ops::Index<usize> for FractionalLorenz {
type Output = f64;
fn index(&self, i: usize) -> &f64 {
&self.state[i]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::DynamicalSystem;
#[test]
fn test_fractional_lorenz_initial_state() {
let sys = FractionalLorenz::new(0.95, 10.0, 28.0, 8.0 / 3.0);
let s = sys.state();
assert_eq!(s.len(), 3);
assert!(s.iter().all(|v| v.is_finite()));
assert_eq!(sys.name(), "fractional_lorenz");
assert_eq!(sys.dimension(), 3);
}
#[test]
fn test_fractional_lorenz_step_changes_state() {
let mut sys = FractionalLorenz::new(0.95, 10.0, 28.0, 8.0 / 3.0);
let before: Vec<f64> = sys.state().to_vec();
sys.step(0.01);
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_fractional_lorenz_state_stays_finite() {
let mut sys = FractionalLorenz::new(0.95, 10.0, 28.0, 8.0 / 3.0);
for _ in 0..500 {
sys.step(0.01);
}
for v in sys.state().iter() {
assert!(v.is_finite(), "State became non-finite: {}", v);
}
}
#[test]
fn test_fractional_lorenz_set_state() {
let mut sys = FractionalLorenz::new(0.95, 10.0, 28.0, 8.0 / 3.0);
sys.set_state(&[2.0, 3.0, 4.0]);
let s = sys.state();
assert!((s[0] - 2.0).abs() < 1e-15);
assert!((s[1] - 3.0).abs() < 1e-15);
assert!((s[2] - 4.0).abs() < 1e-15);
let mut sys2 = sys;
sys2.step(0.01);
for v in sys2.state().iter() {
assert!(v.is_finite(), "State became non-finite after set_state+step: {}", v);
}
}
#[test]
fn test_fractional_lorenz_alpha_one_like_lorenz() {
let mut sys = FractionalLorenz::new(1.0, 10.0, 28.0, 8.0 / 3.0);
for _ in 0..100 {
sys.step(0.01);
}
let s = sys.state();
assert!(s.iter().all(|v| v.is_finite()));
}
#[test]
fn test_fractional_lorenz_speed_positive_after_step() {
let mut sys = FractionalLorenz::new(0.95, 10.0, 28.0, 8.0 / 3.0);
sys.step(0.01);
assert!(sys.speed() > 0.0, "speed should be positive: {}", sys.speed());
}
#[test]
fn test_fractional_lorenz_different_alpha_different_dynamics() {
let mut sys_low = FractionalLorenz::new(0.8, 10.0, 28.0, 8.0 / 3.0);
let mut sys_high = FractionalLorenz::new(1.0, 10.0, 28.0, 8.0 / 3.0);
for _ in 0..200 {
sys_low.step(0.01);
sys_high.step(0.01);
}
let d: f64 = sys_low.state().iter().zip(sys_high.state().iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
assert!(d > 1e-6, "Different alpha should give different trajectories: d={}", d);
}
}