Step 2: Adding road graph and asset classes to the simulation model

In our case, a road graph vertex has a known 2D 'location', and each road graph edge is a polyline connecting two vertices, so an edge has a fixed 'length'.

The Amalgama Platform provides a toolbox to work with a graph structure of that kind. Here are the classes that we are going to need:

Amalgama Platform class Description

com.amalgamasimulation.geometry.Point

Describes a point in 2D space

com.amalgamasimulation.geometry.Polyline

Represents a directed polyline in 2D space

com.amalgamasimulation.graph.Graph

Represents an oriented graph

com.amalgamasimulation.graphagent.GraphEnvironment

Provides a container and helper methods for 'agents' moving along the graph

com.amalgamasimulation.graphagent.AgentGraphArc

Represents a graph arc inside a GraphEnvironment

com.amalgamasimulation.graphagent.AgentGraphNode

Represents a graph vertice (node) inside a GraphEnvironment

com.amalgamasimulation.graphagent.GraphAgent

Represents an agent that lives and moves inside a GraphEnvironment.

An 'agent' here is an object that can move along a graph. At each moment of model time we can 'ask' an agent about its location in the graph, i.e. its current graph vertex and/or graph arc. In our case, Truck will be such an agent, we will cover that in the next section.

In a GraphEnvironment, graph vertices and arcs are represented by AgentGraphNode and AgentGraphArc instances. Each AgentGraphNode has a location in 2D space (represented by a Point object). Each AgentGraphArc connects two AgentGraphNode objects, so its starting and ending points also have a location in 2D space. An AgentGraphArc can have intermediate points. If we sequentially connect the points of an AgentGraphArc, we get the arc’s Polyline object.

Add a new Amalgama Platform library

To be able to use these classes, add the 'graphagent' Amalgama Platform library to your project using the <dependencies> section of the pom.xml file:

<dependency>
  <groupId>com.amalgamasimulation</groupId>
  <artifactId>com.amalgamasimulation.graphagent</artifactId>
  <version>2.0.3</version>
</dependency>

Road network classes in tutorial.model package

Recall that we already have a description of the road graph in our Scenario class (as collections of scenario.Node and scenario.Arc objects and Point objects inside arcs). We will map them to 'simulation-time' instances of the new model.Node and model.Arc classes that implement the AgentGraphNode and AgentGraphArc interfaces, both coming with the Amalgama Platform.

First, create the following new graph-related classes in the 'tutorial.model' packages:

Node.java in the tutorial.model package
package tutorial.model;

import com.amalgamasimulation.geometry.Point;
import com.amalgamasimulation.graphagent.AgentGraphNodeImpl;

public class Node extends AgentGraphNodeImpl {
    public Node(Point point) {
        super(point);
    }
}
Arc.java in the tutorial.model package
package tutorial.model;

import com.amalgamasimulation.geometry.Polyline;
import com.amalgamasimulation.graphagent.AgentGraphArcImpl;

public class Arc extends AgentGraphArcImpl {
    public Arc(Polyline polyline) {
        super(polyline);
    }
}

The 'model.Point' ('road bendpoint') class is not created: we are not going to refer to particular road points during simulation.

Update the Mapping class

Since there are classes with similar names and semantics in both data model and simulation model, it will be convenient to have a mapping between the objects from the two worlds. The Mapping class that we added in Part 1 will help us.

Update the Mapping class located in the tutorial package:

package tutorial;

import com.amalgamasimulation.utils.container.BiMap;

public class Mapping {
    public BiMap<tutorial.scenario.Asset, tutorial.model.Asset> assetsMap = new BiMap<>();
    public BiMap<tutorial.scenario.Node, tutorial.model.Node> nodesMap = new BiMap<>();
    public BiMap<tutorial.scenario.Arc, tutorial.model.Arc> forwardArcsMap = new BiMap<>();
}

The BiMap class is a collection class that supports two-way mapping between keys and values.

In our simulation model we have Asset, Warehouse, and Store classes (all in the 'tutorial.model' package). The model.Asset class will soon be given a node field as well, just like its scenario.Asset counterpart, but make sure you understand the difference:

  1. In the scenario.Asset class, the node field is of the type scenario.Node.

  2. In the model.Asset class, the node field is of the type model.Node.

Update the following classes in the 'tutorial.model` package:

Asset.java in the 'tutorial.model` package
package tutorial.model;

public abstract class Asset {
    private final Node node;
    private final String name;

    protected Asset(Node node, String name) {
        this.node = node;
        this.name = name;
    }

    public Node getNode() {
        return node;
    }

    public String getName() {
        return name;
    }
}
Store.java in the 'tutorial.model` package
package tutorial.model;

public class Store extends Asset {
    public Store(Node node, String name) {
        super(node, name);
    }
}
Warehouse.java in the 'tutorial.model` package
package tutorial.model;

public class Warehouse extends Asset {
    public Warehouse(Node node, String name) {
        super(node, name);
    }
}

Initialize model graph

Our next actions in the Model class will be as follows:

  1. Initialize the simulation-time road graph and assets using the scenario data.

  2. Create a GraphEnvironment instance that uses the simulation-time road graph.

Add the new import statements to the 'Model.java' file:

import com.amalgamasimulation.geometry.Point;
import com.amalgamasimulation.geometry.Polyline;
import com.amalgamasimulation.graphagent.GraphEnvironment;

Add the following fields to the Model class:

Model.java, new fields
private GraphEnvironment<Node, Arc, Truck> graphEnvironment = new GraphEnvironment<>();
private List<Arc> arcs = new ArrayList<>();

The graphEnvironment field will let our trucks use the simulation-time graph.

Add a new initializer method for the graph-related fields:

Model.java, initialization methods
private void initializeGraph() {
    for (int i = 0; i < scenario.getNodes().size(); i++) {
        var scenarioNode = scenario.getNodes().get(i);
        Node node = new Node(new Point(scenarioNode.x(), scenarioNode.y()));
        graphEnvironment.addNode(node);
        mapping.nodesMap.put(scenarioNode, node);
    }
    for (var scenarioArc : scenario.getArcs()) {
        Polyline polyline = createPolyline(scenarioArc);
        if (polyline.getLength() != 0) {
            Node sourceNode = mapping.nodesMap.get(scenarioArc.getSource());
            Node destNode = mapping.nodesMap.get(scenarioArc.getDest());

            Arc forwardArc = new Arc(polyline);
            Arc backwardArc = new Arc(polyline.getReversed());

            forwardArc.setReverseArc(backwardArc);
            backwardArc.setReverseArc(forwardArc);
            graphEnvironment.addArc(sourceNode, destNode, forwardArc, backwardArc);
            mapping.forwardArcsMap.put(scenarioArc, forwardArc);
            this.arcs.add(forwardArc);
            this.arcs.add(backwardArc);
        }
    }
}

private Polyline createPolyline(tutorial.scenario.Arc scenarioArc) {
    List<Point> points = new ArrayList<>();
    points.add(new Point(scenarioArc.getSource().x(), scenarioArc.getSource().y()));
    for (var bendpoint : scenarioArc.getPoints()) {
        points.add(new Point(bendpoint.x(), bendpoint.y()));
    }
    points.add(new Point(scenarioArc.getDest().x(), scenarioArc.getDest().y()));
    return new Polyline(points.stream().distinct().toList());
}

Update the initializeAssets() method to use updated constructors for Warehouses and Stores (now they accept a Node instance):

Model.java, initializeAssets() methods
private void initializeAssets() {
    for (var scenarioWarehouse : scenario.getWarehouses()) {
        var wh = new Warehouse(mapping.nodesMap.get(scenarioWarehouse.getNode()), scenarioWarehouse.getName());
        warehouses.add(wh);
    }
    for (var scenarioStore : scenario.getStores()) {
        var store = new Store(mapping.nodesMap.get(scenarioStore.getNode()), scenarioStore.getName());
        stores.add(store);
    }
}

Update the initializeModelObjects() method to initialize road graph and assets prior to trucks:

private void initializeModelObjects() {
    initializeGraph();
    initializeAssets();
    initializeTrucks();
}

Check the result

Let’s make sure the code compiles and works. Run the program and see if you get the following output:

Scenario            	Trucks count	SL	Expenses	Expenses/SL
scenario            	           1	14,29%	$ 420,00	$ 29,40

The program output should stay the same: we have initialized the model-time road graph, but Trucks do not use it yet.

Time to update the Trucks.