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.
- pynamicalsys/__init__.py +2 -0
- pynamicalsys/__version__.py +2 -2
- pynamicalsys/common/recurrence_quantification_analysis.py +1 -1
- pynamicalsys/common/time_series_metrics.py +85 -0
- pynamicalsys/common/utils.py +3 -3
- pynamicalsys/continuous_time/chaotic_indicators.py +306 -8
- pynamicalsys/continuous_time/models.py +25 -0
- pynamicalsys/continuous_time/numerical_integrators.py +7 -7
- pynamicalsys/continuous_time/trajectory_analysis.py +460 -13
- pynamicalsys/core/continuous_dynamical_systems.py +933 -35
- pynamicalsys/core/discrete_dynamical_systems.py +20 -9
- pynamicalsys/core/hamiltonian_systems.py +1193 -0
- pynamicalsys/core/time_series_metrics.py +65 -0
- pynamicalsys/discrete_time/dynamical_indicators.py +13 -102
- pynamicalsys/discrete_time/models.py +2 -2
- pynamicalsys/discrete_time/trajectory_analysis.py +10 -10
- pynamicalsys/discrete_time/transport.py +1 -1
- pynamicalsys/hamiltonian_systems/__init__.py +16 -0
- pynamicalsys/hamiltonian_systems/chaotic_indicators.py +638 -0
- pynamicalsys/hamiltonian_systems/models.py +68 -0
- pynamicalsys/hamiltonian_systems/numerical_integrators.py +248 -0
- pynamicalsys/hamiltonian_systems/trajectory_analysis.py +293 -0
- pynamicalsys/hamiltonian_systems/validators.py +114 -0
- {pynamicalsys-1.3.0.dist-info → pynamicalsys-1.4.0.dist-info}/METADATA +37 -8
- pynamicalsys-1.4.0.dist-info/RECORD +36 -0
- pynamicalsys-1.3.0.dist-info/RECORD +0 -28
- {pynamicalsys-1.3.0.dist-info → pynamicalsys-1.4.0.dist-info}/WHEEL +0 -0
- {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
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
1138
|
+
@njit
|
1228
1139
|
def lyapunov_vectors():
|
1229
1140
|
# ! To be implemented...
|
1230
1141
|
pass
|
1231
1142
|
|
1232
1143
|
|
1233
|
-
@njit
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
1098
|
+
@njit
|
1099
1099
|
def eigenvalues_and_eigenvectors(
|
1100
1100
|
u: NDArray[np.float64],
|
1101
1101
|
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/>.
|