Instructions
Contents |
Basic demo of Java Swarm on Windows (Jheatbugs-3.0)
Questions? Email timothyrhowe@hotmail.com.
These instructions explain how to build and run the Swarm Jheatbugs Java application from the DOS command line, using only free tools. You may choose instead to use an integrated development environment (IDE), in which case you might follow these instructions loosely.
1. Download and install a Java compiler and engine -- for example, from http://java.sun.com.
2. Download and install Swarm 2.2.
3. Create a source folder for the Jheatbugs application.
4. Create the file setup.bat in your source folder with code similar to this (don't worry about PERLLIB for now):
@echo off set W_SWARMHOME=d:\Swarm-2.2-java set U_SWARMHOME=d:/Swarm-2.2-java set JAVA_HOME=d:\Program Files\Java\jdk1.5.0_05 set PATH=. set PATH=%PATH%;%JAVA_HOME%\bin set PATH=%PATH%;%W_SWARMHOME%\bin set PATH=%PATH%;d:\cygwin\bin set CLASSPATH=. set CLASSPATH=%CLASSPATH%;%U_SWARMHOME%/share/swarm/swarm.jar set CLASSPATH=%CLASSPATH%;%U_SWARMHOME%/share/swarm/kawa.jar set PERLLIB=. set PERLLIB=%PERLLIB%:/cygwin/lib/perl5/5.8.5 set PERLLIB=%PERLLIB%:/cygwin/lib/perl5/5.8.5/cygwin-thread-multi-64int
5. Create the file compile.bat in your source folder with this code:
@echo off @echo Compiling *.java ... javac *.java
6. Create the file interact.bat in your source folder with this code:
@echo off REM Example: interact.bat StartHeatbugs "-Dn=3" @echo off set cmd=java %2 %3 %4 %5 %6 %7 %8 %1 echo In interact.bat, cmd is %cmd% @echo Running %cmd% ... %cmd%
7. Modify the paths (except PERLLIB) in setup.bat appropriately.
8. In your source folder, create the six .java files of Jheatbugs-3.0 with the Java code given below (StartHeatbugs.java through HeatSpace.java).
9. Choose Start - All Programs - Accessories - Command Prompt.
10. In the Command Prompt window that opens, cd to your source folder and type these commands:
setup compile interact StartHeatbugs
11. Expect to see three windows open.
12. Click Start in the ProcCtl window (you may have to move some other windows to expose it).
13. Expect two more windows to open, and expect to see the Heatbugs metabolize.
14. To stop the application, click Quit in the ProcCtl window.
15. To run the application with run-time parameters, as documented in StartHeatbugs.java, in the Command Prompt window, type this command:
interact StartHeatbugs "-Dn=5" "-Dc=1"
If you like Cygwin (Unix emulation on Windows), you can compile your code under Cygwin, but we know of no way to run the application under Cygwin -- if you invoke it, the application will silently die in the call to initSwarm() in StartHeatbugs.java. You must run java from a Command Prompt.
Generally you need to run setup.bat only when you open a Command Prompt, or whenever you change the code in setup.bat.
Generally you need to run compile.bat only when you modify the Java code. Because it compiles all the Java files every time it runs, compile.bat is slow. You can compile faster if you use a makefile. A makefile (lightly tested, with Cygwin's make) is provided below. You will need to change the makefile whenever you change the dependencies of the Java files. To run the makefile, you will need to download the program make, which you can get from http://cygwin.org.
If you use the makefile, to compile just the code that needs to be compiled, invoke
make
If you want to run the program and be certain that you are running the latest code, invoke
make run
If your program gets complicated, you may prefer to use an automated tool to manage the dependencies of the makefile. Most IDEs manage the dependencies automatically. If you want a command-line tool to manage the dependencies (on Cygwin or or DOS), you can use Javamakemake.java from http://timothyhowe.com (or open http://msn.com and search for java.makemake).
Multiple runs for statistical analysis
Running interactively, as shown above, is fine for debugging and exploration of modeling concepts.
But you'll need more if you want to perform multiple runs of Swarm in batch mode, with varying parameter values and varying random seeds, to be followed by statistical analysis using tools of your choice.
For a demo of multiple runs, follow these steps:
1. Download and install Perl, from http://cpan.org (you can also get it from http://cygwin.org, but in March, 2006, that didn't seem to install properly).
2. Modify the PERLLIB paths in setup.bat appropriately. Invoke setup.bat.
3. In your source folder or another folder in your path, create the two ancillary files with the Perl code given below (javarep.pl and repswarm.pl).
4. To confirm that repswarm.pl works, invoke
set SWARMSTARTCLASS=StartHeatbugs perl repswarm.pl -n3 type unhappiness.output
Expect to see in unhappiness.output a record of the average unhappiness of the three heatbugs.
In general you will not be invoking repswarm.pl directly; you will invoke javarep.pl, which will set SWARMSTARTCLASS and invoke repswarm.pl for you.
5. For the demo itself, invoke
perl javarep.pl --sweep p=10 --sweep n=20..25 StartHeatbugs dir exp* /s
Expect the dir command to show the results of the six experiments you requested — looking something like this:
Volume in drive D has no label.
Volume Serial Number is ECAF-C3A4
Directory of D:\m\swarm\jheatbugs-3.0
03/22/2006 09:52 PM <DIR> exp-001
0 File(s) 0 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n20
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n21
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n22
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n23
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n24
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\exp-001\repswarm.pl-p10-n25
03/22/2006 09:52 PM 50 experiment_summary
1 File(s) 50 bytes
Directory of D:\m\swarm\jheatbugs-3.0\java-q3
03/20/2006 09:18 PM 27 experiment_summary
1 File(s) 27 bytes
Total Files Listed:
7 File(s) 327 bytes
1 Dir(s) 32,033,320,960 bytes free
Each of the six experimental subfolders also contains a file named unhappiness.output, as well as a file named stdout0, but for some reason the dir /s command fails to show them. Examine all the files in one or two of the experimental subfolders to see what batch Swarm provides to you.
What to do next
Invoke
javadoc *.java
and study all the Jheatbugs-3.0 documentation, starting with index.html.
For your own Swarm project, you will probably find it easier to modify an existing Swarm program, rather than write a new program from scratch.
For example, suppose your goal is to model the behavior of foxes and lemmings.
Start by getting Jheatbugs-3.0 to run.
Next, rename Heatbug.java to Fox.java, rename StartHeatbugs.java to StartFoxes.java, and similarly for the other files. Change Heatbug to Fox throughout all the Java code (and the makefile, if you're using it), with care to plurals and case sensitivity. Then get the code to compile and run -- if you're not familiar with Java, you will understand quite a bit more about Java class names, file names, and compiling.
Next, copy Fox.java to Lemming.java.
Change Lemming.java only to make all the lemmings blue. Change other code to populate your program with a number of lemmings. Get your new program to run -- which you can confirm by observing the yellow/green foxes and the blue lemmings -- and you will understand more about Java coding and Swarm scheduling.
Then slowly give foxes more fox-like behavior and lemmings more lemming-like behavior.
You will find documentation on the classes and methods that Java provides at http://java.sun.com/j2se/1.5.0/docs/api/ (you can read the documentation online, or download it). The top page of all the Java documentation is http://java.sun.com/j2se/1.5.0/docs/index.html.
Jheatbugs-3.0 Java code
StartHeatbugs.java
// jheatbugs-3.0
// Java Heatbugs program. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import swarm.Globals;
import swarm.objectbase.Swarm;
import swarm.objectbase.SwarmImpl;
import swarm.simtoolsgui.GUISwarm;
import swarm.simtoolsgui.GUISwarmImpl;
/**
<p> See HeatbugModelSwarm for an overview of the jheatbugs program.
<p> The remainder of this discussion is confined to the issue of command-line
parameters.
<p> <b> Controlling this program </b>
<p>
The following Java properties are recognized by this program.
<dir>
c=<boolean>: Start the Heatbugs all in a contiguous cluster
</dir>
<dir>
d=<double>: Specify the diffusion rate (0..1)
</dir>
<dir>
e=<double>: Specify the "evaporation" (really retention) rate (0..1)
</dir>
<dir>
i=<boolean>: Make Heatbugs immobile
</dir>
<dir>
n=<int>: Specify the number of heatbugs
</dir>
<dir>
p=<int>: Specify level of diagnostic messages (try 10 or 100)
</dir>
<dir>
r=<double>: Specify the random-move probability
</dir>
<p>
You can set Java properties on the java command line; for example, invoke
<dir>
<xmp>
javaswarm -Dn=300 StartHeatbugs
</xmp>
</dir>
<p>
to start jheatbugs in GUI mode with 300 Heatbugs, or invoke
<dir>
<xmp>
javaswarm -Dn=300 StartHeatbugs -b
</xmp>
</dir>
<p>
to start jheatbugs in batch mode with 300 Heatbugs.
<p> <b> Precedence of controls </b>
<p>
In batch mode, the Java properties mechanism herein
takes precedence over all other setting of variables,
overriding the SCM file,
which itself overrides the Java initializers and constructors.
<p>
In GUI mode, the probe display
takes precedence over all other setting of variables,
overriding the Java properties mechanism herein,
which itself overrides the Java initializers and constructors.
<p> <b> Modifying this program's properties </b>
<p>
To modify this program to accept an additional boolean property:
<dir>
Add to this documentation section a line analogous to
<dir>
<xmp>
c=<boolean>: Start the Heatbugs all in a contiguous cluster
</xmp>
</dir>
</dir>
<dir>
and add to the code a statement analogous to
<dir>
<xmp>
model.setStartInOneCluster (getBooleanProperty ("c", false));
</xmp>
</dir>
</dir>
<p>
To modify this program to accept an additional double property:
<dir>
Add to this documentation section a line analogous to
<dir>
<xmp>
r=<double>: Specify the random-move probability
</xmp>
</dir>
</dir>
<dir>
If you do not want this file to apply a default
overriding the model's default,
add to the code a statement analogous to
<dir>
<xmp>
model.setRandomMoveProbability
(getDoubleProperty ("r", model.getRandomMoveProbability ()));
</xmp>
</dir>
If you do want this file to apply a default
overriding the model's default,
add a statement analogous to
<dir>
<xmp>
model.setRandomMoveProbability (getDoubleProperty ("r", 0.5));
</xmp>
</dir>
</dir>
<p>
Similarly for int properties, String properties, etc.
*/
public class StartHeatbugs
{
public static void main (String[] args)
{
// Swarm initialization: all Swarm apps must call this first:
System.out.println ("This is StartHeatbugs.main().");
Globals.env.initSwarm
("jheatbugs", "2.1", "bug-swarm@swarm.org", args);
System.out.println ("after initSwarm()");
HeatbugModelSwarm model;
if (Globals.env.guiFlag)
{
// We want graphics, so make an ObserverSwarm to get GUI objects:
HeatbugObserverSwarm topLevelSwarm
= new HeatbugObserverSwarm (Globals.env.globalZone);
Globals.env.setWindowGeometryRecordName (topLevelSwarm, "topLevelSwarm");
model = topLevelSwarm.getHeatbugModelSwarm ();
build (topLevelSwarm, model);
topLevelSwarm.go ();
unbuild (topLevelSwarm, model);
}
else
{
// We do not want graphics, so make a BatchSwarm for writing to files:
HeatbugBatchSwarm topLevelSwarm
= (HeatbugBatchSwarm) Globals.env.lispAppArchiver.getWithZone$key
(Globals.env.globalZone, "batchSwarm");
model = topLevelSwarm.getHeatbugModelSwarm ();
build (topLevelSwarm, model);
topLevelSwarm.go ();
unbuild (topLevelSwarm, model);
}
} /// main()
protected static void build (Swarm swarm, HeatbugModelSwarm model)
{
// ... We'd like to move as much code as possible from the if/then sections
// in main() into this common method build(), but if we move
// getHeatbugModelSwarm(), we get this compile error: *** Error: No method
// named "getHeatbugModelSwarm" was found in type "swarm/objectbase/Swarm".
// Similarly when we try to move go(). So we moved the calls we could
// move and left getHeatbugModelSwarm() and go() behind.
// We could pass immobile to the batch buildObjects(), but we get a compile
// error if we try to pass it to the GUI buildObjects(). So instead we use
// setImmobile() for both:
model.setImmobile (getBooleanProperty ("i", false));
model.setPrintDiagnostics (getIntProperty ("p", 0));
model.setStartInOneCluster (getBooleanProperty ("c", false));
model.setRandomMoveProbability
(getDoubleProperty ("r", model.getRandomMoveProbability ()));
// ... In cases such as this, when we happen not to want to override any
// default set by HeatbugModelSwarm, we apply that value as our own
// default.
model.setDiffusionConstant
(getDoubleProperty ("d", model.getDiffusionConstant ()));
model.setEvaporationRate
(getDoubleProperty ("e", model.getEvaporationRate ()));
model.setNumBugs (getIntProperty ("n", -1));
// ... The special value -1 tells HeatbugModelSwarm to use its own default
// value for numBugs. Use of Integer rather than int would avoid the need
// for this special value.
swarm.buildObjects ();
swarm.buildActions ();
swarm.activateIn (null);
} /// build()
protected static void unbuild (Swarm swarm, HeatbugModelSwarm model)
{
swarm.drop ();
}
/**
This method and the other get...Property() methods are generic convenience
methods that would ideally be defined in some utility library.
*/
protected static boolean getBooleanProperty (String propertyName, boolean dflt)
{
String property = System.getProperty (propertyName);
if (property == null || property.equals ("")) return dflt;
else return property.equals ("true") || property.equals ("1");
}
protected static double getDoubleProperty (String propertyName, double dflt)
{
String property = System.getProperty (propertyName);
if (property == null || property.equals ("")) return dflt;
else return Double.parseDouble (property);
}
protected static int getIntProperty (String propertyName, int dflt)
{
String property = System.getProperty (propertyName);
if (property == null || property.equals ("")) return dflt;
else return Integer.parseInt (property);
}
protected static String getStringProperty (String propertyName, String dflt)
{
String property = System.getProperty (propertyName);
if (property == null) return dflt;
return property;
}
}
HeatbugModelSwarm.java
// jheatbugs-3.0
// Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import swarm.Globals;
import swarm.Selector;
import swarm.defobj.Zone;
import swarm.defobj.SymbolImpl;
import swarm.defobj.FArguments;
import swarm.defobj.FArgumentsImpl;
import swarm.defobj.FCall;
import swarm.defobj.FCallImpl;
import swarm.activity.Activity;
import swarm.activity.ScheduleActivity;
import swarm.activity.ActionGroup;
import swarm.activity.ActionGroupImpl;
import swarm.activity.Schedule;
import swarm.activity.ScheduleImpl;
import swarm.activity.FActionForEach;
import swarm.objectbase.Swarm;
import swarm.objectbase.SwarmImpl;
import swarm.objectbase.VarProbe;
import swarm.objectbase.MessageProbe;
import swarm.objectbase.EmptyProbeMapImpl;
import java.util.ArrayList;
import swarm.space.Grid2d;
import swarm.space.Grid2dImpl;
/**
Since this class is the core of the jheatbugs simulation, we present
here an overview of the entire application.
<p>
The following diagram depicts the structure of the six custom classes of the
jheatbugs application:
<xmp>
| +-----------------+
| | StartHeatbugs |
| +-----------------+
| | 1
| |
| +---------------+-----------+
| | |
| | 1 | 1
| +---------------------+ +------------------------+
| | HeatbugBatchSwarm | - OR - | HeatbugObserverSwarm |
| | extends | | extends |
| | SwarmImpl | | GUISwarmImpl |
| +---------------------+ +------------------------+
| | 1 | 1
| | |
| +---------------+-----------+
| |
| | 1
| +---------------------+
| | HeatbugModelSwarm |-----------------+
| | extends | 1 |
| | SwarmImpl | |
| +---------------------+ |
| | 1 | 1
| | +---------------+
| | | HeatSpace |
| | | extends |
| | | Diffuse2dImpl |
| | +---------------+
| | 0..n | 1
| +-----------------+ |
| | Heatbug |-------------------+
| +-----------------+ 0..n
</xmp>
Five of the custom classes involved in the simulation are:
<dir>
1. HeatbugModelSwarm:
the Heatbug simulation mechanism, which also manages supporting classes
such as Heatbug and HeatSpace, and is available for
viewing through either of the two observer classes.
</dir>
<dir>
2. StartHeatbugs: the class with the <tt>main()</tt> method that
kicks off the simulation. Depending on the command-line option <i>-b</i>,
StartHeatbugs creates either a HeatbugBatchSwarm or a HeatbugObserverSwarm
in which to present the view of the HeatbugModelSwarm.
</dir>
<dir>
3. HeatbugBatchSwarm:
the non-GUI observer: a view of the model presented through
text output. <i>Batch</i> does not refer to a
batch of Heatbugs, but rather to batch mode as opposed to GUI mode.
HeatbugBatchSwarm writes to files and to standard output.
</dir>
<dir>
4. HeatbugObserverSwarm:
the GUI observer: a view of the model presented through
graphs and plots.
</dir>
<dir>
5. Heatbug: the class that defines the individual
agents. A Heatbug has data (including its position in 2-dimensional
space), behavior (including the ability to move in 2-dimensional space),
and the ability to perceive quantities of heat in its immediate neighborhod.
</dir>
We use two 2-dimensional data structures in the model: one to record
the positions of the Heatbugs, and another, of identical size, to record the
heat that the Heatbugs produce as well as seek.
The following diagram provides a rough illustration of the two data
structures:
<xmp>
^ Position: ^ Heat:
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | | | | | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | h | h | h | | | | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | b | | | | | | | | | h | h | h | | | | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | b | | | | | | | | | h | h | h | | | | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | h | h | h | h | h | h | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
y | | | | | | b | | | y | | | | | h | h | h | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| | | | | | | | | | | | | | | | h | h | h | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
0 | | | | | | | | | 0 | | | | | | | | |
| +---+---+---+---+---+---+---+---+ | +---+---+---+---+---+---+---+---+
| --0-------x---------------------> | --0-------x--------------------->
</xmp>
where b represents a bug and h represents a non-zero quantity of heat.
<p>
HeatbugObserverSwarm displays the positions and the heat together on a
ZoomRaster. HeatbugBatchSwarm does not report the positions or heat, but only
the average unhappiness of the Heatbugs.
<p>
For the model's positional data,
we use Swarm's class Grid2d. For the heat data,
we use Swarm's Diffuse2dImpl, whose behavior includes the dissipation
and "evaporation" of heat.
Rather than use Diffuse2dImpl directly, we need to specialize it, so
we define HeatSpace, the last of the six custom classes involved in the
Heatbug simulation:
<dir>
6. HeatSpace: a 2-dimensional array of heat values.
We don't specify the heat units, but they might be degrees Celsius.
HeatSpace inherits diffusion and evaporation behaviors from Diffuse2dImpl,
and it also provides the ability to
report the hottest or coldest cells within a neighborhood.
</dir>
*/
public class HeatbugModelSwarm extends SwarmImpl
{
// Variables referenced by an SCM file or (after Swarm 2.1.1) by a ProbeMap
// must be public:
public int numBugs = 101;
public void setNumBugs (int numBugs)
{ if (numBugs != -1) this.numBugs = numBugs; }
// ... See StartHeatbugs.java for an explanation of the value -1.
public int minIdealTemp = 17000;
public int maxIdealTemp = 31000;
public int minOutputHeat = 3000;
public int maxOutputHeat = 10000;
public boolean randomizeHeatbugUpdateOrder = false;
public double randomMoveProbability = 0.4;
public double getRandomMoveProbability ()
{ return randomMoveProbability; }
public void setRandomMoveProbability (double randomMoveProbability)
{ this.randomMoveProbability = randomMoveProbability; }
public int worldXSize = 80;
public int worldYSize = 80;
// Todo: diffusionConstant and some other variables are copies of
// variables in Diffuse2d -- can we figure out a way to get rid of them?
public double diffusionConstant = 1.0;
// ... 0 = minimum, 1 = maximum diffusion of heat in _heatSpace.
public double getDiffusionConstant ()
{ return diffusionConstant; }
public Object setDiffusionConstant (double diffusionConstant)
{ this.diffusionConstant = diffusionConstant; return this; }
public double evaporationRate = 0.99;
// ... 0 = minimum, 1 = maximum retention of heat in _heatSpace.
public double getEvaporationRate ()
{ return evaporationRate; }
public Object setEvaporationRate (double evaporationRate)
{ this.evaporationRate = evaporationRate; return this; }
// ... According to the documentation for Diffuse2d, newHeat = "evapRate *
// (self + diffusionConstant*(nbdavg - self)) where nbdavg is the weighted
// average of the 8 neighbours" -- but what does "weighted" mean?
protected Schedule _modelSchedule;
protected ArrayList<Heatbug> _heatbugList;
public ArrayList<Heatbug> getHeatbugList ()
{ return _heatbugList; }
protected Grid2d _world;
public Grid2d getWorld ()
{ return _world; }
protected HeatSpace _heatSpace;
public HeatSpace getHeatSpace ()
{ return _heatSpace; }
protected FActionForEach _actionForEach;
protected boolean _immobile = false;
// ... If _immobile == true we will see what happens when Heatbugs never move.
public boolean getImmobile () { return _immobile; }
public void setImmobile (boolean immobile) { _immobile = immobile; }
protected boolean _startInOneCluster = false;
// ... If _startInOneCluster == true we will see what happens when Heatbugs all
// start in a contiguous cluster.
public boolean getStartInOneCluster () { return _startInOneCluster; }
public void setStartInOneCluster (boolean startInOneCluster)
{ _startInOneCluster = startInOneCluster; }
public int printDiagnostics = 0;
public void setPrintDiagnostics (int printDiagnostics)
{ this.printDiagnostics = printDiagnostics; }
/**
The only task this constructor performs is to construct and install a ProbeMap.
<p>
A Probe is a mechanism through which any object can access a
variable or method you define in a Swarm. In practice, Probes are used
almost exclusively by Swarm GUIs
to provide interactive access to variable and methods.
<p>
A ProbeMap is a set of Probes, stored as a Map.
(A Map is a set of name-value pairs.
In a ProbeMap, each name is the name of the variable or method;
each value is a pointer to the variable or method itself.)
<p>
All the Probes in a ProbeMap must belong to the same class; and all the
Probes in a particular class must be in one ProbeMap (unless you are choosing
one among several ProbeMaps at run time).
<p>
A ProbeDisplay is a GUI widget that presents a ProbeMap interactively.
You can see an example by running this application in GUI mode: note that
the variables and methods shown in the ProbeDisplay for HeatbugModelSwarm
exactly match the variables and methods
listed in addProbe() statements in this constructor.
<p>
Probes do not use getters and setters. After Swarm 2.1.1, the variables and
methods in a ProbeMap must be public (as must the variables referenced by an
SCM file).
<p>
Because Probes do not use getters and setters, you cannot use Probes to
control write access differently from read access.
<p>
By default, a ProbeMap will contain <i>all</i> the variables of the specified
class. To control explicitly which variables are in the ProbeMap, create
an EmptyProbeMap rather than a ProbeMap, and insert into
the EmptyProbeMap the variables and methods you want, as we do in this method.
<p>
ProbeMaps are collected into a ProbeLibrary. A Swarm has at most one
ProbeLibrary, and a ProbeLibrary has one ProbeMap for each class that has
one or more Probes, as illustrated in this diagram:
<xmp>
swarmObject1
|
|
|
probeLibrary (for swarmObject1)
|
+----------------------------+
| |
probeMap1 (for Class1) probeMap2 (for Class2)
| |
+------+------+ +-------+-------+-------+------------+
| | | | | | | |
var11 var12 method11() var21 var22 var23 method21() method22()
</xmp>
*/
public HeatbugModelSwarm (Zone aZone)
{
super (aZone);
EmptyProbeMapImpl heatbugModelProbeMap = new EmptyProbeMapImpl
(aZone, getClass ());
heatbugModelProbeMap.addProbe (probeVariable ("numBugs"));
heatbugModelProbeMap.addProbe (probeVariable ("minIdealTemp"));
heatbugModelProbeMap.addProbe (probeVariable ("maxIdealTemp"));
heatbugModelProbeMap.addProbe (probeVariable ("minOutputHeat"));
heatbugModelProbeMap.addProbe (probeVariable ("maxOutputHeat"));
heatbugModelProbeMap.addProbe (probeVariable ("randomMoveProbability"));
heatbugModelProbeMap.addProbe (probeVariable ("printDiagnostics"));
heatbugModelProbeMap.addProbe (probeVariable ("diffusionConstant"));
heatbugModelProbeMap.addProbe (probeVariable ("evaporationRate"));
heatbugModelProbeMap.addProbe (probeVariable ("worldXSize"));
heatbugModelProbeMap.addProbe (probeVariable ("worldYSize"));
// The number of colons after the name of each method must match the number
// of arguments in the method's signature:
heatbugModelProbeMap.addProbe (probeMessage ("addHeatbugs:"));
Globals.env.probeLibrary.setProbeMap$For
(heatbugModelProbeMap, getClass ());
} /// constructor
/**
This method activates the schedules so they're ready to run.
<p>
Todo: explain more thoroughly what this method does. Explain who calls it.
Explain what an Activity is.
@param swarmContext (in)
the larger context within which this model is activated; a model swarm
usually runs in the context of an observer swarm or a super-model swarm;
the effect of specifying a context is to specify a native memory
allocation zone
*/
public Activity activateIn (Swarm swarmContext)
{
super.activateIn (swarmContext);
// Add our own schedule to the base class's schedule:
_modelSchedule.activateIn (this);
// Return the schedule that we inherit from the base class:
return getActivity ();
} /// activateIn()
/**
If Heatbugs gave birth or immigrated, you could use this method to introduce
them into the model.
<p>
Since the program does not consult numBugs after initialization, invocation
of this method from the probe display after the simulation has started will
have no effect.
*/
public Object addHeatbugs (int numNewBugs)
{
numBugs += numNewBugs;
System.out.println ("I will add " + numNewBugs + " new Heatbugs.");
return this;
}
/**
This method schedules the actions of this model Swarm.
<p>
Why do we need to "build actions"? Why can't we just write standard Java
methods? -- they build actions, don't they?
<p>
The answer is that Swarm's simulation engine runs in Objective-C code, behind
the Java Native Interface (JNI). To invoke the behavior of the objects we model
in Java, the simulation engine relies on callbacks, which are methods that we
ask the engine to invoke at the appropriate times. We use
<tt>buildActions()</tt> to define those methods and the timing of the calls.
<p>
This Swarm contains the Schedules, ActionGroups, and Actions depicted in the
following diagram.
<xmp>
Swarm
this
|
|
|
Schedule
modelSchedule
|
+-------------------------------------------------------+
| |
ActionGroup implied
modelActions ActionGroup
| |
+--------------+--------------------+ |
| | | |
Action Action Action Action
_heatSpace _heatbugList.get() _heatSpace this
.stepRule() .heatbugStep() .updateLattice() .modelStep()
</xmp>
<p>
In the remainder of this documentation section we discuss Schedules,
ActionGroups, Actions, repeat intervals, and time values. If you're happy
letting Swarm invoke every behavior at every step of the simulation,
you don't need
to understand all this -- just create one Schedule and no ActionGroups, and
for each behavioral method in your simulation invoke
<xmp>
<schedule>.createActionTo$message
(this, new Selector (this.getClass (), <methodname>, false))
</xmp>
or another <tt>createAction...()</tt> or <tt>createFAction...()</tt> method.
<p>
A Schedule is a three-level hierarchy whose full structure
is illustrated by this example:
<xmp>
Simulation
|
+-----------------+
| |
Schedule Schedule
| |
| +-------------+-------------+------------+--------+--------+
| | | | | | |
ActionGroup ActionGroup ActionGroup ActionGroup | | |
| | | | | | |
+--------+ | | | | | |
| | | | | | | |
Action Action Action Action Action Action Action Action
</xmp>
Consider first a Schedule with this subset of that full sample structure:
<xmp>
Simulation
|
+-----------------+
| |
Schedule Schedule
RI = 1 RI = 3
| |
| +-------------+-------------+
| | | |
ActionGroup ActionGroup ActionGroup ActionGroup
TV = 0 TV = 0 TV = 1 TV = 2
| | | |
+--------+ | | |
| | | | |
Action Action Action Action Action
</xmp>
where RI indicates the <i>repeat interval</i> of a Schedule,
as specified in the invocation of the Schedule, and
TV indicates the <i>time value</i> of an ActionGroup,
as specified in the invocation
of the Schedule's method <tt>at$createAction()</tt>.
<p>
That Schedule would have the following timeline:
<xmp>
| | | | | | | |
S1 S1 S1 S1 S1 S1 S1 S1
S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0
| | | | | | | |
S3 | | S3 | | | S3
S3-tv0 | | S3-tv0 | | | S3-tv0
| S3-tv1 | | S3-tv1 | | |
| | S3-tv2 | | S3-tv2 | |
| | | S3-tv3 | | S3-tv3 | Error: see note.
| | | | | | | |
time --->
</xmp>
where
<p><dir>
S1 represents the Schedule with a repeat interval of 1;
<p><dir>
S1-tv0 represents the ActionGroup in S1 (with a repeat interval of 0);
</dir>
</dir>
<p><dir>
S3 represents the Schedule with a repeat interval of 3;
<p><dir>
S3-tv0 represents the ActionGroup in S3 with a time value of 0;
</dir>
<p><dir>
S3-tv1 represents the ActionGroup in S3 with a time value of 1;
</dir>
<p><dir>
and so on.
</dir>
</dir>
<p>
Note: We introduced Action S3-tv3 into the diagram to illustrate what
happens when the time value of an action group is greater than or equal to
the repeat interval of its ActionGroup. S3-tv3 will cause a run-time error,
because its time value will make the ActionGroup
"run over" into the next iteration of the Schedule.
<p>
As depicted in the first illustration at the top of this section, you can also
insert an Action directly into the Schedule, rather than insert the Action
into an ActionGroup that you insert into the Schedule.
If you insert Actions directly into a Schedule,
you must specify the time value of the Action, and (as for ActionGroups)
the time value must be less than the repeat inverval of the Schedule.
The Schedule collects all Actions with a particular time value
into an implied ActionGroup with that time value. For
example, inserting two Actions
with a time value of 0 and one with a time value of 1 directly into
a Schedule with a repeat interval of 3
would result in the following timeline:
<xmp>
| | | | | | | |
S3 | | S3 | | S3 |
IAG-tv0 | | IAG-tv0 | | IAG-tv0 |
a-tv0-p | | a-tv0-p | | a-tv0-p |
a-tv0-q | | a-tv0-q | | a-tv0-q |
| | | | | | | |
| IAG-tv1 | | IAG-tv1 | | IAG-tv1
| a-tv1 | | a-tv1 | | a-tv1
| | | | | | | |
time --->
</xmp>
where
<p><dir>
IAG-tv0 represents an implied ActionGroup with a time value of 0;
<p><dir>
a-tv0-p represents an Action with a time value of 0;
</dir>
<p><dir>
a-tv0-q represents the other Action with a time value of 0; and
</dir>
</dir>
<p><dir>
IAG-tv1 represents an implied ActionGroup with a time value of 1;
<p><dir>
a-tv1 represents the Action with a time value of 1.
</dir>
</dir>
<p>
In conclusion, the following diagram and timeline summarize the entire
discussion above, without presenting any additional scheduling concepts:
<xmp>
Simulation
|
+-----------------+
| |
Schedule Schedule
RI = 1 RI = 3
| |
| +-------------+-------------+------------+-----------------+
| | | | | |
ActionGroup ActionGroup ActionGroup ActionGroup IAG-tv0 IAG-tv1
TV = 0 TV = 0 TV = 1 TV = 2 | |
| | | | | |
+--------+ | | | +--------+ |
| | | | | | | |
Action Action Action Action Action Action Action Action
- - - - - TV = 0 TV = 0 TV = 1
</xmp>
<xmp>
| | | | | | | |
S1 S1 S1 S1 S1 S1 S1 S1
S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0 S1-tv0
| | | | | | | |
S3 | | S3 | | | S3
S3-tv0 | | S3-tv0 | | | S3-tv0
| S3-tv1 | | S3-tv1 | | |
| | S3-tv2 | | S3-tv2 | |
| | | S3-tv3 | | S3-tv3 | Error: TV >= RI.
IAG-tv0 | | IAG-tv0 | | IAG-tv0 |
a-tv0-p | | a-tv0-p | | a-tv0-p |
a-tv0-q | | a-tv0-q | | a-tv0-q |
| | | | | | | |
| IAG-tv1 | | IAG-tv1 | | IAG-tv1
| a-tv1 | | a-tv1 | | a-tv1
| | | | | | | |
time --->
</xmp>
*/
public Object buildActions ()
{
super.buildActions();
ActionGroup modelActions = new ActionGroupImpl (getZone ());
// Define the first Action of ActionGroup modelActions:
try
{
modelActions.createActionTo$message
(_heatSpace, new Selector (_heatSpace.getClass (), "stepRule", false));
// ... "stepRule" is the name of a callback method in Diffuse2d, which
// HeatSpace inherits from. It is Diffuse2d's sole non-inherited
// behavioral method. In other words, it's the only thing that a
// Diffuse2d as such knows how to do at any step of the simulation.
// In particular, it applies diffusion and evaporation.
//
// ... We haven't yet figured out what "false" does; the Swarm Reference
// Guide has no entry for Selector.
} catch (Exception e)
{ System.err.println ("Exception stepRule: " + e.getMessage ()); }
// Define the second Action of ActionGroup modelActions:
try
{
// Use Heatbug #0 as a prototype for indicating the class that the
// heatbugStep Action will access and for traversing _heatbugList:
Heatbug proto = (Heatbug) _heatbugList.get (0);
Selector sel = new Selector (proto.getClass (), "heatbugStep", false);
_actionForEach = modelActions.createFActionForEachHomogeneous$call
(_heatbugList,
new FCallImpl (this, proto, sel, new FArgumentsImpl (this, sel))
// ... Through Swarm 2.1, FArgumentsImpl() takes 3 arguments (of which
// the last argument should be the boolean value true). After Swarm 2.1,
// it takes 2 arguments.
);
} catch (Exception e)
{ e.printStackTrace (System.err); }
// Tell Swarm to update Heatbugs sequentially, not randomly:
_actionForEach.setDefaultOrder (Globals.env.Sequential);
// Define the third Action of ActionGroup modelActions:
try
{
modelActions.createActionTo$message
(_heatSpace, new Selector (_heatSpace.getClass (), "updateLattice", false));
// ... "updateLattice" is the name of a callback method (see the
// discussion of stepRule above). It is a method in DblBuffer2d.
// HeatSpace extends Diffuse2d, which extends Ca2d, which extends
// DblBuffer2d.
} catch (Exception e)
{ System.err.println ("Exception updateLattice: " + e.getMessage ()); }
// Define the Schedule:
_modelSchedule = new ScheduleImpl
(getZone (),
1
// ... The repeat interval is 1, so _modelSchedule will begin every step
// of the simulation.
);
// Insert the ActionGroup modelActions into the Schedule:
_modelSchedule.at$createAction
(0,
// ... The Schedule will execute ActionGroup modelActions at time
// value 0 relative to the beginning of the Schedule.
modelActions
);
// Define the sole Action of the implied ActionGroup:
try
{
_modelSchedule.createActionTo$message
(this, new Selector (this.getClass (), "modelStep", false));
// ... This method invocation implies a time value of 0; it is equivalent
// to invoking _modelSchedule.at$createActionTo$message (0, ...).
} catch (Exception e)
{ System.err.println ("Exception modelStep: " + e.getMessage ()); }
return this;
} /// buildActions()
/**
This method constructs the custom objects of this model Swarm.
<p>
Why do we need to "build objects"? Why can't we just use standard Java
constructors? -- they build objects, don't they?
<p>
The answer is that Swarm's simulation engine runs in Objective-C code, behind
the Java Native Interface (JNI). We use <tt>buildObjects()</tt> to define the
data structures that the Swarm infrastructure will replicate behind JNI.
*/
public Object buildObjects ()
{
// Let our parent class build anything it needs to:
super.buildObjects();
// Create a 2-dimensional array of Heatbug positions:
_world = new Grid2dImpl (getZone (), worldXSize, worldYSize);
// Create a HeatSpace, which is a 2-dimensional array of heat values:
_heatSpace = new HeatSpace
(getZone (),
worldXSize,
worldYSize,
diffusionConstant,
evaporationRate,
printDiagnostics
);
// Create a list to keep track of the Heatbugs:
_heatbugList = new ArrayList<Heatbug> ();
int x = 0;
int y = 0;
// Create numBugs Heatbugs:
for (int heatbugIndex = 0; heatbugIndex < numBugs; heatbugIndex++)
{
// Create a Heatbug:
Heatbug heatbug = new Heatbug
(_world,
_heatSpace,
this,
heatbugIndex,
printDiagnostics
);
// Add the bug to the end of the list:
_heatbugList.add (heatbug);
// Randomly choose an ideal temperature and an output heat, within the
// allowable range:
int idealTemp =
Globals.env.uniformIntRand.getIntegerWithMin$withMax
(minIdealTemp, maxIdealTemp);
int outputHeat =
Globals.env.uniformIntRand.getIntegerWithMin$withMax
(minOutputHeat, maxOutputHeat);
// Initialize the rest of the Heatbug's state:
heatbug.setIdealTemperature (idealTemp);
heatbug.setOutputHeat (outputHeat);
heatbug.setRandomMoveProbability (randomMoveProbability);
_world.setOverwriteWarnings (true);
if (_startInOneCluster)
{
// This would be all we'd need, if collisions were OK:
///heatbug.putAtX$Y (worldXSize/2, worldYSize/5);
// But we're avoiding collisions, so:
// We will allow no collisions, so we'll squeeze them into a box
// about sqrt (numBugs) high and by sqrt (numBugs) wide:
heatbug.putAtX$Y
((worldXSize/2 + x) % worldXSize,
(worldYSize/5 + y) % worldYSize
);
if (++x >= Math.pow (numBugs, 0.5))
{
x = 0;
++y;
}
}
else
{
// For simpler code, we allow collisions here: -- the Heatbugs
// quickly separate themselves:
heatbug.putAtX$Y
(Globals.env.uniformIntRand.getIntegerWithMin$withMax
(0, (worldXSize-1)),
Globals.env.uniformIntRand.getIntegerWithMin$withMax
(0, (worldYSize-1))
);
// ... We could eliminate collision-warning messages by invoking
// world.setOverwriteWarnings (false) before this loop. We could
// run more safely by setting it true again after the loop. But we
// could, with small probability, still get a warning message if
// two Heatbugs are initialized at the same cell and, being hemmed
// in by other Heatbugs, they both choose to stay in the cell.
}
if (printDiagnostics >= 1)
System.out.println
("I initialized Heatbug " + heatbug + ".");
} /// for each Heatbug
return this;
} /// buildObjects()
public Object modelStep ()
{
_heatSpace.setPrintDiagnostics (printDiagnostics);
// Monitor the heat at an arbitrary cell (2, 2) (HeatSpace monitors
// the same cell):
int x = 2; int y = 2;
if (printDiagnostics >= 10)
System.out.println
("In modelStep(), at step "
+ getActivity ().getScheduleActivity ().getCurrentTime ()
+ ", heat at (" + x + ", " + y + ") is "
+ _heatSpace.getValueAtX$Y (x, y) + "."
);
// See if historical heat is a function of the number of steps:
if (printDiagnostics >= 20)
{
System.out.println
("Historical heat / step count is " +
(_heatSpace.totalHeat () + _heatSpace.getDiscardedHeat ())
/ (getActivity ().getScheduleActivity ().getCurrentTime () + 1)
+ "."
);
}
return this;
}
protected MessageProbe probeMessage (String name)
{
return Globals.env.probeLibrary.getProbeForMessage$inClass
(name, HeatbugModelSwarm.this.getClass ());
}
protected VarProbe probeVariable (String name)
{
return Globals.env.probeLibrary.getProbeForVariable$inClass
(name, HeatbugModelSwarm.this.getClass ());
}
} /// class HeatbugModelSwarm
HeatbugBatchSwarm.java
// jheatbugs-3.0
// Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import swarm.Globals;
import swarm.Selector;
import swarm.defobj.Zone;
import swarm.activity.Activity;
import swarm.activity.ActionGroup;
import swarm.activity.ActionGroupImpl;
import swarm.activity.Schedule;
import swarm.activity.ScheduleImpl;
import swarm.objectbase.Swarm;
import swarm.objectbase.SwarmImpl;
import swarm.analysis.EZGraph;
import swarm.analysis.EZGraphImpl;
/**
This class implements the batch-mode observer of the Heatbug model defined in
HeatbugModelSwarm.java.
<p>
See HeatbugModelSwarm for an overview of the heatbugs application.
*/
public class HeatbugBatchSwarm extends SwarmImpl
{
// Variables referenced by an SCM file or (after Swarm 2.1.1) by a ProbeMap
// must be public:
// This is the number of steps to run the simulation:
public int experimentDuration;
// This is the number of steps to run before writing to the log:
public int loggingFrequency;
protected String _outputFilename = "unhappiness.output";
protected Schedule _displaySchedule;
protected Schedule _stopSchedule;
// The Swarm we're observing:
protected HeatbugModelSwarm _heatbugModelSwarm;
public HeatbugModelSwarm getHeatbugModelSwarm ()
{ return _heatbugModelSwarm; }
// The graph of Heatbug unhappiness, in file mode rather than the usual
// graphics mode:
protected EZGraph _unhappyGraph;
public HeatbugBatchSwarm (Zone aZone) {
super (aZone);
_heatbugModelSwarm = (HeatbugModelSwarm)
Globals.env.lispAppArchiver.getWithZone$key (getZone (), "modelSwarm");
// ... The lispAppArchiver is created automatically from the SCM file.
} /// constructor
public Activity activateIn (Swarm swarmContext)
{
super.activateIn (swarmContext);
_heatbugModelSwarm.activateIn (this);
_stopSchedule.activateIn (this);
if (loggingFrequency > 0)
_displaySchedule.activateIn (this);
return getActivity ();
}
/**
This method schedules the actions of this batch Swarm.
<p>
This Swarm contains the Schedules, ActionGroups, and Actions depicted in the
following diagram.
<xmp>
Swarm
this
|
+-----------------------------------+
| |
Schedule Schedule
_displaySchedule _stopSchedule
| |
| |
| |
ActionGroup implied
displayActions ActionGroup
| |
| |
| |
Action Action
_unhappygraph this
.step() .stopRunning()
</xmp>
<p>
See the documentation in HeatbugModelSwarm.buildActions() for an explanation
of Schedules, ActionGroups, and Actions.
*/
public Object buildActions ()
{
super.buildActions();
// Let the model Swarm build its own schedule:
_heatbugModelSwarm.buildActions();
if (loggingFrequency > 0)
{
// Define the Schedule to run once every loggingFrequency steps:
_displaySchedule = new ScheduleImpl (getZone (), loggingFrequency);
// Define the explicit ActionGroup:
ActionGroup displayActions = new ActionGroupImpl (getZone ());
// Add to the ActionGroup an Action to write the output of the
// graph to _outputFilename:
try
{
displayActions.createActionTo$message
(_unhappyGraph,
new Selector (_unhappyGraph.getClass (), "step", true)
);
} catch (Exception e)
{
System.err.println
("Exception batch _unhappyGraph step: " + e.getMessage ());
}
// Insert the ActionGroup displayActions into the Schedule:
_displaySchedule.at$createAction
(0,
// ... The Schedule will execute ActionGroup displayActions at
// time-step 0 relative to the beginning of the Schedule.
displayActions
);
}
_stopSchedule = new ScheduleImpl (getZone (), true);
// ... The "true" indicates autoDrop: unlike _displaySchedule,
// _stopSchedule does not have a repeatInterval, because we will
// run it just once (at time-step experimentDuration) and then
// drop it from the Schedule.
// Define the sole Action of the implied ActionGroup:
try
{
_stopSchedule.at$createActionTo$message
(experimentDuration,
this,
new Selector (getClass (), "stopRunning", false)
);
} catch (Exception e)
{
System.err.println ("Exception stopRunning: " + e.getMessage ());
}
return this;
} /// buildActions()
public Object buildObjects ()
{
super.buildObjects();
// Let the model Swarm build its objects:
_heatbugModelSwarm.buildObjects ();
// A user who sets loggingFrequency to 0 is requesting no logging at all:
if (loggingFrequency > 0)
{
_unhappyGraph = new EZGraphImpl (getZone (), true);
try
{
// Create a time-series graph of average values:
_unhappyGraph.createAverageSequence$withFeedFrom$andSelector
// ... writing the output to a file:
(_outputFilename,
// ... averaging over all the Heatbugs:
_heatbugModelSwarm.getHeatbugList (),
// ... using values obtained from Heatbug.getUnhappiness ():
new Selector (Class.forName("Heatbug"), "getUnhappiness", false)
);
} catch (Exception e)
{
System.err.println
("Exception batch getUnhappiness: " + e.getMessage ());
}
}
return this;
} /// buildObjects()
public Object go ()
{
System.out.println
("You specified the -b or --batch option,"
+ " so we're running without graphics."
);
System.out.println
("The Heatbugs are running for " + experimentDuration + " timesteps.");
if (loggingFrequency > 0)
System.out.println
("I am logging data every " + loggingFrequency
+ " timesteps to: " + _outputFilename + "."
);
getActivity ().getSwarmActivity ().run ();
if (loggingFrequency > 0)
// todo: explain this:
// Close the output file:
_unhappyGraph.drop ();
return getActivity ().getStatus ();
}
public Object stopRunning () {
// Terminate the simulation.
System.out.println
("Quitting after step " + Globals.env.getCurrentTime () + ".");
Globals.env.getCurrentSwarmActivity ().terminate ();
return this;
}
}
HeatbugObserverSwarm.java
// jheatbugs-3.0
// Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import java.util.ArrayList;
import swarm.Globals;
import swarm.Selector;
import swarm.defobj.Zone;
import swarm.activity.Activity;
import swarm.activity.ActionGroup;
import swarm.activity.ActionGroupImpl;
import swarm.activity.Schedule;
import swarm.activity.ScheduleImpl;
import swarm.objectbase.Swarm;
import swarm.objectbase.VarProbe;
import swarm.objectbase.MessageProbe;
import swarm.objectbase.EmptyProbeMapImpl;
import swarm.gui.Colormap;
import swarm.gui.ColormapImpl;
import swarm.gui.ZoomRaster;
import swarm.gui.ZoomRasterImpl;
import swarm.analysis.EZGraph;
import swarm.analysis.EZGraphImpl;
import swarm.simtoolsgui.GUISwarm;
import swarm.simtoolsgui.GUISwarmImpl;
import swarm.space.Value2dDisplay;
import swarm.space.Value2dDisplayImpl;
import swarm.space.Object2dDisplay;
import swarm.space.Object2dDisplayImpl;
/**
This class implements the GUI-mode observer of the Heatbug model
defined in HeatbugModelSwarm.java.
<p>
See HeatbugModelSwarm for an overview of the heatbugs application.
*/
public class HeatbugObserverSwarm extends GUISwarmImpl
{
// This defines the number of steps after which we display a snapshot of the
// simulation; we could speed up the simulation by displaying less frequently:
public int displayFrequency = 1;
// This defines the timing of the Swarm's Actions:
protected Schedule _displaySchedule;
// This is the model we're observing:
protected HeatbugModelSwarm _heatbugModelSwarm;
public HeatbugModelSwarm getHeatbugModelSwarm ()
{ return _heatbugModelSwarm; }
// This is the index to the palette we will use to paint heat and Heatbugs:
protected Colormap _colormap;
// This is the 2-dimensional display we will use to paint both heat
// and Heatbugs:
protected ZoomRaster _worldRaster;
// This is the time-series graph we will use to display average
// Heatbug unhappiness:
protected EZGraph _unhappyGraph;
// This is the 2-dimensional graph of the heat; we will display
// it on the ZoomRaster:
protected Value2dDisplay _heatDisplay;
// This is the 2-dimensional graph of the Heatbugs; we will display
// it on the ZoomRaster, layered over the _heatDisplay:
protected Object2dDisplay _heatbugDisplay;
public HeatbugObserverSwarm (Zone aZone)
{
super(aZone);
// Create the model that this observer will observe:
_heatbugModelSwarm = new HeatbugModelSwarm (getZone ());
// Create a data structure to hold the Probes:
EmptyProbeMapImpl heatbugObserverProbeMap = new EmptyProbeMapImpl
(aZone, getClass ());
// Create Probes for some variables and methods (see HeatbugModelSwarm.java
// for an explanation of Probes, ProbeMaps, and ProbeDisplays):
heatbugObserverProbeMap.addProbe (probeVariable ("displayFrequency"));
heatbugObserverProbeMap.addProbe (probeMessage ("graphBug:"));
Globals.env.probeLibrary.setProbeMap$For
(heatbugObserverProbeMap, getClass ());
System.out.println
(
"\n" +
"In each field you change in the probe display, press Enter.\n" +
"\n" +
"For method invocations, enter an appropriate value in each argument\n" +
"textbox after the method button, then click the button.\n" +
"\n" +
"Method invocations are subject to acceptance by the program; for example,\n" +
"addHeatbugs() has no effect after you click Start or Next.\n"
);
} /// constructor
/**
This method activates the schedules so they're ready to run.
@param swarmContext (in)
the larger context within which this Swarm is activated; an observer swarm
is usually the top-level Swarm, so the context is usually null; for
sub-Swarms such as _heatbugModelSwarm, this HeatbugObserverSwarm will be
the swarmContext
*/
public Activity activateIn (Swarm swarmContext)
{
super.activateIn (swarmContext);
_heatbugModelSwarm.activateIn (this);
_displaySchedule.activateIn (this);
return getActivity();
}
/**
This method schedules the actions of this GUI observer Swarm.
<p>
This Swarm contains the Schedules, ActionGroups, and Actions depicted in the
following diagram.
<xmp>
Swarm
this
|
|
|
Schedule
_displaySchedule
|
+-----------------------------------+
| |
ActionGroup ActionGroup
updateActions tkActions
| |
+-------------+ |
| | |
Action Action Action
this probeDisplayManager getActionCache()
._update_() .update() doTkEvents()
</xmp>
<p>
See the documentation in HeatbugModelSwarm.buildActions() for an explanation
of Schedules, ActionGroups, and Actions.
*/
public Object buildActions ()
{
super.buildActions();
// Let the model Swarm build its own schedule:
_heatbugModelSwarm.buildActions();
ActionGroup updateActions = new ActionGroupImpl (getZone());
ActionGroup tkActions = new ActionGroupImpl (getZone());
// Define the first Action of ActionGroup updateActions:
try
{
updateActions.createActionTo$message
(this, new Selector (getClass (), "_update_", false));
// Define the second Action of ActionGroup updateActions:
updateActions.createActionTo$message
(Globals.env.probeDisplayManager,
new Selector (Globals.env.probeDisplayManager.getClass (), "update", true)
);
// Define the sole Action of ActionGroup tkActions:
tkActions.createActionTo$message
(getActionCache (),
new Selector (getActionCache ().getClass (), "doTkEvents", true)
);
} catch (Exception e)
{
System.err.println ("Exception in setting up tkActions : "
+ e.getMessage ());
}
// Define the Schedule:
_displaySchedule = new ScheduleImpl (getZone (), displayFrequency);
// ... The repeat interval is displayFrequency, so the schedule will
// begin once every displayFrequency steps of the simulation.
// Insert the updateActions ActionGroup into the Schedule:
_displaySchedule.at$createAction
(0,
// ... Execute the ActionGroup at step 0 relative to the beginning of
// the schedule.
updateActions
);
// Insert the tkActions ActionGroup into the Schedule:
_displaySchedule.at$createAction
(0,
// ... Execute the ActionGroup at step 0 relative to the beginning of
// the schedule.
tkActions
);
return this;
} /// buildActions()
/**
This method creates the plots and graphs that present the
results of the simulation. It delegates the building of the Heatbug model
to HeatbugModelSwarm.buildObjects().
*/
public Object buildObjects ()
{
super.buildObjects ();
// Create probe objects on the model and on this observer, to provide
// GUI channels for reading and writing parameters:
Globals.env.createArchivedProbeDisplay
(_heatbugModelSwarm, "_heatbugModelSwarm");
Globals.env.createArchivedProbeDisplay (this, "heatbugObserverSwarm");
// Wait here until the user clicks Start or Next after optionally changing
// parameters:
getControlPanel ().setStateStopped ();
_heatbugModelSwarm.buildObjects ();
// Create a Colormap for displaying Heatbugs and heat:
_colormap = new ColormapImpl (getZone ());
// Assign colors [ 0.. 63] to shades of red, for heat display;
// assign colors [64..127] to shades of yellow-green, for Heatbug display:
for (double i = 0; i < 64; i++)
{
_colormap.setColor$ToRed$Green$Blue ((byte) i, i / 63, 0, 0);
_colormap.setColor$ToRed$Green$Blue ((byte) (64 + i), i / 63, 1, 0);
}
// Set the colors of the heatbugs from yellow through green (the higher
// the ideal temperature, the more the yellow):
double tempRange
= _heatbugModelSwarm.maxIdealTemp - _heatbugModelSwarm.minIdealTemp;
ArrayList heatbugList = _heatbugModelSwarm.getHeatbugList ();
for (int i = 0; i < heatbugList.size (); i++)
{
Heatbug bug = (Heatbug) heatbugList.get (i);
bug.setColorIndex
((byte)
(64 + 63 *
(bug.getIdealTemperature () - _heatbugModelSwarm.minIdealTemp)
/ tempRange
)
);
}
// Create another window for display, and set its attributes:
_worldRaster = new ZoomRasterImpl (getZone (), "_worldRaster");
try
{
_worldRaster.enableDestroyNotification$notificationMethod
(this, new Selector (getClass (), "_worldRasterDeath_", false));
} catch (Exception e)
{
System.err.println ("Exception _worldRasterDeath_: " + e.getMessage ());
}
_worldRaster.setColormap (_colormap);
_worldRaster.setZoomFactor (4);
_worldRaster.setWidth$Height
((_heatbugModelSwarm.getWorld ()).getSizeX (),
(_heatbugModelSwarm.getWorld ()).getSizeY ()
);
_worldRaster.setWindowTitle ("Heat World");
_worldRaster.pack(); // draw the window
// Create a Value2dDisplay, to display the HeatSpace on the ZoomRaster:
_heatDisplay = new Value2dDisplayImpl
(getZone (), _worldRaster, _colormap, _heatbugModelSwarm.getHeatSpace ());
_heatDisplay.setDisplayMappingM$C (512, 0); // map [0..32767] to [0,63]
// The Heatbug positional data is in the Grid2d, which we can obtain from
// getWorld(). The display widget is the ZoomRaster _worldRaster. An
// Object2dDisplay knows how to draw such data on such a raster:
try
{
_heatbugDisplay = new Object2dDisplayImpl
(getZone (),
_worldRaster,
_heatbugModelSwarm.getWorld (),
new Selector (Class.forName ("Heatbug"), "drawSelfOn", false)
);
} catch (Exception e)
{
System.err.println ("Exception drawSelfOn: " + e.getMessage ());
}
// The Grid2d knows what Heatbugs are on it, and _heatbugDisplay has it, so
// _heatbugDisplay could draw it without any more help from us. But it has
// getSizeX () times getSizeY () cells. If we give it the Heatbug list,
// which has only numBugs elements, it can draw the Heatbugs more
// efficiently:
_heatbugDisplay.setObjectCollection
(_heatbugModelSwarm.getHeatbugList ());
// Tell the world raster to send mouse clicks to the
// _heatbugDisplay. This will allow the user to right-click on the
// display to probe the bugs:
try
{
_worldRaster.setButton$Client$Message
(3,
_heatbugDisplay,
new Selector (_heatbugDisplay.getClass (), "makeProbeAtX$Y", true)
);
} catch (Exception e)
{
System.err.println ("Exception makeProbeAtX$Y: " + e.getMessage ());
}
// Create the graph widget to display unhappiness:
_unhappyGraph = new EZGraphImpl
(getZone (),
"Unhappiness of bugs vs. time",
"time",
"unhappiness",
"_unhappyGraph"
);
// Todo: deal with this now-commented-out code:
// Globals.env.setWindowGeometryRecordName (_unhappyGraph, "_unhappyGraph");
// Assign the method to be used for destroying _unhappyGraph:
try
{
_unhappyGraph.enableDestroyNotification$notificationMethod
(this,
new Selector (getClass (), "_unhappyGraphDeath_", false)
);
} catch (Exception e)
{
System.err.println
("Exception _unhappyGraphDeath_: " + e.getMessage ());
}
// Create the mechanism for computing the average heatbug unhappiness:
try
{
_unhappyGraph.createAverageSequence$withFeedFrom$andSelector
("unhappiness",
_heatbugModelSwarm.getHeatbugList (),
new Selector (Class.forName ("Heatbug"), "getUnhappiness", false)
);
} catch (Exception e)
{
System.err.println ("Exception getUnhappiness: " + e.getMessage ());
}
return this;
} /// buildObjects()
public void drop ()
{
if (_unhappyGraph != null)
_unhappyGraph.disableDestroyNotification ();
if (_worldRaster != null)
_worldRaster.disableDestroyNotification ();
super.drop ();
}
public Object graphBug (Heatbug aBug)
{
if (_unhappyGraph != null)
try
{
_unhappyGraph.createSequence$withFeedFrom$andSelector
("Bug",
aBug,
new Selector (aBug.getClass (), "getUnhappiness", false)
);
} catch (Exception e)
{
System.err.println ("Exception graphBug: " + e.getMessage());
}
return this;
}
public Object _unhappyGraphDeath_ (Object caller)
{
_unhappyGraph.drop ();
_unhappyGraph = null;
return this;
}
/**
This callback method defines what the observer does whenever the Schedule
triggers it.
*/
public Object _update_ ()
{
if (_worldRaster != null)
{
_heatDisplay.display ();
_heatbugDisplay.display ();
_worldRaster.drawSelf ();
}
if (_unhappyGraph != null)
_unhappyGraph.step ();
return this;
}
public Object _worldRasterDeath_ (Object caller)
{
_worldRaster.drop ();
_worldRaster = null;
return this;
}
protected VarProbe probeVariable (String name)
{
return Globals.env.probeLibrary.getProbeForVariable$inClass
(name, HeatbugObserverSwarm.this.getClass ());
}
protected MessageProbe probeMessage (String name) {
return Globals.env.probeLibrary.getProbeForMessage$inClass
(name, HeatbugObserverSwarm.this.getClass ());
}
} /// class HeatbugObserverSwarm
Heatbug.java
// jheatbugs-3.0
// Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import java.awt.*;
import swarm.Globals;
import swarm.space.Grid2d;
import swarm.space.Grid2dImpl;
import swarm.gui.Raster;
/**
See HeatbugModelSwarm for an overview of the heatbugs application.
<p>
A Heatbug is an agent in a 2-dimensional world. A Heatbug has the following
behavior:
<dir>
Each Heatbug has its own ideal temperature, and a color that indicates the
Heatbug's ideal temperature (more green for cool-loving Heatbugs, more yellow
for warmth-loving Heatbugs).
</dir>
<dir>
A Heatbug senses the temperature of the cells in its 9-cell neighborhood.
</dir>
<dir>
A Heatbug has an unhappiness, which is the difference between the Heatbug's
ideal temperature and the temperature of the cell where it sits.
We call a Heatbug unhappy if its unhappiness is non-zero.
</dir>
<dir>
With an arbitrary probability (which is a property of the individual
Heatbug), an unhappy Heatbug will try to move to a randomly-chosen cell in
its 8-cell neighborhood.
</dir>
<dir>
If an unhappy Heatbug does not try to move in the arbitrary fashion described
in the previous paragraph, it will try to move to the cell in
its 8-cell neighborhood whose temperature is closest to
its ideal temperature. If there is more than once such cell, it will
choose at random among the cells with that closest-to-ideal temperature.
</dir>
<dir>
If the cell that a Heatbug tries to move to, as described in the previous two
paragraphs, is not empty, the Heatbug will make 10 attempts to move to a
randomly-chosen empty cell in its 8-cell neighborhood.
</dir>
<dir>
Two or more Heatbugs may not occupy a given cell simultaneously (but at
initialization, double occupancy merely generates a warning).
</dir>
<dir>
If there is no evaporation in the HeatSpace, heat will increase continually
until it reaches MAX_HEAT times the number of cells. With evaporation, the
Heatbugs will typically become happier as they heat their neighborhoods up
from the initial zero temperatures; then, after the heat in many cells hits
its MAX_HEAT ceiling, they will become unhappier as the capped heat diffuses
to distant cells; then, after the heat homogenizes, their unhappiness will
stabilize. Check it out: invoke javaswarm -Dr=0 -De=1 [-Di=1] [-Dc=1] [-Dd=0]
StartHeatbugs.
</dir>
<dir>
If there is no evaporation in the HeatSpace, heat will increase continually
until it reaches MAX_HEAT times the number of cells. With evaporation,
the Heatbugs will typically become happier till a certain time (determined by
ideal temperatures, "evaporation" rate, and MAX_HEAT), then become unhappier,
and then stabilize. Check it out: invoke javaswarm -Dr=0 -De=1 [-Di=1] [-Dc=1]
[-Dd=0] StartHeatbugs.
</dir>
<dir>
A Heatbug produces heat (the amount is a property of the individual Heatbug).
It deposits the heat at the cell where it was sitting, not at the cell it is
going to.
</dir>
*/
public class Heatbug
{
// The amount of heat I produce (units are undefined):
public int outputHeat;
public Object setOutputHeat (int outputHeat)
{ this.outputHeat = outputHeat; return this; }
// The temperature I prefer:
public int idealTemperature;
public int getIdealTemperature () { return idealTemperature; }
public Object setIdealTemperature (int idealTemperature)
{ this.idealTemperature = idealTemperature; return this; }
// The difference between my temperature and my ideal temperature:
public double unhappiness;
public double getUnhappiness () { return unhappiness; }
// The chance that I will move arbitrarily:
public double randomMoveProbability;
public void setRandomMoveProbability (double randomMoveProbability)
{ this.randomMoveProbability = randomMoveProbability; }
// My location in _world as well as in _heatSpace:
public int x, y;
// My index into the ColorMap defined in HeatbugModelSwarm:
public byte colorIndex;
public void setColorIndex (byte colorIndex)
{ this.colorIndex = colorIndex; }
// The 2-dimensional world of motion:
protected Grid2d _world;
// The 2-dimensional world of heat:
protected HeatSpace _heatSpace;
// The model I belong to:
protected HeatbugModelSwarm _model;
// My index in the Heatbug list (needed only for diagnostics):
protected int _heatbugIndex;
// The level of diagnostics to write to standard output:
protected int _printDiagnostics = 0;
public void setPrintDiagnostics (int printDiagnostics)
{ _printDiagnostics = printDiagnostics; }
public Heatbug
(Grid2d world,
HeatSpace heatSpace,
HeatbugModelSwarm model,
int heatbugIndex,
int printDiagnostics
)
{
_world = world;
_heatSpace = heatSpace;
_model = model;
_heatbugIndex = heatbugIndex;
_printDiagnostics = printDiagnostics;
if (_world == null)
System.err.println ("Heatbug was created without a world");
if (_heatSpace == null)
System.err.println ("Heatbug was created without a heatSpace");
} /// constructor
public Object drawSelfOn (Raster raster)
{
raster.drawPointX$Y$Color (x, y, colorIndex);
return this;
}
/**
This method defines what the Heatbug does whenever the Schedule triggers it.
<p>
The method is synchronized, which means the compiler will not let it be
multi-threaded, which means it cannot be parallelized. It is synchronized
because to avoid collisions, the Heatbugs must decide one at a time which
cell to move to.
<p>
There may be other methods in this simulation that should be synchronized.
*/
public synchronized void heatbugStep ()
{
int newX, newY;
// Get the heat where I am sitting:
int heatHere = _heatSpace.getValueAtX$Y (x, y);
// Update my current unhappiness:
int step = _model.getActivity ().getScheduleActivity ().getCurrentTime ();
unhappiness = Math.abs (idealTemperature - heatHere);
if (unhappiness != 0 && ! _model.getImmobile ())
{
double uDR = Globals.env.uniformDblRand.getDoubleWithMin$withMax (0.0, 1.0);
if (uDR < randomMoveProbability)
{
if (_printDiagnostics >= 100)
System.out.print ("Trying to move randomly ... ");
// Pick a random cell within the 9-cell neighborhood, applying
// geographic wrap-around:
newX =
(x + Globals.env.uniformIntRand.getIntegerWithMin$withMax (-1, 1)
+ _world.getSizeX ()
) % _world.getSizeX ();
newY =
(y + Globals.env.uniformIntRand.getIntegerWithMin$withMax (-1, 1)
+ _world.getSizeY ()
) % _world.getSizeY ();
} else
{
if (_printDiagnostics >= 100)
System.out.print ("Trying to move rationally ... ");
Point scratchPoint = new Point (x, y);
// Ask the HeatSpace for a cell in the 9-cell neighborhood
// with the closest-to-ideal temperature:
_heatSpace.findExtremeType$X$Y
((heatHere < idealTemperature ? HeatSpace.HOT : HeatSpace.COLD),
scratchPoint, // scratchPoint is an inout parameter
_world
);
newX = scratchPoint.x;
newY = scratchPoint.y;
}
// ... Whether it chose randomly or rationally, a Heatbug may have
// chosen an already-occupied cell -- including the cell it occupies.
// If it chose its own cell, the choice is about
// to be rejected, since the code below checks to see whether the cell
// is already occupied, without asking which Heatbug is occupying it:
if (_world.getObjectAtX$Y (newX, newY) != null)
{
int tries = 0;
int location, xm1, xp1, ym1, yp1;
// 10 is an arbitrary choice for the number of tries; it is
// *not* implied by the number of cells in the neighborhood:
while ((_world.getObjectAtX$Y (newX, newY) != null) &&
(tries < 10)
)
{
// Choose randomly among the 8 cells in the neighborhood
location = Globals.env.uniformIntRand.getIntegerWithMin$withMax (1,8);
xm1 = (x + _world.getSizeX () - 1) % _world.getSizeX ();
xp1 = (x + 1) % _world.getSizeX ();
ym1 = (y + _world.getSizeY () - 1) % _world.getSizeY ();
yp1 = (y + 1) % _world.getSizeY ();
switch (location)
{
case 1:
newX = xm1; newY = ym1; // NW
break;
case 2:
newX = x ; newY = ym1; // N
break;
case 3:
newX = xp1 ; newY = ym1; // NE
break;
case 4:
newX = xm1 ; newY = y; // W
break;
case 5:
newX = xp1 ; newY = y; // E
break;
case 6:
newX = xm1 ; newY = yp1; // SW
break;
case 7:
newX = x ; newY = yp1; // S
break;
case 8:
newX = xp1 ; newY = yp1; // SE
break;
}
tries++;
}
if (tries == 10)
{
if (_printDiagnostics >= 100)
System.out.println ("no, staying put ... ");
newX = x;
newY = y;
}
else
{
if (_printDiagnostics >= 100)
System.out.println ("no, desperately ... ");
}
}
_heatSpace.addHeat (outputHeat, x, y);
_world.putObject$atX$Y (null, x, y);
x = newX;
y = newY;
_world.putObject$atX$Y (this, x, y);
} /// if unhappiness != 0
else
{
if (_printDiagnostics >= 100)
{
System.out.println ("Too happy to move ... ");
}
_heatSpace.addHeat (outputHeat, x, y);
}
if (_printDiagnostics >= 100)
System.out.println ("Heatbug " + this);
} /// heatbugStep()
/**
This method does not check to see whether the target cell is already occupied.
*/
public Object putAtX$Y (int inX, int inY)
{
x = inX;
y = inY;
_world.putObject$atX$Y (this, x, y);
return this;
}
/**
The Java compiler will invoke this method whenever we use a Heatbug where the
compiler is expecting a String. That gives us an easy way to print diagnostics;
for example, Heatbug heatbug = new Heatbug (...); System.out.println
("I initialized Heatbug " + heatbug + ".");.
*/
public String toString ()
{
return _heatbugIndex + " at (" + x + "," + y + "), heat " + _heatSpace.getValueAtX$Y (x, y);
}
} /// class Heatbug
HeatSpace.java
// jheatbugs-3.0
// Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
// This library is distributed without any warranty; without even the
// implied warranty of merchantability or fitness for a particular
// purpose. See file COPYING for details and terms of copying.
// Changes (from jheatbugs-2001-03-28) by Timothy Howe.
import swarm.Globals;
import swarm.defobj.Zone;
import swarm.space.Diffuse2dImpl;
import swarm.space.Grid2d;
import java.awt.*; // We use this just for class Point.
import java.util.ArrayList;
/**
See HeatbugModelSwarm for an overview of the heatbugs application.
<p>
Swarm's class Diffuse2dImpl provides a 2-dimensional array of integer values.
Behavior of Diffuse2dImpl includes both diffusion and "evaporation" of whatever
the values represent, at rates we can specify.
<p>
The class HeatSpace specializes Diffuse2dImpl; we use the integer values
to represent heat (which Heatbugs produce as well as seek).
<p>
We use <tt>Diffuse2dImpl.getValueAtX$Y()</tt>
and <tt>Diffuse2dImpl.putValue$atX$Y()</tt> to
access the integer values.
<p>
You may wonder why the name of the method is <i>putValue$atX$Y()</i> rather
than <i>setValue$atX$Y()</i>. The name reflects the fact that the change is
buffered and therefore does not have the instant effect that you would expect
from a method named <i>setValue$atX$Y()</i>. In particular, if you change a
value by invoking <tt>putValue$atX$Y()</tt> and then immediately invoke
<tt>getValue$atX$Y()</tt>, you will still see the old heat value.
*/
public class HeatSpace extends Diffuse2dImpl
{
public static final int COLD = 0, HOT = 1;
public static final int MAX_HEAT = 0x7fff;
protected double _discardedHeat = 0.0;
public double getDiscardedHeat () { return _discardedHeat; }
protected int _printDiagnostics = 0;
public void setPrintDiagnostics (int printDiagnostics)
{ _printDiagnostics = printDiagnostics; }
public HeatSpace
(Zone aZone,
int worldXSize,
int worldYSize,
double diffusionConstant,
double evaporationRate,
int printDiagnostics
)
{
super (aZone, worldXSize, worldYSize, diffusionConstant, evaporationRate);
_printDiagnostics = printDiagnostics;
} /// constructor
/**
This method adds heat to the current cell, never exceeding MAX_HEAT.
*/
public Object addHeat (int moreHeat, int x, int y)
{
int heatHere;
heatHere = getValueAtX$Y (x, y); // read the heat
int oldHeatHere = heatHere;
if (heatHere + moreHeat <= MAX_HEAT) // would add be too big?
heatHere = heatHere + moreHeat; // no, just add
else
{
heatHere = MAX_HEAT; // yes, use max
double discardedHeat = heatHere + moreHeat - MAX_HEAT;
_discardedHeat += discardedHeat;
if (_printDiagnostics >= 21)
System.out.println
("In HeatSpace.addHeat() at (" + x + "," + y + "), I discarded heat " + heatHere + " + " + moreHeat + " - " + MAX_HEAT + " = " + discardedHeat + ".");
}
putValue$atX$Y (heatHere, x, y); // set the heat
// Monitor the heat at an arbitrary cell (2, 2) (HeatbugModelSwarm monitors
// the same cell):
if (_printDiagnostics >= 10 && x == 2 && y == 2)
{
System.out.println
("In HeatSpace.addHeat(), heat " + moreHeat + " was added at (" + x + ", " + y + ") to change from " + oldHeatHere + " to " + heatHere + ".");
}
return this;
} /// addHeat()
/**
This method searches targetCell's 9-cell neighborhood for the requested
extreme (COLD or HOT).
<p>Note that a HeatSpace has a wrap-around geography:
the south-eastern neighbor of the cell at (x, y) is the cell at ((x + 1) %
getSizeX (), (y - 1) % getSizeY ()). Of course, <tt>getSizeX()</tt>
and <tt>getSizeY()</tt> are inherited from Diffuse2dImpl.
@param type (in)
HeatSpace.COLD or HeatSpace.HOT
@param targetCell (inout)
the Point at the center of the 9-cell neighborhood; we change it to a Point
randomly selected from among those with the most-ideal temperature
*/
public int findExtremeType$X$Y (int type, Point targetCell, Grid2d world)
{
int x, y;
Point candidate;
// Prime the loop by assuming that the extreme heat is right at targetCell:
int bestHeat = getValueAtX$Y (targetCell.x, targetCell.y);
// Scan through the 9-cell neighborhood, keeping a list of all cells
// that tie for most extreme:
ArrayList<Point> heatList = new ArrayList<Point> ();
for (y = targetCell.y - 1; y <= targetCell.y + 1; y++)
{
for (x = targetCell.x - 1; x <= targetCell.x + 1; x++)
{
candidate = new Point (x % getSizeX (), y % getSizeY ());
int candidateHeat = getValueAtX$Y (candidate.x, candidate.y);
boolean candidateIsBetter
= (type == COLD)
? (candidateHeat < bestHeat)
: (candidateHeat > bestHeat);
boolean candidateIsEqual = (candidateHeat == bestHeat);
if (candidateIsBetter)
{
// ... This cell is more extreme than any so far.
// Delete all the other cells we have accumulated:
heatList.clear ();
heatList.add (new Point (x, y));
bestHeat = candidateHeat;
}
else if (candidateIsEqual)
{
heatList.add (new Point (x, y));
}
}
}
// Choose a point at random from the list of Points tied for most extreme:
int offset = Globals.env.uniformIntRand.getIntegerWithMin$withMax
(0, (heatList.size () - 1));
Point bestCell = (Point) heatList.get (offset);
// We've found the requested extreme. Set the (inout) variable targetCell,
// applying geographic wrap-around, and return the heat we found:
targetCell.x = ((bestCell.x + getSizeX ()) % getSizeX ());
targetCell.y = ((bestCell.y + getSizeY ()) % getSizeY ());
return bestHeat;
} /// findExtremeType$X$Y()
/**
Did you think you could override Diffuse2d.stepRule()? You can -- as we have
done here -- but the method will never be invoked.
<p>
Diffuse2d inherits from Ca2d, which inherits from DblBuffer2d, which maintains
two lattices (old and new). As the Swarm Reference Guide states for
DblBuffer2d, <tt>putValue()</tt>, which <tt>stepRule()</tt> presumably
invokes, is "overridden so writes happen to newLattice".
*/
public Object stepRule ()
{
// super.stepRule ();
System.out.println ("This method never gets invoked.");
return this;
}
/**
This method returns the sum of the heat in each cell of the HeatSpace.
*/
public double totalHeat ()
{
double totalHeat = 0.0;
for (int x = 0; x < getSizeX (); x++)
for (int y = 0; y < getSizeY (); y++)
totalHeat += getValueAtX$Y (x, y);
return totalHeat;
}
} /// class HeatSpace
Ancillary programs
javarep.pl
#!/usr/bin/perl -w
# Notwithstanding any documentation below this comment block,
# this file, javarep.pl. is a program created by Tim Howe
# by making as few modifications to Paul Johnson's replicator.pl,
# to get it to work smoothly with Java programs in a Command Prompt;
# in particular, with Java programs that accept Java System Properties
# defined on the command line.
#
# For example, if you invoke
#
# perl javarep.pl --sweep p=10 --sweep n=20..25 StartHeatbugs
#
# this program will invoke repswarm.pl six times:
#
# repswarm.pl -b -Dp=10 -Dn=20
# repswarm.pl -b -Dp=10 -Dn=21
# repswarm.pl -b -Dp=10 -Dn=22
# repswarm.pl -b -Dp=10 -Dn=23
# repswarm.pl -b -Dp=10 -Dn=24
# repswarm.pl -b -Dp=10 -Dn=25
#
# storing the output of each run in a separate subdirectory;
# for example, ./exp-007/StartHeatbugs-p10-n20.
#
# repswarm.pl will, at each invocation, invoke
#
# java "-Dp=10" "-Dn=20" StartHeatbugs -b
#
# If you invoke
#
# perl javarep.pl --sweep p=10 --sweep n=20..25 -n10
#
# this program will invoke repswarm.pl sixty times -- for every combination of
# sweep values, "-n10" (alternatively, "--NRUNS=10") requests ten runs with
# different random seeds.
#
# This program uses the program repswarm.pl
# (which must be in the working directory)
# as an additional layer wrapping your Java program.
#
# In effect, javarep.pl and repswarm.pl take care of the work discussed
# below in "HOW TO PREPARE YOUR SWARM PROGRAM", as long as your Swarm program
# recognizes Java System Properties defined on the command line.
use strict;
use Getopt::Long; # command line processing
# For File::Copy and IO::Dir, I needed to
# export PERLLIB="$PERLLIB://c/Swarm-2.1.1/lib/perl5/5.00563":
use File::Copy;
use IO::Dir;
#Paul Johnson
# June 16, 2001
# WHAT IS THIS FOR?
# I just learned Perl, and because people having no Perl knowledge
# have difficulty writing their own scripts to manage parameter sweeps
# and repetition of simulations, I offer this! It
# runs a program over and over and passes various command line
# options to it. You can sweep through AS MANY PARAMETERS AS YOU WANT
# with AS MANY RUNS AS YOU WANT per setting.
# HOW DO I USE IT?
# You can give command line options or edit the CONFIGURATION
# section below. Either way should end up the same. Either way, you
# give the name of your program, the number of runs you want for each
# setting, and the parameters you want to sweep. If you enter the
# values in the CONFIGURATION section below, you just get into a
# terminal and type
# perl javarep.pl
# Otherwise, you pass command line options, as in (type all this on
# one line!)
# perl javarep.pl --program=rb --directory=/home/pauljohn/swarm/PJProjects
# --NRUNS=3 --sweep numPPL=100 --sweep aRebConstPOM=0.1,0.2 --sweep vision=1,2,3
# And the thing should start working.
# This sets the numPPL variable at 100, and then two possible
# values for our variable aRebConstPOM, and then 3 values for
# vision. That means this script runs a total of 6 experiments,
# corresponding to these values:
# -vision=1 -aRebConstPOM=0.1 -numPPL=100
# -vision=1 -aRebConstPOM=0.2 -numPPL=100
# -vision=2 -aRebConstPOM=0.1 -numPPL=100
# -vision=2 -aRebConstPOM=0.2 -numPPL=100
# -vision=3 -aRebConstPOM=0.1 -numPPL=100
# -vision=3 -aRebConstPOM=0.2 -numPPL=100
# Or if you use the "shortform" options to your program (short means
# one dash and no equal sign, as in -n100), and tell the script so
# with the shortform option:
# perl javarep.pl --program=rb --directory=/home/pauljohn/swarm/PJProjects
# --NRUNS=3 --shortform --sweep n=100 --sweep c=0.1,0.2 --sweep r=1,2,3
# In short or long form, the key is the "sweep" option, which you
# repeat for each parameter you want to examine. You can put one or
# more comma-separated options to sweep various combinations.
# Or you can specify a range; for example, 5..10.
# If you don't specify --directory, it looks in the current working
# directory. If you don't specify NRUNS, it runs the program 1 time,
# or whatever number is set in the CONFIGURATION section below.
# WHY NOT USE DRONE?
# I love Drone for this kind of work, but Windows users around here
# have a hell of a time compiling expect, and we don't need all the drone
# features for networking and such, so we needed an alternative.
# WHAT IS THE MEANING OF "EXPERIMENT" AND "RUN"
# I use the drone terminology here. An "experiment" is a set of runs
# for a given set of parameters. Every time you run this script, it
# runs at least one experiment. A run is an execution of your program
# with a certain set of parameters. You can have several runs of a
# program at a certain set of parameters, the only thing that changes
# is the random number seed that is fed into the program.
# Each time you run this script, by typing "perl javarep.pl", it should
# create a subdirectory exp-001, or if that exists, exp-002, and so forth.
# Inside there, you should see one directory for each parameter
# setting you select (see below). You have the responsibility of
# creating output files with the run number in them if you want
# separate records for each run of the program. It will create new
# experiment directories every time it runs, but if your program
# writes on top of its old files when it repeats itself, it is
# your own fault.
# This script will chdir to an experiment directory before it invokes
# your program, so your program must be invocable from that directory.
# The script takes care of any SCM files by copying them from your invocation
# directory to the experiment directory. If your program is a Java
# program, you will probably need to list your invocation directory in
# $CLASSPATH.
# HOW TO PREPARE YOUR SWARM PROGRAM
# The assumption is that you have a swarm batch program (though you can
# use this script to run non-swarm programs as well) that runs with
# command line options. Suppose you can run a program by typing its
# name and a bunch of command line options, such as
# ./rb -b --run=1 -seed=1234 --numPPL=110 --vision=2 --aRebConstPOM=0.1
# or the short option form
# ./rb -b -R1 -S1234 -n110 -r2 -f0.1
# -b is for batch, Swarm creates that flag. You create the rest within
# your Swarm arguments code.
# Any program you use with this script MUST accept at least 2 command
# line options (though it is free to ignore them):
# 1. Run number. Short form -Rx
# Long form --run=x
# 2. Seed Value. Short form -Sx
# Long form --seed=x
# This script will take care of the seeds, replication, and so forth,
# but your program must accept the options. The script will
# create directories, and any output generated by your runs will be
# sorted into them. You are rendered almost superfluous by its mighty
# power! (not really...)
# You cannot use this script if you want to specify no-argument options.
# That's because GetOptions returns, for example, key z with value 1, whether
# the command line contains "--sweep z" or "--sweep z=1". So there is no way
# for this script to know that your program will accept -z but fail on -z1.
# Now, about the random number seeds. I started the Perl random
# number generator with the number 1234321, you can change the seed
# number there if you want. Then the script chooses random numbers
# from that stream as the seeds for following runs.
# *************CONFIGURATION*****************
# If you don't use command line options, HERE IS THE PART YOU EDIT:
# Give a full path to the directory that holds your program.
# If you don't specify anything, or leave this blank, then the script
# assumes the directory is your current working directory. Even if
# you are in MS-Windows, don't use separators like "\". Figure out
# what your bash environment needs for paths by typing "pwd" in your
# terminal.
# Edit the following settings if you care to:
# Lets assume your program is in the current directory, the one where
# you plan to run this script:
my $swarmStartClass = "unknown";
my $dir_name = "./";
# That's not necessary, you can put your program wherever you want
# and tell the script so:
# my $dir_name = '/home/pauljohn/swarm/PJProjects/valinux/Protest';
# What is the name of your program:
my $perl_name= 'repswarm.pl';
# How many runs do you want for each set of parameters.
my $NRUNS=1;
# Put in your command line parameters of interest here! Here is an
# example, assuming you want to pass through the "long form" command
# line options. Those are the ones like --option=value. Observe the
# structure of a line below. You need a command line parameter in long form
# (the kind that goes with -- in the command), the => symbol, and
# bracketed parameter values to be swept. If you put just one value,
# it just uses that one value.
my %parameters = (
### numPPL => [ 100 ],
### aRebConstPOM => [0.1, 0.2],
### vision => [1, 2, 3]
);
# I personally like longform, but you may like shortform options.
# If you want shortform options, change this variable $shortform to 1:
#my $shortform = 0;
my $shortform = 1;
# and then give a %parameters statement like:
#my %parameters = (
# n => [ 100 ],
# f => [0.1, 0.2],
# r => [1, 2, 3]
# );
# You don't really need to mess around below here. I think
# you should leave it alone, as a matter of fact.
###################################################
my @command_strings = "";
main ($dir_name, $perl_name);
sub main
{
my ($dir_name, $perl_name) = @_;
processCLI();
foreach my $parameter ( keys %parameters ) {
@command_strings = &updateCommandStrings( $parameters{$parameter}, $parameter );
}
my $expSuperDir = "";
$expSuperDir = &createSuperDirectoryName();
foreach my $string (@command_strings)
{
#beautify the string for directory creation purposes
my $stringNoSpaces = $string;
$stringNoSpaces =~ s/\ //g;
$stringNoSpaces =~ s/--/-/g;
my $fullPath = "$expSuperDir/$perl_name$stringNoSpaces";
mkdir ($fullPath,0777);
my $d = new IO::Dir "$dir_name";
if (defined $d)
{
while (defined (my $f = $d->read)) {
if ($f =~ m/scm$/i) {
File::Copy::copy ($f, $fullPath);
}
}
}
chdir ($fullPath);
if ($dir_name eq ".") { $dir_name = $ENV{PWD};}
my $program = "perl ../../$perl_name";
open(COMMANDS, ">>experiment_summary");
srand 1234321;
my $seedValue = 0;
for (my $i=0; $i < $NRUNS; $i++)
{
my $progString;
$seedValue += int (rand 100000) + 1;
if ($shortform ==1) {
$progString = join("",$program," -b"," -R",$i," -S",$seedValue," ", $string);
}
else {
$progString = join("",$program," -b"," --run=",$i," --seed=",$seedValue," ", $string);
}
print COMMANDS "$progString \n";
print STDERR "\nProgram $0, in folder " . qx { pwd } . " invoking: $progString ...\n";
my $outputFile = "stdout$i";
#diverts stderr to a file
my $output = `SWARMSTARTCLASS=$swarmStartClass $progString 2>&1 >> $outputFile`;
print $output;
}
close(COMMANDS);
chdir ("../..");
}
}
sub processCLI {
my %clinput = ();
GetOptions ('NRUNS:i' => \$NRUNS, 'directory:s' => \$dir_name, 'program:s' => \$perl_name , 'shortform' => \$shortform, "sweep=s" => \%clinput) || die "Invalid options \n";
print "Your Perl program is in directory: $dir_name \n";
print "Your Perl program name is: $perl_name \n";
print "Your desired number of runs is: $NRUNS \n";
print "Your command line input parameters: \n";
if ($shortform == 1){print "You said you were using short form parameters "};
foreach (keys %clinput) { print "$_ $clinput{$_} \n";}
if (%clinput)
{
print "We are using your command line input for parameters \n";
%parameters = ();
foreach (keys %clinput)
{
## print "key is $_; value is $clinput{$_}\n";
my @range_expansion;
eval "\@range_expansion = ($clinput{$_})";
$parameters{$_}=\@range_expansion;
}
}
$swarmStartClass = $ARGV[$#ARGV];
}
sub updateCommandStrings {
my @valArray = @{$_[0]};
my $key = $_[1];
# defined or $_ = " " foreach @{$local_strings};
# @local_strings = @{$local_strings};
my @oldStringArray = @command_strings;
@command_strings=();
foreach my $st (@oldStringArray){
foreach my $val (@valArray){
my $newString;
if ($shortform == 0) {
$newString = join("",$st," --",$key,"=",$val);
}
else {
$newString = join("",$st," -",$key,$val);
}
push (@command_strings, $newString);
}
}
return @command_strings;
}
sub createSuperDirectoryName {
my $dirName = "exp-001";
my $i=1;
while (-e $dirName ){
$i++;
$dirName = join ("-","exp",sprintf("%03d",$i));
}
mkdir ($dirName,0777);
return $dirName
}
exit;
repswarm.pl
#!/bin/perl
##
## To invoke directly:
##
## set SWARMSTARTCLASS=StartHeatbugs
## perl repswarm.pl -n3
##
## To invoke through javarep.pl:
##
## perl javarep.pl --sweep n=3..5 StartHeatbugs
##
# jheatbugs-3.0
# Java Heatbugs application. Copyright © 1999-2000 Swarm Development Group.
# This library is distributed without any warranty; without even the
# implied warranty of merchantability or fitness for a particular
# purpose. See file COPYING for details and terms of copying.
# By Timothy Howe.
# This program invokes a Swarm application in Swarm batch (non-interactive)
# mode. The program is written to be invoked by javarep.pl; always
# specify --program=repswarm.pl, though you may use a different path.
#
# The program supports javarep.pl's shortform and longform options. The
# program does not support bundled options nor spaces between an option and
# its option argument.
#
# If, for example, you specify the parameter
#
# --sweep q=3..7
#
# to javarep.pl, that program will invoke this program (for the first run)
# with the parameter
#
# -q3
#
# and this program will invoke your Java Swarm program with the parameter
#
# -Dq=3
#
# and your Java Swarm program can access the value in the standard fashion
# through the Java System Property named "q". Java System Properties are
# implemented by the class java.util.Properties.
#
# For boolean values, since this program does not support options without
# arguments, use the values 0 for false and 1 for true; for example,
#
# --sweep c=0,1
#
# The program requires that the environment variable SWARMSTARTCLASS be defined
# to indicate the initial Java class for javaswarm to invoke.
#
# Note that javarep.pl handles numeric values only; for example, it can
# handle "--sweep x=173" or "--sweep scale=-0.5,-5,-50", but not "--sweep
# color=r,g,b". That explains why javarep.pl can't pass the starting class
# name to this program -- hence this program gets the starting class name from
# the environment variable $SWARMSTARTCLASS.
$swarmhome = $ENV{"SWARMHOME"};
if (! $ENV{SWARMSTARTCLASS})
{
print STDOUT "(stdout) Fatal error: SWARMSTARTCLASS is undefined; exiting.\n";
print STDERR "(stderr) Fatal error: SWARMSTARTCLASS is undefined; exiting.\n";
exit 1;
}
$argstring .= "-b "; # run Swarm in batch
for ($paramI = 0; $paramI <= $#ARGV; $paramI++)
{
$param = $ARGV[$paramI];
if ($param !~ m/^-./ || $param eq "--")
# ... "-" by itself is not an option; "--" is the option-ending option.
{ last; }
if ($param =~ m/^--(.*?)=(.*)/)
{
# Handle long-form option with option argument:
$propstring .= "\"-D$1=$2\" ";
} elsif ($param =~ m/^--(.*)/)
{
# Handle long-form boolean option:
$propstring .= "\"-D$1=1\" ";
} elsif ($param =~ m/^-(.)(.+)/)
{
# Handle short-form option with option argument:
$propstring .= "\"-D$1=$2\" ";
} else
{
# Handle short-form boolean option:
$param =~ s/^-+//;
$propstring .= "\"-D$param=1\" ";
}
}
print "propstring is $propstring\n";
for (; $paramI <= $#ARGV; $paramI++)
{
$param = $ARGV[$paramI];
$argstring .= " $param";
}
print "argstring is $argstring\n";
my $cmd = "java -version";
# Because this program is run by javarep.pl, the application's Java files
# are two directory levels up; hence we need to modify CLASSPATH.
$cmd = qq { java -classpath "../..;$ENV{CLASSPATH}" $propstring $ENV{SWARMSTARTCLASS} $argstring };
print STDERR "Program $0, in folder " . qx { pwd } . " invoking: $cmd ...\n";
system $cmd;
makefile
# Jheatbugs-3.0 makefile
# By Timothy Howe.
.PSEUDO : compile run clean checkdependencies
C=javac -Xlint -source 1.5
J=java -ea
compile : \
HeatSpace.class \
Heatbug.class \
HeatbugBatchSwarm.class \
HeatbugModelSwarm.class \
HeatbugObserverSwarm.class \
StartHeatbugs.class
run : compile
$J StartHeatbugs
clean :
rm -f *.class
# Begin co-dependencies.
HeatbugModelSwarm.class : HeatbugModelSwarm.java
$C HeatbugModelSwarm.java Heatbug.java
Heatbug.class : Heatbug.java
$C HeatbugModelSwarm.java Heatbug.java
# End co-dependencies.
# Begin simple dependencies.
HeatbugModelSwarm.class : Heatbug.class
HeatbugModelSwarm.class : HeatSpace.class
Heatbug.class : HeatSpace.class
Heatbug.class : HeatbugModelSwarm.class
StartHeatbugs.class : HeatbugObserverSwarm.class
StartHeatbugs.class : HeatbugBatchSwarm.class
StartHeatbugs.class : HeatbugModelSwarm.class
HeatbugObserverSwarm.class : HeatbugModelSwarm.class
HeatbugObserverSwarm.class : Heatbug.class
HeatbugObserverSwarm.class : HeatSpace.class
HeatbugBatchSwarm.class : HeatbugModelSwarm.class
# End simple dependencies.
.SUFFIXES: .java .class .applet .applic
# Setup-free application from .java, for versions of make that support
# empty target suffixes:
.java :
make $*.class
$J $*
# Setup-free application from .class, for versions of make that support
# empty target suffixes:
.class :
$J $*
# Setup-free applet from .java, assuming that the Java file carries its own
# HTML (see DrawImage.java):
.java.applet :
make $*.class
appletviewer $*.java
# Setup-free .applic from .java:
.java.applic :
make $*.class
$J $*
# Setup-free .class from .java:
.java.class :
$C $<
![[Main Page]](/stylesheets/images/wiki.png)