Graph Programming with MASS

To promote agent-based graph analysis with MASS, we extended the Places class to GraphPlaces. Users can import graph data from a variety of file formats or define graphs dynamically by creating vertices and edges at runtime. The following example code propagates agents over a biological network to search for network motifs. This snippet starts by instantiating a new GraphPlaces object and reading graph data in from a DSL file. DSL files represent a proprietary format for graph data. It then adds a new vertex and edge to the graph, uses callAll to invoke the updateState method at each vertex within the graph, and exchangeAll to tell each vertex to invoke the forwardMsg method on each of its neighbors.

import MASS.*;
   
public class GraphCreation {
  public void main(String[] args) {
     MASS.init( );
     
     int gpHandle = 0;
     
     GraphPlaces graph = new GraphPlaces(gpHandle, BionetVertex.class.getName());
     graph.loadDSLFile("/home/NETID/foobar/input.dsl");
     graph.addVertex();
     
     int srcVertex = 99;
     int destVertex = 100;
     graph.addEdge(srcVertex, destVertex);
     
     int updateStateFuncID = 0;
     graph.callAll(updateStateFuncID);

     int forwardMsgFuncID = 1;
     graph.exchangeAll(forwardMsgFuncID);

     Agents searcher = new Agents(2, "Motif", graph, 101);
     crawler.doWhile(()->crawlers.hasAgents());
     
     MASS.finish();
  }  
}

public class Motif extends Agent {
   @onCreation public void init( ) { ...; }
   @onArrival public void walk( ) { ...; }
}

GraphPlaces Class

Constructor

Empty Graph Constructor

GraphPlaces(int handle, String className)

Constructor initializes an empty graph.

Parameters: int handle - A unique identifier that designates a group of places. Must be unique over all machines.

String className - Name of the user-implemented class VertexPlaces are constructed from.

Empty Graph Constructor With Partition

public GraphPlaces(int handle, String className, int PARTITIONS_SIZE)

Constructor initializes an empty graph with specified partition size. Vertices in the same partition are assigned to the same node.

Parameters: int handle - A unique identifier that designates a group of places. Must be unique over all machines.

String className - Name of the user-implemented class VertexPlaces are constructed from.

int PARTITION_SIZE - Defines the maximum number of vertices per partition.

GraphPlaces Input Formats

GraphPlaces supports four file input types, two of which are propreitary to the UW Bothell distributed systems lab.

DSL

The DSL file format is a proprietary graph file format that can be ingested by MASS in parallel by all nodes within the cluster. The format itself represents an adjacency list and looks as follows:

0=1,1
1=0,1;2,1;3,1;4,1
2=1,1;3,1
3=1,1;2,1;4,1
4=1,1;3,1

There is no header to the file and the vertices must be 0-indexed. The integer to the left of the = is the vertex ID and the comma-deliminted pairs to the right of it is its edge list (<vertex_id>;<edge_weight>). Files of this format can be imported into MASS by first instantiating a GraphPlaces object and then calling the loadDSLFile function and passing it the absolute path of the file. It must not be a relative path. This is because every node in the cluster will process the file and nodes other than master will need to the absolute path in order to find the file. An example can be seen below:

import MASS.*;

public class GraphCreation {
   public void main(String[] args) {
      MASS.init( );
      int gpHandle = 0;
      GraphPlaces graph = new GraphPlaces(gpHandle, BionetVertex.class.getName());
      graph.loadDSLFile("/home/NETID/foobar/input.dsl");
      MASS.finish();
  }
}

SAR

SAR is another properietary file format for importing graphs into MASS. It was started as a parallel I/O graph file format and named after the group members whom would be benefiting from its design. Unfortunately, it is not complete and will not work for graphs with more than 10 vertices. As it is currrently, it should not be used. This document will be updated if/when it is ready for use. An example of the file format is shown below:

9,9,9,9,9,9,9,9,9,9
1   2   3   4   5   6   7   8   9
0   2   3   4   5   6   7   8   9
0   1   3   4   5   6   7   8   9
0   1   2   4   5   6   7   8   9
0   1   2   3   5   6   7   8   9
0   1   2   3   4   6   7   8   9
0   1   2   3   4   5   7   8   9
0   1   2   3   4   5   6   8   9
0   1   2   3   4   5   6   7   9
0   1   2   3   4   5   6   7   8

GraphPlaces Methods

public void loadDSLFile(String filePath)

loadDSLFile loads the DSL formatted graph file, from the provided path, into the distributed GraphPlaces object.

Parameters:

String filePath - The path to the DSL formatted graph file. Note that this MUST be an absolute path if running MASS with more than one node.

public void loadSARFile(String filePath)

loadSARFile loads the SAR formatted graph file, from the provided path, into the distributed GraphPlaces object.

Parameters:

String filePath - The path to the SAR formatted graph file. Note that this MUST be an absolute path if running MASS with more than one node.

public int size()

Return:

The total size of the distributed graph.

public int addVertex()

Adds an empty vertex to the graph

Parameters:

Return:

The vertex ID. Returns -1 if vertex is unable to be added.

public int addVertex(Object vertexId)

Adds a vertex with unique identifier, vertexId to the graph

Parameters:

Object vertexId – Unique Identifier of the vertex. Must be unique and implement the hashCode and equals methods.

Return:

The vertex ID. Returns -1 if vertex is unable to be added.

public int addVertex(Object vertexInitParam)

Adds a vertex with a unique identifier and arguments to initialize the object extending VertexPlace.

Parameters:

Object vertexInitParam – Argument(s) to create vertex inorder to initialize vertex

Return:

Integer used for global indexing. Returns -1 if vertex is unable to be added.

public int removeVertex(Object vertexId)

Removes the vertex associated with the provided vertexId object.

Parameters:

Object vertexId – Unique Identifier of the vertex. Must be unique and implement the hashCode and equals methods.

Return:

True if the vertex was successfully removed, false otherwise.

public int removeVertex(int vertexId)

Removes the vertex associated with the provided vertex ID.

Parameters:

int vertexId – The vertex ID associated with the vertex the user wishes to remove.

Return:

True if the vertex was successfully removed, false otherwise.

public int getVertex(Object vertexId)

Returns the vertex associated with the provided vertexId object.

Parameters:

Object vertexId – Unique Identifier of the vertex. Must be unique and implement the hashCode and equals methods.

Return:

The vertex associated with the provided vertex ID object.

public int getVertex(int vertexId)

Returns the vertex associated with the provided vertex ID.

Parameters:

int vertexId – The vertex ID associated with the vertex the user wishes to retrieve.

Return:

The vertex associated with the provided vertex ID.

public int getOwnerID(int vertexID)

Parameters:

int vertexId – The vertex ID.

Return:

The ID of the node that owns the vertex associated with the provided vertex ID.

public boolean addEdge(int vertexId, int neighborId)

Adds an edge to the graph between the provided vertexID and neighborID with a default weight of 1.0. The vertex and neghbor vertices must exist at the time of edge creation.

Parameters:

int vertexId – The source vertex ID.

int neighborId - The neighbor/destination ID.

Return:

True if the edge was successfully added, false otherwise.

public boolean addEdge(int vertexId, int neighborId, double weight)

Adds an edge to the graph between the provided vertexID and neighborID with the provided weight. The vertex and neghbor vertices must exist at the time of edge creation.

Parameters:

int vertexId – The source vertex ID.

int neighborId - The neighbor/destination ID.

double weight – Weight of the edge.

Return:

True if the edge was successfully added, false otherwise.

public boolean addEdge(Object vertexId, Object neighborId)

Adds an edge to the graph between the provided vertexID and neighborID with a default weight of 1.0. The vertex and neghbor vertices must exist at the time of edge creation.

Parameters:

Object vertexId – Unique Identifier of the source vertex. Must be unique and implement the hashCode and equals methods.

Object neighborId - Unique Identifier of the destination vertex. Must be unique and implement the hashCode and equals methods.

Return:

True if the edge was successfully added, false otherwise.

public boolean addEdge(Object vertexId, Object neighborId, double weight)

Adds an edge to the graph between the provided vertexID and neighborID with the provided weight. The vertex and neghbor vertices must exist at the time of edge creation.

Parameters:

Object vertexId – Unique Identifier of the source vertex. Must be unique and implement the hashCode and equals methods.

Object neighborId - Unique Identifier of the destination vertex. Must be unique and implement the hashCode and equals methods.

double weight – Weight of the edge.

Return:

True if the edge was successfully added, false otherwise.

public boolean removeEdge(int vertexId, int neighborId)

Removes the edge between the provided vertex and neighbor IDs.

Parameters:

int vertexId – The vertex ID of the source vertex.

int neighborId – The vertex ID of the neighbor or destination.

Return:

True if the edge was successfully removed, false otherwise.

public boolean removeEdge(Object vertexId, Object neighborId)

Removes the edge between the provided vertex and neighbor IDs.

Parameters:

Object vertexId – Unique Identifier of the vertex that is the source of the edge. Must be unique and implement the hashCode and equals methods.

Object neighborId – Unique Identifier of the vertex that is the neighbor/destination of the edge. Must be unique and implement the hashCode and equals methods.

Return:

True if the edge was successfully removed, false otherwise.

public GraphModel getGraph()

Creates a GraphModel object containing all the vertices as instances of VertexModel. Each instance of VertexModel object contains a a list of neighors. Weight is not supported

Return

GraphModel object representing GraphPlaces graph

public void setGraph(final GraphModel newGraph)

Reinitilizes graph to blank and creates a new distributed graph based on the provided GraphModel. Weighted edges not supported.

Parameter:

final GraphModel newGraph – GraphModel object that will be the new graph.

public VertexMetaValues getVertexMetaValues(Object vertexId)

Given the object ID of the vertex, it returns a VertexMetaValues object containing meta values such as the vertex ID and node ID where the vertex is located.

Parameter:

Object vertexId – Unique Identifier of the vertex. Must be unique and implement the hashCode and equals methods.

Return:

The VertexMetaValues object associated with the provided vertexId object.

public void callAll( int functionId )

Initiates a callAll across all vertices within the graph to invoke the function associated with the provided functionId.

Parameter:

int functionId - The ID associated with the function the caller wishes to invoke on the vertices within the graph.

public void callAll(int functionId, Object argument)

Initiates a callAll across all vertices within the graph to invoke the function associated with the provided functionId.

Parameter:

int functionId - The ID associated with the function the caller wishes to invoke on the vertices within the graph.

Object argument - An object containing the argument the caller wishes to pass to the invoked function.

public Object[] callAll(int functionId, Object argument[])

Initiates a callAll across all vertices within the graph to invoke the function associated with the provided functionId.

Parameter:

int functionId - The ID associated with the function the caller wishes to invoke on the vertices within the graph.

Object argument - An object containing the argument the caller wishes to pass to the invoked function.

Return:

An array of return values from the invoked functions on each vertex, ordered by vertex ID.

public void exchangeAll( int destinationHandle, int functionId )

ExchangeAll tells all vertices within the graph to invoke the function associated with the provided functionID on each of its neighboring vertices and store the data within the calling vertex.

Parameter:

int destinationHandle - The handle of the GraphPlaces object for which the exchangeAll results should be aggregated to. This will most often be the handle of the GraphPlaces object that is calling the function.

int functionId - The ID associated with the function the caller wishes to invoke on neighboring vertices.

Extended Features

Lambda-Based callAll and reductive computation

Although the underlying implementation differs, the usage of the lambda-based callAll is identical to that in Places.

public void callAll(SerializableCAFunction<Place> function)

public <A> void callAll(SerializableCAFunctionWithArg<Place, A> function, Object argument)

public <A,R> Object callAll(SerializableCAFunctionWithReturn<Place, A, R> function, Object[] argument)

In addition, these reduce-related methods are inherited from Places:

public <R> R reduce(SerializableFunction<Place, R> mapper, SerializableBinaryOperator<R> reducer)

public int count()

public Place collectBest(SerializableBinaryOperator<Place> reducer)

public List<Place> collectTopK(int k, SerializableBinaryOperator<Place> better)

Please refer to the Places page for detailed information.

doAll

public void doAll(int callAllFunctionId, Object argument, int destinationHandle, int exchangeAllFunctionId, int numberOfIterations)

This function is inherited from Places. Please refer to the Places page for detailed information.

VertexPlace Methods

VertexPlace is the base class for which vertex implementation can be created and used within GraphPlaces.

public Object [] getNeighbors()

Return:

An array containing the vertex IDs of each of the vertices neighbors.

public Object [] getWeights()

Return:

An array containing the weights of each of the edges between this vertex and each of its neighbors. Note that this isn’t ordered but will share an index with the array returned from getNeighbors. To determine which edge the weight belongs to, use the index of weight to lookup the neighbor from this function call.

HIPPIE Support

The HIPPIE file format describes a protein network in the form of an edge list. Each line in a HIPPIE tab-delimited file represents a protein-protein interaction and a mapping of attributes associated with that interaction. Support is provided by the Hippie class and is found within the edu.uw.bothell.css.dsl.MASS.graph.hippie package. This class extends from GraphPlaces and provides facilities for reading in, and working with, HIPPIE graph data. When adding edges to the graph, HIPPIE attributes can be added as well. These are provided as a String and follow the same format as the attributes field within the HIPPIE file format. An example attributes string looks like this: experiments:in vivo;pmids:9334256,16189514,16713569;sources:HPRD,I2D,Rual05,Lim06. The are key/list pairs that are semi-colon delimited. keys are separated from value lists using a colon, and list values are comma delimited. The example attribute string will create the following edge attribute map:

Map<String, String[]>{
    "experiments": {"in vivo"},
    "pmids": {"9334256","16189514","16713569"},
    "sources": {"HPRD","I2D","Rual05","Lim06"},
}

An example of the file format is as follows:

AL1A1_HUMAN 216 AL1A1_HUMAN 216 0.76    experiments:in vivo,Two-hybrid;pmids:12081471,16189514,25416956;sources:HPRD,BioGRID,IntAct,MINT,I2D,Rual05
ITA7_HUMAN  3679    ACHA_HUMAN  1134    0.73    experiments:in vivo,Affinity Capture-Western,affinity chromatography technology;pmids:10910772;sources:HPRD,BioGRID,I2D
NEB1_HUMAN  55607   ACTG_HUMAN  71  0.65    experiments:in vitro,in vivo;pmids:9362513,12052877;sources:HPRD
SRGN_HUMAN  5552    CD44_HUMAN  960 0.63    experiments:in vivo;pmids:9334256,16189514,16713569;sources:HPRD,I2D,Rual05,Lim06
GRB7_HUMAN  2886    ERBB2_HUMAN 2064    0.9 experiments:in vitro,in vivo,Reconstituted Complex,protein array,affinity chromatography technology,nuclear magnetic resonance,x-ray crystallography,competition binding,pull down;pmids:9079677,16273093,12975581,12061724,17875712;sources:HPRD,BioGRID,MINT,I2D,IntAct
PAK1_HUMAN  5058    ERBB2_HUMAN 2064    0.73    experiments:in vivo,Affinity Capture-Western,affinity chromatography technology;pmids:9774445;sources:HPRD,BioGRID,I2D,STRING

public Hippie(int handle)

Constructs a HIPPIE graph with the provided handle and defaults to using the HIPPIEVertex class as its vertex implementation.

Parameters:

public Hippie(int handle, String className)

Constructs a HIPPIE graph with the provided handle and allows the caller to specify the vertex implementation to use.

Parameters:

public Hippie(int handle, String className, String filePath)

Constructs a HIPPIE graph with the provided handle, className, and HIPPIE filePath. This will populate the graph with the data from the provided file.

Parameters:

void loadFromFile(String filePath)

loadFromFile loads graph data from the provided HIPPIE formatted graph file.

Parameters:

MATSim Support

MATSim is an XML format that was created as a traffic simulation framework called Multi-Agent Traffic Simulation. The MATSim network file is an edge list format with two second-level lists: nodes and links. The nodes list contains a list of all vertices within the network whereas the links list contains the list of edges. Support for this file format is provided by the Matsim class and is found within the edu.uw.bothell.css.dsl.MASS.graph.matsim package. Edges within MATSim graphs can contain all of the edge attribute data found within the file. This can consist of speed, capacity, number of lanes, etc… An example of this file format is as follows:

<network>
    <nodes>
        <node id="1" x="665498.5915828889" y="6600721.058093354" ></node> 
        <node id="10" x="658803.7360160261" y="6575644.324923517"></node> 
        <node id="100" x="566282.7735573719" y="6454558.626888261"></node> 
        <node id="10000" x="408774.9462956431" y="6233917.802557087"></node> 
        <node id="10001" x="408772.02211252623" y="6234167.648466459"></node> ...
    </nodes> 
    <links>
        <link id="pt_99992" from="pt_740057809" to="pt_740057653" length="257.27792215211286" freespeed="8.333333333333334" capacity="500.0" permlanes="1.0" oneway="1" modes="pt" ></link>
        <link id="pt_99996" from="pt_740057599" to="pt_740057756" length="157.21884660387735" freespeed="8.333333333333334" capacity="500.0" permlanes="1.0" oneway="1" modes="pt" ></link>
        <link id="pt_99997" from="pt_740057688" to="pt_740057647" length="207.21244492773178" freespeed="8.333333333333334" capacity="500.0" permlanes="1.0" oneway="1" modes="pt" ></link>
        <link id="pt_99998" from="pt_740057647" to="pt_740057623" length="378.77959250564095" freespeed="8.333333333333334" capacity="500.0" permlanes="1.0" oneway="1" modes="pt" ></link>
        <link id="pt_99999" from="pt_740057623" to="pt_740057686" length="292.07787848782823" freespeed="8.333333333333334" capacity="500.0" permlanes="1.0" oneway="1" modes="pt" ></link>
    </links> 
</network>

public Matsim(int handle)

Constructs a MATSim graph with the provided handle and defaults to using the MatsimVertex class as its vertex implementation.

Parameters:

public Matsim(int handle, String className)

Constructs a MATSim graph with the provided handle and allows the caller to specify the vertex implementation to use.

Parameters:

public Matsim(int handle, String className, String filePath)

Constructs a MATSim graph with the provided handle, className, and MATSim filePath. This will populate the graph with the data from the provided file.

Parameters:

void loadFromFile(String filePath)

loadFromFile loads graph data from the provided MATSim formatted graph file.

Parameters:

GraphAgent Class

The GraphAgent class extends SmartAgent and implements the Serializable interface. It is designed to represent agents in a graph-based simulation. Each agent can be initialized with specific parameters, migrate between different nodes, and interact with other agents. The class includes functionality for distributing agents across computing nodes and managing their behavior during the simulation.

Constructor

GraphAgent()

Initializes a new instance of the agent by calling the superclass (SmartAgent) constructor.

GraphAgent(Object arg)

Initializes a GraphAgent object with a specific argument passed as an object. The argument can be used to configure specific properties of the agent during initialization.

Parameters:

arg - An object containing initialization data for the agent.

GraphAgent Methods

map(int initPopulation, int[] size, int[] index, int offset)

responsible for distributing agents across different nodes in a distributed environment. It calculates how many agents should be assigned to each node based on the total population and system size.

Parameters:

Returns:

The number of agents that should be assigned to a particular node based on its rank and system size.

SmartAgent Class

The SmartAgent class represents agents that can migrate between places and perform actions based on their environment. It builds upon the base Agent class by adding advanced capabilities for managing agent behavior in a distributed simulation environment.

Attributes

Constructor

SmartAgent()

Initializes a new instance of the agent by calling the superclass (Agent) constructor.

SmartAgent(Object arg)

Initializes a SmartAgent object with a specific argument passed as an object. The argument can be used to configure specific properties of the agent during initialization.

Parameters:

SmartAgent Methods

init(int place)

This method initializes the source node and itinerary of the smart agent.

Parameters:

getNextNode()

This method returns the next destination node for migration.

Returns:

The next node index.

setNextNode(int nextNode)

This method sets the next destination node for migration.

Parameters:

setItinerary(int[] itinerary)

This method sets an itinerary (navigation path) for the agent.

Parameters:

migratePropagate(Object arg)

This method migrates an agent to another place and propagates child agents to neighboring nodes (except previous and next nodes).

mgp.png

propagateDown(Object arg)

This method propagates agents downwards in a tree structure by migrating them to lower-indexed neighbors and spawning child agents along available edges (lower indexed neighbors).

pgd.png

migrateMin(Object arg)

This method migrates an agent to a neighboring place with the minimum weight based on some criteria (e.g., distance or cost).

migrateMax(Object arg)

This method migrates an agent to a neighboring place with the maximum weight based on some criteria.

mgmm.png

migrateRandom(Object arg)

This method migrates an agent to a randomly selected neighboring place.

mgr.png

propagateTree(int path, Object arg)

This method propagates agents through a binary tree structure by migrating either along both branches, left branch only, or right branch only based on input parameters.

Parameters:

pgtr.png

migrateSource(Object arg)

This method migrates an agent back to the graph vertex where it was first assigned.

Usage Example

public class CrawlerGraphMASS extends GraphAgent implements Serializable {
    // function identifiers
    public static final int init_ = 0;
    public static final int onArrival_ = 1;
    public static final int departure_ = 2;
    public static final int getTriangle_ = 3;
    public static final int propagateDown_ = 4;
    public static final int migrateSource_ = 5;

    public Object callMethod(int functionId, Object argument) {
        switch (functionId) {
            case init_:
                return init(argument);
            case propagateDown_:
                return propagateDown(argument);
            case migrateSource_:
                return migrateSource(argument);
            case getTriangle_:
                  return getTriangle(argument);
        }
        return null;
    }

    /**
     * Is the default constructor.
     */
    public CrawlerGraphMASS() {
        super();
    }

    /**
     * Is the actual constructor.
     *
     * @param arg an integer array that carries nodes this agent or
     *            parent has travelled so far.
     */
    public CrawlerGraphMASS(Object arg) {

        //Commenting for Auto Agent Migration
        //itinerary = (int[]) arg;

        super(arg);

        MASS.getLogger().debug("agent(" + getAgentId() + ") was born.");
    }

    /**
     * @param arg an integer array that includes only the first place to go.
     * @return nothing
     */
    public Object init(Object arg) {
        int[] itinerary = (int[]) arg;

        setitinerary(itinerary);
        return null;
    }

    /**
     * @param arg assumes null.
     * @return a string that includes three vertices forming a triagle
     */
    public Object getTriangle(Object arg) {
        String triangle = "";
        triangle += "agent(" + Integer.toString(getAgentId()) + "): ";
        for (int i = 0; i < 4; i++)
            triangle += Integer.toString(itinerary[i]) + " ";

        return triangle;
    }
}

public class CountTrianglesGraphMASS {
    private static final boolean CYTOSCAPE_LISTENER = false;
    private static int sequentialId = 2;

    /**
     * Counts and enumerates all triangles in a given graph
     *
     * @param args TBD
     */
    public static void main(String[] args) {
        // Read and validate input parameters
        if (args.length != 1) {
            System.err.println("Usage: java -jar TriangleCounting.jar <dsl_graph_file>");
            System.exit(-1);
        }
        
        String filePath = args[0];

        MASS.setLoggingLevel(LogLevel.DEBUG);
        MASS.init(10000000);

        System.out.println("Begin data space generation");
        
        long begin = System.currentTimeMillis();
        
        GraphPlaces network = new GraphPlaces(1, NodeGraphMASS.class.getName());
        try {
            network.loadDSLFile(filePath);
        } catch(Exception e) {
            System.err.println("Error reading in graph file: " + e);
            System.exit(3);
        }
        
        
        long end = System.currentTimeMillis();
        
        System.out.println("Import complete\nImport time: " + (end - begin) / 1000.0 + " s");

        run_triangle_counting((GraphPlaces) network);

        System.out.println("Finish MASS");

        MASS.finish();
    }

    public static void run_triangle_counting(GraphPlaces network) {
        Graph graph = network;

        List<VertexModel> vertices = graph.getGraph().getVertices();

        int nVertices = vertices.size();

        System.out.println("Number of Vertices is : " + nVertices);

        try {
            network.callAll(NodeGraphMASS.init_);

            // Time measurement starts
            System.out.println("Go! Graph");
            long startTime = System.currentTimeMillis();

            // Instantiate an agent at each node
            Agents crawlers = new Agents(sequentialId++, CrawlerGraphMASS.class.getName(), null, network, nVertices);

            Integer [] indices = vertices.stream()
                    .map(vertex -> {
                        Object value = MASSBase.distributed_map.get(vertex.id);
                        if (value == null) {
                            value = vertex.id;
                        }

                        return (int) value;
                    } )
                    .collect(Collectors.toList())
                    .toArray((Integer[]) Array.newInstance(Integer.class, 0));

            int[][] itineraries = new int[nVertices][4];
            for (int i = 0; i < nVertices; i++) {
                for (int j = 0; j < 4; j++) {
                    itineraries[i][j] = (j == 0) ? indices[i] : -1;
                }
            }
            crawlers.callAll(CrawlerGraphMASS.init_, (Object[]) itineraries);

            // For stats
            int totalAgents = 0; // max # agents in computation

            for (int i = 0; i < 3; i++) {
                Object currStep = (Object) (new Integer(i));
                
                System.out.println("*** iteration = " + i + "************");

                if (i < 2)
                {
                    crawlers.callAll(CrawlerGraphMASS.propagateDown_, currStep);
                }
                else {
                    crawlers.callAll(CrawlerGraphMASS.migrateSource_, currStep);
                }

                crawlers.manageAll();

                // For stats
                int nAgents = crawlers.nAgents();
                totalAgents += nAgents;
                System.out.println("*** Total Agents = " + nAgents + "************");
            }

            // Get results
            int nAgents = crawlers.nAgents();
            System.out.println("# triangles = " + nAgents);

            // Time measurement ends
            long elapsedTime = System.currentTimeMillis() - startTime;
            System.out.println("Elapsed time = " + elapsedTime);

            // Stats
            System.out.println("Statistics: total #agents = " + totalAgents);

            // Enumerate all triangles
            int[][] dummyArgs = new int[nAgents][1];
            Object[] triangles = (Object[]) crawlers.callAll(CrawlerGraphMASS.getTriangle_, (Object[]) dummyArgs);

            for (int i = 0; i < triangles.length; i++)
                System.out.println(i + ": " + (String) triangles[i]);

            network.callAll(NodeGraphMASS.display_);

        } catch (Exception e) {
            System.err.println("Error in execution: " + e.getMessage());

            e.printStackTrace(System.err);
        }
    }
}