Conventionally, Starlab nodes contain two stories (actually, story pointers): the ``log'' story, in which specific events or points of interest in the history of a node may be noted, and the ``dyn'' story, intended to store dynamical variables that are not part of any of the standard classes (see ``structure''). For any node, the member functions get_log_story() and get_dyn_story() return pointers to the two stories. (The pointers may be NULL if no story data exist.) In addition, the starbase class includes a ``star'' story pointer (accessed by starbase member function get_star_story) and the hydrobase class includes a ``hydro'' story (get_hydro_story).
A line of text may be appended to the log story via the member function log_comment(). A typical use might be
b->log_comment("Close encounter with black hole #7 at time 10 Myr");The intent is that the log story forms a sequential record of key events in a node's history. A specific piece of data may be written to a story in the form of a ``keyword = value'' line by use of the ``put'' functions:
putiq(story *, char *, int) // write an int putulq(story *, char *, unsigned long) // write an unsigned long putrq(story *, char *, real) // write a real putra(story *, char *, real *, int) // write a real array putsq(story *, char *, char *) // write a character string putvq(story *, char *, vector) // write a vectorFor example, the function call
putiq(b->get_dyn_story(), "N_orbits", 2);writes the line
N_orbits = 2to node b's dyn story. The data may subsequently be accessed via the corresponding ``get'' functions:
int getiq(story *, char *) unsigned long getulq(story *, char *) real getrq(story *, char *) void getra(story *, char *, real *, int) char * getsq(story *, char *) vector getvq(story *, char *)e.g.
if (getiq(b->get_dyn_story(), "N_orbits") > 0) ...Unlike the log_comment() function, a second call to putiq() will overwrite the data if it exists, and create a new story line otherwise. If we need to check if a line already exists, we can use the find_qmatch() function:
if (find_qmatch(b->get_dyn_story(), "N")) // an "N = " line already exists else // no "N = " line in the storyStories thus provide a highly flexible (if rather inefficient) means of getting data from one part of a program to another, without the need to create a specific route for it to take (e.g. parameters in a sequence of function calls, or extern data, or FORTRAN common blocks). In typical use, a function may write some (real) data to the dyn story of node b using
putrq(b->get_dyn_story(), "an_interesting_number", value);Later (or in another program), the data can be retrieved by
result = getrq(b->get_dyn_story(), "an_interesting_number");The value of such a general mechanism in a complex programming environment should be obvious. Many Starlab tools rely on this approach.
It is possible to write either type of story line to any type of story (a more general tool to append a string to any story is add_story_line(story*, char*)). However, as a matter of style, we prefer not to write unstructured text to the dyn, star, or hydro stories, and to limit the use of the ``keyword = value'' format in log stories to the root node only (where it is heavily used in passing initial and run-time information from one program to another -- see for example the output of any of the ``make...'' tools that create N-body systems).
Once written, stories remain part of a node's ``permanent record'' until they are explicitly deleted or the node is destroyed (in which case the story data may yet live on in the story of the parent or root node). When the system is written out using put_node(), all stories are saved as plain text along with the ``hard-coded'' class data; similarly, stories are read in and reconstructed when the get_node() is used. In this way, the stories survive as data moves from one program to another, or from one segment of a long simulation to the next.
Externally, a node is represented as simple (more or less) human-readable text. For example, part of the output of an N-body calculation might look like:
(Particle i = 4 N = 1 (Log Close encounter with black hole #7 at time 10 Myr )Log (Dynamics N_orbits = 2 an_interesting_number = 42 m = 0.5 r = -0.1 0.2 0.5 v = 0.3 -0.4 -0.3 )Dynamics (Hydro )Hydro (Star Type = main_sequence T_cur = 0 M_rel = 1 M_env = 0.99 M_core = 0.01 T_eff = 6000 L_eff = 1 )Star )ParticleThis snapshot fragment represents a single node, delimited by the matching ``(Particle'' and ``)Particle'' lines. Following the opening (Particle come lines containing node class information: the particle's index i and the total number of particles N ``contained'' within this node -- 1 for a ``leaf,'' 2 for a binary center-of-mass node, and so on. The remainder of the data is divided into four stories:
|Log||(Log ... )Log|
Contains precisely the log story of the node, line for line
|Dynamics||(Dynamics ... )Dynamics|
Contains the dyn story of the node (if any) and the values of all true class variables associated with the dynamics: mass (m), position (r), velocity (v), etc., in ``keyword = value'' form. (Mass is actually part of the node class, but it seems more natural not to separate it from the dyn variables here.) On input, m, r, v, etc. are interpreted and stored in the appropriate class location; everything else is saved in the dyn story.
|Hydro||(Hydro ... )Hydro|
Contains the hydro story and any hydrobase class data for the node (in this case, no hydro part exists). As with the Dynamics story, any data corresponding to a hydrobase class member is interpreted and stored appropriately; the remainder becomes the node's hydro story.
|Star||(Star ... )Star|
Contains the star story and any starbase class data for the node. As with the Hydro story, any data corresponding to a starbase, single_star, or double_star class member is interpreted and stored; the remainder becomes the node's star story.
The internal tree structure is expressed by nesting the external node representations. Thus, if a second (Particle line is found before the delimiting )Particle for the current node, then the new particle is taken to be the daughter of the first, and so on, recursively. In this way the entire Starlab tree structure is faithfully preserved. For example,
(Particle <--------. N = 1 | (Log | )Log | (Dynamics | m = 1 | )Dynamics | )Particle <--------' (Particle <--------. N = 1 | (Log | )Log | (Dynamics | m = 1 | )Dynamics | )Particle <--------' (Particle <--------. N = 1 | (Log | )Log | (Dynamics | m = 1 | )Dynamics | )Particle <--------'represents three sister nodes at the same level, while
(Particle <----------------. N = 2 | (Log | )Log | (Dynamics | m = 1 | )Dynamics | (Particle <--------. | N = 1 | | (Log | | )Log | | (Dynamics | | m = 0.5 | | )Dynamics | | )Particle <--------' | (Particle <--------. | N = 1 | | (Log | | )Log | | (Dynamics | | m = 0.5 | | )Dynamics | | )Particle <--------' | )Particle <----------------'represents a parent and two daughters (arrows added for clarity).
Finally, the ASCII representation of Starlab data is quite inefficient in terms of space, although we have not yet found disk usage to be a limiting factor in our simulations. However, should it be necessary, on some systems (for example, Linux, but unfortunately not on the DEC UNIX systems that host GRAPEs) it is possible to compress and uncompress the data automatically using gzip and gunzip, without having to type these commands explicitly into the command line. The requirement is that the non-standard include file "pfstream.h" be available. Use of automatic compression is controlled by the following environment variables, set in local/cshrc.starlab:
# STARLAB_HAS_GZIP Define if gzip is available AND if # pfstream.h exists; else set to null # STARLAB_USE_GZIP (RUNTIME) Use compressed I/O if setSee ``installation'' for more details.