Interval Statistics

1. Overview

IntervalStatistics class is a class for collecting and analyzing statistics over intervals. This class is used to process, categorize, and manage intervals dynamically, with a common use case being the management of simulation statistics slots(i.e. instances of ExtendableStatsSlot). The intervals can be grouped by keys, typically, but not mandatory, by the class of the slot. For example, intervals can be categorized by the type of slot (e.g., idle slots, moving slots) for more detailed analysis.

This class also provides memory management by limiting storage intervals, which is important when running simulation models with a long simulation period.

Below is an example of usage of IntervalStatistic class:

Engine engine = new Engine();

// Create an IntervalStatistics
IntervalStatistics<ExtendableStatsSlot, Class<?>> intervalStatistics = 
		new IntervalStatistics<>(
				ExtendableStatsSlot::getClass, 
				ExtendableStatsSlot::close
		);

// Adding active object state slots during simulation
intervalStatistics.accept(new IdleSlot(engine.time(), () -> engine.time()));
intervalStatistics.accept(new MovingSlot(engine.time(), () -> engine.time()));

// Getting statistics on how much time an active object spent in moving state
double timeInMovingState = intervalStatistics.intervalSet(MovingSlot.class).length();

The code demonstrates:

  1. Initialization of IntervalStatistics to track time intervals.

  2. Adding statistical slots (e.g., IdleSlot, MovingSlot) during simulation.

  3. Getting statistics on how much time an active object spent in moving state.

This way, we can easily retrieve statistics on how much time an active object spent in each state.

2. Typical use case

Consider we are modeling a vehicle which travels around and moves some cargo. Let it also has states Moving, Loading, Unloading, and Idle.

Now our task is to determine how long the vehicle has been in each state. We also want to calculate the service level, assuming that the states that are useful are Loading, Unloading, and Moving. This is where this interval statistic class might be of a great help for us.

A common use case for the statistics class is together with a state machine. In our example, we have a state enumeration like this:

// Create enum with a vehicle states
enum State{
	IDLE,
	MOVING,
	LOADING,
	UNLOADING
}

For each of the states, we will create statistics slots:

class IdleSlot extends ExtendableStatsSlot {
	public IdleSlot(double beginTime, Supplier<Double> timeSupplier) {
		super(beginTime, timeSupplier);
	}
}

class MovingSlot extends ExtendableStatsSlot {
	public MovingSlot(double beginTime, Supplier<Double> timeSupplier) {
		super(beginTime, timeSupplier);
	}
}

class LoadingSlot extends ExtendableStatsSlot {
	public LoadingSlot(double beginTime, Supplier<Double> timeSupplier) {
		super(beginTime, timeSupplier);
	}
}

class UnloadingSlot extends ExtendableStatsSlot {
	public UnloadingSlot(double beginTime, Supplier<Double> timeSupplier) {
		super(beginTime, timeSupplier);
	}
}

Then create a state machine and a state statistic and subscribe to the necessary actions:

Engine engine = new Engine();
// Create a state machine
StateMachine<State> stateMachine = new StateMachine<>(State.values(), State.IDLE, engine);
// Add all default transitions between all different states
stateMachine.addAllTransitions();
// Create an IntervalStatistics
IntervalStatistics<ExtendableStatsSlot, Class<?>> intervalStatistics = 
		new IntervalStatistics<>(
				ExtendableStatsSlot::getClass, 
				ExtendableStatsSlot::close
		);
// Add actions that create specific slots when entering specific states
stateMachine.addEnterAction(State.IDLE, o -> intervalStatistics.accept(new IdleSlot(engine.time(), () -> engine.time())));
stateMachine.addEnterAction(State.MOVING, o -> intervalStatistics.accept(new MovingSlot(engine.time(), () -> engine.time())));
stateMachine.addEnterAction(State.LOADING, o -> intervalStatistics.accept(new LoadingSlot(engine.time(), () -> engine.time())));
stateMachine.addEnterAction(State.UNLOADING, o -> intervalStatistics.accept(new UnloadingSlot(engine.time(), () -> engine.time())));

Now we can get how long the truck was in each state:

double timeInIdleState = intervalStatistics.intervalSet(IdleSlot.class).length();
double timeInMovingState = intervalStatistics.intervalSet(MovingSlot.class).length();
double timeInLoadingState = intervalStatistics.intervalSet(LoadingSlot.class).length();
double timeInUnloadingState = intervalStatistics.intervalSet(UnloadingSlot.class).length();

And we can calculate the service level:

// Calculate service level
double usefulTime = List.of(MovingSlot.class, LoadingSlot.class, UnloadingSlot.class)
		.stream()
		.mapToDouble(slotType -> intervalStatistics.intervalSet(slotType).length())
		.sum();
double serviceLevel = Utils.zidz(usefulTime, engine.time());

3. Getting stored intervals

In some cases, for example, to display slots on a gantt chart, you may need to get all saved intervals. To do this, use the intervals() method:

// Get stored intervals
List<ExtendableStatsSlot> storedSlots = intervalStatistics.intervals();
// Get current interval
ExtendableStatsSlot currentSlot = intervalStatistics.currentInterval();

For memory management purposes, the Interval Statistics class trims old slots. You can set the maximum number of storage slots using a constructor with three parameters:

// Create an IntervalStatistics with maxIntervalsToStore = 1000
new IntervalStatistics<>(
		ExtendableStatsSlot::getClass, 
		ExtendableStatsSlot::close,
		1000
);

Slot trimming does not affect the return value of the methods totalIntervalSet() and intervalSet(K key). Statistics are collected correctly for the all simulation period. The default value of maxIntervalsToStore is Integer.MAX_VALUE / 2.