LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mapg1k.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_MAPG1K_H
6#define LINE_API_QSYS_QSYS_MAPG1K_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MAP/G/1/K queue with tail drop: Markovian arrivals, an arbitrary service
12 * law F, and a buffer of K packets counting the one in transmission.
13 *
14 * Port of matlab/src/api/qsys/qsys_mapg1k.m, which is self-contained (it calls
15 * no Q-MAM and no BUTools), so this is a faithful transcription rather than a
16 * reconstruction. The service law is NOT fitted to a phase-type distribution:
17 * F enters exactly through the functionals A_m and Q_m, evaluated by
18 * uniformizing the arrival MAP at theta = max_i (-D0(i,i)).
19 *
20 * METHOD. The chain embedded at departure epochs has state (n, j) with
21 * n = 0..K-1 the packets left behind and j the MAP phase. With A_m the matrix
22 * of "m arrivals during one service, phase i -> phase j",
23 * n >= 1: n' = n - 1 + min(m, K - n), the overflow being sum_{m >= K-n} A_m
24 * n == 0: the phase first jumps through Psi = (-D0)^-1 D1, because the idle
25 * period ends at an arrival, and the service then proceeds as from 1.
26 * Its stationary law sigma gives, by Markov renewal reward over one
27 * inter-departure cycle,
28 * E[cycle] = S + sigma_0 (-D0)^-1 e, T = 1/E[cycle], p0 = 1 - T S,
29 * and the level-holding times come from Q_m, the expected time within a service
30 * during which exactly m arrivals have occurred. No PASTA argument is used
31 * anywhere: the MAP phase resolution does that work instead, which is what lets
32 * qsys_mmapg1k read exact PER-CLASS loss ratios off pKvec.
33 *
34 * WHERE THE PORT DIFFERS FROM THE REFERENCE, AND WHY.
35 * - The uniformization coefficients c_n = E[e^{-theta S}(theta S)^n/n!] are
36 * built by RECURSION rather than from log-gamma. For the gamma law they are
37 * the negative binomial pmf, c_0 = (1+theta th)^-al and
38 * c_n = c_{n-1} p (al+n-1)/n with p = theta th/(1+theta th); for the
39 * deterministic law they are the Poisson pmf, c_0 = e^{-theta d} and
40 * c_n = c_{n-1} theta d/n; for a PH law c_n = theta^n alpha M^{n+1} t with
41 * M = (theta I - T)^-1, accumulated as a running row vector. The reference
42 * evaluates each term independently through gammaln, which is the same
43 * number to rounding but costs a special function the port would otherwise
44 * not need at Real50. The recursions are also monotone in n and cannot lose
45 * the leading digits to cancellation.
46 * - The reference WARNS and continues when the c_n series is truncated with a
47 * residual above 1e-6; the port throws. A residual that large means the
48 * uniformization has not converged and every downstream quantity is wrong by
49 * an unknown amount, and a warning nobody reads is the worse failure mode.
50 * The residual is reported in the result either way.
51 * - The density path integrates over an EXPANDING finite window in
52 * u = log x rather than over (-Inf, log tmax] in one call, because the
53 * port's adaptive Gauss-Kronrod rule takes finite endpoints. The window is
54 * widened geometrically until a whole new panel contributes less than the
55 * tolerance, so the truncation is measured rather than assumed. The
56 * substitution itself is the reference's: it turns an integrable
57 * singularity x^(al-1) at the origin into e^(al u), which decays smoothly,
58 * so the singularity disappears instead of being resolved.
59 *
60 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: c_0 needs exp or a
61 * non-integer power for every service law, and the series truncation is a
62 * tolerance. Everything after the c_n -- the A_m and Q_m sums, the embedded
63 * chain, its stationary vector and the reward averaging -- is finite exact
64 * matrix algebra and adds no error of its own.
65 *
66 * MEASURED AGREEMENT: see cpp/tests/test_qsys_mapg1k.cpp. The M/M/1/K collapse
67 * is checked against the closed form (1-rho)rho^K/(1-rho^(K+1)), the M/G/1/K
68 * and MAP/G/1/K instances against MATLAB, and three identities are asserted on
69 * every instance: sum_l plevel(l) = 1, p0 = 1 - T S, and
70 * lossProbability = 1 - T/lambda = pK-weighted arrival loss.
71 *
72 * References:
73 * [1] Chydzinski, A. Per-Flow Throughput of a FIFO Buffer. Applied System
74 * Innovation 2026, 9, 112.
75 * [2] Niu, Z.; Cooper, R.B. Transform-Free Analysis of M/G/1/K and Related
76 * Queues. Mathematics of Operations Research 1993, 18, 486-510.
77 */
78
79#include <cmath>
80#include <cstddef>
81#include <functional>
82#include <limits>
83#include <string>
84#include <vector>
85
89#include "line/num/number.h"
90#include "line/util/error.h"
91#include "line/util/linalg.h"
92#include "line/util/lu.h"
93#include "line/util/matrix.h"
94
95namespace line {
96namespace qsys {
97
98/** Which family the service law belongs to. */
100
101/**
102 * Service-time descriptor, the C++ form of the MATLAB svc struct.
103 *
104 * Build one with the static factories; the fields that a given kind does not
105 * use are left at their default and are never read.
106 */
107template <class T>
110 T shape = num_traits<T>::from_int(1); ///< gamma shape alpha
111 T scale = num_traits<T>::from_int(1); ///< gamma scale theta
112 T det = num_traits<T>::from_int(1); ///< deterministic service time
113 std::vector<T> ph_alpha; ///< PH initial probability row
114 Matrix<T> ph_T; ///< PH subgenerator
115 std::function<T(const T&)> pdf; ///< density of the service time
116 T tmax = num_traits<T>::from_int(0); ///< upper support limit of the density
117 bool tmax_finite = false; ///< whether tmax is used
118
119 /** Gamma(shape, scale). shape = 1 is the exponential, integer shape Erlang. */
120 static ServiceLaw gamma(const T& shape, const T& scale) {
121 ServiceLaw s;
123 s.shape = shape;
124 s.scale = scale;
125 return s;
126 }
127 /** Constant service time d. */
128 static ServiceLaw deterministic(const T& d) {
129 ServiceLaw s;
131 s.det = d;
132 return s;
133 }
134 /** Phase type (alpha, Tmat). */
135 static ServiceLaw phase_type(const std::vector<T>& alpha, const Matrix<T>& Tmat) {
136 ServiceLaw s;
138 s.ph_alpha = alpha;
139 s.ph_T = Tmat;
140 return s;
141 }
142 /** Arbitrary density on (0, inf), or on (0, tmax] when tmax is supplied. */
143 static ServiceLaw density(std::function<T(const T&)> f) {
144 ServiceLaw s;
146 s.pdf = f;
147 return s;
148 }
149 static ServiceLaw density(std::function<T(const T&)> f, const T& tmax) {
150 ServiceLaw s = density(f);
151 s.tmax = tmax;
152 s.tmax_finite = true;
153 return s;
154 }
155};
156
157/** Return value of qsys_mapg1k, mirroring the MATLAB result struct. */
158template <class T>
160 T p0; ///< P(buffer empty)
161 T pK; ///< P(buffer full)
162 T throughput; ///< aggregate departure rate
163 T lossProbability; ///< 1 - throughput/lambda
164 T lambda; ///< aggregate MAP arrival rate
166 T utilization; ///< 1 - p0
167 T rho; ///< offered load lambda S
168 T meanQueueLength; ///< E[number in system]
169 std::size_t nmax; ///< uniformization order used
170 T countingResidual; ///< |1 - sum_n c_n| at the truncation
171 std::vector<T> sigma; ///< stationary law of the embedded chain, K M entries
172 std::vector<T> pKvec; ///< P(level = K, phase j), summing to pK
173 std::vector<T> p0vec; ///< P(level = 0, phase j), summing to p0
174 std::vector<T> plevel; ///< P(level = l), l = 0..K
175};
176
177namespace mapg1k_detail {
178
179/** Machine epsilon of T, the "a whole block added nothing" threshold. */
180template <class T>
181T eps_of() {
182 return std::numeric_limits<T>::epsilon();
183}
184
185/** Mean of the service law. */
186template <class T>
187T service_mean(const ServiceLaw<T>& svc) {
188 switch (svc.kind) {
190 return svc.shape * svc.scale;
192 return svc.det;
194 const std::size_t p = svc.ph_T.rows();
195 const std::vector<T> e = ones<T>(p);
196 const std::vector<T> x = solve(svc.ph_T, e); // T^-1 e
198 for (std::size_t i = 0; i < p; ++i) s -= svc.ph_alpha[i] * x[i];
199 return s;
200 }
202 break;
203 }
204 // Density: E[S] under the same log substitution used for the c_n.
205 return num_traits<T>::from_int(0); // replaced by the caller, see density_moment
206}
207
208/**
209 * E[w(S)] for a density-specified service law, under the substitution
210 * x = exp(u): the integrand becomes w(e^u) f(e^u) e^u, which tends to zero at
211 * both ends because w is bounded and integrability of f forces x f(x) -> 0.
212 * The window is widened geometrically to the left (and to the right when the
213 * support is unbounded) until a whole new panel adds less than the tolerance.
214 */
215template <class T, class W>
216T density_moment(const ServiceLaw<T>& svc, W&& w, const T& reltol) {
217 const T zero = num_traits<T>::from_int(0);
218 using std::exp;
219 using std::log;
220 std::function<T(const T&)> g = [&](const T& u) -> T {
221 const T x = exp(u);
222 const T v = w(x) * svc.pdf(x) * x;
223 return (v == v && num_abs(v) < std::numeric_limits<T>::infinity()) ? v : zero;
224 };
225 // Seed window around log of a representative scale: start at [-1, 1] and
226 // widen by 4 in u each round, so the covered range of x doubles in decades.
227 const T hi0 = svc.tmax_finite ? T(log(svc.tmax)) : num_traits<T>::from_int(1);
228 T lo = hi0 - num_traits<T>::from_int(2);
229 T hi = hi0;
230 const T abstol = num_traits<T>::from_double(1e-300);
231 T total = detail::num_integral<T>(g, lo, hi, reltol, abstol, 40u);
232 const unsigned rounds = 60u;
233 for (unsigned k = 0; k < rounds; ++k) {
234 const T lonew = lo - num_traits<T>::from_int(4);
235 const T add_lo = detail::num_integral<T>(g, lonew, lo, reltol, abstol, 40u);
236 lo = lonew;
237 T add_hi = zero;
238 if (!svc.tmax_finite) {
239 const T hinew = hi + num_traits<T>::from_int(4);
240 add_hi = detail::num_integral<T>(g, hi, hinew, reltol, abstol, 40u);
241 hi = hinew;
242 }
243 total += add_lo + add_hi;
244 const T added = num_abs(T(add_lo)) + num_abs(T(add_hi));
245 if (added <= reltol * num_abs(total)) break;
246 }
247 return total;
248}
249
250/**
251 * c_n = E[e^{-theta S}(theta S)^n/n!] for n = 0..N, together with the mean.
252 * sum_n c_n = E[e^{-theta S} e^{theta S}] = 1 exactly, which both sets the
253 * truncation order and certifies it.
254 */
255template <class T>
256struct ServiceCoefficients {
257 std::vector<T> cn;
258 T mean;
259 T residual; ///< |1 - sum_n c_n|
260};
261
262template <class T>
263ServiceCoefficients<T> service_coefficients(const ServiceLaw<T>& svc, const T& theta, const T& tol,
264 std::size_t nmaxCap) {
265 using std::exp;
266 using std::log;
267 using std::pow;
268 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
269 ServiceCoefficients<T> out;
270 const T qreltol = num_traits<T>::from_double(1e-13);
271 out.mean = (svc.kind == ServiceKind::Density)
272 ? density_moment(svc, [](const T& x) { return x; }, qreltol)
273 : service_mean(svc);
274 if (out.mean <= zero) throw InputError("qsys_mapg1k: service law has non-positive mean");
275
276 // Initial order: the mean of the Poisson-like count plus a deviation
277 // allowance, exactly the reference's i_guess.
278 std::size_t n0;
279 {
280 const double md = num_traits<T>::to_double(T(theta * out.mean));
281 const double g = md + 10.0 * std::sqrt(md > 1.0 ? md : 1.0) + 32.0;
282 n0 = static_cast<std::size_t>(g < 32.0 ? 32.0 : std::ceil(g));
283 if (n0 + 1 > nmaxCap) n0 = nmaxCap > 0 ? nmaxCap - 1 : 0;
284 }
285
286 std::vector<T>& cn = out.cn;
287 // Per-family generator of c_n from c_{n-1}, plus the carried state.
288 switch (svc.kind) {
289 case ServiceKind::Gamma: {
290 const T th = svc.scale, al = svc.shape;
291 if (al <= zero || th <= zero)
292 throw InputError("qsys_mapg1k: gamma shape and scale must be positive");
293 const T q = one + th * theta;
294 const T p = th * theta / q;
295 cn.push_back(exp(-al * log(q)));
296 for (std::size_t n = 1; n <= n0; ++n) {
297 const T nT = num_traits<T>::from_int(static_cast<long>(n));
298 cn.push_back(cn[n - 1] * p * (al + nT - one) / nT);
299 }
300 break;
301 }
303 if (svc.det <= zero)
304 throw InputError("qsys_mapg1k: deterministic service time must be positive");
305 const T m = theta * svc.det;
306 cn.push_back(exp(-m));
307 for (std::size_t n = 1; n <= n0; ++n)
308 cn.push_back(cn[n - 1] * m / num_traits<T>::from_int(static_cast<long>(n)));
309 break;
310 }
312 const std::size_t p = svc.ph_T.rows();
313 if (svc.ph_alpha.size() != p || svc.ph_T.cols() != p)
314 throw InputError("qsys_mapg1k: PH alpha and T are inconsistent");
315 Matrix<T> ThI(p, p);
316 for (std::size_t i = 0; i < p; ++i)
317 for (std::size_t j = 0; j < p; ++j)
318 ThI(i, j) = (i == j ? theta : zero) - svc.ph_T(i, j);
319 const Matrix<T> Minv = inverse(ThI);
320 const std::vector<T> e = ones<T>(p);
321 std::vector<T> t = mulvec(svc.ph_T, e);
322 for (T& v : t) v = -v;
323 std::vector<T> row = vecmul(svc.ph_alpha, Minv); // alpha M
324 for (std::size_t n = 0; n <= n0; ++n) {
325 T v = zero;
326 for (std::size_t i = 0; i < p; ++i) v += row[i] * t[i];
327 cn.push_back(v);
328 row = vecmul(row, Minv);
329 for (T& x : row) x *= theta;
330 }
331 break;
332 }
334 if (!svc.pdf) throw InputError("qsys_mapg1k: density service law has no pdf");
335 for (std::size_t n = 0; n <= n0; ++n) {
336 const T nT = num_traits<T>::from_int(static_cast<long>(n));
337 const T lfact = log(num_factorial<T>(static_cast<unsigned>(n)));
338 cn.push_back(density_moment(
339 svc,
340 [&](const T& x) {
341 return exp(-theta * x + nT * log(theta * x) - lfact);
342 },
343 qreltol));
344 }
345 break;
346 }
347 }
348 // series growth termination rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
349 T total = zero;
350 for (const T& v : cn) total += v;
351 while (cn.size() < nmaxCap) {
352 if (num_abs(T(one - total)) <= tol) break;
353 const std::size_t first = cn.size();
354 const std::size_t last = (first + 63 < nmaxCap) ? first + 63 : nmaxCap - 1;
355 T added = zero;
356 switch (svc.kind) {
357 case ServiceKind::Gamma: {
358 const T q = one + svc.scale * theta;
359 const T p = svc.scale * theta / q;
360 for (std::size_t n = first; n <= last; ++n) {
361 const T nT = num_traits<T>::from_int(static_cast<long>(n));
362 cn.push_back(cn[n - 1] * p * (svc.shape + nT - one) / nT);
363 added += cn.back();
364 }
365 break;
366 }
367 case ServiceKind::Deterministic: {
368 const T m = theta * svc.det;
369 for (std::size_t n = first; n <= last; ++n) {
370 cn.push_back(cn[n - 1] * m / num_traits<T>::from_int(static_cast<long>(n)));
371 added += cn.back();
372 }
373 break;
374 }
375 case ServiceKind::PhaseType: {
376 // block-restart rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
377 const std::size_t p = svc.ph_T.rows();
378 Matrix<T> ThI(p, p);
379 for (std::size_t i = 0; i < p; ++i)
380 for (std::size_t j = 0; j < p; ++j)
381 ThI(i, j) = (i == j ? theta : zero) - svc.ph_T(i, j);
382 const Matrix<T> Minv = inverse(ThI);
383 const std::vector<T> e = ones<T>(p);
384 std::vector<T> t = mulvec(svc.ph_T, e);
385 for (T& v : t) v = -v;
386 std::vector<T> row = vecmul(svc.ph_alpha, Minv);
387 for (std::size_t n = 0; n < first; ++n) {
388 row = vecmul(row, Minv);
389 for (T& x : row) x *= theta;
390 }
391 for (std::size_t n = first; n <= last; ++n) {
392 T v = zero;
393 for (std::size_t i = 0; i < p; ++i) v += row[i] * t[i];
394 cn.push_back(v);
395 added += v;
396 row = vecmul(row, Minv);
397 for (T& x : row) x *= theta;
398 }
399 break;
400 }
401 case ServiceKind::Density: {
402 for (std::size_t n = first; n <= last; ++n) {
403 const T nT = num_traits<T>::from_int(static_cast<long>(n));
404 const T lfact = log(num_factorial<T>(static_cast<unsigned>(n)));
405 cn.push_back(density_moment(
406 svc,
407 [&](const T& x) { return exp(-theta * x + nT * log(theta * x) - lfact); },
408 qreltol));
409 added += cn.back();
410 }
411 break;
412 }
413 }
414 total += added;
415 if (added <= eps_of<T>() * total) break;
416 }
417 out.residual = num_abs(T(one - total));
418 return out;
419}
420
421} // namespace mapg1k_detail
422
423/**
424 * MAP/G/1/K with tail drop.
425 *
426 * @param arrival arrival MAP (D0, D1)
427 * @param svc service law
428 * @param K buffer size in packets, K >= 1, the one in service included
429 * @param tol uniformization truncation tolerance
430 * @param nmaxCap cap on the uniformization order
431 */
432template <class T>
433MapG1kResult<T> qsys_mapg1k(const mam::Map<T>& arrival, const ServiceLaw<T>& svc, std::size_t K,
434 const T& tol, std::size_t nmaxCap) {
436 "qsys_mapg1k requires transcendental arithmetic");
437 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
438 const std::size_t M = arrival.D0.rows();
439 if (arrival.D0.cols() != M || arrival.D1.rows() != M || arrival.D1.cols() != M)
440 throw InputError("qsys_mapg1k: D0 and D1 must be square matrices of equal size");
441 if (K < 1) throw InputError("qsys_mapg1k: buffer size K must be a positive integer");
442 const Matrix<T>& D0 = arrival.D0;
443 const Matrix<T>& D1 = arrival.D1;
444
445 T theta = zero;
446 for (std::size_t i = 0; i < M; ++i) {
447 const T b = -D0(i, i);
448 if (b <= zero)
449 throw InputError("qsys_mapg1k: D0 must have strictly negative diagonal entries");
450 if (b > theta) theta = b;
451 }
452
453 const mapg1k_detail::ServiceCoefficients<T> sc =
454 mapg1k_detail::service_coefficients(svc, theta, tol, nmaxCap);
455 const std::vector<T>& cn = sc.cn;
456 const T Smean = sc.mean;
457 const std::size_t nmax = cn.size() - 1;
458 if (sc.residual > num_traits<T>::from_double(1e-6))
459 throw NumericError(
460 "qsys_mapg1k: the uniformization series for c_n was truncated with residual " +
461 num_traits<T>::to_string(sc.residual) + "; raise nmax");
462
463 // telescoping-form rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
464 std::vector<T> dn(nmax + 1, zero);
465 {
466 T tail = zero;
467 for (std::size_t n = nmax + 1; n-- > 0;) {
468 dn[n] = tail / theta; // tail is sum_{k > n} c_k at this point
469 tail += cn[n];
470 }
471 }
472
473 // A_m and Q_m for m = 0..K-1, plus B0 = sum_m A_m and Qtot = sum_m Q_m.
474 const std::size_t mmax = K - 1;
475 std::vector<Matrix<T>> A(mmax + 1, Matrix<T>(M, M, zero));
476 std::vector<Matrix<T>> Q(mmax + 1, Matrix<T>(M, M, zero));
477 std::vector<Matrix<T>> Sn(mmax + 1, Matrix<T>(M, M, zero));
478 Sn[0] = eye<T>(M);
479 Matrix<T> B0(M, M, zero), Qtot(M, M, zero);
480 Matrix<T> Pn = eye<T>(M);
481 Matrix<T> Pt0(M, M), Pt1(M, M), PD(M, M);
482 for (std::size_t i = 0; i < M; ++i)
483 for (std::size_t j = 0; j < M; ++j) {
484 Pt0(i, j) = (i == j ? one : zero) + D0(i, j) / theta;
485 Pt1(i, j) = D1(i, j) / theta;
486 PD(i, j) = (i == j ? one : zero) + (D0(i, j) + D1(i, j)) / theta;
487 }
488 for (std::size_t n = 0; n <= nmax; ++n) {
489 const std::size_t mtop = (n < mmax) ? n : mmax;
490 for (std::size_t m = 0; m <= mtop; ++m)
491 for (std::size_t i = 0; i < M; ++i)
492 for (std::size_t j = 0; j < M; ++j) {
493 A[m](i, j) += Sn[m](i, j) * cn[n];
494 Q[m](i, j) += Sn[m](i, j) * dn[n];
495 }
496 for (std::size_t i = 0; i < M; ++i)
497 for (std::size_t j = 0; j < M; ++j) {
498 B0(i, j) += Pn(i, j) * cn[n];
499 Qtot(i, j) += Pn(i, j) * dn[n];
500 }
501 if (n < nmax) {
502 std::vector<Matrix<T>> Snew(mmax + 1, Matrix<T>(M, M, zero));
503 const std::size_t mt = (n + 1 < mmax) ? n + 1 : mmax;
504 for (std::size_t m = 0; m <= mt; ++m) {
505 Matrix<T> acc(M, M, zero);
506 if (m <= n) acc = matmul(Sn[m], Pt0);
507 if (m >= 1 && m - 1 <= n) {
508 const Matrix<T> add = matmul(Sn[m - 1], Pt1);
509 for (std::size_t i = 0; i < M; ++i)
510 for (std::size_t j = 0; j < M; ++j) acc(i, j) += add(i, j);
511 }
512 Snew[m] = acc;
513 }
514 Sn = Snew;
515 Pn = matmul(Pn, PD);
516 }
517 }
518
519 const std::vector<T> e = ones<T>(M);
520 Matrix<T> negD0(M, M);
521 for (std::size_t i = 0; i < M; ++i)
522 for (std::size_t j = 0; j < M; ++j) negD0(i, j) = -D0(i, j);
523 const Matrix<T> negD0inv = inverse(negD0);
524 const Matrix<T> Psi = matmul(negD0inv, D1); // phase at the arrival ending an idle period
525 const std::vector<T> idle = mulvec(negD0inv, e);
526
527 // Embedded chain at departure epochs, state (n,j) -> index n M + j.
528 Matrix<T> P(K * M, K * M, zero);
529 const std::size_t lastblk = (K - 1) * M;
530 for (std::size_t n = 1; n + 1 <= K; ++n) {
531 Matrix<T> Bacc = B0;
532 for (std::size_t m = 0; m + n + 1 <= K; ++m) {
533 const std::size_t col = (n - 1 + m) * M;
534 for (std::size_t i = 0; i < M; ++i)
535 for (std::size_t j = 0; j < M; ++j) {
536 P(n * M + i, col + j) += A[m](i, j);
537 Bacc(i, j) -= A[m](i, j);
538 }
539 }
540 for (std::size_t i = 0; i < M; ++i)
541 for (std::size_t j = 0; j < M; ++j) P(n * M + i, lastblk + j) += Bacc(i, j);
542 }
543 {
544 Matrix<T> Bacc = B0;
545 for (std::size_t m = 0; m + 2 <= K; ++m) {
546 const Matrix<T> blk = matmul(Psi, A[m]);
547 for (std::size_t i = 0; i < M; ++i)
548 for (std::size_t j = 0; j < M; ++j) {
549 P(i, m * M + j) += blk(i, j);
550 Bacc(i, j) -= A[m](i, j);
551 }
552 }
553 const Matrix<T> tailblk = matmul(Psi, Bacc);
554 for (std::size_t i = 0; i < M; ++i)
555 for (std::size_t j = 0; j < M; ++j) P(i, lastblk + j) += tailblk(i, j);
556 }
557
558 {
559 T rowdev = zero;
560 for (std::size_t i = 0; i < K * M; ++i) {
561 T s = zero;
562 for (std::size_t j = 0; j < K * M; ++j) s += P(i, j);
563 const T d = num_abs(T(s - one));
564 if (d > rowdev) rowdev = d;
565 }
566 if (rowdev > num_traits<T>::from_double(1e-8))
567 throw NumericError(
568 "qsys_mapg1k: embedded chain rows deviate from 1 by " +
570 "; the uniformization series for A_m has not converged, raise nmax");
571 }
572
573 const std::vector<T> sigma = mc::dtmc_solve(P);
574 std::vector<T> sigma0(sigma.begin(), sigma.begin() + M);
575
576 // Markov renewal reward over one inter-departure cycle.
577 T idleTime = zero;
578 for (std::size_t j = 0; j < M; ++j) idleTime += sigma0[j] * idle[j];
579 const T Ecyc = Smean + idleTime;
580 const T Tput = one / Ecyc;
581 const T p0 = idleTime / Ecyc;
582
583 // Expected time with the buffer full during a cycle, resolved by phase.
584 std::vector<Matrix<T>> Qcum(mmax + 1, Matrix<T>(M, M, zero));
585 {
586 Matrix<T> acc(M, M, zero);
587 for (std::size_t m = 0; m <= mmax; ++m) {
588 for (std::size_t i = 0; i < M; ++i)
589 for (std::size_t j = 0; j < M; ++j) acc(i, j) += Q[m](i, j);
590 Qcum[m] = acc;
591 }
592 }
593 std::vector<T> timeKvec(M, zero);
594 for (std::size_t n = 1; n + 1 <= K; ++n) {
595 const std::size_t rr = K - n - 1;
596 std::vector<T> row(sigma.begin() + n * M, sigma.begin() + (n + 1) * M);
597 Matrix<T> D = Qtot;
598 for (std::size_t i = 0; i < M; ++i)
599 for (std::size_t j = 0; j < M; ++j) D(i, j) -= Qcum[rr](i, j);
600 const std::vector<T> t = vecmul(row, D);
601 for (std::size_t j = 0; j < M; ++j) timeKvec[j] += t[j];
602 }
603 {
604 const std::vector<T> s0Psi = vecmul(sigma0, Psi);
605 Matrix<T> D = Qtot;
606 if (K >= 2)
607 for (std::size_t i = 0; i < M; ++i)
608 for (std::size_t j = 0; j < M; ++j) D(i, j) -= Qcum[K - 2](i, j);
609 const std::vector<T> t = vecmul(s0Psi, D);
610 for (std::size_t j = 0; j < M; ++j) timeKvec[j] += t[j];
611 }
612 std::vector<T> pKvec(M, zero);
613 T pK = zero;
614 for (std::size_t j = 0; j < M; ++j) {
615 pKvec[j] = timeKvec[j] / Ecyc;
616 pK += pKvec[j];
617 }
618
619 // Expected time at every level, from the same Q_m.
620 std::vector<T> timeL(K + 1, zero);
621 timeL[0] = idleTime;
622 for (std::size_t n = 1; n + 1 <= K; ++n) {
623 std::vector<T> row(sigma.begin() + n * M, sigma.begin() + (n + 1) * M);
624 for (std::size_t l = n; l + 1 <= K; ++l) {
625 const std::vector<T> t = vecmul(row, Q[l - n]);
626 for (std::size_t j = 0; j < M; ++j) timeL[l] += t[j];
627 }
628 }
629 {
630 const std::vector<T> s0Psi = vecmul(sigma0, Psi);
631 for (std::size_t l = 1; l + 1 <= K; ++l) {
632 const std::vector<T> t = vecmul(s0Psi, Q[l - 1]);
633 for (std::size_t j = 0; j < M; ++j) timeL[l] += t[j];
634 }
635 }
636 {
637 T s = zero;
638 for (std::size_t j = 0; j < M; ++j) s += timeKvec[j];
639 timeL[K] = s;
640 }
641 std::vector<T> plevel(K + 1, zero);
642 T mass = zero;
643 for (std::size_t l = 0; l <= K; ++l) {
644 plevel[l] = timeL[l] / Ecyc;
645 mass += plevel[l];
646 }
647 if (num_abs(T(mass - one)) > num_traits<T>::from_double(1e-8))
648 throw NumericError("qsys_mapg1k: the level distribution has mass " +
650 "; the Q_m series has not converged, raise nmax");
651 T meanQ = zero;
652 for (std::size_t l = 0; l <= K; ++l)
653 meanQ += num_traits<T>::from_int(static_cast<long>(l)) * plevel[l];
654
655 std::vector<T> p0vec = vecmul(sigma0, negD0inv);
656 for (T& v : p0vec) v /= Ecyc;
657
658 const T lambda = mam::map_lambda(arrival);
659
661 r.p0 = p0;
662 r.pK = pK;
663 r.throughput = Tput;
664 r.lossProbability = one - Tput / lambda;
665 r.lambda = lambda;
666 r.meanServiceTime = Smean;
667 r.utilization = one - p0;
668 r.rho = lambda * Smean;
669 r.meanQueueLength = meanQ;
670 r.nmax = nmax;
671 r.countingResidual = sc.residual;
672 r.sigma = sigma;
673 r.pKvec = pKvec;
674 r.p0vec = p0vec;
675 r.plevel = plevel;
676 return r;
677}
678
679/** qsys_mapg1k with the reference defaults tol = 1e-12, nmax = 200000. */
680template <class T>
681MapG1kResult<T> qsys_mapg1k(const mam::Map<T>& arrival, const ServiceLaw<T>& svc, std::size_t K) {
682 return qsys_mapg1k(arrival, svc, K, T(num_traits<T>::from_double(1e-12)),
683 static_cast<std::size_t>(200000));
684}
685
686} // namespace qsys
687} // namespace line
688
689#endif // LINE_API_QSYS_QSYS_MAPG1K_H
Malformed or inconsistent input (dimensions, negative populations, ...).
Definition error.h:37
InputError(const std::string &what)
Definition error.h:39
The algorithm cannot proceed on this instance (singular matrix, ...).
Definition error.h:43
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LU factorization with partial pivoting, templated on the number type.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
ServiceKind
Which family the service law belongs to.
Definition qsys_mapg1k.h:99
MapG1kResult< T > qsys_mapg1k(const mam::Map< T > &arrival, const ServiceLaw< T > &svc, std::size_t K, const T &tol, std::size_t nmaxCap)
MAP/G/1/K with tail drop.
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
T num_abs(const T &v)
Definition number.h:172
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
Definition linalg.h:62
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
Adaptive quadrature for the qsys functions whose MATLAB originals call integral(),...
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
Return value of qsys_mapg1k, mirroring the MATLAB result struct.
std::vector< T > plevel
P(level = l), l = 0..K.
T throughput
aggregate departure rate
T rho
offered load lambda S
std::vector< T > pKvec
P(level = K, phase j), summing to pK.
std::size_t nmax
uniformization order used
std::vector< T > p0vec
P(level = 0, phase j), summing to p0.
T pK
P(buffer full).
T lambda
aggregate MAP arrival rate
T countingResidual
|1 - sum_n c_n| at the truncation
T p0
P(buffer empty).
T lossProbability
1 - throughput/lambda
T meanQueueLength
E[number in system].
std::vector< T > sigma
stationary law of the embedded chain, K M entries
Service-time descriptor, the C++ form of the MATLAB svc struct.
static ServiceLaw density(std::function< T(const T &)> f, const T &tmax)
static ServiceLaw density(std::function< T(const T &)> f)
Arbitrary density on (0, inf), or on (0, tmax] when tmax is supplied.
Matrix< T > ph_T
PH subgenerator.
T tmax
upper support limit of the density
T shape
gamma shape alpha
static ServiceLaw deterministic(const T &d)
Constant service time d.
std::vector< T > ph_alpha
PH initial probability row.
T scale
gamma scale theta
T det
deterministic service time
bool tmax_finite
whether tmax is used
static ServiceLaw phase_type(const std::vector< T > &alpha, const Matrix< T > &Tmat)
Phase type (alpha, Tmat).
std::function< T(const T &)> pdf
density of the service time
static ServiceLaw gamma(const T &shape, const T &scale)
Gamma(shape, scale).