The Eclpss framework and the generated Eclpss executable Models is programmed with judicious use of the Java programming language, is truly platform independent. Since the framework is implemented in Java, it is possible to use the abundant graphical Java Application Programming Interfaces (API)s for both the modeling environment and the Eclpss models. Furthermore, Java provides many web-based features, such as Java Web Start, which provides a fast and efficient web-based "launching" of the framework.
An Eclpss Model consists of three entities and the structural relationships between these entities. The three entities are:
The Eclpss modeling environment consists of a suite of Graphical User Interface (GUI)-based tools. These tools are used to provide a (mostly) declarative description of the model. This declarative paradigm drastically minimizes the amount of programming required of the user. Components are typically small transformation functions and consist of ßnippets" of Java code.
A unique feature is the ability of the framework to automatically target the model to shard-memory parallel architectures. html: The execution model for simulation is fully parallel, regardless of the number of processors on the host machine! This guarantees that the model will produce the same results, regardless of the number of processors.
To use the framework, and also execute framework-generated models, you must have Java JDK and JRE version 1.4 or later installed on your system. You also need javadoc so that the framework can automatically generate documentation of your models.
A computational grid is either a 2- or 3-dimensional spatial data structure. State Variables populate the grid, and accessor/mutator methods are use to change their states (values).
The framework is designed so that each SV may be stored its own data grid. The data grid for an SV is of arbitrary spatial resolution, ranging from a scalar to one-to-one with the CG. Access to State Variable values is accomplished by accessing the Cells of the ComputationalGrid. Each State Variable has its own accessor and mutator methods, which are performed on the Cell.
As an example of different spatial resolutions, consider an an Eclpss component that references a SunLight SV. Different models may represent SunLight with varying degrees of granularity: from a scalar (the same value for SunLight in each cell of the grid, to a different value in each cell).
Regardless of the spatial granularity of the SVs, both the component code the specification of the SV remain unchanged.
A set of GUI editors (Grid, Component, State Variable, and Model) gives the user an almost declarative way to define (and change) models.
An Eclpss simulation executes over a spatial (2- or 3-dimensional) ComputationalGrid in time. Each cell of the computational grid contains a reference to the State Variables associated with the cell. Each state variable is stored in its own data grid, and the user references the State Variable through Computational Grid. So, for example, if a model has SunLight State Variable, and the user wishes to model SunLight at the "coarsest" granularity, i.e., SunLight is a scalar, then the data grid for SunLight is a scalar also. The framework, then, maps all cells in the computational grid to one the SunLight scalar.
If SunLight is, instead, represented with the finest granularity (that of the computational grid), then the framework performs a one-to-one mapping of each grid cell to its corresponding data cell.
Grid access is either "point"-based or "location"-based. Point-based access references grid cells by ärray indexing"; Location-based access references grid cells by distance.
Reads and writes are relative or absolute.
Reads and writes are restricted, and enforced with exceptions IllegalReadException and IllegalWriteException.
Each dimension of a computational grid is defined to have one of three boundary types: torus, reflected, or border (with a border depth ³ 0).
Appropriate read and write exceptions are thrown for illegal border boundary accesses within the "during" phase of the simulation.
The simplest case is defining one single state variable to reside in each cell. This is reasonable when modeling an ecological entity that is unique, like Sunlight. However, it might be the case that, as a model evolves, it is desirable to model a grid cell as containing not just one, but many of the same state varibles.
Slightly more complicated are Composite state variables: several of the same state variable occupies each cell.
Consider a Tree state variable. The initial model may represent one Tree per grid cell. As the model evolves, a more accurate representation of the trees in the cell is desired: to model the individual trees in the cell. In other words, there may be a "collection" (or "composite") of the same state variable in each cell.
It is also mandatory desirable that whatever code is written to compute on one tree in a cell, that it not need be rewritten to change from to compute over many trees in a cell.
Framework methods make ßingle" and "composite" state variables invisible to the user. The most general way for the user to write a component is to assume that all state variables are composite, and use methods to reference composite state variables. The results are the same whether or not there is only one, or collection of, state variable(s) in a cell.
This leads to the use of an Iterator class to access state variables. An iterator is a class which simply provides a way to access a collection of data.
Whenever the user defines a state variable (using the State Variable Editor), the framework will create a minimum of two classes: one for the state variable, and another which is an Iterator for the state variable.
You can basically use any of the Iterator methods (i.e., hasNext() and next()), or most of the java.util.Vector methods for accessing SVs.
This section gives examples how to use grid methods to update state variables. The user may state variables as simple (only one state variable per grid cell) or composite (more than one of the same state variable per grid cell). Recall that framework regards every state variable as composite. The safe way to access state variables is to always assume they are composite.
Should the user write code to access state variables as though they are simple does so at his/her peril, since the code will not work if the data change to composite.
This section gives example code that updates a simple state variable in the CURRENT (here and now) cell. Not recommended!
This code assumes:
double slv = grid.readSunlight(current,PREVIOUS).getValue();
grid.writeSunlight(current,CURRENT),setValue(slv+1.0).
In the following examples, we treat the Tree state variable as a composite. Add one to Tree height in the CURRENT cell, regardless of whether or not there is one, or more than one, tree in the cell.
For this, we use the iterator associated with the state variable.
Steps:
// get the Tree iterator in the current cell
TreeSVIterator treeItr = grid.writeTree(current,CURRENT);
// loop through the iterator, adding 1 to each tree Height
// while the iterator has a SV
while(treeItr.hasNext()) {
// add 1 to this Tree's Height
treeItr.setHeight(treeItr.getHeight()+1);
// advance to the next. if this is the last SV, hasNext() fails
treeItr.advance();
}
// get the Tree iterator in the current cell
TreeSVIterator treeItr = grid.writeTree(current,CURRENT);
// loop through the iterator, adding 1 to each tree Height
// if the iterator has a SV
if (treeItr.hasNext()) {
do {
// add 1 to this Tree's Height
treeItr.setHeight(treeItr.getHeight()+1);
}
// advance to the next. if this is the last SV, hasNext() fails
while ( treeItr.advance() );
}
// get the Tree iterator in the current cell
TreeSVIterator treeItr = grid.writeTree(current,CURRENT);
// get the size of the Iterator (number of SVs in this iterator)
// loop though iterator using an index, adding 1 to each tree Height
int tsize = treeItr.size();
for (int i = 0; i < tsize; i++) {
// index through the iterator
treeItr.setHeight(i,treeItr.getHeight(i)+1);
}
By assuming every SV is a composite, the code does not have to be rewritten.
SunlightSVItr curItr = grid.writeSunlight(current,CURRENT);
SunlightSVItr prevItr = grid.readSunlight(current,PREVIOUS);
while(curItr.hasNext() && prevItr.hasNext()) {
curItr.setValue(prevItr.getValue()+1.0);
curItr.advance();
prevItr.advance();
}
Simply add a new SV to the iterator at current. The add method
adds a (new) object to the end of an Iterator
Adding 1.0 to everySunlight Value from previous, and adding each
to current.
SunlightSVItr curItr = grid.writeSunlight(current,CURRENT);
SunlightSVItr prevItr = grid.readSunlight(current,PREVIOUS);
if (curItr.size() == prevItr.size()) {
for (int i=0; i$<$curItr.size(); i++) {
curItr,setValue(i, prevItr.getValue(i)+1.0 )
}
}
else {
throw(new IllegalWriteException("Sunlights in current cell "+
current.toString() + "don't match the previous\n"+
"Current = "+curItr.size()+"\n"+
"Previous = "+prevItr.size() ));
}
SunlightSVItr curItr = grid.writeSunlight(current,CURRENT);
SunlightSVItr prevItr = grid.readSunlight(current,PREVIOUS);
while(prevItr.hasNext()) {
curItr.add(new Sunlight(setValue(prevItr.getValue()+1.0));
prevItr.advance();
}
Pre-Sim, Sim andPost-Sim Component Methods.
Java naming conventions are used: class names begin with an uppercase letter; method names begin with a lowercase letter.
| SV Name | Attrib Name | Attrib Storage Type | Unit |
| Tree | Height | double | meter |
| "" | Diameter | double | centimeter |
| "" | Type | int | dimensionless |
| SunLight | Value | double | lumen |
| SV Attrib Name | Attrib Const Name | Value |
| Type | OAK | 1 |
| "" | MAPLE | 2 |
| "" | PINE | 3 |
| SV Name | SVItr Name | SV Attrib Name | get<Attrib Name>(); | set<Attrib Name>(value) |
| Tree | TreeSVItr | Height | getHeight() | setHeight(14.5) |
| Tree | TreeSVItr | Height | getHeight(<index>) | setHeight(<index>, 14.5) |
| Tree | TreeSVItr | Type | getType() | setType(Tree.OAK) |
| Tree | TreeSVItr | Diameter | getDiameter() | setDiameter(14.5) |
| SunLight | SunLightSVItr | Value | getValue() | setValue(17.6) |
Simple SV:
...
// get sunlight value to read at absolute point
Point point = new Point(..., ..., ..., ...);
double sun = grid.readAbsSunLight(point).getValue();
...
Safest way: always treat as a Composite SV:
// get the iterator to read at absolute point
Point point = new Point(..., ..., ..., ...);
SunLightSVItr sItr = grid.readAbsSunlightValue(point);
while (sItr.hasNext()) {
...
double sun = sItr.getValue(); // get the value
...
}