LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_ggnm_diffusion.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_QSYS_GGNM_DIFFUSION_H
6#define LINE_API_QSYS_GGNM_DIFFUSION_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Diffusion approximation for the G/GI/n/m queue.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_ggnm_diffusion.m, cross-checked
14 * against jar/src/main/java/jline/api/qsys/Qsys_ggnm_diffusion.java.
15 *
16 * ONE DIFFUSION WITH TWO REGIONS. Below the staffing level the queue behaves
17 * like an infinite-server system, whose limit is NORMAL with variance-to-mean
18 * ratio the ASYMPTOTIC PEAKEDNESS
19 *
20 * z = 1 + (ca^2 - 1) omega_G, omega_G = int G^c(x)^2 dx / int G^c(x) dx
21 * (1.6)-(1.7)
22 *
23 * above it like a single-server queue, whose limit is EXPONENTIAL with
24 * variability v = (ca^2 + cs^2)/2 (3.7). The steady-state law is a normal piece
25 * spliced to an exponential piece and every measure is an integral of it (3.14):
26 *
27 * P(delay) = [1 + b Phi(b)/(phi(b)(1 - e^{-beta gamma/v}))]^-1, b = beta/sqrt(z)
28 * P(block) = f(gamma) v / sqrt(n) (7.5)
29 *
30 * with beta = sqrt(n)(1-rho) and gamma = m/sqrt(n).
31 *
32 * WHAT z SAYS. The service law enters the delay probability ONLY through
33 * omega_G: 1 for deterministic service, 1/2 for exponential, falling toward 0 as
34 * service gets more variable. At ca^2 = 1 the delay probability does not depend
35 * on the service law at all (z = 1), the long-standing M/GI/n-by-M/M/n
36 * approximation; away from ca^2 = 1 it does, and this is how much.
37 *
38 * At m = Inf this reduces to alpha(beta/sqrt(z)) with the Halfin-Whitt alpha,
39 * i.e. to `qsys_mmk_qed` when the service is exponential and ca^2 = 1.
40 *
41 * ARITHMETIC. erfc, exp and a quadrature: transcendental only.
42 *
43 * DIVERGENCE. The finite-waiting-room delay function is eq. (3.2) of the paper,
44 * whose printed form the available scan does not resolve. What is implemented is
45 * the unique form that (i) reduces to eq. (3.10) as gamma -> Inf and (ii)
46 * reproduces the exact M/M/n/m delay probability in the QED limit, which was
47 * checked numerically against the birth-death chain at n = 100, 400 and 1000.
48 *
49 * Reference: W. Whitt (2004). A diffusion approximation for the G/GI/n/m queue.
50 * Operations Research 52(6), 922-941.
51 */
52
53#include <algorithm>
54#include <cmath>
55#include <cstddef>
56#include <functional>
57#include <limits>
58
60#include "line/num/number.h"
61#include "line/util/error.h"
62
63namespace line {
64namespace qsys {
65
66/** Steady-state measures of the G/GI/n/m diffusion approximation. */
67template <class T>
69 T beta; ///< the QED server slack sqrt(n)(1-rho)
70 T gamma; ///< the scaled waiting room m/sqrt(n)
71 T peakedness; ///< z, the asymptotic peakedness
72 T peakednessWeight; ///< omega_G
73 T variability; ///< v = (ca^2+cs^2)/2
74 T probDelay; ///< P(an arrival waits)
75 T probBlock; ///< P(an arrival is blocked)
76 T meanQueueLength; ///< mean number waiting
77 T meanNumber; ///< mean number in system
78 T meanWait; ///< mean wait of an admitted arrival
79 T utilization; ///< min(rho,1)
80 T throughput; ///< lambda(1-P(block))
81 T trafficIntensity; ///< rho = lambda/(n mu)
82};
83
84namespace detail {
85
86/** Standard normal density. */
87template <class T>
88T ggnm_phi(const T& x) {
89 using std::exp;
90 using std::sqrt;
91 return exp(-x * x / num_traits<T>::from_int(2)) /
93}
94
95/** Standard normal cdf, through erfc. */
96template <class T>
97T ggnm_Phi(const T& x) {
98 using std::erfc;
99 using std::sqrt;
100 return erfc(-x / sqrt(num_traits<T>::from_int(2))) / num_traits<T>::from_int(2);
101}
102
103/**
104 * omega_G of eq. (1.7) by Simpson on a grid cut where the ccdf is negligible.
105 * The denominator is E[S], so only the numerator is integrated.
106 */
107template <class T, class Ccdf>
108T ggnm_omega(Ccdf&& ccdf, const T& ES, double tol, std::size_t panels) {
109 T hi = num_traits<T>::from_int(1);
110 const T tolT = num_traits<T>::from_double(tol);
111 while (ccdf(hi) > tolT) {
112 hi *= num_traits<T>::from_int(2);
113 if (hi > num_traits<T>::from_double(1e12))
114 throw InputError("qsys_ggnm_diffusion: the service ccdf does not decay, so its "
115 "peakedness is undefined");
116 }
117 const T h = hi / num_traits<T>::from_int(static_cast<long>(panels));
118 const T g0 = ccdf(num_traits<T>::from_int(0));
119 const T gn = ccdf(hi);
120 T sum = g0 * g0 + gn * gn;
121 for (std::size_t i = 1; i < panels; ++i) {
122 const T g = ccdf(T(num_traits<T>::from_int(static_cast<long>(i)) * h));
123 sum += num_traits<T>::from_int(i % 2 == 1 ? 4 : 2) * g * g;
124 }
125 return (h / num_traits<T>::from_int(3) * sum) / ES;
126}
127
128} // namespace detail
129
130/**
131 * @brief Diffusion approximation for the G/GI/n/m queue.
132 *
133 * @param lambda arrival rate
134 * @param mu service rate of one server
135 * @param n number of servers, n >= 1
136 * @param m extra waiting spaces; infinity for an unbounded queue
137 * @param ca coefficient of variation of the interarrival time
138 * @param cs coefficient of variation of the service time
139 * @param serviceCcdf G^c(x) = P(S > x); empty takes the exponential of rate mu
140 * @param tol service-tail cut for the peakedness integral
141 * @param panels Simpson panels for it
142 */
143template <class T>
145 const T& lambda, const T& mu, unsigned n, double m, const T& ca, const T& cs,
146 const std::function<T(const T&)>& serviceCcdf = std::function<T(const T&)>(),
147 double tol = 1e-12, std::size_t panels = 4000) {
149 "qsys_ggnm_diffusion needs erfc, exp and a quadrature");
150 using std::exp;
151 using std::expm1;
152 using std::sqrt;
153 const T zero = num_traits<T>::from_int(0);
154 const T one = num_traits<T>::from_int(1);
155 const T two = num_traits<T>::from_int(2);
156 if (lambda <= zero || mu <= zero)
157 throw InputError("qsys_ggnm_diffusion: the arrival and service rates must be positive");
158 if (n < 1) throw InputError("qsys_ggnm_diffusion: the number of servers n must be at least 1");
159 if (m < 0)
160 throw InputError("qsys_ggnm_diffusion: the number of extra waiting spaces m must be "
161 "non-negative");
162
163 const T nT = num_traits<T>::from_int(static_cast<long>(n));
164 const T ca2 = ca * ca;
165 const T cs2 = cs * cs;
166 const T ES = one / mu;
167 const T rho = lambda / (nT * mu);
168 const T beta = sqrt(nT) * (one - rho); // eq. (0.1)
169 const bool finiteRoom = std::isfinite(m);
170 const T gamma = finiteRoom ? T(num_traits<T>::from_double(m) / sqrt(nT))
171 : num_traits<T>::from_double(std::numeric_limits<double>::infinity());
172
173 const T omega = serviceCcdf ? detail::ggnm_omega<T>(serviceCcdf, ES, tol, panels)
175 const T z = one + (ca2 - one) * omega; // eq. (1.6)
176 if (z <= zero)
177 throw InputError("qsys_ggnm_diffusion: the asymptotic peakedness came out non-positive; "
178 "check ca and the service ccdf");
179 const T v = (ca2 + cs2) / two; // eq. (3.7), weight w = 1
180 const T b = beta / sqrt(z);
181 const T r = beta / v;
182
183 // The tail factor is negative together with r when the queue is overloaded,
184 // so the ratio below stays positive on both sides of beta = 0.
185 const T tail = finiteRoom ? T(-expm1(-r * gamma)) : one;
186 T alpha, meanAbove, densityAtTop;
187 if (num_abs(r) < num_traits<T>::from_double(1e-14)) {
188 // beta = 0: the exponential piece degenerates to a uniform on [0,gamma].
189 if (!finiteRoom)
190 throw InputError("qsys_ggnm_diffusion: with beta = 0 the queue needs a finite waiting "
191 "room to be stable");
192 alpha = one / (one + detail::ggnm_Phi(b) / (detail::ggnm_phi(b) * gamma / sqrt(z)));
193 meanAbove = gamma / two;
194 densityAtTop = alpha / gamma;
195 } else {
196 alpha = one / (one + b * detail::ggnm_Phi(b) / (detail::ggnm_phi(b) * tail));
197 if (finiteRoom) {
198 const T e = exp(-r * gamma);
199 meanAbove = (one / r - (gamma + one / r) * e) / tail;
200 densityAtTop = alpha * r * e / tail;
201 } else {
202 meanAbove = one / r;
203 densityAtTop = zero;
204 }
205 }
206
207 // Mean of the normal piece, N(-beta, z) conditioned below 0.
208 const T meanBelow = -beta - sqrt(z) * detail::ggnm_phi(b) / detail::ggnm_Phi(b);
209 const T meanScaled = (one - alpha) * meanBelow + alpha * meanAbove;
210
212 res.beta = beta;
213 res.gamma = gamma;
214 res.peakedness = z;
215 res.peakednessWeight = omega;
216 res.variability = v;
217 res.probDelay = alpha;
218 // Eq. (7.5): the loss rate at the upper boundary over the arrival rate is
219 // the density there times v / sqrt(n).
220 res.probBlock = zero;
221 if (finiteRoom) {
222 T pb = densityAtTop * v / sqrt(nT);
223 if (pb < zero) pb = zero;
224 if (pb > one) pb = one;
225 res.probBlock = pb;
226 }
227 res.meanQueueLength = sqrt(nT) * alpha * meanAbove;
228 res.meanNumber = nT + sqrt(nT) * meanScaled;
229 res.throughput = lambda * (one - res.probBlock);
230 res.meanWait = res.throughput > zero ? T(res.meanQueueLength / res.throughput) : zero;
231 res.utilization = detail::num_min(rho, one);
232 res.trafficIntensity = rho;
233 return res;
234}
235
236} // namespace qsys
237} // namespace line
238
239#endif // LINE_API_QSYS_GGNM_DIFFUSION_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
QsysGgnmResult< T > qsys_ggnm_diffusion(const T &lambda, const T &mu, unsigned n, double m, const T &ca, const T &cs, const std::function< T(const T &)> &serviceCcdf=std::function< T(const T &)>(), double tol=1e-12, std::size_t panels=4000)
Diffusion approximation for the G/GI/n/m queue.
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Shared return type and arithmetic helpers for the templated qsys port.
Steady-state measures of the G/GI/n/m diffusion approximation.
T trafficIntensity
rho = lambda/(n mu)
T meanWait
mean wait of an admitted arrival
T peakedness
z, the asymptotic peakedness
T meanQueueLength
mean number waiting
T probBlock
P(an arrival is blocked).
T probDelay
P(an arrival waits).
T gamma
the scaled waiting room m/sqrt(n)
T beta
the QED server slack sqrt(n)(1-rho)
T meanNumber
mean number in system