An oft-heard request is how to write MASON simualtions which read from parameters specified on-disk. There are lots of ways to do this; depending on one's needs. For the most simple applications, Java's properties class (java.util.Properties) is often enough since it provides the framework for reading/writing/saving/loading String-based parameters and values.
In this tutorial we'll use a subclass of Properties called ParameterDatabase, partly because we know it well (Sean Luke wrote it for the ECJ evolutionary computation toolkit) and partly because it provides a number of advanced features over the simple Properties class. ParameterDatabase can load from a hierarchy of parameter files; load strings, ints, doubles, longs, file pointers, and even create arbitrary instances constructed dynamically by the class defined in the file. This is a lot more than Properties does. But we will only show a very simple situation.
In the simplest situation, a single set of global model parameters will be loaded from a file. This is the scenario we will cover in this tutorial, and the example we will use is loading parameters into HeatBugs. More sophisticated situations may require that each separate agent have its own separate set of parameters, or other conditions where one global set of parameters is not sufficient. For example, you may wish to load unique parameters for every HeatBug. We have a methodology we use for this (it's how ECJ works -- every class in ECJ is defined by the parameter file) but will not cover it in this tutorial. But you should be able to hack something together once you understand the basic approach here.
This tutorial teaches:
Before starting, you need to install the ParameterDatabase facility. Place the following four files in MASON's ec/util/ directory:
You can also download all four of these files as parameterdatabase.zip.
The files you'll create in this tutorial will all be stored in the directory sim/app/webtutorial2 in MASON. If you want to just read the files rather than follow along and build them, they're here:
If you like, you can download all four of these files as source.zip
In sim/app/webtutorial2 create a file called heatbugs.params. This file will be used to store parameter - value pairs in the "java properties" format. Add the following lines to the file:
## ## Random Number Generator Seeed ## seed = time #(alternatively use seed = 12345) ## ## Grid Size ## width = 100 height = 100 ## number of bugs bugs = 100 ## ## Heat model ## min-ideal-heat = 17000 max-ideal-heat = 31000 min-output-heat = 6000 max-output-heat = 10000 evaporation-rate = .993 diffusion-rate = 1 random-move-probability = .1
NOTE:
The equal sign is optional but it is really bad style to not include it. The parameter name (to the left of the equal sign) cannot contain spaces -- whitespace on either end is trimmed. The value (to the right of the equal sign) can contain spaces, but again, the leading and trailing spaces are trimmed out.
Empty lines are ignored. Lines starting with # are considered comments. Note that this is not the same as the Java-style line comment, where everything following // is comment until the end of the line. |
We'll begin by loading the parameters "the hard way", namely, loading each parameter separately and setting values based on them. Later in the tutorial we'll show a nifty approach to loading all properties at once using Java Beans.
In the directory sim/app add a new directory called webtutorial2 and in this directory, create a class called ParameterizedHeatBugs.java Add the following code to the file:
package sim.app.webtutorial2; import ec.util.*; import java.io.*; import sim.app.heatbugs.*; public /*strictfp*/ class ParameterizedHeatBugs extends HeatBugs { public static final String P_SEED = "seed"; public static final String P_WIDTH = "width"; public static final String P_HEIGHT = "height"; public static final String P_BUG_COUNT = "bugs"; public static final String P_MIN_IDEAL_HEAT = "min-ideal-heat"; public static final String P_MAX_IDEAL_HEAT = "max-ideal-heat"; public static final String P_MIN_OUT_HEAT = "min-output-heat"; public static final String P_MAX_OUT_HEAT = "max-output-heat"; public static final String P_EVAPORATION = "evaporation-rate"; public static final String P_DIFFUSION = "diffusion-rate"; public static final String P_RAND_PROB = "random-move-probability";
Notice that these are equivalent to the parameter names we put in the params file.
Next, we'll create a function which loads the parameters from the parameter database. This function is quite long; mostly because we're loading each parameter manually to show how to do it. But as we'll soon see, there's an easier way to do it.
NOTE:
Some explanations are in order here.
First, the property-value retrieval mechanism was designed to consider some default parameter in case
the main parameter was not found in the data base. This explains the null following
param = new Parameter(...) in most of the above calls.
Second, most methods for retrieving numerical values in the parameter data base require lower bounds. Providing upper bounds is also possible. When the value is outside the provided bounds, these mehtods return a value 1 unit smaller than the smallest acceptable value. This is the explanation for the last argument(s) in getInt, getDouble, etc., and also for the tests leading to throwing a RuntimeExeption. |
Create a public contructor and also overwrite the start method to call loadParams:
public ParameterizedHeatBugs(ParameterDatabase paramDB) { super(1);//some bogus seed, we'll use the right one as soon as we load the parmaters from DB this.paramDB = paramDB; loadParams(); createGrids(); } public void start() { loadParams(); super.start(); }Now have the parameter data base loaded from a file in the main method:
public static void main(String[] args) { ParameterizedHeatBugs heatbugs = null; ParameterDatabase parameters = null; for(int x=0;x<args.length-1;x++) if (args[x].equals("-file")) { try { parameters=new ParameterDatabase( new File(args[x+1]).getAbsoluteFile(), args); }catch(IOException ex){ex.printStackTrace();} break; } heatbugs = new ParameterizedHeatBugs(parameters); heatbugs.start(); System.out.println("Starting HeatBugs. Running for 500 steps."); double time; while((time = heatbugs.schedule.time()) < 500) { if (!heatbugs.schedule.step(heatbugs)) break; if (((long)time)%100==0 && time!=0) System.out.println("Time Step " + time); } heatbugs.finish(); }
Save the file and compile the java file. Run java sim.app.webtutorial2.ParameterizedHeatBugs -file heatbugs.params.
Notice that if you omit the -file heatbugs.params, the default model values are used instead.
Add the following line to ParameterizedHeatBugs.java
public static final String V_TIME = "time";Inside the loadParams method change:
FROM... | CHANGE TO |
long seed = paramDB.getLong(param = new Parameter(P_SEED)); random.setSeed(seed); | long seed; String s = paramDB.getString(param = new Parameter(P_SEED), null); if(s.equalsIgnoreCase(V_TIME)) seed = System.currentTimeMillis(); else seed = paramDB.getLong(param); random.setSeed(seed); |
Notice that when the parameter database is constructed, it is done so by passing in the command line arguments (args). The reason for this is because the parameter database can also load parameters from the command line. For example, you can change the width to 9 by adding to the command line: -p width=9
Command-line arguments supercede arguments loaded from files.
Parameter files can have multiple parent files. Parent files are specified with the parameter names parent.0 , parent.1 , ... etc. For example, a file might say parent.0=../parent.params indicating that its parent file is called parent.params and is located in the parent directory relative to the main parameter file. Parent files themselves can have parent files, etc., creating a tree of files. If the same parameter name appears in multiple files, here's how conflicts are resolved. Children take precedence over parents or ancestors. Earlier-numbered ancesters take precedence over later ancesters (for example, files traced through parent.0 take precedence over files traced through, say, parent.4 of a given child file).
For example, consider the parameter files shown at left. Each of them has defined some parent files. Parameters added to the ParameterDatabase programmatically take precedence; then command-line paramters; then parameters in the root parameter file (in this example, "prog.params"), then its left parent ("foo.params"), then "bar.params", then "baz.params", then "quux.params", then "quuux.params", and finally "quuuux.params".
You can simply print out parameters in real time as they are being loaded. This is done by adding a parameter print-params=true and the Parameter database will print out parameters as they are requested. You can also dump parameters in different configurations at the end of the simulation. For example, to dump those parameters which were used at least onece, insert the following piece of code at the end of the main method:
if(parameters != null) { PrintWriter pw = new PrintWriter(System.out, true); parameters.listGotten(pw); }
NOTE:
If you want to print into a file, here's the way
java documentation
says you should make the PrintWriter:PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter("foo.out"))); |
See the ParameterDatabase documentation for other dumping options.
Copy ParameterizedHeatBugs.java into ParameterizedHeatBugs2.java and heatbugs.params into heatbugs2.params.
In the ParameterizedHeatBugs2.java file change all references to ParameterizedHeatBugs to ParameterizedHeatBugs2.
Make the following change to the loadParams method:
Alternative parametrization style
The loadParams can be considerably simplified if we connect the parameters
(java.util.Properties)
with the getters and setters we wrote for an inspectable model (sim.util.Properties).
Instead of defining constants for the parameter names, simply use the names of the properties you have setters and
getters for.
FROM... | CHANGE TO |
random.setSeed(seed); gridWidth = paramDB.getInt(param = new Parameter(P_WIDTH),null,1); ... if(maxOutputHeat<minOutputHeat) throw new RuntimeException("Invalid value for "+param); | random.setSeed(seed); Properties p = Properties.getProperties(this,false,true,false); for(int i=0; i< p.numProperties(); i++) if (p.isReadWrite(i) && !p.isComposite(i) && paramDB.exists(new Parameter(p.getName(i)))) { Parameter pa = new Parameter(p.getName(i)); String value = paramDB.getString(pa,null); p.setValue(i,value); } |
FROM... | CHANGE TO |
## ## Random Number Generator Seeed ## seed = time #(alternatively use seed = 12345) ## ## Grid Size ## width = 100 height = 100 ## number of bugs bugs = 100 ## ## Heat model ## min-ideal-heat = 17000 max-ideal-heat = 31000 min-output-heat = 6000 max-output-heat = 10000 evaporation-rate = .993 diffusion-rate = 1 random-move-probability = .1 | ## ## Random Number Generator Seeed ## seed = time #(alternatively use seed = 12345) ## ## Grid Size ## GridWidth = 100 GridHeight = 100 ## number of bugs BugCount = 100 ## ## Heat model ## MinimumIdealTemperature = 17000 MaximumIdealTemperature = 31000 MinimumOutputHeat = 6000 MaximumOutputHeat = 10000 EvaporationConstant = .993 DiffusionConstant = 1 RandomMovementProbability = .1 |
To run this, use java sim.app.webtutorial2.ParameterizedHeatBugs2 -file heatbugs2.params.
NOTE:
|