Press "Enter" to skip to content

模拟105:使用数值积分进行双摆模型建模

建模混沌系统

摆锤是我们都熟悉的经典物理系统。无论是大型悬挂钟还是摇摆的孩子,我们都见过摆锤的规律、周期性运动。单摆在经典物理中有明确定义,但双摆(将一个摆锤连接到另一个摆锤的末端)则是真正的混沌。在本文中,我们将在对摆锤的直观理解基础上建模双摆的混沌行为。这里的物理现象非常有趣,所需的数值方法是任何人必备的工具。

图1:混沌双摆示例

本文中,我们将:

  • 了解谐振运动并建模单摆的行为
  • 学习混沌理论的基础知识
  • 使用数值方法来模拟双摆的混沌行为

谐振运动和单摆

简谐运动

我们将摆锤的周期性振动称为谐振运动。当系统中存在运动,并且该运动受到与该运动方向相反的成比例恢复力的平衡时,就会发生谐振运动。我们在图2中看到了一个例子,弹簧上的质量由于重力被拉下,但这会将能量传递给弹簧,然后弹簧弹回并将质量拉起。在弹簧系统旁边,我们可以看到质量的高度绕着一个叫做相量图的圆圈运动,进一步说明系统的规律运动。

图2:弹簧上质量的简谐运动示例

谐振运动可以是阻尼的(由于阻力而振幅逐渐减小)或驱动的(由于外力的作用而振幅逐渐增大),但我们将从没有外力作用的无限谐振运动的最简单情况开始(无阻尼运动)。这种运动方式很好地适用于模拟小角度/低振幅摆动的单摆。在这种情况下,我们可以使用下面的方程来模拟运动。

公式1:小角度摆的简谐运动

我们可以轻松地将这个函数编写为代码,并模拟一个简单摆。

def simple_pendulum(theta_0, omega, t, phi):    theta = theta_0*np.cos(omega*t + phi)    return theta#系统的参数theta_0 = np.radians(15) #角度转弧度g = 9.8 #m/s^2l = 1.0 #momega = np.sqrt(g/l)phi = 0 #对于小角度time_span = np.linspace(0,20,300) #模拟20秒,分成300个时间间隔theta = []for t in time_span:    theta.append(simple_pendulum(theta_0, omega, t, phi))#转换为笛卡尔坐标x = l*np.sin(theta)y = -l*np.cos(theta) #取负号确保摆向下
图3:简单摆模拟

使用拉格朗日力学的完整摆动运动

一个简单的小角度摆可以作为一个良好的起点,但我们想要超越这个,模拟一个完整的摆的运动。因为我们不能再使用小角度近似,所以最好使用Lagrange力学模型来描述摆的运动。这是物理学中的一个基本工具,它将我们从系统中的力转变为系统中的能量。我们将参考框架从驱动力与恢复力转变为动能与势能。

Lagrangian是动能与势能之差,由方程2给出。

方程2:Lagrangian

将方程3中给出的摆的动能和势能代入,得到方程4描述的摆的Lagrangian。

方程3:摆的动能和势能
方程4:摆的Lagrangian

有了摆的Lagrangian,我们现在可以描述系统的能量。还有最后一步需要通过欧拉-拉格朗日方程将能量参考转换为动力学/力导向参考。使用这个方程,我们可以使用Lagrangian获取摆的角加速度。

方程5:通过欧拉-拉格朗日方程求得角加速度

经过数学推导,我们得到了角加速度,可以用它来计算角速度和角度本身。这将需要一些数值积分,这将在我们的完整摆模拟中进行说明。即使对于单个摆,非线性动力学意味着没有解析解来求解θ,因此需要数值解。积分非常简单(但强大),我们使用角加速度更新角速度,使用角速度更新θ,将前者添加到后者并乘以一些时间步长。这给我们一个加速度/速度曲线下的面积的近似值。时间步长越小,近似值越准确。

def full_pendulum(g,l,theta,theta_velocity, time_step):    #数值积分    theta_acceleration = -(g/l)*np.sin(theta) #得到加速度    theta_velocity += time_step*theta_acceleration #用加速度更新速度    theta += time_step*theta_velocity #用角速度更新角度    return theta, theta_velocityg = 9.8 #m/s^2l = 1.0 #mtheta = [np.radians(90)] #theta_0theta_velocity = 0 #初始速度为0time_step = 20/300 #定义一个时间步长time_span = np.linspace(0,20,300) #模拟20秒,分成300个时间间隔for t in time_span:    theta_new, theta_velocity = full_pendulum(g,l,theta[-1], theta_velocity, time_step)    theta.append(theta_new)#转换回笛卡尔坐标 x = l*np.sin(theta)y = -l*np.cos(theta)
图4:完整摆的模拟

我们已经模拟了一个完整的摆,但这仍然是一个明确定义的系统。现在是时候进入双摆的混沌世界了。

混沌与双摆

在数学意义上,混沌指的是对初始条件非常敏感的系统。即使是微小的系统起始变化也会导致系统行为的巨大差异。这完美地描述了双摆的运动。与单摆不同,双摆不是一个表现良好的系统,即使是轻微的起始角度变化也会导致系统以完全不同的方式演化。

为了模拟双摆的运动,我们将使用与之前相同的拉格朗日方法(见完整推导)。

模拟105:使用数值积分进行双摆模型建模 四海 第10张

在将此方程实现为代码并找到θ时,我们将使用与之前相同的数值积分方案。

#获取θ1加速度def theta1_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g):    mass1 = -g*(2*m1 + m2)*np.sin(theta1)    mass2 = -m2*g*np.sin(theta1 - 2*theta2)    interaction = -2*np.sin(theta1 - theta2)*m2*np.cos(theta2_velocity**2*l2 + theta1_velocity**2*l1*np.cos(theta1 - theta2))    normalization = l1*(2*m1 + m2 - m2*np.cos(2*theta1 - 2*theta2))        theta1_ddot = (mass1 + mass2 + interaction)/normalization        return theta1_ddot#获取θ2加速度def theta2_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g):    system = 2*np.sin(theta1 - theta2)*(theta1_velocity**2*l1*(m1 + m2) + g*(m1 + m2)*np.cos(theta1) + theta2_velocity**2*l2*m2*np.cos(theta1 - theta2))    normalization = l1*(2*m1 + m2 - m2*np.cos(2*theta1 - 2*theta2))        theta2_ddot = system/normalization    return theta2_ddot#更新θ1def theta1_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step):    #数值积分    theta1_velocity += time_step*theta1_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g)    theta1 += time_step*theta1_velocity    return theta1, theta1_velocity#更新θ2def theta2_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step):    #数值积分    theta2_velocity += time_step*theta2_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g)    theta2 += time_step*theta2_velocity    return theta2, theta2_velocity#运行完整的双摆def double_pendulum(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step,time_span):    theta1_list = [theta1]    theta2_list = [theta2]        for t in time_span:        theta1, theta1_velocity = theta1_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)        theta2, theta2_velocity = theta2_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)        theta1_list.append(theta1)        theta2_list.append(theta2)        x1 = l1*np.sin(theta1_list) #摆1 x    y1 = -l1*np.cos(theta1_list) #摆1 y    x2 = l1*np.sin(theta1_list) + l2*np.sin(theta2_list) #摆2 x    y2 = -l1*np.cos(theta1_list) - l2*np.cos(theta2_list) #摆2 y        return x1,y1,x2,y2

#定义系统参数g = 9.8 #m/s^2m1 = 1 #kgm2 = 1 #kgl1 = 1 #ml2 = 1 #mtheta1 = np.radians(90)theta2 = np.radians(45)theta1_velocity = 0 #m/stheta2_velocity = 0 #m/stheta1_list = [theta1]theta2_list = [theta2]time_step = 20/300time_span = np.linspace(0,20,300)x1,y1,x2,y2 = double_pendulum(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step,time_span)
图5:双摆模拟

我们终于做到了!我们成功地建模了一个双摆,现在是观察一些混沌的时候了。我们最终的模拟将是两个具有稍微不同起始条件的双摆。我们将设置一个摆角1为90度,另一个摆角1为91度。让我们看看会发生什么。

图6:两个具有稍微不同起始条件的双摆

我们可以看到,两个摆的初始轨迹相似,但很快就开始发散。这就是我们所说的混沌,即使角度相差1度,也会导致完全不同的最终行为。

结论

在本文中,我们学习了关于摆动运动及其建模的知识。我们从最简单的谐振运动模型开始,逐步发展到复杂且混沌的双摆模型。在此过程中,我们了解了拉格朗日方程、混沌和数值积分的知识。

双摆是混沌系统的最简单示例。这些系统存在于我们的世界的各个领域,包括人口动态、气候甚至台球。我们可以从双摆中学到的教训,并在遇到混沌系统时加以应用。

要点

  • 混沌系统对初始条件非常敏感,在起始条件稍微改变时,它们的演变方式会发生巨大变化。
  • 在处理一个系统,尤其是混沌系统时,是否有另一个参考系可以使其更容易处理?(例如,从力参考系转换到能量参考系)
  • 当系统变得过于复杂时,我们需要实施数值解来解决问题。这些解法简单而强大,并提供了对实际行为的良好近似。

参考资料

本文中使用的所有图均由作者创建或来自Math Images,并遵循GNU Free Documentation License 1.2

一切都是谐振子

物理学本科生可能开玩笑说宇宙是由谐振子构成的,但他们并不离谱。

www.wired.com

经典力学,约翰·泰勒 https://neuroself.files.wordpress.com/2020/09/taylor-2005-classical-mechanics.pdf

完整代码

简单摆

def makeGif(x,y,name):    !mkdir frames        counter=0    images = []    for i in range(0,len(x)):        plt.figure(figsize = (6,6))        plt.plot([0,x[i]],[0,y[i]], "o-", color = "b", markersize = 7, linewidth=.7 )        plt.title("Pendulum")        plt.xlim(-1.1,1.1)        plt.ylim(-1.1,1.1)        plt.savefig("frames/" + str(counter)+ ".png")        images.append(imageio.imread("frames/" + str(counter)+ ".png"))        counter += 1        plt.close()    imageio.mimsave(name, images)    !rm -r framesdef simple_pendulum(theta_0, omega, t, phi):    theta = theta_0*np.cos(omega*t + phi)    return theta#parameters of our systemtheta_0 = np.radians(15) #degrees to radiansg = 9.8 #m/s^2l = 1.0 #momega = np.sqrt(g/l)phi = 0 #for small angletime_span = np.linspace(0,20,300) #simulate for 20s split into 300 time intervalstheta = []for t in time_span:    theta.append(simple_pendulum(theta_0, omega, t, phi))x = l*np.sin(theta)y = -l*np.cos(theta) #negative to make sure the pendulum is facing down

摆锤

def full_pendulum(g,l,theta,theta_velocity, time_step):    theta_acceleration = -(g/l)*np.sin(theta)    theta_velocity += time_step*theta_acceleration    theta += time_step*theta_velocity    return theta, theta_velocityg = 9.8 #m/s^2l = 1.0 #mtheta = [np.radians(90)] #theta_0theta_velocity = 0time_step = 20/300time_span = np.linspace(0,20,300) #模拟20秒,分成300个时间间隔for t in time_span:    theta_new, theta_velocity = full_pendulum(g,l,theta[-1], theta_velocity, time_step)    theta.append(theta_new)#转回笛卡尔坐标 x = l*np.sin(theta)y = -l*np.cos(theta)#使用简单摆函数makeGif(x,y,"pendulum.gif")

双摆

def theta1_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g):    mass1 = -g*(2*m1 + m2)*np.sin(theta1)    mass2 = -m2*g*np.sin(theta1 - 2*theta2)    interaction = -2*np.sin(theta1 - theta2)*m2*np.cos(theta2_velocity**2*l2 + theta1_velocity**2*l1*np.cos(theta1 - theta2))    normalization = l1*(2*m1 + m2 - m2*np.cos(2*theta1 - 2*theta2))        theta1_ddot = (mass1 + mass2 + interaction)/normalization        return theta1_ddotdef theta2_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g):    system = 2*np.sin(theta1 - theta2)*(theta1_velocity**2*l1*(m1 + m2) + g*(m1 + m2)*np.cos(theta1) + theta2_velocity**2*l2*m2*np.cos(theta1 - theta2))    normalization = l1*(2*m1 + m2 - m2*np.cos(2*theta1 - 2*theta2))        theta2_ddot = system/normalization    return theta2_ddotdef theta1_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step):        theta1_velocity += time_step*theta1_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g)    theta1 += time_step*theta1_velocity    return theta1, theta1_velocitydef theta2_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step):        theta2_velocity += time_step*theta2_acceleration(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g)    theta2 += time_step*theta2_velocity    return theta2, theta2_velocitydef double_pendulum(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step,time_span):    theta1_list = [theta1]    theta2_list = [theta2]        for t in time_span:        theta1, theta1_velocity = theta1_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)        theta2, theta2_velocity = theta2_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)        theta1_list.append(theta1)        theta2_list.append(theta2)        x1 = l1*np.sin(theta1_list)    y1 = -l1*np.cos(theta1_list)    x2 = l1*np.sin(theta1_list) + l2*np.sin(theta2_list)    y2 = -l1*np.cos(theta1_list) - l2*np.cos(theta2_list)        return x1,y1,x2,y2

#定义系统参数,运行双摆g = 9.8 #m/s^2m1 = 1 #kgm2 = 1 #kgl1 = 1 #ml2 = 1 #mtheta1 = np.radians(90)theta2 = np.radians(45)theta1_velocity = 0 #m/stheta2_velocity = 0 #m/stheta1_list = [theta1]theta2_list = [theta2]time_step = 20/300time_span = np.linspace(0,20,300)for t in time_span:    theta1, theta1_velocity = theta1_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)    theta2, theta2_velocity = theta2_update(m1,m2,l1,l2,theta1,theta2,theta1_velocity,theta2_velocity,g,time_step)        theta1_list.append(theta1)    theta2_list.append(theta2)    x1 = l1*np.sin(theta1_list)y1 = -l1*np.cos(theta1_list)x2 = l1*np.sin(theta1_list) + l2*np.sin(theta2_list)y2 = -l1*np.cos(theta1_list) - l2*np.cos(theta2_list)

#制作gif文件!mkdir frames    counter=0images = []for i in range(0,len(x1)):    plt.figure(figsize = (6,6))    plt.figure(figsize = (6,6))    plt.plot([0,x1[i]],[0,y1[i]], "o-", color = "b", markersize = 7, linewidth=.7 )    plt.plot([x1[i],x2[i]],[y1[i],y2[i]], "o-", color = "b", markersize = 7, linewidth=.7 )    plt.title("双摆")    plt.xlim(-2.1,2.1)    plt.ylim(-2.1,2.1)    plt.savefig("frames/" + str(counter)+ ".png")    images.append(imageio.imread("frames/" + str(counter)+ ".png"))    counter += 1    plt.close()imageio.mimsave("double_pendulum.gif", images)!rm -r frames
Leave a Reply

Your email address will not be published. Required fields are marked *