Previous | ToC | Up | Next |
Bob: Hi Alice! Look what I've done, since we last met.
Alice: What have you done?
Bob: I added command line arguments to our latest N-body code.
I was getting so tired of having to edit a line in our driver file,
each time we were doing a different run. It was high time that we
made this switch. Now we can instruct the code on the command line
what options and value to give to the evolve method.
Alice: Can you show me what you did?
Bob: Here is the file rkn1.rb, which reads the command line,
and then invokes evolve. But before showing you the contents, let
me first show you how it works. Here is an example:
Alice: So how do you ask the code to use, say, a leapfrog integrator
instead of your default fourth-order Runge-Kutta, perhaps with a ten
times smaller time step?
Bob: Easy! This is what you type:
Alice: What exactly is the meaning of -o, and so on?
Bob: rather than answering you, let me ask the code. It even has a help
function:
Alice: Adding a help facility is a great improvement, I agree! But what
would happen if I would have typed --help instead? I would
not have guess that the help option would have been the old-fashion
Unix-style -h.
Bob: Try it!
Alice: Okay:
Bob: You're right. I could have added that explicitly. But in a way,
it is there already. Try your "--" notation with the longer words
that appear in the help answer.
Alice: You mean:
Bob: Let me look at the code. Ah, the long option is
--initial_output. I could have written that in the help
output, but I decided that that would be too cryptic. If you would
have gotten [-i initial_output] as part of the answer, that
probably would not made much sense. Instead, I let
the help option echo the sentence [-i (start output at t = 0)].
Alice: Well, I applaud the idea of using command line options, and I also
very much like the addition of a help function. Both aspects are essential
in a good user interface. However, if you don't mind me saying so, your
current help facility still needs a lot of help.
Bob: It's just my first try. In fact, please be critical! I would love
to provide a good user interface. For one thing, if we don't, my students
will keep knocking on my door to ask me how to use all these codes.
If we can make things more transparent, it will actually save me a lot of
time, if that means that the students can figure out things for themselves.
Alice: You really want me to be really critical? You may regret asking!
Bob: Sure, go ahead, be critical!
Alice: Okay! Then I won't hold back. I already mentioned a few things
that were not clear, and certainly not yet documented, but now that
you challenge me, why don't we go through the code you wrote,
and I will critically appraise your whole approach!
Bob: You look as if you mean business. But I have nothing to hide.
Here is the code, in file rkn1.rb It is only a couple pages.
Let me print it out first, and then we can walk through it.
Alice: Can you show me roughly how it all works? No need to go into all
the details, right now, since we'll probably want to change the functionality
soon. If you can just show me the flow of control, that would be fine.
Bob: Don't worry, I'll give you an overview, just enough information to
start your critical quest!
The first line of the file rkn1.rb reads:
which means that it reads in the file rknbody.rb, which is
just a straight copy from the file rknbody9.rb, where we had
just introduced softening as an extra option. Remember, that file
contained the Body and Nbody definitions. The file rknbody.rb
will be the beginning of our N-body library.
Alice: That is something I definitely approve of, to keep the best bits
and piece of our prototyping, and to use those to build up a library.
Bob: Until now, we have used a special driver file where we wrote down
the arguments that were given to the method evolve. In this case, these
arguments will be plucked from the command line, through the method
read_options, the longest method in this file. But before we
get there, it will be easiest to read the file starting at the end.
The last three lines:
are exactly the same as the last three lines of the old driver file
that we used to invoke the softened version of our N-body code. Just
above that, we report the parameter values that are actually used in
the run:
These are also exactly the same as what was written in the old driver.
However, working our way back up, from here on things are different.
Where we wrote down the values of all parameters by hand in the old
driver code, here there are assigned by a call to read_options
with a single argument, called parser, as follows:
The method read_options is written just above this call. At
the top of that method, all default values are assigned to the parameters
that will become the arguments for the call to evolve. Then the method
enters a loop in which all command line options are being read in. They
are being parsed, as they say in computer science, which just means that
their meaning is being interpreted in the correct way.
Alice: And I see that you have defined parser as a global variable.
Somehow this variable, which is an instance of a class called GetoptLong,
knows how to gather the information about all legal options in your code,
both the one-letter versions starting with a single "-" sign,
as well as the longer command line arguments that start with "--".
Bob: Yes, this is a piece of magic that is provided by a package that
I loaded through the statement near the top:
Alice: What does this package do, and roughly how does it work?
Bob: I must admit, I did not really look at it. I was browsing in a few
Ruby books, and I came across this example. It seemed really handy, so I
copied it. And the example was easy enough to change to my own requirements.
The important thing is: it works! And it has made my life already a
lot easier.
1. Command Line Arguments
1.1. A New Approach
|gravity> ruby rkn1.rb -o 2.1088 -e 2.1088 -t 2.1088 < figure8.in
eps = 0
dt = 0.001
dt_dia = 2.1088
dt_out = 2.1088
dt_end = 2.1088
init_out = false
x_flag = false
method = rk4
at time t = 0, after 0 steps :
E_kin = 1.21 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = 0
(E_tot - E_init) / E_init = -0
at time t = 2.109, after 2109 steps :
E_kin = 1.21 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = -2e-15
(E_tot - E_init) / E_init = 1.55e-15
3
2.1089999999998787e+00
1.0000000000000000e+00
-1.6047303546488470e-04 -1.9320664965417420e-04
-9.3227640249930266e-01 -8.6473492670753516e-01
1.0000000000000000e+00
9.7020367429337440e-01 -2.4296620300772800e-01
4.6595057278750124e-01 4.3244644507801255e-01
1.0000000000000000e+00
-9.7004320125790211e-01 2.4315940965738195e-01
4.6632582971180025e-01 4.3228848162952316e-01
You can see from the values that were echoed that I just ran a fourth-order
Runge-Kutta, for 1/3 of an orbit of a figure-eight triple configuration.
And by the way, you can see from the output that I have reproduced the same
positions and velocities as before, as a test that the code still
works correctly.
1.2. Flexibility
|gravity> ruby rkn1.rb -o 10 -e 2.1088 -t 2.1088 -m leapfrog -d 0.0001 < figure8.in
eps = 0
dt = 0.0001
dt_dia = 2.1088
dt_out = 10.0
dt_end = 2.1088
init_out = false
x_flag = false
method = leapfrog
at time t = 0, after 0 steps :
E_kin = 1.21 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = 0
(E_tot - E_init) / E_init = -0
at time t = 2.1088, after 21088 steps :
E_kin = 1.21 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = -6.35e-13
(E_tot - E_init) / E_init = 4.93e-13
You see, I added the option -o 10 to suppress the snapshot output.
This makes the interface much more flexible than it was before.
|gravity> ruby rkn1.rb -h
usage: rkn1.rb [-h (for help)] [-s softening_length] [-d step_size]
[-e diagnostics_interval] [-o output_interval]
[-t total_duration] [-i (start output at t = 0)]
[-x (extra debugging diagnostics)]
[-m integration_method]
You see now what I did. By adding the option -o 10 to the command
line in my last little run above, I asked the program to postpone the first
output to the time t = 10 which is later than the time
t = 2.1088 at which I had ordered the program to halt and to give
energy diagnostics. In that way, I suppressed the output of the snapshot,
so that we could concentrate on looking only at the energy.
1.3. Various Options
|gravity> ruby rkn1.rb --help
usage: rkn1.rb [-h (for help)] [-s softening_length] [-d step_size]
[-e diagnostics_interval] [-o output_interval]
[-t total_duration] [-i (start output at t = 0)]
[-x (extra debugging diagnostics)]
[-m integration_method]
Ah, the same answer. Good! But your help answer is not complete:
it suggests that you can only use single letter options.
|gravity> ruby rkn1.rb --total_duration 1 --output_interval 10 < figure8.in
eps = 0
dt = 0.001
dt_dia = 1
dt_out = 10.0
dt_end = 1.0
init_out = false
x_flag = false
method = rk4
at time t = 0, after 0 steps :
E_kin = 1.21 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = 0
(E_tot - E_init) / E_init = -0
at time t = 1, after 1000 steps :
E_kin = 1.22 , E_pot = -2.5 , E_tot = -1.29
E_tot - E_init = 0
(E_tot - E_init) / E_init = -0
I yes, that indeed works. But what about the -i option? That
was a flag, if I remember it correctly; if you set it, you got the initial
output. What is the long version of the command to set the flag?
1.4. A Critical Attitude
1.5. An Overview
nb = Nbody.new
nb.simple_read
nb.evolve(method, eps, dt, dt_dia, dt_out, dt_end, init_out, x_flag)
eps, dt, dt_dia, dt_out, dt_end, init_out, x_flag, method =
read_options(parser)
Previous | ToC | Up | Next |