pynamicalsys 1.3.1__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.
@@ -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,6 +31,8 @@ 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
35
+
34
36
 
35
37
  @njit
36
38
  def lyapunov_1D(
@@ -1047,8 +1049,7 @@ def GALI_k(
1047
1049
  return np.array([gali])
1048
1050
 
1049
1051
 
1050
- @njit
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)
@@ -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/>.