Three Visualizations of Chaos

In this blog post, we explore some computationally simple systems with complex behaviours that I came across while reading James Gleick's excellent book Chaos: Making a New Science.

The logistic map: a simple population model¶

The first system we'll compute is the logistic map. As described in the book, this system comes from a 1976 paper by the biologist Robert May and can be described as a discrete-time model for how a population grows.

Given a population $x_n$, you can compute its next state $x_{n+1}$ with the following function:

In [1]:
def logistic_iter(x, r):
"One iteration of the logistic map."
return r * x - r * x**2


The above expression is made of two parts: $r x$ is analogous to a growth term as a function of previous population while $-r x^2$ can be described as a death rate when the population gets high. Critically, both are multiplied by $r$, which is a parameter of this model.

Let's build some understanding of this simple system by iterating it for 50 times, with a given start population between 0 and 1.

In [2]:
%matplotlib inline
from ipywidgets import interact, interactive, fixed
from matplotlib import pyplot as plt
import numpy as np

In [3]:
@interact
def plot_logistic_iterated(x0=(0.1, 1, 0.01), r=(0, 4, 0.01)):
"Plots 50 iterations of a population, given the r parameter."
vals = [x0]
for _ in range(50):
vals.append(logistic_iter(vals[-1], r))
plt.plot(vals)


Interestingly, different things can happen. For low $r$ values, the population will just decrease:

In [4]:
plot_logistic_iterated(x0=0.4, r=0.3)


For intermediate values, below 4, the population will reach equilibrium.

In [5]:
plot_logistic_iterated(x0=0.4, r=2.3)


And then for values closer but below 4, strange things happen:

In [6]:
plt.subplot(2, 2, 1)
plot_logistic_iterated(x0=0.4, r=3.5)
plt.subplot(2, 2, 2)
plot_logistic_iterated(x0=0.4, r=3.6)
plt.subplot(2, 2, 3)
plot_logistic_iterated(x0=0.4, r=3.7)
plt.subplot(2, 2, 4)
plot_logistic_iterated(x0=0.4, r=3.8)


We can visualize this in another way, by showing the iterated values on a two dimensional graph:

In [7]:
@interact
def plot_logistic_iterated_2D(x0=(0.1, 1, 0.01), r=(0.01, 4, 0.01), ax=fixed(None)):
"Plots logistic map for 50 iterations on 2D plot."
# computes iterated values
vals = [x0]
for _ in range(50):
vals.append(logistic_iter(vals[-1], r))
# plottingf
if ax is None:
fig, ax = plt.subplots()

for x_start, x_end in zip(vals[:-1], vals[1:]):
ax.plot([x_start, x_start, x_end], [x_start, x_end, x_end], '-ko')
xx = np.linspace(0, 1)
ax.plot(xx, logistic_iter(xx, r))
ax.plot(xx, xx)


We can display what we observed before in this visualization:

In [8]:
plt.figure(figsize=(10, 8))
ax = plt.subplot(2, 2, 1)
plot_logistic_iterated_2D(0.3, 1, ax=ax) # decrease to 0
ax = plt.subplot(2, 2, 2)
plot_logistic_iterated_2D(0.9, 2, ax=ax) # stable
ax = plt.subplot(2, 2, 3)
plot_logistic_iterated_2D(0.9, 3, ax=ax) # 2 cycle
ax = plt.subplot(2, 2, 4)
plot_logistic_iterated_2D(0.9, 3.9, ax=ax) # ? cycles


Finally, let's make an animation of this.

In [9]:
import matplotlib.pyplot as plt
import numpy as np
from moviepy.editor import VideoClip
from moviepy.video.io.bindings import mplfig_to_npimage

x = np.linspace(-2, 2, 200)

duration = 10

fig, ax = plt.subplots(dpi=100)
def make_frame(t):
r = t / duration * 3.99
ax.clear()
plot_logistic_iterated_2D(0.3, r, ax=ax)
return mplfig_to_npimage(fig)

animation = VideoClip(make_frame, duration=duration)
plt.close(fig)
animation.ipython_display(fps=25, loop=True, autoplay=True)

100%|███████████████████████████████████████▊| 250/251 [00:43<00:00,  5.71it/s]

Out[9]: