LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mmc_prio_tardiness.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_MMC_PRIO_TARDINESS_H
6#define LINE_API_QSYS_QSYS_MMC_PRIO_TARDINESS_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * Exact stationary expected tardiness in a multiclass priority M/M/c queue
12 * (Hafizoglu, Gel and Keskinocak, INFORMS J. Comput. 25(2), 2013).
13 *
14 * Port of matlab/src/api/qsys/qsys_mmc_prio_tardiness.m. Classes are listed in
15 * priority order (class 1 highest), all share one exponential service rate mu,
16 * FCFS within a class, under head-of-line ("np") or preemptive-resume ("pr")
17 * priority. A class-i order that finds all servers busy waits q+1 busy periods
18 * of an M/M/1 queue with the higher-priority rate and service rate c mu
19 * (Theorem 2); under preemption the in-service states solve the tridiagonal
20 * system of Theorem 4. Mixing over the stationary state seen on arrival gives
21 * the response-time transform f(s); tardiness and its probability are the
22 * inverses at d of (s E[T] - 1 + f(s))/s^2 and (1 - f(s))/s. Classes with no
23 * higher-priority traffic use a closed form.
24 *
25 * ARITHMETIC: double. The result is a numerical Laplace inversion.
26 */
27
28#include <algorithm>
29#include <cctype>
30#include <cmath>
31#include <complex>
32#include <cstddef>
33#include <limits>
34#include <string>
35#include <vector>
36
39#include "line/util/error.h"
40
41namespace line {
42namespace qsys {
43
44namespace tarddetail {
45
46using Cplx = std::complex<double>;
47
48inline bool is_preemptive(const std::string& type, const char* fn) {
49 std::string t = type;
50 std::transform(t.begin(), t.end(), t.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
51 if (t == "np" || t == "hol" || t == "nonpreemptive" || t == "non-preemptive") return false;
52 if (t == "pr" || t == "prpr" || t == "preemptive" || t == "fcfsprprio") return true;
53 throw InputError(ErrorKind("Argument"), std::string(fn) + ": unknown priority type '" + type + "', use 'np' or 'pr'");
54}
55
56inline double erlang_c(unsigned c, double A) {
57 double B = 1.0;
58 for (unsigned k = 1; k <= c; ++k) B = A * B / (k + A * B);
59 return c * B / (c - A * (1 - B));
60}
61
62/** LST of the M/M/1 busy period with rates lb and cmu, conjugate form. */
63inline Cplx busy_lst(Cplx s, double lb, double cmu) {
64 const double a = lb + cmu;
65 const double b = 2.0 * std::sqrt(lb * cmu);
66 return 2.0 * cmu / (s + a + std::sqrt(s + (a - b)) * std::sqrt(s + (a + b)));
67}
68
69inline Cplx ipow(Cplx z, unsigned k) {
70 Cplx out(1.0, 0.0);
71 while (k > 0) {
72 if (k & 1u) out *= z;
73 z *= z;
74 k >>= 1;
75 }
76 return out;
77}
78
79/** Theorem 4 transforms f_0..f_{c-1}, by the tridiagonal (Thomas) solve. */
80inline std::vector<Cplx> pr_state_lst(Cplx s, double lb, double mu, unsigned c) {
81 std::vector<Cplx> diag(c), cp(c), dp(c), f(c);
82 std::vector<double> sup(c, 0.0), sub(c, 0.0);
83 for (unsigned v = 0; v < c; ++v) {
84 diag[v] = s + (lb + (v + 1) * mu);
85 if (v + 1 < c) sup[v] = -lb;
86 if (v > 0) sub[v] = -static_cast<double>(v) * mu;
87 }
88 diag[c - 1] = s + (lb + c * mu) - lb * busy_lst(s, lb, c * mu);
89 cp[0] = sup[0] / diag[0];
90 dp[0] = mu / diag[0];
91 for (unsigned v = 1; v < c; ++v) {
92 const Cplx den = diag[v] - cp[v - 1] * sub[v];
93 cp[v] = sup[v] / den;
94 dp[v] = (mu - dp[v - 1] * sub[v]) / den;
95 }
96 f[c - 1] = dp[c - 1];
97 for (int v = static_cast<int>(c) - 2; v >= 0; --v) f[v] = dp[v] - cp[v] * f[v + 1];
98 return f;
99}
100
101/** Mean response times S_0..S_{c-1} of the Theorem 4 states. */
102inline std::vector<double> pr_means(double lb, double mu, unsigned c) {
103 std::vector<double> diag(c), sup(c, 0.0), sub(c, 0.0), rhs(c, 1.0), cp(c), dp(c), x(c);
104 for (unsigned v = 0; v < c; ++v) {
105 diag[v] = lb + (v + 1) * mu;
106 if (v + 1 < c) sup[v] = -lb;
107 if (v > 0) sub[v] = -static_cast<double>(v) * mu;
108 }
109 diag[c - 1] = c * mu;
110 rhs[c - 1] = 1 + lb / (c * mu - lb);
111 cp[0] = sup[0] / diag[0];
112 dp[0] = rhs[0] / diag[0];
113 for (unsigned v = 1; v < c; ++v) {
114 const double den = diag[v] - cp[v - 1] * sub[v];
115 cp[v] = sup[v] / den;
116 dp[v] = (rhs[v] - dp[v - 1] * sub[v]) / den;
117 }
118 x[c - 1] = dp[c - 1];
119 for (int v = static_cast<int>(c) - 2; v >= 0; --v) x[v] = dp[v] - cp[v] * x[v + 1];
120 return x;
121}
122
123/** {S, Tard, P} of T = X + B Y, X ~ Exp(mu), Y ~ Exp(theta), B ~ Bernoulli(Pc). */
124inline void exp_mix_tard(double mu, double theta, double Pc, double d, double& S, double& T, double& P) {
125 S = 1.0 / mu + Pc / theta;
126 if (d <= 0) { T = S - d; P = 1.0; return; }
127 if (std::isinf(d)) { T = 0.0; P = 0.0; return; }
128 const double delta = theta - mu;
129 const double E1 = delta == 0 ? d : -std::expm1(-delta * d) / delta;
130 const double emd = std::exp(-mu * d);
131 T = (1 - Pc) * emd / mu + Pc * (emd / mu + emd * (1 + mu * E1) / theta);
132 P = (1 - Pc) * emd + Pc * emd * (1 + mu * E1);
133}
134
135/** Tard and P by inverting (s S - 1 + F)/s^2 and (1 - F)/s at d. */
136inline void invert_tard(const lti::LaplaceFn& F, double S, double d, lti::LaplaceMethod m, double& T, double& P) {
137 if (d <= 0) { T = S - d; P = 1.0; return; }
138 if (std::isinf(d)) { T = 0.0; P = 0.0; return; }
139 T = lti::laplace_invert([&F, S](Cplx s) { return (s * S - 1.0 + F(s)) / (s * s); }, d, m);
140 P = lti::laplace_invert([&F](Cplx s) { return (1.0 - F(s)) / s; }, d, m);
141 T = std::min(std::max(T, 0.0), S);
142 P = std::min(std::max(P, 0.0), 1.0);
143}
144
145/** Tard and P of T = Exp(mu) + Erlang(K, theta) by nonnegative Poisson series. */
146inline void hypo_tard(unsigned K, double mu, double theta, double d, double S, double& T, double& P) {
147 if (d <= 0) { T = S - d; P = 1.0; return; }
148 if (std::isinf(d)) { T = 0.0; P = 0.0; return; }
149 const double w = 1.0 - mu / theta;
150 const std::size_t M = w <= 0 ? 0 : static_cast<std::size_t>(std::ceil(std::log(1e-18) / std::log(w)));
151 const double y = theta * d;
152 const std::size_t J = K + M + 2;
153 std::vector<double> p(J), Qc(J + 1, 0.0);
154 for (std::size_t j = 0; j < J; ++j) {
155 p[j] = std::exp(-y + static_cast<double>(j) * std::log(y) - std::lgamma(static_cast<double>(j) + 1.0));
156 Qc[j + 1] = Qc[j] + p[j];
157 }
158 P = Qc[K];
159 T = 0.0;
160 for (unsigned j = 0; j < K; ++j) T += (K - j) * p[j];
161 double wm = 1.0;
162 for (std::size_t m = 0; m <= M; ++m) {
163 P += wm * p[K + m];
164 T += wm * Qc[K + m + 1];
165 wm *= w;
166 }
167 T /= theta;
168 T = std::min(std::max(T, 0.0), S);
169 P = std::min(std::max(P, 0.0), 1.0);
170}
171
172} // namespace tarddetail
173
174/** MATLAB's [Tard, Ptard, res] outputs. */
176 std::vector<double> Tard; ///< per-class E[(T_i - d_i)^+], class 1 first
177 std::vector<double> Ptard; ///< per-class P(T_i > d_i)
178 QsysRes res; ///< meanSojournTime, tardiness, probTardy, dueDate, analyzer
179};
180
181/**
182 * @brief Stationary per-class expected tardiness of a priority M/M/c queue.
183 *
184 * @param lambda per-class arrival rates, highest priority first
185 * @param mu common service rate
186 * @param c number of servers
187 * @param d one due date, or one per class (infinity = none)
188 * @param type "np" head-of-line or "pr" preemptive resume
189 * @param method Laplace inversion method of lti::laplace_invert
190 */
191inline MmcPrioTardinessResult qsys_mmc_prio_tardiness(const std::vector<double>& lambda, double mu, unsigned c,
192 const std::vector<double>& d, const std::string& type = "np",
193 const std::string& method = "euler") {
194 using namespace tarddetail;
195 const char* fn = "qsys_mmc_prio_tardiness";
196 const std::size_t R = lambda.size();
197 bool bad = R == 0 || !(mu > 0);
198 for (double l : lambda)
199 if (!(l > 0)) bad = true;
200 if (bad) throw InputError(ErrorKind("Argument"), std::string(fn) + ": lambda must be positive and mu a positive scalar");
201 if (c < 1) throw InputError(ErrorKind("Argument"), std::string(fn) + ": c must be a positive integer");
202 if (d.size() != 1 && d.size() != R)
203 throw InputError(ErrorKind("Argument"), std::string(fn) + ": d must be a scalar or one due date per class");
204 std::vector<double> dv(R);
205 for (std::size_t i = 0; i < R; ++i) {
206 dv[i] = d.size() == 1 ? d[0] : d[i];
207 if (std::isnan(dv[i]))
208 throw InputError(ErrorKind("Argument"), std::string(fn) + ": d must be a scalar or one due date per class");
209 }
210 const bool preemptive = is_preemptive(type, fn);
211 const lti::LaplaceMethod m = lti::laplace_method(method);
212 std::vector<double> Lam(R);
213 double acc = 0;
214 for (std::size_t i = 0; i < R; ++i) Lam[i] = (acc += lambda[i]);
215 const double A = Lam[R - 1] / mu;
216 if (A >= c) resdetail::throw_unstable(fn, "lambda/(c mu)", A / c);
217 const double PcAll = erlang_c(c, A);
219 out.Tard.assign(R, 0.0);
220 out.Ptard.assign(R, 0.0);
221 std::vector<double> S(R, 0.0);
222 const double cmu = c * mu;
223 for (std::size_t i = 0; i < R; ++i) {
224 const double lb = i == 0 ? 0.0 : Lam[i] - lambda[i];
225 const double Li = Lam[i];
227 if (preemptive) {
228 const double a = Li / mu, rho = Li / cmu;
229 std::vector<double> pv(c + 1);
230 pv[0] = 1.0;
231 for (unsigned v = 1; v <= c; ++v) pv[v] = pv[v - 1] * a / v;
232 double Z = pv[c] / (1 - rho);
233 for (unsigned v = 0; v < c; ++v) Z += pv[v];
234 for (double& x : pv) x /= Z;
235 if (lb == 0) {
236 exp_mix_tard(mu, cmu - Li, pv[c] / (1 - rho), dv[i], S[i], out.Tard[i], out.Ptard[i]);
237 continue;
238 }
239 const std::vector<double> Sv = pr_means(lb, mu, c);
240 double s0 = 0;
241 for (unsigned v = 0; v < c; ++v) s0 += pv[v] * Sv[v];
242 S[i] = s0 + pv[c] / (1 - rho) * Sv[c - 1] + pv[c] / ((cmu - lb) * (1 - rho) * (1 - rho));
243 F = [lb, mu, c, cmu, rho, pv](Cplx s) {
244 const std::vector<Cplx> f = pr_state_lst(s, lb, mu, c);
245 const Cplx g = busy_lst(s, lb, cmu);
246 Cplx o(0.0, 0.0);
247 for (unsigned v = 0; v < c; ++v) o += pv[v] * f[v];
248 return o + pv[c] * g * f[c - 1] / (1.0 - rho * g);
249 };
250 } else {
251 const double sig = Li / cmu;
252 if (lb == 0) {
253 exp_mix_tard(mu, cmu - Li, PcAll, dv[i], S[i], out.Tard[i], out.Ptard[i]);
254 continue;
255 }
256 S[i] = 1 / mu + PcAll / ((1 - sig) * (cmu - lb));
257 F = [lb, mu, cmu, sig, PcAll](Cplx s) {
258 const Cplx g = busy_lst(s, lb, cmu);
259 return mu / (s + mu) * ((1 - PcAll) + PcAll * (1 - sig) * g / (1.0 - sig * g));
260 };
261 }
262 invert_tard(F, S[i], dv[i], m, out.Tard[i], out.Ptard[i]);
263 }
264 const std::string tstr = preemptive ? "pr" : "np";
265 out.res.set("meanSojournTime", S);
266 out.res.set("tardiness", out.Tard);
267 out.res.set("probTardy", out.Ptard);
268 out.res.set("dueDate", dv);
269 out.res.analyzer = "exact:M/M/" + std::to_string(c) + "-prio-" + tstr + "-tardiness";
270 return out;
271}
272
273} // namespace qsys
274} // namespace line
275
276#endif // LINE_API_QSYS_QSYS_MMC_PRIO_TARDINESS_H
InputError(const std::string &what)
Definition error.h:105
The exception types the port throws.
Numerical inversion of a Laplace transform: Euler, Talbot, Gaver-Stehfest.
double laplace_invert(const LaplaceFn &F, double t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
Invert F at t by the named method.
LaplaceMethod
The methods laplace_invert accepts.
LaplaceMethod laplace_method(const std::string &s)
Parse the reference's method names, including its two Gaver spellings.
std::function< Cplx(Cplx)> LaplaceFn
The transform, evaluated at complex argument.
void throw_unstable(const char *fn, const char *what, double v)
A LINE:input:Unstable refusal "fn: unstable system: <what> = %g >= 1".
Definition qsys_res.h:213
std::vector< Cplx > pr_state_lst(Cplx s, double lb, double mu, unsigned c)
Theorem 4 transforms f_0..f_{c-1}, by the tridiagonal (Thomas) solve.
void hypo_tard(unsigned K, double mu, double theta, double d, double S, double &T, double &P)
Tard and P of T = Exp(mu) + Erlang(K, theta) by nonnegative Poisson series.
Cplx ipow(Cplx z, unsigned k)
bool is_preemptive(const std::string &type, const char *fn)
double erlang_c(unsigned c, double A)
Cplx busy_lst(Cplx s, double lb, double cmu)
LST of the M/M/1 busy period with rates lb and cmu, conjugate form.
void exp_mix_tard(double mu, double theta, double Pc, double d, double &S, double &T, double &P)
{S, Tard, P} of T = X + B Y, X ~ Exp(mu), Y ~ Exp(theta), B ~ Bernoulli(Pc).
std::complex< double > Cplx
void invert_tard(const lti::LaplaceFn &F, double S, double d, lti::LaplaceMethod m, double &T, double &P)
Tard and P by inverting (s S - 1 + F)/s^2 and (1 - F)/s at d.
std::vector< double > pr_means(double lb, double mu, unsigned c)
Mean response times S_0..S_{c-1} of the Theorem 4 states.
MmcPrioTardinessResult qsys_mmc_prio_tardiness(const std::vector< double > &lambda, double mu, unsigned c, const std::vector< double > &d, const std::string &type="np", const std::string &method="euler")
Stationary per-class expected tardiness of a priority M/M/c queue.
QsysResT< double > QsysRes
Definition qsys_res.h:96
std::complex< double > Cplx
Definition qsys_mg1_ps.h:89
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...
ErrorKind(std::string n)
Definition error.h:39
MATLAB's [Tard, Ptard, res] outputs.
QsysRes res
meanSojournTime, tardiness, probTardy, dueDate, analyzer
std::vector< double > Ptard
per-class P(T_i > d_i)
std::vector< double > Tard
per-class E[(T_i - d_i)^+], class 1 first
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