use std::f64::consts::PI;
use faer::Mat;
pub trait MapFn: Send + Sync {
fn step(&self, state: &[f64]) -> Vec<f64>;
fn dim(&self) -> usize;
fn name(&self) -> &str;
}
#[derive(Debug, Clone)]
pub struct StandardMap {
pub epsilon: f64,
}
impl Default for StandardMap {
fn default() -> Self {
Self { epsilon: 0.12 }
}
}
impl MapFn for StandardMap {
fn step(&self, state: &[f64]) -> Vec<f64> {
let x = state[0];
let y = state[1];
let y_new = (y + self.epsilon * (2.0 * PI * x).sin()).rem_euclid(1.0);
let x_new = (x + y_new).rem_euclid(1.0);
vec![x_new, y_new]
}
fn dim(&self) -> usize {
2
}
fn name(&self) -> &str {
"standard_map"
}
}
#[derive(Debug, Clone)]
pub struct FroeschleMap {
pub epsilon1: f64,
pub epsilon2: f64,
pub eta: f64,
}
impl Default for FroeschleMap {
fn default() -> Self {
Self {
epsilon1: 0.02,
epsilon2: 0.02,
eta: 0.01,
}
}
}
impl MapFn for FroeschleMap {
fn step(&self, state: &[f64]) -> Vec<f64> {
let (x1, y1, x2, y2) = (state[0], state[1], state[2], state[3]);
let coupling = self.eta * (2.0 * PI * x1 + 2.0 * PI * x2).sin();
let y1_new = (y1 + self.epsilon1 * (2.0 * PI * x1).sin() + coupling).rem_euclid(1.0);
let x1_new = (x1 + y1_new).rem_euclid(1.0);
let y2_new = (y2 + self.epsilon2 * (2.0 * PI * x2).sin() + coupling).rem_euclid(1.0);
let x2_new = (x2 + y2_new).rem_euclid(1.0);
vec![x1_new, y1_new, x2_new, y2_new]
}
fn dim(&self) -> usize {
4
}
fn name(&self) -> &str {
"froeschle_map"
}
}
#[derive(Debug, Clone)]
pub struct ExtendedStandardMap {
pub epsilon: f64,
pub delta: f64,
}
impl Default for ExtendedStandardMap {
fn default() -> Self {
Self {
epsilon: 0.01,
delta: 0.001,
}
}
}
impl MapFn for ExtendedStandardMap {
fn step(&self, state: &[f64]) -> Vec<f64> {
let (x, y, z) = (state[0], state[1], state[2]);
let x_new = (x + self.epsilon * (2.0 * PI * z).sin() + self.delta * (2.0 * PI * y).sin())
.rem_euclid(1.0);
let y_new = (y + self.epsilon * (2.0 * PI * z).sin()).rem_euclid(1.0);
let z_new = (z + x_new).rem_euclid(1.0);
vec![x_new, y_new, z_new]
}
fn dim(&self) -> usize {
3
}
fn name(&self) -> &str {
"extended_standard_map"
}
}
#[derive(Debug, Clone)]
pub struct HenonMap {
pub a: f64,
pub b: f64,
}
impl Default for HenonMap {
fn default() -> Self {
Self { a: 1.4, b: 0.3 }
}
}
impl MapFn for HenonMap {
fn step(&self, state: &[f64]) -> Vec<f64> {
let x = state[0];
let y = state[1];
vec![1.0 - self.a * x * x + y, self.b * x]
}
fn dim(&self) -> usize {
2
}
fn name(&self) -> &str {
"henon_map"
}
}
#[derive(Debug, Clone)]
pub struct LogisticMap {
pub r: f64,
}
impl Default for LogisticMap {
fn default() -> Self {
Self { r: 3.9 }
}
}
impl MapFn for LogisticMap {
fn step(&self, state: &[f64]) -> Vec<f64> {
let x = state[0];
vec![self.r * x * (1.0 - x)]
}
fn dim(&self) -> usize {
1
}
fn name(&self) -> &str {
"logistic_map"
}
}
pub struct ClosureMap<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> {
func: F,
dim: usize,
name: String,
}
impl<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> ClosureMap<F> {
pub fn new(func: F, dim: usize, name: impl Into<String>) -> Self {
Self {
func,
dim,
name: name.into(),
}
}
}
impl<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> MapFn for ClosureMap<F> {
fn step(&self, state: &[f64]) -> Vec<f64> {
(self.func)(state)
}
fn dim(&self) -> usize {
self.dim
}
fn name(&self) -> &str {
&self.name
}
}
pub fn generate_trajectory(initial_condition: &[f64], map: &dyn MapFn, n_iter: usize) -> Mat<f64> {
let n_dim = initial_condition.len();
let mut traj = Mat::<f64>::zeros(n_dim, n_iter + 1);
for i in 0..n_dim {
traj[(i, 0)] = initial_condition[i];
}
let mut state = initial_condition.to_vec();
for k in 1..=n_iter {
state = map.step(&state);
for i in 0..n_dim {
traj[(i, k)] = state[i];
}
}
traj
}
pub fn generate_phase_grid(
x_range: (f64, f64),
y_range: (f64, f64),
resolution: usize,
) -> (Vec<f64>, Vec<f64>) {
let x_coords: Vec<f64> = (0..resolution)
.map(|i| x_range.0 + (x_range.1 - x_range.0) * i as f64 / (resolution - 1) as f64)
.collect();
let y_coords: Vec<f64> = (0..resolution)
.map(|i| y_range.0 + (y_range.1 - y_range.0) * i as f64 / (resolution - 1) as f64)
.collect();
(x_coords, y_coords)
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_near(a: f64, b: f64, eps: f64) {
assert!(
(a - b).abs() < eps,
"expected {a} ≈ {b} (diff = {})",
(a - b).abs()
);
}
#[test]
fn test_standard_map_integrable() {
let map = StandardMap { epsilon: 0.0 };
let state = vec![0.3, 0.2];
let next = map.step(&state);
assert_near(next[1], 0.2, 1e-12); assert_near(next[0], 0.5, 1e-12); }
#[test]
fn test_standard_map_modular() {
let map = StandardMap { epsilon: 0.12 };
let state = vec![0.9, 0.9];
let next = map.step(&state);
assert!(next[0] >= 0.0 && next[0] < 1.0);
assert!(next[1] >= 0.0 && next[1] < 1.0);
}
#[test]
fn test_henon_map_fixed_point() {
let map = HenonMap { a: 0.0, b: 0.0 };
let state = vec![0.5, 0.0];
let next = map.step(&state);
assert_near(next[0], 1.0, 1e-12); assert_near(next[1], 0.0, 1e-12);
}
#[test]
fn test_logistic_map_fixed_point() {
let map = LogisticMap { r: 2.0 };
let state = vec![0.5];
let next = map.step(&state);
assert_near(next[0], 0.5, 1e-12); }
#[test]
fn test_logistic_map_chaos() {
let map = LogisticMap { r: 4.0 };
let state = vec![0.1];
let next = map.step(&state);
assert_near(next[0], 0.36, 1e-12); }
#[test]
fn test_froeschle_map_uncoupled() {
let map = FroeschleMap {
epsilon1: 0.0,
epsilon2: 0.0,
eta: 0.0,
};
let state = vec![0.3, 0.2, 0.5, 0.1];
let next = map.step(&state);
assert_near(next[0], 0.5, 1e-12);
assert_near(next[1], 0.2, 1e-12);
assert_near(next[2], 0.6, 1e-12);
assert_near(next[3], 0.1, 1e-12);
}
#[test]
fn test_extended_standard_map() {
let map = ExtendedStandardMap::default();
let state = vec![0.5, 0.3, 0.2];
let next = map.step(&state);
assert_eq!(next.len(), 3);
for &v in &next {
assert!(v >= 0.0 && v < 1.0);
}
}
#[test]
fn test_generate_trajectory() {
let map = StandardMap { epsilon: 0.12 };
let traj = generate_trajectory(&[0.5, 0.3], &map, 100);
assert_eq!(traj.nrows(), 2);
assert_eq!(traj.ncols(), 101); assert_near(traj[(0, 0)], 0.5, 1e-12);
assert_near(traj[(1, 0)], 0.3, 1e-12);
}
#[test]
fn test_generate_phase_grid() {
let (x, y) = generate_phase_grid((0.0, 1.0), (0.0, 1.0), 10);
assert_eq!(x.len(), 10);
assert_eq!(y.len(), 10);
assert_near(x[0], 0.0, 1e-12);
assert_near(x[9], 1.0, 1e-12);
}
#[test]
fn test_closure_map() {
let map = ClosureMap::new(|state: &[f64]| vec![state[0] * 2.0], 1, "doubling");
assert_eq!(map.dim(), 1);
assert_eq!(map.name(), "doubling");
let next = map.step(&[0.25]);
assert_near(next[0], 0.5, 1e-12);
}
}