我只是在游戏中测试几种轨道动力学的集成方案。 我在这里采用了RK4,并采用了自适应步骤 http://www.physics.buffalo.edu/phy410-505/2011/topic2/app1/index.html
我将它与简单的verlet集成(和euler,但它的性能非常差)进行了比较。似乎RK4具有恒定步长优于verlet。具有自适应步骤的RK4更好,但不是那么多。我想知道我做错了什么?或者说在哪种意义上说RK4比verlet要好得多?
我们认为Force的每个RK4步骤评估为4x,但每个verlet步骤只评估1x。所以为了获得相同的性能,我可以为verlet设置更小的time_step 4x。时间步长小4倍,比RK4更精确,步长恒定,几乎可与RK4相比,具有自适应步骤。
查看图片: https://lh4.googleusercontent.com/-I4wWQYV6o4g/UW5pK93WPVI/AAAAAAAAA7I/PHSsp2nEjx0/s800/kepler.png
10T意味着10个轨道周期,以下数字48968,7920,48966是需要的力量评估数量
python代码(使用pylab)如下:
from pylab import *
import math
G_m1_plus_m2 = 4 * math.pi**2
ForceEvals = 0
def getForce(x,y):
global ForceEvals
ForceEvals += 1
r = math.sqrt( x**2 + y**2 )
A = - G_m1_plus_m2 / r**3
return x*A,y*A
def equations(trv):
x = trv[0]; y = trv[1]; vx = trv[2]; vy = trv[3];
ax,ay = getForce(x,y)
flow = array([ vx, vy, ax, ay ])
return flow
def SimpleStep( x, dt, flow ):
x += dt*flow(x)
def verletStep1( x, dt, flow ):
ax,ay = getForce(x[0],x[1])
vx = x[2] + dt*ax; vy = x[3] + dt*ay;
x[0]+= vx*dt; x[1]+= vy*dt;
x[2] = vx; x[3] = vy;
def RK4_step(x, dt, flow): # replaces x(t) by x(t + dt)
k1 = dt * flow(x);
x_temp = x + k1 / 2; k2 = dt * flow(x_temp)
x_temp = x + k2 / 2; k3 = dt * flow(x_temp)
x_temp = x + k3 ; k4 = dt * flow(x_temp)
x += (k1 + 2*k2 + 2*k3 + k4) / 6
def RK4_adaptive_step(x, dt, flow, accuracy=1e-6): # from Numerical Recipes
SAFETY = 0.9; PGROW = -0.2; PSHRINK = -0.25; ERRCON = 1.89E-4; TINY = 1.0E-30
scale = flow(x)
scale = abs(x) + abs(scale * dt) + TINY
while True:
x_half = x.copy(); RK4_step(x_half, dt/2, flow); RK4_step(x_half, dt/2, flow)
x_full = x.copy(); RK4_step(x_full, dt , flow)
Delta = (x_half - x_full)
error = max( abs(Delta[:] / scale[:]) ) / accuracy
if error <= 1:
break;
dt_temp = SAFETY * dt * error**PSHRINK
if dt >= 0:
dt = max(dt_temp, 0.1 * dt)
else:
dt = min(dt_temp, 0.1 * dt)
if abs(dt) == 0.0:
raise OverflowError("step size underflow")
if error > ERRCON:
dt *= SAFETY * error**PGROW
else:
dt *= 5
x[:] = x_half[:] + Delta[:] / 15
return dt
def integrate( trv0, dt, F, t_max, method='RK4', accuracy=1e-6 ):
global ForceEvals
ForceEvals = 0
trv = trv0.copy()
step = 0
t = 0
print "integrating with method: ",method," ... "
while True:
if method=='RK4adaptive':
dt = RK4_adaptive_step(trv, dt, equations, accuracy)
elif method=='RK4':
RK4_step(trv, dt, equations)
elif method=='Euler':
SimpleStep(trv, dt, equations)
elif method=='Verlet':
verletStep1(trv, dt, equations)
step += 1
t+=dt
F[:,step] = trv[:]
if t > t_max:
break
print " step = ", step
# ============ MAIN PROGRAM BODY =========================
r_aphelion = 1
eccentricity = 0.95
a = r_aphelion / (1 + eccentricity)
T = a**1.5
vy0 = math.sqrt(G_m1_plus_m2 * (2 / r_aphelion - 1 / a))
print " Semimajor axis a = ", a, " AU"
print " Period T = ", T, " yr"
print " v_y(0) = ", vy0, " AU/yr"
dt = 0.0003
accuracy = 0.0001
# x y vx vy
trv0 = array([ r_aphelion, 0, 0, vy0 ])
def testMethod( trv0, dt, fT, n, method, style ):
print " "
F = zeros((4,n));
integrate(trv0, dt, F, T*fT, method, accuracy);
print "Periods ",fT," ForceEvals ", ForceEvals
plot(F[0],F[1], style ,label=method+" "+str(fT)+"T "+str( ForceEvals ) );
testMethod( trv0, dt, 10, 20000 , 'RK4', '-' )
testMethod( trv0, dt, 10, 10000 , 'RK4adaptive', 'o-' )
testMethod( trv0, dt/4, 10, 100000, 'Verlet', '-' )
#testMethod( trv0, dt/160, 2, 1000000, 'Euler', '-' )
legend();
axis("equal")
savefig("kepler.png")
show();
答案 0 :(得分:9)
我知道这个问题现在还很老了,但这与其中一种方法的“优越性”无关,或者与你的编程有关 - 他们只是善于处理不同的事情。 (所以不,这个答案不会真正与代码有关。甚至是关于编程。它更多的是数学,真的......)
Runge-Kutta解算器系列非常擅长以相当好的精度攻击几乎所有问题,并且在自适应方法的情况下,性能。但是,它们不是symplectic,这很快意味着它们不能在问题中节省能量。
另一方面,Verlet方法可能需要比RK方法小得多的步长,以便最小化解决方案中的振荡,但该方法是辛的。你的问题是节约能源;在任意转数之后,行星体的总能量(动能+电位)应与初始条件下的相同。对于Verlet积分器(至少作为时间窗口平均值),情况确实如此,但RK系列的积分器不会这样 - 随着时间的推移,RK求解器会产生一个由于能量而不会减少的误差在数值积分中迷失了。
要验证这一点,请尝试在每个时间步骤中保存系统中的总能量,并绘制它(您可能需要做十次以上的旋转才能注意到差异)。 RK方法将稳定地降低能量,而Verlet方法将在恒定能量周围产生振荡。
如果这是你需要解决的确切问题,我还推荐开普勒方程,它可以解析这个问题。即使对于具有许多行星,卫星等的相当复杂的系统,行星际相互作用也是如此微不足道,以至于您可以单独使用每个物体的开普勒方程,并且它的旋转中心可以单独使用而不会损失太多精度。但是,如果你正在编写一个游戏,你可能真的对其他一些问题感兴趣,这只是一个学习的例子 - 在这种情况下,阅读各种求解器族的属性,并选择一个适合于你的问题。
答案 1 :(得分:3)
我不知道我是否会回答你的具体问题,但这是我的想法。
您定义了一个非常简单的力模型。在这种情况下,保存一些步骤可能无法提高性能,因为计算RK4中的新步骤可能需要更长时间。如果力模型更复杂,带自适应步骤的RK4可以为您节省更多时间。从你的情节我认为Verlet也正在偏离正确的解决方案,一个重复的椭圆。
对于轨道力学,您还可以尝试使用RK7(8)自适应积分器,Adams-Bashforth multistep或Gauss Jackson方法。这是一篇论文,展示了其中一些方法:http://drum.lib.umd.edu/bitstream/1903/2202/7/2004-berry-healy-jas.pdf
最后,如果你的力模型总是一个简单的中心力量,就像在这个例子中一样,看一下开普勒方程。解决它是精确,快速,你可以跳到任意时间。
答案 2 :(得分:1)
好的,最后,我使用了自适应Runge-Kutta-Fehlberg(RKF45)。 有趣的是,当我要求更高的精度(最佳是1E-9)时,它更快(需要更少的步骤),因为在较低的精度(<1e-6)下,解决方案是不稳定的,并且通过放弃的步骤浪费了大量的迭代(这些步骤是长期和不准确的)。当我要求更好的精确度(1E-12)时,由于时间步长较短,因此需要更多步骤。
对于圆形轨道,可以在高达3倍速度增益的情况下判定(1e-5),但是当我需要模拟高度偏心(椭圆轨道)时,我宁愿保持安全的1E-9精度。
如果有人解决类似的问题,有代码 http://www.openprocessing.org/sketch/96977 它还显示了模拟一个时间单位的轨迹长度需要多少力评估