我的重力模拟有什么问题?

时间:2013-03-02 16:49:05

标签: algorithm math simulation astronomy runge-kutta

根据this answer给我的建议,我在我的引力模拟器中实现了Runge-Kutta积分器。

然而,在我模拟一年的太阳系之后,这些位置仍然偏离了110,000公里,这是不可接受的。

我的初始数据是由NASA的HORIZONS系统提供的。通过它,我获得了行星,冥王星,月亮,Deimos和Phobos在特定时间点的位置和速度矢量。

这些矢量是3D的,然而,有些人告诉我,我可以忽略第三维,因为行星在太阳周围的板块中对齐,所以我做到了。我只是将x-y坐标复制到我的文件中。

这是我改进的更新方法的代码:

"""
Measurement units:

[time] = s
[distance] = m
[mass] = kg
[velocity] = ms^-1
[acceleration] = ms^-2
"""

class Uni:
    def Fg(self, b1, b2):
        """Returns the gravitational force acting between two bodies as a Vector2."""

        a = abs(b1.position.x - b2.position.x) #Distance on the x axis
        b = abs(b1.position.y - b2.position.y) #Distance on the y axis

        r = math.sqrt(a*a + b*b)

        fg = (self.G * b1.m * b2.m) / pow(r, 2)

        return Vector2(a/r * fg, b/r * fg)

    #After this is ran, all bodies have the correct accelerations:
    def updateAccel(self):
        #For every combination of two bodies (b1 and b2) out of all bodies:
        for b1, b2 in combinations(self.bodies.values(), 2):
            fg = self.Fg(b1, b2) #Calculate the gravitational force between them

            #Add this force to the current force vector of the body:
            if b1.position.x > b2.position.x:
                b1.force.x -= fg.x
                b2.force.x += fg.x
            else:
                b1.force.x += fg.x
                b2.force.x -= fg.x


            if b1.position.y > b2.position.y:
                b1.force.y -= fg.y
                b2.force.y += fg.y
            else:
                b1.force.y += fg.y
                b2.force.y -= fg.y

        #For body (b) in all bodies (self.bodies.itervalues()):
        for b in self.bodies.itervalues():
            b.acceleration.x = b.force.x/b.m
            b.acceleration.y = b.force.y/b.m
            b.force.null() #Reset the force as it's not needed anymore.

    def RK4(self, dt, stage):
        #For body (b) in all bodies (self.bodies.itervalues()):
        for b in self.bodies.itervalues():
            rd = b.rk4data #rk4data is an object where the integrator stores its intermediate data

            if stage == 1:
                rd.px[0] = b.position.x
                rd.py[0] = b.position.y
                rd.vx[0] = b.velocity.x
                rd.vy[0] = b.velocity.y
                rd.ax[0] = b.acceleration.x
                rd.ay[0] = b.acceleration.y

            if stage == 2:
                rd.px[1] = rd.px[0] + 0.5*rd.vx[0]*dt
                rd.py[1] = rd.py[0] + 0.5*rd.vy[0]*dt
                rd.vx[1] = rd.vx[0] + 0.5*rd.ax[0]*dt
                rd.vy[1] = rd.vy[0] + 0.5*rd.ay[0]*dt
                rd.ax[1] = b.acceleration.x
                rd.ay[1] = b.acceleration.y

            if stage == 3:
                rd.px[2] = rd.px[0] + 0.5*rd.vx[1]*dt
                rd.py[2] = rd.py[0] + 0.5*rd.vy[1]*dt
                rd.vx[2] = rd.vx[0] + 0.5*rd.ax[1]*dt
                rd.vy[2] = rd.vy[0] + 0.5*rd.ay[1]*dt
                rd.ax[2] = b.acceleration.x
                rd.ay[2] = b.acceleration.y

            if stage == 4:
                rd.px[3] = rd.px[0] + rd.vx[2]*dt
                rd.py[3] = rd.py[0] + rd.vy[2]*dt
                rd.vx[3] = rd.vx[0] + rd.ax[2]*dt
                rd.vy[3] = rd.vy[0] + rd.ay[2]*dt
                rd.ax[3] = b.acceleration.x
                rd.ay[3] = b.acceleration.y

            b.position.x = rd.px[stage-1]
            b.position.y = rd.py[stage-1]

    def update (self, dt):
        """Pushes the uni 'dt' seconds forward in time."""

        #Repeat four times:
        for i in range(1, 5, 1):
            self.updateAccel() #Calculate the current acceleration of all bodies
            self.RK4(dt, i) #ith Runge-Kutta step

        #Set the results of the Runge-Kutta algorithm to the bodies:
        for b in self.bodies.itervalues():
            rd = b.rk4data
            b.position.x = b.rk4data.px[0] + (dt/6.0)*(rd.vx[0] + 2*rd.vx[1] + 2*rd.vx[2] + rd.vx[3]) #original_x + delta_x
            b.position.y = b.rk4data.py[0] + (dt/6.0)*(rd.vy[0] + 2*rd.vy[1] + 2*rd.vy[2] + rd.vy[3])

            b.velocity.x = b.rk4data.vx[0] + (dt/6.0)*(rd.ax[0] + 2*rd.ax[1] + 2*rd.ax[2] + rd.ax[3])
            b.velocity.y = b.rk4data.vy[0] + (dt/6.0)*(rd.ay[0] + 2*rd.ay[1] + 2*rd.ay[2] + rd.ay[3])

        self.time += dt #Internal time variable

算法如下:

  1. 更新系统中所有实体的加速度
  2. RK4(第一步)
  3. 转到1
  4. RK4(第二)
  5. 转到1
  6. RK4(第三)
  7. 转到1
  8. RK4(第四)
  9. 我的RK4实施是否搞砸了?或者我只是从损坏的数据开始(重要的机构太少而忽略了第三维)?

    如何解决这个问题?


    我的数据说明......

    我所有的坐标都是相对于太阳的(即太阳位于(0,0))。

    ./my_simulator 1yr
    
    Earth position: (-1.47589927462e+11, 18668756050.4)
    
    HORIZONS (NASA):
    
    Earth position: (-1.474760457316177E+11,  1.900200786726017E+10)
    

    我通过从我的模拟器预测的那个减去NASA给出的地球x坐标得到110 000 km错误。

    relative error = (my_x_coordinate - nasa_x_coordinate) / nasa_x_coordinate * 100
                   = (-1.47589927462e+11 + 1.474760457316177E+11) / -1.474760457316177E+11 * 100
                   = 0.077%
    

    相对误差似乎微不足道,但这仅仅是因为在我的模拟和NASA中,地球离太阳太远了。距离仍然巨大,使我的模拟器无用。

6 个答案:

答案 0 :(得分:4)

110 000 km绝对误差意味着什么相对误差?

  

我通过减去预测的地球x得到了110 000 km的值   与美国宇航局的地球x坐标协调。

我不确定你在这里计算什么,或者你的意思是“NASA的地球x坐标”。距离什么原点,坐标系是什么时间? (据我所知,地球围绕太阳运行,所以它的x坐标w.r.t.一个以太阳为中心的坐标系一直在变化。)

在任何情况下,您通过从“NASA的地球x坐标”中减去计算值来计算绝对误差为110,000 km。你似乎认为这是一个糟糕的答案。你有什么期望?要点击它吗?在一米之内?一公里?你能接受什么以及为什么?

通过将误差除以“NASA的地球x坐标”得到相对误差。把它想象成一个百分比。你有什么价值?如果它是1%或更低,祝贺你自己。那会很不错。

你应该知道floating point numbers aren't exact on computers。 (您不能完全用二进制表示0.1,而不能用十进制表示1/3。)会出现错误。您作为模拟器的工作是理解错误并尽可能地最小化它们。

你可能有一个步长问题。尝试将时间步长减半,看看你做得更好。如果你这样做,它表示你的结果没有收敛。再次减半,直到达到可接受的误差。

你的方程可能条件很差。如果这是真的,那么小的初始错误将随着时间的推移而放大。

我建议您对方程式进行非维数化并计算稳定性极限步长。你对“足够小”步长应该是什么的直觉可能让你大吃一惊。

我还会更多地了解many body problem。这很微妙。

您也可以尝试使用数字集成库而不是集成方案。您将对方程进行编程并将其提供给工业强度积分器。它可以让您深入了解它是您的实现还是导致问题的物理原因。

就个人而言,我不喜欢你的实施。如果您在考虑数学向量的情况下完成它,那将是一个更好的解决方案。对相对位置的“if”测试让我感到冷淡。矢量力学会使标志自然出现。

更新:

好的,你的相对错误非常小。

当然,绝对错误确实很重要 - 取决于您的要求。如果你在一个星球上登陆车辆,你不想被那么多的车辆降落。

因此,您需要停止对步长太小的假设做出假设,并采取必要措施将错误驱动到可接受的水平。

计算中的所有数量都是64位IEEE浮点数吗?如果没有,你永远不会到那里。

64位浮点数约为16 digits of accuracy。如果你需要更多,你将不得不使用像Java的BigDecimal这样的无限精度对象 - 或者等待它 - 将你的方程式重新缩放以使用除公里之外的长度单位。如果你通过对你的问题有意义的东西(例如,地球的直径或地球轨道的主轴/短轴的长度)来缩放所有距离,你可能会做得更好。

答案 1 :(得分:4)

要进行太阳能系统的RK4集成,您需要非常好的精度,否则您的解决方案将迅速发散。假设您已正确实现了所有内容,您可能会看到RK对此类模拟的缺点。

要验证是否是这种情况:尝试使用其他集成算法。我发现使用Verlet integration太阳系模拟将不那么混乱。 Verlet比RK4更容易实现。

Verlet(和派生方法)通常比RK4更好的长期预测(如全轨道)是因为它们是辛,即保留RK4不具有的动量。因此,Verlet即使在它出现分歧后也会给出更好的行为(一个真实的模拟但是有一个错误),而RK一旦发散就会给出非物理行为。

另外:确保尽可能使用浮点数。不要在太阳系中使用以米为单位的距离,因为浮点数的精度在0..1区间内要好得多。使用AU或其他标准化比例比使用仪表要好得多。正如在另一个主题上所建议的那样,确保您使用时间段以避免在添加时间步骤时累积错误。

答案 2 :(得分:4)

这种模拟众所周知是不可靠的。舍入误差累积并引入不稳定性。提高精度并没有多大帮助;问题是你(必须)使用有限步长,自然使用零步长。

您可以通过减小步长来减少问题,因此错误变得明显需要更长的时间。如果您不是实时执行此操作,则可以使用动态步长,如果两个或更多个实体非常接近,则会减小该步长。

我对这些模拟做的一件事是在每一步之后“重新标准化”以使总能量相同。整个系统的引力加动能之和应该是一个常数(能量守恒)。计算每个步骤后的总能量,然后将所有物体速度按恒定量进行缩放,以使总能量保持恒定。这至少使输出看起来更合理。如果没有这种缩放,每一步后都会向系统中添加或移除少量能量,并且轨道往往会爆炸到无限远或螺旋形成太阳。

答案 3 :(得分:3)

非常简单的改变可以改善事物(正确使用浮点值)

  • 更改单位系统,尽可能多地使用尾数位。使用米,你做错了...使用AU,如上所述。更好的是,扩大尺寸以使太阳能系统适合1x1x1的盒子
  • 已在另一篇文章中说过:你的时间,计算时间= epoch_count * time_step,而不是添加!这样就可以避免累积错误。
  • 在对多个值求和时,使用高精度求和算法,如Kahan求和。在python中,math.fsum为你做。

答案 4 :(得分:1)

力量分解不应该

th = atan(b, a);
return Vector2(cos(th) * fg, sin(th) * fg)

(请参阅http://www.physicsclassroom.com/class/vectors/Lesson-3/Resolution-of-Forceshttps://fiftyexamples.readthedocs.org/en/latest/gravity.html#solution

BTW:你用平方根计算距离,但实际上你需要平方距离...

为什么不简化

 r = math.sqrt(a*a + b*b)
 fg = (self.G * b1.m * b2.m) / pow(r, 2)

 r2 = a*a + b*b
 fg = (self.G * b1.m * b2.m) / r2

我不确定python,但在某些情况下,您可以获得更精确的中间结果计算(Intel CPU支持80位浮点数,但在分配给变量时,它们会被截断为64位): Floating point computation changes if stored in intermediate "double" variable

答案 5 :(得分:0)

目前尚不清楚您的行星坐标和速度在哪个参照系中。如果它处于日心框架(参考框架与太阳相连),那么你有两个选择:(i)在非惯性参照系中进行计算(太阳不是静止的),(ii)转换位置和进入惯性重心参考系的速度。如果您的坐标和速度处于重心参照系,那么您必须具有太阳的坐标和速度。