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
@@ -18,12 +18,18 @@
18
18
  import numpy as np
19
19
  from numpy.typing import NDArray
20
20
 
21
+ from numbers import Integral, Real
22
+
21
23
  from pynamicalsys.common.recurrence_quantification_analysis import (
22
24
  recurrence_matrix,
23
25
  RTEConfig,
24
26
  white_vertline_distr,
25
27
  )
26
28
 
29
+ from pynamicalsys.discrete_time.validators import validate_positive
30
+
31
+ from pynamicalsys.common.time_series_metrics import hurst_exponent
32
+
27
33
 
28
34
  class TimeSeriesMetrics:
29
35
  def __init__(self, time_series: NDArray[np.float64]) -> None:
@@ -137,3 +143,62 @@ class TimeSeriesMetrics:
137
143
  result.append(P)
138
144
 
139
145
  return result[0] if len(result) == 1 else tuple(result)
146
+
147
+ def hurst_exponent(self, wmin: int = 2):
148
+ """
149
+ Estimate the Hurst exponent for a system trajectory using the rescaled range (R/S) method.
150
+
151
+ Parameters
152
+ ----------
153
+ u : NDArray[np.float64]
154
+ Initial condition vector of shape (n,).
155
+ parameters : NDArray[np.float64]
156
+ Parameters passed to the mapping function.
157
+ total_time : int
158
+ Total number of iterations used to generate the trajectory.
159
+ mapping : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]]
160
+ A function that defines the system dynamics, i.e., how `u` evolves over time given `parameters`.
161
+ wmin : int, optional
162
+ Minimum window size for the rescaled range calculation. Default is 2.
163
+ transient_time : Optional[int], optional
164
+ Number of initial iterations to discard as transient. If `None`, no transient is removed. Default is `None`.
165
+
166
+ Returns
167
+ -------
168
+ NDArray[np.float64]
169
+ Estimated Hurst exponents for each dimension of the input vector `u`, of shape (n,).
170
+
171
+ Notes
172
+ -----
173
+ The Hurst exponent is a measure of the long-term memory of a time series:
174
+
175
+ - H = 0.5 indicates a random walk (no memory).
176
+ - H > 0.5 indicates persistent behavior (positive autocorrelation).
177
+ - H < 0.5 indicates anti-persistent behavior (negative autocorrelation).
178
+
179
+ This implementation computes the rescaled range (R/S) for various window sizes and
180
+ performs a linear regression in log-log space to estimate the exponent.
181
+
182
+ The function supports multivariate time series, estimating one Hurst exponent per dimension.
183
+ """
184
+
185
+ sample_size = self.time_series.shape[0]
186
+
187
+ validate_positive(wmin, "wmin", Integral)
188
+
189
+ if wmin < 2 or wmin >= sample_size // 2:
190
+ raise ValueError(
191
+ f"`wmin` must be an integer >= 2 and <= len(time_series) / 2. Got {wmin}."
192
+ )
193
+
194
+ if self.time_series.ndim == 1:
195
+ time_series = self.time_series.reshape(sample_size, 1)
196
+ else:
197
+ time_series = self.time_series
198
+
199
+ result = hurst_exponent(time_series, wmin=wmin)
200
+
201
+ if self.time_series.ndim == 1:
202
+ return result[0]
203
+ else:
204
+ return result
@@ -18,7 +18,7 @@
18
18
  from typing import Optional, Tuple, Union, Callable
19
19
  from numpy.typing import NDArray
20
20
  import numpy as np
21
- from numba import njit
21
+ from numba import njit, prange
22
22
 
23
23
  from pynamicalsys.common.recurrence_quantification_analysis import (
24
24
  RTEConfig,
@@ -31,8 +31,10 @@ from pynamicalsys.discrete_time.trajectory_analysis import (
31
31
  )
32
32
  from pynamicalsys.common.utils import qr, householder_qr, fit_poly, wedge_norm
33
33
 
34
+ from pynamicalsys.common.time_series_metrics import hurst_exponent
34
35
 
35
- @njit(cache=True)
36
+
37
+ @njit
36
38
  def lyapunov_1D(
37
39
  u: NDArray[np.float64],
38
40
  parameters: NDArray[np.float64],
@@ -120,7 +122,7 @@ def lyapunov_1D(
120
122
  return history if return_history else np.array([exponent / sample_size])
121
123
 
122
124
 
123
- @njit(cache=True)
125
+ @njit
124
126
  def lyapunov_er(
125
127
  u: NDArray[np.float64],
126
128
  parameters: NDArray[np.float64],
@@ -246,7 +248,7 @@ def lyapunov_er(
246
248
  return aux_exponents, u_contig
247
249
 
248
250
 
249
- @njit(cache=True)
251
+ @njit
250
252
  def maximum_lyapunov_er(
251
253
  u: NDArray[np.float64],
252
254
  parameters: NDArray[np.float64],
@@ -367,7 +369,7 @@ def maximum_lyapunov_er(
367
369
  return np.array([exponent / sample_size]), u_contig
368
370
 
369
371
 
370
- @njit(cache=True)
372
+ @njit
371
373
  def lyapunov_qr(
372
374
  u: NDArray[np.float64],
373
375
  parameters: NDArray[np.float64],
@@ -701,7 +703,7 @@ def dig(
701
703
  return -np.log10(abs(WB0 - WB1))
702
704
 
703
705
 
704
- @njit(cache=True)
706
+ @njit
705
707
  def SALI(
706
708
  u: NDArray[np.float64],
707
709
  parameters: NDArray[np.float64],
@@ -761,7 +763,7 @@ def SALI(
761
763
  - Uses QR decomposition to initialize orthonormal deviation vectors.
762
764
  - Computes both Parallel (PAI) and Antiparallel (AAI) Alignment Indices.
763
765
  - Early termination occurs if SALI < `tol` (indicating chaotic behavior).
764
- - Optimized with `@njit(cache=True)` for performance.
766
+ - Optimized with `@njit` for performance.
765
767
  """
766
768
 
767
769
  np.random.seed(seed) # For reproducibility
@@ -1047,8 +1049,7 @@ def GALI_k(
1047
1049
  return np.array([gali])
1048
1050
 
1049
1051
 
1050
- @njit(cache=True)
1051
- def hurst_exponent(
1052
+ def hurst_exponent_wrapped(
1052
1053
  u: NDArray[np.float64],
1053
1054
  parameters: NDArray[np.float64],
1054
1055
  total_time: int,
@@ -1057,105 +1058,15 @@ def hurst_exponent(
1057
1058
  transient_time: Optional[int] = None,
1058
1059
  return_last: bool = False,
1059
1060
  ) -> NDArray[np.float64]:
1060
- """
1061
- Estimate the Hurst exponent for a system trajectory using the rescaled range (R/S) method.
1062
-
1063
- Parameters
1064
- ----------
1065
- u : NDArray[np.float64]
1066
- Initial condition vector of shape (n,).
1067
- parameters : NDArray[np.float64]
1068
- Parameters passed to the mapping function.
1069
- total_time : int
1070
- Total number of iterations used to generate the trajectory.
1071
- mapping : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]]
1072
- A function that defines the system dynamics, i.e., how `u` evolves over time given `parameters`.
1073
- wmin : int, optional
1074
- Minimum window size for the rescaled range calculation. Default is 2.
1075
- transient_time : Optional[int], optional
1076
- Number of initial iterations to discard as transient. If `None`, no transient is removed. Default is `None`.
1077
-
1078
- Returns
1079
- -------
1080
- NDArray[np.float64]
1081
- Estimated Hurst exponents for each dimension of the input vector `u`, of shape (n,).
1082
-
1083
- Notes
1084
- -----
1085
- The Hurst exponent is a measure of the long-term memory of a time series:
1086
-
1087
- - H = 0.5 indicates a random walk (no memory).
1088
- - H > 0.5 indicates persistent behavior (positive autocorrelation).
1089
- - H < 0.5 indicates anti-persistent behavior (negative autocorrelation).
1090
-
1091
- This implementation computes the rescaled range (R/S) for various window sizes and
1092
- performs a linear regression in log-log space to estimate the exponent.
1093
-
1094
- The function supports multivariate time series, estimating one Hurst exponent per dimension.
1095
- """
1096
-
1097
1061
  u = u.copy()
1098
1062
  neq = len(u)
1099
1063
  H = np.zeros(neq)
1100
1064
 
1101
- # Handle transient time
1102
- if transient_time is not None:
1103
- sample_size = total_time - transient_time
1104
- for i in range(transient_time):
1105
- u = mapping(u, parameters)
1106
- else:
1107
- sample_size = total_time
1108
-
1109
1065
  time_series = generate_trajectory(
1110
1066
  u, parameters, total_time, mapping, transient_time=transient_time
1111
1067
  )
1112
1068
 
1113
- ells = np.arange(wmin, sample_size // 2)
1114
- RS = np.empty((ells.shape[0], neq))
1115
- for j in range(neq):
1116
-
1117
- for i, ell in enumerate(ells):
1118
- num_blocks = sample_size // ell
1119
- R_over_S = np.empty(num_blocks)
1120
-
1121
- for block in range(num_blocks):
1122
- start = block * ell
1123
- end = start + ell
1124
- block_series = time_series[start:end, j]
1125
-
1126
- # Mean adjustment
1127
- mean_adjusted_series = block_series - np.mean(block_series)
1128
-
1129
- # Cumulative sum
1130
- Z = np.cumsum(mean_adjusted_series)
1131
-
1132
- # Range (R)
1133
- R = np.max(Z) - np.min(Z)
1134
-
1135
- # Standard deviation (S)
1136
- S = np.std(block_series)
1137
-
1138
- # Avoid division by zero
1139
- if S > 0:
1140
- R_over_S[block] = R / S
1141
- else:
1142
- R_over_S[block] = 0
1143
-
1144
- if np.all(R_over_S == 0):
1145
- RS[i, j] == 0
1146
- else:
1147
- RS[i, j] = np.mean(R_over_S[R_over_S > 0])
1148
-
1149
- if np.all(RS[:, j] == 0):
1150
- H[j] = 0
1151
- else:
1152
- # Log-log plot and linear regression to estimate the Hurst exponent
1153
- inds = np.where(RS[:, j] > 0)[0]
1154
- x_fit = np.log(ells[inds])
1155
- y_fit = np.log(RS[inds, j])
1156
- fitting = fit_poly(x_fit, y_fit, 1)
1157
-
1158
- H[j] = fitting[0]
1069
+ H = hurst_exponent(time_series, wmin=wmin)
1159
1070
 
1160
1071
  if return_last:
1161
1072
  result = np.zeros(2 * neq)
@@ -1224,13 +1135,13 @@ def finite_time_hurst_exponent(
1224
1135
  return H_values
1225
1136
 
1226
1137
 
1227
- @njit(cache=True)
1138
+ @njit
1228
1139
  def lyapunov_vectors():
1229
1140
  # ! To be implemented...
1230
1141
  pass
1231
1142
 
1232
1143
 
1233
- @njit(cache=True)
1144
+ @njit
1234
1145
  def lagrangian_descriptors(
1235
1146
  u: NDArray[np.float64],
1236
1147
  parameters: NDArray[np.float64],
@@ -408,7 +408,7 @@ def logistic_map_jacobian(
408
408
  # ! ------------------ !
409
409
 
410
410
 
411
- @njit(cache=True)
411
+ @njit
412
412
  def rulkov_map(
413
413
  u: NDArray[np.float64], parameters: Union[NDArray[np.float64], Sequence[float]]
414
414
  ) -> NDArray[np.float64]:
@@ -419,7 +419,7 @@ def rulkov_map(
419
419
  return np.array([x_new, y_new])
420
420
 
421
421
 
422
- @njit(cache=True)
422
+ @njit
423
423
  def rulkov_map_jacobian(
424
424
  u: NDArray[np.float64],
425
425
  parameters: Union[NDArray[np.float64], Sequence[float]],
@@ -22,7 +22,7 @@ from numba import njit, prange
22
22
  from numpy.typing import NDArray
23
23
 
24
24
 
25
- @njit(cache=True)
25
+ @njit
26
26
  def iterate_mapping(
27
27
  u: NDArray[np.float64],
28
28
  parameters: NDArray[np.float64],
@@ -78,7 +78,7 @@ def iterate_mapping(
78
78
  return u
79
79
 
80
80
 
81
- @njit(cache=True)
81
+ @njit
82
82
  def generate_trajectory(
83
83
  u: NDArray[np.float64],
84
84
  parameters: NDArray[np.float64],
@@ -331,7 +331,7 @@ def bifurcation_diagram(
331
331
  return param_values, results
332
332
 
333
333
 
334
- @njit(cache=True)
334
+ @njit
335
335
  def period_counter(
336
336
  u: NDArray[np.float64],
337
337
  parameters: NDArray[np.float64],
@@ -415,7 +415,7 @@ def period_counter(
415
415
  return -1
416
416
 
417
417
 
418
- @njit(cache=True)
418
+ @njit
419
419
  def rotation_number(
420
420
  u: Union[NDArray[np.float64], Sequence[float], float],
421
421
  parameters: Union[NDArray[np.float64], Sequence[float], float],
@@ -438,7 +438,7 @@ def rotation_number(
438
438
  return rn
439
439
 
440
440
 
441
- @njit(cache=True)
441
+ @njit
442
442
  def escape_basin_and_time_entering(
443
443
  u: NDArray[np.float64],
444
444
  parameters: NDArray[np.float64],
@@ -515,7 +515,7 @@ def escape_basin_and_time_entering(
515
515
  return -1, max_time
516
516
 
517
517
 
518
- @njit(cache=True)
518
+ @njit
519
519
  def escape_time_exiting(
520
520
  u: NDArray[np.float64],
521
521
  parameters: NDArray[np.float64],
@@ -584,7 +584,7 @@ def escape_time_exiting(
584
584
  return -1, max_time # No escape
585
585
 
586
586
 
587
- @njit(cache=True)
587
+ @njit
588
588
  def survival_probability(
589
589
  escape_times: NDArray[np.int32],
590
590
  max_time: np.int32,
@@ -659,7 +659,7 @@ def survival_probability(
659
659
  return t_values, survival_probs
660
660
 
661
661
 
662
- @njit(cache=True)
662
+ @njit
663
663
  def is_periodic(
664
664
  u: NDArray[np.float64],
665
665
  parameters: NDArray[np.float64],
@@ -811,7 +811,7 @@ def scan_phase_space(
811
811
  return result
812
812
 
813
813
 
814
- @njit(cache=True)
814
+ @njit
815
815
  def scan_symmetry_line(
816
816
  points: NDArray[np.float64],
817
817
  parameters: NDArray[np.float64],
@@ -1095,7 +1095,7 @@ def find_periodic_orbit(
1095
1095
  return periodic_orbit
1096
1096
 
1097
1097
 
1098
- @njit(cache=True)
1098
+ @njit
1099
1099
  def eigenvalues_and_eigenvectors(
1100
1100
  u: NDArray[np.float64],
1101
1101
  parameters: NDArray[np.float64],
@@ -390,7 +390,7 @@ def mean_squared_displacement(
390
390
  return output
391
391
 
392
392
 
393
- @njit(cache=True)
393
+ @njit
394
394
  def recurrence_times(
395
395
  u: NDArray[np.float64],
396
396
  parameters: NDArray[np.float64],
@@ -0,0 +1,16 @@
1
+ # # __init__.py
2
+
3
+ # # Copyright (C) 2025 Matheus Rolim Sales
4
+ # #
5
+ # # This program is free software: you can redistribute it and/or modify
6
+ # # it under the terms of the GNU General Public License as published by
7
+ # # the Free Software Foundation, either version 3 of the License, or
8
+ # # (at your option) any later version.
9
+ # #
10
+ # # This program is distributed in the hope that it will be useful,
11
+ # # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # # GNU General Public License for more details.
14
+ # #
15
+ # # You should have received a copy of the GNU General Public License
16
+ # # along with this program. If not, see <https://www.gnu.org/licenses/>.