Previous ToC Up Next

5. The World Class

5.1. Of Forests and Trees

Alice: You know, I'd like to go over the whole code once again, now that we make sure it works. I feel I can't see the forest for the trees. I'd like to gain a good overview.

Bob: That's a good idea. How shall we do this, from the beginning to the end, or from the end to the beginning?

Alice: I prefer to go top-down. Given the way we have written the code, this means starting at the end, or at least close to the end, with the World class.

Bob: Okay, let's print it out.

 class World
 
   def evolve(c)
     while @era.start_time < @t_end
       @new_era, dn = @era.evolve(c.dt_era, @dt_max, c.shared_flag)
       @nsteps += dn
       @time = @era.end_time
       while @t_dia <= @era.end_time and @t_dia <= @t_end
         @era.write_diagnostics(@t_dia, @nsteps, @initial_energy)
         @t_dia += c.dt_dia
       end
       while @t_out <= @era.end_time and @t_out <= @t_end
         output(c)
         @t_out += c.dt_out
       end
       @old_era = @era
       @era = @new_era
     end
   end
 
   def output(c)
     if c.world_output_flag
       acs_write($stdout, false, c.precision, c.add_indent)
     else
       @era.take_snapshot(@t_out).acs_write($stdout, true,
                                            c.precision, c.add_indent)
     end
   end
 
   def setup_from_world(c)
     init_output(c)
     @t_out += c.dt_out
     @t_end += c.dt_end
     @dt_max = c.dt_era * c.dt_max_param
     @new_era = @era.next_era(c.dt_era)
     @old_era = @era
     @era = @new_era
   end
 
   def setup_from_snapshot(ss, c)
     @era = WorldEra.new
     @era.setup_from_snapshot(ss, c.dt_param, c.dt_era)
     @nsteps = 0
     @dt_max = c.dt_era * c.dt_max_param
     @initial_energy = @era.startup_and_report_energy(@dt_max)
     @time = @era.start_time
     @t_out = @time
     @t_dia = @time
     @t_end = @time
     init_output(c)
     @t_out += c.dt_out
     @t_dia += c.dt_dia
     @t_end += c.dt_end
   end
 
   def init_output(c)
     @era.write_diagnostics(@time, @nsteps, @initial_energy, true)
     if c.init_out
       if c.world_output_flag
         acs_write($stdout, false, c.precision, c.add_indent)
       else
         @era.take_snapshot(@t_out).acs_write($stdout, true,
                                             c.precision, c.add_indent)
       end
     end
   end
 
   def World.admit(file, c)
     object = acs_read([self, WorldSnapshot], file)
     if object.class == self
       object.setup_from_world(c)
       return object
     elsif object.class == WorldSnapshot
       w = World.new
       w.setup_from_snapshot(object, c) if object.class == WorldSnapshot
       return w
     else
       raise "#{object.class} not recognized"
     end
   end
 
 end

5.2. Admittance

Alice: We'd better start at the gate: at the very bottom of our code, we can see how we make our entrance:

 clop = parse_command_line(options_text)                                      
 World.admit($stdin, clop).evolve(clop)                                       

The method World.admit can be found at the end of our World class definition. Unlike most methods we have written so far, this particular function is not an instance method, but a class method, as indicated by the fact that the Class name is added here in front of it.

Bob: And we need this feature, otherwise we couldn't read in the data for a World object. Just like the general class method new, the class method admit can operate by itself, without having to be invoked from an existing World object.

Alice: The first thing this method does is to invoke our acs_read function, which returns an object called object, that can either be a World object or a Worldsnapshot object.

In the first case, we are dealing with a complete output dump from a previous run. Any invocation of world1.rb gives us the choice to produce a snapshot or a complete World dump, as we will see in a moment. In the case of a dump, the class of the object is World, and since we're looking at a class method of class World, self here is the same as World. In that case, the if statement evaluates to true, and we invoke the method object.setup_from_world, in other words, an instance method of class World, with the name setup_from_world.

Now I'm puzzled. That last function sets up all kind of things, it seems, performing a variety of initializations. But after that, World.admit just returns object. What happens with that object, of type World?

Bob: Ah, the compactness of Ruby notation! On the same line where we invoke World.admit, at the very end of the code, we then invoke the member function evolve of the object that is returned.

Alice: Oh, yes, of course. Okay, what happens if we start with a snapshot output? For example, we could start with a Plummer model input file. Or we could take the output from a previous run, if we had decided to do an output of a WorldSnapshot object. Back to World.admit.

Bob: Wait a minute. If we start with a Plummer model, the class of the object that has been read in will be NBody, not WorldSnapshot. Wouldn't the elsif statement in World.admit fail?

Alice: No, it won't. That's the magic that we've built into acsio.rb, remember? In the first line, acs_read tries to recognize either a World object or a WorldSnapshot object. Now an NBody object certainly has nothing to do with the World class. But We have defined the WorldSnapshot class as a subclass of NBody. When acs_read sees this, it opens the gate for the Nbody object, and it then reads it in as a WorldSnapshot object.

Bob: Ah yes, how clever we were, when we wrote that! Almost too clever, I'm afraid. But it does all seem to work. Okay, so even in the case of a completely fresh Plummer model input file, the elsif statement will evaluate as true. Good. In that case we create a new World object w, which we return so that it can be evolved.

Alice: Yes, but before doing so, there is quite a bit of work done in the method setup_from_snapshot, more than in the World case.

Bob: The reason is that a World object is already all set to go, since it came from a previous integration with the same code world1.rb. If we start from a general NBody object, we'll have to do more book keeping. For example, we have to compute the initial energy. You can see the call to startup_and_report_energy, a method of the WorldEra class, in the middle of setup_from_snapshot. In contrast, if we start from a World object, we can safely assume that the initial energy value has already been stored there.

5.3. Output Choice

Alice: I get the picture now; it all comes back. And corresponding to the two ways of reading input, there are also two ways of writing output. In the case of input, we did not need any command line option, since we would recognize from the input offered what type of file we were dealing with. But for doing output, a decision has to be made. This we do with the option, well, . . . I've forgotten which option. Let's ask Ruby to list, first, all the options, and then to explain what the output switch really does:

 |gravity> kali world1.rb -h
 Individual Time Step, Individual Integration Scheme Code
 -c  --step_size_control        : Determines the time step size  [default: 0.01]
 -e  --era_length       : Duration of an era             [default: 0.01]
 -m  --max_timestep_param: Maximum time step (units dt_era) [default: 1]
 -d  --diagnostics_interval: Diagnostics output interval         [default: 1]
 -o  --output_interval  : Snapshot output interval       [default: 1]
 -t  --time_period      : Duration of the integration    [default: 10]
 -i  --init_out         : Output the initial snapshot
 -r  --world_output     : World output format, instead of snapshot
 -a  --shared_timesteps : All particles share the same time step
 --verbosity            : Screen Output Verbosity Level  [default: 1]
 --acs_verbosity                : ACS Output Verbosity Level     [default: 1]
 --precision            : Floating point precision       [default: 16]
 --indentation          : Incremental indentation        [default: 2]
 -h  --help             : Help facility
 ---help                        : Program description (the header part of --help)
 |gravity> kali world1.rb --help -r
 -r  --world_output     : World output format, instead of snapshot
 
     If this flag is set to true, each output will take the form of a
     full world dump, instead of a snapshot (the default).  Reading in
     such an world again will allow a fully accurate restart of the
     integration,  since no information is lost in the process of writing
     out and reading in, in terms of world format.
 
Ah, yes, the -r option, or more descriptively, the --world_output.

Bob: The longer option is easier to recognize, while the shorter option is easier to remember to write.

Alice: I'm not sure about that last part. Anyway, we can use either one, and the help facility can always remind us of both. And in the code, the world_output_flag determines the behavior of the methods output and init_output in the World class.

Bob: With the -r flag switched on, the output is really simple: output does nothing else but invoke our standard output method acs_write, writing out self, the current state of the World object itself.

Without that flag, that is, in the default case, we first have to take a snapshot, using @era.take_snapshot, and then we can write out that snapshot in a similar way, with a call to acs_write.

It's time that we have a look at WorldEra, to see all that happens there, behind the scenes. The only part that we can see here is we create a new era in the first line of setup_from_snapshot, and then ask World#evolve to invoke WorldEra#evolve, through the call to @era.evolve in the second line of the evolve method here.

Alice: Perhaps we should first look at WorldSnapshot, before we descend through the lineage of WorldEra, WorldLine, and WorldPoint.
Previous ToC Up Next