Step 5: Transportation request generation

The TransportationRequest class

A request to move cargo from a Warehouse to a Store will be presented by an object of a new TransportationRequest class. The class will contain several constructor-initialized fields backed up by getter methods:

  1. 'id' (unique request identifier);

  2. 'sourceAsset' (a Warehouse, transportation source);

  3. 'destAsset' (a Store, transportation destination);

  4. 'beginTime' (model time when the request has appeared);

  5. 'deadlineTime' (model time when we want the request to be fulfilled).

Besides, the TransportationRequest will also need a boolean field to denote whether the request has been fulfilled ('completed'), and a double-valued field to store the model time when the request has been completed ('completedTime').

Create the following 'TransportationRequest.java' file in the 'tutorial.model' package:

TransportationRequest.java
package tutorial.model;

public class TransportationRequest {
    private final int id;
    private final Warehouse sourceAsset;
    private final Store destAsset;
    private final double createdTime;
    private final double deadlineTime;
    private boolean completed;
    private double completedTime;
    
    public TransportationRequest(int id, Warehouse sourceAsset, Store destAsset, double beginTime, double deadlineTime) {
        this.id = id;
        this.sourceAsset = sourceAsset;
        this.destAsset = destAsset;
        this.createdTime = beginTime;
        this.deadlineTime = deadlineTime;
        //System.out.println(String.format("%.3f\tRequest #%s created", createdTime, id));
    }
    
    public int getId() {
        return id;
    }
    
    public Asset getSourceAsset() {
        return sourceAsset;
    }
    
    public Asset getDestAsset() {
        return destAsset;
    }

    public double getCreatedTime() {
        return createdTime;
    }

    public double getDeadlineTime() {
        return deadlineTime;
    }

    public double getCompletedTime() {
        return completedTime;
    }

    public boolean isCompleted() {
        return completed;
    }
    
    public void setCompletedTime(double completedTime) {
        this.completedTime = completedTime;
        this.completed = true;
    }
}

Uncomment the System.out.println call in the class constructor to see more debug information.

The RequestGenerator class

The 'outer world' that supplies new transportation requests to our model will be presented by an object of the new RequestGenerator class.

This object will generate transportation requests with varying parameters at random moments, as defined by the Scenario object.

The following parameters should be passed to the RequestGenerator class constructor:

  1. The Model instance, that will act as the source of Engine instance (needed to schedule request generation during modeling);

  2. Time interval between requests distibution;

  3. Max delivery time.

Note that we do not pass the Scenario object here: the RequestGenerator does not use any class from the tutorial.scenario package.

We will let other objects subscribe to new requests via a special addNewRequestHandler() method.

Create the following 'RequestGenerator.java' file in the 'tutorial.model' package:

RequestGenerator.java
package tutorial.model;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.math3.distribution.RealDistribution;

public class RequestGenerator {
    private final Model model;
    private final RealDistribution newRequestIntervalDistribution;
    private final double maxDeliveryTimeHrs;
    private List<Consumer<TransportationRequest>> newRequestHandlers = new ArrayList<>();
    private int lastUsedRequestId = 0;

    public RequestGenerator(Model model, RealDistribution newRequestIntervalDistribution, double maxDeliveryTimeHrs) {
        this.model = model;
        this.newRequestIntervalDistribution = newRequestIntervalDistribution;
        this.maxDeliveryTimeHrs = maxDeliveryTimeHrs;
        model.engine().scheduleRelative(0, this::createTransportationRequest);
    }
    
    public void addNewRequestHandler(Consumer<TransportationRequest> newRequestHandler) {
        newRequestHandlers.add(newRequestHandler);
    }
    
    private void createTransportationRequest() {
        Warehouse from = model.getWarehouses().get(model.getRandomGenerator().nextInt(model.getWarehouses().size()));
        Store to = model.getStores().get(model.getRandomGenerator().nextInt(model.getStores().size()));
        TransportationRequest newRequest = new TransportationRequest(getNextRequestId(), from, to,
                model.engine().time(), model.engine().time() + maxDeliveryTimeHrs * model.engine().hour());
        newRequestHandlers.forEach(handler -> handler.accept(newRequest));
        model.engine().scheduleRelative(newRequestIntervalDistribution.sample(), this::createTransportationRequest);        
    }
    
    private int getNextRequestId() {
        return ++lastUsedRequestId;
    }
}

Let’s discuss the features of the createTransportationRequest() method:

  1. To make the request generation a recurring process we place request creation inside a createTransportationRequest() method that uses the Engine.scheduleRelative() method to schedule a self-call. The next createTransportationRequest() call will happen after a period of model time drawn from the distribution object. This is how the delay between consecutive requests is modeled.

  2. Note how a source Warehouse and a destination Store are chosen: the randomGenerator object is used to enforce model reproducibility.

Changes to the Model class

Now let’s get back to the Model class and create a new RequestGenerator object inside the Model class constructor.

Model.java, creating a RequestGenerator object in class constructor
RealDistribution newRequestIntervalDistribution = new ExponentialDistribution(randomGenerator, scenario.getIntervalBetweenRequestsHrs());
RequestGenerator requestGenerator = new RequestGenerator(this, newRequestIntervalDistribution, scenario.getMaxDeliveryTimeHrs());
It is important that the same 'randomness source' (randomGenerator) be used in each and every place of the model that uses random values.

Check the result

Start the program. You should see the following output printed to the console:

0,000	Request #1 created
1,024	Request #2 created
3,733	Request #3 created
3,871	Request #4 created
3,877	Request #5 created
4,037	Request #6 created
4,789	Request #7 created
7,358	Request #8 created
8,120	Request #9 created
8,197	Request #10 created
9,129	Request #11 created
10,034	Request #12 created
10,803	Request #13 created