3.1 Types of Grid

The bread and butter of the framework consists of different types of Grids and Agents. these are presented below.

AgentGrid0D
Holds nonspatial agents
AgentGrid1D
Holds all 1D agents
AgentGrid2D
Holds all 2D agents
AgentGrid3D
Holds all 3D agents
PDEGrid1D
facilitates modeling a single diffusible field in 1D
PDEGrid2D
facilitates modeling a single diffusible field in 2D
PDEGrid3D
facilitates modeling a single diffusible field in 3D

3.2 Types of Agent

Agent0D
Nonspatial agents. this agent type is a member of AgentGrid0D grid type.
AgentSQ2Dunstackable
“Square” agents in 2D. this agent type is bound to the AgentGrid2D lattice, and only one AgentSQ2Dunstackable can occupy a given lattice position at a time.
AgentSQ2D
“Square” agents in 2D. this agent type is bound to the AgentGrid2D lattice, however multiple AgentSQ2Ds can occupy the same lattice position
AgentPT2D
“Point” agents in 2D. this agent type is not bound to the AgentGrid2D lattice, and is free to move continuously within the Grid, provided it does not try to cross the Grid boundaries.
AgentSQ2Dunstackable
“Square” agents in 2D. this agent type is bound to the AgentGrid2D lattice, and only one AgentSQ2Dunstackable can occupy a given lattice position at a time.
AgentSQ2D
“Square” agents in 2D. this agent type is bound to the AgentGrid2D lattice, however multiple AgentSQ2Ds can occupy the same lattice position
AgentPT2D
“Point” agents in 2D. this agent type is not bound to the AgentGrid2D lattice, and is free to move continuously within the Grid, provided it does not try to cross the Grid boundaries.
AgentSQ3Dunstackable
“Square” agents in 3D (cube?). this agent type is bound to the AgentGrid3D lattice, and only one AgentSQ3Dunstackable can occupy a given lattice position at a time (gotta love copy paste)
AgentSQ3D
“Square” agents in 3D. this agent type is bound to the AgentGrid3D lattice, however multiple AgentSQ3Ds can occupy the same lattice position
AgentPT3D
“Point” agents in 3D. this agent type is not bound to the AgentGrid3D lattice, and is free to move continuously within the Grid, provided it does not try to cross the Grid boundaries.

3.3 Grid and Agent Class Definition

for demonstration we will use bits of code from the CompetitiveReleaseModel example. It is a fairly simple yet complete model, so it is a good place to begin learning the syntax of HAL. Grid classes and Agent classes used in models will usually be created as extensions of the Grid and Agent classes shown above. This extension is done with the following syntax:

Project specific AgentGrid definition syntax:

1public class ExampleModel extends Grid2D<ExCell> {

Project specific Agent class definition syntax:

1class ExampleCell extends AgentSQ2Dunstackable<ExModel> {

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 Grid 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.

3.4 Grid Constructors

In order to create a class that extends any of the Grids, you must provide a constructor. let’s look at part of the ExampleGrid constructor as an example

1public ExampleModel(int x, int y, Rand generator) { 
2    super(x, y, ExampleCell.class);

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.

3.5 Agent Initialization Functions

a word of caution: 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. An example from CompRelModel.java:

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

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.

3.6 Grid Indexing

There are 3 different ways to describe or index locations on framework grids:

3.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.

3.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.

3.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.

3.7 Typical Grid Loop

here we look at an example Loop or Run function. This is taken from the GOLGrid class, but all models will usually follow a similar pattern.

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

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.

3.8 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 heirarchy that the framework uses. Figure 1 summarizes this heirarchy for 2D agents.



Figure 1:
PIC

each node in the heirarchy 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 heirarchy 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.

3.9 Agent Functions and Properties

here we describe the builtin functions that the Agents expose to the user. (3D adds a z)

G:
returns the grid that the agent belongs to (this is a permanent agent property, not a function)
Age():
returns the age of the agent, in ticks. Be sure to use IncTick on the AgentGrid appropriately for this function to work.
BirthTick():
returns the tick on which the agent was born
Alive():
returns whether or not the agent currently exists on the grid
Isq():
returns the index of the square that the agent is currently on
Xsq(),Ysq():
returns the X or Y indices of the square that the agent is currently on.
Xpt(),Ypt():
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.
MoveSQ(x,y),MoveSQ(i):
moves the agent to the middle of the square at the indices/index specified
MovePT(x,y):
moves the agent to the coordinates specified
MoveSafeSQ(x,y),MoveSafePT(x,y):
Similar to the move functions, only it will automatically either apply wraparound, or prevent moving along a partiular axis if movement would cause the agent to go out of bounds.
Dispose():
removes the agent from the grid
SwapPoisition(otherAgent):
swaps the positions of two agents. useful mostly for the AgentSQ2unstackable and AgentSQ3unstackable classes, which don’t allow stacking of agents, making this maneuver otherwise difficult.
MapHood(int[]neighborhood):
This function takes a neighborhood (As generated by the neighborhood Util functions, or using GenHood2D/GenHood3D), translates the neighborhood coordinates to be centered around the agent, and computes the set of indices that the translated coordinates map to. The function returns the number of valid locations it set, which if wraparound is disabled may be less than the full set of coordinate pairs. this means that the passed in ret list will not be completely filled with valid entries. See the CellStep function in the Complete Model example for more information.
MapEmptyHood(int[]neighborhood),MapOccupiedHood(int[]neighborhood):
These functions are similar to the the MapHood function, but they will only include valid empty or occupied indices, and skip the others.
MapHood(int[]neighborhood,CoordsToBool):
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.

3.10 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)

radius:
the radius property is used during SumForces to determine collisions
xVel,yVel:
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 can cause agents to move in a particular direction
SumForces(interactionRad,OverlapForceResponse):
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.
ForceMove():
adds xVel and yVel property values to the x,y position of the agent.
Divide(divRadius,scratchCoordArr,Rand):
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.
CapVelocity(maxVel):
caps the xVel and yVel variables such that their norm is not greater than maxVel
ApplyFriction(frictionConst):
mulitplies xVel and yVel by frictionConst. if frictionConst = 1, then no friction force will be applied, if frictionConst = 0, then the cell won’t move.

3.11 Universal Grid Functions and Properties

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

xDim,yDim:
the x or y dimension of the Grid
length:
the total number of squares in the grid, equivalent to xDim*yDim
I(x,y):
converts a set of coordinates to the index of the square at those coordinates.
ItoX(i),ItoY(i):
converts an index of a square to that square’s X or Y coordinate
WrapI(x,y):
returns the index of the square at the provided x,y coordinates, with wraparound.
In(x,y):
returns whether the x and y coordinates provided are inside the Grid.
ConvXsq(x,otherGrid),ConvYsq(y,otherGrid):
returns the index of the center of the square in otherGrid that the coordinate maps to.
ConvXpt(x,otherGrid),ConvYpt(y,otherGrid):
returns the provided coordinate scaled to the dimensions of the otherGrid.
DispX(x1,x2),DispY(y1,y2):
gets the displacement from the first coorinate to the second. using wraparound if allowed over the given axis to find the shortest displacement.
Dist(x1,y1,x2,y2):
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)
DistSquared(x1,y1,x2,y2):
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) more efficient than the Dist function above as it skips a square-root calculation.
ChangeGridsSQ(outsideAgent,x,y),ChangeGridsPT(outsideAgent,x,y):
removes the agent from whichever grid it is currently living on, and moves it to this grid. The age of the agent is also reset so that the agent appears to be just born this timestep.
MapHood(int[]hood,centerX,centerY):
This function takes a neighborhood centered around the origin, translates the set of coordinates to be centered around a particular central location, and computes which indices the translated coordinates map to. The function returns the number of valid locations it set. this function differs from HoodToIs and CoordsToIs in that it takes no ret[], MapHood instead puts the result of the mapping back into the hood array.
MapHood(int[]hood,centerX,centerY,EvaluationFunction):
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.
IncTick():
increments the internal grid tick counter by 1, 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.
GetTick():
gets the current grid timestep.
ResetTick():
sets the tick to 0.

3.12 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)

NewAgentSQ(x,y),NewAgentSQ(i):
returns a new agent, which will be placed at the center of the indicated square. x,y, and i are assumed to be integers
NewAgentPT(x,y):
returns a new agent, which will be placed at the coordinates specified. x and y are assumed to be doubles
Pop():
returns the number of agents that are alive on the entire grid.
CleanAgents():
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!
ShuffleAgents():
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!
CleanShuffIe():
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!
Reset():
disposes all agents, and resets the tick counter.
MapHoodEmpty(int[]coords,centerX,centerY):
similar to the MapHood function, but will only include indices of locations that are empty
MapHoodOccupied(int[]coords,centerX,centerY):
similar to the MapHood function, but will only include indices of locations that are occupied
RandomAgent(rand):
returns a random living agent

3.12.1 AgentGrid Agent Search Functions

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

GetAgent(x,y)GetAgent(i):
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.
GetAgentSafe(x,y):
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.
IterAgents(x,y),IterAgents(i):
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.
IterAgentsSafe(x,y):
Same as IterAgents above, but will apply wraparound if x,y fall outside the grid dimensions.
IterAgentsRad(xPT,yPT,rad):
will iterate over all agents around the given coordinate pair that fall within radius rad.
IterAgentsRadApprox(xPT,yPT,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.
IterAgentsHood(int[]hood,centerX,centerY):
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.
IterAgentsHoodMapped(int[]hood,hoodLen):
will iterate over all agents in the already mapped neighborhood. be sure to supply the proper hood length.
IterAgentsRect(x,y,width,height):
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.
GetAgents(ArrayList<AgentType>,x,y)GetAgentsHood...:
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.
RandomAgent(x,y,rand,EvalAgent)RandomAgentHood...:
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
PopAt(x,y),PopAt(i):
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.

3.13 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 the next timestep values are calculated and stored on 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)

Get(x,y):
returns the “current field” double array which is usually used as the main field that agents interact with
Add(x,y,val),Mul(x,y,val):
adds or multiplies a value in the “current field” and adds the change to the “next field”
Set(x,y,val):
sets a value in the “next field”. any previous changes will be overwritten.
GetAll(val),SetAll(val[])AddAll(val)MulAll(val):
applies the Set/Get/Mul operations to all entries of the current field
MaxDifInternal():
returns the maximum difference in any single lattice position between the current field and the current field the last time MaxDifInternal was called.
MaxDifNext():
returns the maximum difference in any single lattice position between the current field and the next field.
Update():
adds the next field into the current field, also increments the tick.
Diffusion(diffCoef,wrapX,wrapY):
runs diffusion on the current field, putting the result into the swap field, then swaps their identities, so that the now current field stores the result of diffusion. This form of the function assumes either a reflective or wrapping boundary. the nonDimDiffCoef variable is the nondimensionalized diffusion conefficient. If the dimensionalized diffusion coefficient is x then nonDimDiffCoef can be found by computing x*TSipmaeceSStetpep2 Note that if the nonDimDiffCoef exceeds 0.25, this diffusion method will become numerically unstable.
Diffusion(diffCoef,boundaryValue,wrapX,wrapY):
has the same effect as the diffusion function without the boundary value argument, except that at the boundaries the function assumes either a constant value (which is the boundary value) or a wrapping bondary.
DiffusionADIupdate(diffCoef):
runs diffusion on the current field using the ADI (alternating direction implicit) method. ADI is numerically stable at any diffusion rate. However at high rates it may produce artifacts. ADI is done sequentially, not simultaneously like the other functions described here, so the field is updated first and then ADI is applied.
DiffusionADIupdate(diffCoef,boundaryValue):
runs diffusion on the current field using the ADI (alternating direction implicit) method. ADI is numerically stable at any diffusion rate. However at high rates it may produce artifacts. adding a boundary value to the function call will cause boundary conditions to be imposed. ADI is done sequentially, not simultaneously like the other functions described here, so the field is updated first and then ADI is applied.
Advection(xVel,yVel):
runs advection, which shifts 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.
Advection(xVel,yVel,bounaryValue):
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.
GradientX(x,y),GradientY(x,y):
returns the gradient of the diffusible along the specified axis

3.14 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.

GetField():
returns the field double array that the grid stores
Get(i),Get(x,y),Set(i),Set(x,y,val),Add(i),Add(x,y,val),Mul(i,val),Mul(x,y,val):
gets, sets, adds, or multiplies with a single value in the field at the specified coordinates
SetAll(val),SetAll(val[])AddAll(val)MulAll(val):
applies the Set/Get/Mul operations to all entries of the field
BoundAll(min,max)BoundAllSwap(min,max):
sets all values in the field so that they are between min and max
GradientX(x,y),GradientY(x,y):
returns the gradient of the diffusible along the specified axis

3.15 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.

AddAgent(agent):
adds an agent to the AgentList, agents can be added multiple times.
RemoveAgent(agent):
removes all instances of the agent from the AgentList
GetPop():
returns the size of the AgentList, duplicate agents will be counted multiple times
InSet(agent):
returns whether a given agent is already in the AgentList
ShuffleAgents(rng):
Shuffles the agentlist order
CleanAgents():
may speed up AgentList iteration if many agents from the AgentList have died recently
RandomAgent(rng):
returns a random agent from the AgentList