1use std::f64::consts::PI;
2
3use faer::Mat;
4
5pub trait MapFn: Send + Sync {
9 fn step(&self, state: &[f64]) -> Vec<f64>;
11
12 fn dim(&self) -> usize;
14
15 fn name(&self) -> &str;
17}
18
19#[derive(Debug, Clone)]
24pub struct StandardMap {
25 pub epsilon: f64,
26}
27
28impl Default for StandardMap {
29 fn default() -> Self {
30 Self { epsilon: 0.12 }
31 }
32}
33
34impl MapFn for StandardMap {
35 fn step(&self, state: &[f64]) -> Vec<f64> {
36 let x = state[0];
37 let y = state[1];
38 let y_new = (y + self.epsilon * (2.0 * PI * x).sin()).rem_euclid(1.0);
39 let x_new = (x + y_new).rem_euclid(1.0);
40 vec![x_new, y_new]
41 }
42 fn dim(&self) -> usize {
43 2
44 }
45 fn name(&self) -> &str {
46 "standard_map"
47 }
48}
49
50#[derive(Debug, Clone)]
57pub struct FroeschleMap {
58 pub epsilon1: f64,
59 pub epsilon2: f64,
60 pub eta: f64,
61}
62
63impl Default for FroeschleMap {
64 fn default() -> Self {
65 Self {
66 epsilon1: 0.02,
67 epsilon2: 0.02,
68 eta: 0.01,
69 }
70 }
71}
72
73impl MapFn for FroeschleMap {
74 fn step(&self, state: &[f64]) -> Vec<f64> {
75 let (x1, y1, x2, y2) = (state[0], state[1], state[2], state[3]);
76 let coupling = self.eta * (2.0 * PI * x1 + 2.0 * PI * x2).sin();
77
78 let y1_new = (y1 + self.epsilon1 * (2.0 * PI * x1).sin() + coupling).rem_euclid(1.0);
79 let x1_new = (x1 + y1_new).rem_euclid(1.0);
80 let y2_new = (y2 + self.epsilon2 * (2.0 * PI * x2).sin() + coupling).rem_euclid(1.0);
81 let x2_new = (x2 + y2_new).rem_euclid(1.0);
82
83 vec![x1_new, y1_new, x2_new, y2_new]
84 }
85 fn dim(&self) -> usize {
86 4
87 }
88 fn name(&self) -> &str {
89 "froeschle_map"
90 }
91}
92
93#[derive(Debug, Clone)]
99pub struct ExtendedStandardMap {
100 pub epsilon: f64,
101 pub delta: f64,
102}
103
104impl Default for ExtendedStandardMap {
105 fn default() -> Self {
106 Self {
107 epsilon: 0.01,
108 delta: 0.001,
109 }
110 }
111}
112
113impl MapFn for ExtendedStandardMap {
114 fn step(&self, state: &[f64]) -> Vec<f64> {
115 let (x, y, z) = (state[0], state[1], state[2]);
116 let x_new = (x + self.epsilon * (2.0 * PI * z).sin() + self.delta * (2.0 * PI * y).sin())
117 .rem_euclid(1.0);
118 let y_new = (y + self.epsilon * (2.0 * PI * z).sin()).rem_euclid(1.0);
119 let z_new = (z + x_new).rem_euclid(1.0);
120 vec![x_new, y_new, z_new]
121 }
122 fn dim(&self) -> usize {
123 3
124 }
125 fn name(&self) -> &str {
126 "extended_standard_map"
127 }
128}
129
130#[derive(Debug, Clone)]
135pub struct HenonMap {
136 pub a: f64,
137 pub b: f64,
138}
139
140impl Default for HenonMap {
141 fn default() -> Self {
142 Self { a: 1.4, b: 0.3 }
143 }
144}
145
146impl MapFn for HenonMap {
147 fn step(&self, state: &[f64]) -> Vec<f64> {
148 let x = state[0];
149 let y = state[1];
150 vec![1.0 - self.a * x * x + y, self.b * x]
151 }
152 fn dim(&self) -> usize {
153 2
154 }
155 fn name(&self) -> &str {
156 "henon_map"
157 }
158}
159
160#[derive(Debug, Clone)]
164pub struct LogisticMap {
165 pub r: f64,
166}
167
168impl Default for LogisticMap {
169 fn default() -> Self {
170 Self { r: 3.9 }
171 }
172}
173
174impl MapFn for LogisticMap {
175 fn step(&self, state: &[f64]) -> Vec<f64> {
176 let x = state[0];
177 vec![self.r * x * (1.0 - x)]
178 }
179 fn dim(&self) -> usize {
180 1
181 }
182 fn name(&self) -> &str {
183 "logistic_map"
184 }
185}
186
187pub struct ClosureMap<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> {
189 func: F,
190 dim: usize,
191 name: String,
192}
193
194impl<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> ClosureMap<F> {
195 pub fn new(func: F, dim: usize, name: impl Into<String>) -> Self {
196 Self {
197 func,
198 dim,
199 name: name.into(),
200 }
201 }
202}
203
204impl<F: Fn(&[f64]) -> Vec<f64> + Send + Sync> MapFn for ClosureMap<F> {
205 fn step(&self, state: &[f64]) -> Vec<f64> {
206 (self.func)(state)
207 }
208 fn dim(&self) -> usize {
209 self.dim
210 }
211 fn name(&self) -> &str {
212 &self.name
213 }
214}
215
216pub fn generate_trajectory(initial_condition: &[f64], map: &dyn MapFn, n_iter: usize) -> Mat<f64> {
220 let n_dim = initial_condition.len();
221 let mut traj = Mat::<f64>::zeros(n_dim, n_iter + 1);
222
223 for i in 0..n_dim {
225 traj[(i, 0)] = initial_condition[i];
226 }
227
228 let mut state = initial_condition.to_vec();
229 for k in 1..=n_iter {
230 state = map.step(&state);
231 for i in 0..n_dim {
232 traj[(i, k)] = state[i];
233 }
234 }
235
236 traj
237}
238
239pub fn generate_phase_grid(
243 x_range: (f64, f64),
244 y_range: (f64, f64),
245 resolution: usize,
246) -> (Vec<f64>, Vec<f64>) {
247 let x_coords: Vec<f64> = (0..resolution)
248 .map(|i| x_range.0 + (x_range.1 - x_range.0) * i as f64 / (resolution - 1) as f64)
249 .collect();
250 let y_coords: Vec<f64> = (0..resolution)
251 .map(|i| y_range.0 + (y_range.1 - y_range.0) * i as f64 / (resolution - 1) as f64)
252 .collect();
253 (x_coords, y_coords)
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 fn assert_near(a: f64, b: f64, eps: f64) {
261 assert!(
262 (a - b).abs() < eps,
263 "expected {a} ≈ {b} (diff = {})",
264 (a - b).abs()
265 );
266 }
267
268 #[test]
269 fn test_standard_map_integrable() {
270 let map = StandardMap { epsilon: 0.0 };
272 let state = vec![0.3, 0.2];
273 let next = map.step(&state);
274 assert_near(next[1], 0.2, 1e-12); assert_near(next[0], 0.5, 1e-12); }
277
278 #[test]
279 fn test_standard_map_modular() {
280 let map = StandardMap { epsilon: 0.12 };
281 let state = vec![0.9, 0.9];
282 let next = map.step(&state);
283 assert!(next[0] >= 0.0 && next[0] < 1.0);
284 assert!(next[1] >= 0.0 && next[1] < 1.0);
285 }
286
287 #[test]
288 fn test_henon_map_fixed_point() {
289 let map = HenonMap { a: 0.0, b: 0.0 };
291 let state = vec![0.5, 0.0];
292 let next = map.step(&state);
293 assert_near(next[0], 1.0, 1e-12); assert_near(next[1], 0.0, 1e-12);
295 }
296
297 #[test]
298 fn test_logistic_map_fixed_point() {
299 let map = LogisticMap { r: 2.0 };
301 let state = vec![0.5];
302 let next = map.step(&state);
303 assert_near(next[0], 0.5, 1e-12); }
305
306 #[test]
307 fn test_logistic_map_chaos() {
308 let map = LogisticMap { r: 4.0 };
309 let state = vec![0.1];
310 let next = map.step(&state);
311 assert_near(next[0], 0.36, 1e-12); }
313
314 #[test]
315 fn test_froeschle_map_uncoupled() {
316 let map = FroeschleMap {
318 epsilon1: 0.0,
319 epsilon2: 0.0,
320 eta: 0.0,
321 };
322 let state = vec![0.3, 0.2, 0.5, 0.1];
323 let next = map.step(&state);
324 assert_near(next[0], 0.5, 1e-12);
326 assert_near(next[1], 0.2, 1e-12);
327 assert_near(next[2], 0.6, 1e-12);
329 assert_near(next[3], 0.1, 1e-12);
330 }
331
332 #[test]
333 fn test_extended_standard_map() {
334 let map = ExtendedStandardMap::default();
335 let state = vec![0.5, 0.3, 0.2];
336 let next = map.step(&state);
337 assert_eq!(next.len(), 3);
338 for &v in &next {
340 assert!(v >= 0.0 && v < 1.0);
341 }
342 }
343
344 #[test]
345 fn test_generate_trajectory() {
346 let map = StandardMap { epsilon: 0.12 };
347 let traj = generate_trajectory(&[0.5, 0.3], &map, 100);
348 assert_eq!(traj.nrows(), 2);
349 assert_eq!(traj.ncols(), 101); assert_near(traj[(0, 0)], 0.5, 1e-12);
351 assert_near(traj[(1, 0)], 0.3, 1e-12);
352 }
353
354 #[test]
355 fn test_generate_phase_grid() {
356 let (x, y) = generate_phase_grid((0.0, 1.0), (0.0, 1.0), 10);
357 assert_eq!(x.len(), 10);
358 assert_eq!(y.len(), 10);
359 assert_near(x[0], 0.0, 1e-12);
360 assert_near(x[9], 1.0, 1e-12);
361 }
362
363 #[test]
364 fn test_closure_map() {
365 let map = ClosureMap::new(|state: &[f64]| vec![state[0] * 2.0], 1, "doubling");
366 assert_eq!(map.dim(), 1);
367 assert_eq!(map.name(), "doubling");
368 let next = map.step(&[0.25]);
369 assert_near(next[0], 0.5, 1e-12);
370 }
371}