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.Asset
class, thenode
field is of the typescenario.Node
. -
In the
model.Asset
class, thenode
field 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
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:
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.