Skip to content

Commit

Permalink
Merge branch 'kapitanov/add_draw_2d'
Browse files Browse the repository at this point in the history
  • Loading branch information
hukenovs committed Aug 14, 2020
2 parents a5becc6 + 64fdffb commit 70c5e45
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 172 deletions.
100 changes: 91 additions & 9 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,30 +36,112 @@ Project requires:
- Pytest (>= 5.4.3)
- Pre-commit (>= 2.6.0)

Chaotic model (examples)
~~~~~~~~~~~~~~~~~~~~~~~~

Lorenz attractor::

dx/dt = sigma * (y - x)
dy/dt = rho * (x - z) - y
dz/dt = x * y - beta * z

where sigma = 10, rho = 28 and beta = 8/3.

.. image:: https://raw.githubusercontent.com/capitanov/chaospy/master/img/Lorenz_3d.gif?sanitize=true

Rossler attractor::

dx/dt = sigma * (y - x)
dy/dt = rho * (x - z) - y
dz/dt = x * y - beta * z

where a = 0.2, b = 0.2 and c = 5.7.

.. image:: https://raw.githubusercontent.com/capitanov/chaospy/master/img/Rossler_3D.png?sanitize=true

Source code
~~~~~~~~~~~

You can check the latest sources with the command::

git clone https://github.com/capitanov/chaospy.git
$ git clone https://github.com/capitanov/chaospy.git
$ cd chaospy
$ pip install -r requirements.txt

Chaotic model
~~~~~~~~~~~~~~
Help
~~~~

::

usage: parser.py [-h] [-p POINTS] [-s STEP]
[--init_point INIT_POINT [INIT_POINT ...]] [--show_plots]
[--save_plots] [--add_2d_gif]
{lorenz,rossler,rikitake,chua,duffing,wang,nose-hoover,lotka-volterra}
...

Specify command line arguments for dynamic system.Calculate some math
parameters and plot some graphs of a given chaotic system.

optional arguments:
-h, --help show this help message and exit
-p POINTS, --points POINTS
Number of points for dymanic system. Default: 1024.
-s STEP, --step STEP Step size for calculating the next coordinates of
chaotic system. Default: 100.
--init_point INIT_POINT [INIT_POINT ...]
Initial point as string of three floats: "X, Y, Z".
--show_plots Show plots of a model. Default: False.
--save_plots Save plots to PNG files. Default: False.
--add_2d_gif Add 2D coordinates to 3D model into GIF. Default:
False.

Chaotic models:
You can select one of the chaotic models:

{lorenz,rossler,rikitake,chua,duffing,wang,nose-hoover,lotka-volterra}
lorenz Lorenz chaotic model
rossler Rossler chaotic model
rikitake Rikitake chaotic model
chua Chua chaotic model
duffing Duffing chaotic model
wang Wang chaotic model
nose-hoover Nose-hoover chaotic model
lotka-volterra Lotka-volterra chaotic model

Chaotic attractors are used as subparse command. Example:

Lorenz attractor
****************
::

usage: parser.py lorenz [-h] [--sigma SIGMA] [--beta BETA] [--rho RHO]

optional arguments:
-h, --help show this help message and exit

Lorenz model arguments:
--sigma SIGMA Lorenz system parameter. Default: 10
--beta BETA Lorenz system parameter. Default: 2.6666666666666665
--rho RHO Lorenz system parameter. Default: 28

Chua circuit
************
::

dx/dt = sigma * (y - x)
dy/dt = rho * (x - z) - y
dz/dt = x * y - beta * z
usage: parser.py chua [-h] [--alpha ALPHA] [--beta BETA] [--mu0 MU0]
[--mu1 MU1]

where sigma= 10, rho= 28 and beta= 8/3.
optional arguments:
-h, --help show this help message and exit

.. image:: https://raw.githubusercontent.com/capitanov/chaospy/master/img/Lorenz_3d.gif?sanitize=true
Chua model arguments:
--alpha ALPHA Chua system parameter. Default: 0.1
--beta BETA Chua system parameter. Default: 28
--mu0 MU0 Chua system parameter. Default: -1.143
--mu1 MU1 Chua system parameter. Default: -0.714

See Also
~~~~~~~~

- `Wikipedia -> chaotic attractors. <https://en.wikipedia.org/wiki/Attractor>`__
- `My articles on habrahabr. (russian language) <https://habr.com/users/capitanov/topics/>`__
- `My articles on habrahabr. (rus lang.) <https://habr.com/users/capitanov/topics/>`__
22 changes: 13 additions & 9 deletions src/dynamic_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ def __init__(self, input_args: Optional[tuple] = None, show_log: bool = False):
# Initialize attributes
self.initialize(input_args, show_log)

def initialize(self, input_args: Optional[tuple] = None, show_log: bool = False):
def initialize(self, input_args: Optional[tuple] = None, show_logs: bool = False):
# Update parameters
self.settings = Settings(show_log=show_log)
self.settings.update_params(input_args, show_log)
self.settings = Settings(show_logs=show_logs)
self.settings.update_params(input_args)

# Update chaotic model
self.model = self.settings.model

# Update drawer for plots
self.drawer = PlotDrawer(save_plots=self.settings.save_plots, show_plots=self.settings.show_plots)
self.drawer = PlotDrawer(self.settings.save_plots, self.settings.show_plots, self.settings.add_2d_gif)
self.drawer.model_name = self.settings.attractor.capitalize()

# Update main calculator
Expand All @@ -118,7 +118,7 @@ def run(self):

# Calculate
stats = self.collect_statistics(_points)
if self.settings.show_log:
if self.settings.show_logs:
print(f"[INFO]: Show statistics:\n{stats}\n")

self.calculator.check_probability(_points)
Expand All @@ -130,18 +130,22 @@ def run(self):

# self.drawer.show_time_plots()
# self.drawer.show_3d_plots()
self.drawer.make_3d_plot_gif(5)
self.drawer.make_3d_plot_gif(50)
# self.drawer.show_all_plots()


if __name__ == "__main__":
command_line = (
"--init_point",
"1 -1 2",
"--points",
"500",
"2000",
"--step",
"100",
"50",
"--save_plots",
"lorenz",
# "--show_plots",
"--add_2d_gif",
"rossler",
)

dynamic_system = DynamicSystem(input_args=command_line, show_log=True)
Expand Down
96 changes: 66 additions & 30 deletions src/utils/drawer.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,23 @@ class PlotDrawer:
"""

def __init__(self, save_plots: bool = False, show_plots: bool = False, model_name: str = None):
_plot_axis = ((1, 0), (1, 2), (2, 0))
_plot_labels = {0: "X", 1: "Y", 2: "Z"}

def __init__(
self, save_plots: bool = False, show_plots: bool = False, add_2d_gif: bool = False, model_name: str = None,
):
self.save_plots = save_plots
self.show_plots = show_plots
self.add_2d_gif = add_2d_gif
self.time_or_dot = False

self._model_name = model_name
self._coordinates = None
# Internal parameters
self._color_map = None
self._plot_list = None
self._min_max_axis = None

def __len__(self):
return len(self.coordinates)
Expand All @@ -83,7 +94,7 @@ def coordinates(self, value: np.ndarray):
self._coordinates = value

@property
@lru_cache(maxsize=8)
@lru_cache(maxsize=16)
def min_max_axis(self):
return np.vstack([np.min(self.coordinates, axis=0), np.max(self.coordinates, axis=0)]).T

Expand All @@ -104,17 +115,25 @@ def show_time_plots(self):
if self.show_plots:
plt.show()

def axis_defaults(self, ax):
ax.set_title(f"{self.model_name} attractor")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_xlim3d(self.min_max_axis[0])
ax.set_ylim3d(self.min_max_axis[1])
ax.set_zlim3d(self.min_max_axis[2])
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
def _axis_defaults_3d(self, plots):
plots.set_title(f"{self.model_name} attractor")
plots.set_xlabel("X")
plots.set_ylabel("Y")
plots.set_zlabel("Z")
plots.set_xlim3d(self.min_max_axis[0])
plots.set_ylim3d(self.min_max_axis[1])
plots.set_zlim3d(self.min_max_axis[2])
plots.xaxis.pane.fill = False
plots.yaxis.pane.fill = False
plots.zaxis.pane.fill = False

def _axis_defaults_2d(self, plots):
for idx, (xx, yy) in enumerate(self._plot_axis):
plots[idx].set_xlim(self.min_max_axis[xx])
plots[idx].set_ylim(self.min_max_axis[yy])
plots[idx].set_xlabel(self._plot_labels[xx])
plots[idx].set_ylabel(self._plot_labels[yy])
plots[idx].grid(True)
# ax.set_axis_off()
# ax.xaxis.pane.set_edgecolor('w')
# ax.yaxis.pane.set_edgecolor('w')
Expand All @@ -124,54 +143,70 @@ def axis_defaults(self, ax):

def show_3d_plots(self):
"""Plot 3D coordinates as time series."""
plot_axis = ((1, 0), (1, 2), (2, 0))
plot_labels = {0: "X", 1: "Y", 2: "Z"}

fig = plt.figure(f"3D model of {self.model_name} system", figsize=(8, 6), dpi=100)
for ii, (xx, yy) in enumerate(plot_axis):
for ii, (xx, yy) in enumerate(self._plot_axis):
plt.subplot(2, 2, 1 + ii)
plt.plot(self.coordinates[:, xx], self.coordinates[:, yy], linewidth=0.75)
plt.grid()
plt.xlabel(plot_labels[xx])
plt.ylabel(plot_labels[yy])
plt.xlabel(self._plot_labels[xx])
plt.ylabel(self._plot_labels[yy])
# TODO: 2020/07/26: Set limits!
plt.xlim(self.min_max_axis[xx])
plt.ylim(self.min_max_axis[yy])

ax = fig.add_subplot(2, 2, 4, projection="3d")
ax.plot(self.coordinates[:, 0], self.coordinates[:, 1], self.coordinates[:, 2], linewidth=0.7)
self.axis_defaults(ax)
self._axis_defaults_3d(ax)
plt.tight_layout()

if self.save_plots:
plt.savefig(f"{self.model_name}_3d_coordinates.png")
if self.show_plots:
plt.show()

def _add_2d_to_plots(self, figure):
self._plot_list += [figure.add_subplot(2, 2, 1 + ii) for ii in range(3)]
self._axis_defaults_2d(self._plot_list[1:])
plt.tight_layout()

def make_3d_plot_gif(self, step_size: int = 10):
"""Make git for 3D coordinates as time series."""

nodes = 2 if self.add_2d_gif else 1
posit = 4 if self.add_2d_gif else 1
fig = plt.figure(f"3D model of {self.model_name} system", figsize=(8, 6), dpi=100)
ax = fig.add_subplot(111, projection="3d")
self.axis_defaults(ax)
(pic,) = plt.plot([], [], ".--", lw=0.75)

self._plot_list = [fig.add_subplot(nodes, nodes, posit, projection="3d")]
self._axis_defaults_3d(self._plot_list[0])

if self.add_2d_gif:
self._add_2d_to_plots(fig)

# Convert ax to plot
self._plot_list = [item.plot([], [], ".--", lw=0.75)[0] for item in self._plot_list]

step_dots = len(self.coordinates) // step_size
self._color_map = plt.cm.get_cmap("hsv", step_dots)

ani = animation.FuncAnimation(
fig, self.update_coordinates, step_dots, fargs=(step_size, pic), interval=100, blit=False, repeat=True
fig, self.update_coordinates, step_dots, fargs=(step_size,), interval=100, blit=False, repeat=True
)

if self.save_plots:
ani.save(f"{self.model_name}_3d.gif", writer="imagemagick")
if self.show_plots:
plt.show()

def update_coordinates(self, num, step, pic):
pic.set_data(self.coordinates[0 : 1 + num * step, 0], self.coordinates[0 : 1 + num * step, 1])
pic.set_3d_properties(self.coordinates[0 : 1 + num * step, 2])
pic.set_color(self._color_map(num))
def update_coordinates(self, num, step):
self._plot_list[0].set_data(self.coordinates[0 : 1 + num * step, 0], self.coordinates[0 : 1 + num * step, 1])
self._plot_list[0].set_3d_properties(self.coordinates[0 : 1 + num * step, 2])
self._plot_list[0].set_color(self._color_map(num))
if self.add_2d_gif:
for ii, (x, y) in enumerate(self._plot_axis):
self._plot_list[ii + 1].set_data(
self.coordinates[0 : 1 + num * step, x], self.coordinates[0 : 1 + num * step, y]
)
self._plot_list[ii + 1].set_color(self._color_map(num))

def show_all_plots(self):
"""Cannot show all plots while 'show_plots' is True.
Expand All @@ -191,12 +226,13 @@ def close_all_plots():
np.random.seed(42)
points = np.cumsum(np.random.randn(50, 3), axis=1)

drawer = PlotDrawer(show_plots=True)
drawer = PlotDrawer(show_plots=True, add_2d_gif=True)
drawer.coordinates = points
drawer.model_name = "Chaotic"

# print(drawer.min_max_axis)
drawer.make_3d_plot_gif()
# drawer.show_time_plots(coordinates=points)
# drawer.make_3d_plot_gif(show_2d_plots=True)
# drawer.show_time_plots()
# drawer.show_3d_plots()
# drawer.show_all_plots()
Loading

0 comments on commit 70c5e45

Please sign in to comment.