2.6. A Comparison test
Bob: Okay. Let me first repeat forward Euler, for a couple choices.
This was the largest time step for which we did not get an explosion:
dt = 0.001 # time step
dt_dia = 10 # diagnostics printing interval
dt_out = 10 # output interval
dt_end = 10 # duration of the integration
method = "forward" # integration method
##method = "leapfrog" # integration method
|gravity> ruby integrator_driver1a.rb < euler.in
dt = 0.001
dt_dia = 10
dt_out = 10
dt_end = 10
method = forward
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 10000 steps :
E_kin = 0.0451 , E_pot = -0.495 , E_tot = -0.45
E_tot - E_init = 0.425
(E_tot - E_init) / E_init =-0.486
1.0000000000000000e+00
2.0143551288236803e+00 1.6256533638564666e-01
-1.5287552868811088e-01 2.5869644289548283e-01
Alice: That is a horrible error in the energy. But we have seen before
that the positions get more accurate with a smaller time step, in such
a way that the error in the position goes down linearly with the time
step size. We can expect the energy error to scale in a similar way.
Bob: Here is the same run with a ten times smaller time step.
|gravity> ruby integrator_driver1b.rb < euler.in
dt = 0.0001
dt_dia = 10
dt_out = 10
dt_end = 10
method = forward
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 100000 steps :
E_kin = 1.27 , E_pot = -2.07 , E_tot = -0.8
E_tot - E_init = 0.0749
(E_tot - E_init) / E_init =-0.0856
1.0000000000000000e+00
2.9271673782679269e-01 3.8290774857970239e-01
-1.5655189697698089e+00 -3.1395706386716327e-01
In fact, the energy went down by less than a factor of ten, but that may
well be because we have not yet reached the linear regime.
Alice: That is probably the reason. With errors
this large, you cannot expect an asymptotic behavior.
Bob: We'll just have to take an even smaller time step. Here goes:
|gravity> ruby integrator_driver1c.rb < euler.in
dt = 1.0e-05
dt_dia = 10
dt_out = 10
dt_end = 10
method = forward
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 1000000 steps :
E_kin = 0.693 , E_pot = -1.56 , E_tot = -0.865
E_tot - E_init = 0.0103
(E_tot - E_init) / E_init =-0.0117
1.0000000000000000e+00
5.1997064263476844e-01 -3.7681799204170163e-01
1.1712678714357090e+00 1.1470087974069308e-01
Alice: That is going in the right direction at least, creeping closer
to a factor ten in decrease of the energy errors.
Bob: But creeping very slowly. Before I'll be convinced, I want to see
the time step shrinking by another factor of ten:
|gravity> ruby integrator_driver1d.rb < euler.in
dt = 1.0e-06
dt_dia = 10
dt_out = 10
dt_end = 10
method = forward
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 10000000 steps :
E_kin = 0.566 , E_pot = -1.44 , E_tot = -0.874
E_tot - E_init = 0.00103
(E_tot - E_init) / E_init =-0.00118
1.0000000000000000e+00
5.9216816556748519e-01 -3.6259219766731704e-01
1.0441831294511545e+00 2.0515662908862703e-01
Alice: Ah, finally! Now we're in the linear regime.
Bob: Meanwhile, I am really curious to see
what the leapfrog method will give us. Let's have a look:
dt = 0.001 # time step
dt_dia = 10 # diagnostics printing interval
dt_out = 10 # output interval
dt_end = 10 # duration of the integration
##method = "forward" # integration method
method = "leapfrog" # integration method
|gravity> ruby integrator_driver1e.rb < euler.in
dt = 0.001
dt_dia = 10
dt_out = 10
dt_end = 10
method = leapfrog
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 10000 steps :
E_kin = 0.554 , E_pot = -1.43 , E_tot = -0.875
E_tot - E_init = 3.2e-07
(E_tot - E_init) / E_init =-3.65e-07
1.0000000000000000e+00
5.9946121055215340e-01 -3.6090779482156415e-01
1.0308896785838775e+00 2.1343145669114691e-01
Alice: Ah, much better. A thousand times longer time step, and yet
an enormously better accuracy already! What will happen with a ten times
smaller time step?
2.7. Scaling
|gravity> ruby integrator_driver1f.rb < euler.in
dt = 0.0001
dt_dia = 10
dt_out = 10
dt_end = 10
method = leapfrog
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 100000 steps :
E_kin = 0.554 , E_pot = -1.43 , E_tot = -0.875
E_tot - E_init = 3.2e-09
(E_tot - E_init) / E_init =-3.65e-09
1.0000000000000000e+00
5.9961599191051762e-01 -3.6063731614990768e-01
1.0308077390676098e+00 2.1389066543649665e-01
Bob: A hundred times smaller error, as is appropriate for a second order
method. What a relief, after the earlier slooooow convergence of
forward Euler! Clearly leapfrog is a much much better integrator.
Alice: Yes, to get such a high accuracy would have taken forever with
a forward Euler integrator. Congratulations, Bob!
Bob: Thanks, but don't get too excited yet: note that the error in the
position is quite a bit larger than the error in the energy. The difference
between the positions in the last two runs is of the order of
, while the next-to-last run already had an energy
accuracy of a few time
.
Alice: This must be an example of what we talked about, when we first
discussed the limits of using energy conservation as a check. The leapfrog
is an example of a symplectic integration scheme, one which has better
built-in energy conservation. Clearly it conserves energy better than
position.
Bob: But since it is a second order scheme, also the accuracy of the
position should increase by a factor hundred when we decrease the time
step by a factor ten. Let's try and see.
|gravity> ruby integrator_driver1g.rb < euler.in
dt = 1.0e-05
dt_dia = 10
dt_out = 10
dt_end = 10
method = leapfrog
at time t = 0, after 0 steps :
E_kin = 0.125 , E_pot = -1 , E_tot = -0.875
E_tot - E_init = 0
(E_tot - E_init) / E_init =-0
at time t = 10, after 1000000 steps :
E_kin = 0.554 , E_pot = -1.43 , E_tot = -0.875
E_tot - E_init = 3.21e-11
(E_tot - E_init) / E_init =-3.66e-11
1.0000000000000000e+00
5.9961753925629424e-01 -3.6063461077225456e-01
1.0308069185587891e+00 2.1389525780602489e-01
Alice: You are right. The difference between the x component of the
position in the last two runs is
, while the
difference between the previous run and the run before that was
. So the scheme really is second-order accurate.
Still, it would be nice to try another second order integrator, one
that does not have built-in energy conservation.
Bob: I'll try to see what I can do, this evening. And who knows,
I might even try my hand at a fourth-order integrator. But that will
be it; we can't go on testing the two-body problem forever. We have
other work to do: finding a reasonable graphics package, for one thing.
Alice: Definitely. Okay, see you tomorrow!