14.5 Queues in software

Software 14.5.1 (M/M/1). The M/M/1 formulas for \(\lambda = 4\), \(\mu = 5\), and a simulation of \(400\,000\) customers by Lindley’s recursion \(W_{n + 1} = \max (0, W_n + S_n - A_{n + 1})\), which gives the wait of each customer from that of the one before:

>>> import numpy as np
>>> from math import factorial
>>> lam, mu = 4.0, 5.0                     # arrivals and services an hour
>>> rho = lam / mu
>>> L, W = rho / (1 - rho), 1 / (mu - lam)
>>> Wq = rho / (mu - lam); Lq = lam * Wq
>>> rho, L, W, Wq, Lq                      # Little: L = lam W, Lq = lam Wq
(0.8, 4.000000000000001, 1.0, 0.8, 3.2)
>>> rng = np.random.default_rng(0)
>>> def fifo_waits(inter, serv):           # Lindley's recursion for the waits
...     w = np.zeros(len(serv))
...     for n in range(1, len(serv)):
...         w[n] = max(0.0, w[n - 1] + serv[n - 1] - inter[n])
...     return w
...
>>> n = 400000
>>> inter = rng.exponential(1 / lam, n); serv = rng.exponential(1 / mu, n)
>>> wq = fifo_waits(inter, serv)[n // 10:]   # discard the warm-up
>>> round(wq.mean(), 4), round((wq > 0).mean(), 4)   # Wq, P(wait) = rho
(0.7943, 0.7987)

Reading the output. \(\rho = 0.8\), \(L = 4\), \(W = 1\), \(W_q = 0.8\) hours, \(L_q = 3.2\). The simulated mean wait, after discarding the first tenth as warm-up, is \(0.794\) hours, and the fraction of customers who wait is \(0.799\), close to \(\rho \). The simulation needs many customers because successive waits are strongly correlated when \(\rho \) is high.

MATLAB/Octave: the same recursion in a loop.

Software 14.5.2 (Erlang C and pooling). The Erlang C formula, the probability of waiting and mean wait for \(3\), \(4\) and \(5\) tellers in Example 14.2.2, and the wait with three separate M/M/1 queues against one pooled queue:

>>> def erlang_c(c, lam, mu):              # P(an arrival waits) in M/M/c
...     a = lam / mu; r = a / c
...     top = a**c / factorial(c) / (1 - r)
...     return top / (sum(a**k / factorial(k) for k in range(c)) + top)
...
>>> lam, mu = 24.0, 10.0                   # a bank: 24 customers an hour
>>> for c in (3, 4, 5):
...     pw = erlang_c(c, lam, mu); wq = pw / (c * mu - lam)
...     print(c, round(pw, 4), round(60 * wq, 2), 'min')
...
3 0.6472 6.47 min
4 0.287 1.08 min
5 0.1135 0.26 min
>>> pw1 = erlang_c(1, 8.0, 10.0)           # three separate tellers, 8 each
>>> pooled = erlang_c(3, 24, 10)           # one queue for all three
>>> round(60 * pw1 / (10 - 8), 2), round(60 * pooled / (30 - 24), 2)
(24.0, 6.47)

Reading the output. With \(3\), \(4\), \(5\) tellers the probability of waiting is \(0.647\), \(0.287\), \(0.114\) and the mean wait \(6.47\), \(1.08\), \(0.26\) minutes; the fourth teller cuts the wait by a factor of six. Three separate queues give a mean wait of \(24\) minutes against \(6.47\) for one shared queue with the same tellers.

MATLAB/Octave: the same function, with factorial.

Software 14.5.3 (The Pollaczek–Khinchine formula). \(L_q\) from the formula and from simulation for one server at \(\rho = 0.8\) with deterministic, exponential and gamma service times of squared coefficient of variation \(0\), \(1\) and \(4\):

>>> lam, mu = 4.0, 5.0; rho = lam / mu
>>> def pk_lq(cv2):                  # Pollaczek-Khinchine, Var S = cv2 / mu^2
...     return (lam**2 * cv2 / mu**2 + rho**2) / (2 * (1 - rho))
...
>>> laws = (('M/D/1', 0.0, lambda k: np.full(k, 1 / mu)),
...         ('M/M/1', 1.0, lambda k: rng.exponential(1 / mu, k)),
...         ('M/G/1, cv2 = 4', 4.0, lambda k: rng.gamma(0.25, 4 / mu, k)))
>>> for name, cv2, gen in laws:
...     s = gen(n); wq = fifo_waits(rng.exponential(1 / lam, n), s)[n // 10:]
...     print(f"{name:15s} Lq: formula {pk_lq(cv2):.3f}, "
...           f"simulated {lam * wq.mean():.3f}")
...
M/D/1           Lq: formula 1.600, simulated 1.576
M/M/1           Lq: formula 3.200, simulated 3.204
M/G/1, cv2 = 4  Lq: formula 8.000, simulated 7.702

Reading the output. The formula gives \(1.6\), \(3.2\) and \(8.0\), in the ratio \(1 : 2 : 5\) of \((1 + c_s^2)/2\), and the simulations agree, the last (\(7.70\)) less closely: with very variable services, rare long jobs dominate the average, and the simulation converges slowly.

MATLAB/Octave: gamrnd(0.25, 4/mu, 1, n) for the variable service times.

Questions on this section

Stuck on something here? Ask below and it stays attached to this topic.