PREVIOUS: N-body Applications | UP: Course Outline | NEXT: Boundary Value Problems |
Note that these quantities all have dimension of time, and they represent estimates of how rapidly particles i and j can cross the distance between them. The first two represent simple crossing times (distance divided by relative velocity), the third the free-fall time (square root of distance divided by acceleration). If we have access to these quantites during the integration (which we do in our N-body code, but generally don't in a canned integration routine), then we can compute a characteristic time scale for the entire system at the same time as we compute the accelerations (the "ij" quantities are only known inside the force calculation loop).
The basic approach is then to take some fixed multiple of this time scale (0.01, say) as our time step, making our integrator adaptive. This works because, if we consider just the radial acceleration, we find
so the "jerk" error term
we find
or scaling to some convenient time step at some separation:
Repeat exercise 5.1, but this time adjust the time step after every step using the above formula. Take dt0 = 0.01, v0 = 0.09, eps = 0, and run to time t = 50. Note that the variable time step scheme is (a) much more accurate than the fixed time step scheme, and (b) no longer time reversible. Which of these is more important depends on the application. Gravitational N-body people can generally live with the non-reversibility, while molecular dynamicists and planetary scientists usually insist on a reversible integrator. (Solution.)
That is, every time the accelerations of particles i and j are calcluated, also compute the two time scales and determine the minimum time scale across all pairs. This will require the value of tau to be propagated through two functions. A slightly modified version of the previous solution, which propagates tau through the step and acceleration functions but doesn't actually compute it, is provided here. Complete the code to compute tau and plot its value as a function of time along the orbit. Don't use tau yet to vary the time step. That will be the topic of the next exercise. (Solution.)
Here are some notes on using numpy to generate random points and velocities.
y = y1 + phi (2dt)^3 y = y2 + 2 phi dt^3assuming that the errors in the two shorter steps simply add. Thus we find
Delta = y2 - y1 = 6 phi dt^3that is, we can measure phi directly from the numerical data.
The trick then is to adjust the time step to keep the error constant. This sounds straightforward, but it reveals the downside of keeping our fingers out of the code -- we have to decide (or in a canned implementation, somehow tell the integrator) exactly what the error is that we want to limit. In this case, we could elect to limit the error in the position, or the velocity, or both, or some combination of the two. The choice is not always obvious. Here, we have been focusing on energy conservation, so it makes sense to scale the step to keep the energy error per step constant.
In other words, after we take the short and long steps, we calculate the energy after each:
t0 = t pos0 = pos.copy() vel0 = vel.copy() t,pos,vel = step(t, mass, pos, vel, eps**2, dt) t,pos,vel = step(t, mass, pos, vel, eps**2, dt) E1 = energy(mass, pos, vel, eps**2) t2,pos2,vel2 = step(t0, mass, pos0, vel0, eps**2, dt2) E2 = energy(mass, pos2, vel2, eps**2)We then define
DeltaE = abs(E2 - E1)and we scale dt to keep DeltaE fixed at some value dEtol, i.e. we choose the next step according to
dtnext = dt*(DeltaE/dEtol)**(-1./3)This should properly scale the step to force the error back to the desired value. You'll need to experiment with dEtol until the desired global accuracy is obtained. Note that the order of the method enters directly into the scaling through the -1/3 exponent.
One conservative caveat: you could in principle just accept dtnext as the new step, but it is usually advisable to limit changes in dt, just in case something unexpected happens. I prefer to accept dtnext if it is less than dt (it's always OK to take too short a step), but to limit the growth of dt as a precaution. The logic is:
if dtnext > 1.5*dt: dt = 1.5*dt else: dt = dtnext