LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_ggingi_tga.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_GGINGI_TGA_H
6#define LINE_API_QSYS_GGINGI_TGA_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Truncated Gaussian approximation (TGA-G) for the G/GI/n+GI queue.
12 *
13 * Templated port of matlab/src/api/qsys/qsys_ggingi_tga.m, cross-checked against
14 * jar/src/main/java/jline/api/qsys/Qsys_ggingi_tga.java.
15 *
16 * A FLUID CENTRE PLUS A GAUSSIAN FLUCTUATION, TRUNCATED. In the
17 * efficiency-driven regime (rho > 1 fixed as n grows) the fluid limit gives the
18 * centre -- every server busy, w = F^-1(1-1/rho), Q = lambda int_0^w F^c -- and
19 * the many-server CLT gives a normal fluctuation of order sqrt(n) around it:
20 *
21 * sigma_W^2 = [(ca^2-1) + (cs+1)rho] / (2 mu rho^2 f(w)) (24)
22 * sigma_X^2 = mu^2 sigma_W^2
23 * + lambda int_0^w F^c(u)[1 + (ca^2-1)F^c(u)] du (11)
24 * W = (w + sigma_W Z/sqrt(n))^+, Q = (nQ + sqrt(n) sigma_X Z)^+ (18)-(20)
25 *
26 * Adding fluid and fluctuation directly can produce negative queues and waits,
27 * so BOTH ARE TRUNCATED at zero; that truncation is what makes the formulas
28 * usable down to moderate overload, reportedly rho > 1.02.
29 *
30 * The three sources of variability enter separately, which is what lets the
31 * exponential-service formula be generalized: the service law appears only as
32 * the factor (cs+1)rho, which is 2rho at cs = 1.
33 *
34 * ARITHMETIC. erfc, exp and quadrature: transcendental only.
35 *
36 * Reference: Y. Liu, W. Whitt, Y. Yu (2016). Approximations for heavily-loaded
37 * G/GI/n+GI queues. Naval Research Logistics 63(3), 187-217.
38 */
39
40#include <algorithm>
41#include <cmath>
42#include <cstddef>
43#include <functional>
44#include <string>
45
47#include "line/num/number.h"
48#include "line/util/error.h"
49
50namespace line {
51namespace qsys {
52
53/** Steady-state measures of the G/GI/n+GI truncated Gaussian approximation. */
54template <class T>
56 std::string regime; ///< "underloaded" or "overloaded"
57 T trafficIntensity; ///< rho = lambda/(n mu)
58 T fluidWait; ///< w, the fluid waiting time
59 T fluidQueueLength; ///< the fluid queue content
60 T meanWait; ///< E[W] after truncation
61 T varWait; ///< Var[W]
62 T meanQueueLength; ///< E[Q] after truncation
63 T varQueueLength; ///< Var[Q]
65 T meanNumber; ///< E[X] = E[B] + E[Q]
66 T probDelay; ///< P(W > 0)
67 T probAbandon; ///< P(patience < wait)
68 T sigmaW; ///< the CLT scale of the wait
69 T sigmaX; ///< the CLT scale of the content
70};
71
72namespace detail {
73
74/** Standard normal density. */
75template <class T>
76T tga_phi(const T& x) {
77 using std::exp;
78 using std::sqrt;
79 return exp(-x * x / num_traits<T>::from_int(2)) /
81}
82
83/** Standard normal cdf, through erfc. */
84template <class T>
85T tga_Phi(const T& x) {
86 using std::erfc;
87 using std::sqrt;
88 return erfc(-x / sqrt(num_traits<T>::from_int(2))) / num_traits<T>::from_int(2);
89}
90
91/** Composite Simpson rule on a fixed even panel count. */
92template <class T, class Fn>
93T tga_simpson(Fn&& f, const T& a, const T& b, std::size_t m = 2000) {
94 const T zero = num_traits<T>::from_int(0);
95 if (b <= a) return zero;
96 const T h = (b - a) / num_traits<T>::from_int(static_cast<long>(m));
97 T sum = f(a) + f(b);
98 for (std::size_t i = 1; i < m; ++i)
99 sum += num_traits<T>::from_int(i % 2 == 1 ? 4 : 2) *
100 f(T(a + num_traits<T>::from_int(static_cast<long>(i)) * h));
101 return h / num_traits<T>::from_int(3) * sum;
102}
103
104/** Smallest w with F^c(w) = target, by doubling then bisection. */
105template <class T, class Ccdf>
106T tga_inv_ccdf(Ccdf&& ccdf, const T& target) {
107 const T two = num_traits<T>::from_int(2);
108 T lo = num_traits<T>::from_int(0), hi = num_traits<T>::from_int(1);
109 while (ccdf(hi) > target) {
110 hi *= two;
111 if (hi > num_traits<T>::from_double(1e12))
112 throw InputError("qsys_ggingi_tga: the patience ccdf never falls to 1/rho, so the "
113 "overloaded model has no fluid equilibrium");
114 }
115 const T one = num_traits<T>::from_int(1);
116 const T tol = num_traits<T>::from_double(1e-12);
117 while (hi - lo > tol * (hi > one ? hi : one)) {
118 const T mid = (lo + hi) / two;
119 if (ccdf(mid) > target) {
120 lo = mid;
121 } else {
122 hi = mid;
123 }
124 }
125 return (lo + hi) / two;
126}
127
128/** Mean and variance of max(Z,-a) for a standard normal Z. */
129template <class T>
130void tga_trunc_moments(const T& a, T& m1, T& v) {
131 const T one = num_traits<T>::from_int(1);
132 const T Pa = tga_Phi(a);
133 const T pa = tga_phi(a);
134 m1 = pa - a * (one - Pa);
135 const T m2 = Pa - a * pa + a * a * (one - Pa);
136 v = m2 - m1 * m1;
137 if (v < num_traits<T>::from_int(0)) v = num_traits<T>::from_int(0);
138}
139
140} // namespace detail
141
142/**
143 * @brief Truncated Gaussian approximation (TGA-G) for the G/GI/n+GI queue.
144 *
145 * @param lambda arrival rate
146 * @param mu service rate of one server
147 * @param n number of servers
148 * @param ca coefficient of variation of the interarrival time
149 * @param cs coefficient of variation of the service time
150 * @param patienceCcdf F^c(x) = P(patience > x)
151 * @param patiencePdf the patience density; empty differences the ccdf
152 * @param serviceCcdf G^c(x), used only in the underloaded branch
153 */
154template <class T>
156 const T& lambda, const T& mu, unsigned n, const T& ca, const T& cs,
157 const std::function<T(const T&)>& patienceCcdf,
158 const std::function<T(const T&)>& patiencePdf = std::function<T(const T&)>(),
159 const std::function<T(const T&)>& serviceCcdf = std::function<T(const T&)>()) {
160 static_assert(num_traits<T>::has_transcendental, "qsys_ggingi_tga needs erfc and exp");
161 using std::sqrt;
162 const T zero = num_traits<T>::from_int(0);
163 const T one = num_traits<T>::from_int(1);
164 const T two = num_traits<T>::from_int(2);
165 if (lambda <= zero || mu <= zero)
166 throw InputError("qsys_ggingi_tga: the arrival and service rates must be positive");
167 if (n < 1) throw InputError("qsys_ggingi_tga: the number of servers n must be at least 1");
168
169 const T nT = num_traits<T>::from_int(static_cast<long>(n));
170 const T ca2 = ca * ca;
171 const T rho = lambda / (nT * mu);
172 const T lamPn = lambda / nT;
173
174 std::function<T(const T&)> pdf = patiencePdf;
175 if (!pdf) {
176 pdf = [&patienceCcdf](const T& x) {
177 const T h = num_traits<T>::from_double(1e-6);
178 const T lo = x - h < num_traits<T>::from_int(0) ? num_traits<T>::from_int(0) : T(x - h);
179 const T d = (patienceCcdf(lo) - patienceCcdf(T(x + h))) / (num_traits<T>::from_int(2) * h);
180 return d < num_traits<T>::from_int(0) ? num_traits<T>::from_int(0) : d;
181 };
182 }
183
185 r.trafficIntensity = rho;
186 if (rho <= one) {
187 // Underloaded: no queue in the limit; the content is normal with the
188 // infinite-server variance (eq. 10).
189 T omega = num_traits<T>::from_rational(1, 2);
190 if (serviceCcdf) {
191 const T hi = detail::tga_inv_ccdf<T>(serviceCcdf, num_traits<T>::from_double(1e-12));
192 omega = detail::tga_simpson<T>([&](const T& x) { return T(serviceCcdf(x) * serviceCcdf(x)); },
193 zero, hi) * mu;
194 }
195 r.regime = "underloaded";
196 r.fluidWait = r.fluidQueueLength = r.meanWait = r.varWait = zero;
198 r.meanNumberInService = r.meanNumber = lambda / mu;
199 r.sigmaW = zero;
200 r.sigmaX = sqrt((lambda / mu) * (one + (ca2 - one) * omega));
201 return r;
202 }
203
204 // Overloaded: the fluid centre of Theorem 2.1(b).
205 const T w = detail::tga_inv_ccdf<T>(patienceCcdf, T(one / rho));
206 const T fw = pdf(w);
207 if (fw <= zero)
208 throw InputError("qsys_ggingi_tga: the patience density vanishes at the fluid waiting "
209 "time, so the Gaussian correction is undefined there");
210 const T qPerServer = lamPn * detail::tga_simpson<T>(patienceCcdf, zero, w);
211
212 // Eq. (24): the service law enters only through the (cs+1)rho term.
213 const T sigmaW2 = ((ca2 - one) + (cs + one) * rho) / (two * mu * rho * rho * fw);
214 const T sigmaX2 =
215 mu * mu * sigmaW2 +
216 lamPn * detail::tga_simpson<T>(
217 [&](const T& x) { return T(patienceCcdf(x) * (one + (ca2 - one) * patienceCcdf(x))); },
218 zero, w);
219 r.sigmaW = sqrt(sigmaW2 < zero ? zero : sigmaW2);
220 r.sigmaX = sqrt(sigmaX2 < zero ? zero : sigmaX2);
221
222 const T aW = sqrt(nT) * w / r.sigmaW; // eq. (21)
223 const T aX = sqrt(nT) * qPerServer / r.sigmaX; // eq. (19)
224 T m1W, vW, m1X, vX;
225 detail::tga_trunc_moments(aW, m1W, vW);
226 detail::tga_trunc_moments(aX, m1X, vX);
227
228 r.regime = "overloaded";
229 r.fluidWait = w;
230 r.fluidQueueLength = nT * qPerServer;
231 r.meanWait = w * (detail::tga_Phi(aW) + detail::tga_phi(aW) / aW);
232 r.varWait = (r.sigmaW * r.sigmaW / nT) * vW;
233 r.meanQueueLength = nT * qPerServer * (detail::tga_Phi(aX) + detail::tga_phi(aX) / aX);
234 r.varQueueLength = nT * r.sigmaX * r.sigmaX * vX;
235 // E[B] = E[min(X_n,n)]: every server is busy but for the lower tail.
237 nT - sqrt(nT) * r.sigmaX * (detail::tga_phi(aX) - aX * (one - detail::tga_Phi(aX)));
239 r.probDelay = detail::tga_Phi(aW); // eq. (22)
240 // Eq. (23): a customer abandons when its patience falls short of its wait.
241 const T hi = w * num_traits<T>::from_int(20) > w + num_traits<T>::from_int(20)
242 ? T(w * num_traits<T>::from_int(20))
243 : T(w + num_traits<T>::from_int(20));
244 T pa = detail::tga_simpson<T>(
245 [&](const T& x) { return T((one - detail::tga_Phi(T(aW * (x / w - one)))) * pdf(x)); }, zero,
246 hi);
247 if (pa < zero) pa = zero;
248 if (pa > one) pa = one;
249 r.probAbandon = pa;
250 return r;
251}
252
253} // namespace qsys
254} // namespace line
255
256#endif // LINE_API_QSYS_GGINGI_TGA_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
QsysTgaResult< T > qsys_ggingi_tga(const T &lambda, const T &mu, unsigned n, const T &ca, const T &cs, const std::function< T(const T &)> &patienceCcdf, const std::function< T(const T &)> &patiencePdf=std::function< T(const T &)>(), const std::function< T(const T &)> &serviceCcdf=std::function< T(const T &)>())
Truncated Gaussian approximation (TGA-G) for the G/GI/n+GI queue.
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+GI truncated Gaussian approximation.
T sigmaW
the CLT scale of the wait
std::string regime
"underloaded" or "overloaded"
T trafficIntensity
rho = lambda/(n mu)
T meanNumber
E[X] = E[B] + E[Q].
T probAbandon
P(patience < wait).
T fluidWait
w, the fluid waiting time
T fluidQueueLength
the fluid queue content
T meanWait
E[W] after truncation.
T sigmaX
the CLT scale of the content
T meanQueueLength
E[Q] after truncation.