Maven artifactId: rinsim-core
The core module contains the core simulation components. The core module is the entry point for getting to know the basics of RinSim. It also contains the most used models.
There are generally two classes that can be used to configure RinSim:
- rinsim.core.Simulator, a simple and direct way to interact with the simulator. This is the recommended way to learn how RinSim works. Explained in this section.
- rinsim.experiment.Experiment, a more advanced interface that encapsulates Simulator, it is specifically designed for (scientific) experiments. Explained in the Experiment section.
core
The simulator is in essence a collection of models. When configuring the simulator, using Simulator.builder()
, the desired models can be added. For ease of use, the simulator contains a TimeModel
and a RandomModel
by default. The configuration phase is concluded by calling build()
of the Simulator.Builder
class which returns a Simulator
instance.
Simulator sim = Simulator.builder()
.addModel(...)
// add more models
.build()
The Simulator
object provides several methods for controlling the simulation. It also has a register()
method that allows to register simulation entities into the simulator. Typically, a simulation entity interacts with one or more models that are configured in the simulator. A common way for a simulation entity to interact with a model is to implement an associated type of a model.
model
A model in RinSim is a software entity that models something, usually a real-world concept (e.g. traveling over a road in the RoadModel
), but a model can also be a simulation utility (e.g. generating random numbers in the RandomModel
). A Model has a generic type T
that is called the associated type (or supported type) of a Model
. A model that is part of a simulator, automatically receives all instances of its associated type that are added to the simulator. Using this mechanism it is also possible to implement dependency injection of a model into its associated type. An example:
interface ExampleType {
void injectExampleModel(ExampleModel m)
}
class ExampleModel extends AbstractModel<ExampleType> {
public boolean register(ExampleType element){
// dependency injection:
element.injectExampleModel(this);
}
.. // other required methods
}
When this ExampleModel
is part of the Simulator
any object that implements ExampleType
that is added to the simulator via Simulator.register(..)
will receive the reference to ExampleModel
via the injectExampleModel(..)
method. Using this mechanism, RinSim achieves a modular and configurable design that enables reuse of models.
Besides the associated type, there are two other relationships that a Model
can have: it can provide types and it can have dependencies on types. A model that has declared dependencies can only be used in a simulator with instances of those types available. For resolving dependencies, the simulator uses the provided types of a model.
The header comment of each Model
implementation should contain the following section:
/**
* <p>
* <b>Model properties</b>
* <ul>
* <li><i>Associated type:</i> {@link ExampleType}.</li>
* <li><i>Provides:</i> nothing.</li>
* <li><i>Dependencies:</i> none.</li>
* </ul>
*/
model.time
RinSim is a discrete time simulator, this means that time is sliced into fixed-length intervals called ‘ticks’. TimeModel is the model that is responsible for the advancing of time. Its associated type is TickListener. On every tick, all registered TickListener
s will be called in the order in which they are registered in the model. TickListener
s can be registered by calling the register(..) method of the Simulator The figure below shows the order of execution of the tick(..)
and afterTick(..)
methods of the registered TickListener
s.
Implementing a TickListener
allows you to receive ticks and receive a TimeLapse reference, e.g.:
class MyTickListener implements TickListener {
public void tick(TimeLapse timeLapse){
}
public void afterTick(TimeLapse timeLapse){
}
}
A TimeLapse
is a consumable interval of time: [start, end)
. The difference between start
and end
is the tick length, which is constant within a simulation (it can be configured). The TimeLapse
can be consumed, which means that it can be used as credit to spend on actions that require time. For example, moving over the RoadModel requires time and can therefore only be done from within a tick(..)
method, using the available time inside the TimeLapse
. Since the travel distance is proportional to the amount of time available (among others), this mechanism allows RinSim to enforce time consistency for time dependent actions. Note that in the afterTick(..)
method, the TimeLapse
is always empty (can not be consumed), regardless of whether it was consumed in the tick(..)
method.
TimeLapse
as it is mutable. Internally, there is just a single instance which is used for all TickListener
s during the entire simulation.Note that adding and removing a TickListener
from within a tick(..)
or afterTick(..)
only has effect after all tick(..)
or afterTick(..)
invocations have been executed, respectively. For example, if you remove TickListener
A from within a tick(..)
(could be from within the tick(..)
of A itself), the last call that A receives is tick(..)
, it will not receive its afterTick(..)
.
configuration
Since the TimeModel
is such a central component to the simulator, it is added by default as a model in Simulator
. This means that it can be configured via two different ways. Using the default TimeModel
, via the Simulator
:
Simulator.builder()
.setTimeUnit(..) // allows to change the time unit e.g.:
// SI.MILLI(SI.SECOND) or NonSI.MINUTE.
.setTickLength(..) // allows to change the length of the tick
Or by creating and adding the TimeModel
manually:
Simulator.builder()
.addModel(
TimeModel.builder()
.setTimeUnit(..).setTickLength(..) // same as above
.withRealTime() // only when needed, see real-time section below
)
real-time
Parts of the following text have been adapted from van Lon, R.R.S. & Holvoet, T. (2017) (section 3.1).
The standard Java virtual machine (JVM) has no built-in support for real-time execution. However, with a careful software design, the standard JVM can be used to obtain soft real-time behavior. Soft real-time, as opposed to hard real-time, allows occasional deviations from the desired execution timing.
When simulating without real-time constraints, the TimeModel
will compute all ticks as fast as possible. In a real-time simulator the interval between the start of two ticks should be the tick length (e.g. 250 ms). Since the JVM doesn’t allow precise control over the timings of threads it is generally impossible to guarantee hard real-time constraints. In real-time mode, RinSim uses a dedicated thread for executing the ticks. If computations need to be done that are expected to last longer than a tick, they must be done in a different thread. RinSim provides a separate model for running solvers in a separate thread called RtSolverModel
(see this section for more information). This minimizes interference of RtSolverModel
computations with the advancing of time in the simulated world as executed by the TimeModel
. Additionally, the processor affinity of the threads are set at the operating system level. Setting the processor affinity to a Java thread instructs the operating system to use one processor exclusively for executing that thread. In practice, the actual scheduling of threads on processors depends on the number of available processors and the operating system. Informal tests on a multi core processor running Linux have shown that different threads are indeed run on different processor cores, exactly as specified. By setting the processor affinity of the TimeModel
thread, deviations from the desired execution timing are minimized.
TimeModel
has an additional provided type: RealtimeClockController.model.road
The road
package contains the RoadModel
, a model that simulates traveling over roads. The associated type is RoadUser, a RoadUser
can be added to the RoadModel
at a certain position but it cannot move. MovingRoadUsers (a subtype of RoadUser
) can move over the RoadModel
.
There are five different RoadModel
variants:
Name | Type | Example |
---|---|---|
RoadModel | Super interface. | NA |
PlaneRoadModel | Based on a Euclidean plane. | Simple example |
CollisionPlaneRoadModel | Based on a Euclidean plane and has basic collision detection. | UAV example |
GraphRoadModel | Uses a graph-based road layout. | Taxi example |
DynamicGraphRoadModel | Uses a modifiable graph-based road layout. | AGV example |
CollisionGraphRoadModel | Uses a modifiable graph-based road layout and has basic collision detection. | AGV example |
RoadModel
s can be configured via RoadModelBuilders, for example:
RoadModelBuilders.plane(); // constructs PlaneRoadModel
RoadModelBuilders.plane()
.withCollisionAvoidance(); // constructs CollisionPlaneRoadModel
RoadModelBuilders.staticGraph(g); // constructs GraphRoadModel
RoadModelBuilders.dynamicGraph(g); // constructs DynamicGraphRoadModel
RoadModelBuilders.dynamicGraph(g)
.withCollisionAvoidance(); // constructs CollisionGraphRoadModel
For the graph based RoadModel
s, an graph is needed, see the geom module for more information about graphs. For all road models it is also possible to change the default distance unit (km) and default speed unit (kmh):
RoadModelBuilders.plane()
.withDistanceUnit(SI.KILOMETER) // or e.g.: SI.METER, NonSI.MILE
.withSpeedUnit(NonSI.KILOMETERS_PER_HOUR); // or e.g.: SI.METERS_PER_SECOND
model.comm
The CommModel allows simulating message-based communication between objects in the simulator.
CommModel | |
Associated type: | CommUser |
Provides: | CommModel |
Dependency: | RandomProvider (see the section about rand) |
Classes that implement CommUser can construct a CommDevice
that can be used to send and receive messages between CommUser
s. Both broadcasting as well as direct messaging is supported.
The actual sending of messages is done in the afterTick(..)
of the model, this means that recipients will be able to see new messages in the afterTick(..)
.
CommModel
can be used.model.pdp
The PDPModel is a model that simulates the pickup and delivery of parcels.
DefaultDPModel | |
Associated type: | PDPObject. Subclasses: Vehicle, Parcel, and Depot. |
Provides: | PDPModel |
Dependency: | RoadModel (see the section about road) |
There are three important types in this model:
Vehicle
(abstract), needs to be subclassed in order to add moving logic. A vehicle is both aTickListener
as well as aMovingRoadUser
.Parcel
, the object to be transported. Can be used directly or can be subclassed for customization. Instances can be obtained viaParcel.builder(..)
.Depot
, basic implementation, it is nothing more than a marker on the map. It is typically used to indicate the starting point of vehicles. Can be extended to add constraints.
PDPModel
can be used.model.rand
Random numbers are often needed in simulations. Scientific experiments require reproducible simulations, therefore the RandomModel provides a systematic way for using random numbers in RinSim.
RandomModel | |
Associated type: | RandomUser |
Provides: | RandomProvider |
Dependency: | none |
There are two ways in which the RandomModel
can be configured. Either in the simulator:
Simulator.builder()
.setRandomSeed(..)
.setRandomGenerator(..)
Or by configuring and adding the model manually:
Simulator.builder()
.addModel(
RandomModel.builder()
.withSeed(..)
.withRandomGenerator(..)
)
util
This is a package that contains several utilities that have proven very useful but are too small to deserve their own package. There are two notable classes here:
- StochasticSupplier this is a generic factory type that requires a random number as input to instantiate an object. Also check StochasticSuppliers for many standard implementations.
- TimeWindow, a value object representing an interval.