A Top-Down Introduction To Implementing
an Experiment Using Swarm


This document attempts to explain the logical structure of a Swarm experiment application. Starting with a very general outline of an idealized experimental procedure, we successively increase the level of specification of each stage of this idealized structure until we arrive at details of an actual running Swarm application.

Along the way, we introduce and describe (very briefly) some of the tools currently available in Swarm to help users build experiments. The tools presented here are only suggestive: the Swarm library documentation and example applications will give a more detailed view of using specific components of Swarm.

These tools are more fully documented in the appropriate library directories. This high-level overview skips much of the detail in an effort to illustrate the logical structure of a Swarm application and to provide some motivation for why things are done the way they are in the example applications.


How we'll proceed

In the following, we will start out with a very high-level outline/abstraction of the "generic form" of an experimental procedure. Then we will increase the magnification on each piece of the outline until we reach specific details of Swarm code. If you compare the gross structure of the example applications you will see that despite the differences in detail, the overall architecture of Swarm applications adheres to the general abstraction of an experimental procedure.

The application we will use as an example is the Heatbugs application. The source code for that example is a thoroughly commented application and intended to be read by new Swarm programmers. It can be a useful template to start from: so can the very-stripped-down "template" application. We recommend that you gradually ease yourself into the Swarm perspective by experimenting with the example applications and branching out by modifying them in various ways until you catch on to the conceptual style of a Swarm Experiment.


Mag 1x: Experimental Procedure in a Computer

At the highest level of abstraction ( = the lowest level of magnification), most experiments look like this:
  1. Set up the physical system to be studied
  2. Set up and calibrate the instrumentation
  3. Run the experimental system and record the outputs of the instrumentation
  4. Analyze results
  5. If not happy with the results:
  6. Publish paper -> tenure -> fame -> etc.....

The important part of step 6) is that the published paper includes enough detail about the experimental setup and how it was run so that other labs with access to the same equipment can recreate the experiment and test the repeatability of the results. This is hardly ever done (or even possible) in the context of experiments run in computers, and the crucial process of independent verification via replication of results is almost unheard of in computer simulation. One goal of Swarm is to bring simulation writing up to a higher level of expression, writing applications with reference to a standard set of simulation tools.

First, let's look at what happens when we port the above stages into the world of a computer. In a computer, you don't just drag the pieces of your experiment in from the outside world and hook them up. You have to create a world with space and time, a bunch of objects in that world (stuff to study and stuff to look at it with), schedules of events over those objects, all sorts of computer widgetry to interact with that artificial world and to manage multiple experimental runs and the data that they generate, and so forth. In other words, in a computer, one usually has to first *create* from scratch all of the bits and pieces of the experimental setup - the virtual equivalent of beakers, bunsen burners, microscopes etc.

Perhaps the most important difference between an experiment in the "real" world and an experiment inside of a computer is the nature of time. In the real world, everything in one's experimental setup is moved forward in time via a very concurrency courtesy of the laws of physics. In a computer experiment, however, the experimenter has to explicitly move every object in his/her artificial universe forward in time, making sure that everything remains within some well-understood state of synchronization. Many fundamental problems in computer science have arisen in the course of trying to understand how to control and use concurrency. Furthermore, most people who implement computer simulations aren't even aware of the subtle, but quite-possibly dominating, impacts of assumptions that they aren't even aware that they are making about concurrency in their model when they code it up and run it.

Therefore, a very important aspect of setting up an experiment in a computer is how one weaves the multiple threads of time that must be woven together coherently in order to produce reliable, repeatable results. Much of our work on Swarm has been devoted to not only making the task of managing concurrency manageable, but towards mechanisms to make people aware that they are always making implicit assumptions about how multiple threads of time are interacting with one another in their experimental setups. Swarm forces experimenters to make their concurrency assumptions explicit, so that others can reproduce their results by implementing the same assumptions about the flow of time.


Mag 2x: Basis of Swarm Computation

Here is a first approximation to embedding the above outline of an experimental procedure in Swarm code:

Swarm is implemented in the Object-Oriented Programming language Objective-C. Computation in a Swarm application takes place by having objects send messages to each other. The basic message syntax is

[targetObject message Arg1: var1 Arg2: var2]

Where "targetObject" is the recipient of the message, "messageArg1:Arg2:" is the message to send to that object, and "var1", "var2", etc, are arguments to pass along with the message. Objective C's messages are keyword/value oriented, which is why the message name "messageArg1:Arg2:" is interspersed with the arguments.

The whole idea of Swarm is to provide an execution context within which a large number of objects can "live their lives" and interact with one another in a distributed, concurrent manner. Furthermore, we wish to insulate the user from having to master all of the highly baroque computer-science wizardry usually required to implement such massively distributed systems of autonomous agents reliably and robustly.

In the context of the Swarm simulation system, the generic outline of an experimental procedure takes the following form:

  1. Create an artificial universe replete with space, time, and objects that can be located, within reason, to certain "points" in the overall structure of space and time within the universe., and allow these objects to determine their own behavior according to their own rules and internal state in concert with sampling the state of the world, usually only sparsely.
  2. Create a number of objects which will serve to observe, record, and analyze data produced by the behavior of the objects in the artificial universe implemented in step 1)
  3. Run the universe, moving both the simulation and observation objects forward in time under some explicit model of concurrency.
  4. Interact with the experiment via the data produced by the instrumentation objects to perform a series of controlled experimental runs of the system.
  5. Depending on what is observed in stage 4), alter the experimental or instrumental "apparatus" and go back to 3).
  6. Publish paper *including* detailed specification of the experimental set-up so that others can recreate your experiment and verify your results.

Mag 3x: Swarm Structures

Swarm applications are structured around the concept of the Swarm. Swarms are the basic building blocks of Swarm simulations: a Swarm is a combination of a collection of objects and a schedule of activity over those objects. The collection are like the matter of the Swarm and the schedule is like the arrow of time moving the objects forward.

Model Swarms

For our current demos, Swarm applications contain two swarms. At the core is the model swarm, the Swarm that encapsulates the simulated model. Everything in the model swarm corresponds to objects in the world being modeled. For instance, in Heatbugs the HeatbugModelSwarm contains a collection of Heatbug agents, a HeatSpace to represent a physical property of the world, and a Grid2d to store agent position.

In addition to the object collection, the model swarm also contains a schedule of activity on the model. The schedule defines the effect of passing time on the model. For the simple heatbugs schedule, the execution is simply to update the HeatSpace (diffusing heat across the world) and then telling each Heatbug agent to move itself.

Model swarms consist of a set of inputs and outputs. The inputs to the HeatbugModelSwarm are model parameters: things like the size of the world, the number of HeatBugs, and the diffusion rate of heat. The outputs of the HeatbugModelSwarm are the observables of the model: the individual Heatbugs, the distribution of heat across the world, etc.

ObserverSwarms

The model swarm alone defines the simulated world. But an experiment does not just consist of the objects being experimented upon, it also includes the experimental apparatus used for observation and measurements. In Swarm computer simulations, those observation objects are placed in an observer swarm.

The most important object in an observer swarm is the model swarm that is being studied. The model swarm is one component of the observer, kind of like a little world in a petri dish on the lab bench. Other observer objects can then input data into the model swarm (setting simulation parameters, for instance) and read data out of the model swarm (collecting statistics of the behavior of agents).

Just as in setting up a model swarm, an observer swarm has a collection of objects (the instrumentation), a schedule of activity, and a set of inputs and outputs. The activity of the observer schedule is to drive data collection - read this number out of the model, draw it on a graph. The inputs to the observer swarm are configurations of the observer tools: what sorts of graphs to generate, for instance. The outputs are the observations.

When running in graphics mode, the observer swarm objects are largely used to mediate user interface. For instance, in Heatbugs the HeatbugObserverSwarm creates Raster widgets, BLTGraphs, Averagers, and Probes. All of these objects are connected into the HeatbugModel swarm to read data, and to graphical interface objects so the human sitting in front of the computer can observe the world.

Interactive, graphical experimentation with models is useful for coming up with intuitions. But for serious experimentation it is necessary to collect statistics, which means doing many runs and storing data for analysis. As an alternative to a graphical observer swarm, you can also create batch swarms, observer swarms that are intended to be run without any interaction at all. Instead of putting up fancy graphics, batch swarms read data from files to control the model and write the data out to other files for analysis. The key here is that the model swarm used in the batch swarm is the exact same model as that used in a graphical observer swarm: the only difference is what tools the observer (batch or graphical) connects to the model.

Summary

Multiple Swarms are used to create an experimental apparatus and control it. The use of multiple Swarms is not restricted only to this use, though: in particular, a model Swarm could itself contain its own subswarms, building a hierarchical simulation. In future Swarm development, we intend to use the power of the multiple Swarm modeling approach to build complicated and flexible models.


Mag 4x: Sketch of Code

Now that we have multiple Swarms and an experimental apparatus, it's time to learn how to use the objects themselves inside an application. Some examples are provided here: to understand this better, it will be necessary to read through example applications and the library documentation. These examples come from the Heatbugs application.

Building a Model Swarm

The key component of a simulation is the model Swarm. Here is the definition of a HeatbugModelSwarm, from HeatbugModelSwarm.h
@interface HeatbugModelSwarm : Swarm {
  int numBugs;                                    // simulation parameters
  double evaporationRate;
  double diffuseConstant;
  int worldXSize, worldYSize;
  int minIdealTemp, maxIdealTemp;
  int minOutputHeat, maxOutputHeat;
  double randomMoveProbability;

  id modelActions;                                // scheduling data structures
  id modelSchedule;

  id heatbugList;                                 // list of all the heatbugs
  Grid2d * world;                                 // objects representing
  HeatSpace * heat;                               // the world
}

-getHeatbugList;                                  // access methods into the
-(Grid2d *) getWorld;                             // model swarm. These methods
-(HeatSpace *) getHeat;                           // allow the model swarm to
                                                  // be observed.

+createBegin: (id) aZone;                         // extra methods you
-createEnd;                                       // provide for Swarms
-buildObjects;
-buildActions;
-activateIn: (id) swarmContext;
The first section of code says that a HeatbugModelSwarm is a kind of Swarm. HeatbugModelSwarm inherits a lot of behavior from generic Swarm, but also adds new variables and methods.

The new variables are enclosed in the braces in the definition of HeatbugModelSwarm. They are split into three general classes of things: simulation parameters, schedule data structures, and objects in the world. This is a typical sort of model swarm.

Finally, a HeatbugModelSwarm defines new methods. The first few methods are used to allow the model to be observed: a HeatbugModelSwarm will give out its list of Heatbugs, for instance, or its HeatSpace. Observers use these methods to monitor the model.

In addition to the observation methods, there are several Swarm-specific methods for the building of Swarms. These are fairly stereotyped. The createBegin and createEnd messages are used to create the Swarm object itself. buildObjects builds the model objects, and buildActions builds the model schedule - more on these later. Finally, activateIn arranges for the execution machinery to execute the Swarm itself.

Defining an Agent

The agents are typically the real focus of a modeling effort. Most of the work in a simulation comes in defining the agent behavior so that the computer agents resemble the real world phenomena you are trying to create.

In the case of Heatbugs, agents are pretty simple. Here is their definition, from Heatbug.h:

@interface Heatbug: SwarmObject {
  double unhappiness;                             // my current unhappiness
  int x, y;                                       // my spatial coordinates
  HeatValue idealTemperature;                     // my ideal temperature
  HeatValue outputHeat;                           // how much heat I put out
  float randomMoveProbability;                    // chance of moving randomly

  Grid2d * world;                                 // the world I live in
  int worldXSize, worldYSize;                     // how big that world is
  HeatSpace * heat;                               // the heat for the world
  Color bugColor;                                 // my colour (display)
}

-setWorld: (Grid2d *) w Heat: (HeatSpace *) h;    // which world are we in?
-createEnd;

-(double) getUnhappiness;

-setIdealTemperature: (HeatValue) i;
-setOutputHeat: (HeatValue) o;
-setRandomMoveProbability: (float) p;
-setX: (int) x Y: (int) y;                        // bug's position
-setBugColor: (Color) c;                          // bug's colour (display)

-step;

-drawSelfOn: (Raster *) r;
Heatbug is a subclass of SwarmObject. SwarmObjects have very little behavior of their own - they are defined as the root class of most objects and control computer science aspects like memory allocation and probability.

Heatbug carry with them a variety of state variables. For instance, each Heatbug has a notion of its ideal temperature, which will affect it's behavior. In addition, Heatbugs have variables that let them know about the world: these agents are storing references to the HeatSpace object, for example.

Most of the Heatbug methods have to do with setting up the agents state - the inputs to a Heatbug. Every heatbug must set up its world and heat objects, via the setWorld:Heat: method. In addition when Heatbugs are created they have their ideal temperature set, their output heat, etc. Heatbugs are also observable. Heatbugs define a getUnhappiness method - the unhappiness is the major measurable aspect of a heatbug, how well optimized it is at the moment. They also have a drawSelfOn method that directs the heatbug to draw itself on the specified graphics widget.

Finally, and most importantly, a Heatbug has a step method. step is where the Heatbugs behavior is defined: every time the Heatbug is told to step it performs its internal calculations, choosing where to move. Each heatbug is told to step when appropriate by the model schedule. The code for step is the real intellectual input into the model, and is worth reading as an example of an agent's behavior.

Building Agents

Now that Heatbugs have been defined, the model swarm needs to create them. This code fragment is from the buildObjects method on HeatbugModelSwarm.
// A loop to create a bunch of heatbugs.
for (i = 0; i < numBugs; i++) {
  Heatbug * hbug;
  int idealTemp, outputHeat;

  // Choose a random ideal temperature, output heat from the specified
  // range (model parameters).
  idealTemp = [uniformRandom rMin: minIdealTemp Max: maxIdealTemp];
  outputHeat = [uniformRandom rMin: minOutputHeat Max: maxOutputHeat];

  // Create the heatbug, set the creation time variables
  hbug = [Heatbug createBegin: [self getZone]];
  [hbug setWorld: world Heat: heat];
  hbug = [hbug createEnd];

  // Add the bug to the end of the list.
  [heatbugList addLast: hbug];

  // Now initialize the rest of the heatbug's state.
  [hbug setIdealTemperature: idealTemp];
  [hbug setOutputHeat: outputHeat];
  [hbug setX: [uniformRandom rMax: worldXSize]  // random position
        Y: [uniformRandom rMax: worldYSize]];                               
}
The details of this code are best explained in reading the documentation for the libraries. Essentially, we first generate two random numbers: an ideal temperature and an output heat for the new Heatbug. We then create the Hheatbug itself with createBegin and fill in the required parameters of world and heat. Once those are set, we can send createEnd to the Heatbug and it is finished being created. After it's done being created we add it into a list of Heatbugs in the model and set a few parameters on it like the ideal temperature and the initial position.

Building Space objects

In Swarm, spaces are really just another kind of agent. In the heatbugs model we create a HeatSpace, a subclass of a diffusion object from the Swarm space libaries (specified in HeatSpace.m). Here is the code from buildObjects in the HeatbugModelSwarm
heat = [HeatSpace createBegin: [self getZone]];
[heat setSizeX: worldXSize Y: worldYSize];
[heat setDiffusionConstant: diffuseConstant];
[heat setEvaporationRate: evaporationRate];
heat = [heat createEnd];
the object is created, a few parameters are set, and then the creation is finalized.

Scheduling a Model Swarm

Once all of the simulated objects are created in buildObjects, the next task is to schedule them in the method buildActions.
modelActions = [ActionGroup create: [self getZone]];
[modelActions createActionTo:      heat        message: M(stepRule)];
[modelActions createActionForEach: heatbugList message: M(step)];
[modelActions createActionTo:      heat        message: M(updateLattice)];

modelSchedule = [Schedule createBegin: [self getZone]];
[modelSchedule setRepeatInterval: 1];
modelSchedule = [modelSchedule createEnd];
[modelSchedule at: 0 createAction: modelActions];
The heatbug model schedule actually consists of two components: an ActionGroup called modelActions and a Schedule called modelSchedule. The ActionGroup is a tightly coupled list of three messages: every time the action group is executed, it will send three messages in a row:
[heat stepRule];
[heatbugList forEach: step];
[heat updateLattice];
The ActionGroup alone specifies three messages to send - in order to put it in the simulation, that ActionGroup is then dropped into a Schedule. The Schedule itself only has one action - to execute modelActions itself. That action takes place at time 0. But because we've set a repeat interval on the schedule of 1, the schedule itself loops, executing every 1 time step. The final result is that modelActions is executed at time 0, time 1, etc.

Building a Graphical Observer Swarm

With the model swarm defined, arranging for a graphical observer Swarm is the next step. For Heatbugs, the code is in HeatbugObserverSwarm. The structure of an observer swarm is almost exactly like building a model swarm.

@interface HeatbugObserverSwarm : GUISwarm {
  int displayFrequency;                           // one parameter: update freq

  id displayActions;                              // schedule data structs
  id displaySchedule;

  HeatbugModelSwarm * heatbugModelSwarm;          // the Swarm we're observing

  // Lots of display objects. First, widgets
  XColormap * colormap;                           // allocate colours
  ZoomRaster * worldRaster;                       // 2d display widget
  BLTGraph * unhappyGraph;                        // graphing widget
  GraphElement * unhappyData;                     // data element on graph

  // Now, higher order display and data objects
  Value2dDisplay * heatDisplay;                   // display the heat
  Object2dDisplay * heatbugDisplay;               // display the heatbugs
  Averager * unhappinessAverager;                 // average value
  ActiveGraph * unhappinessGrapher;               // object that does graph
}                                                            
Again we have input parameters (display frequency), schedule data structures, and resident objects (model swarm, display widgets). The important exception is that HeatbugObserverSwarm is a subclass not just of the generic Swarm class, but specifically a GUISwarm. That implies that the HeatbugObserverSwarm will contain a control panel to allow the user to stop execution, and will also have a special go method to set everything running.

Building a Data Graph

An example of an object inside the HeatbugObserverSwarm is a data graph, the graph of average unhappiness. Here is the code necessary to create that object:
// Create the graph widget to display unhappiness.
unhappyGraph = [BLTGraph create: [self getZone]];
[unhappyGraph title: "Average unhappiness of bugs vs. time"];
[unhappyGraph axisLabelsX: "time" Y: "average unhappiness"];
[unhappyGraph pack];

// Create one data element inside the graph for displaying unhappiness
unhappyData = [unhappyGraph createElement];
[unhappyData setLabel: "u"];

// and build a datapipe: a combination of an averager and a grapher.
// The averager object collects average statistics, the grapher draws them.
unhappinessAverager = [Averager createBegin: [self getZone]];
[unhappinessAverager setList: [heatbugModelSwarm getHeatbugList]];
[unhappinessAverager setProbedSelector: M(getUnhappiness)];
unhappinessAverager = [unhappinessAverager createEnd];

[unhappinessGrapher = [ActiveGraph createBegin: [self getZone]];
[unhappinessGrapher setElement: unhappyData];
[unhappinessGrapher setDataFeed: unhappinessAverager]; // chain them up
[unhappinessGrapher setProbedSelector: M(getAverage)];
unhappinessGrapher = [unhappinessGrapher createEnd];
The first step is to build an instance of a BLTGraph itself and set its captions. Then one GraphElement, one dataset, is created inside that graph. An Averager object is constructed: it is told to use the message getUnhappiness on the model swarm's heatbugList to construct its data. Finally, an ActiveGraph object serves to connect the Averager to the GraphElement: it uses the getAverage method on the Averager to read in data, and sends it to the given ActiveGraph. It takes four components to build this graph, but the resulting tools are powerful and can be configured to work in many different ways.

main()

The last main type of code needed for an application is the function main(), the first function called in your program. All the real work has been done already - all that's left is to create the objects at the right time.
int
main(int argc, char ** argv) {
  HeatbugObserverSwarm * observerSwarm;
  HeatbugBatchSwarm * batchSwarm;

  // Swarm initialization: all Swarm apps must call this first.
  initSwarm(argc, argv);

  // swarmGUIMode is set in initSwarm(). It's set to be 1 if your
  // DISPLAY environment variable is set (ie, you have an X server to
  // do graphics with). Otherwise, it's set to 0.

  if (swarmGUIMode == 1) {
    // We've got graphics, so make a full ObserverSwarm to get GUI objects
    observerSwarm = [HeatbugObserverSwarm create: globalZone];
    [observerSwarm buildObjects];
    [observerSwarm buildActions];
    [observerSwarm activateIn: nil];
    [observerSwarm go];
  } else {
    // No graphics - make a batchmode swarm and run it.
    batchSwarm = [HeatbugBatchSwarm create: globalZone];
    [batchSwarm buildObjects];
    [batchSwarm buildActions];
    [batchSwarm activateIn: nil];
    [batchSwarm go];
  }

  // The toplevel swarm has finished processing, so it's time to quit.
  return 0;
}
main() calls initSwarm (required in all Swarm applications). It then detects if it should do graphics or not, creates the appropriate top level Swarm to contain the model, and sets it to running. Simple as that!

Conclusion

Swarm tries to help computer simulation authors by making it easier to write simulations by making it more formal. The text above gives a narrative introduction into using Swarm for your own models, but a real understanding of Swarm will only come when you start to read through our examples and try to write your own applications. Good luck, and may your simulations be successful!


Swarm Team <swarm-request@santafe.edu>
Last modified: Sun May 12 22:19:42 MDT 1996