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 |
|---|---|
Describes a point in 2D space |
|
Represents a directed polyline in 2D space |
|
Represents an oriented graph |
|
Provides a container and helper methods for 'agents' moving along the graph |
|
Represents a graph arc inside a |
|
Represents a graph vertice (node) inside a |
|
Represents an agent that lives and moves inside a |
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:
package tutorial.model;
import com.amalgamasimulation.geometry.Point;
import com.amalgamasimulation.graphagent.AgentGraphNodeImpl;
public class Node extends AgentGraphNodeImpl {
public Node(Point point) {
super(point);
}
}
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.
Update asset-related classes in 'tutorial.model` package
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:
-
In the
scenario.Assetclass, thenodefield is of the typescenario.Node. -
In the
model.Assetclass, thenodefield is of the typemodel.Node.
Update the following classes 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;
}
}
package tutorial.model;
public class Store extends Asset {
public Store(Node node, String name) {
super(node, name);
}
}
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:
-
Initialize the simulation-time road graph and assets using the scenario data.
-
Create a
GraphEnvironmentinstance 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:
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:
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):
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.