-
Notifications
You must be signed in to change notification settings - Fork 1
Simulation
Quartz simulations are runned using a Quartz::Simulation instance.
Initializing a simulation requires a model as an argument:
model = QueuingSystem.new("queuing_system")
simulation = Simulation.new(model)Simulating the model now simply requires calling the #simulate method:
simulation.simulateAs we didn't configured the simulation with a maximum duration or a custom termination condition, our model will be simulated indefinitely. More precisely, it runs as long as the model keeps scheduling events. It may stop on its own if the model plans an infinite duration as an internal event.
When you models keep running indefinitely, you typically want to run a simulation until a certain point in virtual time. This can be done by initializing the simulation with the duration named argument, which expects a 'Duration' or a 'TimePoint':
simulation = Simulation.new(model, duration: 100.time_units)For more complex situations, you can set a custom termination condition via the #termination_condition method, which yields the virtual time as well as the root model. It expects a block returning true if the simulation should stop, false otherwise.
This is particularly useful if you want to stop a simulation once a model or an observer reached a given state.
Below is such an example :
model = QueuingSystem.new("queuing_system")
simulation = Simulation.new(model)
max = TimePoint.new(1, Scale::KILO)
simulation.termination_condition do |time, model|
return true if time > max
queuing_system.servers.any? { |s| s.state.processing_time / time >= 1 }
end
simulation.simulateNote that setting a custom termination condition overrides the duration parameter provided it was set during the simulation initialization.
A pending event set is at the heart of discrete event simulations. It encompasses all future events scheduled to occur. As such, its role is to implement a planning strategy for all events to be evaluated.
Events must be dequeued in a strict order of precedence, according to their associated priority so that causality is guaranteed. This role is ensured in Quartz by a PriorityQueue, internally used by the EventSet data type.
Quartz implements several priority queues available for use in a simulation.
A default preferred priority queue implementation can be given to the constructor of a Simulation object via the scheduler named parameter:
simulation = Simulation.new(model, scheduler: :heap_set)The current available priority queues are:
- The
:binary_heapwhich maps to thePriorityQueuedata type and implements an array-based min-heap. It cache indices to avoid linear complexity during the lookup of arbitrary elements. It is currently the fall-back choice if no priority queue is provided. - The
:heap_set, which maps to theHeapSetdata type. It also implements a binary heap but it optimized for higher event collision rate. It is the recommended choice for simulations involving many simultaneous events, since instead of adding all events in the heap, simultaneous events are gathered as a set in the heap. - the
:fibonacci_heapmaps to theFibonacciHeapdata type, which implements a fibonacci heap as described by Fredman and Tarjan. While performs better theoretically, it is not the case with this implementation (binary heaps have better cache locality).
Previous versions of Quartz implemented more sophisticated priority queues (e.g. ladder queue or calendar queue). They are not available since the introduction of multiscale time in Quartz, but will hopefully be fixed soon.
Thanks to the way hierarchical models are simulated, it is possible to declare a preferred event set strategy to use at a particular level of the hierarchy. This can be useful to enhance performances of a subset of the models having a known particular event distribution.
Coupled models have a class property that can be set to change the preferred event set strategy to use:
MyCoupledModel.preferred_event_set = :heap_setYou can also define this at the class level using the event_set macro:
class MyCoupledModel < Quartz::CoupledModel
event_set fibonacci_heap
endThose preferences, if defined, will override the default priority queue given during the Simulation initialization.
While a model hierarchy can be leveraged to use distinct event set strategies as described earlier, it is usually a source of overhead. Furthermore, the overhead grows as the depth of the hierarchy increase.
Quartz provides a way to reduce the complexity of a model hierarchy by applying a model transformation called direct connection. The idea is to re-arrange couplings between models automatically to obtain a tree of depth one.
This feature is simply enabled by setting the maintain_hierarchy named argument to false during Simulation initialization:
simulation = Simulation.new(model, maintain_hierarchy: false)simulation = Simulation.new(model, run_validations: true)See the related page