Simulation Engine
1. What is engine and how it works
Simulation engine allows developers to run discrete-event simulation models in Java applications. Engine is a Java class that provides API for scheduling events for execution at specific instant in simulation time. When running, Engine executes a loop of the 3 repeating steps:
-
determine the time of the next chronological event,
-
advance the simulation time to this instant,
-
execute the action associated with this event.
2. Creating an engine
This code creates an engine, schedules an event for execution at time 0.1, schedules a stop at time 1 and runs the engine:
Engine engine = new Engine();
engine.scheduleAbsolute(0.1, () -> {
System.out.println("Event is executed at time " + engine.time());
});
engine.scheduleStop(1, "Stop");
engine.run();
Engine can be created and used without any external context, so it can be used in any type of Java application: console apps, Eclipse RCP apps or web apps.
3. Model time and model date
Most simulation models use a concept of model calendar to simulate the behavior of a system during the specific period of time. To define the model calendar, it is sufficient to specify the date of zero simulation time and the time unit:
engine.setTemporal(LocalDateTime.of(2022, 1, 1, 0, 0), ChronoUnit.DAYS);
Next section covers model time and model date in more detail.
4. Relative events scheduling
The events can be scheduled from inside code of other events’ actions. In this case, it is convenient to schedule events for a moment “in some time from now”. This can be done with scheduleRelative method:
Engine engine = new Engine();
engine.setTemporal(LocalDateTime.of(2022, 1, 1, 0, 0), ChronoUnit.DAYS);
engine.scheduleAbsolute(10, () -> {
// This is the code of first event's action
System.out.println("Event 1 is executed at date " + engine.date());
// Schedule the second event in 1 time unit (i.e. day) from the current
// simulation moment
engine.scheduleRelative(1, () -> System.out.println("Event 2 is executed at date " + engine.date()));
});
engine.setFastMode(true);
engine.run();
// This code prints the following two lines:
// Event 1 is executed at date 2022-01-11T00:00
// Event 2 is executed at date 2022-01-12T00:00
5. Modes of running the engine
The engine can run in normal or fast mode:
-
In normal mode, the engine waits between the execution of events for the amount of real time defined by the simulation time unit and the specified time scale. This mode is used when some sort of animation or visualization must be shown.
-
In fast mode, the engine runs as fast as possible, without any waiting time between the events. This mode is used when no animation is needed and users are interested just in the results of the simulation.
The engine can be run both synchronously and asynchronously.
-
Synchronous execution is useful when multiple experiments are run in parallel, for example, by using parallel streams.
-
Asynchronous execution is typically used for running models with animation.
The following code demonstrates running the engine in normal asynchronous mode with time scale of 10:
Engine engine = new Engine();
engine.setTemporal(LocalDateTime.of(2022, 1, 1, 0, 0), ChronoUnit.DAYS);
engine.scheduleAbsolute(10, () -> System.out.println("Event 1 is executed at time " + engine.time()));
engine.scheduleAbsolute(20, () -> System.out.println("Event 2 is executed at time " + engine.time()));
engine.scheduleStop(30, "Stop");
engine.setTimeScale(10); // 10 simulation time units (days in this case) will be simulated in 1 real time second
engine.run(); // Run the engine asynchronously and return immediately
// At this point, the engine is running in some other thread.
// The state of the model can be visualized with the certain frequency
Multiple engines can be used inside one program and even run in parallel. The following code runs 10 engines inside a parallel stream:
// Create the stream with 10 elements
IntStream.range(0, 10)
// Make it parallel
.parallel().mapToObj(i -> {
// For each stream element, create an engine
Engine engine = new Engine();
engine.setFastMode(true);
// Schedule some meaningful events that change the models' states here
engine.scheduleStop(10, "Stop");
return engine;
// This call will run each engine synchronously and return after the engine is stopped
}).forEach(e -> e.run(true));
6. Engine life cycle
The engine can be either running or stopped.
The isRunning
method can be used to understand current state of engine.
Engine can be stopped by calling stop
method and then re-run by calling one of the overloads of run
method.
The engine can be stopped and resumed infinite number of times.
Time scale and normal/fast mode can be changed both when engine is running and when it is stopped.
It is possible to understand whether the engine is running in fast mode by calling isRunningFast
method.
The engine can be reset back to its initial state by calling the reset
method.
7. Reusing the engine instance
The same instance of engine can be reused by calling reset
method to start the simulation over again:
Engine engine = new Engine();
engine.scheduleStop(10, "Stop");
// Run the engine synchronously and return after the engine is stopped
engine.run(true);
// At this point, the engine is stopped at the model time 10
engine.reset();
// At this point, the engine is reset and ready to run from model time 0
engine.scheduleStop(10, "Stop");
engine.run(true);
After reset, all scheduled events are deleted and the model time is set back to zero. Reusing engine instance is useful in GUI applications that have references to the only engine responsible for running models with animation.