pynamicalsys 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. pynamicalsys/__init__.py +2 -0
  2. pynamicalsys/__version__.py +2 -2
  3. pynamicalsys/common/recurrence_quantification_analysis.py +1 -1
  4. pynamicalsys/common/time_series_metrics.py +85 -0
  5. pynamicalsys/common/utils.py +3 -3
  6. pynamicalsys/continuous_time/chaotic_indicators.py +306 -8
  7. pynamicalsys/continuous_time/models.py +25 -0
  8. pynamicalsys/continuous_time/numerical_integrators.py +7 -7
  9. pynamicalsys/continuous_time/trajectory_analysis.py +460 -13
  10. pynamicalsys/core/continuous_dynamical_systems.py +933 -35
  11. pynamicalsys/core/discrete_dynamical_systems.py +20 -9
  12. pynamicalsys/core/hamiltonian_systems.py +1193 -0
  13. pynamicalsys/core/time_series_metrics.py +65 -0
  14. pynamicalsys/discrete_time/dynamical_indicators.py +13 -102
  15. pynamicalsys/discrete_time/models.py +2 -2
  16. pynamicalsys/discrete_time/trajectory_analysis.py +10 -10
  17. pynamicalsys/discrete_time/transport.py +1 -1
  18. pynamicalsys/hamiltonian_systems/__init__.py +16 -0
  19. pynamicalsys/hamiltonian_systems/chaotic_indicators.py +638 -0
  20. pynamicalsys/hamiltonian_systems/models.py +68 -0
  21. pynamicalsys/hamiltonian_systems/numerical_integrators.py +248 -0
  22. pynamicalsys/hamiltonian_systems/trajectory_analysis.py +293 -0
  23. pynamicalsys/hamiltonian_systems/validators.py +114 -0
  24. {pynamicalsys-1.3.0.dist-info → pynamicalsys-1.4.0.dist-info}/METADATA +37 -8
  25. pynamicalsys-1.4.0.dist-info/RECORD +36 -0
  26. pynamicalsys-1.3.0.dist-info/RECORD +0 -28
  27. {pynamicalsys-1.3.0.dist-info → pynamicalsys-1.4.0.dist-info}/WHEEL +0 -0
  28. {pynamicalsys-1.3.0.dist-info → pynamicalsys-1.4.0.dist-info}/top_level.txt +0 -0
@@ -16,18 +16,24 @@
16
16
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
18
  from numbers import Integral, Real
19
- from typing import Any, Callable, Dict, List, Optional, Sequence, Union
19
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Union, Tuple
20
+ from IPython.display import Math
20
21
 
21
22
  import numpy as np
22
23
  from numpy.typing import NDArray
23
24
 
24
25
  from pynamicalsys.common.utils import householder_qr, qr
26
+
25
27
  from pynamicalsys.continuous_time.chaotic_indicators import (
26
28
  LDI,
27
29
  SALI,
28
30
  GALI,
29
31
  lyapunov_exponents,
32
+ maximum_lyapunov_exponent,
33
+ recurrence_time_entropy,
34
+ hurst_exponent_wrapped,
30
35
  )
36
+
31
37
  from pynamicalsys.continuous_time.models import (
32
38
  henon_heiles,
33
39
  henon_heiles_jacobian,
@@ -37,17 +43,30 @@ from pynamicalsys.continuous_time.models import (
37
43
  rossler_system_4D,
38
44
  rossler_system_4D_jacobian,
39
45
  rossler_system_jacobian,
46
+ duffing,
47
+ duffing_jacobian,
40
48
  )
49
+
41
50
  from pynamicalsys.continuous_time.numerical_integrators import (
42
51
  estimate_initial_step,
43
52
  rk4_step_wrapped,
44
53
  rk45_step_wrapped,
45
54
  )
55
+
46
56
  from pynamicalsys.continuous_time.trajectory_analysis import (
47
- ensemble_trajectories,
48
57
  evolve_system,
58
+ generate_maxima_map,
49
59
  generate_trajectory,
60
+ ensemble_trajectories,
61
+ generate_poincare_section,
62
+ ensemble_poincare_section,
63
+ generate_stroboscopic_map,
64
+ ensemble_stroboscopic_map,
65
+ generate_maxima_map,
66
+ ensemble_maxima_map,
67
+ basin_of_attraction,
50
68
  )
69
+
51
70
  from pynamicalsys.continuous_time.validators import (
52
71
  validate_initial_conditions,
53
72
  validate_non_negative,
@@ -102,6 +121,15 @@ class ContinuousDynamicalSystem:
102
121
  __AVAILABLE_MODELS: Dict[str, Dict[str, Any]] = {
103
122
  "lorenz system": {
104
123
  "description": "3D Lorenz system",
124
+ "equation": Math(
125
+ r"""
126
+ \dot{x} = \sigma (y - x), \quad
127
+ \dot{y} = x (\rho - z) - y, \quad
128
+ \dot{z} = xy - \beta z
129
+ """
130
+ ),
131
+ "equation_readable": "x' = σ(y − x), y' = x(ρ − z) − y, z' = xy − βz",
132
+ "notes": "Classic Lorenz 1963 model of atmospheric convection. Exhibits chaotic dynamics for some parameter values.",
105
133
  "has_jacobian": True,
106
134
  "has_variational_equations": True,
107
135
  "equations_of_motion": lorenz_system,
@@ -111,7 +139,16 @@ class ContinuousDynamicalSystem:
111
139
  "parameters": ["sigma", "rho", "beta"],
112
140
  },
113
141
  "henon heiles": {
114
- "description": "Two d.o.f. Hénon-Heiles system",
142
+ "description": "Two d.o.f. HénonHeiles system",
143
+ "equation": Math(
144
+ r"""
145
+ H = \frac{1}{2}(p_x^2 + p_y^2) +
146
+ \frac{1}{2}(x^2 + y^2) +
147
+ x^2 y - \frac{1}{3}y^3
148
+ """
149
+ ),
150
+ "equation_readable": "H = ½(pₓ² + pᵧ²) + ½(x² + y²) + x²y − y³/3",
151
+ "notes": "Hamiltonian system modeling stellar motion near a galactic center; classic example of a mixed chaotic/regular system.",
115
152
  "has_jacobian": True,
116
153
  "has_variational_equations": True,
117
154
  "equations_of_motion": henon_heiles,
@@ -122,6 +159,15 @@ class ContinuousDynamicalSystem:
122
159
  },
123
160
  "rossler system": {
124
161
  "description": "3D Rössler system",
162
+ "equation": Math(
163
+ r"""
164
+ \dot{x} = -y - z, \quad
165
+ \dot{y} = x + a y, \quad
166
+ \dot{z} = b + z(x - c)
167
+ """
168
+ ),
169
+ "equation_readable": "x' = −y − z, y' = x + a y, z' = b + z(x − c)",
170
+ "notes": "Continuous-time chaotic system proposed by Otto Rössler (1976).",
125
171
  "has_jacobian": True,
126
172
  "has_variational_equations": True,
127
173
  "equations_of_motion": rossler_system,
@@ -132,6 +178,16 @@ class ContinuousDynamicalSystem:
132
178
  },
133
179
  "4d rossler system": {
134
180
  "description": "4D Rössler system",
181
+ "equation": Math(
182
+ r"""
183
+ \dot{x} = -y - z, \quad
184
+ \dot{y} = x + a y + w, \quad
185
+ \dot{z} = b + z(x - c), \quad
186
+ \dot{w} = -d y
187
+ """
188
+ ),
189
+ "equation_readable": "x' = −y − z, y' = x + a y + w, z' = b + z(x − c), w' = −d y",
190
+ "notes": "A 4D generalization of the Rössler attractor with an added variable w.",
135
191
  "has_jacobian": True,
136
192
  "has_variational_equations": True,
137
193
  "equations_of_motion": rossler_system_4D,
@@ -140,6 +196,21 @@ class ContinuousDynamicalSystem:
140
196
  "number_of_parameters": 4,
141
197
  "parameters": ["a", "b", "c", "d"],
142
198
  },
199
+ "duffing": {
200
+ "description": "Duffing oscillator (nonlinear forced damped oscillator)",
201
+ "equation": Math(
202
+ r"\ddot{x} + \delta \dot{x} - \alpha x + \beta x^3 = \gamma \cos(\omega t)"
203
+ ),
204
+ "equation_readable": "x'' + δ x' − α x + β x³ = γ cos(ω t)",
205
+ "notes": "A nonlinear oscillator with a double-well potential, forced and damped; exhibits chaos under some parameters.",
206
+ "has_jacobian": True,
207
+ "has_variational_equations": True,
208
+ "equations_of_motion": duffing,
209
+ "jacobian": duffing_jacobian,
210
+ "dimension": 2,
211
+ "number_of_parameters": 5,
212
+ "parameters": ["delta", "alpha", "beta", "gamma", "omega"],
213
+ },
143
214
  }
144
215
 
145
216
  __AVAILABLE_INTEGRATORS: Dict[str, Dict[str, Any]] = {
@@ -325,7 +396,7 @@ class ContinuousDynamicalSystem:
325
396
 
326
397
  def evolve_system(
327
398
  self,
328
- u: NDArray[np.float64],
399
+ u: Union[NDArray[np.float64], Sequence[float]],
329
400
  total_time: float,
330
401
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
331
402
  ) -> NDArray[np.float64]:
@@ -334,7 +405,7 @@ class ContinuousDynamicalSystem:
334
405
 
335
406
  Parameters
336
407
  ----------
337
- u : NDArray[np.float64]
408
+ u : Union[NDArray[np.float64], Sequence[float]]
338
409
  Initial conditions of the system. Must match the system's dimension.
339
410
  total_time : float
340
411
  Total time over which to evolve the system.
@@ -392,7 +463,7 @@ class ContinuousDynamicalSystem:
392
463
 
393
464
  def trajectory(
394
465
  self,
395
- u: NDArray[np.float64],
466
+ u: Union[NDArray[np.float64], Sequence[float]],
396
467
  total_time: float,
397
468
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
398
469
  transient_time: Optional[float] = None,
@@ -402,7 +473,7 @@ class ContinuousDynamicalSystem:
402
473
 
403
474
  Parameters
404
475
  ----------
405
- u : NDArray[np.float64]
476
+ u : Union[NDArray[np.float64], Sequence[float]]
406
477
  Initial conditions of the system. Must match the system's dimension.
407
478
  total_time : float
408
479
  Total time over which to evolve the system (including transient).
@@ -432,7 +503,7 @@ class ContinuousDynamicalSystem:
432
503
  --------
433
504
  >>> from pynamicalsys import ContinuousDynamicalSystem as cds
434
505
  >>> ds = cds(model="lorenz system")
435
- >>> u = [0.1, 0.1, 0.1]  # Initial condition
506
+ >>> u = [0.1, 0.1, 0.1] # Initial condition
436
507
  >>> parameters = [10, 28, 8/3]
437
508
  >>> total_time = 700
438
509
  >>> transient_time = 500
@@ -480,9 +551,482 @@ class ContinuousDynamicalSystem:
480
551
  integrator=self.__integrator_func,
481
552
  )
482
553
 
554
+ def poincare_section(
555
+ self,
556
+ u: Union[NDArray[np.float64], Sequence[float]],
557
+ num_intersections: int,
558
+ section_index: int,
559
+ section_value: float,
560
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
561
+ transient_time: Optional[float] = None,
562
+ crossing: int = 1,
563
+ ) -> NDArray[np.float64]:
564
+ """
565
+ Compute the Poincaré section of the dynamical system for given initial conditions.
566
+
567
+ A Poincaré section records the points where a trajectory intersects a chosen hypersurface
568
+ in phase space (e.g. x = constant). This reduces a continuous flow to a lower-dimensional
569
+ map, making it easier to identify periodic orbits, quasi-periodic motion, or chaotic
570
+ structures.
571
+
572
+ Parameters
573
+ ----------
574
+ u : Union[NDArray[np.float64], Sequence[float]]
575
+ Initial conditions of the system. Must match the system's dimension.
576
+ num_intersections : int
577
+ Number of intersections to record in the Poincaré section.
578
+ section_index : int
579
+ Index of the coordinate to define the Poincaré section (0-based).
580
+ section_value : float
581
+ Value of the coordinate at which the section is defined.
582
+ parameters : Union[None, Sequence[float], NDArray[np.float64]], optional
583
+ Parameters of the system, by default None. Can be a scalar, a sequence of floats, or a numpy array.
584
+ transient_time : float, optional
585
+ Initial time to discard before recording the section.
586
+ crossing : int, default=1
587
+ Specifies the type of crossing to consider:
588
+ - 1 : positive crossing (from below to above section_value)
589
+ - -1 : negative crossing (from above to below section_value)
590
+ - 0 : all crossings
591
+
592
+ Returns
593
+ -------
594
+ result : NDArray[np.float64]
595
+ The Poincaré section points.
596
+
597
+ - For a single initial condition (u.ndim = 1), returns a 2D array of shape
598
+ (num_intersections, neq), where each row is a system state at a crossing.
599
+ - For multiple initial conditions (u.ndim = 2), returns a 3D array of shape
600
+ (num_ic, num_intersections, neq).
601
+
602
+ Raises
603
+ ------
604
+ ValueError
605
+ - If the initial condition dimension does not match the system dimension.
606
+ - If the number of parameters does not match the system.
607
+ - If section_index is larger than the system dimension.
608
+ TypeError
609
+ - If `section_value` is not a real number.
610
+ - If `num_intersections` or `transient_time` are not valid numbers.
611
+
612
+ Examples
613
+ --------
614
+ >>> from pynamicalsys import ContinuousDynamicalSystem as cds
615
+ >>> ds = cds(model="lorenz system")
616
+ >>> u = [0.1, 0.1, 0.1] # Initial condition
617
+ >>> parameters = [10, 28, 8/3]
618
+ >>> num_intersections = 500
619
+ >>> section_index = 2
620
+ >>> section_value = 25.0
621
+ >>> ps = ds.poincare_section(u, num_intersections, section_index, section_value, parameters=parameters)
622
+ (500, 3)
623
+ >>> u = [[0.1, 0.1, 0.1],
624
+ ... [0.2, 0.2, 0.2]] # Two initial conditions
625
+ >>> ps_ensemble = ds.poincare_section(u, num_intersections, section_index, section_value, parameters=parameters)
626
+ (2, 500, 3)
627
+ """
628
+
629
+ u = validate_initial_conditions(u, self.__system_dimension)
630
+ u = u.copy()
631
+
632
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
633
+
634
+ validate_non_negative(num_intersections, "num_intersections", Integral)
635
+
636
+ validate_non_negative(section_index, "section_index", Integral)
637
+ if section_index > self.__system_dimension:
638
+ raise ValueError("section_index must be smaller than the sustem_dimension")
639
+
640
+ if not isinstance(section_value, Real):
641
+ raise TypeError("section_value must be a valid real number")
642
+
643
+ if transient_time is not None:
644
+ validate_non_negative(transient_time, "transient_time", Real)
645
+
646
+ if not isinstance(crossing, Integral):
647
+ raise TypeError("crossing must be an integer number")
648
+ elif crossing not in [-1, 0, 1]:
649
+ raise ValueError(
650
+ "crossing must be -1 (downward crossings), 0 (all crossings), or 1 (upward crossing)"
651
+ )
652
+
653
+ time_step = self.__get_initial_time_step(u, parameters)
654
+
655
+ if u.ndim == 1:
656
+ return generate_poincare_section(
657
+ u,
658
+ parameters,
659
+ num_intersections,
660
+ self.__equations_of_motion,
661
+ transient_time,
662
+ time_step,
663
+ self.__atol,
664
+ self.__rtol,
665
+ self.__integrator_func,
666
+ section_index,
667
+ section_value,
668
+ crossing,
669
+ )
670
+ else:
671
+ return ensemble_poincare_section(
672
+ u,
673
+ parameters,
674
+ num_intersections,
675
+ self.__equations_of_motion,
676
+ transient_time,
677
+ time_step,
678
+ self.__atol,
679
+ self.__rtol,
680
+ self.__integrator_func,
681
+ section_index,
682
+ section_value,
683
+ crossing,
684
+ )
685
+
686
+ def stroboscopic_map(
687
+ self,
688
+ u: Union[NDArray[np.float64], Sequence[float]],
689
+ num_samples: int,
690
+ sampling_time: float,
691
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
692
+ transient_time: Optional[float] = None,
693
+ ) -> NDArray[np.float64]:
694
+ """
695
+ Compute the stroboscopic map of the dynamical system for given initial conditions.
696
+
697
+ A stroboscopic map samples the state of a time-periodic or driven system at fixed time
698
+ intervals (typically one driving period). This converts the continuous-time dynamics
699
+ into a discrete-time sequence that highlights periodicity, phase locking, and
700
+ bifurcations.
701
+
702
+ Parameters
703
+ ----------
704
+ u : Union[NDArray[np.float64], Sequence[float]]
705
+ Initial conditions of the system. Must match the system's dimension.
706
+ num_samples : int
707
+ Number of samples to record in the stroboscopic map.
708
+ sampling_time : float
709
+ Time interval between consecutive samples.
710
+ parameters : Union[None, Sequence[float], NDArray[np.float64]], optional
711
+ Parameters of the system, by default None. Can be a scalar, a sequence of floats, or a numpy array.
712
+ transient_time : float, optional
713
+ Initial time to discard before recording the map.
714
+
715
+ Returns
716
+ -------
717
+ result : NDArray[np.float64]
718
+ The stroboscopic map points.
719
+
720
+ - For a single initial condition (u.ndim = 1), returns a 2D array of shape
721
+ (num_samples, neq + 1), where the first column is the time and the remaining
722
+ columns are the system coordinates at each sampled time.
723
+ - For multiple initial conditions (u.ndim = 2), returns a 3D array of shape
724
+ (num_ic, num_samples, neq + 1).
725
+
726
+ Raises
727
+ ------
728
+ ValueError
729
+ - If the initial condition dimension does not match the system dimension.
730
+ - If the number of parameters does not match the system.
731
+ TypeError
732
+ - If `num_samples` or `sampling_time` are not valid numbers.
733
+ - If `transient_time` is provided and is not a valid number.
734
+
735
+ Examples
736
+ --------
737
+ >>> from pynamicalsys import ContinuousDynamicalSystem as cds
738
+ >>> ds = cds(model="lorenz system")
739
+ >>> u = [0.1, 0.1, 0.1] # Initial condition
740
+ >>> parameters = [10, 28, 8/3]
741
+ >>> num_samples = 500
742
+ >>> sampling_time = 0.1
743
+ >>> smap = ds.stroboscopic_map(u, num_samples, sampling_time, parameters=parameters)
744
+ (500, 4)
745
+ >>> u = [[0.1, 0.1, 0.1],
746
+ ... [0.2, 0.2, 0.2]] # Two initial conditions
747
+ >>> smap_ensemble = ds.stroboscopic_map(u, num_samples, sampling_time, parameters=parameters)
748
+ (2, 500, 4)
749
+ """
750
+
751
+ u = validate_initial_conditions(u, self.__system_dimension)
752
+ u = u.copy()
753
+
754
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
755
+
756
+ validate_non_negative(num_samples, "num_samples", Integral)
757
+
758
+ validate_non_negative(sampling_time, "sampling_time", Real)
759
+
760
+ if transient_time is not None:
761
+ validate_non_negative(transient_time, "transient_time", Real)
762
+
763
+ time_step = self.__get_initial_time_step(u, parameters)
764
+
765
+ if u.ndim == 1:
766
+ return generate_stroboscopic_map(
767
+ u,
768
+ parameters,
769
+ num_samples,
770
+ sampling_time,
771
+ self.__equations_of_motion,
772
+ transient_time,
773
+ time_step,
774
+ self.__atol,
775
+ self.__rtol,
776
+ self.__integrator_func,
777
+ )
778
+ else:
779
+ return ensemble_stroboscopic_map(
780
+ u,
781
+ parameters,
782
+ num_samples,
783
+ sampling_time,
784
+ self.__equations_of_motion,
785
+ transient_time,
786
+ time_step,
787
+ self.__atol,
788
+ self.__rtol,
789
+ self.__integrator_func,
790
+ )
791
+
792
+ def maxima_map(
793
+ self,
794
+ u: Union[NDArray[np.float64], Sequence[float]],
795
+ num_points: int,
796
+ maxima_index: int,
797
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
798
+ transient_time: Optional[float] = None,
799
+ ) -> NDArray[np.float64]:
800
+ """
801
+ Compute the maxima map of the dynamical system for given initial conditions.
802
+
803
+ A maxima map records the local maxima of a chosen system variable along the trajectory.
804
+ By plotting successive maxima, one obtains a discrete return map that reveals
805
+ oscillation amplitudes, period-doubling cascades, and other nonlinear behaviours.
806
+
807
+ Parameters
808
+ ----------
809
+ u : Union[NDArray[np.float64], Sequence[float]]
810
+ Initial conditions of the system. Must match the system's dimension.
811
+ num_points : int
812
+ Number of points to record in the maxima map.
813
+ maxima_index : int
814
+ Index of the variable whose maxima are to be recorded.
815
+ parameters : Union[None, Sequence[float], NDArray[np.float64]], optional
816
+ Parameters of the system, by default None. Can be a scalar, a sequence of floats, or a numpy array.
817
+ transient_time : float, optional
818
+ Initial time to discard before recording the map.
819
+
820
+ Returns
821
+ -------
822
+ result : NDArray[np.float64]
823
+ The maxima map points.
824
+
825
+ - For a single initial condition (u.ndim = 1), returns a 2D array of shape
826
+ (num_points, neq + 1), where the first column is the time and the remaining
827
+ columns are the system coordinates at each maxima point.
828
+ - For multiple initial conditions (u.ndim = 2), returns a 3D array of shape
829
+ (num_ic, num_points, neq + 1).
830
+
831
+ Raises
832
+ ------
833
+ ValueError
834
+ - If the initial condition dimension does not match the system dimension.
835
+ - If the number of parameters does not match the system.
836
+ TypeError
837
+ - If `num_points` or `maxima_index` are not valid numbers.
838
+ - If `transient_time` is provided and is not a valid number.
839
+
840
+ Examples
841
+ --------
842
+ >>> from pynamicalsys import ContinuousDynamicalSystem as cds
843
+ >>> ds = cds(model="lorenz system")
844
+ >>> u = [0.1, 0.1, 0.1] # Initial condition
845
+ >>> parameters = [10, 28, 8/3]
846
+ >>> num_points = 500
847
+ >>> maxima_index = 0
848
+ >>> smap = ds.maxima_map(u, num_points, maxima_index, parameters=parameters)
849
+ >>> smap.shape
850
+ (500, 4)
851
+ >>> u = [[0.1, 0.1, 0.1],
852
+ ... [0.2, 0.2, 0.2]] # Two initial conditions
853
+ >>> smap_ensemble = ds.stroboscopic_map(u, num_samples, sampling_time, parameters=parameters)
854
+ >>> smap_ensemble.shape
855
+ (2, 500, 4)
856
+ """
857
+
858
+ u = validate_initial_conditions(u, self.__system_dimension)
859
+ u = u.copy()
860
+
861
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
862
+
863
+ validate_non_negative(num_points, "num_samples", Integral)
864
+
865
+ validate_non_negative(maxima_index, "maxima_index", Integral)
866
+
867
+ if transient_time is not None:
868
+ validate_non_negative(transient_time, "transient_time", Real)
869
+
870
+ time_step = self.__get_initial_time_step(u, parameters)
871
+
872
+ if u.ndim == 1:
873
+ return generate_maxima_map(
874
+ u,
875
+ parameters,
876
+ num_points,
877
+ maxima_index,
878
+ self.__equations_of_motion,
879
+ transient_time,
880
+ time_step,
881
+ self.__atol,
882
+ self.__rtol,
883
+ self.__integrator_func,
884
+ )
885
+ else:
886
+ return ensemble_maxima_map(
887
+ u,
888
+ parameters,
889
+ num_points,
890
+ maxima_index,
891
+ self.__equations_of_motion,
892
+ transient_time,
893
+ time_step,
894
+ self.__atol,
895
+ self.__rtol,
896
+ self.__integrator_func,
897
+ )
898
+
899
+ def basin_of_attraction(
900
+ self,
901
+ u: Union[NDArray[np.float64], Sequence[float]],
902
+ num_intersections: int,
903
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
904
+ transient_time: Optional[float] = None,
905
+ map_type: str = "SM",
906
+ section_index: Optional[int] = None,
907
+ section_value: Optional[float] = None,
908
+ crossing: Optional[int] = None,
909
+ sampling_time: Optional[float] = None,
910
+ eps: float = 0.05,
911
+ min_samples: int = 1,
912
+ ) -> NDArray[np.int32]:
913
+ """
914
+ Compute the basin of attraction for a dynamical system for a set of initial conditions.
915
+
916
+ Parameters
917
+ ----------
918
+ u : Union[NDArray[np.float64], Sequence[float]]
919
+ Initial conditions for the dynamical system.
920
+ num_intersections : int
921
+ Number of intersections (or samples) to use in constructing the map (stroboscopic or Poincaré).
922
+ parameters : Union[None, Sequence[float], NDArray[np.float64]], optional
923
+ System parameters. If None, defaults will be used. Default is None.
924
+ transient_time : float, optional
925
+ Transient time to discard before analyzing the trajectories. Default is None.
926
+ map_type : str, default "SM"
927
+ Type of map to compute:
928
+ - "SM" : stroboscopic map
929
+ - "PS" : Poincaré section
930
+ section_index : int, optional
931
+ Index of the coordinate used for the Poincaré section (required if map_type="PS").
932
+ section_value : float, optional
933
+ Value of the section plane (required if map_type="PS").
934
+ crossing : int, optional
935
+ Crossing direction for Poincaré section:
936
+ - -1 : downward crossings
937
+ - 0 : all crossings
938
+ - 1 : upward crossings
939
+ Required if map_type="PS".
940
+ sampling_time : float, optional
941
+ Sampling time for stroboscopic map (required if map_type="SM").
942
+ eps : float, default 0.05
943
+ The maximum distance between points to be considered in the same cluster (DBSCAN parameter).
944
+ min_samples : int, default 1
945
+ The minimum number of points to form a cluster (DBSCAN parameter).
946
+
947
+ Returns
948
+ -------
949
+ NDArray[np.int32]
950
+ Array of integer labels indicating which attractor each initial condition belongs to.
951
+ Label `-1` indicates points classified as noise (not part of any attractor).
952
+
953
+ Notes
954
+ -----
955
+ The basin of attraction is determined by first constructing either a stroboscopic map
956
+ or a Poincaré section from the trajectories. Then, the attractors are identified by
957
+ clustering the trajectory centroids using the DBSCAN algorithm from scikit-learn.
958
+
959
+ DBSCAN groups points that are close to each other in phase space, with `eps` defining
960
+ the neighborhood radius and `min_samples` specifying the minimum number of points to
961
+ form a cluster. Each cluster corresponds to a distinct attractor, and initial conditions
962
+ whose trajectories end up in the same cluster are considered to belong to the same basin
963
+ of attraction.
964
+ """
965
+ u = validate_initial_conditions(u, self.__system_dimension)
966
+ u = u.copy()
967
+
968
+ validate_non_negative(num_intersections, "num_intersections", Integral)
969
+
970
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
971
+
972
+ if transient_time is not None:
973
+ validate_non_negative(transient_time, "transient_time", Real)
974
+
975
+ if not isinstance(map_type, str):
976
+ raise TypeError("map_type must a valid string")
977
+ if map_type not in ["SM", "PS"]:
978
+ raise ValueError(
979
+ "map_type must be either SM (stroboscopic map) or PS (Poicaré section)"
980
+ )
981
+
982
+ if section_index is not None:
983
+ validate_non_negative(section_index, "section_index", Integral)
984
+ if section_index > self.__system_dimension:
985
+ raise ValueError("section_index must be <= system_dimension")
986
+
987
+ if section_value is not None:
988
+ if not isinstance(section_value, Real):
989
+ raise TypeError("section_value must be a valid real number")
990
+
991
+ if crossing is not None:
992
+ if not isinstance(crossing, Integral):
993
+ raise TypeError("crossing must be an integer number")
994
+ elif crossing not in [-1, 0, 1]:
995
+ raise ValueError(
996
+ "crossing must be -1 (downward crossings), 0 (all crossings), or 1 (upward crossing)"
997
+ )
998
+
999
+ if sampling_time is not None:
1000
+ validate_non_negative(sampling_time, "sampling_time", Real)
1001
+
1002
+ validate_non_negative(eps, "eps", Real)
1003
+
1004
+ validate_non_negative(min_samples, "min_samples", Integral)
1005
+
1006
+ time_step = self.__get_initial_time_step(u, parameters)
1007
+
1008
+ return basin_of_attraction(
1009
+ u,
1010
+ parameters,
1011
+ num_intersections,
1012
+ self.__equations_of_motion,
1013
+ transient_time,
1014
+ time_step,
1015
+ self.__atol,
1016
+ self.__rtol,
1017
+ self.__integrator_func,
1018
+ map_type,
1019
+ section_index,
1020
+ section_value,
1021
+ crossing,
1022
+ sampling_time,
1023
+ eps,
1024
+ min_samples,
1025
+ )
1026
+
483
1027
  def lyapunov(
484
1028
  self,
485
- u: NDArray[np.float64],
1029
+ u: Union[NDArray[np.float64], Sequence[float]],
486
1030
  total_time: float,
487
1031
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
488
1032
  transient_time: Optional[float] = None,
@@ -499,7 +1043,7 @@ class ContinuousDynamicalSystem:
499
1043
 
500
1044
  Parameters
501
1045
  ----------
502
- u : NDArray[np.float64]
1046
+ u : Union[NDArray[np.float64], Sequence[float]]
503
1047
  Initial conditions of the system. Must match the system's dimension.
504
1048
  total_time : float
505
1049
  Total time over which to evolve the system (including transient).
@@ -603,31 +1147,46 @@ class ContinuousDynamicalSystem:
603
1147
  if log_base == 1:
604
1148
  raise ValueError("The logarithm function is not defined with base 1")
605
1149
 
606
- result = lyapunov_exponents(
607
- u,
608
- parameters,
609
- total_time,
610
- self.__equations_of_motion,
611
- self.__jacobian,
612
- num_exponents,
613
- transient_time=transient_time,
614
- time_step=time_step,
615
- atol=self.__atol,
616
- rtol=self.__rtol,
617
- integrator=self.__integrator_func,
618
- return_history=return_history,
619
- seed=seed,
620
- log_base=log_base,
621
- QR=qr_func,
622
- )
1150
+ if num_exponents == 1:
1151
+ result = maximum_lyapunov_exponent(
1152
+ u,
1153
+ parameters,
1154
+ total_time,
1155
+ self.__equations_of_motion,
1156
+ self.__jacobian,
1157
+ transient_time,
1158
+ time_step,
1159
+ self.__atol,
1160
+ self.__rtol,
1161
+ self.__integrator_func,
1162
+ return_history,
1163
+ seed,
1164
+ )
1165
+ else:
1166
+ result = lyapunov_exponents(
1167
+ u,
1168
+ parameters,
1169
+ total_time,
1170
+ self.__equations_of_motion,
1171
+ self.__jacobian,
1172
+ num_exponents,
1173
+ transient_time=transient_time,
1174
+ time_step=time_step,
1175
+ atol=self.__atol,
1176
+ rtol=self.__rtol,
1177
+ integrator=self.__integrator_func,
1178
+ return_history=return_history,
1179
+ seed=seed,
1180
+ QR=qr_func,
1181
+ )
623
1182
  if return_history:
624
- return np.array(result)
1183
+ return np.array(result) / np.log(log_base)
625
1184
  else:
626
- return np.array(result[0])
1185
+ return np.array(result[0]) / np.log(log_base)
627
1186
 
628
1187
  def SALI(
629
1188
  self,
630
- u: NDArray[np.float64],
1189
+ u: Union[NDArray[np.float64], Sequence[float]],
631
1190
  total_time: float,
632
1191
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
633
1192
  transient_time: Optional[float] = None,
@@ -640,7 +1199,7 @@ class ContinuousDynamicalSystem:
640
1199
 
641
1200
  Parameters
642
1201
  ----------
643
- u : NDArray[np.float64]
1202
+ u : Union[NDArray[np.float64], Sequence[float]]
644
1203
  Initial conditions of the system. Must match the system's dimension.
645
1204
  total_time : float
646
1205
  Total time over which to evolve the system (including transient).
@@ -738,7 +1297,7 @@ class ContinuousDynamicalSystem:
738
1297
 
739
1298
  def LDI(
740
1299
  self,
741
- u: NDArray[np.float64],
1300
+ u: Union[NDArray[np.float64], Sequence[float]],
742
1301
  total_time: float,
743
1302
  k: int,
744
1303
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
@@ -752,7 +1311,7 @@ class ContinuousDynamicalSystem:
752
1311
 
753
1312
  Parameters
754
1313
  ----------
755
- u : NDArray[np.float64]
1314
+ u : Union[NDArray[np.float64], Sequence[float]]
756
1315
  Initial conditions of the system. Must match the system's dimension.
757
1316
  total_time : float
758
1317
  Total time over which to evolve the system (including transient).
@@ -853,7 +1412,7 @@ class ContinuousDynamicalSystem:
853
1412
 
854
1413
  def GALI(
855
1414
  self,
856
- u: NDArray[np.float64],
1415
+ u: Union[NDArray[np.float64], Sequence[float]],
857
1416
  total_time: float,
858
1417
  k: int,
859
1418
  parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
@@ -867,7 +1426,7 @@ class ContinuousDynamicalSystem:
867
1426
 
868
1427
  Parameters
869
1428
  ----------
870
- u : NDArray[np.float64]
1429
+ u : Union[NDArray[np.float64], Sequence[float]]
871
1430
  Initial conditions of the system. Must match the system's dimension.
872
1431
  total_time : float
873
1432
  Total time over which to evolve the system (including transient).
@@ -965,3 +1524,342 @@ class ContinuousDynamicalSystem:
965
1524
  return np.array(result)
966
1525
  else:
967
1526
  return np.array(result[0])
1527
+
1528
+ def recurrence_time_entropy(
1529
+ self,
1530
+ u: Union[NDArray[np.float64], Sequence[float]],
1531
+ num_intersections: int,
1532
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
1533
+ transient_time: Optional[float] = None,
1534
+ map_type: str = "SM",
1535
+ section_index: Optional[int] = None,
1536
+ section_value: Optional[float] = None,
1537
+ crossing: Optional[int] = None,
1538
+ sampling_time: Optional[float] = None,
1539
+ maxima_index: Optional[float] = None,
1540
+ **kwargs,
1541
+ ) -> Union[float, Tuple[float, NDArray[np.float64]]]:
1542
+ """Compute the Recurrence Time Entropy (RTE) for a dynamical system.
1543
+
1544
+ Parameters
1545
+ ----------
1546
+ u: Union[NDArray[np.float64], Sequence[float]]
1547
+ Initial condition of shape(d,) where d is system dimension
1548
+ num_intersections: int
1549
+ Number of intersections to record in the Poincaré section or stroboscopic map.
1550
+ parameters: Union[None, float, Sequence[np.float64], NDArray[np.float64]], optional
1551
+ System parameters of shape(p,) passed to mapping function
1552
+ transient_time : float, optional
1553
+ Initial time to discard before recording the section.
1554
+ map_type : str
1555
+ Which map to use: stroboscopic map or Poincaré section, by default "SM"
1556
+ section_index : Optional[int]
1557
+ Index of the coordinate to define the Poincaré section (0-based). Only used when map_type="PS".
1558
+ section_value : Optional[float]
1559
+ Value of the coordinate at which the section is defined. Only used when map_type="PS".
1560
+ crossing : Optional[int]
1561
+ Specifies the type of crossing to consider:
1562
+ - 1 : positive crossing (from below to above section_value)
1563
+ - -1 : negative crossing (from above to below section_value)
1564
+ - 0 : all crossings
1565
+
1566
+ Only used when map_type="PS".
1567
+ sampling_time : float
1568
+ Time interval between consecutive samples in the stroboscopic map. Only used when map_type="SM".
1569
+ maxima_index : Optional[int]
1570
+ Index of the coordinate whose maxima will be recorded. Only used when map_type="MM".
1571
+ metric: {"supremum", "euclidean", "manhattan"}, default = "supremum"
1572
+ Distance metric used for phase space reconstruction.
1573
+ std_metric: {"supremum", "euclidean", "manhattan"}, default = "supremum"
1574
+ Distance metric used for standard deviation calculation.
1575
+ lmin: int, default = 1
1576
+ Minimum line length to consider in recurrence quantification.
1577
+ threshold: float, default = 0.1
1578
+ Recurrence threshold(relative to data range).
1579
+ threshold_std: bool, default = True
1580
+ Whether to scale threshold by data standard deviation.
1581
+ return_final_state: bool, default = False
1582
+ Whether to return the final system state in results.
1583
+ return_recmat: bool, default = False
1584
+ Whether to return the recurrence matrix.
1585
+ return_p: bool, default = False
1586
+ Whether to return white vertical line length distribution.
1587
+
1588
+ Returns
1589
+ -------
1590
+ Union[float, Tuple[float, NDArray[np.float64]]]
1591
+ - float: RTE value(base case)
1592
+ - Tuple: (RTE, white_line_distribution) if return_distribution = True
1593
+
1594
+ Raises
1595
+ ------
1596
+ ValueError
1597
+ - If `u` is not a 1D array matching the system dimension.
1598
+ - If `parameters` is not `None` and does not match the expected number of parameters.
1599
+ - If `parameters` is `None` but the system expects parameters.
1600
+ - If `parameters` is a scalar or array-like but not 1D.
1601
+ - If `transient_time` is negative.
1602
+ - If `map_type` is not one of {"SM", "PS"}.
1603
+ - If `map_type="PS"` but any of `section_index`, `section_value`, or `crossing` is `None`.
1604
+ - If `section_index` is negative or ≥ system dimension.
1605
+ - If `crossing` is not one of {-1, 0, 1}.
1606
+ - If `map_type="SM"` but `sampling_time` is `None` or negative.
1607
+ TypeError
1608
+ - If `u` is not a scalar or array-like type.
1609
+ - If `parameters` is not a scalar or array-like type.
1610
+ - If `map_type` is not a string.
1611
+ - If `section_value` is not a real number when `map_type="PS"`.
1612
+ - If `crossing` is not an integer when `map_type="PS"`.
1613
+ - If `sampling_time` is not a real number when `map_type="SM"`.
1614
+
1615
+ Notes
1616
+ -----
1617
+ - Higher RTE indicates more complex dynamics
1618
+ - Set min_recurrence_time = 2 to ignore single-point recurrences
1619
+ - Implementation follows [1]
1620
+
1621
+ References
1622
+ ----------
1623
+ [1] Sales et al., Chaos 33, 033140 (2023)
1624
+
1625
+ Examples
1626
+ --------
1627
+ >>> # Basic usage
1628
+ >>> rte = system.recurrence_time_entropy(u0, params, 5000)
1629
+
1630
+ >>> # With distribution output
1631
+ >>> rte, dist = system.recurrence_time_entropy(
1632
+ ... u0, params, 5000,
1633
+ ... return_distribution=True,
1634
+ ... recurrence_threshold=0.1
1635
+ ...)
1636
+ """
1637
+ u = validate_initial_conditions(
1638
+ u, self.__system_dimension, allow_ensemble=False
1639
+ )
1640
+ u = u.copy()
1641
+
1642
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
1643
+
1644
+ validate_non_negative(transient_time, "transient_time", Real)
1645
+
1646
+ if not isinstance(map_type, str):
1647
+ raise TypeError("map_type must be a string")
1648
+
1649
+ if map_type == "PS":
1650
+ if section_index is None or section_value is None or crossing is None:
1651
+ raise ValueError(
1652
+ 'When using map_type="PS", you must inform section_index, section_value, and crossing'
1653
+ )
1654
+
1655
+ validate_non_negative(section_index, "section_index", Integral)
1656
+ if section_index >= self.__system_dimension:
1657
+ raise ValueError("section_index must be in [0, system_dimension)")
1658
+
1659
+ if not isinstance(section_value, Real):
1660
+ raise TypeError("section_value must be a valid real number")
1661
+
1662
+ if not isinstance(crossing, Integral):
1663
+ raise TypeError("crossing must be a valid integer number")
1664
+ elif crossing not in [-1, 0, 1]:
1665
+ raise ValueError("crossing must be -1, 0, or 1")
1666
+
1667
+ elif map_type == "SM":
1668
+
1669
+ if sampling_time is not None:
1670
+ validate_non_negative(sampling_time, "sampling_time", Real)
1671
+ else:
1672
+ raise ValueError(
1673
+ 'When using map_type="SM" you must inform sampling_time'
1674
+ )
1675
+ elif map_type == "MM":
1676
+ if maxima_index is not None:
1677
+ validate_non_negative(maxima_index, "maxima_index", Integral)
1678
+ else:
1679
+ raise ValueError(
1680
+ 'When using map_type="MM" you must inform maxima_index'
1681
+ )
1682
+ else:
1683
+ raise ValueError(
1684
+ "map_type must be SM (stroboscopic map), PS (Poincaré section), or MM (Maxima map)"
1685
+ )
1686
+
1687
+ time_step = self.__get_initial_time_step(u, parameters)
1688
+
1689
+ return recurrence_time_entropy(
1690
+ u,
1691
+ parameters,
1692
+ num_intersections,
1693
+ transient_time,
1694
+ self.__equations_of_motion,
1695
+ time_step,
1696
+ self.__atol,
1697
+ self.__rtol,
1698
+ self.__integrator_func,
1699
+ map_type,
1700
+ section_index,
1701
+ section_value,
1702
+ crossing,
1703
+ sampling_time,
1704
+ maxima_index,
1705
+ **kwargs,
1706
+ )
1707
+
1708
+ def hurst_exponent(
1709
+ self,
1710
+ u: Union[NDArray[np.float64], Sequence[float]],
1711
+ num_intersections: int,
1712
+ parameters: Union[None, Sequence[float], NDArray[np.float64]] = None,
1713
+ transient_time: Optional[float] = None,
1714
+ wmin: int = 2,
1715
+ map_type: str = "SM",
1716
+ section_index: Optional[int] = None,
1717
+ section_value: Optional[float] = None,
1718
+ crossing: Optional[int] = None,
1719
+ sampling_time: Optional[float] = None,
1720
+ maxima_index: Optional[float] = None,
1721
+ ) -> Union[float, Tuple[float, NDArray[np.float64]]]:
1722
+ """
1723
+ Estimate the Hurst exponent for a system trajectory using the rescaled range (R/S) method.
1724
+
1725
+ Parameters
1726
+ ----------
1727
+ u : NDArray[np.float64]
1728
+ Initial condition vector of shape (n,).
1729
+ parameters : Union[None, float, Sequence[np.float64], NDArray[np.float64]], optional
1730
+ Parameters passed to the mapping function.
1731
+ total_time : int
1732
+ Total number of iterations used to generate the trajectory.
1733
+ transient_time : Optional[int], optional
1734
+ Number of initial iterations to discard as transient. If `None`, no transient is removed. Default is `None`.
1735
+ wmin : int, optional
1736
+ Minimum window size for the rescaled range calculation. Default is 2.
1737
+ map_type : str
1738
+ Which map to use: stroboscopic map or Poincaré section, by default "SM"
1739
+ section_index : Optional[int]
1740
+ Index of the coordinate to define the Poincaré section (0-based). Only used when map_type="PS".
1741
+ section_value : Optional[float]
1742
+ Value of the coordinate at which the section is defined. Only used when map_type="PS".
1743
+ crossing : Optional[int]
1744
+ Specifies the type of crossing to consider:
1745
+ - 1 : positive crossing (from below to above section_value)
1746
+ - -1 : negative crossing (from above to below section_value)
1747
+ - 0 : all crossings
1748
+
1749
+ Only used when map_type="PS".
1750
+ sampling_time : float
1751
+ Time interval between consecutive samples in the stroboscopic map. Only used when map_type="SM".
1752
+ maxima_index : Optional[int]
1753
+ Index of the coordinate whose maxima will be recorded. Only used when map_type="MM".
1754
+
1755
+ Returns
1756
+ -------
1757
+ NDArray[np.float64]
1758
+ Estimated Hurst exponents for each dimension of the input vector `u`, of shape (n,).
1759
+
1760
+ Raises
1761
+ ------
1762
+ TypeError
1763
+ - If `map_type` is not a string.
1764
+ - If `section_value` is not a real number when `map_type="PS"`.
1765
+ - If `crossing` is not an integer when `map_type="PS"`.
1766
+ - If `sampling_time` is not a real number when `map_type="SM"`.
1767
+ - If `maxima_index` is not an integer when `map_type="MM"`.
1768
+ ValueError
1769
+ - If `map_type` is not one of {"SM", "PS", "MM"}.
1770
+ - If `map_type="PS"` and any of `section_index`, `section_value`, or `crossing` is `None`.
1771
+ - If `section_index` is negative or ≥ system dimension when `map_type="PS"`.
1772
+ - If `crossing` is not in {-1, 0, 1} when `map_type="PS"`.
1773
+ - If `map_type="SM"` and `sampling_time` is `None` or negative.
1774
+ - If `map_type="MM"` and `maxima_index` is `None` or negative.
1775
+ - If `transient_time` is negative.
1776
+ - If `wmin` is less than 2 or greater than or equal to `num_intersections // 2`.
1777
+
1778
+ Notes
1779
+ -----
1780
+ The Hurst exponent is a measure of the long-term memory of a time series:
1781
+
1782
+ - H = 0.5 indicates a random walk (no memory).
1783
+ - H > 0.5 indicates persistent behavior (positive autocorrelation).
1784
+ - H < 0.5 indicates anti-persistent behavior (negative autocorrelation).
1785
+
1786
+ This implementation computes the rescaled range (R/S) for various window sizes and
1787
+ performs a linear regression in log-log space to estimate the exponent.
1788
+
1789
+ The function supports multivariate time series, estimating one Hurst exponent per dimension.
1790
+ """
1791
+ u = validate_initial_conditions(
1792
+ u, self.__system_dimension, allow_ensemble=False
1793
+ )
1794
+ u = u.copy()
1795
+
1796
+ parameters = validate_parameters(parameters, self.__number_of_parameters)
1797
+
1798
+ validate_non_negative(transient_time, "transient_time", Real)
1799
+
1800
+ if not isinstance(map_type, str):
1801
+ raise TypeError("map_type must be a string")
1802
+
1803
+ if map_type == "PS":
1804
+ if section_index is None or section_value is None or crossing is None:
1805
+ raise ValueError(
1806
+ 'When using map_type="PS", you must inform section_index, section_value, and crossing'
1807
+ )
1808
+
1809
+ validate_non_negative(section_index, "section_index", Integral)
1810
+ if section_index >= self.__system_dimension:
1811
+ raise ValueError("section_index must be in [0, system_dimension)")
1812
+
1813
+ if not isinstance(section_value, Real):
1814
+ raise TypeError("section_value must be a valid real number")
1815
+
1816
+ if not isinstance(crossing, Integral):
1817
+ raise TypeError("crossing must be a valid integer number")
1818
+ elif crossing not in [-1, 0, 1]:
1819
+ raise ValueError("crossing must be -1, 0, or 1")
1820
+
1821
+ elif map_type == "SM":
1822
+
1823
+ if sampling_time is not None:
1824
+ validate_non_negative(sampling_time, "sampling_time", Real)
1825
+ else:
1826
+ raise ValueError(
1827
+ 'When using map_type="SM" you must inform sampling_time'
1828
+ )
1829
+ elif map_type == "MM":
1830
+ if maxima_index is not None:
1831
+ validate_non_negative(maxima_index, "maxima_index", Integral)
1832
+ else:
1833
+ raise ValueError(
1834
+ 'When using map_type="MM" you must inform maxima_index'
1835
+ )
1836
+ else:
1837
+ raise ValueError(
1838
+ "map_type must be SM (stroboscopic map), PS (Poincaré section), or MM (Maxima map)"
1839
+ )
1840
+
1841
+ if wmin < 2 or wmin >= num_intersections // 2:
1842
+ raise ValueError(
1843
+ f"`wmin` must be an integer >= 2 and <= total_time / 2. Got {wmin}."
1844
+ )
1845
+
1846
+ time_step = self.__get_initial_time_step(u, parameters)
1847
+
1848
+ return hurst_exponent_wrapped(
1849
+ u,
1850
+ parameters,
1851
+ num_intersections,
1852
+ self.__equations_of_motion,
1853
+ time_step,
1854
+ self.__atol,
1855
+ self.__rtol,
1856
+ self.__integrator_func,
1857
+ map_type,
1858
+ section_index,
1859
+ section_value,
1860
+ crossing,
1861
+ sampling_time,
1862
+ maxima_index,
1863
+ wmin,
1864
+ transient_time,
1865
+ )