1

I wrote a program that computes and animates the orbit of pluto, and have begun rewriting it using classes because this seems like a sensible way of introducing more planets into the simulation. i.e have a class that defines the physics, and then feed in specific planet data to get the orbital data.

    class Planet(object):
        m_sun = 1.989*(10**30)
        G = 6.67*(10**-11)
        dt = 1
        coords = []
        def __init__(self, x, y, vx, vy, m):
            self.x = x
            self.y = y
            self.vx = vx
            self.vy = vy
            self.m = m


        def genData(self):
            while self.dt < 100000000:
                r = ((self.x)**2 + (self.y)**2)**0.5
                a = ((self.G*self.m_sun)/r**2)
                ax = -a*((self.x)/r)
                ay = -a*((self.y)/r)
                self.vx = self.vx + ax*self.dt
                self.vy = self.vy + ay*self.dt
                self.x = self.x + self.vx*self.dt
                self.y = self.y + self.vy*self.dt
                coord = (self.x, self.y)
                print coord
                self.coords.append(coord)
                self.dt = self.dt + 1000

pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))
pluto.genData()

I'm sure it isn't perfect, but it appears to be working (this is the first class i've built on my own). My question is how do I extract the data from 'coords' into a list that I can work with outside of the class.

I want to generate data for each planet, and then use this data to create an animation in Pygame. For example, a list of (x,y) coordinates for pluto, earth, saturn etc. As it stands, it churns out the data, but it doesn't appear to be accessible from outside the class.

I hope my question makes sense.

Thanks!

jm22b
  • 377
  • 1
  • 3
  • 16
  • This isn't related to your question about accessing the `coords` data, but you seem to be using `self.dt` for two different things. You're using it in the computation code as the time step size, but also in the condition of the `while` loop as the total elapsed time. Those should probably be separate variables (with the elapsed time being incremented by the timestep each pass through the loop). – Blckknght Aug 30 '15 at 12:18
  • Thank you, I always appreciate comments on how to improve my code. – jm22b Aug 30 '15 at 14:03
  • @Blckknght: Yikes! I didn't even notice that! That would explain the crazy output. :) – PM 2Ring Aug 30 '15 at 15:39
  • @Jacobadtr: You need to change your code! Eg, in `self.vx = self.vx + ax*self.dt` the `self.dt` has to be the time _increment_ **not** the total time. The standard formula v = u + at uses the total time for t, and u is initial velocity at time t=0. But in your loop, you're using the velocity at the previous timestep for the initial velocity, so self.dt has to be the time increment. The same thing applies to your position calculations. – PM 2Ring Aug 30 '15 at 15:46
  • Ach! You're right, that probably explains why my planets get faster and faster as they go around, too! Thanks for all the help guys/gals! – jm22b Aug 30 '15 at 15:57

4 Answers4

5

Your question's been answered, but I have some info you should find useful to improve your program. Technically, this info belongs in a comment (since you didn't actually ask about this in your question), but it wouldn't fit. :)

  1. Pluto's orbit is inclined quite a bit to the ecliptic, so if you want to plot it along with several other planets in the Solar System you need to work in 3D to be accurate. But I guess that's not a big deal for your application.

  2. Earth's orbit is tiny compared to Pluto, so you'll probably need to implement zooming to see them both on one animation.

  3. You can get more accuracy in your calculations by using the Standard gravitational parameter rather than using separate values of G and mass.

  4. Your algorithm for calculating velocity and position is called Euler integration. It's equivalent to approximating the curve of the orbit by a polygon. It works, but it's not very accurate. So you need to make the time delta very small otherwise the orbit won't be very realistic and it may not even be a closed curve. And even that doesn't help a lot because the error is accumulative, so eventually the computed orbit will bear little resemblance to reality.

No technique of numerical integration is perfect (except on very simple functions), but a popular family of integration techniques that are more accurate (and hence work ok with a much larger time step) are the Runge-Kutta methods. You can find lots of example code of orbit calculation using a Runge-Kutta method; most code examples use the variant known as RK4.

However, I strongly urge you to try Leapfrog integration. It's quite easy to code the synchronized form of Leapfrog and it has a major benefit over Runge-Kutta in that it's symplectic, which (roughly) means that it conserves energy, so the error won't accumulate from orbit to orbit.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Thank you! Lots of great information here for me to dig into – jm22b Aug 30 '15 at 14:08
  • Regarding your comment on Euler integration; When I run my simulation of Pluto, which is 'complete' and uses the exact same physics model as shown in my OP, the pattern of the orbit produces a sort of 'basket weave'. Is this the 'not closed curve' you mention? – jm22b Aug 30 '15 at 14:11
  • @Jacobadtr: It sounds like it, although 1000 seconds per step on Pluto shouldn't give you a totally crazy trajectory like that... I guess you've checked that your initial position & velocity are valid, but it is easy to use kilometres instead of metres (or vice versa). :) I've never tried computing Pluto's trajectory (I mostly do simple stuff where I can work in years & AU), but IIRC an Euler integration of Mars's orbit won't produce a closed curve with a time step of a day - you need to go down to an hour, or smaller if you want to compute several revolutions. – PM 2Ring Aug 30 '15 at 14:30
  • I used Pluto's average distance from the sun, and average tangential velocity as my starting conditions, with Pluto it's average distance from the sun along the x axis and it's average tangential velocity as it's initial y-velocity. Do you know of any good repositories of accurate data on the planets that I could use for this kind of thing? – jm22b Aug 30 '15 at 14:38
  • @Jacobadtr The [USNO](http://aa.usno.navy.mil/publications/docs/asa.php) has good data; some of it's available free of charge. – PM 2Ring Aug 30 '15 at 15:01
3

Did you try pluto.coords?

You can access members of a class from outside by using the instance followed by dot followed by the member name, i.e. attribute access. This is just as you have done when calling the genData() method.

BTW, you can define your constants using exponential notation:

m_sun = 1.989e+30
G = 6.67e-11

and

pluto = Planet(4495978707000, 0, 0, 4670, 1.305e+22)

which is more readable (important) and saves a few calculations for the definition of your class (less/not important).

mhawke
  • 84,695
  • 9
  • 117
  • 138
3

Instead of storing the values in self.coords, yield the values:

    def genData(self):
        while self.dt < 100000000:
            ...  
            self.x = self.x + self.vx*self.dt
            self.y = self.y + self.vy*self.dt
            coord = (self.x, self.y)
            yield coord
            self.dt = self.dt + 1000

pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))

for coord in pluto.genData():
    print(coord)

Note that this makes genData a generator function.

To obtain a list of coords, you can accumulate the values in the loop:

coords = []
for coord in pluto.genData():
    coords.append(coord)

or use coords = list(pluto.genData()).


By the way, it's usually a good policy to separate code-that-calculates from code-that-prints. That way you can call code-that-calculates many times, or include it in a chain of calculations without always emitting print statements.


I want to generate data for each planet, and then use this data to create an animation in Pygame.

It sounds like you don't need to accumulate the data. You can plot the current point for each planet without needing to know the planet's coordinate history. In that case, using generator functions would be more memory-efficient since they would allow you to generate the next coordinate for each planet without having to store all the coordinates in a list first:

# In Python2 
import itertools as IT
for coords in IT.izip(*[planet.genData() for planet in [pluto, neptune, uranus, ...]]):
    # plot coords

or

# In Python3
for coords in zip(*[planet.genData() for planet in [pluto, neptune, uranus, ...]]):
    # plot coords
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • Depends a bit on the use case. Maybe the coordinates will be accessed again by the object. They could be regenerated, but storing them seems viable too. You could do both. – mhawke Aug 30 '15 at 10:49
  • @mhawke: Yes, that's true. When I first skimmed the code I thought the while-loop was infinite. – unutbu Aug 30 '15 at 10:53
  • Thank you for the comment. I have found this very good thread which I will use to understand the 'why' of your remark better. I'm not that familiar with 'yield', yet! http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python – jm22b Aug 30 '15 at 10:59
2

Actually it's very easy, just pluto.coords:

pluto = Planet(4495978707000, 0, 0, 4670, 1.305*(10**22))
pluto.genData()
print pluto.coords
Igor Pejic
  • 3,658
  • 1
  • 14
  • 32