Simulating a Single Server Queuing System with Python

Paul Rivera
Analytics Vidhya
Published in
6 min readSep 28, 2020

Definitions and objective

In this post, I will show a discrete event simulation on a single-server queuing system using the numpy and pandas libraries from python. The objective of running a simulation is to measure the performance of the system a priori and observe its behavior under predefined situations.

The elements of the system are simply the customers and the server and both can be persons or objects:

  • Customer: Receives the service
  • Server: Provides the service

In this queuing system, one only server caters the arriving customers, which after being served leave the system. For the sake of simplicity we’ll assume that customers arrive at intervals following a uniform distribution, and the service time by the server also follow a uniform distribution.

Next arrival time is generated each time a customer arrives, meaning that when a customer arrives, we random sample inter-arrival time and compute the arrival time of the next customer. Similarly, when the customer goes to the server, we random sample the service time and compute the departure time of that customer. There will be times that customers arrive when there is still a customer being served, so they’d have to wait in the queue until the current customer departs and server is available. The flowchar below explains this in more details.

Model

We will use the numpy and pandas libraries to create the system. The random generated inter-arrival and service times are obtained with the numpy.random.uniform() function, which takes the minimum and maximum values of the uniform distribution as arguments.

Fist we import the needed libraries and define the parameters of the problem: We assume inter-arrival and service time follow a uniform distribution, with maximum values of 5 and 6 minutes, for inter-arrival and service times respectively. Similarly, we assume minimum values of 3 and 2 minutes for inter-arrival and service times respectively. For this exercise, we set the number of events to simulate to 30.

Then, the system in initialized at dummy event zero, which generates the first arrival time with the numpy.random.uniform() function. By default, the first event is an arrival while the status of the server at first arrival time is idle and queue is zero. Event zero is completed and event counter is increased (to 1).

Each event details are added to a data frame so at the end we can have the events in a tabular manner for better understanding, so once the initial status of the server at event 1 is determined; we create this data frame with the values obtained previously.

Now, each arrival generates the next arrival time in the simulation and according to the queuing system (and common sense), if the queue is zero, the arriving customer will go straight to the server. Therefore, this arrival is going to generate two events: Departure time of this arriving customer and the arrival time of the next customer.

Below is the code that will run the rest of the events from event one and get the arrival and departure time of each customer according to the flow chart. This code is relatevely slow, relies too much on pandas and has redundant lines, but this way I find it easier to read and explain what each part does, especially for those like me who are not programmers.

At the end of all the iterations, the resulting data frame looks like this:

One caveat is that the loop stops at n_events, so the system stops arbitrarily when the number of events required is reached. This means that one customer might be already in the system but it will not be served when the simulation stops. For instance, below are the five last events of the run, and while customers 16 and 17 are expected to arrive before the run stops, these customer will not be served. Therefore, we can simply remove these two rows and keep only the customers that left the system.

Lastly, we create a summary of the event times per customer and compute their respective time spent in the queue, server and system: from where we can compute averages, etc. If needed.

The code above removes the customers that do not leave the system when simulation stops

Below are the five first rows of the resulting data frame from the code above with the times per customer:

Until this point, we made one simulation run, but in order to make a proper analysis and make decisions, multiple simulation runs are needed to see how the system behaves. We need to run the code above multiple times and change the seed of the pseudo-random number generator so we obtain different random numbers in each run. To achieve this we create a function that will run the code explained above and take as argument a “seed” integer, then run this function n times changing the “seed” each iteration so different random samples are obtained each iteration.

The function run_queue() in the code below runs the queuing system explained above and returns the average time in queue, server and system per customer. For instance, run_queue(seed = 1) returns the following:

We then create the another function that runs the run_queue() function n times and append the results of each run to a data frame to a see the results.

As metioned above, the code that runs the queue is relatevely slow, and it takes a bit more of a second per run, the 50 runs would take from 50 to 60 seconds approximately to complete.

The resulting data frame looks like the table below, and from here we can get summary statistics of the experiments and plot the results if needed.

Simulating different scenarios

As mentioned in the begining, the objectinve of running simulations is to observe the system behaviour in different scenarios and measure it’s performance. Luckyly the code above allows to do this easily by changing the maximum and minimum value of the distributions.

Below are the distributions of the service time and inter-arrival time of the original problem (left plot) with its respective average metrics after 50 runs (right plot).

Average time in queue = 1.713610
Average time in server = 3.914950
Average time in system = 5.628637

Let’s supposed we have no control over inter-arrival time of the customer but we can push the maximum service time from 6 minutes down to 5 and want to see how the system would perform. We simply change the value in the variable service_time_max to 5 and run the 50 simulations through the function run_experiments(). The new probability distribution and the resulting metrics are the following:

Average time in queue = 0.450533
Average time in server = 3.465720
Average time in system = 3.916413

The example above is only an illustration of the utility of simulations, and I do not intend to do any analysis on the properties of the system. There is a wide range of scenarios with which the system can be toyed around, from changing the minimum and maximum values of both distributions to change the distributions from uniform to normal or exponential and compare the magnitude of the changes.

Conclusion

To conclude, we built a simplistic queuing system with only one server, where arrival and service times follow a uniform distribution. In real life, obviously queuing systems are more complex as well as the probability distributions encountered. Nevertheless, this exercises is a good example of how once we have the code of a model built, it can be easily re-used to run experiments that can be useful to make decisions or foresee potential scenarios that might hinder the stability of the studied system.

Feel free to point out errors you may encounter or recommendations in the comments.

--

--