The Lorenz Attractor
$\newcommand{\dd}{\mathrm{d}}$
The Lorenz system is a classic example of chaos in dynaimical systems. It’s known for its butterfly-shaped attractor and is defined by the following set of ordinary differential equations:
\[\frac{\dd x}{\dd t}=\sigma(y-x)\] \[\frac{\dd y}{\dd t}=x(\rho-z)-y\] \[\frac{\dd z}{\dd t}=xy-\beta z\]Because of its striking visual when plotted it’ll serve as a good example to
show how the animplotlib
package can be used for creating a more complex
animation in both 2D and 3D.
First ensure the animplotlib
package is installed:
pip install animplotlib
We’ll make use of scipy.integrate.odeint
to solve the system to generate the
data being plotted. First we import the necessary libraries and define the
attractor:
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
import animplotlib as anim
x0 = [0, 1, 15]
t = np.linspace(0.01, 100, 10000)
# defining the Lorenz system
def lorenz(x_var, t, sigma, rho, beta):
x, y, z = x_var
dx_dt = sigma * (y - x)
dy_dt = x * (rho - z) - y
dz_dt = x * y - beta * z
return [dx_dt, dy_dt, dz_dt]
# solving the system
def solve_lorenz(x0, t, sigma, rho, beta):
return odeint(lorenz, x0, t, args=(sigma, rho, beta))
x_solve = solve_lorenz(x0, t, sigma=10, rho=28, beta=8/3)
x, y, z = x_solve.T
2D Animation
Let’s visualize the Lorenz attractor in three 2D projections: x vs y
, x vs
z
, and y vs z
, each on its own axis within the same figure. The method
parallels the example of making a 2D animation containing a single axis,
however, here we create a list of axes, lines and points and pass them to
AnimPlot the same way.
# creating three subplots
fig, axs = plt.subplots(1, 3, figsize=(15, 5))
# lists to hold lines, points, and data for each subplot
lines = []
points = []
xs = []
ys = []
# x vs y
line1, = axs[0].plot([], [], lw=0.5)
point1, = axs[0].plot([], [], 'ro', markersize=4)
axs[0].set_xlim(np.min(x), np.max(x))
axs[0].set_ylim(np.min(y), np.max(y))
axs[0].set_title('x vs y')
axs[0].set_axis_off()
lines.append(line1)
points.append(point1)
xs.append(x)
ys.append(y)
# x vs z
line2, = axs[1].plot([], [], lw=0.5)
point2, = axs[1].plot([], [], 'ro', markersize=4)
axs[1].set_xlim(np.min(x), np.max(x))
axs[1].set_ylim(np.min(z), np.max(z))
axs[1].set_title('x vs z')
axs[1].set_axis_off()
lines.append(line2)
points.append(point2)
xs.append(x)
ys.append(z)
# y vs z
line3, = axs[2].plot([], [], lw=0.5)
point3, = axs[2].plot([], [], 'ro', markersize=4)
axs[2].set_xlim(np.min(y), np.max(y))
axs[2].set_ylim(np.min(z), np.max(z))
axs[2].set_title('y vs z')
axs[2].set_axis_off()
lines.append(line3)
points.append(point3)
xs.append(y)
ys.append(z)
# call the AnimPlot class
anim.AnimPlot(fig, lines, points, xs, ys, plot_speed=2, l_num=1000)

3D Animation
To better see how these projections relate to each other we can create an animation in 3D using the AnimPlot3D class. A standout property of chaotic systems is that they’re very sensitive to changes in their input parameters. We can visualise this by plotting the Lorenz attractor with various initial conditions on the same axes.
To do this we solve the Lorenz system for each set of initial conditions and
store the results in separate arrays. We then create a line and a point for
each solution and add them to a list of lines and points to be passed to
AnimPlot3D. Note that when passing the axes to AnimPlot3D we multiply the list
by len(lines)
. This is because AnimPlot3D expects a list of axes, one for
each line/trajectory we want to animate. Passing just [ax]
(a single axis)
would result in only the first line/trajectory being animated. By passing
[ax] * len(lines)
, we create a list with the same axis repeated for each
line, allowing all lines to be animated.
t = np.linspace(0.01, 50, 5000)
# list of initial conditions
x0 = [
(0., 1., 1.05),
(15., 10., 20.),
(-15., -10., 30.),
(5., -20., 40.)
]
# distinct colors for each trajectory
colors = [
"#4E79A7",
"#F28E2B",
"#46ACB8",
"#E15759"
]
# definig the Lorenz system
def lorenz(x_var, t, sigma, rho, beta):
x, y, z = x_var
dxdt = sigma * (y - x)
dydt = x * (rho - z) - y
dzdt = x * y - beta * z
return [dxdt, dydt, dzdt]
# solving the system
def solve_lorenz(x0, t, sigma, rho, beta):
return odeint(lorenz, x0, t, args=(sigma, rho, beta))
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, projection='3d')
ax.set_axis_off()
# lists to hold lines, points, and data for each trajectory
lines = []
points = []
xs, ys, zs = [], [], []
# solve for each set of initial conditions and create a line and point for each
for init_cond, color in zip(x0, colors):
x_solve = solve_lorenz(init_cond, t, sigma=10, rho=28, beta=8/3)
x, y, z = x_solve.T
xs.append(x)
ys.append(y)
zs.append(z)
line, = ax.plot([], [], [], lw=2, color=color)
point, = ax.plot([], [], [], 'o', color=color, markersize=8)
lines.append(line)
points.append(point)
ax.set_xlim(np.min(xs), np.max(xs))
ax.set_ylim(np.min(ys), np.max(ys))
ax.set_zlim(np.min(zs), np.max(zs))
anim.AnimPlot3D(fig, [ax] * len(lines), lines, points, xs, ys, zs, plot_speed=1,
rotation_speed=0.25, l_num=100, p_num=1)
