pynamicalsys 1.0.1__tar.gz → 1.2.1__tar.gz
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-1.2.1/CHANGELOG.md +43 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/PKG-INFO +15 -3
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/README.md +13 -1
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/pyproject.toml +1 -1
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/__init__.py +8 -1
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/__version__.py +2 -2
- pynamicalsys-1.2.1/src/pynamicalsys/continuous_time/chaotic_indicators.py +347 -0
- pynamicalsys-1.2.1/src/pynamicalsys/continuous_time/models.py +240 -0
- pynamicalsys-1.2.1/src/pynamicalsys/continuous_time/numerical_integrators.py +337 -0
- pynamicalsys-1.2.1/src/pynamicalsys/continuous_time/trajectory_analysis.py +163 -0
- pynamicalsys-1.2.1/src/pynamicalsys/continuous_time/validators.py +114 -0
- pynamicalsys-1.2.1/src/pynamicalsys/core/continuous_dynamical_systems.py +794 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/core/discrete_dynamical_systems.py +44 -45
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/trajectory_analysis.py +3 -2
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys.egg-info/PKG-INFO +15 -3
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys.egg-info/SOURCES.txt +23 -1
- pynamicalsys-1.2.1/tests/continuous_time/test_SALI_and_LDI.ipynb +237 -0
- pynamicalsys-1.2.1/tests/continuous_time/test_lyapunov.ipynb +332 -0
- pynamicalsys-1.2.1/tests/continuous_time/test_trajectory.ipynb +443 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_FTLE.ipynb +101 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_RTE.ipynb +146 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_SALI.py +62 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_bif_diagram.ipynb +187 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_chaotic_saddle.ipynb +369 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_dig.ipynb +160 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_escape.ipynb +577 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_fractal_dimension.ipynb +179 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_hurst_exponent.ipynb +132 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_lyapunov_exponents.ipynb +473 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_manifold.ipynb +266 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_manifolds.ipynb +264 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_period_counter.ipynb +206 -0
- pynamicalsys-1.2.1/tests/discrete-time/test_phase_space.ipynb +108 -0
- pynamicalsys-1.0.1/CHANGELOG.md +0 -31
- pynamicalsys-1.0.1/src/pynamicalsys/core/continuous_dynamical_systems.py +0 -18
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/.gitignore +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/CITATION.cff +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/CONTRIBUTING.md +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/LICENSE +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/MANIFEST.in +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/setup.cfg +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/common/__init__.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/common/basin_analysis.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/common/recurrence_quantification_analysis.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/common/utils.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/continuous_time/__init__.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/core/__init__.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/core/basin_metrics.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/core/plot_styler.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/core/time_series_metrics.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/__init__.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/dynamical_indicators.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/models.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/transport.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys/discrete_time/validators.py +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys.egg-info/dependency_links.txt +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys.egg-info/requires.txt +0 -0
- {pynamicalsys-1.0.1 → pynamicalsys-1.2.1}/src/pynamicalsys.egg-info/top_level.txt +0 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [v1.2.1] - 2025-06-29
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- `ContinuousDynamicalSystem` class for simulating and analyzing continuous nonlinear dynamical systems:
|
13
|
+
- Integration using the 4th order Runge-Kutta method with fixed time step.
|
14
|
+
- Integration using the adaptive 4th/5th order Runge-Kutta method with adaptive time step.
|
15
|
+
- Trajectory computation.
|
16
|
+
- Lyapunov exponents calculation.
|
17
|
+
- The smallest aligment index (SALI) and linear dependence index (LDI) for chaos detection.
|
18
|
+
|
19
|
+
## [v1.0.0] - 2025-06-16
|
20
|
+
|
21
|
+
### Added
|
22
|
+
|
23
|
+
- `DiscreteDynamicalSystems` class for simulating and analyzing discrete nonlinear dynamical systems:
|
24
|
+
- Trajectory computation.
|
25
|
+
- Chaotic indicators.
|
26
|
+
- Fixed points, periodic orbits, and manifolds.
|
27
|
+
- Statistical analysis of ensemble of trajetories.
|
28
|
+
- Escape basin quantification.
|
29
|
+
- Initial release of the package
|
30
|
+
- First version of documentation
|
31
|
+
- Basic tests
|
32
|
+
|
33
|
+
- `BasinMetrics` class to compute basin metris such as basin entropy and boundary dimension.
|
34
|
+
|
35
|
+
- `TimeSeriesMetrics` class to compute metrics related to time series analysis.
|
36
|
+
|
37
|
+
- `PlotStyler` utility class to globally configure and apply consistent styling for Matplotlib plots.
|
38
|
+
|
39
|
+
---
|
40
|
+
|
41
|
+
<!-- Dummy heading to avoid ending on a transition -->
|
42
|
+
|
43
|
+
##
|
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: pynamicalsys
|
3
|
-
Version: 1.
|
4
|
-
Summary: A Python toolkit for
|
3
|
+
Version: 1.2.1
|
4
|
+
Summary: A Python toolkit for the analysis of dynamical systems
|
5
5
|
Author-email: Matheus Rolim Sales <rolim.sales.m@gmail.com>
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
7
7
|
Version 3, 29 June 2007
|
@@ -757,7 +757,19 @@ $ pip install --upgrade pip build
|
|
757
757
|
|
758
758
|
## Citation
|
759
759
|
|
760
|
-
Currently, our research paper is under review, but in the mean time, if you use **pynamicalsys** in your work, you can cite the arXiv version:
|
760
|
+
Currently, our research paper is under review, but in the mean time, if you use **pynamicalsys** in your work, you can cite the [arXiv](https://arxiv.org/abs/2506.14044) version:
|
761
|
+
|
762
|
+
```bibtex
|
763
|
+
@misc{pynamicalsys,
|
764
|
+
title={pynamicalsys: A Python toolkit for the analysis of dynamical systems},
|
765
|
+
author={Matheus Rolim Sales and Leonardo Costa de Souza and Daniel Borin and Michele Mugnaine and José Danilo Szezech Jr. and Ricardo Luiz Viana and Iberê Luiz Caldas and Edson Denis Leonel and Chris G. Antonopoulos},
|
766
|
+
year={2025},
|
767
|
+
eprint={2506.14044},
|
768
|
+
archivePrefix={arXiv},
|
769
|
+
primaryClass={nlin.CD},
|
770
|
+
url={https://arxiv.org/abs/2506.14044},
|
771
|
+
}
|
772
|
+
```
|
761
773
|
|
762
774
|
## Contributing
|
763
775
|
|
@@ -65,7 +65,19 @@ $ pip install --upgrade pip build
|
|
65
65
|
|
66
66
|
## Citation
|
67
67
|
|
68
|
-
Currently, our research paper is under review, but in the mean time, if you use **pynamicalsys** in your work, you can cite the arXiv version:
|
68
|
+
Currently, our research paper is under review, but in the mean time, if you use **pynamicalsys** in your work, you can cite the [arXiv](https://arxiv.org/abs/2506.14044) version:
|
69
|
+
|
70
|
+
```bibtex
|
71
|
+
@misc{pynamicalsys,
|
72
|
+
title={pynamicalsys: A Python toolkit for the analysis of dynamical systems},
|
73
|
+
author={Matheus Rolim Sales and Leonardo Costa de Souza and Daniel Borin and Michele Mugnaine and José Danilo Szezech Jr. and Ricardo Luiz Viana and Iberê Luiz Caldas and Edson Denis Leonel and Chris G. Antonopoulos},
|
74
|
+
year={2025},
|
75
|
+
eprint={2506.14044},
|
76
|
+
archivePrefix={arXiv},
|
77
|
+
primaryClass={nlin.CD},
|
78
|
+
url={https://arxiv.org/abs/2506.14044},
|
79
|
+
}
|
80
|
+
```
|
69
81
|
|
70
82
|
## Contributing
|
71
83
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
[project]
|
2
2
|
dynamic = ["version"]
|
3
3
|
name = "pynamicalsys"
|
4
|
-
description = "A Python toolkit for
|
4
|
+
description = "A Python toolkit for the analysis of dynamical systems"
|
5
5
|
authors = [{ name = "Matheus Rolim Sales", email = "rolim.sales.m@gmail.com" }]
|
6
6
|
readme = "README.md"
|
7
7
|
license = { file = "LICENSE" }
|
@@ -16,9 +16,16 @@
|
|
16
16
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
17
17
|
|
18
18
|
from pynamicalsys.core.discrete_dynamical_systems import DiscreteDynamicalSystem
|
19
|
+
from pynamicalsys.core.continuous_dynamical_systems import ContinuousDynamicalSystem
|
19
20
|
from pynamicalsys.core.basin_metrics import BasinMetrics
|
20
21
|
from pynamicalsys.core.plot_styler import PlotStyler
|
21
22
|
from pynamicalsys.core.time_series_metrics import TimeSeriesMetrics
|
22
23
|
from .__version__ import __version__
|
23
24
|
|
24
|
-
__all__ = [
|
25
|
+
__all__ = [
|
26
|
+
"DiscreteDynamicalSystem",
|
27
|
+
"ContinuousDynamicalSystem",
|
28
|
+
"PlotStyler",
|
29
|
+
"TimeSeriesMetrics",
|
30
|
+
"BasinMetrics",
|
31
|
+
]
|
@@ -0,0 +1,347 @@
|
|
1
|
+
# chaotic_indicators.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, Union, Tuple, Dict, List, Any, Sequence
|
19
|
+
from numpy.typing import NDArray
|
20
|
+
import numpy as np
|
21
|
+
from numba import njit, prange
|
22
|
+
|
23
|
+
from pynamicalsys.common.utils import qr
|
24
|
+
from pynamicalsys.continuous_time.trajectory_analysis import evolve_system
|
25
|
+
from pynamicalsys.continuous_time.numerical_integrators import rk4_step_wrapped
|
26
|
+
|
27
|
+
|
28
|
+
@njit(cache=True)
|
29
|
+
def lyapunov_exponents(
|
30
|
+
u: NDArray[np.float64],
|
31
|
+
parameters: NDArray[np.float64],
|
32
|
+
total_time: float,
|
33
|
+
equations_of_motion: Callable[
|
34
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
35
|
+
],
|
36
|
+
jacobian: Callable[
|
37
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
38
|
+
],
|
39
|
+
transient_time: Optional[float] = None,
|
40
|
+
time_step: float = 0.01,
|
41
|
+
atol: float = 1e-6,
|
42
|
+
rtol: float = 1e-3,
|
43
|
+
integrator=rk4_step_wrapped,
|
44
|
+
return_history: bool = False,
|
45
|
+
seed: int = 13,
|
46
|
+
log_base: float = np.e,
|
47
|
+
QR: Callable[
|
48
|
+
[NDArray[np.float64]], Tuple[NDArray[np.float64], NDArray[np.float64]]
|
49
|
+
] = qr,
|
50
|
+
) -> NDArray[np.float64]:
|
51
|
+
|
52
|
+
neq = len(u) # Number of equations of the system
|
53
|
+
nt = neq + neq**2 # Total number of equations including variational equations
|
54
|
+
|
55
|
+
u = u.copy()
|
56
|
+
|
57
|
+
# Handle transient time
|
58
|
+
if transient_time is not None:
|
59
|
+
u = evolve_system(
|
60
|
+
u,
|
61
|
+
parameters,
|
62
|
+
transient_time,
|
63
|
+
equations_of_motion,
|
64
|
+
time_step=time_step,
|
65
|
+
atol=atol,
|
66
|
+
rtol=rtol,
|
67
|
+
integrator=integrator,
|
68
|
+
)
|
69
|
+
sample_time = total_time - transient_time
|
70
|
+
time = transient_time
|
71
|
+
else:
|
72
|
+
sample_time = total_time
|
73
|
+
time = 0
|
74
|
+
|
75
|
+
# State + deviation vectors
|
76
|
+
uv = np.zeros(nt)
|
77
|
+
uv[:neq] = u.copy()
|
78
|
+
|
79
|
+
# Randomly define the deviation vectors and orthonormalize them
|
80
|
+
np.random.seed(seed)
|
81
|
+
uv[neq:] = -1 + 2 * np.random.rand(nt - neq)
|
82
|
+
v = uv[neq:].reshape(neq, neq)
|
83
|
+
v, _ = QR(v)
|
84
|
+
uv[neq:] = v.reshape(neq**2)
|
85
|
+
|
86
|
+
exponents = np.zeros(neq, dtype=np.float64)
|
87
|
+
history = []
|
88
|
+
|
89
|
+
while time < total_time:
|
90
|
+
if time + time_step > total_time:
|
91
|
+
time_step = total_time - time
|
92
|
+
|
93
|
+
uv_new, time_new, time_step_new, accept = integrator(
|
94
|
+
time,
|
95
|
+
uv,
|
96
|
+
parameters,
|
97
|
+
equations_of_motion,
|
98
|
+
jacobian=jacobian,
|
99
|
+
time_step=time_step,
|
100
|
+
)
|
101
|
+
|
102
|
+
if accept:
|
103
|
+
time = time_new
|
104
|
+
uv = uv_new.copy()
|
105
|
+
# Reshape the deviation vectors into a neq x neq matrix
|
106
|
+
v = uv[neq:].reshape(neq, neq).copy()
|
107
|
+
|
108
|
+
# Perform the QR decomposition
|
109
|
+
v, R = QR(v)
|
110
|
+
|
111
|
+
# Accumulate the log
|
112
|
+
exponents += np.log(np.abs(np.diag(R))) / np.log(log_base)
|
113
|
+
|
114
|
+
if return_history:
|
115
|
+
result = [time]
|
116
|
+
for i in range(neq):
|
117
|
+
result.append(
|
118
|
+
exponents[i]
|
119
|
+
/ (time - (transient_time if transient_time is not None else 0))
|
120
|
+
)
|
121
|
+
history.append(result)
|
122
|
+
|
123
|
+
# Reshape v back to uv
|
124
|
+
uv[neq:] = v.reshape(neq**2)
|
125
|
+
|
126
|
+
time_step = time_step_new
|
127
|
+
|
128
|
+
if return_history:
|
129
|
+
return history
|
130
|
+
else:
|
131
|
+
result = []
|
132
|
+
for i in range(neq):
|
133
|
+
result.append(
|
134
|
+
exponents[i]
|
135
|
+
/ (time - (transient_time if transient_time is not None else 0))
|
136
|
+
)
|
137
|
+
return [result]
|
138
|
+
|
139
|
+
|
140
|
+
@njit(cache=True)
|
141
|
+
def SALI(
|
142
|
+
u: NDArray[np.float64],
|
143
|
+
parameters: NDArray[np.float64],
|
144
|
+
total_time: float,
|
145
|
+
equations_of_motion: Callable[
|
146
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
147
|
+
],
|
148
|
+
jacobian: Callable[
|
149
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
150
|
+
],
|
151
|
+
transient_time: Optional[float] = None,
|
152
|
+
time_step: float = 0.01,
|
153
|
+
atol: float = 1e-6,
|
154
|
+
rtol: float = 1e-3,
|
155
|
+
integrator=rk4_step_wrapped,
|
156
|
+
return_history: bool = False,
|
157
|
+
seed: int = 13,
|
158
|
+
threshold: float = 1e-16,
|
159
|
+
) -> NDArray[np.float64]:
|
160
|
+
|
161
|
+
neq = len(u) # Number of equations of the system
|
162
|
+
ndv = 2 # Number of deviation vectors
|
163
|
+
nt = neq + neq * ndv # Total number of equations including variational equations
|
164
|
+
|
165
|
+
u = u.copy()
|
166
|
+
|
167
|
+
# Handle transient time
|
168
|
+
if transient_time is not None:
|
169
|
+
u = evolve_system(
|
170
|
+
u,
|
171
|
+
parameters,
|
172
|
+
transient_time,
|
173
|
+
equations_of_motion,
|
174
|
+
time_step=time_step,
|
175
|
+
atol=atol,
|
176
|
+
rtol=rtol,
|
177
|
+
integrator=integrator,
|
178
|
+
)
|
179
|
+
time = transient_time
|
180
|
+
else:
|
181
|
+
time = 0
|
182
|
+
|
183
|
+
# State + deviation vectors
|
184
|
+
uv = np.zeros(nt)
|
185
|
+
uv[:neq] = u.copy()
|
186
|
+
|
187
|
+
# Randomly define the deviation vectors and orthonormalize them
|
188
|
+
np.random.seed(seed)
|
189
|
+
uv[neq:] = -1 + 2 * np.random.rand(nt - neq)
|
190
|
+
v = uv[neq:].reshape(neq, ndv)
|
191
|
+
v, _ = qr(v)
|
192
|
+
uv[neq:] = v.reshape(neq * ndv)
|
193
|
+
|
194
|
+
history = []
|
195
|
+
|
196
|
+
while time < total_time:
|
197
|
+
if time + time_step > total_time:
|
198
|
+
time_step = total_time - time
|
199
|
+
|
200
|
+
uv_new, time_new, time_step_new, accept = integrator(
|
201
|
+
time,
|
202
|
+
uv,
|
203
|
+
parameters,
|
204
|
+
equations_of_motion,
|
205
|
+
jacobian=jacobian,
|
206
|
+
time_step=time_step,
|
207
|
+
number_of_deviation_vectors=ndv,
|
208
|
+
)
|
209
|
+
|
210
|
+
if accept:
|
211
|
+
time = time_new
|
212
|
+
uv = uv_new.copy()
|
213
|
+
|
214
|
+
# Reshape the deviation vectors into a neq x ndv matrix
|
215
|
+
v = uv[neq:].reshape(neq, ndv)
|
216
|
+
|
217
|
+
# Normalize the deviation vectors
|
218
|
+
v[:, 0] /= np.linalg.norm(v[:, 0])
|
219
|
+
v[:, 1] /= np.linalg.norm(v[:, 1])
|
220
|
+
|
221
|
+
# Calculate the aligment indexes and SALI
|
222
|
+
PAI = np.linalg.norm(v[:, 0] + v[:, 1])
|
223
|
+
AAI = np.linalg.norm(v[:, 0] - v[:, 1])
|
224
|
+
sali = min(PAI, AAI)
|
225
|
+
|
226
|
+
if return_history:
|
227
|
+
result = [time, sali]
|
228
|
+
history.append(result)
|
229
|
+
|
230
|
+
# Early termination
|
231
|
+
if sali <= threshold:
|
232
|
+
break
|
233
|
+
|
234
|
+
# Reshape v back to uv
|
235
|
+
uv[neq:] = v.reshape(neq * ndv)
|
236
|
+
|
237
|
+
time_step = time_step_new
|
238
|
+
|
239
|
+
if return_history:
|
240
|
+
return history
|
241
|
+
else:
|
242
|
+
return [[time, sali]]
|
243
|
+
|
244
|
+
|
245
|
+
# @njit(cache=True)
|
246
|
+
def LDI(
|
247
|
+
u: NDArray[np.float64],
|
248
|
+
parameters: NDArray[np.float64],
|
249
|
+
total_time: float,
|
250
|
+
equations_of_motion: Callable[
|
251
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
252
|
+
],
|
253
|
+
jacobian: Callable[
|
254
|
+
[np.float64, NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]
|
255
|
+
],
|
256
|
+
number_deviation_vectors: int,
|
257
|
+
transient_time: Optional[float] = None,
|
258
|
+
time_step: float = 0.01,
|
259
|
+
atol: float = 1e-6,
|
260
|
+
rtol: float = 1e-3,
|
261
|
+
integrator=rk4_step_wrapped,
|
262
|
+
return_history: bool = False,
|
263
|
+
seed: int = 13,
|
264
|
+
threshold: float = 1e-16,
|
265
|
+
) -> NDArray[np.float64]:
|
266
|
+
|
267
|
+
neq = len(u) # Number of equations of the system
|
268
|
+
ndv = number_deviation_vectors # Number of deviation vectors
|
269
|
+
nt = neq + neq * ndv # Total number of equations including variational equations
|
270
|
+
|
271
|
+
u = u.copy()
|
272
|
+
|
273
|
+
# Handle transient time
|
274
|
+
if transient_time is not None:
|
275
|
+
u = evolve_system(
|
276
|
+
u,
|
277
|
+
parameters,
|
278
|
+
transient_time,
|
279
|
+
equations_of_motion,
|
280
|
+
time_step=time_step,
|
281
|
+
atol=atol,
|
282
|
+
rtol=rtol,
|
283
|
+
integrator=integrator,
|
284
|
+
)
|
285
|
+
time = transient_time
|
286
|
+
else:
|
287
|
+
time = 0
|
288
|
+
|
289
|
+
# State + deviation vectors
|
290
|
+
uv = np.zeros(nt)
|
291
|
+
uv[:neq] = u.copy()
|
292
|
+
|
293
|
+
# Randomly define the deviation vectors and orthonormalize them
|
294
|
+
np.random.seed(seed)
|
295
|
+
uv[neq:] = -1 + 2 * np.random.rand(nt - neq)
|
296
|
+
v = uv[neq:].reshape(neq, ndv)
|
297
|
+
v, _ = qr(v)
|
298
|
+
uv[neq:] = v.reshape(neq * ndv)
|
299
|
+
|
300
|
+
history = []
|
301
|
+
|
302
|
+
while time < total_time:
|
303
|
+
if time + time_step > total_time:
|
304
|
+
time_step = total_time - time
|
305
|
+
|
306
|
+
uv_new, time_new, time_step_new, accept = integrator(
|
307
|
+
time,
|
308
|
+
uv,
|
309
|
+
parameters,
|
310
|
+
equations_of_motion,
|
311
|
+
jacobian=jacobian,
|
312
|
+
time_step=time_step,
|
313
|
+
number_of_deviation_vectors=ndv,
|
314
|
+
)
|
315
|
+
|
316
|
+
if accept:
|
317
|
+
time = time_new
|
318
|
+
uv = uv_new.copy()
|
319
|
+
|
320
|
+
# Reshape the deviation vectors into a neq x ndv matrix
|
321
|
+
v = uv[neq:].reshape(neq, ndv)
|
322
|
+
|
323
|
+
# Normalize the deviation vectors
|
324
|
+
for i in range(ndv):
|
325
|
+
v[:, i] /= np.linalg.norm(v[:, i])
|
326
|
+
|
327
|
+
# Calculate the singular values
|
328
|
+
S = np.linalg.svd(v, full_matrices=False, compute_uv=False)
|
329
|
+
ldi = np.prod(S)
|
330
|
+
|
331
|
+
if return_history:
|
332
|
+
result = [time, ldi]
|
333
|
+
history.append(result)
|
334
|
+
|
335
|
+
# Early termination
|
336
|
+
if ldi <= threshold:
|
337
|
+
break
|
338
|
+
|
339
|
+
# Reshape v back to uv
|
340
|
+
uv[neq:] = v.reshape(neq * ndv)
|
341
|
+
|
342
|
+
time_step = time_step_new
|
343
|
+
|
344
|
+
if return_history:
|
345
|
+
return history
|
346
|
+
else:
|
347
|
+
return [[time, ldi]]
|