我正在尝试使用一系列谐波振荡器对一维波进行简化模拟。第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个振荡器发出冲动。你可以看到它不仅不会产生波浪,而且还会产生一个总能量增加的运动(即使我加了阻尼!)。我做错了什么?
答案 0 :(得分:4)
所有给出的答案都提供了(大量)有用的信息。 你必须做的是:
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');
波形演变见下图:
答案 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的第二个订单开始。