diff --git a/README.rst b/README.rst
index 531d61c..ccfbde0 100644
--- a/README.rst
+++ b/README.rst
@@ -59,6 +59,10 @@ 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
+Spectrum and auto-correlations::
+
+.. image:: https://raw.githubusercontent.com/capitanov/chaospy/master/img/Lorenz_Spectrum.png?sanitize=true
+
Source code
~~~~~~~~~~~
diff --git a/img/Lorenz_Spectrum.png b/img/Lorenz_Spectrum.png
new file mode 100644
index 0000000..8249c8a
Binary files /dev/null and b/img/Lorenz_Spectrum.png differ
diff --git a/src/_attractors.py b/src/_attractors.py
deleted file mode 100644
index e01c747..0000000
--- a/src/_attractors.py
+++ /dev/null
@@ -1,468 +0,0 @@
-"""Chaotic attractors.
-
-Description : This is the main module which collects attractors
- together. You can choose: Lorenz, Rossler, Rikitake,
- Nose-Hoover, Chua, Lottki, Duffing, Wang etc.
-
- Attention!
- You should set correct initial values of coordinates [Xo, Yo, Zo]
- because of some attractors has an overflow effect.
-
- ----------
- Lorenz:
- ----------
- Lorenz attractor is ordinary differential equation (ODE) of
- 3rd order system.
- In 1963, E. Lorenz developed a simplified mathematical model for
- atmospheric convection.
-
- Lorenz equations are:
- dx/dt = sigma * (y - x)
- dy/dt = rho * x - y - x * z
- dz/dt = x * y - beta * z
-
- where beta, rho and sigma - are Lorenz system parameters. Default
- values are: beta = 8/3, rho = 28 and sigma = 10.
-
- Wiki: If rho < 1 then there is only one equilibrium point.
-
- ----------
- Rikitake:
- ----------
- Rikitake system is ordinary differential equation (ODE) of
- 3rd order system.
- Rikitake system attempts to explain the reversal of the Earth’s
- magnetic field.
-
- Rikitake equations are:
- dx/dt = -mu * x + z * y
- dy/dt = -mu * y + x * (z - a)
- dz/dt = 1 - x * y
-
- where a, mu - are Rikitake system parameters. Default values are
- a = 5, mu = 2 or a = mu = 1.
-
- ----------
- Rossler:
- ----------
- Rossler attractor is the attractor for the Rössler system. Rossler
- attractor is a system of three non-linear ordinary differential
- equations.
-
- Rossler equations are:
- dx/dt = -(y + z)
- dy/dt = x + a * y
- dz/dt = b + z * (x - c)
-
- where a, b and sigma - are Rossler system parameters. Default
- values are: a = 0.2, b = 0.2 and c = 5.7.
-
- 1) a = 0.2, b = 0.2 and c = 5.7 (Standard model)
- 2) a = 0.1, b = 0.1 and c = 14 (another useful parameters)
- 3) a = 0.5, b = 1.0 and c = 3 (J. C. Sprott)
-
- Wiki (Varying parameters);
-
- Varying a:
- b = 0.2 and c = 5.7 are fixed. Change a:
-
- a <= 0 : Converges to the centrally located fixed point
- a = 0.1 : Unit cycle of period 1
- a = 0.2 : Standard parameter value selected by Rössler, chaotic
- a = 0.3 : Chaotic attractor, significantly more Möbius strip-like
- (folding over itself).
- a = 0.35 : Similar to .3, but increasingly chaotic
- a = 0.38 : Similar to .35, but increasingly chaotic
-
- Varying b:
- a = 0.2 and c = 5.7 are fixed. Change b:
-
- If b approaches 0 the attractor approaches infinity, but if b would
- be more than a and c, system becomes not a chaotic.
-
- Varying c:
- a = b = 0.1 are fixed. Change c:
-
- c = 4 : period-1 orbit,
- c = 6 : period-2 orbit,
- c = 8.5 : period-4 orbit,
- c = 8.7 : period-8 orbit,
- c = 9 : sparse chaotic attractor,
- c = 12 : period-3 orbit,
- c = 12.6 : period-6 orbit,
- c = 13 : sparse chaotic attractor,
- c = 18 : filled-in chaotic attractor.
-
- ----------
- Nose–Hoover:
- ----------
- Nose–Hoover oscillator is ordinary differential equation (ODE) of
- 3rd order system.
- Nose–Hoover system has only five terms and two quadratic
- nonlinearities.
-
- Nose–Hoover equations are:
- dx/dt = y
- dy/dt = y * z - x
- dz/dt = 1 - y * y
-
- Nose–Hoover system doesn't have any system parameters.
-
- ----------
- Wang attractor:
- ----------
- Wang system (improved Lorenz model) as classic chaotic attractor
-
- Wang equations are:
- dx/dt = x - y*z
- dy/dt = x - y + x*z
- dz/dt = -3*z + x*y
-
- ----------
- Duffing map:
- ----------
- It is a discrete-time dynamical system (2nd order)
- The map depends on the two constants: a and b.
- Z coordinate is a linear function.
-
- Duffing equations are:
- Eq. 1:
- dx/dt = y
- dy/dt = -a*y - x**3 + b * cos(z)
- dz/dt = 1
- where a = 0.1 and b = 11 (default parameters)
-
- Eq. 2:
- dx/dt = y
- dy/dt = a*y - y**3 - b*x
- dz/dt = 1
- where a = 2.75 and b = 0.2 (default parameters)
-
- ----------
- Chua circuit:
- ----------
- Chua circuit. This is a simple electronic circuit that exhibits
- classic chaotic behavior.
-
- Chua equations are:
- Eq. 1:
- dx/dt = alpha * (y - x - ht)
- dy/dt = x - y + z
- dz/dt = -beta * y
-
- where ht = mu1*x + 0.5*(mu0 - mu1)*(np.abs(x + 1) - np.abs(x - 1))
- and alpha, beta, mu0 and mu1 - are Chua system parameters.
-
- Default values are:
- alpha = 15.6
- beta = 28
- mu0 = -1.143
- mu1 = -0.714
-
- Eq. 2:
- dx/dt = 0.3*y + x - x**3
- dy/dt = x + z
- dz/dt = y
-
- ----------
- Lotka–Volterra:
- ----------
- The Lotka–Volterra equations, also known as the predator–prey
- equations.
-
- Chaotic Lotka-Volterra model require a careful tuning of
- parameters and are even less likely to exhibit chaos as the number
- of species increases.
-
- Lotka–Volterra equations are:
- Eq. 1:
- dx/dt = x * (1 - x - 9*y)
- dy/dt = -y * (1 - 6*x - y + 9*z)
- dz/dt = z * (1 - 3*x - z)
-
- Be careful! Init parameters of x, y, z should be set right.
- For example, [x, y, z] = [0.6; 0.2; 0.01].
-
-------------------------------------------------------------------------
-
-GNU GENERAL PUBLIC LICENSE
-Version 3, 29 June 2007
-
-Copyright (c) 2019 Kapitanov Alexander
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see .
-
-THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
-APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
-HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
-WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT
-NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
-PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
-DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
-OR CORRECTION.
-
-------------------------------------------------------------------------
-"""
-
-
-# Authors : Alexander Kapitanov
-# ...
-# Contacts :
-# ...
-# Release Date : 2019/05/31
-# License : GNU GENERAL PUBLIC LICENSE
-
-
-import matplotlib.pyplot as plt
-import numpy as np
-from mpl_toolkits.mplot3d import axes3d # noqa # pylint: disable=unused-import
-from scipy.fftpack import fft, fftshift
-from scipy.stats import gaussian_kde, kurtosis, skew
-from src.attractors.chua import chua
-from src.attractors.duffing import duffing
-from src.attractors.lorenz import lorenz
-from src.attractors.lotka_volterra import lotka_volterra
-from src.attractors.nose_hoover import nose_hoover
-from src.attractors.rikitake import rikitake
-from src.attractors.rossler import rossler
-from src.attractors.wang import wang
-
-# #####################################################################
-# Functions
-# #####################################################################
-
-# TODO: Reimplement this file! 2020/07/14
-
-
-def sel_chaos(chtype=None, xt=0, yt=0, zt=0, **kwargs):
- """
- Select chaotic system function by name
-
- Parameters
- ----------
- chtype : str
- Select function
- xt, yt, zt : float
- Input coordinates Z, Y, Z respectively
- kwargs : float
- Chaotic system parameters
-
- """
- ch_case = chtype.casefold()
- if ch_case == "wang":
- return wang(xt, yt, zt)
- elif ch_case == "nose-hoover":
- return nose_hoover(xt, yt, zt)
- elif ch_case == "lotka":
- return lotka_volterra(xt, yt, zt)
- elif ch_case == "chua":
- return chua(xt, yt, zt, **kwargs)
- elif ch_case == "lorenz":
- return lorenz(xt, yt, zt, **kwargs)
- elif ch_case == "rossler":
- return rossler(xt, yt, zt, **kwargs)
- elif ch_case == "duffing":
- return duffing(xt, yt, zt, **kwargs)
- elif ch_case == "rikitake":
- return rikitake(xt, yt, zt, **kwargs)
- else:
- raise Exception("Error: you should set the correct chaotic system")
-
-
-def sel_args(chtype=None, args=None):
- """
- Select arguments for chaotic system
-
- Parameters
- ----------
- chtype : str
- Select function
- args : dict
- Chaotic system arguments (as a dict)
- """
- ch_case = chtype.casefold()
- if ch_case in ("lorenz", "rossler", "duffing", "chua", "rikitake"):
- return args[ch_case]
- return {}
-
-
-# #####################################################################
-# Input parameters
-# #####################################################################
-
-CHTYPE = "Wang" # Chaotic system name
-CHARGS = {
- "rikitake": {"a": 1, "mu": 1},
- "duffing": {"a": 0.1, "b": 11},
- "rossler": {"a": 0.2, "b": 0.2, "c": 5.7},
- "lorenz": {"sigma": 10, "beta": 8 / 3, "rho": 28},
- "chua": {"alpha": 0.1, "beta": 28, "mu0": -1.143, "mu1": -0.714},
-} # Set arguments
-
-NW = 5000 # Number of ODE's dots
-DT = 100 # Step for equations
-NFFT = 2 ** 12 # Number of FFT dots
-
-
-# #####################################################################
-# Calculate attractor
-# #####################################################################
-def calc_chaos():
- print("Start analyzing the next chaotic system: [%s]\n" % CHTYPE)
-
- # Create zero arrays for coordinates
- xt, yt, zt = np.zeros(NW), np.zeros(NW), np.zeros(NW)
- # Set initial values for [X, Y, Z]
- xt[0], yt[0], zt[0] = 1.0, 0.5, 1.0
-
- for ii in range(NW - 1):
- x_nt, y_nt, z_nt = sel_chaos(chtype=CHTYPE, xt=xt[ii], yt=yt[ii], zt=zt[ii], **sel_args(CHTYPE, CHARGS),)
- xt[ii + 1] = xt[ii] + (x_nt / DT)
- yt[ii + 1] = yt[ii] + (y_nt / DT)
- zt[ii + 1] = zt[ii] + (z_nt / DT)
-
- # #####################################################################
- # Calculate standardized moments
- # #####################################################################
-
- ds_3d = ("X", "Y", "Z")
- ch_3d = np.stack((xt, yt, zt))
-
- nn, mm = ch_3d.shape[0], ch_3d.shape[1]
- print("Calculate mean, variance, skewness, kurtosis and median for each " "coordinate of chaotic system:")
-
- m15 = np.zeros([nn, mm])
- for ii in range(nn):
- m15[ii, 0] = np.mean(ch_3d[ii, :]) # Mean
- m15[ii, 1] = np.var(ch_3d[ii, :]) # Variance
- m15[ii, 2] = skew(ch_3d[ii, :]) # Skewness
- m15[ii, 3] = kurtosis(ch_3d[ii, :]) # Kurtosis
- m15[ii, 4] = np.median(ch_3d[ii, :]) # Median
-
- print("%s axis: " % ds_3d[ii], end="")
- print("M[t]: {:+.4f}, ".format(m15[ii, 0]), end="")
- print("D[t]: {:+.4f}, ".format(m15[ii, 1]), end="")
- print("A[t]: {:+.4f}, ".format(m15[ii, 2]), end="")
- print("E[t]: {:+.4f}, ".format(m15[ii, 3]), end="")
- print("Md[t]: {:+.4f}. ".format(m15[ii, 4]))
-
- # Find Probability density function
- n_pdf = 3000
- p_axi = np.zeros([nn, n_pdf])
- d_kde = np.zeros([nn, n_pdf])
- for ii in range(nn):
- p_axi[ii] = np.linspace(ch_3d[ii, :].min(), ch_3d[ii, :].max(), n_pdf)
- d_kde[ii] = gaussian_kde(ch_3d[ii, :]).evaluate(p_axi[ii, :])
- d_kde[ii] /= d_kde[ii].max()
-
- # #####################################################################
- # Calculate Autocorrelation (Cross-correlation) and FFT
- # #####################################################################
-
- # TODO implement FFT and autocorrelation
- d_acf = np.zeros([nn, mm])
- d_fft = np.zeros([nn, NFFT], dtype=np.complex)
- for ii in range(nn):
- d_acf[ii] = np.correlate(ch_3d[ii, :], ch_3d[ii, :], "same")
- d_acf[ii] /= d_acf[ii].max()
- d_fft[ii] = fft(ch_3d[ii, 1:NFFT], NFFT)
-
- d_fft = np.abs(fftshift(d_fft, axes=1))
- d_fft /= np.max(d_fft)
- d_fft = 20 * np.log10(d_fft)
-
- plt.rc("font", size=8) # controls default text sizes
- plt.rc("axes", titlesize=10) # fontsize of the axes title
- plt.rc("axes", labelsize=10) # fontsize of the x and y labels
- plt.rc("legend", fontsize=8) # legend fontsize
-
- lim_xyz = [(np.min(ch_3d[ii]), np.max(ch_3d[ii])) for ii in range(3)]
-
- # Plot 3D model
- # fig1 = plt.figure('3D model of chaotic system')
- # ax = fig1.gca(projection='3d')
- # ax.plot(xt, yt, zt, 'o-', linewidth=0.1, markersize=0.3)
- # ax.set_xlabel("X")
- # ax.set_ylabel("Y")
- # ax.set_zlabel("Z")
- # ax.set_xlim(lim_xyz[0])
- # ax.set_ylim(lim_xyz[1])
- # ax.set_zlim(lim_xyz[2])
- # ax.set_title("3D Chaotic Attractor")
- # plt.tight_layout()
-
- # Plot 2D coordinates of 3D model
- fig2 = plt.figure("3D Coordinates")
- plt.subplot(2, 2, 1)
- plt.plot(yt, xt, linewidth=0.75)
- plt.grid()
- plt.xlabel("X")
- plt.ylabel("Y")
- plt.xlim(lim_xyz[1])
- plt.ylim(lim_xyz[0])
-
- plt.subplot(2, 2, 2)
- plt.plot(yt, zt, linewidth=0.75)
- plt.grid()
- plt.xlabel("Z")
- plt.ylabel("Y")
- plt.xlim(lim_xyz[1])
- plt.ylim(lim_xyz[2])
-
- plt.subplot(2, 2, 3)
- plt.plot(zt, xt, linewidth=0.75)
- plt.grid()
- plt.xlabel("X")
- plt.ylabel("Z")
- plt.xlim(lim_xyz[2])
- plt.ylim(lim_xyz[0])
-
- ax = fig2.add_subplot(2, 2, 4, projection="3d")
- ax.plot(xt, yt, zt, linewidth=0.7)
- ax.set_xlabel("X")
- ax.set_ylabel("Y")
- ax.set_zlabel("Z")
- plt.tight_layout()
-
- # Plot 2D data in timex
- plt.figure("Coordinates evolution in time")
- for ii in range(nn):
- plt.subplot(2, 2, ii + 1)
- plt.plot(ch_3d[ii], linewidth=0.75)
- plt.grid()
- plt.ylabel(ds_3d[ii])
- plt.xlim([0, NW - 1])
- plt.ylim(lim_xyz[ii])
- plt.tight_layout()
-
- # Plot Probability density function
- plt.figure("Probability density function")
- for ii in range(nn):
- plt.plot(d_kde[ii], ".")
- plt.xlim([0, n_pdf - 1])
- plt.grid()
- plt.tight_layout()
-
- # Plot Autocorrelation and Spectrum
- plt.figure("Autocorrelation and Spectrum")
- for ii in range(nn):
- plt.subplot(2, 3, ii + 1)
- plt.plot(d_acf[ii], linewidth=0.75)
- plt.grid()
- plt.subplot(2, 3, ii + 1 + 3)
- plt.plot(d_fft[ii], linewidth=0.75)
- plt.grid()
- plt.tight_layout()
-
- plt.show()
-
-
-# Execute main function: Chaotic system
-calc_chaos()
diff --git a/src/dynamic_system.py b/src/dynamic_system.py
index 2e52513..c5a2a65 100644
--- a/src/dynamic_system.py
+++ b/src/dynamic_system.py
@@ -40,7 +40,6 @@
from typing import Optional
-import numpy as np
import pandas as pd
from src.utils.calculator import Calculator
from src.utils.drawer import PlotDrawer
@@ -61,7 +60,6 @@ class DynamicSystem:
calculator: Calculator()
settings: Settings()
-
Examples
--------
# TODO: Add examples
@@ -102,10 +100,10 @@ def initialize(self, input_args: Optional[tuple] = None, show_logs: bool = False
# Update main calculator
self.calculator = Calculator()
- def collect_statistics(self, points: np.ndarray):
+ def collect_statistics(self):
math_dict = {}
- _min_max = self.calculator.check_min_max(points)
- _moments = self.calculator.check_moments(points)
+ _min_max = self.calculator.check_min_max()
+ _moments = self.calculator.check_moments()
math_dict.update({"Min": _min_max[0]})
math_dict.update({"Max": _min_max[1]})
math_dict.update(_moments)
@@ -115,22 +113,24 @@ def collect_statistics(self, points: np.ndarray):
def run(self):
# Get vector of coordinates
_points = self.model.get_coordinates()
+ self.calculator.coordinates = _points
# Calculate
- stats = self.collect_statistics(_points)
+ stats = self.collect_statistics()
if self.settings.show_logs:
print(f"[INFO]: Show statistics:\n{stats}\n")
- self.calculator.check_probability(_points)
- self.calculator.calculate_fft(_points)
+ self.calculator.check_probability()
+ spectrums = self.calculator.calculate_spectrum()
+ correlations = self.calculator.calculate_correlation()
# Draw results
if self.settings.show_plots or self.settings.save_plots:
self.drawer.coordinates = _points
-
+ self.drawer.show_spectrum_and_correlation(spectrums, correlations)
# self.drawer.show_time_plots()
# self.drawer.show_3d_plots()
- self.drawer.make_3d_plot_gif(50)
+ # self.drawer.make_3d_plot_gif(50)
# self.drawer.show_all_plots()
@@ -139,13 +139,13 @@ def run(self):
"--init_point",
"1 -1 2",
"--points",
- "2000",
+ "3000",
"--step",
"50",
"--save_plots",
# "--show_plots",
"--add_2d_gif",
- "rossler",
+ "lorenz",
)
dynamic_system = DynamicSystem(input_args=command_line, show_log=True)
diff --git a/src/utils/calculator.py b/src/utils/calculator.py
index 0fdd01c..5e079de 100644
--- a/src/utils/calculator.py
+++ b/src/utils/calculator.py
@@ -42,40 +42,52 @@
from typing import Tuple
import numpy as np
-from scipy.stats import kurtosis, skew
+from scipy.fftpack import fft, fftshift
+from scipy.stats import gaussian_kde, kurtosis, skew
class Calculator:
- """Main class for calculate math parameters.
+ """Main class for calculate math parameters: FFTs, Auto-Correlation, KDE (Prob) etc.
See Also:
-----
"""
- def __init__(self):
- pass
+ def __init__(self, kde_dots: int = 1000, fft_dots: int = 4096):
+ self.kde_dots = kde_dots
+ self.fft_dots = fft_dots
+ self._coordinates = None
- @staticmethod
- def check_min_max(coordinates: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
- """Calculate minimum and maximum for X, Y, Z coordinates.
+ def __len__(self):
+ return len(self.coordinates)
+
+ @property
+ def coordinates(self) -> np.ndarray:
+ return self._coordinates
+
+ @coordinates.setter
+ def coordinates(self, value: np.ndarray):
+ """3D coordinates.
Parameters
----------
- coordinates: np.array
+ value : np.ndarray
Numpy 3D array of dynamic system coordinates.
+
"""
- return np.min(coordinates, axis=0), np.max(coordinates, axis=0)
+ self._coordinates = value
- @staticmethod
- def check_moments(coordinates: np.ndarray, is_common: bool = False) -> dict:
+ def check_min_max(self) -> Tuple[np.ndarray, np.ndarray]:
+ """Calculate minimum and maximum for X, Y, Z coordinates.
+ """
+ return np.min(self.coordinates, axis=0), np.max(self.coordinates, axis=0)
+
+ def check_moments(self, is_common: bool = False) -> dict:
"""Calculate stochastic parameters: mean, variance, skewness, kurtosis etc.
Parameters
----------
- coordinates: np.array
- Numpy 3D array of dynamic system coordinates.
-
is_common : bool
If False - method returns moments for each coordinate. Otherwise
returns moments over all ndarray. Similar for axis or axes along
@@ -84,21 +96,62 @@ def check_moments(coordinates: np.ndarray, is_common: bool = False) -> dict:
"""
axis = None if is_common else 0
return {
- "Mean": np.mean(coordinates, axis=axis),
- "Variance": np.var(coordinates, axis=axis),
- "Skewness": skew(coordinates, axis=axis),
- "Kurtosis": kurtosis(coordinates, axis=axis),
- "Median": np.median(coordinates, axis=axis),
+ "Mean": np.mean(self.coordinates, axis=axis),
+ "Variance": np.var(self.coordinates, axis=axis),
+ "Skewness": skew(self.coordinates, axis=axis),
+ "Kurtosis": kurtosis(self.coordinates, axis=axis),
+ "Median": np.median(self.coordinates, axis=axis),
}
- def check_probability(self, coordinates: np.ndarray):
- pass
- # TODO: Implement this method!
+ def check_probability(self):
+ """Check probability for each chaotic coordinates.
+
+ """
+ p_axi = np.zeros([3, self.kde_dots])
+ d_kde = np.zeros([3, self.kde_dots])
+ for ii in range(3):
+ p_axi[ii] = np.linspace(self.coordinates[ii, :].min(), self.coordinates[ii, :].max(), self.kde_dots)
+ d_kde[ii] = gaussian_kde(self.coordinates[ii, :]).evaluate(p_axi[ii, :])
+ d_kde[ii] /= d_kde[ii].max()
+ return d_kde
+
+ def calculate_spectrum(self):
+ """Calculate FFT (in dB) for input 3D coordinates. You can set number of FFT points into the object instance.
- def calculate_fft(self, coordinates: np.ndarray):
- pass
- # TODO: Implement this method!
+ """
+ spectrum = fft(self.coordinates, self.fft_dots, axis=0)
+ spectrum = np.abs(fftshift(spectrum, axes=0))
+ # spectrum = np.abs(spectrum)
+ spectrum /= np.max(spectrum)
+ spec_log = 20 * np.log10(spectrum + np.finfo(np.float32).eps)
+ return spec_log
+
+ def calculate_correlation(self):
+ """Calculate auto correlation function for chaotic coordinates.
+
+ """
+ nn, mm = 3, len(self.coordinates)
+ auto_corr = np.zeros([mm, nn])
+ for ii in range(nn):
+ auto_corr[:, ii] = np.correlate(self.coordinates[:, ii], self.coordinates[:, ii], "same")
+ return auto_corr
if __name__ == "__main__":
- pass
+ calc = Calculator()
+
+ num_dots = 10000
+ np.random.seed(42)
+ calc.coordinates = np.random.randn(num_dots, 3)
+
+ dd_kde = calc.check_probability()
+ import matplotlib.pyplot as plt
+
+ print(dd_kde.shape)
+ plt.figure("Probability density function")
+ for idx in range(dd_kde.shape[0]):
+ plt.plot(dd_kde[idx], ".")
+ # plt.xlim([0, dd_kde.shape[1] - 1])
+ plt.grid()
+ plt.tight_layout()
+ plt.show()
diff --git a/src/utils/drawer.py b/src/utils/drawer.py
index 1a84532..ed7f03c 100644
--- a/src/utils/drawer.py
+++ b/src/utils/drawer.py
@@ -56,7 +56,7 @@ class PlotDrawer:
"""
- _plot_axis = ((1, 0), (1, 2), (2, 0))
+ _plot_axis = ((0, 1), (2, 1), (0, 2))
_plot_labels = {0: "X", 1: "Y", 2: "Z"}
def __init__(
@@ -98,10 +98,53 @@ def coordinates(self, value: np.ndarray):
def min_max_axis(self):
return np.vstack([np.min(self.coordinates, axis=0), np.max(self.coordinates, axis=0)]).T
+ # TODO: Add plots for KDE
+ # # Plot Probability density function
+ # plt.figure("Probability density function")
+ # for ii in range(nn):
+ # plt.plot(d_kde[ii], ".")
+ # plt.xlim([0, n_pdf - 1])
+ # plt.grid()
+ # plt.tight_layout()
+
+ def show_spectrum_and_correlation(self, spectrums: np.ndarray, correlations: np.ndarray):
+ """Plot 3D coordinates as time series."""
+ _ = plt.figure("Autocorrelation and Spectrum", figsize=(8, 6), dpi=100)
+
+ x_corr = np.linspace(-len(self.coordinates) // 2, len(self.coordinates) // 2, len(self.coordinates))
+ x_ffts = np.linspace(-0.5, 0.5, len(spectrums))
+
+ plt.suptitle(f"{self.model_name} attractor", x=0.1)
+ for ii, axis in enumerate(self._plot_labels.values()):
+ plt.subplot(3, 3, ii + 1)
+ plt.title("Time plots", y=1.0) if ii % 2 == 1 else None
+ plt.plot(self.coordinates[:, ii], linewidth=0.75)
+ plt.grid(True)
+ plt.ylabel(axis)
+ plt.xlim([0, len(self.coordinates) - 1])
+ plt.subplot(3, 3, ii + 4)
+ plt.title("Spectrum plots", y=1.0) if ii % 2 == 1 else None
+ plt.plot(x_ffts, spectrums[:, ii], linewidth=0.75)
+ plt.grid(True)
+ plt.ylabel(axis)
+ plt.xlim([np.min(x_ffts), np.max(x_ffts)])
+ plt.subplot(3, 3, ii + 7)
+ plt.title("Correlation plots", y=1.0) if ii % 2 == 1 else None
+ plt.plot(x_corr, correlations[:, ii], linewidth=0.75)
+ plt.grid(True)
+ plt.ylabel(axis)
+ plt.xlim([np.min(x_corr), np.max(x_corr)])
+ plt.tight_layout()
+
+ if self.save_plots:
+ plt.savefig(f"{self.model_name}_spectrum_correlations.png")
+ if self.show_plots:
+ plt.show()
+
def show_time_plots(self):
"""Plot 3D coordinates as time series."""
_ = plt.figure("Coordinates evolution in time", figsize=(8, 6), dpi=100)
- for ii, axis in enumerate(["X", "Y", "Z"]):
+ for ii, axis in enumerate(self._plot_labels.values()):
plt.subplot(3, 1, ii + 1)
plt.plot(self.coordinates[:, ii], linewidth=0.75)
plt.grid(True)
@@ -116,7 +159,7 @@ def show_time_plots(self):
plt.show()
def _axis_defaults_3d(self, plots):
- plots.set_title(f"{self.model_name} attractor")
+ plots.set_title(f"{self.model_name} model")
plots.set_xlabel("X")
plots.set_ylabel("Y")
plots.set_zlabel("Z")
@@ -151,12 +194,11 @@ def show_3d_plots(self):
plt.grid()
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)
+ ax.plot(self.coordinates[:, 0], self.coordinates[:, 1], self.coordinates[:, 2], linewidth=0.75)
self._axis_defaults_3d(ax)
plt.tight_layout()
@@ -189,7 +231,7 @@ def make_3d_plot_gif(self, step_size: int = 10):
self._color_map = plt.cm.get_cmap("hsv", step_dots)
ani = animation.FuncAnimation(
- fig, self.update_coordinates, step_dots, fargs=(step_size,), 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:
@@ -197,7 +239,8 @@ def make_3d_plot_gif(self, step_size: int = 10):
if self.show_plots:
plt.show()
- def update_coordinates(self, num, step):
+ def _update_coordinates(self, num, step):
+ """Update plots for making gif"""
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))
@@ -208,31 +251,55 @@ def update_coordinates(self, num, step):
)
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.
- Note: After closing plots you cannot reopen them!
- """
- if not self.show_plots:
- plt.show()
+ # def show_all_plots(self):
+ # """Cannot show all plots while 'show_plots' is True.
+ # Note: After closing plots you cannot reopen them!
+ # """
+ # if not self.show_plots:
+ # plt.show()
+ #
+ # @staticmethod
+ # def close_all_plots():
+ # plt.clf()
+ # plt.cla()
+ # plt.close()
+
+
+def random_circle(num_points: int = 100) -> np.ndarray:
+ r"""Create random circle for 3D plots.
+
+ Parameters
+ ----------
+ num_points : int
+ Number of points to draw
+
+ Returns
+ -------
+
+ arr: np.ndarray
+ Numpy 3D array with shapes [3, num_points]
+ """
- @staticmethod
- def close_all_plots():
- plt.clf()
- plt.cla()
- plt.close()
+ angle = 3
+ ratio = 0.020
+
+ theta = np.linspace(0, angle * np.pi, num_points)
+ xyz = np.vstack([np.cos(theta), np.sin(theta), np.sin(np.linspace(0, 2.05 * np.pi, num_points))]).T
+ xyz += ratio * np.cumsum(np.random.randn(num_points, 3), axis=1)
+ return xyz
if __name__ == "__main__":
np.random.seed(42)
- points = np.cumsum(np.random.randn(50, 3), axis=1)
+
+ circle_points = random_circle(num_points=200)
drawer = PlotDrawer(show_plots=True, add_2d_gif=True)
- drawer.coordinates = points
+ drawer.coordinates = circle_points
drawer.model_name = "Chaotic"
# print(drawer.min_max_axis)
- drawer.make_3d_plot_gif()
- # drawer.make_3d_plot_gif(show_2d_plots=True)
+ drawer.make_3d_plot_gif(step_size=20)
# drawer.show_time_plots()
# drawer.show_3d_plots()
# drawer.show_all_plots()