Step 3: Simulation classes

Simulation-time Assets

Create a new tutorial.model package. This package will store the Model class (will be created soon) and several model-related classes used during simulation.

Create the following new classes in the tutorial.model package:

Asset.java
package tutorial.model;

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;
    }
}
Warehouse.java
package tutorial.model;

public class Warehouse extends Asset {

    public Warehouse(String id, String name) {
        super(id, name);
    }
}
Store.java
package tutorial.model;

public class Store extends Asset {

    public Store(String id, String name) {
        super(id, name);
    }
}

Currently the source code of these classes is the same as for their counterparts in the tutorial.scenario package. However, making these model-time classes separate will allow them to evolve independently of the input data format. As we will see in Part 2, instances of these classes will be placed inside a model-time road graph.

We will make out simulation model operate on these classes instead of those found in the Scenario object.

The Mapping class

During simulation, it is sometimes necessary to get the original, Scenario-contained object thaw was used to construct the simulation object. For such purposes, a new Mapping class is created.

Create a new Mapping class in the tutorial package:

Mapping.java
package tutorial;

import com.amalgamasimulation.utils.container.BiMap;

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

Here, a bidirectional mapping for assets is stored. Note that it is not initialized automatically, we will need to do it when creating model-time Assets.

The Model class

We are going to create the class that stores the model state and coordinates the simulation-related objects - the Model class.

The class com.amalgamasimulation.engine.Model from the Amalgama Platform will be the base class for our Model class. This class provides some convenience methods that we will use in the upcoming steps. To create an instance of com.amalgamasimulation.engine.Model we need an Engine, so we will pass an Engine instance as a constructor parameter to our Model class.

We have already created the Scenario class that represents the model input, so let’s pass a scenario object to the constructor of our Model class and use it to change the settings of the Engine instance.

Create a new 'tutorial.model' package.

Create a new 'Model.java' file in the 'tutorial.model' package with the following contents:

Model.java
package tutorial.model;

import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.math3.distribution.ExponentialDistribution;
import org.apache.commons.math3.distribution.RealDistribution;
import org.apache.commons.math3.random.RandomGenerator;

import com.amalgamasimulation.engine.Engine;
import com.amalgamasimulation.utils.random.DefaultRandomGenerator;

import tutorial.Mapping;
import tutorial.scenario.Scenario;

public class Model extends com.amalgamasimulation.engine.Model {
    private final Scenario scenario;
    private final RandomGenerator randomGenerator = new DefaultRandomGenerator(1);
    private Mapping mapping = new Mapping();
    private List<Warehouse> warehouses = new ArrayList<>();
    private List<Store> stores = new ArrayList<>();

    public Model(Engine engine, Scenario scenario) {
        super(engine);
        engine.setTemporal(scenario.getSimulationStartDt(), ChronoUnit.HOURS);
        engine.scheduleStop(engine.dateToTime(scenario.getSimulationEndDt()), "Stop");
        this.scenario = scenario;
        initializeModelObjects();
    }

    public double getRouteLength(Asset asset1, Asset asset2) {
        return scenario.getRouteLength(mapping.assetsMap.key(asset1), mapping.assetsMap.key(asset2));
    }

    private void initializeModelObjects() {
        initializeAssets();
    }

    private void initializeAssets() {
        for (var scenarioWarehouse : scenario.getWarehouses()) {
            var wh = new Warehouse(scenarioWarehouse.getId(), scenarioWarehouse.getName());
            warehouses.add(wh);
            mapping.assetsMap.put(scenarioWarehouse, wh);
        }
        for (var scenarioStore : scenario.getStores()) {
            var store = new Store(scenarioStore.getId(), scenarioStore.getName());
            stores.add(store);
            mapping.assetsMap.put(scenarioStore, store);
        }
    }

    public RandomGenerator getRandomGenerator() {
        return randomGenerator;
    }

    public List<Warehouse> getWarehouses() {
        return warehouses;
    }
    
    public List<Store> getStores() {
        return stores;
    }

}

You may have noticed that there are a lot of yet unused imports here. We are going to build up the Model class gradually, adding objects and methods in several stages, so we intentionally add all imports at the outset to stay fully focused on the main code thereafter.

The randomGenerator fields will be the 'single source of randomness' in the model, so as to keep modeling results reproducible.

In the Model class constructor, the Engine.setTemporal() method maps the initial moment of the simulation to astronomical time. We also ask the engine to use 'hours' for time units, since we are going to measure time in hours and truck speed in km/h. See Model time and date article for more details.

Now that we have applied the time mapping, we can use dateToTime() method of the Engine instance to map 'astronomical' time to non-negative model time of the moment when the engine should stop the simulation.

The last line of the class constructor calls the initializeModelObjects() method. Currently only assets get initialized.