根据其中一段视频重建Veritasium的情节

2022-02-24 00:00:00 python matplotlib slider

问题描述

灵感来自Veritasium YouTube的一个视频,他在视频中解释了混沌分岔映射(Logistic映射)。数学方程式很简单:X[i+1]=R*X[i](1-X[i])

他绘制的第一个图:这个X[i]值(y轴值,在0到1的范围内)与迭代时间i(x轴值,计算次数)具有某个R值(比如R_init=2,我想在代码中加入一个滑块来更改R值)

第二个图(分叉图)他绘制了:值R(x轴值)与X[i]的平衡总体,即:它在其间振荡的X[i]值的数目(取决于R值,在一定/多次迭代i之后,X[i]可以在有限数和无限数之间振荡--混沌!)

最终,在代码中,我想要两个并排的子图,左边是第一个图形";,使用这两个变量的Slider,右边是";第二个图形";。 下面我粘贴了我的入门代码,其中我只尝试使用R的Slider和初始值X[0]来实现绘制第一个图形和X[0](但是,当然,到目前为止它没有显示任何内容。) 如果有人能帮我完成这个项目,或者就我有问题的代码给我一些建议,我将不胜感激。

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)替换为x = np.arange(100)
  • 删除k = []
  • update函数开头添加k0, R = k_slider.val, r_slider.val
  • k.append(k_init)替换为k = [k0]

详细答案

1.获取运行(没有动画)的模拟

首先,让我们尝试获得一个没有滑块的工作图。以下是一些提示和需要解决的问题:

  • x = np.linspace(0, 100, 1)只包含一个元素,而y将包含100个值!
  • 由于y的最终大小已知,请将其初始化为空数组,而不是向列表k添加项。这将使代码更具可读性,速度更快。
  • 使用变量存储常量值,如纪元数。
  • 在问题描述中使用变量x,而在实现中使用变量yansk:定义有意义的符号,并坚持使用。
  • 定义r_sliderslider_r这样相似的名称比较容易混淆,特别是这两个变量存储的对象完全不同。
## 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()

2.让我们引入与滑块的交互

在函数中update

  • 您应该使用滑块的新值更新显示的数据。通常,当更新滑块时,它会将其新值作为参数提供给回调函数(这里是update函数)。因为这里有两个滑块链接到相同的更新函数,所以您不能使用此输入参数。但是,您可以使用每个滑块的属性val访问这些值。(由于此类回调函数需要参数,因此您可以适当使用*args;)。
  • 我不确定是不是故意的,但是k是一个全局列表。因此,每次调用100个update时,都会添加新的值。
## 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()

个人提示-要使用滑块调试回调函数,很容易散布一些print。但是,由于这是徒劳的,您可以改为在绘图标题内显示调试文本!

3.下一步是什么?

现在第一个情节是交互式的。对于分叉图,我看不到可以添加的交互性。

相关文章