pub fn generate(
a: f32,
b: f32,
c: f32,
initial: (f32, f32, f32),
dt: f32,
steps: usize,
) -> Vec<(f32, f32, f32)> {
let mut path = Vec::with_capacity(steps);
let (mut x, mut y, mut z) = initial;
for _ in 0..steps {
let k1_x = -y - z;
let k1_y = x + a * y;
let k1_z = b + z * (x - c);
let x2 = x + k1_x * dt * 0.5;
let y2 = y + k1_y * dt * 0.5;
let z2 = z + k1_z * dt * 0.5;
let k2_x = -y2 - z2;
let k2_y = x2 + a * y2;
let k2_z = b + z2 * (x2 - c);
let x3 = x + k2_x * dt * 0.5;
let y3 = y + k2_y * dt * 0.5;
let z3 = z + k2_z * dt * 0.5;
let k3_x = -y3 - z3;
let k3_y = x3 + a * y3;
let k3_z = b + z3 * (x3 - c);
let x4 = x + k3_x * dt;
let y4 = y + k3_y * dt;
let z4 = z + k3_z * dt;
let k4_x = -y4 - z4;
let k4_y = x4 + a * y4;
let k4_z = b + z4 * (x4 - c);
x += (dt / 6.0) * (k1_x + 2.0 * k2_x + 2.0 * k3_x + k4_x);
y += (dt / 6.0) * (k1_y + 2.0 * k2_y + 2.0 * k3_y + k4_y);
z += (dt / 6.0) * (k1_z + 2.0 * k2_z + 2.0 * k3_z + k4_z);
path.push((x, y, z));
}
path
}
pub fn rossler_spiral(steps: usize) -> Vec<(f32, f32, f32)> {
let total_steps = steps + 100;
let full_path = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, total_steps);
full_path.into_iter().skip(100).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rossler_attractor_length() {
let path = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 100);
assert_eq!(path.len(), 100);
}
#[test]
fn test_rossler_spiral() {
let path = rossler_spiral(50);
assert_eq!(path.len(), 50);
}
#[test]
fn test_rossler_stays_bounded() {
let path = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 1000);
for (x, y, z) in path {
assert!(x.abs() < 20.0, "X should stay bounded, got {}", x);
assert!(y.abs() < 20.0, "Y should stay bounded, got {}", y);
assert!(z < 30.0 && z > -5.0, "Z should stay bounded, got {}", z);
}
}
#[test]
fn test_rossler_is_chaotic() {
let path1 = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 2000);
let path2 = generate(0.2, 0.2, 5.7, (0.11, 0.0, 0.0), 0.01, 2000);
let (x1, y1, z1) = path1[1999];
let (x2, y2, z2) = path2[1999];
let distance = ((x2 - x1).powi(2) + (y2 - y1).powi(2) + (z2 - z1).powi(2)).sqrt();
assert!(
distance > 0.01,
"Rössler paths should diverge from different initial conditions, distance was {}",
distance
);
}
#[test]
fn test_rossler_deterministic() {
let path1 = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 100);
let path2 = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 100);
for i in 0..100 {
let (x1, y1, z1) = path1[i];
let (x2, y2, z2) = path2[i];
assert!((x1 - x2).abs() < 1e-6);
assert!((y1 - y2).abs() < 1e-6);
assert!((z1 - z2).abs() < 1e-6);
}
}
#[test]
fn test_rossler_different_c_values() {
let path_periodic = generate(0.2, 0.2, 3.0, (0.1, 0.0, 0.0), 0.01, 1000);
let path_chaotic = generate(0.2, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 1000);
for (x, y, z) in &path_periodic {
assert!(x.is_finite() && y.is_finite() && z.is_finite());
}
for (x, y, z) in &path_chaotic {
assert!(x.is_finite() && y.is_finite() && z.is_finite());
}
let (x1_a, _, _) = path_periodic[500];
let (x2_a, _, _) = path_chaotic[500];
let (x1_b, _, _) = path_periodic[800];
let (x2_b, _, _) = path_chaotic[800];
let diff_a = (x1_a - x2_a).abs();
let diff_b = (x1_b - x2_b).abs();
assert!(diff_a > 0.01 || diff_b > 0.1,
"Different c values should produce different trajectories, diffs: {}, {}", diff_a, diff_b);
}
#[test]
fn test_rossler_different_a_values() {
let path1 = generate(0.1, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 500);
let path2 = generate(0.3, 0.2, 5.7, (0.1, 0.0, 0.0), 0.01, 500);
let (x1, y1, _) = path1[499];
let (x2, y2, _) = path2[499];
let dist = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
assert!(dist > 0.1, "Different a values should produce different trajectories");
}
#[test]
fn test_rossler_all_coordinates_evolve() {
let path = generate(0.2, 0.2, 5.7, (1.0, 1.0, 1.0), 0.01, 500);
let (x0, y0, z0) = path[0];
let mut x_changed = false;
let mut y_changed = false;
let mut z_changed = false;
for i in 1..500 {
let (x, y, z) = path[i];
if (x - x0).abs() > 0.5 {
x_changed = true;
}
if (y - y0).abs() > 0.5 {
y_changed = true;
}
if (z - z0).abs() > 0.5 {
z_changed = true;
}
}
assert!(x_changed, "X coordinate should evolve over time");
assert!(y_changed, "Y coordinate should evolve over time");
assert!(z_changed, "Z coordinate should evolve over time");
}
}
pub fn classic() -> Vec<(f32, f32, f32)> {
rossler_spiral(500)
}
pub fn extended() -> Vec<(f32, f32, f32)> {
rossler_spiral(1000)
}
pub fn chaotic() -> Vec<(f32, f32, f32)> {
let full = generate(0.2, 0.2, 5.4, (0.1, 0.0, 0.0), 0.01, 600);
full.into_iter().skip(100).collect()
}