LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_gimc.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_QSYS_GIMC_H
6#define LINE_API_QSYS_QSYS_GIMC_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact analysis of the GI/M/c queue (Takacs, 1962).
12 *
13 * Port of matlab/src/api/qsys/qsys_gimc.m. With A(s) the interarrival LST,
14 * omega in (0,1) is the root of omega = A(c mu (1 - omega)). An arrival finds
15 * n >= c-1 jobs with probability D omega^(n-c), and fewer with the alternating
16 * binomial sums of the U_n built from g_j = A(j mu); D normalizes. Waiting is
17 * then exponential, P(W > t) = PW exp(-c mu (1 - omega) t), PW = D/(1-omega),
18 * and the time-average queue law follows from the arrival law by rate
19 * balance. MATLAB's refusals when omega cannot be bracketed (LINE:numerical:Root)
20 * and when the alternating sums lose accuracy (LINE:numerical:Cancellation)
21 * are kept.
22 *
23 * ARITHMETIC: double, as the reference. MATLAB's fzero on [0, 1-d] is a
24 * bracketing root search; it is reproduced here by bisection run to the
25 * resolution of the double grid, which brackets the same simple root.
26 */
27
28#include <algorithm>
29#include <cmath>
30#include <cstdio>
31#include <functional>
32#include <limits>
33#include <string>
34#include <vector>
35
38#include "line/util/error.h"
39
40namespace line {
41namespace qsys {
42
43/**
44 * @brief Exact GI/M/c queue, interarrival law given by its LST.
45 *
46 * @param lst interarrival LST at real arguments
47 * @param lambda arrival rate (MATLAB's 'lambda' option, required with a handle)
48 * @param mu service rate of one server
49 * @param c number of servers
50 * @param o tPoints, percentiles and nmax options
51 */
52inline QsysRes qsys_gimc(const std::function<double(double)>& lst, double lambda, double mu, unsigned c,
53 const QsysResOptions& o = QsysResOptions()) {
54 using namespace resdetail;
55 check_percentiles(o.percentiles, "qsys_gimc");
56 if (!lst) throw Error("qsys_gimc: the interarrival law must be a Distribution or an LST function handle");
57 if (!(lambda > 0.0) || !std::isfinite(lambda))
58 throw Error("qsys_gimc: with an LST handle the arrival rate must be given with 'lambda'");
59 if (!(mu > 0.0)) throw Error("qsys_gimc: mu must be a positive scalar");
60 if (c < 1) throw Error("qsys_gimc: c must be a positive integer");
61 const double A = lambda / mu;
62 if (A >= c) throw_unstable("qsys_gimc", "lambda/(c mu)", A / c);
63
64 const std::function<double(double)> g = [&](double w) { return lst(c * mu * (1.0 - w)) - w; };
65 double d = 1e-3;
66 while (g(1.0 - d) >= 0.0 && d > 1e-15) d /= 10.0;
67 if (g(1.0 - d) >= 0.0) throw NumericError(ErrorKind{"Root"}, "qsys_gimc: could not bracket the root omega in (0,1)");
68 double lo = 0.0, hi = 1.0 - d;
69 for (int it = 0; it < 2000; ++it) {
70 const double mid = 0.5 * (lo + hi);
71 if (mid <= lo || mid >= hi) break;
72 const double gm = g(mid);
73 if (gm == 0.0) {
74 lo = hi = mid;
75 break;
76 }
77 if (gm > 0.0) lo = mid;
78 else hi = mid;
79 }
80 const double omega = std::fabs(g(lo)) <= std::fabs(g(hi)) ? lo : hi;
81
82 std::vector<double> gj(c), Cj(c), terms(c);
83 for (unsigned j = 1; j <= c; ++j) gj[j - 1] = lst(j * mu);
84 double cp = 1.0;
85 for (unsigned j = 1; j <= c; ++j) {
86 cp *= gj[j - 1] / (1.0 - gj[j - 1]);
87 Cj[j - 1] = cp;
88 }
89 double tsum = 0.0;
90 for (unsigned j = 1; j <= c; ++j) {
91 terms[j - 1] = nchoosek_r(c, j) * (c * (1.0 - gj[j - 1]) - j) /
92 (Cj[j - 1] * (1.0 - gj[j - 1]) * (c * (1.0 - omega) - j));
93 tsum += terms[j - 1];
94 }
95 const double D = 1.0 / (1.0 / (1.0 - omega) + tsum);
96 std::vector<double> Cn(c + 1);
97 Cn[0] = 1.0;
98 for (unsigned j = 1; j <= c; ++j) Cn[j] = Cj[j - 1];
99 std::vector<double> Un(c);
100 for (unsigned nn = 0; nn < c; ++nn) {
101 double s = 0.0;
102 for (unsigned j = nn; j < c; ++j) s += terms[j];
103 Un[nn] = D * Cn[nn] * s;
104 }
105 long nmax = o.nmax;
106 if (nmax < 0)
107 nmax = static_cast<long>(c) +
108 static_cast<long>(std::max(1.0, std::ceil(std::log(1e-14 * (1.0 - omega)) /
109 std::log(std::max(omega, std::numeric_limits<double>::min())))));
110 nmax = std::max<long>(nmax, c);
111 std::vector<double> Pi(static_cast<std::size_t>(nmax + 1));
112 for (long nn = 0; nn <= nmax; ++nn) {
113 if (nn >= static_cast<long>(c) - 1) {
114 Pi[nn] = D * std::pow(omega, static_cast<double>(nn - static_cast<long>(c)));
115 } else {
116 double s = 0.0;
117 for (long ii = nn; ii < static_cast<long>(c); ++ii)
118 s += (((ii - nn) % 2) ? -1.0 : 1.0) * nchoosek_r(ii, nn) * Un[ii];
119 Pi[nn] = s;
120 }
121 }
122 double PiTot = D / omega / (1.0 - omega);
123 for (long nn = 0; nn + 1 < static_cast<long>(c); ++nn) PiTot += Pi[nn];
124 bool neg = false;
125 for (long nn = 0; nn < std::max<long>(static_cast<long>(c) - 1, 1); ++nn)
126 if (Pi[nn] < -1e-10) neg = true;
127 if (std::fabs(PiTot - 1.0) > 1e-8 || neg) {
128 char buf[256];
129 std::snprintf(buf, sizeof buf,
130 "qsys_gimc: the arrival distribution does not normalize (sum = %.12g): the alternating "
131 "sums lost accuracy at c = %u",
132 PiTot, c);
133 throw NumericError(ErrorKind{"Cancellation"}, buf);
134 }
135 for (double& v : Pi) v = std::max(v, 0.0);
136
137 std::vector<double> Pn(Pi.size(), 0.0);
138 for (long nn = 1; nn <= nmax; ++nn) Pn[nn] = A * Pi[nn - 1] / std::min<long>(nn, c);
139 double sj = 0.0;
140 for (unsigned j = 1; j + 1 <= c; ++j) sj += Pi[j - 1] * (1.0 / j - 1.0 / c);
141 Pn[0] = 1.0 - A / c - A * sj;
142
143 const double PW = D / (1.0 - omega);
144 const double theta = c * mu * (1.0 - omega);
145 const double EW = PW / theta;
146 const double EW2 = 2.0 * PW / (theta * theta);
147 const double VW = EW2 - EW * EW;
148 const double tailc = (A / c) * D;
149 const double om1 = 1.0 - omega;
150 const double EQ = tailc / (om1 * om1);
151 const double EQ2 = tailc * (1.0 + omega) / (om1 * om1 * om1);
152 const double m = c + 1.0;
153 double EN2 = 0.0;
154 for (unsigned n0 = 0; n0 <= c; ++n0) EN2 += static_cast<double>(n0) * n0 * Pn[n0];
155 EN2 += tailc * (m * m / om1 + 2.0 * m * omega / (om1 * om1) + omega * (1.0 + omega) / (om1 * om1 * om1));
156 const double EN = EQ + A;
157
158 QsysRes r;
159 r.set("meanQueueLength", EN);
160 r.set("varQueueLength", EN2 - EN * EN);
161 r.set("meanQueueLengthQ", EQ);
162 r.set("varQueueLengthQ", EQ2 - EQ * EQ);
163 r.set("meanWaitingTime", EW);
164 r.set("varWaitingTime", VW);
165 r.set("meanSojournTime", EW + 1.0 / mu);
166 r.set("varSojournTime", VW + 1.0 / (mu * mu));
167 r.set("probWait", PW);
168 r.set("condMeanWaitingTime", 1.0 / theta);
169 r.set("throughput", lambda);
170 r.set("utilization", A / c);
171 r.set("omega", omega);
172 r.set("queueLengthDist", Pn);
173 r.set("arrivalDist", Pi);
174 const std::function<double(double)> Wccdf = [PW, theta](double t) {
175 return t < 0.0 ? 1.0 : PW * std::exp(-theta * t);
176 };
177 const std::function<double(double)> Tccdf = [PW, theta, mu](double t) {
178 if (t < 0.0) return 1.0;
179 if (std::fabs(theta - mu) > 1e-12 * mu) {
180 const double G = PW * mu / (mu - theta);
181 return (1.0 - G) * std::exp(-mu * t) + G * std::exp(-theta * t);
182 }
183 return (1.0 + PW * mu * t) * std::exp(-mu * t);
184 };
185 if (!o.tPoints.empty()) {
186 r.set("tPoints", o.tPoints);
187 r.set("waitingTimeCdf", cdf_of(Wccdf, o.tPoints));
188 r.set("sojournTimeCdf", cdf_of(Tccdf, o.tPoints));
189 }
190 if (!o.percentiles.empty()) {
191 r.set("percentiles", o.percentiles);
192 std::vector<double> wp(o.percentiles.size());
193 for (std::size_t i = 0; i < wp.size(); ++i)
194 wp[i] = std::max(0.0, std::log(PW / (1.0 - o.percentiles[i] / 100.0)) / theta);
195 r.set("waitingTimePercentile", wp);
196 r.set("sojournTimePercentile", pct_of(Tccdf, o.percentiles, 50.0 / std::min(mu, theta)));
197 }
198 r.analyzer = "exact:GI/M/" + std::to_string(c);
199 return r;
200}
201
202/**
203 * @brief Exact GI/M/c queue, interarrival law given as a Distribution (its LST,
204 * and its rate 1/mean unless `lambda` > 0 is given, as MATLAB's
205 * LA.evalLST / LA.getMean).
206 */
207inline QsysRes qsys_gimc(const lang::Distrib<double>& LA, double mu, unsigned c,
208 const QsysResOptions& o = QsysResOptions(), double lambda = -1.0) {
209 const lang::Distrib<double> d = LA;
210 if (!(lambda > 0.0)) lambda = 1.0 / lang::dist_moment(d, 1);
211 return qsys_gimc([d](double s) { return lang::dist_lst(d, s); }, lambda, mu, c, o);
212}
213
214} // namespace qsys
215} // namespace line
216
217#endif // LINE_API_QSYS_QSYS_GIMC_H
Error(const std::string &what)
Definition error.h:75
NumericError(const std::string &what)
Definition error.h:112
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
T dist_lst(const Distrib< T > &d, const T &s)
sn.lst: the Laplace-Stieltjes transform E[exp(-sX)].
T dist_moment(const Distrib< T > &d, unsigned k)
The k-th raw moment.
QsysRes qsys_gimc(const std::function< double(double)> &lst, double lambda, double mu, unsigned c, const QsysResOptions &o=QsysResOptions())
Exact GI/M/c queue, interarrival law given by its LST.
Definition qsys_gimc.h:52
QsysResT< double > QsysRes
Definition qsys_res.h:96
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The res struct the exact queueing-system functions return, and the numeric helpers those functions sh...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
The name-value options shared by the exact functions: tPoints, percentiles and nmax (a negative nmax ...
Definition qsys_res.h:102
std::string analyzer
Definition qsys_res.h:59
void set(const std::string &name, const T &v)
Sets (or overwrites) a scalar field.
Definition qsys_res.h:62