Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/get ready for v002 #4

Merged
merged 5 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

<!--next-version-placeholder-->

### v0.0.2 (21.10.2023)
- Added more Systems.
- Added new Lyapunov spectrum measure.
- Added new simulation backend.
- Added example folder.
- Added static folder to generate animation in readme.
- Changed plotly dependency to matplotlib.
- More smaller improvements...

### v0.0.1 (16.03.2023)
- First release of `lorenzpy`

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ $ pip install lorenzpy
```

To install with the additional plotting functionality.
This also installs `plotly` and `matplotlib`. ⚠️ Plotting functionality not in a useful
state.
This also installs `matplotlib`. ⚠️ Plotting functionality not in a useful state.
```bash
$ pip install lorenzpy[plot]
```
Expand Down Expand Up @@ -59,6 +58,8 @@ lle = lpy.measures.largest_lyapunov_exponent(
The calculated largest Lyapunov exponent of *0.9051...* is very close to the literature
value of *0.9056*[^SprottChaos].

For more examples see the [examples folder](examples/README.md).

## 💫 Supported systems


Expand Down
1 change: 1 addition & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

### simulations module:
::: lorenzpy.simulations
::: lorenzpy.simulations.solvers

### measures module:
::: lorenzpy.measures
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Examples:

### Contents
- ``double_pendulum_lyapunov_spectrum.py``: Plot the lyapunov spectrum of the double pendulum.
- ``lyapunov_spectra_of_3d_autonomous_flows.py``: Plot the Lyapunov spectrum of all 3D autonomous dissipative flow systems.

⚠️ Not many examples here yet, and examples might be flawed.

68 changes: 68 additions & 0 deletions examples/double_pendulum_lyapunov_spectrum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Lyapunov Spectrum of single system."""

import matplotlib.pyplot as plt
import numpy as np

from lorenzpy import measures as meas
from lorenzpy import simulations as sims

sys_obj = sims.DoublePendulum(dt=0.1)
dt = sys_obj.dt

# Calculate exponents:
m = 4
deviation_scale = 1e-10
steps = 1000
part_time_steps = 15
steps_skip = 0

iterator_func = sys_obj.iterate
starting_point = sys_obj.get_default_starting_pnt()

le_spectrum = meas.lyapunov_exponent_spectrum(
iterator_func=iterator_func,
starting_point=starting_point,
deviation_scale=deviation_scale,
steps=steps,
part_time_steps=part_time_steps,
steps_skip=steps_skip,
dt=dt,
m=m,
initial_pert_directions=None,
return_convergence=True,
)

fig, ax = plt.subplots(
1, 1, figsize=(6, 6), layout="constrained", sharex=True, sharey=False
)

# x and y titles:
fig.supxlabel("Number of renormalization steps")
fig.supylabel("Lyapunov exponent convergence")

x = np.arange(1, steps + 1)
ax.plot(
x,
le_spectrum,
linewidth=1,
)
ax.grid(True)

final_les = np.round(le_spectrum[-1, :], 4).tolist()
final_les = [str(x) for x in final_les]
le_string = "\n".join(final_les)
le_string = "Final LEs: \n" + le_string
x_position = 0.1 # X-coordinate of the upper-left corner for each subplot
y_position = 0.5
ax.text(
x_position,
y_position,
le_string,
fontsize=10,
bbox=dict(facecolor="white", edgecolor="black", boxstyle="round"),
verticalalignment="center",
horizontalalignment="left",
transform=ax.transAxes,
)

plt.show()
91 changes: 91 additions & 0 deletions examples/lyapunov_spectra_of_3d_autonomous_flows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Calculate and plot the Lyapunov Spectra of all three-dimensional chaotic flows."""

import matplotlib.pyplot as plt
import numpy as np

from lorenzpy import measures as meas
from lorenzpy import simulations as sims

systems = [
"Lorenz63",
"Chen",
"ChuaCircuit",
"ComplexButterfly",
"DoubleScroll",
"Halvorsen",
"Roessler",
"Rucklidge",
"Thomas",
"WindmiAttractor",
]

# Calculate exponents:
m = 3
deviation_scale = 1e-10
steps = 1000
part_time_steps = 15
steps_skip = 50

solver = "rk4"
# solver = sims.solvers.create_scipy_ivp_solver(method="RK45")

lyap_dict = {}
for i_sys, system in enumerate(systems):
print(system)
sys_obj = getattr(sims, system)(solver=solver)
iterator_func = sys_obj.iterate
starting_point = sys_obj.get_default_starting_pnt()
dt = sys_obj.dt

lyap_dict[system] = meas.lyapunov_exponent_spectrum(
iterator_func=iterator_func,
starting_point=starting_point,
deviation_scale=deviation_scale,
steps=steps,
part_time_steps=part_time_steps,
steps_skip=steps_skip,
dt=dt,
m=m,
initial_pert_directions=None,
return_convergence=True,
)

fig, axs = plt.subplots(
2, 5, figsize=(15, 8), layout="constrained", sharex=True, sharey=False
)

# x and y titles:
fig.supxlabel("Number of renormalization steps")
fig.supylabel("Lyapunov exponent convergence")

axs = axs.flatten()
x = np.arange(1, steps + 1)
for i_ax, ax in enumerate(axs):
system = systems[i_ax]
le_spectrum = lyap_dict[system]
ax.title.set_text(system)
ax.plot(
x,
le_spectrum,
linewidth=1,
)
ax.grid(True)

final_les = np.round(le_spectrum[-1, :], 4).tolist()
final_les = [str(x) for x in final_les]
le_string = "\n".join(final_les)
le_string = "Final LEs: \n" + le_string
x_position = 0.1 # X-coordinate of the upper-left corner for each subplot
y_position = 0.5
ax.text(
x_position,
y_position,
le_string,
fontsize=10,
bbox=dict(facecolor="white", edgecolor="black", boxstyle="round"),
verticalalignment="center",
horizontalalignment="left",
transform=ax.transAxes,
)

plt.show()
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "lorenzpy"
readme = "README.md"
version = "0.0.1"
version = "0.0.2"
description = "A Python package to simulate and measure chaotic dynamical systems."
authors = [
{name = "Dennis Duncan", email = "[email protected]"},
Expand Down Expand Up @@ -41,7 +41,6 @@ dev = [
"pre-commit==3.1.1", # add version?
]
plot = [
"plotly>=5.11",
"matplotlib>=3.5"
]

Expand All @@ -59,7 +58,7 @@ line-length = 88
files = "src/lorenzpy/"

[[tool.mypy.overrides]]
module = ['plotly.*', 'numpy', 'pytest', "scipy.*", "matplotlib.*", "PIL"] # ignore missing imports from the plotly package.
module = ['numpy', 'pytest', "scipy.*", "matplotlib.*", "PIL", "mpl_toolkits.*"]
ignore_missing_imports = true

[tool.ruff]
Expand Down
2 changes: 1 addition & 1 deletion src/lorenzpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@

from . import measures, simulations

__version__ = "0.0.1"
__version__ = "0.0.2"
1 change: 1 addition & 0 deletions src/lorenzpy/plot/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
"""Submodule to plot chaotic dynamics systems."""
from .plot import create_3d_line_plot
39 changes: 39 additions & 0 deletions src/lorenzpy/plot/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Plot the data of dynamical systems."""

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D


def create_3d_line_plot(data: np.ndarray, ax: "Axes3D" = None, **kwargs) -> "Axes3D":
"""Create a three-dimensional line plot of data.

Args:
data (np.ndarray): A NumPy array containing 3D data points with shape (n, 3).
ax (Axes3D, optional): A Matplotlib 3D axis to use for plotting.
If not provided, a new 3D plot will be created.
**kwargs: Additional keyword arguments to pass to ax.plot.

Returns:
Axes3D: The Matplotlib 3D axis used for the plot.

Example:
>>> data = np.random.rand(100, 3) # Replace this with your own 3D data
>>> create_3d_line_plot(data, color='b', linestyle='--')
>>> plt.show()
"""
if ax is None:
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")

x = data[:, 0]
y = data[:, 1]
z = data[:, 2]

ax.plot(x, y, z, **kwargs) # Use plot for a line plot with kwargs

ax.set_xlabel("X Axis")
ax.set_ylabel("Y Axis")
ax.set_zlabel("Z Axis")

return ax
17 changes: 0 additions & 17 deletions src/lorenzpy/plot/plot_3d.py

This file was deleted.

18 changes: 1 addition & 17 deletions src/lorenzpy/simulations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,8 @@
>>> data = sims.Lorenz63().simulate(1000)
>>> data.shape
(1000, 3)


TODO <below>
- Probably for each concrete simulation class + public methods. Compare with sklearn
- Find out which functionality is missing. E.g. Raising error when wrong values are
parsed.
- Check where to add proper tests and how to add them efficiently. Fixtures?
Parametrization?
- Implement all the other dynamical systems.
- Check if the names of files and functions make sense?
- Add functionality to add your own dynamical system? As my base-classes are
protected this is maybe not so easy? -> Make ABC public?
- Think about adding NARMA? Maybe I need a random number generator framework.
- Check if I can further reduce code duplication. Maybe regarding solvers.
- Check for proper doc-generation. It seems that the methods of inhereted members
is not implemented yet. See:
https://github.com/mkdocstrings/mkdocstrings/issues/78
"""

from . import solvers
from .autonomous_flows import (
Chen,
Expand Down
2 changes: 1 addition & 1 deletion src/lorenzpy/simulations/autonomous_flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class ComplexButterfly(_BaseSimFlow):
def __init__(
self,
a: float = 0.55,
dt: float = 0.05,
dt: float = 0.1,
solver: str | str | Callable[[Callable, float, np.ndarray], np.ndarray] = "rk4",
):
"""Initialize the ComplexButterfly simulation object.
Expand Down
5 changes: 2 additions & 3 deletions src/lorenzpy/simulations/others.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,12 @@ def simulate(
if starting_point.size == self.history_steps + 1:
initial_history = starting_point
elif starting_point.size == 1:
initial_history = np.repeat(starting_point, self.history_steps)
initial_history = np.repeat(starting_point, self.history_steps + 1)
else:
raise ValueError("Wrong size of starting point.")

traj_w_hist = np.zeros((self.history_steps + time_steps, 1))
traj_w_hist[: self.history_steps, :] = initial_history[:, np.newaxis]
traj_w_hist[self.history_steps, :] = starting_point
traj_w_hist[: self.history_steps + 1, :] = initial_history[:, np.newaxis]

for t in range(1, time_steps + transient):
t_shifted = t + self.history_steps
Expand Down
Binary file modified static/attractor_animation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading