There are 10 base types of agents that are named according to how many spatial dimensions they occupy, and how they are allowed to move in space. The SQ and PT suffixes refer to whether the agents are imagined to exist as lattice bound squares/voxels that move discretely, or as as non-volumetric points in space that move continuously.

AgentGrids are used as spatial containers for agents (1, 2, 3 dimensional and 0 dimensional or non-spatial). Internally, AgentGrids are composed of two datastructures: an agent list for agent iteration, and an agent lattice for spatial queries (even off-lattice agents are stored on a lattice for quick access). The agent list can be shuffled at every iteration to randomize iteration order, and the list holds onto removed agents to facilitate object recycling.

Similarly, PDE Grids consist of either a 1D, 2D, or 3D lattice of concentrations. PDE grids contain functions that will solve reaction-advection-diffusion equations. Currently implemented PDE solution methods include:

  • Forward Difference in time and 2nd order central difference in space Diffusion
  • ADI Diffusion
  • 1st order upwind finite difference advection for incompressible flows
  • 1st order finite volume upwind advection for compressible flows
  • Modification of values at single lattice positions to facilitate reaction with agents or other sources/sinks.

Most of these methods are flexible, allowing for variable diffusion rates and advection velocities as well as different boundary conditions such as periodic, Dirichlet, and zero-flux Neumann.

4.1 Types of Grid

Holds nonspatial agents
Holds all 1D agents
Holds all 2D agents
Holds all 3D agents
facilitates modeling a single diffusible field in 1D
facilitates modeling a single diffusible field in 2D
facilitates modeling a single diffusible field in 3D

4.2 Types of Agent


Table 1: Here we summarize the attributes of each agent type. Spatial Dimension refers to how many dimensions the agents exist in, Lattice-Bound refers to whether agents are bound to discrete lattice positions or can move continuously. Stackable refers to whether multiple agents can exist at the same position.

4.3 Grid and Agent Class Definition

For demonstration we will reuse portions of code from the CompetitiveReleaseModel example (see section 9). When developing your own custom AgentGrid or Agent classes, begin by “extending” the relevant AgentGrid or Agent classes (listed above in Section 3). This extension is done with the following syntax:

Project specific AgentGrid definition syntax:
1.   public class ExampleModel extends AgentGrid2D<ExCell>

Project specific Agent class definition syntax:
1.    public class ExampleCell extends AgentSQ2Dunstackable<ExampleModel>

Note the <> after the base class name. In Java this is called a generic type argument. This is how we tell the Grid what kinds of Agent it will store, and how we tell the Agents what kind of AgentGrid will store them. It is used by the ExampleModel and ExampleCell to identify each other, so that their constituent functions can return the proper type, and access each other’s variables and methods.

4.4 Grid Constructors

In order to create a class that extends any of the AgentGrids, you must provide a constructor.

ExampleGrid Constructor
1.    public ExampleGrid(int x, int y, Rand generator) {
2.    	    super(x, y, ExampleCell.class);
3.    }

The first line declares the constructor and arguments, the second line calls super, which is required since our class extends a class with a constructor. Super is used to call the constructor of the base class. Into super we pass what the AgentGrid2D needs for initialization: an x and y dimension, which define the size of the grid, and ExampleCell.class, which is the class object of the ExampleCell. We pass the class object so that the ExampleGrid can create ExampleCells for us, as described in the next section.

4.5 Agent Initialization Functions

Note: Do not define a constructor for your Agent classes.

The AgentGrid that houses the agent will act as a “factory” for agents, and produce them with the NewAgent() function. This will return an agent that is either newly constructed, or an agent that has died and is being recycled. This returning of dead agents for reuse as new ones allows the model to run without tasking the garbage collector with removing all of the dead agents. This will increase the speed and decrease the memory footprint of your model. Instead of a constructor you should define some sort of initialization for your agents, which you do directly after the call to NewAgent.

Agent Initialization
1.    int RESISTANT = 0;
2.    int SENSITIVE = 1;
3.    for(int i = 0; i < totalCells; i++) {
4.      if (rng.Double() < resistantProb) {
5.        NewAgentSQ(tumorNeighborhood[i]).type = RESISTANT;
6.      } else {
7.        NewAgentSQ(tumorNeighborhood[i]).type = SENSITIVE;
8.      }
9.    }	

These lines of code come from the InitTumor function that the ExampleModel calls once at the beginning of a simulation. We use a random number generator and an if-else statement to decide whether to create a sensitive or resistant cell. since this type property is the only information individual cells store in this model, setting it is all that is needed to initialize a new cell. We call the NewAgentSQ() function, and pass in an index (“tumorNeighborhood” is an array of starting indices to setup the tumor) that marks where to place the new agent.

4.6 Grid Indexing

There are 3 different ways to describe or index locations on framework grids: single indexing (find the “ith” agent), square index (find the agent at grid point x, y) and point indexing (similar to square indexing, but for PT agents). Calling any of these functions will return NULL when no agent is present at that location.

4.6.1 Single Indexing

Since the x dimension, y dimension, and possibly the z dimension values are Grid constants, every square or voxel can be uniquely identified with a single integer index. Functions using this kind of indexing typically end with the SQ (abbreviating Square) phrase at the end of the function name. Single indexing is done with the following mappings:

In 2D: I(x,y) = x * yDim + y

In 3D: I(x,y,z) = x * yDim * zDim + y * zDim + z

the agents/values in the grids are stored as a single array, so single indexing is actually the most efficient as it requires no conversion.

4.6.2 Square Indexing

Similar to single indexing, square indexing uses a set of integers to refer to a specific square or voxel, as an (x,y) or an (x,y,z) set. Functions using this kind of indexing typically end with the SQ (abbreviating Square) phrase at the end of the function name.

4.6.3 Point Indexing

Uses a set of double values, to define continuous coordinates. Functions using this kind of indexing typically end with the PT (abbreviating Point) phrase at the end of the function name. The integer flooring of a coordinate set corresponds to the Square or Voxel that contains the point.

4.7 Typical Grid Loop

Next, an example Loop or Run function shows how to iterate through all agents in order to call “Step()” on each agent, which may contain functions related to birth, death, or mutations, for example.

1.    public void Run() {
2.    for(int i = 0; i < runTicks; i++) {
3.      for(GOLAgent a : this) {
4.        a.Step();
5.      }
6.    }	

The outer for loop counts the ticks, meaning that we will run for a total of runTicks steps. The inner for loop iterates over all the agents in the grid (“this” here is the grid that calls the Run function), and inside the loop we call that agent’s Step function, which is defined in the GOLAgent class from the GameOfLife example.

4.8 Grids containing Grid objects

Grids typically are containers of “agent” objects, but object Grids can be constructed to contain any arbitrary object including other grids. It may be useful to have a grid of grid objects when doing multiple stochastic simulations along with many other scenarios.

1.    Grid2Dobject <ExampleGrid> GlobalGrid = new Grid2Dobject <>(10,10);
2.    for(int x = 0; x < 10; x++) {
3.      for(int y = 0; y < 10; y++) {
4.        GlobalGrid.Set(x,y,new ExampleGrid(100,100,new Rand()));
5.      }
6.    }	

In the above example, GlobalGrid contains 10 rows and 10 columns of ExampleGrid grids, which in turn contain 100 rows and 100 columns of ExampleCell objects.

4.9 Assigning a Grid as an attribute

HAL allows for multiple grids overlaying each other. To access one grid from a second grid, it’s ideal to set the first grid as an attribute of the second grid, as follows:

1.    public class SecondGrid extends AgentGrid2D <ExampleAgent> {
2.      final public ExampleGrid firstGrid;
4.      // constructor
5.      SecondGrid(int x, int y, ExampleGrid firstGrid) {
6.        super(x,y,ExampleAgent.class);
7.        this.exampleFirst = firstGrid;
9.      }	
10.   }

In the above example, the constructor for the second grid requires an argument for the first grid. After instantiation of SecondGrid, we have access to the firstGrid as a member of SecondGrid

4.10 Type Hierarchy

In order to navigate the source code and see the full set of functions and properties of the framework components, it is important to become familiar with the type hierarchy that the framework uses. Figure 2 summarizes this hierarchy for 2D agents.

Figure 1:

Each node in the hierarchy names a class. each arrow denotes an extends relationship, eg. AgentBaseSpatial extends AgentBase. The blue classes are abstract and cannot be used directly. The green classes are fully implemented and can be used/extended further. Classes later in the hierarchy keep all properties and methods of the classes that they extend, which means that to see the full set of functions a class implements, one has to look at all of the extended classes beneath that class as well. The method summaries included in this manual can simplify this process somewhat as they show useful methods of the extended classes regardless of where they come from in the class heirarchy.

4.11 Agent Functions and Properties

Here we describe the built-in Agent functions. (3D adds a z)

returns the grid that the agent belongs to (this is a permanent agent property, not a function)
returns the age of the agent, in ticks. Be sure to use IncTick on the AgentGrid appropriately for this function to work.
returns the tick on which the agent was born
sets the agent’s birthTick value, which is used for the Age calculation
returns whether or not the agent currently exists on the grid
returns the index of the square that the agent is currently on
returns the X or Y indices of the square that the agent is currently on.
returns the X or Y coordinates of the agent. If the Agent is on-lattice, these functions will return the coordinates of the middle of the square that the agent is on.
moves the agent to the middle of the square at the indices/index specified
moves the agent to the coordinates specified
Similar to the move functions, only it will automatically either apply wraparound (move the agent to the other side of the AgentGrid if it goes out of bounds and the Grid has enabled wraparound in that direction), or prevent moving along a partiular axis if movement would cause the agent to go out of bounds.
removes the agent from the grid
This function takes a neighborhood (As generated by the neighborhood Util functions, or using GenHood2D/GenHood3D), and computes the set of position indices that the coordinates map to, with the agent at the (0,0) position of the neighborhood. The function returns the number of valid locations it found, which if wraparound is disabled may be less than the full set of neighborhood locations. See the CellStep function in the Complete Model example for more information.
These functions are similar to the the MapHood function, but they will only include valid empty or occupied indices, and skip the others.
This function takes a neighborhood and another mapping function as argument. The mapping function should return a boolean that specifies whether coordinates map to a valid location. The function will only include valid indices, and skip the others.
returns the displacement from the agent to a given x or y
returns the distance from this agent to the given position

4.12 SphericalAgent Functions and Properties

An extension of the AgentPT2D and AgentPT3D agent types, spherical agents have the additional property of a radius and x and y velocities. These properties allow spherical agents to behave like spheres that repel each other (3D adds a z).

the radius property is used during SumForces to determine collisions
The x and y velocity properties are added to by SumForces and applied to the agent’s position by calling ForceMove. Adding to these properties and calling ForceMove() will cause agents to move in a particular direction.
A default initialization function that sets the radius based on the argument, and the x and y velocities to 0.
The interactionRad argument is a double that specifies how far apart to check for other agent centers to interact with, and should be set to the maximum distance apart that two interacting agent centers can be. The OverlapForceResponse argument must be a function that takes in an overlap and an agent, and returns a force response aka. (double,Agent) -> double. The double argument is the extent of the overlap. If this value is positive, then the two agents are “overlapping” by the distance specified. If the value is negative, then the two agents are separated by the distance specified. The OverlapForceResponse should return a double which indicates the force to apply to the agent as a result of the overlap. If the force is positive, it will repel the agent away from the overlap direction, if it is negative it will pull it towards that direction. SumForces alters the xVel and yVel properites of the agent by calling OverlapForceResponse using every other agent within the interactionRad.
adds the xVel and yVel property values to the x,y position of the agent, moving it.
Facilitiates modeling cell division. The divRadius specifies how far apart from the center of the parent agent the daughters should be separated. The scratchCoordArr will store the randomly calculated axis of division. The axis is calculated using the Rand argument (HAL’s random number generator object). If no Rand argument is provided, the values currently in the scratchCoordArr will be used to determine the axis of division. The first entry of scratchCoordArr is the x component of the axis, the second entry is the y component. Division is achieved by placing the newly generated daughter cell divRadius away from the parent cell using divCoordArr for the x and y components of the direction, and placing the parent divRadius away in the negative direction. Divide returns the newly created daughter cell.
caps the xVel and yVel variables such that their norm is not greater than maxVel
Multiplies xVel and yVel by frictionConst. If frictionConst = 1, then no friction force will be applied, if frictionConst = 0, then the cell’s velocity components will be set to 0.

4.13 Universal Grid Functions and Properties

These functions and properties are shared by all of the different types of grids (3D adds a z):

the x or y dimension of the Grid
the total number of squares in the grid, equivalent to xDim*yDim
boolean properties of the Grid that specify whether wraparound is enabled along the left and right edges of the Grid (wrapX) and the top and bottom edges of the Grid (wrapY).
converts a set of coordinates to the position index of the square at those coordinates.
converts an index of a square to that square’s X or Y coordinate
returns the index of the square at the provided x,y coordinates, with wraparound.
returns whether the x and y coordinates provided are inside the Grid.
returns whether the x and y coordinates provided are inside the Grid, takes Grid-enabled wraparound into account.
returns the index of the center of the square in otherGrid that the coordinate maps to.
returns the position that the x/y argument rescales to in otherGrid
gets the displacement from the first coorinate to the second. if wraparound is enabled, the shortest displacement taking this into account will be returned.
gets the distance between two positions with or without grid wrap around (if wraparound is enabled, the shortest distance taking this into account will be returned)
gets the distance squared between two positions with or without grid wrap around (if wraparound is enabled, the shortest distance taking this into account will be returned). This is more efficient than the Dist function above as it skips a square-root calculation.
This function takes a neighborhood array, translates the set of coordinates to be centered around a particular central location, and computes which indices the translated coordinates map to, putting the location indices in the hood array. The function returns the number of valid locations it set.
This function is very similar to the previous definition of MapHood, only it additionally takes as argument an EvaluationFunctoin. this function should take as argument (i,x,y) of a location and return a boolean that decides whether that location should be included as a valid one.
increments the internal grid tick counter by 1. The grid tick is used with the Age() and BirthTick() functions to get age information about the agents on an AgentGrid. can otherwise be used as a counter with the other grid types.
gets the current grid timestep.
sets the tick to 0.
applies the action function to all positions in the rectangle, will use wraparound if appropriate
applies the action function to all position in the neighborhood, centered around centerX,centerY. This function will ignore any neighborhood mapping, and will not modify any neighborhood mapping.
applies the action function to all positions in the neighborhood up to validCount, assumes the neighborhood is already mapped
returns whether a valid index exists in the neighborhood
returns a list of indices, where each index maps to one square on the boundary of the grid
returns the set of indicies of squares that the line between (x1,y1) and (x2,y2) touches.

4.14 AgentGrid Method Descriptions

Here we describe the builtin functions that the agent containing Grids expose to the user. Most functions that take x,y arguments can alternatively take a single index argument. The Grid will use the index to find the appropriate square (3D adds a z).

returns a new agent, which will be placed at the center of the indicated square. x,y, and i are assumed to be integers
returns a new agent, which will be placed at the coordinates specified. x and y are assumed to be doubles
returns the number of agents that are alive on the entire grid.
reorders the list of agents so that dead agents will no longer have to be iterated over. Don’t call this during the middle of iteration!
shuffles the list of agents, so that they will no longer be iterated over in the same order. Don’t call this during the middle of iteration!
Cleans the agent list and shuffles it. This function is often called at the end of a timestep. Don’t call this during the middle of iteration!
disposes all agents, and resets the tick counter.
similar to the MapHood function, but will only include indices of locations that are empty
similar to the MapHood function, but will only include indices of locations that are occupied
returns a random living agent. Don’t call this during the middle of iteration!

4.14.1 AgentGrid Agent Search Functions

Several convenience methods have been added to make searching for agents easier.

Gets a single agent at the specified grid square, beware using this function with stackable agents, as it will only return one of the stack of agents. This function is recommended for the Unstackable Agents, as it tends to perform better than the other methods for single agent accesses.
Same as GetAgent above, but if x or y are outside the domain, it will apply wrap around if wrapping is enabled, or return null.
use in a foreach loop to iterate over all agents at a location. example: for(AGENT_TYPE a : IterAgents(5,6)) will run a for loop over all agents at grid square (5,6) with the agent being stored as the “a” variable. be sure to set AGENT_TYPE to the type of agent that lives of the grid that you are iterating over.
Same as IterAgents above, but will apply wraparound if x,y fall outside the grid dimensions.
will iterate over all agents around the given coordinate pair that fall within radius rad.
will iterate over all agents around the given coordinate pair that at least fall within radius rad, it is more efficient than IterAgentsRad, but it will also include some agents that fall outside rad, so be sure to do an additional distance check inside the function.
will iterate over all agents in the neighborhood as though it were mapped to centerX,centerY position. Note that this function won’t technically do the mapping. if the neighborhood is already mapped, use IterAgentsHoodMapped instead.
will iterate over all agents in the already mapped neighborhood. Be sure to supply the proper hood length.
iterates over all agents in the rectangle defined by (x,y) as the lower left corner, and (x+width,y+height) as the top right corner.
A variation of this function exists that matches every IterAgents variant mentioned above. Puts into the argument ArrayList all agents found at the specified location. Call the clear function on the ArrayList before passing it to GetAgents if you don’t want to append to whatever agents were already added there.
A variation of this function exists that matches every IterAgents variant mentioned above. These functions are used to query a location and choose a random agent from that area, an optional EvalAgent function (a function that takes an Agent and returns a boolean) can be provided to ensure that the random agent satisfies the condition
returns the number of agents that occupy the specified position. This is more efficient than using GetAgents and taking the length of the resulting ArrayList.

4.15 PDEGrid Method Descriptions

The PDEGrid classes do not store agents. They are meant to be used to model Diffusible substances. PDEGrids contain two fields (arrays) of double values: a current field, and a next field. The intended usage of these fields is that the values from the current timestep are stored in the current field while changes due to transformations are accumulated in the next field. Central to the function of the PDEGrid is the Update() function, which updates the current field with the values in the next field. The functions included in the PDEGrid class are the following: (3D adds a z, 1D removes y).

Note: For diffusion and advection schemes with a fixed boundary value, the boundary of the grid is defined at 1/2 a lattice position outside the edges of the usual lattice domain (xDim,yDim) except in the case of ADI (for now...).

adds the “next field” into the “current field”. The values in the “current field” won’t change unless Update() is called!
returns the “current field” double array which is usually used as the main field that agents interact with
adds the val to the “next field”
multiplies the val by the “current field” value at (x,y), and adds the product to the “next field”
sets a value in the “next field”. Any previous changes will be overwritten.
returns the maximum difference in any single lattice position between the current field and the current field the last time MaxDifRecord was called. (the function saves a record of the state for future comparison whenever it is called)
returns the maximum difference in any single lattice position between the current field and the next field.
returns the maximum difference in any single lattice position between the current field and next field, but proportional to the current field value. the denom offset is added to the current field value to make sure that a divide by 0 error can’t occur.
runs diffusion on the current field, adding the deltas to the next field. This form of the function assumes either a reflective or periodic (wraparound) boundary, depending on how the PDEGrid was specified. The diffCoef variable is the nondimensionalized diffusion conefficient. If the dimensionalized diffusion coefficient is D then diffCoef can be found by computing D*STpaicmeeSStetepp2 Note that if the diffCoef exceeds 0.25, this diffusion method will become numerically unstable.
has the same effect as the above diffusion function without the boundary value argument, except rather than assuming zero flux, the boundary condition is set to either the boundaryValue, or wraparound, depending on how the PDEGrid was specified.
runs diffusion on the current field using the ADI (alternating direction implicit) method. Without a boundaryValue argument, a zero flux boundary condition is imposed. Wraparound will not work with ADI. ADI is numerically stable at any diffusion rate.
runs diffusion on the current field using the ADI (alternating direction implicit) method. ADI is numerically stable at any diffusion rate. Adding a boundary value to the function call will cause boundary conditions to be imposed. for ADI boundary conditions, the boundary of the grid is defined at 1 lattice position outside the edges of the usual lattice domain
runs advection, which moves the concentrations using a constant flow with the x and y velocities passed. this signature of the function assumes wrap-around, so there can be no net flux of concentrations. Note that xVel and yVel are nondimensionalized velocity coefficients. If the dimensionalized advection velocity in the x direction is V x then xVel can be found by computing Vx*TimeStep
 SpaceStep. If |xV el| + |yV el| > 1, the function will be unstable.
runs advection as described above with a boundary value, meaning that the boundary value will advect in from the upwind direction, and the concentration will disappear in the downwind direction. If |xV el| + |yV el| > 1, the function will be unstable.
returns the gradient of the diffusible in a direction at the coordinates specified
returns the mean value of the field
returns the max value in the field
returns the min value in the field
applies the Set/Add/Mul operations to all entries of the “current field”, adding the deltas to the “next field”

4.16 Griddouble/Gridint/Gridlong Method Descriptions

As an alternative to this class, it may be useful to simply employ a double/int/long array whose length is equal to the length of the other associated grids. The I() function of any associated grids can be used to access values in the double array with x,y or x,y,z coordinates.

returns the field double array that the grid stores
gets, sets, adds, or multiplies with a single value in the field at the specified coordinates
applies the Set/Get/Mul operations to all entries of the field
sets all values in the field so that they are between min and max
returns the gradient of the field in a direction at the coordinates specified
returns mean value of the grid
returns the max value in the grid
returns the min value in the grid

4.17 AgentList Method Descriptions

AgentLists allow for keeping track of agent sub-populations. AgentLists must be instantiated with a type argument which specifies the type of agent that they will hold. When an agent dies, it is automatically removed from any AgentLists that contain it, so the AgentList will only contain living agents. It is not necissarily ordered. Use the java foreach loop syntax to iterate over the AgentList, just like the AgentGrids.

adds an agent to the AgentList, agents can be added multiple times.
removes all instances of the agent from the AgentList, returns the number of instances removed
returns the size of the AgentList, duplicate agents will be counted multiple times
returns whether a given agent is already in the AgentList
Shuffles the agentlist order
may speed up AgentList iteration if many agents from the AgentList have died recently
returns a random agent from the AgentList