Skip to content

Simulation

Romain Franceschini edited this page Apr 27, 2020 · 3 revisions

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.simulate

As 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.

Termination condition

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.simulate

Note that setting a custom termination condition overrides the duration parameter provided it was set during the simulation initialization.

Configuring the event set

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)

Available priority queues

The current available priority queues are:

  • The :binary_heap which maps to the PriorityQueue data 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 the HeapSet data 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_heap maps to the FibonacciHeap data 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.

Coupled models preferred priority queue

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_set

You can also define this at the class level using the event_set macro:

class MyCoupledModel < Quartz::CoupledModel
  event_set fibonacci_heap
end

Those preferences, if defined, will override the default priority queue given during the Simulation initialization.

Enabling direct connection

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)

Enabling runtime checking

simulation = Simulation.new(model, run_validations: true)

See the related page


Next: features

Clone this wiki locally