marqueur eStat'Perso RTS3 : Software specifications

The software

Building artificial salps and Vibilia having a life-like behavior means transposing, with as little distortion as possible, many features of their structure and biology into computer code. This is of course only a model, at a certain scale, under certain assumptions: many things are not implemented, either because the scale is not appropriate (for example the molecular structure), or the underlying mechanisms are unknown, or they are not deemed important for the purpose of the model. One interest of a model is that, if its results do not fit the field observations, one may try different parameter values, or introduce new parameters or mechanisms. It could happen that it is necessary to redesign several parts of the software, a huge task!

To write this software I used my favorite programming language, Ada 95, because I thought it has many qualities which would make it adequate for this project: it was designed with concurrency in mind from the beginning, it has real-time extensions, it is very legible (user-defined types make easy to express biological or ecological concepts). See also Ada 95: the language of choice for real-time concurrent programming.

RTS3 is somewhat different from most simulation software (a simulation is a model unrolling in time). In RTS3, I refrained myself from using obscure computer tricks (which are the delight of many programmers, but are a catastrophe from a software engineering point of view). The clear and unambiguous syntax of Ada 95, and the thorough static checking of the compiler, were of great help when writing the code. The Ada 95 real-time constructs allowed coding temporal behaviors in a natural way. This source code should not appear too cryptic to a biologist.

In using the HOOD software design method, I tried as much as possible to only retain architectures having a biological meaning, even when alternate designs would seem easier to implement. For example, it would have been easier to put a "with Tunicate, Vibilia;" context clause to the Space object. The characteristics of the creatures would thus be available everywhere. I stuck to a "with Space;" context clause for the Salps and Vibilia packages: the creatures know the space, because they are embedded in it, whereas the physical space has no knowledge of the objects inside it.

Reading the software specifications may help the user to see if, and how, a feature deemed important is implemented. However, it is only the reading of the source code, with its comments, which ultimately may answer the question.

For a more technical description of the RTS3 software, see Laval, 2001. CALIFE 95, the ancestor of RTS3, is documented in Laval, 1997.

 

RTS3 specifications (what the software does)

(All values in italics are set in the configuration file RTS3.CFG. This text file contains comments about the meaning of each parameter).

 

Space

The simulation represents a rectangular area of the ocean wiewed from above. This area is a 2-D grid with 316 x 224 locations. A rectangular frame limits the area, with obstacles all around the frame, which is a reflecting boundary. There is of course no such thing in the open ocean. However, in the course of a simulation (usually 120 to 200 simulated days), the creatures have not enough time to reach the boundaries, so that the ocean area simulated may be considered unbounded.

In the original 1997 CALIFE paper (CALIFE was the former name of RTS3), each grid cell represented a volume 1,000 m long x 0.5 m high, with a virtual width of 0.5 m. (one thousand meters being the distance a salp may travel during a mean day).

I now think that this scale is too large at an individual level. In a salp swarm, individuals are not 1,000 m apart. Cells (daily locations) should correspond to a small volume, say 20 m x 0.5 m x 0.01 m = 0.1 m3, rather than 250 m3. This volume corresponds to the amount of water filtered daily by 25 aggregates of Salpa fusiformis (why 25? See below The representation of chains of aggregates in the software).

 

Time

Each individual has is own "biological clock" which starts at its birth, and triggers its developmental stages. The creatures do not rely on a global clock that would synchronize their behavior. There is, however, a global clock: the simulation clock. This clock is only consulted by the zooids to know if the simulation duration is elapsed.

Each individual, oozooid or chain, occupies a location for a 'day'. It respires, metabolizes, feeds (by filtering water), and if it age and reserves allow, reproduce. Therefore, its respiration, feeding, etc. are rather 'mean daily actions'. This daily granularity has been chosen to conduct simulations for about 150 (simulated) days, to cover for example the springtime. This temporal scale does not allow to accurately model shorter events, like diel vertical migrations; the daily averaging should take into account feeding at the range of depths experienced by the individual during 24 hours.

Time in RTS3 is a scaled down monotonic time, one 'mean day' being represented with a CPU duration in milliseconds, usually 1,500 ms. This is in contrast with the usual "event-driven simulations" where time advances from one event to the next. In RTS3 the time is theoretically continuous (but is, for practical reasons, discrete - because a continuous real interval cannot be represented with a better accuracy than the one of the CPU clock). In RTS3, the biological events occur at their proper dates. However, RTS3 is not strictly speaking a "clock-driven simulation" (it is rather a time-driven simulation), because the creatures are autonomous and asynchronous: they have their own internal clocks, independent from each other and from the simulation clock.

Note that because there are a discrete number of locations on the spatial grid, two creatures cannot access concurrently the same location: perfect simultaneity is impossible. Whatever the time granularity, when two creatures want to access the same location, there is always one, and only one, which reaches it.

 

Zooids

Each "zooid" (a name meaning oozooid or chain) has an associated concurrent Ada task. This task models the behavior of the individual. Once started, this task loops at Internal_Pace intervals (Internal_Pace milliseconds correspond to 1 mean day for a zooid).

After its birth, the embryo does not feed first on external food, but on the eleoblast, and stays attached to its parent. The duration of this short phase, Embryonic_Delay, is either constant or random (uniformly distributed between 0.0 and Embryonic_Delay). In the latter case, the development of all the zooids is not synchronous; this adds some "biological" variability to the simulation.

The oozooid reproduces when its age reaches First_Chain_Emission and its weight is greater or equal to the Oozooid_Adult_Weight. It can reproduce several times, at Between_Chains_Interval(s), producing Number_Of_Chains chain generations. When its age becomes greater than Oozooid_Longevity, its state passes from "Live" to "Cadaver".

Unlike live individuals, cadavers do not swim but fall down; after a decomposition duration they disappear. Cadavers are still "active" creatures. They are still present on the screen, where they can only move downward. When Oozoid_Cadaver_Longevity is elapsed, the cadaver, completely decomposed, disappears and the task is completed. The memory allocated to the record representing the zooid is not recovered, because this would prevent to follow the chain of pointers to record the zooid positions, down from the First_Zooid created by the user. When the cadaver disappears, its state becomes "Void"; this is useful in the recursive descent of the pointers to signal that the zooid structure is no longer displayed.

Between clock cycles, the zooid task moves, feeds and accomplishes its metabolic functions: respiration, excretion. In this version, a procedure Basic_Metabolism, which consumes an increasing amount of reserves according to the current weight of the zooid(s), performs these functions.

According to its metabolic condition, the length of the chain an oozooid produces and the number of chain generations it emits may vary (the values read in the configuration file are only for initialization). A newborn oozooid contains some amount of reserves (a fixed ratio Ooz_Reserves_Min of the initial weight); likewise for the blastozooid. The food requirements are a fixed ratio Food_Coeff of the weight. Passing to the Mature stage is only accomplished if the developing duration (First_Chain_Emission for the oozooid, Oozooid_Emission for the chain) is elapsed and the weight has reached Ooz_ or Bl_Adult_Weight.

Emitting a chain or an oozooid only occurs if the organism is mature and its reserves are greater than the metabolic cost of reproduction. In the case of an oozooid emitting a chain, the number of buds in the chain is proportional to the amount of reserves (up to Number_Of_Aggregates) if the current amount of reserves is < 3 times Ooz_Reproduction_Cost, otherwise the chain will have Number_Of_Aggregates blastozooids.

Reproduction decreases the reserves by Reproduction_Cost, a fixed proportion of the adult weight. After a duration of Oozooid_Longevity, the oozooid passes to the "Cadaver" state; after Oozooid_Cadaver_Longevity it passes to the "Void" state. The food requirements, up to maturity, increase with the weight, and then remain constant.

At each cycle the zooid moves: it checks all the 8 (or 4) adjacent locations around him until it finds an empty (non-ocupied) location and goes to this location. if all 8 (or 4) locations are non-empty it does not move but tries again at the next cycle. He does so 10 times, after what he 'dies by overcrowding'.

Feeding occurs when a space position provides food. The amount of food at that position decreases by the amount removed by the zooid. Each zooid filters a proportion Food_Coeff of its weight. The assimilation is a proportion Food_Conversion_Coeff of the filtered amount. In the non-mature individual, only a part of the assimilated amount (relatively to the adult weight) increases the weight; the remaining amount increases the reserves. This part varies in a logarithmic manner, from 100 % of the current weight at the beginning of development, until near 0 % at maturity. In the mature individual, weight does not continue to increase; 20 percent of the assimilated amount goes to the reserves. Each day, all individuals consume a proportion Metabolic_Coeff of the reserves for their metabolism (not taking reproduction into account). If a zooid starves, its reserves may become negative, up to a minimum value Ooz_ or Bl_Reserves_Min. The state of the zooid passes to Cadaver when it reaches this value, so that death will arrive at the next cycle.

A chain in the software is treated like a kind of "super-individual ", because all its aggregates are clones with identical characteristics, moving together.

For chains, all amounts are multiplied by the current Number_Of_Aggregates. The chain reproduces only once, when its age becomes greater than Chain_Maturation, if it contains enough reserves. Each blastozooid in the chain emits one oozooid. After a duration of Aggregate_Longevity, the chain passes to the "Cadaver" state; after Aggregate_Cadaver_Longevity it passes to the "Void" state.

The representation of chains of aggregates in the software

If chains were represented faithfully in the software, i.e. with about 125 aggregates as is the case in nature, after one generation each chain would produce 125 oozooids, which in turn would give birth (assuming no mortality) to 125 chains, and so on. After only 3 generations, there will exist 125x10^3 or 1,953,125 oozooids. This is much more than a computer with 256 Mb memory may sustain. Tolerable numbers of zooids in 120 simulated days may only be obtained if Number_Of_Aggregates in the software is set to about 5. This is not a problem if the purpose of the simulation is to obtain a swarm, that is, a fair number of interacting zooids. Conclusions drawn from 600 zooids would not teach us much more than from two millions. Of course the amount of food filtered by these "scaled chains" should be adjusted to the number of aggregates they are composed of.

Zooids may become parasited by a demarsupiating Vibilia (the demarsupiation is the process of transferring larve from the brood pouch -- or marsupium -- onto the host), or eaten by a maturing adult Vibilia. When a Vibilia moves to a position occupied by an alive (non already parasited) zooid, it attacks it: it manages to get an "access value" to the zooid (this may succeed or not, depending of the timing of the attack). Once in possession of its host access value, the Vibilia may access the zooid reserves, and decrease them each time she eats.

 

Vibilia parasitoids

When there are enough zooids, an artificial adult Vibilia armata may be created, which will be the founder of an artificial Vibilia population. Vibilia can only live by attacking zooids, laying larvae on them. These larvae then develop on their host. After a maturation duration, they become free-living juveniles. Juveniles devour encountered zooids. They then search for mature zooid hosts to reproduce on them.

A Vibilia individuals undergo the following life-cycle stages: Developing, Preadult, Maturing, Seeking_Hosts (and at times Not_Seeking, see below).

Developing takes place from the moment the larva is deposited on a host, Preadult from the moment the juvenile leaves the host, Maturing from the subadult molt to the time when the larvae issued from the eggs are laid in the brood pouch. When all the larvae have been deposited on hosts, Seeking_Hosts ends. [Maturing + Seeking_Hosts] may occur V_Brood_Number times. When the brood pouch is empty, the state of the Vibilia passes to Not_Seeking. In this state, it continues to devour hosts, but of course does not deposit larvae on them.

The eggs hatch in the female brood pouch, giving "Pantochelis" larvae. The female deposits these larvae, unable to swim, on zooid hosts. Once deposited onto a host, a Pantochelis larva transforms to a Protopleon stage (there are 3 of them).

The larva goes through its development, passing from Protopleon I, II and III, and to Juvenile (the exact number of juvenile stages is unknown; a likely number is 6 juvenile stages and 1 subadult stage before passing to Adult). The juvenile leaves its host at an age estimated to V_Transf. + (V_Dev. / 2) and begins to devour each encountered zooid.

When its age reaches V_Transf.+ V_Dev. + V_Maturation, and its weight reaches V_Adult_Weight, the juv. passes to Maturing. The female reproduces (if she has enough reserves) when its age reaches V_Transf. + V_Development + V_Maturation.

The mature adult (there are only females in this version...) reproduces V_Brood_Number times. Each time there are V_Nb_Of_Larvae (Pantochelis stage) in the mother's brood pouch. The mother seeks a Tunicate host (a mature chain or oozooid) to deposit up to Max_Deposited larvae from its brood pouch. The adult female continues to seek hosts and to deposit larvae on them until there are no more larvae in its brood pouch.

The adult female in the Maturing state eats whole oozooids or aggregates (when she eats a chain, the Number_Of_Aggregates of the chain is adjusted at reproduction, according to the current amount of reserves. After a duration of V_Maturation, her state passes to Seeking_Hosts.

The female in this state searches mature Tunicate hosts to deposit its larvae ("demarsupiation"). When the female is Seeking_Hosts, each successful encounter empties the brood pouch by 1 larva (if the host found is an oozooid) or, if the host is a chain, by My_Max_Deposited larvae or number of aggregates larvae (whichever is less). When all larvae in the brood pouch have been deposited on hosts (or if a duration of Max_Seeking is elapsed), the female enters again a Maturing state (where she eats again). There may be up to V_Nb_Of_Broods [Maturing + Seeking_Hosts] cycles.

When the female deposits its larvae, she does not eat. When the adult intermolt duration is elapsed, the female enters again the Maturing state. If not all her larvae are deposited when the new adult molt arrives, she loses the remaining larvae.

If when she moves the Vibilia with larvae encounters a suitable host at a location, it manages to get the host's access value (a pointer in Ada parlance). With this access value, the Vibilia can reproduce, giving the host's access value to her larvae, or eat the host.

Only one larva may be deposited on an oozooid (if not already parasited); if the host is a chain, the female deposits one larva per aggregate, up to Max_Deposited (or the chain number of aggregates, whichever is less). The larvae access the reserves of their host. They decrease them by an amount proportional to their weight (and increase their reserves by the same amount). This amount increases nonlinearly with their weight, and remains constant when the Maturing state is reached.

When the larva begins to eat, it becomes a "Juvenile" and enters the Developing state. During this phase, the larva grows and requests more and more food. When the juvenile is able to swim, (Preadult phase) if its host dies it is leaved by the juvenile, which starts preying upon zooids. A juvenile on its host is not displayed (the host is instead displayed with a "Parasited" code). When the juvenile leaves its host, it updates its position and swimming direction according to those of the host and leaves it, searching other zooids to eat them. Free-swimming juveniles and Maturing Vibilia search "hosts" (in this case, preys) in the same manner as "Seeking_Hosts" Vibilia.

When they get the prey access value, they eat the prey reserves like larvae do, but with an amount that may be large enough to kill it immediately or a little later. After each contact or attack of a prey, they search new ones, until they become Seeking_Hosts.

When a juvenile demands to eat more than the reserves of a zooid, the host dies (its state passes to "Cadaver"). A newborn Vibilia contains some amount of reserves (a fixed ratio V_Reserves_Min of the initial weight). The food requirements are, for the adult, equal to the salp reserves corresponding to the digestion of one adult oozooid: Adult_Weight * V_Food_Conversion_Coeff.

For the young stages, the food requirements are scaled to the ratio of the current weight relative to the adult weight. Passing to the Maturing stage may only be accomplished if the developing duration (V_Transformation + V_Maturation) is elapsed and the weight has reached V_Adult_Weight.

The production of larvae in the brood pouch may be done if the Vibilia is mature and its reserves are greater than the metabolic cost of reproduction. The oocyte maturation (during the Maturing phase) decreases the reserves by V_Reproduction_Cost, a fixed proportion of the adult weight. If at the times of reproduction the current reserves are less than 3 times V_Reproduction_Cost, the number of larvae produced will be proportional to the reserves level; otherwise, V_Number_Of_Larvae will be produced.

After a duration of Vibilia_Longevity, the Vibilia passes to the "Cadaver" state; after Vibilia_Cadaver_Longevity it passes to the "Void" state.

The food requirements, up to maturity, increase with the weight, and then remain constant. In the non-mature individual, only a part of the assimilated amount (relatively to the adult weight) is used to increase the weight, the remaining increases the reserves. This part varies in a logarithmic manner, from 100 % of the current weight at the beginning of development until near 0 % at maturity. In the mature individual, weight does not continue to increase; 20 percent of the assimilated amount goes to the reserves.

In all individuals, a proportion V_Metabolic_Coeff of the reserves is consumed each day for the metabolism (not taking reproduction into account). If a Vibilia starves, its reserves may become negative, up to a minimum value V_Reserves_Min. If this value is reached, the state of the Vibilia is set to Cadaver, so that its death will arrive at the next cycle.

A free (non-parasiting) Vibilia swims in straight lines, changing randomly its swimming direction every V_Nb_Of_Jumps moves. When the Vibilia hits an obstacle, it searches the next available free position, changing each time its swimming direction to the next available direction (clockwise). To find hosts successfully, a Vibilia must swim faster than the zooids. A greater swimming speed is also necessary when the "Seeking_Hosts" Vibilia leaves its host: otherwise, it will appear in an area where the zooids have had time to exhaust the food, and thus void of potential hosts. To swim faster than a zooid, the code around the Move is enclosed in a loop executed V_Nb_Of_Jumps times.

When the Simulation_Duration is elapsed the Vibilia tasks terminate.


Return to home page

 

(Last modified 2006-06-26)