Physics 试图模拟一维波

Physics 试图模拟一维波,physics,algorithm,numerical-methods,Physics,Algorithm,Numerical Methods,我试图用一系列谐振子对一维波做一个简化的模拟。根据教科书,第i个振荡器的位置x[i](假设在x[i]=0时平衡)的微分方程是: m*x[i]''=-k(2*x[i]-x[i-1]-x[i+1]) (导数随时间变化)因此我尝试用以下算法数值计算动力学。这里,osc[i]是作为具有属性的对象的第i个振荡器,loc(绝对位置)、vel(速度)、acc(加速度)和bloc(平衡位置),dt是时间增量: for every i: osc[i].vel+=dt*osc[i].acc; osc

我试图用一系列谐振子对一维波做一个简化的模拟。根据教科书,第i个振荡器的位置
x[i]
(假设在
x[i]=0时平衡)的微分方程是:

m*x[i]''=-k(2*x[i]-x[i-1]-x[i+1])

(导数随时间变化)因此我尝试用以下算法数值计算动力学。这里,
osc[i]
是作为具有属性的对象的第i个振荡器,
loc
(绝对位置)、
vel
(速度)、
acc
(加速度)和
bloc
(平衡位置),
dt
是时间增量:

for every i:
    osc[i].vel+=dt*osc[i].acc;
    osc[i].loc+=dt*osc[i].vel;
    osc[i].vel*=0.99;    
    osc[i].acc=-k*2*(osc[i].loc-osc[i].bloc);

    if(i!=0){
      osc[i].acc+=+k*(osc[i-1].loc-osc[i-1].bloc);
    }
    if(i!=N-1){
      osc[i].acc+=+k*(osc[i+1].loc-osc[i+1].bloc);
    }

你可以看到算法在运行,只需点击给第六个振荡器一个脉冲。你可以看到,它不仅不会产生波浪,而且会产生总能量不断增加的运动(即使我加上了阻尼!)。我做错了什么?

随时间增长的振幅很可能是您使用的简单Euler积分的产物。您可以尝试使用更短的时间步长,或者尝试切换到辛欧拉,它具有更好的能量守恒特性

至于奇怪的传播行为(波似乎传播得很慢),可能是弹簧常数(k)相对于粒子质量太低

另外,你发布的等式看起来有点错误,因为它应该涉及k/m,而不是k*m。也就是说,方程式应该是

x[i]''=-k/m(2*x[i]-x[i-1]-x[i+1])

(c.f.)。但是,这只会影响传播的总体速度。

您在代码中用两种重要方式错误地表达了初始方程:

首先,请注意,该方程仅表示相对失真;也就是说,在等式中,如果
x[i]==x[i-1]==x[i+1]
那么
x“[i]=0
无论距离
x[i]
是从零开始的。在您的代码中,您测量的是每个粒子与平衡点之间的绝对距离。也就是说,方程只在边界处固定振荡器行,就像两端固定的一个弹簧,但您模拟的是一整套小弹簧,每个弹簧固定在某个“平衡”点上

其次,你的术语如
osc[i].acc+=+k*(osc[i-1].loc-osc[i-1].bloc);
没有多大意义。这里你设置的
osc[i].acc
完全基于它旁边粒子的绝对位置,而不是两者之间的相对位置

第二个问题可能是你获得能量的原因,但这只是做错误模拟的副作用,不一定意味着你的模拟产生了错误。目前没有证据表明你需要改变简单的Euler模拟(如Nathan所建议的).首先要得到正确的方程式,如果需要的话,用模拟的方法进行模拟

相反,编写相对位移的方程,即使用类似于
osc.loc[i]-osc.loc[i-1]
的术语


评论:我很少喜欢评论别人对我回答的问题的回答,但Nathan和ja72都关注的不是主要问题。首先纠正模拟方程,然后,可能会担心比Euler更奇特的方法,以及更新模型的顺序术语等,如果需要的话。对于一个简单的线性一阶方程,特别是有一点阻尼的方程,如果时间步长足够小,直接欧拉方法可以很好地工作,所以首先让它像这样工作。

对于一个例子,你的加速度
osc[i]。这里的acc
取决于更新的位置
osc[i-1].loc
和尚未更新的位置
osc[i+1].loc

首先需要计算所有节点的所有加速度,然后在单独的循环中更新位置和速度

// calculate accelerations
// each spring has length L
// work out deflections of previous and next spring
for(i=1..N)
{
   prev_pos = if(i>1, pos[i-1], 0)
   next_pos = if(i<N, pos[i+1], i*L)
   prev_del = (pos[i]-prev_pos)-L
   next_del = (next_pos-pos[i])-L
   acc[i] = -(k/m)*(prev_del-next_del)
}

// calculate next step, with semi-implicit Euler
// step size is h
for(i=1..N)
{
    vel[i] = vel[i] + h*acc[i]
    pos[i] = pos[i] + h*vel[i]
}
最好创建一个函数,返回给定时间、位置和速度的加速度

// calculate accelerations
// each spring has length L
// work out deflections of previous and next spring
for(i=1..N)
{
   prev_pos = if(i>1, pos[i-1], 0)
   next_pos = if(i<N, pos[i+1], i*L)
   prev_del = (pos[i]-prev_pos)-L
   next_del = (next_pos-pos[i])-L
   acc[i] = -(k/m)*(prev_del-next_del)
}

// calculate next step, with semi-implicit Euler
// step size is h
for(i=1..N)
{
    vel[i] = vel[i] + h*acc[i]
    pos[i] = pos[i] + h*vel[i]
}
//计算加速度
//每个弹簧的长度为L
//计算上一个和下一个春天的挠度
对于(i=1..N)
{
上一个位置=如果(i>1,位置[i-1],0)

next_pos=if(i所有给出的答案都提供了大量有用的信息。 你必须做的是:

  • 使用ja72的观察来执行更新-您需要连贯一致,并根据来自同一迭代批的位置计算节点处的加速度(即,不要混合$x)⁽K⁾$ 和x美元⁽k+1⁾$ 在相同的加速度表达式中,这些是属于不同迭代步骤的量)
  • 忘记To10所说的原始位置-你只需要考虑相对位置-这个波动方程的行为就像一个拉普拉斯平滑滤波器应用到一个多边形线-一个点被放松使用它的两个直接连接的邻居,被拉到由这些邻居确定的段的中间。/李>
  • 最后,如果你希望你的能量得到保存(即水面不会停止振动)这样的模拟不需要高阶积分器,但如果你有时间,考虑使用VILET或鲁思森林,因为它们都是辛和精度为2或3的。Runge Kutta将像隐式欧拉一样,汲取能量(慢得多)。从系统中,你会得到固有的阻尼。显式欧拉最终会给你带来厄运-这是一个糟糕的选择-总是(特别是对于刚性或无阻尼系统)。还有很多其他积分器,所以继续实验吧
p.S.您没有实现真正的隐式Euler,因为它需要同时影响所有粒子。为此,您必须使用投影共轭梯度