Step 14: Statistics: Collect & Display

So, our warehouse model is already functional.

Simulation modeling is often used to collect data about the behavior of a modeled object under various conditions and gain insights.

Let’s see how we can use the Amalgama Platform to collect and display online statistics for:

  1. Forklift Utilization

  2. Average Truck (un)loading time

1. Collecting Forklift Utilization

First, we will look deeper into the amount of time the forklifts are busy and idle.

Forklift.java
public class Forklift extends GraphAgent<Node, Arc> {
...
    private final SingleStateStatistics<IEquipmentState> stateStats = new SingleStateStatistics<>();
...
    public double getUtilizedTime() {
        return stateStats.getEverEnteredStates().stream()
            .filter(s -> s.isUtilized())
            .mapToDouble(s -> stateStats.getStateDuration(s, time()))
            .sum();
    }
...

Add one line at the beginning of the putStatsSlot() method:

    public void putStatsSlot(IEquipmentState state) {
        stateStats.onEnteredState(state, time());
...

We employ the SingleStateStatistics class from the Amalgama Platform. It can be used to collect the statistics for the states the modeled object is in.

Recall that each task state has the isUtilized() method that indicates that some 'useful job' is being done when an object is in such a state. The SingleStateStatistics.getStateDuration() gives the time spent in each state.

Now we can calculate the average utilization over all forklifts in the Model:

Model.java
public class Model extends com.amalgamasimulation.engine.Model {
...
    public double getForkliftUtilization() {
        final double avgUtilizationTime = forklifts.stream()
                .mapToDouble(f -> f.getUtilizedTime())
                .average().orElse(0);
        
        return Utils.zidz(avgUtilizationTime, time());
    }
...

The Utils.zidz() method helps to safely handle division by zero.

2. Collecting average truck (un)loading time

Let’s find out how much time trucks spend at the gates.

Model.java
public class Model extends com.amalgamasimulation.engine.Model {
...
    private final Map<Direction, DoubleSummaryStatistics> truckLoadingDuration = Map.of(
            Direction.IN, new DoubleSummaryStatistics(),
            Direction.OUT, new DoubleSummaryStatistics()
            );
...
    public DoubleSummaryStatistics getTruckLoadingDuration(Direction direction) {
        return truckLoadingDuration.get(direction);
    }
...

DoubleSummaryStatistics is one of the standard JDK classes. We will use it to calculate the average of a series of truck loading durations.

Modify the Dispatcher.handleTruck() method to collect the values:

Dispatcher.java
...
    private void handleTruck(Truck truck, Gate gate, Runnable onComplete) {
        gate.parkTruck(truck);
        final double parkedTime = engine.time();
        requestForklift((forklift, forkliftReleaser) ->
            new HandleTruckTask(engine, truck, gate, forklift).start(() -> {
                forkliftReleaser.run();
                gate.unparkTruck(truck);
                model.getTruckLoadingDuration(truck.getDirection()).accept(engine.time() - parkedTime);
                onComplete.run();
            })
        );
    }
...

3. Statistics visualization

Go to the SimulationStatisticsPart class (in the 'application' bundle, in the 'simulation' package). Replace its onShowModel() method with the following:

SimulationStatisticsPart.java
...
    protected void onShowModel(Model model) {
        if(model != null) {
            List<Indicator> data = List.of(
                    new Indicator(messages.INDICATOR_ARCS, () -> (double)appData.getScenario().getArcs().size(), Formats.getDefaultFormats()::noDecimals, false),
                    new Indicator(messages.INDICATOR_NODES, () -> (double)appData.getScenario().getNodes().size(), Formats.getDefaultFormats()::noDecimals, false),
                    new Indicator("Truck average unloading time", () -> model.getTruckLoadingDuration(Direction.IN).getAverage(), Formats.getDefaultFormats()::twoDecimals, false),
                    new Indicator("Truck average loading time", () -> model.getTruckLoadingDuration(Direction.OUT).getAverage(), Formats.getDefaultFormats()::twoDecimals, false),
                    new Indicator("Forklift utilization", () -> model.getForkliftUtilization(), Formats.getDefaultFormats()::percent, false)
                    );
            tableView.setData(data);
        } else {
            tableView.setData(Collections.emptyList());
        }
    }
...
Make sure you import the Direction class.

4. Statistics is updated in real time

Run the simulation.

In the bottom-left corner of the application window, the collected statistics are shown:

Statistics

5. Happy end

Our intro into the world of the Amalgama Platform has come to a close.

You can find the complete source code for this tutorial here: https://github.com/amalgama-llc/warehouse-tutorial

We wish you insightful simulation modeling!

Amalgama Team