Search code examples
pythonfloating-pointsimulationastronomy

Why is my astronomy simulation inaccurate?


I've made a program that simulates movement of bodies in the solar system, however, I'm getting various inaccuracies in my results.

I believe that it probably has something to do with my integration method.


tl;dr there's a slight difference between the position and velocity of Earth between my simulation and NASA's data, if you could please look at my code below and tell me whether my math is wrong.


The test I've ran is a 10 day long (864000 seconds) simulation, that starts at Thu Mar 13 18:30:59 2006 and ends at Thu Mar 23 18:30:59 2006.

After the simulation the program reported the following statistics for the planet Earth:

Earth position: (-1.48934630382e+11, -7437423391.22)
Earth velocity: (990.996767368, -29867.6967867)

The measurement units are, of course meters and meters per second.

I've used the HORIZONS system to get the starting position and velocity vectors of most large bodies at Thu Mar 13 18:30:59 2006 in the solar system and put them in the simulation.

After the test, I've queried HORIZONS again for Thu Mar 23 18:30:59 2006 Earth data, and got the following results for it:

Earth position: (-1.489348720130393E+11, -7437325664.023257)
Earth velocity: (990.4160633376971, -2986.736541327986)

As you can see, the results are almost always the same in their first four digits. However, that is still a pretty big miss! I'm worried because I'll have to simulate a few years of time and the error could escalate.

Could you please look at the core of my simulation and tell me whether my maths are incorrect?

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

    self.time += dt

    for b1, b2 in combinations(self.bodies.values(), 2):
        fg = self.Fg(b1, b2)

        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 b in self.bodies.itervalues():
        ax = b.force.x/b.m
        ay = b.force.y/b.m

        b.position.x += b.velocity.x*dt
        b.position.y += b.velocity.y*dt

        nvx = ax*dt
        nvy = ay*dt

        b.position.x += 0.5*nvx*dt
        b.position.y += 0.5*nvy*dt

        b.velocity.x += nvx
        b.velocity.y += nvy

        b.force.x = 0
        b.force.y = 0

I have another version of this method, that should perform better, but it performs much worse:

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

    self.time += dt

    for b1, b2 in combinations(self.bodies.values(), 2):
        fg = self.Fg(b1, b2)

        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 b in self.bodies.itervalues():
        #Acceleration at (t):
        ax  = b.force.x/b.m
        ay  = b.force.y/b.m
        #Velocity at (t):
        ovx = b.velocity.x
        ovy = b.velocity.y
        #Velocity at (t+dt):
        nvx = ovx + ax*dt
        nvy = ovy + ay*dt
        #Position at (t+dt):
        b.position.x = b.position.x + dt*(ovx+nvx)/2
        b.position.y = b.position.y + dt*(ovy+nvy)/2


        b.force.null() #Reset the forces.

Solution

  • The integration method is very important. You are using Euler explicit method, which is of low order precision, too low for proper physics simulation. Now, you get choices

    • General behaviour matters most : Verlet method, or Beeman method (Verlet with more precision), which have very good energy conservation but lesser precision for position and speed.
    • Precise positions matters most : Runge-Kutta order 4 or more. Energy won't be conserved, so your simulated system will act as if energy increases.

    Furthermore, time = time + dt will have increasing loss of precisions for large number of steps. Consider time = epoch * dt where epoch is an integer, would make the precision of the time variable independent from the number of steps.