Monday, May 20, 2013

Simulation Class: Midpoint Method and Velocity Verlet

Higher-Order Difference Equations

In our previous class, we were able to fix the instability of our spring system by switching from the never-correct Forward Euler time step to the Euler-Cromer time step, which made our simulation stable, but did not achieve an accurate result over time when compared to the exact answer.

The solutions we've explored so far are primarily explicit time integrations, in that we're only using values from previous points in time to solve for points at future points in time. When we consider, later, implicit time integration, where values at the current time are solved self-referentially, things will get a lot more complex. So, we'd like to continue exploring techniques that are fundamentally explicit in nature.

The approach that all of the higher-order explicit methods will take will be to use lower-order approximations to compute temporary values, which will be used to compute better derivatives that will then be used to integrate forward. The first, and most common higher-order method is called "The Midpoint Method".

The Midpoint Method

Let's approximate the velocity at half-way through our time step, rather than all the way to the new time, just using the forward finite-difference approximation we used with Forward Euler:
\( a( t ) = f( t, x( t ) ) \\ x( t + \frac{\Delta t}{2} ) \approx x( t ) + \frac{\Delta t}{2} v( t ) \\ v( t + \Delta t ) \approx v( t ) + \Delta t a( t ) \\ x( t + \Delta t ) \approx x( t + \frac{\Delta t}{2} ) + \frac{\Delta t}{2} v( t + \frac{\Delta t}{2} ) \)
Now we've got one extra step of computation at each time step, computing a midpoint position. This isn't a huge improvement over what we had before, and here's what it looks like in code:
// Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update position half-way.
    State[StatePositionX] += ( i_dt/2.0 ) * State[StateVelocityX];

    // Update velocity based on acceleration.
    State[StateVelocityX] += i_dt * A;
    
    // Update position half-way.
    State[StatePositionX] += ( i_dt/2.0 ) * State[StateVelocityX];

    // Update current time.
    State[StateCurrentTime] += i_dt;
}

And here that is:

So really we've actually made things worse with this simple higher-order approximation. An alternative midpoint-method approach is called Velocity Verlet, and it looks like this:

\( a( t ) = f( t, x( t ) ) \\ v( t + \frac{\Delta t}{2} ) \approx v(t) + \frac{\Delta t}{2} a( t ) \\ x( t + \Delta t ) \approx x( t ) + \Delta t v( t + \frac{\Delta t}{2} ) \\ a( t + \Delta t ) = f( t+\Delta t, x( t + \Delta t ) \\ v( t + \Delta t ) \approx v( t + \frac{\Delta t}{2} ) + \frac{\Delta t}{2} a( t + \Delta t ) \)
And here's what that looks like in code:


// Time Step function.
void TimeStep( float i_dt )
{
    // Compute acceleration from current position.
    float A = ( -Stiffness / BobMass ) * State[StatePositionX];

    // Update velocity half-way.
    State[StateVelocityX] += ( i_dt/2.0 ) * A;

    // Update position.
    State[StatePositionX] += i_dt * State[StateVelocityX];

    // Re-compute acceleration.
    A = ( -Stiffness / BobMass ) * State[StatePositionX];
    
    // Update velocity half-way.
    State[StateVelocityX] += ( i_dt/2.0 ) * A;

    // Update current time.
    State[StateCurrentTime] += i_dt;
}

And here it is running:

Not bad! Now we're cooking with gas! Still not perfect, though. The next class will explore the fourth-order Runge Kutta approach, and begin to consider implicit methods.

Download

All of the files associated with this class are available via a public github repository, found here: https://github.com/blackencino/SimClass

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.