Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Binder

We model the behaviour of the queue of customers waiting at the cashier desks in a supermarket.

Topics:

  • Discrete Event Simulation

Objectives:

  • How does the practical implementation of a discrete-event model work from scratch?

  • What are performance bottlenecks of the implementation and how can they be improved?

  • What is the impact of randomess on the results?

Motivation / System

A modeller needs to model the behaviour of a single supermarket queue with kk cashiers. He finds that a new customers arrives, on average, every μa\mu_a seconds and that it takes on cashier, on average, μs\mu_s seconds to serve a customer.

Conceptual Model

The modeller opts for a classic discrete event model and specifies the model using the Event Graph in Figure 1. The times tat_a and tst_s are hereby random variables with E(ta)=μaE(t_a)=\mu_a and E(ts)=μsE(t_s)=\mu_s.

Event graph representation of a multiple server queue.

Figure 1:Event graph representation of a multiple server queue.

Implemented Model

Below, we define a base-class for a generic discrete event model from scratch. The class is driven by event notices that extend the Event class. These object need to implememt the eval which executes what happens when they are executed.

State Variables and Parameters

The base class is now easily embedded to a queueing model, cosidering the three parameters k,μa,μsk,\mu_a,\mu_s and two state variables: a queue QQ and a number of available servers SS. Note how TT and XX are used for geneting simulation output.

At this point we also need to specify the distribution for tat_a and tat_a to sample new values. We leave this open by adding two provider functions as additional parameters. Both need to sample positive values with average 1. By default, the variables are sampled form an exponential distribution.

Events

In order to make the model executable, one needs to implement specific events. It quickly becomes apparent that there are exactly three events that change the behaviour of the simulation.

Arrival Event

On arrival of a new entity, the queue-length increases. If a server is available, the Start-Service event is instantaneously scheduled without further delay. Finally, a new Arrival event is scheduled to keep the simulation running.

Start-Service Event

When the service has started, an entity is taken from the queue and a server is set busy. The End-Service event is scheduled with corresponding service-time delay.

End-Service Event

When service has ended, a server is freed again. If entities are waiting, the next Start-Service event is scheduled.

Run Event

Finally, a run event needs to get the simulation started.

With the events properly defined, the first basis riund for the simulation can be executed. A plotting routine needs to be defined, to visualise the output. Executing the simulation several times also shows the impact of stochasticity on the outcome.

Note how the command np.random.seed(xxx) makes the result reproducible, regardless of randomness.

It is evident that statistics need to be carried out in order to evaluate the results. This, however, is not trivial since all runs have a different time vector. Therefore, we need to interpolate the results

Experimenting with Different Parameters

It stands to reason that the balance between μs/k\mu_s/k and μa\mu_a is of great importance for the stability of the system. If the number of servers kk is too small or the mean service time μs\mu_s too long, the system becomes unstable and the queue grows in the long term. Otherwise, the servers are always able to clear the queue.

Experimenting with Different Distributions

We furthermore explore the impact of different distributions on the outcomes, by varying the provider functions. We chose the following selection of positive distributions, each with mean equal to 1.

In particular the skewness and variance of the distribution have a negative impact on the average queue sizes. In the optimal case of a fully deterministic simulation, the queue even remains entirely stable. This can be stated as a general property of queuing systems:

if μsk=μa, then E(Q)<∞⇔V(ts)=V(ta)=0.\text{if }\frac{\mu_s}{k}=\mu_a\text{, then }E(Q)<\infty\Leftrightarrow V(t_s)=V(t_a)=0.

Performance

Even from this simple example we easily see that the performance of the discrete event simulation is a crucial factor for the usability of the simulation - nobody wants to wait ages for termination.

The bottleneck of discret event simulators is the way how event lists are kept sorted. In above implementation, the list is re-sorted whenever a new event is added. Although, Python’s list.sort is extremely optimised and fast, it causes an effort of roughly O(Nlog⁡(N))\mathcal{O}(N\log(N)) whenever a new event is sorted into the list, whereas NN is the length of the list (likely this actually goes down to O(N)\mathcal{O}(N), since all but the new element are already in the correct order).

Anyhow, there are more efficent ways to keep things sorted. One example is the use of heap- or tree-structures, in which every element is rather a leaf on a 2-dimensional tree rather than a part of a 1-dimensional list. Python’s heapq module offers easy-to-use methods to sort new elements into a tree with effort of only O(log⁡(N))O(\log(N)). While this also elevates the efforts for getting the next element out of the structure from O(1)O(1) (just pop from the list) to O(log⁡(N))O(\log(N)) is provdes a considerable performance boost.

The implementation below illustrates this.