Python Euler Method implementation in Two Body Problem not working

后端 未结 2 1182
死守一世寂寞
死守一世寂寞 2020-12-18 11:03

I am currently trying to get a two body problem to work, that I can then upgrade to more planets, but it is not working. It is outputting me impossible positions. Does anyon

相关标签:
2条回答
  • 2020-12-18 11:34

    picture - 1000 words

    The direct errors in your code are

    • You compute the force in the wrong direction, it should be rx = b[n].x-b[x].x etc. or you need to remove the minus sign some lines later.

    • Your computation in single coordinates invites copy-paste errors as in

      x = int(Bodies[n].x) + int(Bodies[n].vx) * dt
      y = int(Bodies[n].y) + int(Bodies[n].vx) * dt
      z = int(Bodies[n].z) + int(Bodies[n].vz) * dt
      

      where in the y coordinate you still use vx. The intermediate rounding to integer values makes no sense, it only reduces the accuracy somewhat.


    I changed your code to use numpy arrays as vectors, separated the acceleration computation from the Euler updates, removed the non-sensical rounding to integer values during the numerical simulation, removed unused variables and fields, removed intermediate variables for the force/acceleration computations to directly update the acceleration field, changed the loop to use the time to notice when a year (or 10) has passed (your code iterates over 100 years in 0.1day increments, was that intended?), ... and added Venus to the bodies and added code to produce images, result see above.

    This spiraling is typical for the Euler method. You can easily improve that pattern by changing the Euler update to the symplectic Euler update, which means to update the velocity first and compute the position with the new velocity. This gives, with everything else the same, the image

    day = 60*60*24
    # Constants
    G = 6.67408e-11
    au = 1.496e11
    
    class CelBody(object):
        # Constants of nature
        # Universal constant of gravitation
        def __init__(self, id, name, x0, v0, mass, color, lw):
            # Name of the body (string)
            self.id = id
            self.name = name
            # Mass of the body (kg)
            self.M = mass
            # Initial position of the body (au)
            self.x0 = np.asarray(x0, dtype=float)
            # Position (au). Set to initial value.
            self.x = self.x0.copy()
            # Initial velocity of the body (au/s)
            self.v0 = np.asarray(v0, dtype=float)
            # Velocity (au/s). Set to initial value.
            self.v = self.v0.copy()
            self.a = np.zeros([3], dtype=float)
            self.color = color
            self.lw = lw
    
    # All Celestial Bodies
    
    t = 0
    dt = 0.1*day
    
    Bodies = [
        CelBody(0, 'Sun', [0, 0, 0], [0, 0, 0], 1.989e30, 'yellow', 10),
        CelBody(1, 'Earth', [-1*au, 0, 0], [0, 29783, 0], 5.9742e24, 'blue', 3),
        CelBody(2, 'Venus', [0, 0.723 * au, 0], [ 35020, 0, 0], 4.8685e24, 'red', 2),
        ]
    
    paths = [ [ b.x[:2].copy() ] for b in Bodies]
    
    # loop over ten astronomical years
    v = 0
    while t < 10*365.242*day:
        # compute forces/accelerations
        for body in Bodies:
            body.a *= 0
            for other in Bodies:
                # no force on itself
                if (body == other): continue # jump to next loop
                rx = body.x - other.x
                r3 = sum(rx**2)**1.5
                body.a += -G*other.M*rx/r3
    
        for n, planet in enumerate(Bodies):
            # use the symplectic Euler method for better conservation of the constants of motion
            planet.v += planet.a*dt
            planet.x += planet.v*dt
            paths[n].append( planet.x[:2].copy() )
            #print("%10s x:%53s v:%53s"%(planet.name,planet.x, planet.v))
        if t > v:
            print("t=%f"%t)
            for b in Bodies: print("%10s %s"%(b.name,b.x))
            v += 30.5*day
        t += dt
    
    plt.figure(figsize=(8,8))
    for n, planet in enumerate(Bodies): 
        px, py=np.array(paths[n]).T; 
        plt.plot(px, py, color=planet.color, lw=planet.lw)
    plt.show()
    
    0 讨论(0)
  • 2020-12-18 11:34

    I think the core of your problem is that you are not thinking of it as a state engine.

    Imagine "Bodies" is a completely unchangable value that determines the state of the system at one point in time:

    bodies_at_time_0 = ((sun, position, velocity, mass), (earth, position, velocity, mass))
    

    You get the next state like so:

    bodies_at_time_1 = apply_euler_method_for_one_tick( bodies_at_time_0 )
    

    Thus your "Bodies" is completely fixed at one time, and you compute a whole new "Bodies" for the next time. Inside the computation you ALWAYS use the data in the input, which is where they are now. What you are doing is moving some things, and then computing where to move other things based on the wrong number (because you already moved other stuff).

    Once you make sure your function uses the input state, and returns an output state, you can break it down much more easily:

    # advance all bodies one time interval, using their frozen state 
    def compute(bodies):
        new_bodies = []
        for body in bodies:
            new_bodies.append(compute_one_body(body, bodies))
        return new_bodies
    
    # figure out where one body will move to, return its new state
    def compute_one_body(start, bodies):
        end = math stuff using the fixed state in bodies
        return end
    
    # MAIN
    bodies = initial_state
    for timepoint in whatever:
        bodies = compute(bodies)
    

    I like to use tuples for this sort of thing, to avoid accidentally changing a list in some other scope (because lists are mutable).

    0 讨论(0)
提交回复
热议问题