use temporal_attractor_studio::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
temporal_attractor_studio::init()?;
println!("=== Temporal Attractor Studio - FTLE Integration Test ===\n");
println!("1. Testing with chaotic trajectory data:");
test_chaotic_trajectory()?;
println!("\n2. Testing delay embedding for univariate time series:");
test_delay_embedding()?;
println!("\n3. Testing with custom FTLE parameters:");
test_custom_parameters()?;
println!("\n4. Testing FTLE field calculation:");
test_ftle_field()?;
println!("\n=== All FTLE tests completed successfully! ===");
Ok(())
}
fn test_chaotic_trajectory() -> StudioResult<()> {
let mut trajectory = Vec::new();
let dt = 0.01;
let n_points = 2000;
let mut x = 1.0;
let mut y = 1.0;
for i in 0..n_points {
let t = i as f64 * dt;
let x_new = 1.0 - 1.4 * x * x + y + 0.01 * (t * 0.1).sin();
let y_new = 0.3 * x + 0.005 * (t * 0.17).cos();
trajectory.push(vec![x_new, y_new]);
x = x_new;
y = y_new;
}
println!(" Generated {} trajectory points", trajectory.len());
let result = estimate_lyapunov_default(&trajectory)?;
println!(" Lyapunov exponent: {:.6}", result.lambda);
println!(" Doubling time: {:.3} time units", result.doubling_time);
println!(" Lyapunov time: {:.3} time units", result.lyapunov_time);
println!(" Pairs found: {}", result.pairs_found);
println!(" Points used: {}", result.points_used);
println!(" Dimension: {}", result.dimension);
if result.lambda > 0.0 {
println!(" ✓ Positive Lyapunov exponent indicates chaotic behavior");
}
Ok(())
}
fn test_delay_embedding() -> StudioResult<()> {
let mut series = Vec::new();
let r = 3.8; let mut x = 0.5;
for _ in 0..1500 {
x = r * x * (1.0 - x);
series.push(x);
}
println!(" Generated univariate series with {} points", series.len());
let embedding_dim = 3;
let delay = 1;
let embedded = delay_embed(&series, embedding_dim, delay)?;
println!(" Embedded dimension: {}", embedding_dim);
println!(" Delay: {}", delay);
println!(" Embedded vectors: {}", embedded.len());
for (i, vec) in embedded.iter().take(3).enumerate() {
println!(" Vector {}: [{:.4}, {:.4}, {:.4}]", i, vec[0], vec[1], vec[2]);
}
let result = estimate_lyapunov_default(&embedded)?;
println!(" Embedded series Lyapunov exponent: {:.6}", result.lambda);
println!(" Doubling time: {:.3} time units", result.doubling_time);
println!(" Pairs found: {}", result.pairs_found);
Ok(())
}
fn test_custom_parameters() -> StudioResult<()> {
let mut trajectory = Vec::new();
let dt = 0.005;
let mut x = 1.0;
let mut y = 0.5;
let mut z = 0.0;
for i in 0..1000 {
let t = i as f64 * dt;
let dx = -y - z + 0.01 * (t * 0.1).sin();
let dy = x + 0.2 * y + 0.01 * (t * 0.13).cos();
let dz = 0.2 + z * (x - 10.0) + 0.005 * (t * 0.07).sin();
x += dx * dt;
y += dy * dt;
z += dz * dt;
trajectory.push(vec![x, y, z]);
}
println!(" Generated 3D trajectory with {} points", trajectory.len());
let custom_params = FtleParams {
dt: 0.005,
k_fit: 15, theiler: 30, max_pairs: 6000, min_init_sep: 1e-10, };
let result = estimate_lyapunov_with_params(&trajectory, &custom_params)?;
println!(" Custom parameters used:");
println!(" dt: {}", custom_params.dt);
println!(" k_fit: {}", custom_params.k_fit);
println!(" theiler: {}", custom_params.theiler);
println!(" max_pairs: {}", custom_params.max_pairs);
println!(" min_init_sep: {:.0e}", custom_params.min_init_sep);
println!(" Results:");
println!(" Lyapunov exponent: {:.6}", result.lambda);
println!(" Doubling time: {:.3} time units", result.doubling_time);
println!(" Pairs found: {}", result.pairs_found);
println!(" Points used: {}", result.points_used);
println!(" Dimension: {}", result.dimension);
Ok(())
}
fn test_ftle_field() -> StudioResult<()> {
let grid_size = 20;
let mut initial_conditions = Vec::new();
for i in 0..grid_size {
for j in 0..grid_size {
let x = -2.0 + 4.0 * i as f64 / (grid_size - 1) as f64;
let y = -2.0 + 4.0 * j as f64 / (grid_size - 1) as f64;
initial_conditions.push(vec![x, y]);
}
}
println!(" Created {}x{} grid ({} points)", grid_size, grid_size, initial_conditions.len());
let integration_time = 2.0;
let dt = 0.01;
let steps = (integration_time / dt) as usize;
let mut all_trajectories = Vec::new();
for ic in &initial_conditions {
let mut traj = Vec::new();
let mut x = ic[0];
let mut y = ic[1];
for _ in 0..steps {
let dx = -y + x * (1.0 - x * x - y * y);
let dy = x + y * (1.0 - x * x - y * y);
x += dx * dt;
y += dy * dt;
traj.push(vec![x, y]);
}
all_trajectories.push(traj);
}
let ftle_field = calculate_ftle_field(&all_trajectories[0], steps, dt)?;
println!(" FTLE field calculated:");
println!(" Integration time: {:.1}", integration_time);
println!(" Field size: {}", ftle_field.len());
let min_ftle = ftle_field.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_ftle = ftle_field.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let mean_ftle = mean(&ftle_field);
println!(" FTLE range: [{:.4}, {:.4}]", min_ftle, max_ftle);
println!(" Mean FTLE: {:.4}", mean_ftle);
println!(" Sample FTLE values:");
for i in 0..std::cmp::min(5, ftle_field.len()) {
println!(" Point {}: {:.4}", i, ftle_field[i]);
}
Ok(())
}