1.3. Input
Alice: But what about this line
@body[i].nb = self
Bob: I have given the Body class an extra instance variable @nb,
which for each body instance will contain the address of the parent,
an instance of the Nbody class. In that way, each Body daughter
is doubly linked to her Nbody parent. The parent can call the
appropriate daughter, by selecting her from the @body[] array,
and the daughter can call the parent directly through her own @nb
variable. Remember that the expression self gives the address of the
Nbody instance itself, which then gets handed down to each body.
Alice: Hmm. In general, I am quite wary of doubly linked list. It is
all too easy to change the link in one direction and to forget to change
the link in the other direction, or to change it in the wrong way.
Bob: Typical one-off errors that often happen in C and C++ are less
likely to occur in Ruby, because so much of the bookkeeping is handled
behind the scenes, as long as you don't confuse 0...n and
0..n. But I see your point, and perhaps we should change that,
later on. For now, let's just go through the code, and then we can decide
whether it will be easy to unlink the backward pointers from daughter
to parent.
My motivation to provide backward links was to give each daughter the
possibility to communicate with her siblings. If one daughter wants
to compute her acceleration, she would need to find the positions of
all other daughters, and the simplest way to do that, I thought, was
to give her a way to ask her parents how to find all the others.
Alice: Fine for now. In the simple_read program you also
provide those backward links for each particle, after which you invoke
the simple_read method for that particle:
def simple_read
@mass = gets.to_f
@pos = gets.split.map{|x| x.to_f}.to_v
@vel = gets.split.map{|x| x.to_f}.to_v
end
The format you have chosen is to start with the number of particles
and the time, followed by the data for each particle.
Bob: Yes. It seemed safer to tell the input routine how many particles
to expect, rather than to let it read in everything to the end of the file.
In some cases you might want to store more than one snapshot, for example,
in one file.
In fact, my code normally will output a series of snapshots, one after each
dt_out interval, just as we did it for the single pseudo-body case.
This will make it possible to restart a run: you can later sequentially read
in a number of those snapshots, each with a nb.simple_read statement
in the driver, selecting the proper one by checking the time variable
specified.
For example, when you invoke the code with:
dt_out = 5
dt_end = 10
nb = Nbody.new
nb.simple_read
nb.evolve(method, dt, dt_dia, dt_out, dt_end)
you can continue the run from the output of this first run, by reading
in that output and discarding the first snapshot:
nb = Nbody.new
nb.simple_read
nb.simple_read
nb.evolve(method, dt, dt_dia, dt_out, dt_end)
Alternatively, and more safely, you could check for the time:
nb = Nbody.new
nb.simple_read
while (nb.time < 10)
nb.simple_read
end
It would be better to use a slightly smaller value than 10, if you want
to pick up the snapshot corresponding to nb.time = 10, since it
is quite possible that it will have been output at nb.time = 9.99999
or so. Also, you would want to check whether the snapshot time is not too
far beyond nb.time = 10. But those are details.