1use crate::analysis::advanced;
7use crate::analysis::types::*;
8use crate::error::{IntegrateError, IntegrateResult};
9use scirs2_core::ndarray::{Array1, Array2};
10use scirs2_core::numeric::Complex64;
11
12pub struct StabilityAnalyzer {
14 pub dimension: usize,
16 pub tolerance: f64,
18 pub integration_time: f64,
20 pub basin_grid_size: usize,
22}
23
24impl StabilityAnalyzer {
25 pub fn new(dimension: usize) -> Self {
27 Self {
28 dimension,
29 tolerance: 1e-8,
30 integration_time: 100.0,
31 basin_grid_size: 50,
32 }
33 }
34
35 pub fn analyze_stability<F>(
37 &self,
38 system: F,
39 domain: &[(f64, f64)],
40 ) -> IntegrateResult<StabilityResult>
41 where
42 F: Fn(&Array1<f64>) -> Array1<f64> + Send + Sync + Clone + 'static,
43 {
44 let fixed_points = self.find_fixed_points(&system, domain)?;
46
47 let periodic_orbits = self.find_periodic_orbits(&system, domain)?;
49
50 let lyapunov_exponents = self.compute_lyapunov_exponents(&system)?;
52
53 let basin_analysis = if self.dimension == 2 {
55 Some(self.analyze_basins(&system, domain, &fixed_points)?)
56 } else {
57 None
58 };
59
60 Ok(StabilityResult {
61 fixed_points,
62 periodic_orbits,
63 lyapunov_exponents,
64 basin_analysis,
65 })
66 }
67
68 fn find_fixed_points<F>(
70 &self,
71 system: &F,
72 domain: &[(f64, f64)],
73 ) -> IntegrateResult<Vec<FixedPoint>>
74 where
75 F: Fn(&Array1<f64>) -> Array1<f64>,
76 {
77 let mut fixed_points: Vec<FixedPoint> = Vec::new();
78 let grid_size = 10; let mut initial_guesses = Vec::new();
82 self.generate_grid_points(domain, grid_size, &mut initial_guesses);
83
84 for guess in initial_guesses {
85 if let Ok(fixed_point) = self.newton_raphson_fixed_point(system, &guess) {
86 let mut is_duplicate = false;
88 for existing_fp in &fixed_points {
89 let distance = (&fixed_point - &existing_fp.location)
90 .iter()
91 .map(|&x| x * x)
92 .sum::<f64>()
93 .sqrt();
94
95 if distance < self.tolerance * 10.0 {
96 is_duplicate = true;
97 break;
98 }
99 }
100
101 if !is_duplicate {
102 let jacobian = self.compute_jacobian_at_point(system, &fixed_point)?;
104 let eigenvalues = self.compute_real_eigenvalues(&jacobian)?;
105 let eigenvectors = self.compute_eigenvectors(&jacobian, &eigenvalues)?;
106 let stability = self.classify_stability(&eigenvalues);
107
108 fixed_points.push(FixedPoint {
109 location: fixed_point,
110 stability,
111 eigenvalues,
112 eigenvectors,
113 });
114 }
115 }
116 }
117
118 Ok(fixed_points)
119 }
120
121 fn generate_grid_points(
123 &self,
124 domain: &[(f64, f64)],
125 grid_size: usize,
126 points: &mut Vec<Array1<f64>>,
127 ) {
128 fn generate_recursive(
129 domain: &[(f64, f64)],
130 grid_size: usize,
131 current: &mut Vec<f64>,
132 dim: usize,
133 points: &mut Vec<Array1<f64>>,
134 ) {
135 if dim == domain.len() {
136 points.push(Array1::from_vec(current.clone()));
137 return;
138 }
139
140 if grid_size <= 1 {
142 return; }
144 let step = (domain[dim].1 - domain[dim].0) / (grid_size - 1) as f64;
145 for i in 0..grid_size {
146 let value = domain[dim].0 + i as f64 * step;
147 current.push(value);
148 generate_recursive(domain, grid_size, current, dim + 1, points);
149 current.pop();
150 }
151 }
152
153 let mut current = Vec::new();
154 generate_recursive(domain, grid_size, &mut current, 0, points);
155 }
156
157 fn newton_raphson_fixed_point<F>(
159 &self,
160 system: &F,
161 initial_guess: &Array1<f64>,
162 ) -> IntegrateResult<Array1<f64>>
163 where
164 F: Fn(&Array1<f64>) -> Array1<f64>,
165 {
166 let mut x = initial_guess.clone();
167 let max_iterations = 100;
168
169 for _ in 0..max_iterations {
170 let f_x = system(&x);
171 let sum_squares = f_x.iter().map(|&v| v * v).sum::<f64>();
172 if sum_squares < 0.0 {
173 return Err(IntegrateError::ComputationError(
174 "Negative sum of squares in residual norm calculation".to_string(),
175 ));
176 }
177 let residual_norm = sum_squares.sqrt();
178
179 if residual_norm < self.tolerance {
180 return Ok(x);
181 }
182
183 let jacobian = self.compute_jacobian_at_point(system, &x)?;
184
185 let mut augmented = Array2::zeros((self.dimension, self.dimension + 1));
187 for i in 0..self.dimension {
188 for j in 0..self.dimension {
189 augmented[[i, j]] = jacobian[[i, j]];
190 }
191 augmented[[i, self.dimension]] = -f_x[i];
192 }
193
194 let dx = self.gaussian_elimination(&augmented)?;
195 x += &dx;
196 }
197
198 Err(IntegrateError::ConvergenceError(
199 "Newton-Raphson did not converge".to_string(),
200 ))
201 }
202
203 fn compute_jacobian_at_point<F>(
205 &self,
206 system: &F,
207 x: &Array1<f64>,
208 ) -> IntegrateResult<Array2<f64>>
209 where
210 F: Fn(&Array1<f64>) -> Array1<f64>,
211 {
212 let h = 1e-8_f64;
213 let n = x.len();
214 let mut jacobian = Array2::zeros((n, n));
215
216 let f0 = system(x);
217
218 if h.abs() < 1e-15 {
220 return Err(IntegrateError::ComputationError(
221 "Step size too small for finite difference calculation".to_string(),
222 ));
223 }
224
225 for j in 0..n {
226 let mut x_plus = x.clone();
227 x_plus[j] += h;
228 let f_plus = system(&x_plus);
229
230 for i in 0..n {
231 jacobian[[i, j]] = (f_plus[i] - f0[i]) / h;
232 }
233 }
234
235 Ok(jacobian)
236 }
237
238 fn gaussian_elimination(&self, augmented: &Array2<f64>) -> IntegrateResult<Array1<f64>> {
240 let n = augmented.nrows();
241 let mut a = augmented.clone();
242
243 for k in 0..n {
245 let mut max_row = k;
247 for i in k + 1..n {
248 if a[[i, k]].abs() > a[[max_row, k]].abs() {
249 max_row = i;
250 }
251 }
252
253 if max_row != k {
255 for j in k..n + 1 {
256 let temp = a[[k, j]];
257 a[[k, j]] = a[[max_row, j]];
258 a[[max_row, j]] = temp;
259 }
260 }
261
262 if a[[k, k]].abs() < 1e-14 {
264 return Err(IntegrateError::ComputationError(
265 "Matrix is singular".to_string(),
266 ));
267 }
268
269 for i in k + 1..n {
271 let factor = a[[i, k]] / a[[k, k]];
272 for j in k..n + 1 {
273 a[[i, j]] -= factor * a[[k, j]];
274 }
275 }
276 }
277
278 let mut x = Array1::zeros(n);
280 for i in (0..n).rev() {
281 x[i] = a[[i, n]];
282 for j in i + 1..n {
283 x[i] -= a[[i, j]] * x[j];
284 }
285 if a[[i, i]].abs() < 1e-14 {
287 return Err(IntegrateError::ComputationError(
288 "Zero diagonal element in back substitution".to_string(),
289 ));
290 }
291 x[i] /= a[[i, i]];
292 }
293
294 Ok(x)
295 }
296
297 fn compute_real_eigenvalues(&self, matrix: &Array2<f64>) -> IntegrateResult<Vec<Complex64>> {
299 let n = matrix.nrows();
301
302 if n == 2 {
303 let a = matrix[[0, 0]];
304 let b = matrix[[0, 1]];
305 let c = matrix[[1, 0]];
306 let d = matrix[[1, 1]];
307
308 let trace = a + d;
309 let det = a * d - b * c;
310 let discriminant = trace * trace - 4.0 * det;
311
312 if discriminant >= 0.0 {
313 let sqrt_disc = discriminant.sqrt();
314 let lambda1 = (trace + sqrt_disc) / 2.0;
315 let lambda2 = (trace - sqrt_disc) / 2.0;
316 Ok(vec![
317 Complex64::new(lambda1, 0.0),
318 Complex64::new(lambda2, 0.0),
319 ])
320 } else {
321 let real_part = trace / 2.0;
322 let imag_part = (-discriminant).sqrt() / 2.0;
323 Ok(vec![
324 Complex64::new(real_part, imag_part),
325 Complex64::new(real_part, -imag_part),
326 ])
327 }
328 } else {
329 self.eigenvalues_qr_algorithm(matrix)
331 }
332 }
333
334 fn eigenvalues_qr_algorithm(&self, matrix: &Array2<f64>) -> IntegrateResult<Vec<Complex64>> {
336 let n = matrix.nrows();
337 let mut a = matrix.clone();
338 let max_iterations = 100;
339 let tolerance = 1e-10;
340
341 a = self.reduce_to_hessenberg(&a)?;
343
344 for _ in 0..max_iterations {
346 let (q, r) = self.qr_decomposition_real(&a)?;
347 a = r.dot(&q);
348
349 let mut converged = true;
351 for i in 1..n {
352 if a[[i, i - 1]].abs() > tolerance {
353 converged = false;
354 break;
355 }
356 }
357
358 if converged {
359 break;
360 }
361 }
362
363 let mut eigenvalues = Vec::new();
365 let mut i = 0;
366 while i < n {
367 if i == n - 1 || a[[i + 1, i]].abs() < tolerance {
368 eigenvalues.push(Complex64::new(a[[i, i]], 0.0));
370 i += 1;
371 } else {
372 let trace = a[[i, i]] + a[[i + 1, i + 1]];
374 let det = a[[i, i]] * a[[i + 1, i + 1]] - a[[i, i + 1]] * a[[i + 1, i]];
375 let discriminant = trace * trace - 4.0 * det;
376
377 if discriminant >= 0.0 {
378 let sqrt_disc = discriminant.sqrt();
379 eigenvalues.push(Complex64::new((trace + sqrt_disc) / 2.0, 0.0));
380 eigenvalues.push(Complex64::new((trace - sqrt_disc) / 2.0, 0.0));
381 } else {
382 let real_part = trace / 2.0;
383 let imag_part = (-discriminant).sqrt() / 2.0;
384 eigenvalues.push(Complex64::new(real_part, imag_part));
385 eigenvalues.push(Complex64::new(real_part, -imag_part));
386 }
387 i += 2;
388 }
389 }
390
391 Ok(eigenvalues)
392 }
393
394 fn reduce_to_hessenberg(&self, matrix: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
396 let n = matrix.nrows();
397 let mut h = matrix.clone();
398
399 for k in 0..(n - 2) {
400 let mut x = Array1::<f64>::zeros(n - k - 1);
402 for i in 0..(n - k - 1) {
403 x[i] = h[[k + 1 + i, k]];
404 }
405
406 if x.iter().map(|&v| v * v).sum::<f64>().sqrt() > 1e-15 {
407 let alpha = if x[0] >= 0.0 {
409 -x.iter().map(|&v| v * v).sum::<f64>().sqrt()
410 } else {
411 x.iter().map(|&v| v * v).sum::<f64>().sqrt()
412 };
413
414 let mut v = x.clone();
415 v[0] -= alpha;
416 let v_norm = v.iter().map(|&vi| vi * vi).sum::<f64>().sqrt();
417
418 if v_norm > 1e-15 {
419 v.mapv_inplace(|vi| vi / v_norm);
420
421 for j in k..n {
424 let dot_product: f64 =
425 (0..(n - k - 1)).map(|i| v[i] * h[[k + 1 + i, j]]).sum();
426 for i in 0..(n - k - 1) {
427 h[[k + 1 + i, j]] -= 2.0 * v[i] * dot_product;
428 }
429 }
430
431 for i in 0..n {
433 let dot_product: f64 =
434 (0..(n - k - 1)).map(|j| h[[i, k + 1 + j]] * v[j]).sum();
435 for j in 0..(n - k - 1) {
436 h[[i, k + 1 + j]] -= 2.0 * v[j] * dot_product;
437 }
438 }
439 }
440 }
441 }
442
443 Ok(h)
444 }
445
446 fn qr_decomposition_real(
448 &self,
449 matrix: &Array2<f64>,
450 ) -> IntegrateResult<(Array2<f64>, Array2<f64>)> {
451 let (m, n) = matrix.dim();
452 let mut q = Array2::<f64>::eye(m);
453 let mut r = matrix.clone();
454
455 for k in 0..std::cmp::min(m - 1, n) {
456 let mut x = Array1::<f64>::zeros(m - k);
458 for i in 0..(m - k) {
459 x[i] = r[[k + i, k]];
460 }
461
462 let norm_x = x.iter().map(|&v| v * v).sum::<f64>().sqrt();
464 if norm_x > 1e-15 {
465 let alpha = if x[0] >= 0.0 { -norm_x } else { norm_x };
466
467 let mut v = x.clone();
468 v[0] -= alpha;
469 let v_norm = v.iter().map(|&vi| vi * vi).sum::<f64>().sqrt();
470
471 if v_norm > 1e-15 {
472 v.mapv_inplace(|vi| vi / v_norm);
473
474 for j in k..n {
476 let dot_product: f64 = (0..(m - k)).map(|i| v[i] * r[[k + i, j]]).sum();
477 for i in 0..(m - k) {
478 r[[k + i, j]] -= 2.0 * v[i] * dot_product;
479 }
480 }
481
482 for i in 0..m {
484 let dot_product: f64 = (0..(m - k)).map(|j| q[[i, k + j]] * v[j]).sum();
485 for j in 0..(m - k) {
486 q[[i, k + j]] -= 2.0 * v[j] * dot_product;
487 }
488 }
489 }
490 }
491 }
492
493 Ok((q, r))
494 }
495
496 fn compute_eigenvectors(
498 &self,
499 _matrix: &Array2<f64>,
500 eigenvalues: &[Complex64],
501 ) -> IntegrateResult<Array2<Complex64>> {
502 let n = eigenvalues.len();
503 let eigenvectors = Array2::<Complex64>::zeros((n, n));
504
505 Ok(eigenvectors)
508 }
509
510 fn classify_stability(&self, eigenvalues: &[Complex64]) -> StabilityType {
512 let mut has_positive_real = false;
513 let mut has_negative_real = false;
514 let mut has_zero_real = false;
515
516 for eigenvalue in eigenvalues {
517 if eigenvalue.re > 1e-10 {
518 has_positive_real = true;
519 } else if eigenvalue.re < -1e-10 {
520 has_negative_real = true;
521 } else {
522 has_zero_real = true;
523 }
524 }
525
526 if has_zero_real {
527 StabilityType::Degenerate
528 } else if has_positive_real && has_negative_real {
529 StabilityType::Saddle
530 } else if has_positive_real {
531 StabilityType::Unstable
532 } else if has_negative_real {
533 StabilityType::Stable
534 } else {
535 StabilityType::Center
536 }
537 }
538
539 fn find_periodic_orbits<F>(
541 &self,
542 system: &F,
543 domain: &[(f64, f64)],
544 ) -> IntegrateResult<Vec<PeriodicOrbit>>
545 where
546 F: Fn(&Array1<f64>) -> Array1<f64>,
547 {
548 let mut periodic_orbits = Vec::new();
549
550 if let Ok(shooting_orbits) = self.shooting_method_periodic_orbits(system, domain) {
552 periodic_orbits.extend(shooting_orbits);
553 }
554
555 if let Ok(return_map_orbits) = self.return_map_periodic_orbits(system, domain) {
557 periodic_orbits.extend(return_map_orbits);
558 }
559
560 if let Ok(fourier_orbits) = self.fourier_analysis_periodic_orbits(system, domain) {
562 periodic_orbits.extend(fourier_orbits);
563 }
564
565 let filtered_orbits = self.remove_duplicate_periodic_orbits(periodic_orbits);
567
568 Ok(filtered_orbits)
569 }
570
571 fn shooting_method_periodic_orbits<F>(
573 &self,
574 system: &F,
575 domain: &[(f64, f64)],
576 ) -> IntegrateResult<Vec<PeriodicOrbit>>
577 where
578 F: Fn(&Array1<f64>) -> Array1<f64>,
579 {
580 let mut periodic_orbits = Vec::new();
581
582 if self.dimension != 2 {
583 return Ok(periodic_orbits); }
585
586 let n_guesses = 20;
588 let mut initial_points = Vec::new();
589 self.generate_grid_points(domain, n_guesses, &mut initial_points);
590
591 let periods = vec![
593 std::f64::consts::PI, 2.0 * std::f64::consts::PI, std::f64::consts::PI / 2.0, 4.0 * std::f64::consts::PI, ];
598
599 for initial_point in &initial_points {
600 for &period in &periods {
601 if let Ok(orbit) = self.shooting_method_single_orbit(system, initial_point, period)
602 {
603 periodic_orbits.push(orbit);
604 }
605 }
606 }
607
608 Ok(periodic_orbits)
609 }
610
611 fn shooting_method_single_orbit<F>(
613 &self,
614 system: &F,
615 initial_guess: &Array1<f64>,
616 period: f64,
617 ) -> IntegrateResult<PeriodicOrbit>
618 where
619 F: Fn(&Array1<f64>) -> Array1<f64>,
620 {
621 let max_iterations = 50;
622 let tolerance = 1e-8;
623 let dt = period / 100.0; let mut current_guess = initial_guess.clone();
626
627 for _iter in 0..max_iterations {
629 let final_state =
631 self.integrate_trajectory_period(system, ¤t_guess, period, dt)?;
632
633 let shooting_residual = &final_state - ¤t_guess;
635 let residual_norm = shooting_residual.iter().map(|&x| x * x).sum::<f64>().sqrt();
636
637 if residual_norm < tolerance {
638 let floquet_multipliers =
640 self.compute_floquet_multipliers(system, ¤t_guess, period)?;
641 let stability = self.classify_periodic_orbit_stability(&floquet_multipliers);
642
643 return Ok(PeriodicOrbit {
644 representative_point: current_guess,
645 period,
646 stability,
647 floquet_multipliers,
648 });
649 }
650
651 let flow_jacobian = self.compute_flow_jacobian(system, ¤t_guess, period, dt)?;
653
654 let identity = Array2::<f64>::eye(self.dimension);
656 let shooting_jacobian = &flow_jacobian - &identity;
657
658 let newton_step =
660 self.solve_linear_system_for_shooting(&shooting_jacobian, &(-&shooting_residual))?;
661 current_guess += &newton_step;
662 }
663
664 Err(IntegrateError::ConvergenceError(
665 "Shooting method did not converge to periodic orbit".to_string(),
666 ))
667 }
668
669 fn integrate_trajectory_period<F>(
671 &self,
672 system: &F,
673 initial_state: &Array1<f64>,
674 period: f64,
675 dt: f64,
676 ) -> IntegrateResult<Array1<f64>>
677 where
678 F: Fn(&Array1<f64>) -> Array1<f64>,
679 {
680 let n_steps = (period / dt) as usize;
681 let mut state = initial_state.clone();
682
683 for _ in 0..n_steps {
685 let k1 = system(&state);
686 let k2 = system(&(&state + &(&k1 * (dt / 2.0))));
687 let k3 = system(&(&state + &(&k2 * (dt / 2.0))));
688 let k4 = system(&(&state + &(&k3 * dt)));
689
690 state += &((&k1 + &k2 * 2.0 + &k3 * 2.0 + &k4) * (dt / 6.0));
691 }
692
693 Ok(state)
694 }
695
696 fn compute_flow_jacobian<F>(
698 &self,
699 system: &F,
700 initial_state: &Array1<f64>,
701 period: f64,
702 dt: f64,
703 ) -> IntegrateResult<Array2<f64>>
704 where
705 F: Fn(&Array1<f64>) -> Array1<f64>,
706 {
707 let h = 1e-8;
708 let n = initial_state.len();
709 let mut jacobian = Array2::<f64>::zeros((n, n));
710
711 let base_final = self.integrate_trajectory_period(system, initial_state, period, dt)?;
713
714 for j in 0..n {
716 let mut perturbed_initial = initial_state.clone();
717 perturbed_initial[j] += h;
718
719 let perturbed_final =
720 self.integrate_trajectory_period(system, &perturbed_initial, period, dt)?;
721
722 for i in 0..n {
723 jacobian[[i, j]] = (perturbed_final[i] - base_final[i]) / h;
724 }
725 }
726
727 Ok(jacobian)
728 }
729
730 fn compute_floquet_multipliers<F>(
732 &self,
733 system: &F,
734 representative_point: &Array1<f64>,
735 period: f64,
736 ) -> IntegrateResult<Vec<Complex64>>
737 where
738 F: Fn(&Array1<f64>) -> Array1<f64>,
739 {
740 let dt = period / 100.0;
741 let flow_jacobian = self.compute_flow_jacobian(system, representative_point, period, dt)?;
742
743 let multipliers = self.compute_real_eigenvalues(&flow_jacobian)?;
745
746 Ok(multipliers)
747 }
748
749 fn classify_periodic_orbit_stability(
751 &self,
752 floquet_multipliers: &[Complex64],
753 ) -> StabilityType {
754 let max_magnitude = floquet_multipliers
759 .iter()
760 .map(|m| m.norm())
761 .fold(0.0, f64::max);
762
763 if max_magnitude < 1.0 - 1e-10 {
764 StabilityType::Stable
765 } else if max_magnitude > 1.0 + 1e-10 {
766 StabilityType::Unstable
767 } else {
768 let on_unit_circle = floquet_multipliers
770 .iter()
771 .any(|m| (m.norm() - 1.0).abs() < 1e-10);
772
773 if on_unit_circle {
774 StabilityType::Center
775 } else {
776 StabilityType::Degenerate
777 }
778 }
779 }
780
781 fn return_map_periodic_orbits<F>(
783 &self,
784 system: &F,
785 domain: &[(f64, f64)],
786 ) -> IntegrateResult<Vec<PeriodicOrbit>>
787 where
788 F: Fn(&Array1<f64>) -> Array1<f64>,
789 {
790 let mut periodic_orbits = Vec::new();
791
792 if self.dimension != 2 {
793 return Ok(periodic_orbits); }
795
796 let section_plane = Array1::from_vec(vec![1.0, 0.0]); let section_point = Array1::zeros(2); let n_trajectories = 10;
802 let mut initial_points = Vec::new();
803 self.generate_grid_points(domain, n_trajectories, &mut initial_points);
804
805 for initial_point in &initial_points {
806 if let Ok(return_points) = self.compute_poincare_return_map(
807 system,
808 initial_point,
809 §ion_plane,
810 §ion_point,
811 ) {
812 if let Ok(orbit) = self.analyze_return_map_for_periodicity(&return_points) {
814 periodic_orbits.push(orbit);
815 }
816 }
817 }
818
819 Ok(periodic_orbits)
820 }
821
822 fn compute_poincare_return_map<F>(
824 &self,
825 system: &F,
826 initial_point: &Array1<f64>,
827 section_normal: &Array1<f64>,
828 section_point: &Array1<f64>,
829 ) -> IntegrateResult<Vec<Array1<f64>>>
830 where
831 F: Fn(&Array1<f64>) -> Array1<f64>,
832 {
833 let mut return_points = Vec::new();
834 let dt = 0.01;
835 let max_time = 50.0;
836 let n_steps = (max_time / dt) as usize;
837
838 let mut state = initial_point.clone();
839 let mut prev_distance = self.distance_to_section(&state, section_normal, section_point);
840
841 for _ in 0..n_steps {
842 let derivative = system(&state);
844 state += &(derivative * dt);
845
846 let curr_distance = self.distance_to_section(&state, section_normal, section_point);
848
849 if prev_distance * curr_distance < 0.0 {
850 if let Ok(crossing_point) =
852 self.refine_section_crossing(system, &state, dt, section_normal, section_point)
853 {
854 return_points.push(crossing_point);
855
856 if return_points.len() > 20 {
857 break; }
859 }
860 }
861
862 prev_distance = curr_distance;
863 }
864
865 Ok(return_points)
866 }
867
868 fn distance_to_section(
870 &self,
871 point: &Array1<f64>,
872 section_normal: &Array1<f64>,
873 section_point: &Array1<f64>,
874 ) -> f64 {
875 let relative_pos = point - section_point;
876 relative_pos.dot(section_normal)
877 }
878
879 fn refine_section_crossing<F>(
881 &self,
882 system: &F,
883 current_state: &Array1<f64>,
884 dt: f64,
885 section_normal: &Array1<f64>,
886 section_point: &Array1<f64>,
887 ) -> IntegrateResult<Array1<f64>>
888 where
889 F: Fn(&Array1<f64>) -> Array1<f64>,
890 {
891 let derivative = system(current_state);
893 let prev_state = current_state - &(derivative * dt);
894
895 let mut left = prev_state;
896 let mut right = current_state.clone();
897
898 for _ in 0..10 {
899 let mid = (&left + &right) * 0.5;
900 let mid_distance = self.distance_to_section(&mid, section_normal, section_point);
901
902 if mid_distance.abs() < 1e-10 {
903 return Ok(mid);
904 }
905
906 let left_distance = self.distance_to_section(&left, section_normal, section_point);
907
908 if left_distance * mid_distance < 0.0 {
909 right = mid;
910 } else {
911 left = mid;
912 }
913 }
914
915 Ok((&left + &right) * 0.5)
916 }
917
918 fn analyze_return_map_for_periodicity(
920 &self,
921 return_points: &[Array1<f64>],
922 ) -> IntegrateResult<PeriodicOrbit> {
923 if return_points.len() < 3 {
924 return Err(IntegrateError::ComputationError(
925 "Insufficient return points for periodicity analysis".to_string(),
926 ));
927 }
928
929 let tolerance = 1e-6;
930
931 for period in 1..std::cmp::min(return_points.len() / 2, 10) {
933 let mut is_periodic = true;
934 let mut max_error: f64 = 0.0;
935
936 for i in 0..(return_points.len() - period) {
937 let error = (&return_points[i] - &return_points[i + period])
938 .iter()
939 .map(|&x| x * x)
940 .sum::<f64>()
941 .sqrt();
942
943 max_error = max_error.max(error);
944
945 if error > tolerance {
946 is_periodic = false;
947 break;
948 }
949 }
950
951 if is_periodic {
952 let estimated_period = period as f64 * 2.0 * std::f64::consts::PI;
954
955 return Ok(PeriodicOrbit {
956 representative_point: return_points[0].clone(),
957 period: estimated_period,
958 stability: StabilityType::Stable, floquet_multipliers: vec![], });
961 }
962 }
963
964 Err(IntegrateError::ComputationError(
965 "No periodic behavior detected in return map".to_string(),
966 ))
967 }
968
969 fn fourier_analysis_periodic_orbits<F>(
971 &self,
972 system: &F,
973 domain: &[(f64, f64)],
974 ) -> IntegrateResult<Vec<PeriodicOrbit>>
975 where
976 F: Fn(&Array1<f64>) -> Array1<f64>,
977 {
978 let mut periodic_orbits = Vec::new();
979
980 let n_trajectories = 5;
982 let mut initial_points = Vec::new();
983 self.generate_grid_points(domain, n_trajectories, &mut initial_points);
984
985 for initial_point in &initial_points {
986 if let Ok(orbit) = self.fourier_analysis_single_trajectory(system, initial_point) {
987 periodic_orbits.push(orbit);
988 }
989 }
990
991 Ok(periodic_orbits)
992 }
993
994 fn fourier_analysis_single_trajectory<F>(
996 &self,
997 system: &F,
998 initial_point: &Array1<f64>,
999 ) -> IntegrateResult<PeriodicOrbit>
1000 where
1001 F: Fn(&Array1<f64>) -> Array1<f64>,
1002 {
1003 let dt = 0.01;
1004 let total_time = 20.0;
1005 let n_steps = (total_time / dt) as usize;
1006
1007 let mut trajectory = Vec::new();
1009 let mut state = initial_point.clone();
1010
1011 for _ in 0..n_steps {
1012 trajectory.push(state.clone());
1013 let derivative = system(&state);
1014 state += &(derivative * dt);
1015 }
1016
1017 if let Ok(dominant_period) = self.detect_dominant_period(&trajectory, dt) {
1019 if dominant_period > 0.0 && dominant_period < total_time {
1020 return Ok(PeriodicOrbit {
1021 representative_point: initial_point.clone(),
1022 period: dominant_period,
1023 stability: StabilityType::Stable, floquet_multipliers: vec![], });
1026 }
1027 }
1028
1029 Err(IntegrateError::ComputationError(
1030 "No periodic behavior detected via Fourier analysis".to_string(),
1031 ))
1032 }
1033
1034 fn detect_dominant_period(&self, trajectory: &[Array1<f64>], dt: f64) -> IntegrateResult<f64> {
1036 if trajectory.len() < 100 {
1037 return Err(IntegrateError::ComputationError(
1038 "Trajectory too short for period detection".to_string(),
1039 ));
1040 }
1041
1042 let signal: Vec<f64> = trajectory.iter().map(|state| state[0]).collect();
1044
1045 let max_lag = std::cmp::min(signal.len() / 4, 500);
1047 let mut autocorr = vec![0.0; max_lag];
1048
1049 let mean = signal.iter().sum::<f64>() / signal.len() as f64;
1050 let variance =
1051 signal.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / signal.len() as f64;
1052
1053 if variance < 1e-12 {
1054 return Err(IntegrateError::ComputationError(
1055 "Signal has zero variance".to_string(),
1056 ));
1057 }
1058
1059 for lag in 1..max_lag {
1060 let mut correlation = 0.0;
1061 let count = signal.len() - lag;
1062
1063 for i in 0..count {
1064 correlation += (signal[i] - mean) * (signal[i + lag] - mean);
1065 }
1066
1067 autocorr[lag] = correlation / (count as f64 * variance);
1068 }
1069
1070 let mut max_corr = 0.0;
1072 let mut period_lag = 0;
1073
1074 for lag in 10..max_lag {
1075 if autocorr[lag] > max_corr && autocorr[lag] > 0.5 {
1076 if lag > 0
1078 && lag < max_lag - 1
1079 && autocorr[lag] > autocorr[lag - 1]
1080 && autocorr[lag] > autocorr[lag + 1]
1081 {
1082 max_corr = autocorr[lag];
1083 period_lag = lag;
1084 }
1085 }
1086 }
1087
1088 if period_lag > 0 {
1089 Ok(period_lag as f64 * dt)
1090 } else {
1091 Err(IntegrateError::ComputationError(
1092 "No dominant period detected".to_string(),
1093 ))
1094 }
1095 }
1096
1097 fn remove_duplicate_periodic_orbits(&self, orbits: Vec<PeriodicOrbit>) -> Vec<PeriodicOrbit> {
1099 let mut filtered: Vec<PeriodicOrbit> = Vec::new();
1100 let tolerance = 1e-4;
1101
1102 for orbit in orbits {
1103 let mut is_duplicate = false;
1104
1105 for existing in &filtered {
1106 let distance = (&orbit.representative_point - &existing.representative_point)
1107 .iter()
1108 .map(|&x| x * x)
1109 .sum::<f64>()
1110 .sqrt();
1111
1112 let period_diff = (orbit.period - existing.period).abs();
1113
1114 if distance < tolerance && period_diff < tolerance {
1115 is_duplicate = true;
1116 break;
1117 }
1118 }
1119
1120 if !is_duplicate {
1121 filtered.push(orbit);
1122 }
1123 }
1124
1125 filtered
1126 }
1127
1128 fn solve_linear_system_for_shooting(
1130 &self,
1131 matrix: &Array2<f64>,
1132 rhs: &Array1<f64>,
1133 ) -> IntegrateResult<Array1<f64>> {
1134 let n = matrix.nrows();
1135 if n != matrix.ncols() || n != rhs.len() {
1136 return Err(IntegrateError::ComputationError(
1137 "Inconsistent matrix dimensions in shooting method".to_string(),
1138 ));
1139 }
1140
1141 let mut augmented = Array2::<f64>::zeros((n, n + 1));
1142 for i in 0..n {
1143 for j in 0..n {
1144 augmented[[i, j]] = matrix[[i, j]];
1145 }
1146 augmented[[i, n]] = rhs[i];
1147 }
1148
1149 for k in 0..n {
1151 let mut max_row = k;
1153 for i in (k + 1)..n {
1154 if augmented[[i, k]].abs() > augmented[[max_row, k]].abs() {
1155 max_row = i;
1156 }
1157 }
1158
1159 if max_row != k {
1161 for j in k..=n {
1162 let temp = augmented[[k, j]];
1163 augmented[[k, j]] = augmented[[max_row, j]];
1164 augmented[[max_row, j]] = temp;
1165 }
1166 }
1167
1168 if augmented[[k, k]].abs() < 1e-14 {
1170 return Err(IntegrateError::ComputationError(
1171 "Singular matrix in shooting method".to_string(),
1172 ));
1173 }
1174
1175 for i in (k + 1)..n {
1177 let factor = augmented[[i, k]] / augmented[[k, k]];
1178 for j in k..=n {
1179 augmented[[i, j]] -= factor * augmented[[k, j]];
1180 }
1181 }
1182 }
1183
1184 let mut solution = Array1::<f64>::zeros(n);
1186 for i in (0..n).rev() {
1187 solution[i] = augmented[[i, n]];
1188 for j in (i + 1)..n {
1189 solution[i] -= augmented[[i, j]] * solution[j];
1190 }
1191 solution[i] /= augmented[[i, i]];
1192 }
1193
1194 Ok(solution)
1195 }
1196
1197 fn compute_lyapunov_exponents<F>(&self, system: &F) -> IntegrateResult<Option<Array1<f64>>>
1199 where
1200 F: Fn(&Array1<f64>) -> Array1<f64> + Send + Sync + Clone,
1201 {
1202 if self.dimension == 0 || self.dimension > 10 {
1204 return Ok(None);
1205 }
1206
1207 let initial_state = Array1::zeros(self.dimension);
1210
1211 let dt = match self.dimension {
1213 1 => 0.01,
1214 2 => 0.005,
1215 3 => 0.002,
1216 4..=6 => 0.001,
1217 _ => 0.0005,
1218 };
1219
1220 let n_exponents = if self.dimension <= 4 {
1222 self.dimension
1223 } else {
1224 std::cmp::min(4, self.dimension)
1226 };
1227
1228 let calculator = advanced::LyapunovCalculator::new(n_exponents, dt);
1229
1230 let integration_time = self.integration_time * (self.dimension as f64).sqrt();
1232
1233 let system_clone = system.clone();
1235 let system_wrapper = move |state: &Array1<f64>| system_clone(state);
1236
1237 match calculator.calculate_lyapunov_exponents(
1238 system_wrapper,
1239 &initial_state,
1240 integration_time,
1241 ) {
1242 Ok(exponents) => {
1243 let filtered_exponents = exponents.mapv(|x| if x.abs() < 1e-12 { 0.0 } else { x });
1245 Ok(Some(filtered_exponents))
1246 }
1247 Err(e) => {
1248 eprintln!("Warning: Lyapunov exponent computation failed: {e:?}");
1250 Ok(None)
1251 }
1252 }
1253 }
1254
1255 fn analyze_basins<F>(
1257 &self,
1258 system: &F,
1259 domain: &[(f64, f64)],
1260 fixed_points: &[FixedPoint],
1261 ) -> IntegrateResult<BasinAnalysis>
1262 where
1263 F: Fn(&Array1<f64>) -> Array1<f64> + Send + Sync,
1264 {
1265 if self.dimension != 2 || domain.len() != 2 {
1266 return Err(IntegrateError::ValueError(
1267 "Basin analysis only implemented for 2D systems".to_string(),
1268 ));
1269 }
1270
1271 let grid_size = self.basin_grid_size;
1272 let mut grid_points = Array2::zeros((grid_size * grid_size, 2));
1273 let mut attractor_indices = Array2::<i32>::zeros((grid_size, grid_size));
1274
1275 let dx = (domain[0].1 - domain[0].0) / (grid_size - 1) as f64;
1276 let dy = (domain[1].1 - domain[1].0) / (grid_size - 1) as f64;
1277
1278 for i in 0..grid_size {
1280 for j in 0..grid_size {
1281 let x = domain[0].0 + i as f64 * dx;
1282 let y = domain[1].0 + j as f64 * dy;
1283
1284 grid_points[[i * grid_size + j, 0]] = x;
1285 grid_points[[i * grid_size + j, 1]] = y;
1286
1287 let initial_state = Array1::from_vec(vec![x, y]);
1289 let final_state = self.integrate_trajectory(system, &initial_state)?;
1290
1291 let mut closest_attractor = -1;
1293 let mut min_distance = f64::INFINITY;
1294
1295 for (idx, fp) in fixed_points.iter().enumerate() {
1296 if fp.stability == StabilityType::Stable {
1297 let distance = (&final_state - &fp.location)
1298 .iter()
1299 .map(|&x| x * x)
1300 .sum::<f64>()
1301 .sqrt();
1302
1303 if distance < min_distance && distance < 0.1 {
1304 min_distance = distance;
1305 closest_attractor = idx as i32;
1306 }
1307 }
1308 }
1309
1310 attractor_indices[[i, j]] = closest_attractor;
1311 }
1312 }
1313
1314 let attractors = fixed_points
1316 .iter()
1317 .filter(|fp| fp.stability == StabilityType::Stable)
1318 .map(|fp| fp.location.clone())
1319 .collect();
1320
1321 Ok(BasinAnalysis {
1322 grid_points,
1323 attractor_indices,
1324 attractors,
1325 })
1326 }
1327
1328 fn integrate_trajectory<F>(
1330 &self,
1331 system: &F,
1332 initial_state: &Array1<f64>,
1333 ) -> IntegrateResult<Array1<f64>>
1334 where
1335 F: Fn(&Array1<f64>) -> Array1<f64>,
1336 {
1337 let dt = 0.01;
1339 let n_steps = (self.integration_time / dt) as usize;
1340 let mut state = initial_state.clone();
1341
1342 for _ in 0..n_steps {
1343 let derivative = system(&state);
1344 state += &(derivative * dt);
1345 }
1346
1347 Ok(state)
1348 }
1349}