试图模拟一维波

时间:2013-09-03 20:15:24

标签: 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].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);
    }

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

4 个答案:

答案 0 :(得分:4)

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

  • 使用ja72的观察来执行更新 - 您需要连贯并根据同一迭代批次中的位置计算节点的加速度(即不要混合$x⁽k⁾$和$x⁽k +1⁾$在加速度的相同表达式中,因为这些是属于不同迭代步骤的金额)
  • 忘记你的原始位置,正如tom10所说 - 你只需要考虑相对位置 - 这个波动方程的行为就像应用于折线的拉普拉斯平滑滤波器 - 使用它的两个直接连接的邻居放松一个点,拉向这些邻居确定的细分中间
  • 最后,如果你想节约你的能量(即没有让水面停止振动),那么使用辛积分器是值得的。辛/半隐式欧拉会做。对于这种模拟,您不需要更高阶的积分器,但是如果您有时间,请考虑使用Verlet或Ruth-Forest,因为它们既是辛的又是2或3的精度。 Runge-Kutta将像隐式Euler一样从系统中吸取能量(慢得多),因此你将获得固有阻尼。明确的欧拉最终会为你拼写厄运 - 这是一个糟糕的选择 - 总是(特别是对于僵硬或无阻尼的系统)。那里还有很多其他集成商,所以继续进行实验。

P.S。你没有实现真正的隐式Euler,因为那个需要同时影响所有粒子。为此,您必须使用投影共轭梯度方法,导出雅可比行列式并为您的粒子串求解稀疏线性系统。显式或半隐式方法将使您获得动画所期望的“跟随领导者”行为。

现在,如果你想要更多,我在SciLab中实现并测试了这个答案(懒得用C ++编程):

n=50;
for i=1:n
    x(i) = 1;
end

dt = 0.02;

k = 0.05;
x(20) = 1.1;
xold = x;
v = 0 * x;
plot(x, 'r');
plot(x*0,'r');
for iteration=1:10000
    for i = 1:n
        if (i>1 & i < n) then
            acc = k*(0.5*(xold(i-1) + xold(i+1)) - xold(i));
            v(i) = v(i) + dt * acc;
            x(i) = xold(i) + v(i) *dt;
        end
    end
    if (iteration/500  == round(iteration / 500) ) then
        plot(x - iteration/10000,'g');

    end
    xold = x;
end
plot(x,'b');

波形演变见下图: enter image description here

答案 1 :(得分:2)

随着时间的推移,增长幅度可能是您正在使用的简单Euler积分的一个假象。您可以尝试使用较短的时间步长,或尝试切换到semi-implicit Euler,也就是辛欧拉,它具有更好的节能特性。

对于奇数传播行为(波似乎传播非常缓慢),可能是弹簧常数(k)相对于粒子质量太低。

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

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

(c.f。this Wikipedia page)。然而,这只会影响整体传播速度。

答案 2 :(得分:2)

您已在两个重要方面错误地在代码中表达了初始等式

首先,请注意该等式仅表示相对失真;也就是说,在等式中,如果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,而不是两者之间的相对位置。

这第二个问题可能是你获得能量的原因,但这只是做错误模拟的副作用,并不一定意味着你的模拟会产生错误。目前没有证据表明你需要改变简单的欧拉模拟(正如内森所建议的那样)。首先得到方程正确,然后如果需要,可以使用模拟方法。

相反,请编写相对位移的等式,即使用osc.loc[i]-osc.loc[i-1]等词语。

评论我很少评论别人对我所回答的问题的答案,但Nathan和ja72都专注于那些不是主要问题的事情。 首先让你的模拟方程正确,然后,也许,担心比欧拉更漂亮的方法,以及更新你的术语等的顺序,如果你需要的话。对于一个简单的,线性的一阶方程,特别是有一点阻尼,如果时间步长足够小,直接Euler方法可以正常工作,所以首先让它像这样工作。

答案 3 :(得分:2)

对于您的加速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]
}

您可能需要使用更高阶的积分器方案。从隐式Runge-Kutta的第二个订单开始。