Step 1: Adding road network and resources to Scenario
Road network classes in tutorial.scenario
package
Let’s start with new road network classes in the data model.
Create the following classes in the tutorial.scenario
package:
package tutorial.scenario;
public record Node (String id, double x, double y) {}
package tutorial.scenario;
import java.util.List;
public class Arc {
private final Node source;
private final Node dest;
private List<Point> points;
public Arc(Node source, Node dest, List<Point> points) {
this.source = source;
this.dest = dest;
this.points = points;
}
public Arc(Node source, Node dest) {
this(source, dest, List.of());
}
public Node getSource() {
return source;
}
public Node getDest() {
return dest;
}
public List<Point> getPoints() {
return points;
}
}
package tutorial.scenario;
public record Point (double x, double y) {}
The scenario.Node
class represents a 'map location' for assets.
The scenario.Arc
class describes a road that connects two nodes.
The scenario.Point
class defines an intermediate road location, allowing a road to be a polyline rather than a straight line.
Update Asset classes in tutorial.scenario
package
We have already used the Asset
, Warehouse
, and Store
classes
in Part 1.
This time we will update them to be located in some Node
of
the road network.
Update the asset-related classes in the tutorial.scenario
package
so that they look as follows:
package tutorial.scenario;
public abstract class Asset {
private final String id;
private final String name;
private final Node node;
protected Asset(String id, String name, Node node) {
this.id = id;
this.name = name;
this.node = node;
}
public String getName() {
return name;
}
public Node getNode() {
return node;
}
public String getId() {
return id;
}
}
package tutorial.scenario;
public class Store extends Asset {
public Store(String id, String name, Node node) {
super(id, name, node);
}
}
package tutorial.scenario;
public class Warehouse extends Asset {
public Warehouse(String id, String name, Node node) {
super(id, name, node);
}
}
Note how convenient it is to use roads to connect nodes, not assets. Now we can have several assets in one geographical point (for example, several different stores in one place) and use one road leading to that Node to deliver goods to/from all assets in that location. If we had no Nodes, we’d have to create a dedicated road to each asset in the same geographical point, which is rather cumbersome.
Truck class in tutorial.scenario
package
In Part 1, all trucks had the same speed, and all were initially located near the 1st Store. That’s why we did not need a separate class for trucks to describe them in the Scenario object.
We will now add some customization to cover the case when several trucks with different speed are used.
Create a new Truck
class in tutorial.scenario
package:
package tutorial.scenario;
public class Truck {
private String id;
private String name;
private double speed;
private Node initialNode;
public Truck(String id, String name, double speed, Node initialNode) {
this.id = id;
this.name = name;
this.speed = speed;
this.initialNode = initialNode;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getSpeed() {
return speed;
}
public Node getInitialNode() {
return initialNode;
}
}
Now it is also possible to set up each truck’s initial location (i.e., its location at the start of the modeling).
A truck here is an example of a resource, i.e. a thing that is used to execute a task.
Add road graph and trucks to Scenario
class
We will now use the newly created classes to describe the initial model state in the Scenario
class.
First, remove the following fields and respective getter methods:
-
truckCount
field andgetTruckCount()
method (each truck will be defined separately); -
truckSpeed
field andgetTruckSpeed()
method (each truck will be defined separately); -
routeLengthContainer
field andgetRouteLength()
method.
Update the list of Scenario class fields:
private final double intervalBetweenRequestsHrs;
private final double maxDeliveryTimeHrs;
private final List<Warehouse> warehouses;
private final List<Store> stores;
private final LocalDateTime beginDate;
private final LocalDateTime endDate;
private final List<Truck> trucks;
private final List<Node> nodes;
private final List<Arc> arcs;
Replace the Scenario
constructor to initialize the new fields:
public Scenario(List<Truck> trucks,
double intervalBetweenRequestsHrs,
double maxDeliveryTimeHrs,
List<Node> nodes,
List<Arc> arcs,
List<Warehouse> warehouses,
List<Store> stores,
LocalDateTime beginDate, LocalDateTime endDate) {
this.trucks = trucks;
this.intervalBetweenRequestsHrs = intervalBetweenRequestsHrs;
this.maxDeliveryTimeHrs = maxDeliveryTimeHrs;
this.beginDate = beginDate;
this.endDate = endDate;
this.nodes = nodes;
this.arcs = arcs;
this.warehouses = warehouses;
this.stores = stores;
}
Add getter methods for the new fields:
public List<Truck> getTrucks() {
return trucks;
}
public List<Node> getNodes() {
return nodes;
}
public List<Arc> getArcs() {
return arcs;
}
Update the Model
class
Update the getRouteLength()
method:
public double getRouteLength(Asset asset1, Asset asset2) {
// warning: this is a mock
return 100;
}
For a while, this method will return a constant value. We will delegate the route length calculation logic to a model-time road graph that we will soon introduce.
Update the initializeTrucks()
method in the Model
class to use the updated Scenario
:
private void initializeTrucks() {
var initialAssetForTrucks = getStores().get(0);
for (tutorial.scenario.Truck scenarioTruck : scenario.getTrucks()) {
Truck truck = new Truck(scenarioTruck.getId(), scenarioTruck.getName(), scenarioTruck.getSpeed(), initialAssetForTrucks, engine());
trucks.add(truck);
}
}
This time, model-time Truck uses more information from the Scenario-based Truck. The only thing that is still ignored is the Truck’s initial node: we still place the model Truck near the 1st Store. This will be updated after the road graph is introduced to the Model.
Update the Main
class
There has been a major change to the Scenario
class, so we need to
almost fully rewrite the Main
to keep the code compile.
First, remove the following:
-
routeLengthContainer
field and the respectiveimport
statement; -
runExperiment()
method;
Add the new fields to keep the road graph and resources information that we will pass to the Scenario
constructor:
private static List<Node> nodes;
private static List<Arc> arcs;
private static List<Truck> trucks;
When adding new import statements, be sure to import the correct classes - the ones from the tutorial.scenario package.
|
Then, rewrite the static{}
section to correctly initialize these new fields (and old ones, too):
static {
Node node1 = new Node("node1", 0, 0);
Node node2 = new Node("node2", 0, 30);
Node node3 = new Node("node3", 40, 30);
Node node4 = new Node("node4", 40, 0);
nodes = List.of(node1, node2, node3, node4);
Arc arc13 = new Arc(node1, node3);
Arc arc14 = new Arc(node1, node4);
Arc arc23 = new Arc(node2, node3);
Arc arc24 = new Arc(node2, node4);
arcs = List.of(arc13, arc14, arc23, arc24);
Warehouse wh1 = new Warehouse("wh1", "Warehouse 1", node1);
Warehouse wh2 = new Warehouse("wh2", "Warehouse 2", node1);
Warehouse wh3 = new Warehouse("wh3", "Warehouse 3", node2);
warehouses = List.of(wh1, wh2, wh3);
Store st1 = new Store("st1", "Store 1", node3);
Store st2 = new Store("st2", "Store 2", node3);
Store st3 = new Store("st3", "Store 3", node4);
stores = List.of(st1, st2, st3);
Truck truck1 = new Truck("truck-1", "Truck 1", TRUCK_SPEED, node3);
trucks = List.of(truck1);
}
All Warehouses will be kept in nodes 1 and 2, while Stores will reside in nodes 3 and 4. Note how we can place several assets to the same road graph node.
Replace the main()
method:
public static void main(String[] args) {
createAndRunOneExperimentWithStats();
}
Update the runScenarioAnalysis()
method:
private static void runScenarioAnalysis() {
var trucks = new ArrayList<Truck>();
for (int numberOfTrucks = 1; numberOfTrucks <= 10; numberOfTrucks++) {
trucks.add(new Truck("truck-" + numberOfTrucks, "Truck " + numberOfTrucks, TRUCK_SPEED, stores.get(0).getNode()));
Scenario scenario = new Scenario( trucks,
INTERVAL_BETWEEN_REQUESTS_HRS,
MAX_DELIVERY_TIME_HRS,
nodes,
arcs,
warehouses,
stores,
LocalDateTime.of(2023, 1, 1, 0, 0),
LocalDateTime.of(2023, 2, 1, 0, 0));
runExperimentWithStats(scenario, "scenario");
}
}
Update the createAndRunOneExperimentWithStats()
method:
private static void createAndRunOneExperimentWithStats() {
Scenario scenario = new Scenario( trucks,
INTERVAL_BETWEEN_REQUESTS_HRS,
MAX_DELIVERY_TIME_HRS,
nodes,
arcs,
warehouses,
stores,
LocalDateTime.of(2023, 1, 1, 0, 0),
LocalDateTime.of(2023, 1, 1, 12, 0));
runExperimentWithStats(scenario, "scenario");
}
Replace the runExperimentWithStats()
method and the headerPrinted
field with the following code:
runExperimentWithStats()
method and headerPrinted
fieldprivate static boolean headerPrinted = false;
private static void runExperimentWithStats(Scenario scenario, String scenarioName) {
ExperimentRun experiment = new ExperimentRun(scenario, new Engine());
experiment.run();
Statistics statistics = experiment.getStatistics();
if (!headerPrinted) {
System.out.println("Scenario \tTrucks count\tSL\tExpenses\tExpenses/SL");
headerPrinted = true;
}
System.out.println("%-20s\t%12s\t%s\t%s\t%s".formatted(scenarioName, scenario.getTrucks().size(),
Formats.getDefaultFormats().percentTwoDecimals(statistics.getServiceLevel()),
Formats.getDefaultFormats().dollarTwoDecimals(statistics.getExpenses()),
Formats.getDefaultFormats().dollarTwoDecimals(statistics.getExpensesPerServiceLevelPercent())));
}
Check the result
Start the program.
This should appear in the console output:
Scenario Trucks count SL Expenses Expenses/SL scenario 1 14,29% $ 420,00 $ 29,40
Recall that in Part 1 we had different results for the same configuration. The reason why now we have a lower SL is that we use a fake (longer) distance (100 km) instead of 'real' distance between nodes, so it takes the truck more time to travel, and it rarely comes in time.
To make the model realistic, we need to add the road graph and use it to calculate the distances.