Inspired by one of Veritasium Youtube video where he explained chaotic bifurcation map (logistic map). the mathematical equation is simply: X[i+1]=R*X[i](1-X[i])
the first graph he plots: this X[i]
values (y-axis values, in the range of 0 to 1) versus iteration time i
(x-axis values, number of calculation) with a certain value of R (say, R_init=2, and I wanted to incorporate a Slider in my code for changing the value of R)
the second graph (the bifurcation graph) he plots: the value R (x-axis values) versus equilibrium population of X[i], that is: the number of X[i] values that it oscillates between (depending on the R value, after certain/many iteration i, X[i] can oscillate between finite numbers and also infinite numbers -- chaos!)
Eventually, in the code, I want two side by side subplot, left one is the "first graph" with the Slider for those two variables, right side is the "second graph". Below I pasted the my code for a starter, where I only try to achieve plotting "first graph" with a Slider for R and initial value of X[0] (but, of course, it failed to show anything so far..) I'd appreciate if anyone can help me finishing up this project, or giving me some advice on my problematic code.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
R = 1.5
x = np.linspace(0, 100, 1)
k_init = 0.4 # initial y value
fig = plt.figure(figsize=(8, 8))
ax = plt.axes([0.125, 0.15, 0.775, 0.80])
myplot, = plt.plot(0, 0, c="royalblue") # not really ploting anything first
plt.xlim(0, 100)
plt.ylim(0, 1)
# create slider panel & values sets
slider_r = plt.axes([0.125, 0.03, 0.775, 0.03])
slider_k = plt.axes([0.125, 0.07, 0.775, 0.03])
r_slider = Slider(slider_r, "R", 1, 20, valinit=R, valstep=0.1)
k_slider = Slider(slider_k, "k_init", 0, 1, valinit=k_init, valstep=0.05)
k = [] # y-values starts with List items
def update(*args):
k.append(k_init)
for i in np.arange(1,100,1):
k_new = R*(k[i-1])*(1-(k[i-1]))
k.append(k_new)
y = np.array(k)
myplot.set_xdata(x)
myplot.set_ydata(y)
r_slider.on_changed(update)
k_slider.on_changed(update)
update()
plt.show()
x = np.linspace(0, 100, 1)
by x = np.arange(100)
k = []
k0, R = k_slider.val, r_slider.val
at the beginning of update
functionk.append(k_init)
by k = [k0]
First, let's try to obtain a working plot without sliders. Here are some tips and some things to fix:
x = np.linspace(0, 100, 1)
only contains one element, while y
will contain 100 values!y
is already known, initialize it as an empty array, instead of adding items to the list k
. This will make the code more readable and faster.x
whereas in the implementation it is y
ans k
: Define notations that make sense, and stick to them.r_slider
and slider_r
, especially since these two variables store completely different objects.## define constants
R = 2.6
X0 = 0.5 # initial value
N = 75 # number of epochs
## logistic map iterates
def get_logistic_map(x0, R, N):
x = np.empty(N)
x[0] = x0
for i in range(1, N):
x[i] = R * x[i-1] * (1 - x[i-1])
return x
x = get_logistic_map(X0, R, N)
## plot
plt.figure(figsize=(7, 4))
plt.plot(x, 'o-', c="royalblue", ms=2)
plt.ylim(0, 1)
plt.margins(x=.01)
plt.grid(c="lightgray")
plt.xlabel(r"$n$")
plt.ylabel(r"$x_n$")
plt.show()
In function update
:
update
function). Since here two sliders are linked to the same update function, you cannot use this input parameter. However you can access these values using attribute val
of each slider. (And since such callback functions require a parameter, you use an *args
appropriately ;).k
is a global list. So each time update
is called 100 new values are added.## Prepare figure layout
fig = plt.figure(figsize=(7, 5))
ax = plt.axes([0.125, 0.15, 0.775, 0.80])
line, = plt.plot(range(N), np.zeros(N), 'o-', c="royalblue", ms=2)
# set the layout of the main axes before defining the axes of the sliders
plt.ylim(0, 1)
plt.xlim(0, N-1)
## Create sliders
ax_slider_x0 = plt.axes([0.125, 0.03, 0.775, 0.03])
ax_slider_r = plt.axes([0.125, 0.07, 0.775, 0.03])
slider_x0 = Slider(ax_slider_x0, r"$x_0$", 0, 1, valinit=X0)
slider_r = Slider(ax_slider_r, r"$R$", 0, 4, valinit=R)
# plt.sca(ax) # uncomment to set the main axes as the current one
def update(*args):
x0_val, r_val = slider_x0.val, slider_r.val
x = get_logistic_map(x0_val, r_val, N)
line.set_ydata(x)
# Set the title on the main axes (plt.title would have added a
# title on the current axes (by default the last one to be defined)
ax.set_title(rf"Logistic map with $R$={r_val:.3f} and $x_0={x0_val:.3f}$")
slider_x0.on_changed(update)
slider_r.on_changed(update)
update() # initialize the plot
plt.show()
A personal tip – To debug a callback function with sliders, it is tempting to scatter some
Now the first plot is interactive. For the bifurcation graph, I don't see the interactivity that could be added.