There are two different programming paradigms that you may use when developing applications using the MASS library:
Traditionally, the MASS library has been used in the centralized mode of operation, using patterns and methods similar to those found with other distributed computing frameworks like MPI. In this style of development, methods within Places and Agents would be called from a main application, and thus the performance of the simulation is directly linked to the master node.
With the 1.3.0 release of the MASS library, a new paradigm is offered that provides an “event-centric” model for developing applications that aims to simplify development and provide a more intuitive interface for certain types of agent-based implementations. In this style, methods within Agents and Places are executed upon certain lifecycle event triggers.
In the event-oriented model, code that would direct Agent movement or Place operations is invoked from “within” the Agent or Place. For example, code that controls Agent movement is contained entirely within the Agent class, rather than partially split between the Agent and main application (“callAll”). This code is invoked during the lifecycle of the agent, as the simulation executes. Event triggers are synchronized with the master node, thus preventing out-of-order execution caused by individual node performance.
There are currently three events that trigger execution of methods in Agents and Places:
Annotations are used to determine which methods are invoked when these events occur. These events are described individually in more detail below.
A method annotated with “@OnCreation” will be called once, after initial creation of the Agent and after association with a Place.
A method annotated with “@OnArrival” will be called after migration to a new Place. This method will be called AFTER the Place has executed “@OnArrival” for this Agent.
A method annotated with “@OnDeparture” will be called immediately before the Agent actually beings migrating to a new Place. This method will be called BEFORE the Place has executed “@OnDeparture” for this Agent.
A method annotated with “@OnCreation” will be called once, after initial creation of the Place.
A method annotated with “@OnArrival” will be called when an Agent migrates to this Place. This method will be called BEFORE the Agent has executed “@OnArrival”.
A method annotated with “@OnDeparture” will be called before the Agent actually migrates away from this Place. This method will be called AFTER the Agent has executed “@OnDeparture”.
A method annotated with “@OnMessage” will be called when a message addressed to the Agent/Place “arrives”, and the (optional) Class of the argument matches the Class of the message. For example, if the method annotated with “OnMessage” accepts an argument of type “Integer”, then that method will be called if an Integer message arrives. If a message of type “String” arrives, that method would not be called.
An event-centric MASS application will typically contain at least three classes:
The event-centric MASS main application will call “MASS.init()” to set up the cluster and to start initial distribution of Places and Agents. After initialization has been performed, the main application can cycle through the simulation using either “doAll”, “doUntil”, or “doWhile” methods.
Instead of repeatedly calling (Agents) callAll() and manageAll() as you would with the traditional MASS application, you can use the “doAll” method, which will perform the same action. A variant of “doAll” accepts an iteration count, so the simulation will perform the specified number of iterations without having to explicity create a “for” loop.
The “doUntil” method accepts a function as an argument. While the function continues to evaluate to FALSE, the “doAll” method is called repeatedly. For each iteration the function is reevaluated to see if the condition has changed.
The “doWhile” method operates the same as “doUntil”, except that that logic is opposite; while the function continues to evaluate to TRUE, the “doWhile” method is called repeatedly. For each iteration the function is reevaluated to see if the condition has changed.
Following are code snippets that provide examples of how annotated event methods may be used by Agents and Places. #### Initializing a Place or Agent #### This snippet shows how an Agent can be initialized and prepared immediately before the simulation begins.
@OnCreation
public void doInitializationTasks() {
// Determine the first Place I should start working at
// Set my initial state
// Ready to start!
}@OnArrival
public void onArrival() {
Node node = ( Node ) getPlace( );
// have I not arrived at the target node yet?
if ( nextNode != node.getIndex( )[0] ) return;
// any where else to go? was I not the first to arrive at this node?
if ( node.getFootprint() == Node.VISITED || node.getNeighbors() == null || node.getNeighbors().length == 0 ) {
MASS.getLogger( ).debug( "Agent [" + getAgentId( ) + "] terminated onArrival at a deadend [" + getPlace( ).getIndex( )[0] + "]" );
kill();
return;
}
// upon arrival at a Node, set footprint
node.setFootprint( Node.VISITED );
}@OnArrival
public void doAgentArrival() {
// increment the visit count
numAgentsVistited++;
}There are several different ways to send a message, your selection will depend on which Agents/Places should receive the message and the message payload (if any):
// Send a String object as a message to ALL Agents
MASS.getMessagingProvider().sendAgentMessage( MessageDestination.ALL_AGENTS, "Hello!" );
// Send an Integer to a single, specific Agent by ID
MASS.getMessagingProvider().sendAgentMessage( 1, 42 ); // send "42" to Agent #1
// Send a broadcast message to ALL Places
MASS.getMessagingProvider().sendPlaceMessage( MessageDestination.ALL_PLACES, "Hello!" );Here is an example of an Agent sending a broadcast message informing all other Agents that it has found a new best value:
// Have I found a better value? Broadcast it if so!
if ( bestPV < bestGV ) {
BestXYV newBestCoordinates = new BestXYV( bestPX, bestPX, bestPV );
MASS.getMessagingProvider().sendAgentMessage( MessageDestination.ALL_AGENTS, newBestCoordinates );
}Messages may be used to convey information or to simply trigger behavior. The annotated method must either accept no arguments or match the data type of the transmitted message payload.
@OnMessage
public void receiveIntegerPayloadMessage( int payload ) {
// do something with the received message
myLocalCount += payload;
}
@OnMessage
public void receiveAnyMessage() {
// this is called whenever a message is received
doTriggeredActivity();
}The following code shows how the main application iterates through a simulation until a condition evaluates to false. In this example, the Agents “die” once their mission has completed. When all Agents have completed, the condition evaluates to FALSE and the simulation ends.
// start MASS
MASS.init();
// Time measurement starts
System.out.println( "Go!" );
long startTime = System.currentTimeMillis( );
// Create a map
Places network = new Places( 1, Node.class.getName( ), null, nNodes );
long networkGenerationEndTime = System.currentTimeMillis( );
// Instantiate agents
Args2Agents args2agents = new Args2Agents( src );
Agents agents = new Agents( 2, Crawler.class.getName( ), ( Object ) args2agents, network, 1 );
// run the simulation until all agents have completed their tasks
agents.doWhile( () -> agents.hasAgents() );
// Time measurement ends
long graphGenTime = networkGenerationEndTime - startTime;
long elapsedTime = System.currentTimeMillis( ) - startTime;
// Stats
System.out.println( "Statistics:");
System.out.println( "Time to generate graph (ms): " + ( graphGenTime ) );
System.out.println( "Time to traverse graph (ms): " + ( elapsedTime - graphGenTime ) );
System.out.println( "Total Elapsed time (ms): " + elapsedTime );
MASS.finish( );