4

As per advice given to me in this answer, I have implemented a Runge-Kutta integrator in my gravity simulator.

However, after I simulate one year of the solar system, the positions are still off by cca 110 000 kilometers, which isn't acceptable.

My initial data was provided by NASA's HORIZONS system. Through it, I obtained position and velocity vectors of the planets, Pluto, the Moon, Deimos and Phobos at a specific point in time.

These vectors were 3D, however, some people told me that I could ignore the third dimension as the planets aligned themselves in a plate around the Sun, and so I did. I merely copied the x-y coordinates into my files.

This is the code of my improved update method:

"""
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

The algorithm is as follows:

  1. Update the accelerations of all bodies in the system
  2. RK4(first step)
  3. goto 1
  4. RK4(second)
  5. goto 1
  6. RK4(third)
  7. goto 1
  8. RK4(fourth)

Did I mess something up with my RK4 implementation? Or did I just start with corrupted data (too few important bodies and ignoring the 3rd dimension)?

How can this be fixed?


Explanation of my data etc...

All of my coordinates are relative to the Sun (i.e. the Sun is at (0, 0)).

./my_simulator 1yr

Earth position: (-1.47589927462e+11, 18668756050.4)

HORIZONS (NASA):

Earth position: (-1.474760457316177E+11,  1.900200786726017E+10)

I got the 110 000 km error by subtracting the Earth's x coordinate given by NASA from the one predicted by my simulator.

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

The relative error seems miniscule, but that's simply because Earth is really far away from the Sun both in my simulation and in NASA's. The distance is still huge and renders my simulator useless.

Peter Nazarenko
  • 411
  • 1
  • 7
  • 16
corazza
  • 31,222
  • 37
  • 115
  • 186
  • 2
    This isn't a programming question, it's a maths problem (of some variety). – Oliver Charlesworth Mar 02 '13 at 16:50
  • Good point, but I believe it's well suited for SO because it's about a specific implementation of an algorithm - which is something you don't do in math but mostly in programming. I've also asked a very similar question to this one (but fundamentally different) here and it was quite well received. – corazza Mar 02 '13 at 16:52
  • 1
    Ok, but you're essentially just asking people to spot errors in your code by inspection, which is not a great question. – Oliver Charlesworth Mar 02 '13 at 16:53
  • 1
    For one thing the force summing operation doesn't make sense. You're finding a vector for force on b1 toward b2. So you should update b1's total force by adding this vector and b2 by subtracting it. The if statements you have do something whacky. As to RK, a good way to go is implement simple Euler first (with a very small time step) and then remove Euler and plug in RK. This way you have ruled out all but the integrator as a source of bugs. – Gene Mar 02 '13 at 16:57
  • 1
    Simple Euler is likely to require a step size so small for stability that it'll be useless. Easy to program, impractical to use. – duffymo Mar 02 '13 at 16:59
  • @Gene: that's exactly what I did. I first had Euler (as witnessed by my first question), and then I implemented RK4. The force calculation works pretty well, actually, the `if` statements have to do with the fact that the force vector is always in the first quadrant (positive). – corazza Mar 02 '13 at 17:02

6 Answers6

4

110 000 km absolute error means what relative error?

I got the 110 000 km value by subtracting my predicted Earth's x coordinate with NASA's Earth x coordinate.

I'm not sure what you're calculating here or what you mean by "NASA's Earth x coordinate". That's a distance from what origin, in what coordinate system, at what time? (As far as I know, the earth moves in orbit around the sun, so its x-coordinate w.r.t. a coordinate system centered at the sun is changing all the time.)

In any case, you calculated an absolute error of 110,000 km by subtracting your calculated value from "NASA's Earth x coordinate". You seem to think this is a bad answer. What's your expectation? To hit it spot on? To be within a meter? One km? What's acceptable to you and why?

You get a relative error by dividing your error difference by "NASA's Earth x coordinate". Think of it as a percentage. What value do you get? If it's 1% or less, congratulate yourself. That would be quite good.

You should know that floating point numbers aren't exact on computers. (You can't represent 0.1 exactly in binary any more than you can represent 1/3 exactly in decimal.) There are going to be errors. Your job as a simulator is to understand the errors and minimize them as best you can.

You could have a stepsize problem. Try reducing your time step size by half and see if you do better. If you do, it says that your results have not converged. Reduce by half again until you achieve acceptable error.

Your equations might be poorly conditioned. Small initial errors will be amplified over time if that's true.

I'd suggest that you non-dimensionalize your equations and calculate the stability limit step size. Your intuition about what a "small enough" step size should be might surprise you.

I'd also read a bit more about the many body problem. It's subtle.

You might also try a numerical integration library instead of your integration scheme. You'll program your equations and give them to an industrial strength integrator. It could give some insight into whether or not it's your implementation or the physics that causes the problem.

Personally, I don't like your implementation. It'd be a better solution if you'd done it with mathematical vectors in mind. The "if" test for the relative positions leaves me cold. Vector mechanics would make the signs come out naturally.

UPDATE:

OK, your relative errors are pretty small.

Of course the absolute error does matter - depending on your requirements. If you're landing a vehicle on a planet you don't want to be off by that much.

So you need to stop making assumptions about what constitutes too small a step size and do what you must to drive the errors to an acceptable level.

Are all the quantities in your calculation 64-bit IEEE floating point numbers? If not, you'll never get there.

A 64 bit floating point number has about 16 digits of accuracy. If you need more than that, you'll have to use an infinite precision object like Java's BigDecimal or - wait for it - rescale your equations to use a length unit other than kilometers. If you scale all your distances by something meaningful for your problem (e.g., the diameter of the earth or the length of the major/minor axis of the earth's orbit) you might do better.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • I got the 110 000 km value by subtracting my predicted Earth's x coordinate with NASA's Earth x coordinate. The step size was `60s`, which is quite small actually, and I don't believe that's the problem. I'll try halving it and see if it helps. – corazza Mar 02 '13 at 17:05
  • What does "non-dimensionalize" mean? Not considering the measurement units or something? How would that help? – corazza Mar 02 '13 at 17:10
  • You'd better Google it. Non-dimensionalizing does not mean ignoring units. Anyone who understood the physics would know what that meant. (Think Reynolds number in fluid mechanics.) It'll give you some characteristic numbers that will tell you something about your system. – duffymo Mar 02 '13 at 17:12
  • Well I told you how I got the number, but I don't know what do you mean by "relative error"... – corazza Mar 02 '13 at 17:39
  • Hey, I've calculated the relative error and explained my data, see the edited section of the question. tl;dr: all coordinates are relative to the Sun, the relative error is 0.077%, but it doesn't matter because the distance is still really huge. – corazza Mar 02 '13 at 18:30
  • Actually, I believe rescaling the units wouldn't do me any good, as it wont be able to conserve any information like that. I mean, it's just a factor. Currently I'm using meters, and not kilometers too. – corazza Mar 02 '13 at 19:09
  • You have no idea what you're doing. No wonder you can't get good answers. – duffymo Mar 02 '13 at 20:17
  • Well I'm trying to get an idea here. This is the first time I'm ever doing something like this, of course I have to learn new things... So I should use astronomical units (or something of that scale) instead of meters? Would you care to explain why that would be better? – corazza Mar 02 '13 at 21:20
  • Not really. I don't care if you ever get an answer. I've already told you enough. I used to do computer simulations for a living. It's not easy. – duffymo Mar 02 '13 at 22:06
  • Yeah, it's true that you've already told me plenty, your answer keeps growing. Thanks. – corazza Mar 02 '13 at 23:14
  • "you'll have to use an infinite precision object like Java's BigDecimal or - wait for it - rescale your equations to use a length unit". This sounds like a pretty bad suggestion to me. Java's BigDecimal is probably going to slow down the simulation by orders of magnitude. Rescaling does not help either, since the relative truncation error will remain the same for normal numbers. Using double-double (or quad-double) precision is probably a better option to consider, as long as a smaller time step, if you really need more precision/accuracy. – Ale Apr 08 '18 at 14:29
  • @duffymo Could you provide some resources, ie books/articles/generally-googlable-topics, for someone like corazza (or myself) to learn more about best-practices? – Tyler Camp Dec 17 '18 at 18:14
  • Nope. This is a six year old question. Do your own research. Start by understanding the physics better. I learned a lot of what I know by getting degrees in mechanical engineering. Start there. – duffymo Dec 17 '18 at 19:54
4

To do a RK4 integration of the solar system you need a very good precision or your solution will diverge quickly. Assuming you have implemented everything correctly, you may be seeing the drawbacks with RK for this sort of simulation.

To verify if this is the case: try a different integration algorithm. I found that using Verlet integration a solar system simulation will be much less chaotic. Verlet is much simpler to implement than RK4 as well.

The reason Verlet (and derived methods) are often better than RK4 for long term prediction (like full orbits) is that they are symplectic, that is, conserve momentum which RK4 does not. Thus Verlet will give a better behavior even after it diverges (a realistic simulation but with an error in it) whereas RK will give a non-physical behavior once it diverges.

Also: make sure you are using floating point as good as you can. Don't use distances in meters in the solar system, since the precision of floating point numbers is much better in the 0..1 interval. Using AU or some other normalized scale is much better than using meters. As suggested on the other topic, ensure you use an epoch for the time to avoid accumulating errors when adding time steps.

Anders Forsgren
  • 10,827
  • 4
  • 40
  • 77
4

Such simulations are notoriously unreliable. Rounding errors accumulate and introduce instability. Increasing precision doesn't help much; the problem is that you (have to) use a finite step size and nature uses a zero step size.

You can reduce the problem by reducing the step size, so it takes longer for the errors to become apparent. If you are not doing this in real time, you can use a dynamic step size which reduces if two or more bodies are very close.

One thing I do with these kinds of simulations is "re-normalise" after each step to make the total energy the same. The sum of gravitational plus kinetic energy for the system as a whole should be a constant (conservation of energy). Work out the total energy after each step, and then scale all the object speeds by a constant amount to keep the total energy a constant. This at least keeps the output looking more plausible. Without this scaling, a tiny amount of energy is either added to or removed from the system after each step, and orbits tend to blow up to infinity or spiral into the sun.

Peter Webb
  • 671
  • 4
  • 14
3

Very simple changes that will improve things (proper usage of floating point values)

  • Change the unit system, to use as much mantissa bits as possible. Using meters, you're doing it wrong... Use AU, as suggested above. Even better, scale things so that the solar system fits in a 1x1x1 box
  • Already said in an other post : your time, compute it as time = epoch_count * time_step, not by adding ! This way, you avoid accumulating errors.
  • When doing a summation of several values, use a high precision sum algorithm, like Kahan summmation. In python, math.fsum does it for you.
Monkey
  • 1,838
  • 1
  • 17
  • 24
1

Shouldn't the force decomposition be

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

(see http://www.physicsclassroom.com/class/vectors/Lesson-3/Resolution-of-Forces or https://fiftyexamples.readthedocs.org/en/latest/gravity.html#solution)

BTW: you take the square root to calculate the distance, but you actually need the squared distance...

Why not simplify

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

to

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

I am not sure about python, but in some cases you get more precise calculations for intermediate results (Intel CPUs support 80 bit floats, but when assigning to variables, they get truncated to 64 bit): Floating point computation changes if stored in intermediate "double" variable

Community
  • 1
  • 1
Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51
0

It is not quite clear in what frame of reference you have your planet coordinates and velocities. If it is in heliocentric frame (the frame of reference is tied to the Sun), then you have two options: (i) perform calculations in non-inertial frame of reference (the Sun is not motionless), (ii) convert positions and velocities into the inertial barycentric frame of reference. If your coordinates and velocities are in barycentric frame of reference, you must have coordinates and velocities for the Sun as well.