pub fn generate(a: f32, b: f32, c: f32, d: f32, initial: (f32, f32), n: usize) -> Vec<(f32, f32)> {
let mut path = Vec::with_capacity(n);
let (mut x, mut y) = initial;
for _ in 0..n {
path.push((x, y));
let denominator = 1.0 + x * x + y * y;
let t = c - d / denominator;
let cos_t = t.cos();
let sin_t = t.sin();
let x_next = a + b * (x * cos_t - y * sin_t);
let y_next = b * (x * sin_t + y * cos_t);
x = x_next;
y = y_next;
}
path
}
pub fn ikeda_x(a: f32, b: f32, c: f32, d: f32, initial: (f32, f32), n: usize) -> Vec<f32> {
generate(a, b, c, d, initial, n)
.into_iter()
.map(|(x, _)| x)
.collect()
}
pub fn ikeda_y(a: f32, b: f32, c: f32, d: f32, initial: (f32, f32), n: usize) -> Vec<f32> {
generate(a, b, c, d, initial, n)
.into_iter()
.map(|(_, y)| y)
.collect()
}
pub fn ikeda_spiral(n: usize) -> Vec<(f32, f32)> {
let total_steps = n + 10;
let full_path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), total_steps);
full_path.into_iter().skip(10).collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ikeda_map_length() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
assert_eq!(path.len(), 100);
}
#[test]
fn test_ikeda_spiral() {
let path = ikeda_spiral(50);
assert_eq!(path.len(), 50);
}
#[test]
fn test_ikeda_first_point_is_initial() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.5, 0.5), 10);
let (x0, y0) = path[0];
assert_eq!(x0, 0.5);
assert_eq!(y0, 0.5);
}
#[test]
fn test_ikeda_stays_bounded() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 1000);
for (x, y) in path {
assert!(
x.abs() < 5.0,
"X should stay bounded, got {}",
x
);
assert!(
y.abs() < 5.0,
"Y should stay bounded, got {}",
y
);
}
}
#[test]
fn test_ikeda_deterministic() {
let path1 = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
let path2 = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
for i in 0..100 {
let (x1, y1) = path1[i];
let (x2, y2) = path2[i];
assert!((x1 - x2).abs() < 1e-6);
assert!((y1 - y2).abs() < 1e-6);
}
}
#[test]
fn test_ikeda_different_initial_conditions() {
let path1 = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
let path2 = generate(1.0, 0.9, 0.4, 6.0, (0.5, 0.5), 100);
let (x1_end, y1_end) = path1[99];
let (x2_end, y2_end) = path2[99];
let dist = ((x2_end - x1_end).powi(2) + (y2_end - y1_end).powi(2)).sqrt();
assert!(dist > 0.1, "Different initial conditions should diverge");
}
#[test]
fn test_ikeda_evolution() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 5);
let (x1, y1) = path[1];
assert!((x1 - 1.0).abs() < 0.001, "x1 should be 1.0, got {}", x1);
assert!((y1 - 0.0).abs() < 0.001, "y1 should be 0.0, got {}", y1);
}
#[test]
fn test_ikeda_different_a_values() {
let path1 = generate(0.8, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
let path2 = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 100);
let (x1, y1) = path1[50];
let (x2, y2) = path2[50];
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_ikeda_different_d_values() {
let path1 = generate(1.0, 0.9, 0.4, 4.0, (0.0, 0.0), 100);
let path2 = generate(1.0, 0.9, 0.4, 8.0, (0.0, 0.0), 100);
let (x1, y1) = path1[50];
let (x2, y2) = path2[50];
let dist = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
assert!(dist > 0.1, "Different D values should produce different trajectories");
}
#[test]
fn test_ikeda_x_convenience() {
let x_only = ikeda_x(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 32);
let full_path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 32);
let x_full: Vec<f32> = full_path.into_iter().map(|(x, _)| x).collect();
assert_eq!(x_only, x_full);
}
#[test]
fn test_ikeda_y_convenience() {
let y_only = ikeda_y(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 32);
let full_path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 32);
let y_full: Vec<f32> = full_path.into_iter().map(|(_, y)| y).collect();
assert_eq!(y_only, y_full);
}
#[test]
fn test_ikeda_single_iteration() {
let path = generate(1.0, 0.9, 0.4, 6.0, (1.0, 1.0), 1);
assert_eq!(path.len(), 1);
let (x0, y0) = path[0];
assert_eq!(x0, 1.0);
assert_eq!(y0, 1.0);
}
#[test]
fn test_ikeda_coordinates_evolve() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.1, 0.1), 100);
let (x0, y0) = path[0];
let (x99, y99) = path[99];
assert!((x99 - x0).abs() > 0.1, "X coordinate should evolve");
assert!((y99 - y0).abs() > 0.1, "Y coordinate should evolve");
}
#[test]
fn test_ikeda_produces_finite_values() {
let path = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 1000);
for (x, y) in path {
assert!(x.is_finite(), "X should be finite");
assert!(y.is_finite(), "Y should be finite");
}
}
#[test]
fn test_ikeda_small_b_converges() {
let path = generate(1.0, 0.5, 0.4, 6.0, (2.0, 2.0), 100);
for (x, y) in path {
assert!(x.is_finite() && y.is_finite());
assert!(x.abs() < 5.0 && y.abs() < 5.0);
}
}
#[test]
fn test_ikeda_phase_parameter() {
let path1 = generate(1.0, 0.9, 0.2, 6.0, (0.0, 0.0), 100);
let path2 = generate(1.0, 0.9, 0.6, 6.0, (0.0, 0.0), 100);
let (x1, y1) = path1[50];
let (x2, y2) = path2[50];
let dist = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
assert!(dist > 0.1, "Different C values should produce different trajectories");
}
#[test]
fn test_ikeda_chaotic_behavior() {
let path1 = generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 200);
let path2 = generate(1.0, 0.9, 0.4, 6.0, (0.001, 0.0), 200);
let (x1, y1) = path1[199];
let (x2, y2) = path2[199];
let distance = ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt();
assert!(
distance > 0.01,
"Ikeda map should be sensitive to initial conditions, distance was {}",
distance
);
}
}
pub fn classic() -> Vec<(f32, f32)> {
generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 150)
}
pub fn chaotic() -> Vec<(f32, f32)> {
generate(1.0, 0.9, 0.4, 7.0, (0.0, 0.0), 200)
}
pub fn extended() -> Vec<(f32, f32)> {
generate(1.0, 0.9, 0.4, 6.0, (0.0, 0.0), 300)
}