LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qbd_setupdelayoff.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_MAM_QBD_SETUPDELAYOFF_H
6#define LINE_API_MAM_QBD_SETUPDELAYOFF_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Mean queue length of an M/M/1 queue with a setup delay and a delayed-off
12 * period, solved as a QBD.
13 *
14 * Templated port of matlab/src/api/mam/qbd_setupdelayoff.m. The server is
15 * switched off when the system empties, but only after a delay-off period of
16 * rate betarate and SCV betascv has elapsed; an arrival during that period
17 * finds the server still up. Once off, an arrival starts a setup of rate
18 * alpharate and SCV alphascv before service can begin.
19 *
20 * The QBD phase index is overloaded by level, which is why the phases cannot
21 * be redistributed on a level change:
22 * - at level 0 phase 1 is "server off" and phases na+1..na+nb are the
23 * delay-off phases;
24 * - above level 0 phases 1..na are the setup phases and phase na+1 is the
25 * busy server.
26 * An arrival to an off server must therefore enter the setup at phase 1,
27 * which is why both phases are built in CANONICAL COXIAN form: its entry
28 * vector is [1 0 ... 0] for every SCV. The reference notes that
29 * APH.fitMeanAndSCV violates this for SCV > 1, returning a hyperexponential
30 * entered at phase 2 with probability 3/4, which the chain then silently
31 * entered at phase 1.
32 *
33 * The phases are given as RATES and an exponential phase is built from its
34 * rate directly rather than round-tripped through its mean, which is what the
35 * reference does after the round trip turned a finite 1e8 rate into an
36 * infinite one.
37 *
38 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: the Coxian fit takes
39 * a square root and R comes from cyclic reduction.
40 *
41 * TRUNCATION. The level series is cut where MATLAB's QBD_pi cuts it, at
42 * accumulated mass 1 - 1e-10 or 501 level vectors, whichever comes first
43 * (QBD_pi's MaxNumComp default is 500). qbd_pi's own default of 20000 levels
44 * would keep more of the tail and report a slightly larger queue length, so
45 * the cap is passed explicitly.
46 */
47
48#include <cmath>
49#include <cstddef>
50#include <vector>
51
52#include "line/api/mam/qbd_r.h"
55#include "line/num/number.h"
56#include "line/util/error.h"
57#include "line/util/linalg.h"
58#include "line/util/matrix.h"
59
60namespace line {
61namespace mam {
62
63/**
64 * Sub-generator of the canonical Coxian form with the given RATE and SCV,
65 * entered at phase 1 (the coxian_phase local of qbd_setupdelayoff.m, which
66 * routes through Coxian.fitMeanAndSCV for SCV != 1).
67 *
68 * The four branches of Coxian.fitMeanAndSCV, with MEAN = 1/rate and
69 * CoarseTol = 1e-3:
70 * |SCV - 1| <= tol one exponential phase of rate 1/MEAN;
71 * 0.5 + tol < SCV < 1 - tol
72 * two phases, mu_i = 2/MEAN/(1 -+ sqrt(2 SCV - 1)),
73 * phi = [0, 1], i.e. a pure series (hypoexponential);
74 * SCV <= 0.5 + tol an Erlang of n = ceil(1/SCV) phases of rate n/MEAN;
75 * SCV > 1 + tol two phases with mu_1 = 2/MEAN, mu_2 = mu_1/(2 SCV)
76 * and phi_1 = 1 - mu_2/mu_1, the Coxian form of a
77 * hyperexponential.
78 * The sub-generator is diag(-mu) + diag(mu_i (1 - phi_i), 1), as in
79 * Coxian.m's process assembly.
80 */
81template <class T>
82Matrix<T> coxian_phase_subgen(const T& rate, const T& scv) {
84 "coxian_phase_subgen requires transcendental arithmetic");
85 const T one = num_traits<T>::from_int(1);
86 const T two = num_traits<T>::from_int(2);
87 const T zero = num_traits<T>::from_int(0);
88 if (rate <= zero) throw InputError("coxian_phase_subgen: the rate must be positive");
89 if (scv <= zero) throw InputError("coxian_phase_subgen: the SCV must be positive");
90
91 // An exponential phase is built from the rate directly.
92 if (scv == one) {
93 Matrix<T> D0(1, 1);
94 D0(0, 0) = -rate;
95 return D0;
96 }
97
98 const T tol = num_traits<T>::from_double(1e-3);
99 const T mean = one / rate;
100 std::vector<T> mu, phi;
101 if (scv >= one - tol && scv <= one + tol) {
102 mu.push_back(one / mean);
103 phi.push_back(one);
104 } else if (scv > T(one / two) + tol && scv < one - tol) {
105 using std::sqrt;
106 const T s = T(sqrt(T(one + two * (scv - one))));
107 mu.push_back(two / mean / (one + s));
108 mu.push_back(two / mean / (one - s));
109 phi.push_back(zero);
110 phi.push_back(one);
111 } else if (scv <= T(one / two) + tol) {
112 const double inv = 1.0 / num_traits<T>::to_double(scv);
113 const long n = static_cast<long>(std::ceil(inv));
114 const T lambda = num_traits<T>::from_int(n) / mean;
115 for (long k = 0; k < n; ++k) {
116 mu.push_back(lambda);
117 phi.push_back(zero);
118 }
119 phi[static_cast<std::size_t>(n) - 1] = one;
120 } else {
121 const T mu1 = two / mean;
122 const T mu2 = mu1 / (two * scv);
123 mu.push_back(mu1);
124 mu.push_back(mu2);
125 phi.push_back(T(one - mu2 / mu1));
126 phi.push_back(one);
127 }
128 phi.back() = one;
129
130 const std::size_t n = mu.size();
131 Matrix<T> D0(n, n, zero);
132 for (std::size_t i = 0; i < n; ++i) {
133 D0(i, i) = -mu[i];
134 if (i + 1 < n) D0(i, i + 1) = mu[i] * (one - phi[i]);
135 }
136 return D0;
137}
138
139/**
140 * Mean queue length of the M/M/1 queue with setup delay and delay-off
141 * (qbd_setupdelayoff.m).
142 *
143 * @param lambda arrival rate
144 * @param mu service rate
145 * @param alpharate rate of the setup phase
146 * @param alphascv SCV of the setup phase
147 * @param betarate rate of the delay-off phase
148 * @param betascv SCV of the delay-off phase
149 */
150template <class T>
151T qbd_setupdelayoff(const T& lambda, const T& mu, const T& alpharate, const T& alphascv,
152 const T& betarate, const T& betascv) {
154 "qbd_setupdelayoff requires transcendental arithmetic");
155 using namespace qbd_detail;
156 const T zero = num_traits<T>::from_int(0);
157
158 const Matrix<T> Ta = coxian_phase_subgen(alpharate, alphascv);
159 const std::size_t na = Ta.rows();
160 std::vector<T> ta(na, zero); // completion rate out of each setup phase
161 for (std::size_t i = 0; i < na; ++i) {
162 T s = zero;
163 for (std::size_t j = 0; j < na; ++j) s += Ta(i, j);
164 ta[i] = -s;
165 }
166
167 const Matrix<T> Tb = coxian_phase_subgen(betarate, betascv);
168 const std::size_t nb = Tb.rows();
169 std::vector<T> tb(nb, zero);
170 for (std::size_t i = 0; i < nb; ++i) {
171 T s = zero;
172 for (std::size_t j = 0; j < nb; ++j) s += Tb(i, j);
173 tb[i] = -s;
174 }
175
176 const std::size_t n = na + nb;
177 Matrix<T> F(n, n, zero), B(n, n, zero), L(n, n, zero), L0(n, n, zero);
178
179 for (std::size_t i = 0; i < na; ++i) F(i, i) = lambda;
180 for (std::size_t i = 0; i < nb; ++i) F(na + i, na) = lambda;
181 F(na, na) = lambda;
182 B(na, na) = mu;
183
184 for (std::size_t i = 0; i < na; ++i) {
185 // whole generator row rationale: see _kb/03-api-layer.md (cpp port notes: mam)
186 for (std::size_t j = 0; j < na; ++j) L(i, j) = Ta(i, j);
187 L(i, i) -= lambda;
188 L(i, na) = ta[i]; // setup completes from phase i -> busy server
189 }
190 L(na, na) = -mu - lambda;
191 for (std::size_t i = 1; i < nb; ++i) L(na + i, na + i) = -lambda;
192
193 for (std::size_t i = 0; i < na; ++i) L0(i, i) = -lambda;
194 for (std::size_t i = 0; i < nb; ++i) {
195 for (std::size_t j = 0; j < nb; ++j) L0(na + i, na + j) = Tb(i, j);
196 L0(na + i, na + i) -= lambda;
197 L0(na + i, 0) = tb[i]; // delay-off expires from phase i -> server off
198 }
199
200 const QbdFundMat<T> fm = qbd_fundmat(B, L, F);
201 const Matrix<T> pn =
202 qbd_pi(B, L0, fm.R, static_cast<std::size_t>(501), T(num_traits<T>::from_double(1e-10)));
203
204 // prior reference bug (15% high queue length): see _kb/03-api-layer.md (cpp port notes: mam)
205 T QN = zero;
206 for (std::size_t k = 1; k < pn.rows(); ++k) {
207 T s = zero;
208 for (std::size_t j = 0; j < n; ++j) s += pn(k, j);
209 QN += num_traits<T>::from_int(static_cast<long>(k)) * s;
210 }
211 return QN;
212}
213
214/** Mean queue length and throughput of the CLOSED setup/delay-off queue. */
215template <class T>
217 T QN; ///< mean number of jobs at the station
218 T XN; ///< throughput of the station
219};
220
221/**
222 * Mean queue length and throughput of a FINITE-POPULATION queue with setup
223 * delay and delay-off, a port of matlab/src/api/mam/qbd_setupdelayoff_closed.m.
224 *
225 * The closed twin of `qbd_setupdelayoff`. The population N is finite and Z is
226 * the complementary delay, the mean time a customer spends away from this
227 * station, so the arrival rate is state dependent, lambda(n) = (N - n)/Z, and
228 * the level index is bounded by N. That makes the chain a LEVEL-DEPENDENT QBD
229 * over finitely many levels, i.e. a finite CTMC, and it is solved exactly rather
230 * than by a matrix-geometric tail.
231 *
232 * THE SEMANTICS ARE THE SIMULATOR'S, not the mean-value shortcut's. When the
233 * queue empties the server begins a delay-off period; an arrival DURING it finds
234 * the server still warm and resumes without setup (Solver_ssj's cancelDelayoff),
235 * and only an arrival after the delay-off has expired pays the setup. That is an
236 * M/M/1 with setup time AND close-down time. The per-instance cold-start race
237 * `p_cold*E[setup] + S` this replaces raced the delay-off against the
238 * per-instance idle time and carried NO queueing term, so it described a
239 * serverless instance pool rather than a single-server vacation queue and left
240 * the reported response time byte-identical across a tenfold change in the setup
241 * mean.
242 *
243 * The phase index is overloaded by level exactly as in the open twin: at level 0
244 * phase 1 is the OFF server and the rest are the delay-off; above level 0 the
245 * phases are the setup and the last one is the busy server. Only the REACHABLE
246 * states are enumerated, because a finite chain cannot carry an unreachable row:
247 * it would be absorbing and the stationary solve singular.
248 *
249 * ARITHMETIC. Gated on num_traits<T>::has_transcendental, like the open twin:
250 * the Coxian fit takes a square root.
251 *
252 * @param N population of the closed chain
253 * @param Z complementary delay, the mean time a customer spends away
254 * @param mu service rate of the station
255 * @param alpharate rate of the setup phase
256 * @param alphascv SCV of the setup phase
257 * @param betarate rate of the delay-off phase
258 * @param betascv SCV of the delay-off phase
259 */
260template <class T>
261SetupDelayoffClosed<T> qbd_setupdelayoff_closed(const T& N, const T& Z, const T& mu,
262 const T& alpharate, const T& alphascv,
263 const T& betarate, const T& betascv) {
265 "qbd_setupdelayoff_closed requires transcendental arithmetic");
266 const T zero = num_traits<T>::from_int(0);
268 out.QN = zero;
269 out.XN = zero;
270 const long pop = static_cast<long>(std::llround(num_traits<T>::to_double(N)));
271 if (pop <= 0 || num_traits<T>::to_double(mu) <= 0) return out;
272 T Zc = Z;
275
276 const Matrix<T> Ta = coxian_phase_subgen(alpharate, alphascv);
277 const std::size_t na = Ta.rows();
278 std::vector<T> ta(na, zero);
279 for (std::size_t i = 0; i < na; ++i) {
280 T s = zero;
281 for (std::size_t j = 0; j < na; ++j) s += Ta(i, j);
282 ta[i] = T(-s);
283 }
284 const Matrix<T> Tb = coxian_phase_subgen(betarate, betascv);
285 const std::size_t nb = Tb.rows();
286 std::vector<T> tb(nb, zero);
287 for (std::size_t i = 0; i < nb; ++i) {
288 T s = zero;
289 for (std::size_t j = 0; j < nb; ++j) s += Tb(i, j);
290 tb[i] = T(-s);
291 }
292
293 const std::size_t off = 0; // level 0, server off
294 const std::size_t base = 1 + nb; // level 0 delay-off occupies 1..nb
295 const std::size_t P = na + 1;
296 const std::size_t m = base + static_cast<std::size_t>(pop) * P;
297 Matrix<T> Q(m, m, zero);
298
299 // lambda(n) = (N - n)/Z, zero at the top level
300 std::vector<T> lam(static_cast<std::size_t>(pop) + 1, zero);
301 for (long n = 0; n < pop; ++n)
302 lam[static_cast<std::size_t>(n)] = T(num_traits<T>::from_int(pop - n) / Zc);
303
304 if (num_traits<T>::to_double(lam[0]) > 0) Q(off, base) += lam[0];
305 for (std::size_t j = 0; j < nb; ++j) {
306 const std::size_t rj = 1 + j;
307 for (std::size_t j2 = 0; j2 < nb; ++j2)
308 if (j2 != j) Q(rj, 1 + j2) += Tb(j, j2);
309 Q(rj, off) += tb[j];
310 // an arrival during the delay-off cancels it and resumes WITHOUT setup
311 if (num_traits<T>::to_double(lam[0]) > 0) Q(rj, base + na) += lam[0];
312 }
313 for (long n = 1; n <= pop; ++n) {
314 const std::size_t lvl = base + static_cast<std::size_t>(n - 1) * P;
315 const std::size_t up = base + static_cast<std::size_t>(n) * P;
316 const T& ln = lam[static_cast<std::size_t>(n)];
317 const bool rising = n < pop && num_traits<T>::to_double(ln) > 0;
318 for (std::size_t i = 0; i < na; ++i) {
319 for (std::size_t i2 = 0; i2 < na; ++i2)
320 if (i2 != i) Q(lvl + i, lvl + i2) += Ta(i, i2);
321 Q(lvl + i, lvl + na) += ta[i];
322 // an arrival during the setup joins the queue and the setup carries on
323 // in the SAME phase: the level rises, the phase does not move
324 if (rising) Q(lvl + i, up + i) += ln;
325 }
326 if (rising) Q(lvl + na, up + na) += ln;
327 // a completion that empties the queue starts the delay-off at its phase 1
328 const std::size_t down = n - 1 >= 1 ? base + static_cast<std::size_t>(n - 2) * P + na : 1;
329 Q(lvl + na, down) += mu;
330 }
331 for (std::size_t i = 0; i < m; ++i) {
332 T s = zero;
333 for (std::size_t j = 0; j < m; ++j)
334 if (j != i) s += Q(i, j);
335 Q(i, i) = T(-s);
336 }
337
338 const std::vector<T> pi = mc::ctmc_solve(Q);
339 double total = 0.0;
340 for (std::size_t i = 0; i < pi.size(); ++i)
341 total += std::max(0.0, num_traits<T>::to_double(pi[i]));
342 if (total <= 0) return out;
343
344 T QN = zero, pbusy = zero;
345 for (long n = 1; n <= pop; ++n) {
346 const std::size_t lvl = base + static_cast<std::size_t>(n - 1) * P;
347 T level = pi[lvl + na];
348 for (std::size_t i = 0; i < na; ++i) level += pi[lvl + i];
349 QN += T(num_traits<T>::from_int(n) * level);
350 pbusy += pi[lvl + na];
351 }
352 const T norm = num_traits<T>::from_double(total);
353 out.QN = T(QN / norm);
354 out.XN = T(mu * pbusy / norm);
355 return out;
356}
357
358} // namespace mam
359} // namespace line
360
361#endif // LINE_API_MAM_QBD_SETUPDELAYOFF_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Dense matrix and non-owning view.
Matrix< T > coxian_phase_subgen(const T &rate, const T &scv)
Sub-generator of the canonical Coxian form with the given RATE and SCV, entered at phase 1 (the coxia...
T qbd_setupdelayoff(const T &lambda, const T &mu, const T &alpharate, const T &alphascv, const T &betarate, const T &betascv)
Mean queue length of the M/M/1 queue with setup delay and delay-off (qbd_setupdelayoff....
QbdFundMat< T > qbd_fundmat(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
G and R by cyclic reduction (qbd_fundmat.m, the Bini-Meini logarithmic reduction on the raw level blo...
Definition qbd_r.h:280
SetupDelayoffClosed< T > qbd_setupdelayoff_closed(const T &N, const T &Z, const T &mu, const T &alpharate, const T &alphascv, const T &betarate, const T &betascv)
Mean queue length and throughput of a FINITE-POPULATION queue with setup delay and delay-off,...
Matrix< T > qbd_pi(const Matrix< T > &B, const Matrix< T > &Lbar, const Matrix< T > &R, std::size_t max_levels, const T &mass_tol)
Stationary distribution of a QBD given R (QBD_pi.m, continuous-time branch, default boundary).
Definition qbd_r.h:412
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
static constexpr double FineTol
Definition lang_types.h:668
G and R together, as returned by qbd_fundmat.
Definition qbd_r.h:259
Matrix< T > R
Definition qbd_r.h:261
Mean queue length and throughput of the CLOSED setup/delay-off queue.
T QN
mean number of jobs at the station
T XN
throughput of the station