pynamicalsys 1.0.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 +24 -0
- pynamicalsys/__version__.py +21 -0
- pynamicalsys/common/__init__.py +16 -0
- pynamicalsys/common/basin_analysis.py +170 -0
- pynamicalsys/common/recurrence_quantification_analysis.py +426 -0
- pynamicalsys/common/utils.py +344 -0
- pynamicalsys/continuous_time/__init__.py +16 -0
- pynamicalsys/core/__init__.py +16 -0
- pynamicalsys/core/basin_metrics.py +206 -0
- pynamicalsys/core/continuous_dynamical_systems.py +18 -0
- pynamicalsys/core/discrete_dynamical_systems.py +3391 -0
- pynamicalsys/core/plot_styler.py +155 -0
- pynamicalsys/core/time_series_metrics.py +139 -0
- pynamicalsys/discrete_time/__init__.py +16 -0
- pynamicalsys/discrete_time/dynamical_indicators.py +1226 -0
- pynamicalsys/discrete_time/models.py +435 -0
- pynamicalsys/discrete_time/trajectory_analysis.py +1459 -0
- pynamicalsys/discrete_time/transport.py +501 -0
- pynamicalsys/discrete_time/validators.py +313 -0
- pynamicalsys-1.0.0.dist-info/METADATA +791 -0
- pynamicalsys-1.0.0.dist-info/RECORD +23 -0
- pynamicalsys-1.0.0.dist-info/WHEEL +5 -0
- pynamicalsys-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,501 @@
|
|
1
|
+
# transport.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/>.
|
17
|
+
|
18
|
+
from typing import Optional, Callable
|
19
|
+
from numpy.typing import NDArray
|
20
|
+
import numpy as np
|
21
|
+
from numba import njit, prange
|
22
|
+
from .trajectory_analysis import iterate_mapping
|
23
|
+
|
24
|
+
|
25
|
+
@njit(cache=True, parallel=True)
|
26
|
+
def diffusion_coefficient(
|
27
|
+
u0: NDArray[np.float64],
|
28
|
+
parameters: NDArray[np.float64],
|
29
|
+
total_time: int,
|
30
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
31
|
+
axis: int = 1,
|
32
|
+
) -> np.float64:
|
33
|
+
"""
|
34
|
+
Compute the diffusion coefficient for an ensemble of trajectories.
|
35
|
+
|
36
|
+
The diffusion coefficient D is estimated using the Einstein relation:
|
37
|
+
D = lim_{t→∞} ⟨(x(t) - x(0))²⟩ / (2t)
|
38
|
+
where ⟨·⟩ denotes ensemble averaging.
|
39
|
+
|
40
|
+
Parameters
|
41
|
+
----------
|
42
|
+
u0 : NDArray[np.float64]
|
43
|
+
Array of initial conditions (shape: (num_ic, neq))
|
44
|
+
parameters : NDArray[np.float64]
|
45
|
+
System parameters passed to mapping function
|
46
|
+
total_time : int
|
47
|
+
Total evolution time (must be > transient_time)
|
48
|
+
mapping : Callable[[NDArray, NDArray], NDArray]
|
49
|
+
System evolution function: u_next = mapping(u, parameters)
|
50
|
+
axis : int, optional
|
51
|
+
axis index to analyze (default: 1)
|
52
|
+
|
53
|
+
Returns
|
54
|
+
-------
|
55
|
+
float
|
56
|
+
Estimated diffusion coefficient D
|
57
|
+
|
58
|
+
Raises
|
59
|
+
------
|
60
|
+
ValueError
|
61
|
+
If total_time ≤ transient_time
|
62
|
+
If axis index is invalid
|
63
|
+
|
64
|
+
Notes
|
65
|
+
-----
|
66
|
+
- Assumes normal diffusion (linear mean squared displacement growth)
|
67
|
+
- For anisotropic systems, analyze each axis separately
|
68
|
+
- Parallelized over initial conditions for large ensembles
|
69
|
+
"""
|
70
|
+
# Input validation
|
71
|
+
if axis < 0 or axis >= u0.shape[1]:
|
72
|
+
raise ValueError(f"axis must be in [0, {u0.shape[1]-1}]")
|
73
|
+
|
74
|
+
num_ic = u0.shape[0]
|
75
|
+
u_final = np.empty_like(u0)
|
76
|
+
|
77
|
+
# Parallel evolution of trajectories
|
78
|
+
for i in prange(num_ic):
|
79
|
+
# Evolve each initial condition
|
80
|
+
u_final[i] = iterate_mapping(u0[i], parameters, total_time, mapping)
|
81
|
+
|
82
|
+
# Compute mean squared displacement
|
83
|
+
msd = np.mean((u_final[:, axis] - u0[:, axis]) ** 2)
|
84
|
+
|
85
|
+
return msd / (2 * (total_time))
|
86
|
+
|
87
|
+
|
88
|
+
@njit(cache=True, parallel=True)
|
89
|
+
def average_vs_time(
|
90
|
+
u: NDArray[np.float64],
|
91
|
+
parameters: NDArray[np.float64],
|
92
|
+
total_time: int,
|
93
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
94
|
+
sample_times: Optional[NDArray[np.int32]] = None,
|
95
|
+
axis: int = 1,
|
96
|
+
transient_time: int = 0,
|
97
|
+
) -> NDArray[np.float64]:
|
98
|
+
"""
|
99
|
+
Compute the time evolution of ensemble averages for a dynamical system.
|
100
|
+
|
101
|
+
Tracks the average value of a specified coordinate across multiple trajectories,
|
102
|
+
with options for downsampling and transient removal. Useful for studying
|
103
|
+
convergence to equilibrium or statistical properties.
|
104
|
+
|
105
|
+
Parameters
|
106
|
+
----------
|
107
|
+
u : NDArray[np.float64]
|
108
|
+
Array of initial conditions (shape: (num_ic, num_dim))
|
109
|
+
parameters : NDArray[np.float64]
|
110
|
+
System parameters passed to mapping function
|
111
|
+
total_time : int
|
112
|
+
Total number of iterations (must be > transient_time)
|
113
|
+
mapping : Callable[[NDArray, NDArray], NDArray]
|
114
|
+
System evolution function: u_next = mapping(u, parameters)
|
115
|
+
sample_times : Optional[NDArray[np.int64]], optional
|
116
|
+
Specific time steps to record (default: record all steps)
|
117
|
+
axis : int, optional
|
118
|
+
Coordinate index to analyze (default: 1)
|
119
|
+
transient_time : int, optional
|
120
|
+
Initial iterations to discard (default: 0)
|
121
|
+
|
122
|
+
Returns
|
123
|
+
-------
|
124
|
+
NDArray[np.float64]
|
125
|
+
Array of average values at requested times
|
126
|
+
|
127
|
+
Raises
|
128
|
+
------
|
129
|
+
ValueError
|
130
|
+
If total_time ≤ transient_time
|
131
|
+
If sample_times contains values > total_time
|
132
|
+
If axis is invalid
|
133
|
+
|
134
|
+
Notes
|
135
|
+
-----
|
136
|
+
- Uses parallel processing over initial conditions
|
137
|
+
- For large ensembles, consider using sample_times to reduce memory
|
138
|
+
- The output length matches len(sample_times) if provided, else (total_time - transient_time)
|
139
|
+
"""
|
140
|
+
# Input validation
|
141
|
+
if total_time <= transient_time:
|
142
|
+
raise ValueError("total_time must be > transient_time")
|
143
|
+
if axis < 0 or axis >= u.shape[1]:
|
144
|
+
raise ValueError(f"axis must be in [0, {u.shape[1]-1}]")
|
145
|
+
if sample_times is not None:
|
146
|
+
if np.any(sample_times >= total_time):
|
147
|
+
raise ValueError("All sample_times must be < total_time")
|
148
|
+
|
149
|
+
# Initialize tracking
|
150
|
+
num_ic = u.shape[0]
|
151
|
+
effective_time = total_time - transient_time
|
152
|
+
u_current = u.copy()
|
153
|
+
|
154
|
+
# Handle output array
|
155
|
+
if sample_times is not None:
|
156
|
+
output = np.empty(len(sample_times))
|
157
|
+
else:
|
158
|
+
output = np.empty(effective_time)
|
159
|
+
|
160
|
+
output_idx = 0
|
161
|
+
|
162
|
+
# Main evolution loop
|
163
|
+
for t in range(total_time):
|
164
|
+
# Parallel evolution
|
165
|
+
for i in prange(num_ic):
|
166
|
+
u_current[i] = mapping(u_current[i], parameters)
|
167
|
+
|
168
|
+
# Record if past transient and matches sampling
|
169
|
+
if t >= transient_time:
|
170
|
+
output[output_idx] = np.mean(u_current[:, axis])
|
171
|
+
if sample_times is None:
|
172
|
+
output_idx += 1
|
173
|
+
elif t in sample_times:
|
174
|
+
output_idx += 1
|
175
|
+
|
176
|
+
return output
|
177
|
+
|
178
|
+
|
179
|
+
@njit(cache=True, parallel=True)
|
180
|
+
def cumulative_average_vs_time(
|
181
|
+
u: NDArray[np.float64],
|
182
|
+
parameters: NDArray[np.float64],
|
183
|
+
total_time: int,
|
184
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
185
|
+
sample_times: Optional[NDArray[np.int32]] = None,
|
186
|
+
axis: int = 1,
|
187
|
+
transient_time: int = 0,
|
188
|
+
) -> NDArray[np.float64]:
|
189
|
+
"""
|
190
|
+
Compute the time evolution of the cumulative average of a coordinate across trajectories.
|
191
|
+
|
192
|
+
Parameters
|
193
|
+
----------
|
194
|
+
u : NDArray[np.float64]
|
195
|
+
Array of initial conditions (shape: (num_ic, num_dim))
|
196
|
+
parameters : NDArray[np.float64]
|
197
|
+
System parameters passed to mapping function
|
198
|
+
total_time : int
|
199
|
+
Total number of iterations (must be > transient_time)
|
200
|
+
mapping : Callable[[NDArray, NDArray], NDArray]
|
201
|
+
System evolution function: u_next = mapping(u, parameters)
|
202
|
+
sample_times : Optional[NDArray[np.int64]], optional
|
203
|
+
Specific time steps to record (default: record all steps)
|
204
|
+
axis : int, optional
|
205
|
+
Coordinate index to analyze (default: 1)
|
206
|
+
transient_time : int, optional
|
207
|
+
Initial iterations to discard (default: 0)
|
208
|
+
|
209
|
+
Returns
|
210
|
+
-------
|
211
|
+
NDArray[np.float64]
|
212
|
+
Array of cumulative average values at requested times
|
213
|
+
|
214
|
+
Raises
|
215
|
+
------
|
216
|
+
ValueError
|
217
|
+
If total_time ≤ transient_time
|
218
|
+
If sample_times contains invalid values
|
219
|
+
If axis is invalid
|
220
|
+
|
221
|
+
Notes
|
222
|
+
-----
|
223
|
+
- Uses parallel processing over initial conditions
|
224
|
+
- For large total_time, use sample_times to reduce memory usage
|
225
|
+
- The cumulative average is computed over the ensemble after removing transient
|
226
|
+
"""
|
227
|
+
|
228
|
+
num_ic = u.shape[0]
|
229
|
+
u_current = u.copy()
|
230
|
+
sum_values = np.zeros(num_ic)
|
231
|
+
|
232
|
+
# Initialize output array
|
233
|
+
if sample_times is not None:
|
234
|
+
output_size = len(sample_times)
|
235
|
+
else:
|
236
|
+
output_size = total_time - transient_time
|
237
|
+
|
238
|
+
cumul_average = np.zeros(output_size)
|
239
|
+
output_idx = 0
|
240
|
+
|
241
|
+
# Main evolution loop
|
242
|
+
for t in range(1, total_time + 1):
|
243
|
+
# Parallel evolution
|
244
|
+
for i in prange(num_ic):
|
245
|
+
u_current[i] = mapping(u_current[i], parameters)
|
246
|
+
|
247
|
+
# Record if past transient and matches sampling
|
248
|
+
if t > transient_time:
|
249
|
+
# Update running sum of squares
|
250
|
+
sum_values += u_current[:, axis]
|
251
|
+
cumul_average[output_idx] = np.mean(sum_values / t)
|
252
|
+
if sample_times is None:
|
253
|
+
output_idx += 1
|
254
|
+
elif t in sample_times:
|
255
|
+
output_idx += 1
|
256
|
+
|
257
|
+
return cumul_average
|
258
|
+
|
259
|
+
|
260
|
+
@njit(cache=True, parallel=True)
|
261
|
+
def root_mean_squared(
|
262
|
+
u: NDArray[np.float64],
|
263
|
+
parameters: NDArray[np.float64],
|
264
|
+
total_time: int,
|
265
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
266
|
+
sample_times: Optional[NDArray[np.int32]] = None,
|
267
|
+
axis: int = 1,
|
268
|
+
transient_time: int = 0,
|
269
|
+
) -> NDArray[np.float64]:
|
270
|
+
"""
|
271
|
+
Compute the time evolution of the root mean square (RMS) of a coordinate across trajectories.
|
272
|
+
|
273
|
+
The RMS is calculated as:
|
274
|
+
RMS(t) = sqrt(∑(x_i(t)²)/N)
|
275
|
+
where N is the number of trajectories and x_i are the coordinate values.
|
276
|
+
|
277
|
+
Parameters
|
278
|
+
----------
|
279
|
+
u : NDArray[np.float64]
|
280
|
+
Array of initial conditions (shape: (num_ic, num_dim))
|
281
|
+
parameters : NDArray[np.float64]
|
282
|
+
System parameters passed to mapping function
|
283
|
+
total_time : int
|
284
|
+
Total number of iterations (must be > transient_time)
|
285
|
+
mapping : Callable[[NDArray, NDArray], NDArray]
|
286
|
+
System evolution function: u_next = mapping(u, parameters)
|
287
|
+
sample_times : Optional[NDArray[np.int64]], optional
|
288
|
+
Specific time steps to record (default: record all steps)
|
289
|
+
axis : int, optional
|
290
|
+
Coordinate index to analyze (default: 1)
|
291
|
+
transient_time : int, optional
|
292
|
+
Initial iterations to discard (default: 0)
|
293
|
+
|
294
|
+
Returns
|
295
|
+
-------
|
296
|
+
NDArray[np.float64]
|
297
|
+
Array of RMS values at requested times
|
298
|
+
|
299
|
+
Raises
|
300
|
+
------
|
301
|
+
ValueError
|
302
|
+
If total_time ≤ transient_time
|
303
|
+
If sample_times contains invalid values
|
304
|
+
If axis is invalid
|
305
|
+
|
306
|
+
Notes
|
307
|
+
-----
|
308
|
+
- Uses parallel processing over initial conditions
|
309
|
+
- For large total_time, use sample_times to reduce memory usage
|
310
|
+
- The RMS is computed over the ensemble after removing transient
|
311
|
+
"""
|
312
|
+
|
313
|
+
num_ic = u.shape[0]
|
314
|
+
u_current = u.copy()
|
315
|
+
sum_squares = np.zeros(num_ic)
|
316
|
+
|
317
|
+
# Initialize output array
|
318
|
+
if sample_times is not None:
|
319
|
+
output_size = len(sample_times)
|
320
|
+
else:
|
321
|
+
output_size = total_time - transient_time
|
322
|
+
|
323
|
+
rms = np.zeros(output_size)
|
324
|
+
output_idx = 0
|
325
|
+
|
326
|
+
# Main evolution loop
|
327
|
+
for t in range(1, total_time + 1):
|
328
|
+
# Parallel evolution
|
329
|
+
for i in prange(num_ic):
|
330
|
+
u_current[i] = mapping(u_current[i], parameters)
|
331
|
+
|
332
|
+
# Record if past transient and matches sampling
|
333
|
+
if t > transient_time:
|
334
|
+
# Update running sum of squares
|
335
|
+
sum_squares += u_current[:, axis] ** 2
|
336
|
+
rms[output_idx] = np.sqrt(np.mean(sum_squares / t))
|
337
|
+
if sample_times is None:
|
338
|
+
output_idx += 1
|
339
|
+
elif t in sample_times:
|
340
|
+
output_idx += 1
|
341
|
+
|
342
|
+
return rms
|
343
|
+
|
344
|
+
|
345
|
+
@njit(cache=True, parallel=True)
|
346
|
+
def mean_squared_displacement(
|
347
|
+
u0: NDArray[np.float64],
|
348
|
+
parameters: NDArray[np.float64],
|
349
|
+
total_time: int,
|
350
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
351
|
+
sample_times: Optional[NDArray[np.int32]] = None,
|
352
|
+
axis: int = 1,
|
353
|
+
transient_time: int = 0,
|
354
|
+
) -> NDArray[np.float64]:
|
355
|
+
"""
|
356
|
+
Compute the mean squared displacement (MSD) of a coordinate across multiple trajectories.
|
357
|
+
|
358
|
+
The MSD is calculated as:
|
359
|
+
MSD(t) = ⟨(x_i(t) - x_i(0))²⟩
|
360
|
+
where ⟨·⟩ denotes the average over all trajectories.
|
361
|
+
|
362
|
+
Parameters
|
363
|
+
----------
|
364
|
+
u0 : NDArray[np.float64]
|
365
|
+
Array of initial conditions (shape: (num_ic, num_dim))
|
366
|
+
parameters : NDArray[np.float64]
|
367
|
+
System parameters passed to mapping function
|
368
|
+
total_time : int
|
369
|
+
Total number of iterations (must be > transient_time)
|
370
|
+
mapping : Callable[[NDArray, NDArray], NDArray]
|
371
|
+
System evolution function: u_next = mapping(u, parameters)
|
372
|
+
sample_times : Optional[NDArray[np.int64]], optional
|
373
|
+
Specific time steps to record (default: record all steps)
|
374
|
+
axis : int, optional
|
375
|
+
Coordinate index to analyze (default: 1)
|
376
|
+
transient_time : int, optional
|
377
|
+
Initial iterations to discard (default: 0)
|
378
|
+
|
379
|
+
Returns
|
380
|
+
-------
|
381
|
+
NDArray[np.float64]
|
382
|
+
Array of MSD values at requested times
|
383
|
+
|
384
|
+
Raises
|
385
|
+
------
|
386
|
+
ValueError
|
387
|
+
If total_time ≤ transient_time
|
388
|
+
If sample_times contains invalid values
|
389
|
+
If axis is invalid
|
390
|
+
|
391
|
+
Notes
|
392
|
+
-----
|
393
|
+
- Uses parallel processing over initial conditions
|
394
|
+
- For normal diffusion, MSD grows linearly with time
|
395
|
+
- The output length matches len(sample_times) if provided, else (total_time - transient_time)
|
396
|
+
"""
|
397
|
+
# Input validation
|
398
|
+
|
399
|
+
num_ic = u0.shape[0]
|
400
|
+
u = u0.copy()
|
401
|
+
# Store initial values for MSD calculation
|
402
|
+
initial_values = u0[:, axis].copy()
|
403
|
+
|
404
|
+
# Initialize output array
|
405
|
+
if sample_times is not None:
|
406
|
+
output_size = len(sample_times)
|
407
|
+
else:
|
408
|
+
output_size = total_time - transient_time
|
409
|
+
|
410
|
+
msd = np.zeros(output_size)
|
411
|
+
output_idx = 0
|
412
|
+
|
413
|
+
# Main evolution loop
|
414
|
+
for t in range(1, total_time + 1):
|
415
|
+
# Parallel evolution
|
416
|
+
for i in prange(num_ic):
|
417
|
+
u[i] = mapping(u[i], parameters)
|
418
|
+
|
419
|
+
# Calculate and store MSD if past transient
|
420
|
+
if t > transient_time:
|
421
|
+
displacements = u[:, axis] - initial_values
|
422
|
+
msd[output_idx] = np.mean(displacements**2)
|
423
|
+
if sample_times is None:
|
424
|
+
output_idx += 1
|
425
|
+
elif t in sample_times:
|
426
|
+
output_idx += 1
|
427
|
+
|
428
|
+
return msd
|
429
|
+
|
430
|
+
|
431
|
+
@njit(cache=True)
|
432
|
+
def recurrence_times(
|
433
|
+
u: NDArray[np.float64],
|
434
|
+
parameters: NDArray[np.float64],
|
435
|
+
total_time: int,
|
436
|
+
mapping: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]],
|
437
|
+
eps: float,
|
438
|
+
transient_time: Optional[int] = None,
|
439
|
+
) -> NDArray[np.float64]:
|
440
|
+
"""Compute recurrence times to a neighborhood of the initial condition.
|
441
|
+
|
442
|
+
Parameters
|
443
|
+
----------
|
444
|
+
u : NDArray[np.float64]
|
445
|
+
Initial state vector (shape: `(neq,)`).
|
446
|
+
parameters : NDArray[np.float64]
|
447
|
+
System parameters passed to `mapping` and `jacobian`.
|
448
|
+
total_time : int
|
449
|
+
Total number of iterations to compute (must be > 0)
|
450
|
+
mapping : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]]
|
451
|
+
System mapping function (must be Numba-compatible)
|
452
|
+
eps : float
|
453
|
+
Size of the neighborhood (must be > 0)
|
454
|
+
transient_time : Optional[int], optional
|
455
|
+
Number of initial iterations to discard (default: None, no transient removal)
|
456
|
+
|
457
|
+
Returns
|
458
|
+
-------
|
459
|
+
NDArray[np.float64]
|
460
|
+
Array of recurrence times where:
|
461
|
+
- Each element is the time between returns to the eps-neighborhood
|
462
|
+
- Empty array if no recurrences occur
|
463
|
+
|
464
|
+
Notes
|
465
|
+
-----
|
466
|
+
- A recurrence occurs when the trajectory enters the hypercube:
|
467
|
+
[u-eps/2, u+eps/2]^d
|
468
|
+
- Useful for analyzing:
|
469
|
+
- Stickiness in Hamiltonian systems
|
470
|
+
- Chaotic vs regular orbits
|
471
|
+
- For meaningful results:
|
472
|
+
- eps should be small but not smaller than numerical precision
|
473
|
+
- total_time should be >> expected recurrence times
|
474
|
+
"""
|
475
|
+
|
476
|
+
u = u.copy()
|
477
|
+
|
478
|
+
if transient_time is not None:
|
479
|
+
u = iterate_mapping(u, parameters, transient_time, mapping)
|
480
|
+
|
481
|
+
lower_bound = u - eps / 2
|
482
|
+
upper_bound = u + eps / 2
|
483
|
+
|
484
|
+
# Initialize recurrence time and list
|
485
|
+
rt = 0
|
486
|
+
rts = []
|
487
|
+
|
488
|
+
# Iterate over the total time
|
489
|
+
for t in range(total_time):
|
490
|
+
# Evolve the system
|
491
|
+
u = mapping(u, parameters)
|
492
|
+
|
493
|
+
# Increment the recurrence time
|
494
|
+
rt += 1
|
495
|
+
|
496
|
+
# Check if the state has entered the box
|
497
|
+
if np.all(u >= lower_bound) and np.all(u <= upper_bound):
|
498
|
+
rts.append(rt)
|
499
|
+
rt = 0
|
500
|
+
|
501
|
+
return np.array(rts)
|