Step 2: Real-world input data (scenario)
We will keep the simulation parameters and initial real-world objects definitions in a separate Scenario
class.
Once a Scenario object has been defined, each simulation based on this scenario should produce the same result. This useful trait is called 'reproducibility' of a simulation model. For a model to be reproducible, each and every random aspect of a model should take the same value during each model run. This includes the sequence in which random numbers are generated and the sorting order of any collection used in the model.
The Scenario
object will contain three kinds of data:
-
Real-world attributes provided by the problem description (transportation requests frequency distribution, assets, distances between assets, truck speed, max delivery time);
-
Real-world parameters that we can alter to see how it affects the performance of the modeled system (number of trucks);
-
Simulation time period.
In the upcoming steps the set of parameters will be changed, but the attribution of a parameter to one of the groups will hold.
There will be two random variables in our simulation: time interval between requests (exponential distribution, one parameter).
We will now create a class to hold all these parameters. All parameters are passed upon constructions and are not modified after a scenario is created.
Create a new tutorial.scenario
package - it will contain scenario-related classes.
Add the following classes to the tutorial.scenario
package:
package tutorial.scenario;
public abstract class Asset {
private final String id;
private final String name;
protected Asset(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
package tutorial.scenario;
public class Warehouse extends Asset {
public Warehouse(String id, String name) {
super(id, name);
}
}
package tutorial.scenario;
public class Store extends Asset {
public Store(String id, String name) {
super(id, name);
}
}
package tutorial.scenario;
import java.util.HashMap;
import java.util.Map;
public class RouteLengthContainer {
private Map<Warehouse, Map<Store, Double>> routeLengths = new HashMap<>();
public void add(Warehouse warehouse, Store store, double routeLength) {
routeLengths.computeIfAbsent(warehouse, wh -> new HashMap<>()).put(store, routeLength);
}
public double getRouteLength(Asset asset1, Asset asset2) {
Warehouse warehouse;
if (asset1 instanceof Warehouse wh) {
warehouse = wh;
} else if (asset2 instanceof Warehouse wh) {
warehouse = wh;
} else {
throw new IllegalArgumentException("Either asset1 or asset2 must be a Warehouse");
}
Store store;
if (asset1 instanceof Store s) {
store = s;
} else if (asset2 instanceof Store s) {
store = s;
} else {
throw new IllegalArgumentException("Either asset1 or asset2 must be a Store");
}
double length = routeLengths.getOrDefault(warehouse, Map.of()).getOrDefault(store, Double.NaN);
if (Double.isNaN(length)) {
throw new IllegalStateException("No route length information found for the supplied assets");
}
return length;
}
}
package tutorial.scenario;
import java.time.LocalDateTime;
import java.util.List;
public class Scenario {
private final int truckCount;
private final double truckSpeed;
private final double intervalBetweenRequestsHrs;
private final double maxDeliveryTimeHrs;
private final RouteLengthContainer routeLengthContainer;
private final List<Warehouse> warehouses;
private final List<Store> stores;
private final LocalDateTime simulationStartDt;
private final LocalDateTime simulationEndDt;
public Scenario(int truckCount, double truckSpeed, double intervalBetweenRequestsHrs, double maxDeliveryTimeHrs,
List<Warehouse> warehouses, List<Store> stores, RouteLengthContainer routeLengthContainer,
LocalDateTime simulationStartDt, LocalDateTime simulationEndDt) {
this.truckCount = truckCount;
this.truckSpeed = truckSpeed;
this.intervalBetweenRequestsHrs = intervalBetweenRequestsHrs;
this.maxDeliveryTimeHrs = maxDeliveryTimeHrs;
this.routeLengthContainer = routeLengthContainer;
this.warehouses = warehouses;
this.stores = stores;
this.simulationStartDt = simulationStartDt;
this.simulationEndDt = simulationEndDt;
}
public int getTruckCount() {
return truckCount;
}
public double getTruckSpeed() {
return truckSpeed;
}
public double getIntervalBetweenRequestsHrs() {
return intervalBetweenRequestsHrs;
}
public double getMaxDeliveryTimeHrs() {
return maxDeliveryTimeHrs;
}
public double getRouteLength(Asset asset1, Asset asset2) {
return routeLengthContainer.getRouteLength(asset1, asset2);
}
public LocalDateTime getSimulationStartDt() {
return simulationStartDt;
}
public LocalDateTime getSimulationEndDt() {
return simulationEndDt;
}
public List<Warehouse> getWarehouses() {
return warehouses;
}
public List<Store> getStores() {
return stores;
}
}
There are two types of assets: warehouses and stores. Goods are initially stored in warehouses, and then transported to stores for further consumption.
To find out the transportation duration, we need to know the transportation distance and truck speed.
Truck speed is passed to the Scenario constructor, while transportation distance
is supplied by an instance of RouteLengthContainer
class.
We assume that the distance between any two assets remains the same during simulation. |
However, here we do not calculate the distance using some kind of a road graph, which is something that we are going to introduce in the next part of this tutorial.