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 * - A c_n series truncated with a residual above 1e-6 WARNS (via
47 * qsys_svclaw_coeffs) and the solve continues, as in the reference. The
48 * residual is reported in the result as countingResidual.
49 * - The density path integrates over an EXPANDING finite window in
50 * u = log x rather than over (-Inf, log tmax] in one call, because the
51 * port's adaptive Gauss-Kronrod rule takes finite endpoints. The window is
52 * widened geometrically until a whole new panel contributes less than the
53 * tolerance, so the truncation is measured rather than assumed. The
54 * substitution itself is the reference's: it turns an integrable
55 * singularity x^(al-1) at the origin into e^(al u), which decays smoothly,
56 * so the singularity disappears instead of being resolved.
57 *
58 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: c_0 needs exp or a
59 * non-integer power for every service law, and the series truncation is a
60 * tolerance. Everything after the c_n -- the A_m and Q_m sums, the embedded
61 * chain, its stationary vector and the reward averaging -- is finite exact
62 * matrix algebra and adds no error of its own.
63 *
64 * MEASURED AGREEMENT: see cpp/tests/test_qsys_mapg1k.cpp. The M/M/1/K collapse
65 * is checked against the closed form (1-rho)rho^K/(1-rho^(K+1)), the M/G/1/K
66 * and MAP/G/1/K instances against MATLAB, and three identities are asserted on
67 * every instance: sum_l plevel(l) = 1, p0 = 1 - T S, and
68 * lossProbability = 1 - T/lambda = pK-weighted arrival loss.
69 *
70 * References:
71 * [1] Chydzinski, A. Per-Flow Throughput of a FIFO Buffer. Applied System
72 * Innovation 2026, 9, 112.
73 * [2] Niu, Z.; Cooper, R.B. Transform-Free Analysis of M/G/1/K and Related
74 * Queues. Mathematics of Operations Research 1993, 18, 486-510.
75 */
76
77#include <cmath>
78#include <cstddef>
79#include <cstdio>
80#include <functional>
81#include <iostream>
82#include <limits>
83#include <string>
84#include <vector>
85
89#include "line/num/number.h"
90#include "line/util/error.h"
92#include "line/util/linalg.h"
93#include "line/util/lu.h"
94#include "line/util/matrix.h"
95
96namespace line {
97namespace qsys {
98
99/** Which family the service law belongs to. */
101
102/**
103 * Service-time descriptor, the C++ form of the MATLAB svc struct.
104 *
105 * Build one with the static factories; the fields that a given kind does not
106 * use are left at their default and are never read.
107 */
108template <class T>
111 T shape = num_traits<T>::from_int(1); ///< gamma shape alpha
112 T scale = num_traits<T>::from_int(1); ///< gamma scale theta
113 T det = num_traits<T>::from_int(1); ///< deterministic service time
114 std::vector<T> ph_alpha; ///< PH initial probability row
115 Matrix<T> ph_T; ///< PH subgenerator
116 std::function<T(const T&)> pdf; ///< density of the service time
117 T tmax = num_traits<T>::from_int(0); ///< upper support limit of the density
118 bool tmax_finite = false; ///< whether tmax is used
119
120 /** Gamma(shape, scale). shape = 1 is the exponential, integer shape Erlang. */
121 static ServiceLaw gamma(const T& shape, const T& scale) {
122 ServiceLaw s;
124 s.shape = shape;
125 s.scale = scale;
126 return s;
127 }
128 /** Constant service time d. */
129 static ServiceLaw deterministic(const T& d) {
130 ServiceLaw s;
132 s.det = d;
133 return s;
134 }
135 /** Phase type (alpha, Tmat). */
136 static ServiceLaw phase_type(const std::vector<T>& alpha, const Matrix<T>& Tmat) {
137 ServiceLaw s;
139 s.ph_alpha = alpha;
140 s.ph_T = Tmat;
141 return s;
142 }
143 /** Arbitrary density on (0, inf), or on (0, tmax] when tmax is supplied. */
144 static ServiceLaw density(std::function<T(const T&)> f) {
145 ServiceLaw s;
147 s.pdf = f;
148 return s;
149 }
150 static ServiceLaw density(std::function<T(const T&)> f, const T& tmax) {
151 ServiceLaw s = density(f);
152 s.tmax = tmax;
153 s.tmax_finite = true;
154 return s;
155 }
156};
157
158/** Return value of qsys_mapg1k, mirroring the MATLAB result struct. */
159template <class T>
161 T p0; ///< P(buffer empty)
162 T pK; ///< P(buffer full)
163 T throughput; ///< aggregate departure rate
164 T lossProbability; ///< 1 - throughput/lambda
165 T lambda; ///< aggregate MAP arrival rate
167 T utilization; ///< 1 - p0
168 T rho; ///< offered load lambda S
169 T meanQueueLength; ///< E[number in system]
170 std::size_t nmax; ///< uniformization order used
171 T countingResidual; ///< |1 - sum_n c_n| at the truncation
172 std::vector<T> sigma; ///< stationary law of the embedded chain, K M entries
173 std::vector<T> pKvec; ///< P(level = K, phase j), summing to pK
174 std::vector<T> p0vec; ///< P(level = 0, phase j), summing to p0
175 std::vector<T> plevel; ///< P(level = l), l = 0..K
176};
177
178namespace mapg1k_detail {
179
180/** Machine epsilon of T, the "a whole block added nothing" threshold. */
181template <class T>
182T eps_of() {
183 return std::numeric_limits<T>::epsilon();
184}
185
186/** Mean of the service law. */
187template <class T>
188T service_mean(const ServiceLaw<T>& svc) {
189 switch (svc.kind) {
191 return svc.shape * svc.scale;
193 return svc.det;
195 const std::size_t p = svc.ph_T.rows();
196 const std::vector<T> e = ones<T>(p);
197 const std::vector<T> x = solve(svc.ph_T, e); // T^-1 e
199 for (std::size_t i = 0; i < p; ++i) s -= svc.ph_alpha[i] * x[i];
200 return s;
201 }
203 break;
204 }
205 // Density: E[S] under the same log substitution used for the c_n.
206 return num_traits<T>::from_int(0); // replaced by the caller, see density_moment
207}
208
209/**
210 * E[w(S)] for a density-specified service law, under the substitution
211 * x = exp(u): the integrand becomes w(e^u) f(e^u) e^u, which tends to zero at
212 * both ends because w is bounded and integrability of f forces x f(x) -> 0.
213 * The window is widened geometrically to the left (and to the right when the
214 * support is unbounded) until a whole new panel adds less than the tolerance.
215 */
216template <class T, class W>
217T density_moment(const ServiceLaw<T>& svc, W&& w, const T& reltol) {
218 const T zero = num_traits<T>::from_int(0);
219 using std::exp;
220 using std::log;
221 std::function<T(const T&)> g = [&](const T& u) -> T {
222 const T x = exp(u);
223 const T v = w(x) * svc.pdf(x) * x;
224 return (v == v && num_abs(v) < std::numeric_limits<T>::infinity()) ? v : zero;
225 };
226 // Seed window around log of a representative scale: start at [-1, 1] and
227 // widen by 4 in u each round, so the covered range of x doubles in decades.
228 const T hi0 = svc.tmax_finite ? T(log(svc.tmax)) : num_traits<T>::from_int(1);
229 T lo = hi0 - num_traits<T>::from_int(2);
230 T hi = hi0;
231 const T abstol = num_traits<T>::from_double(1e-300);
232 T total = detail::num_integral<T>(g, lo, hi, reltol, abstol, 40u);
233 const unsigned rounds = 60u;
234 for (unsigned k = 0; k < rounds; ++k) {
235 const T lonew = lo - num_traits<T>::from_int(4);
236 const T add_lo = detail::num_integral<T>(g, lonew, lo, reltol, abstol, 40u);
237 lo = lonew;
238 T add_hi = zero;
239 if (!svc.tmax_finite) {
240 const T hinew = hi + num_traits<T>::from_int(4);
241 add_hi = detail::num_integral<T>(g, hi, hinew, reltol, abstol, 40u);
242 hi = hinew;
243 }
244 total += add_lo + add_hi;
245 const T added = num_abs(T(add_lo)) + num_abs(T(add_hi));
246 if (added <= reltol * num_abs(total)) break;
247 }
248 return total;
249}
250
251/**
252 * c_n = E[e^{-theta S}(theta S)^n/n!] for n = 0..N, together with the mean.
253 * sum_n c_n = E[e^{-theta S} e^{theta S}] = 1 exactly, which both sets the
254 * truncation order and certifies it.
255 */
256template <class T>
257struct ServiceCoefficients {
258 std::vector<T> cn;
259 T mean;
260 T residual; ///< |1 - sum_n c_n|
261};
262
263template <class T>
264ServiceCoefficients<T> service_coefficients(const ServiceLaw<T>& svc, const T& theta, const T& tol,
265 std::size_t nmaxCap) {
266 using std::exp;
267 using std::log;
268 using std::pow;
269 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
270 ServiceCoefficients<T> out;
271 const T qreltol = num_traits<T>::from_double(1e-13);
272 out.mean = (svc.kind == ServiceKind::Density)
273 ? density_moment(svc, [](const T& x) { return x; }, qreltol)
274 : service_mean(svc);
275 if (out.mean <= zero) throw InputError("qsys_mapg1k: service law has non-positive mean");
276
277 // Initial order: the mean of the Poisson-like count plus a deviation
278 // allowance, exactly the reference's i_guess.
279 std::size_t n0;
280 {
281 const double md = num_traits<T>::to_double(T(theta * out.mean));
282 const double g = md + 10.0 * std::sqrt(md > 1.0 ? md : 1.0) + 32.0;
283 n0 = static_cast<std::size_t>(g < 32.0 ? 32.0 : std::ceil(g));
284 if (n0 > nmaxCap) n0 = nmaxCap; // reference i_guess: min(cap, ...), so cn may hold nmax+1 terms
285 }
286
287 std::vector<T>& cn = out.cn;
288 // Per-family generator of c_n from c_{n-1}, plus the carried state.
289 switch (svc.kind) {
290 case ServiceKind::Gamma: {
291 const T th = svc.scale, al = svc.shape;
292 if (al <= zero || th <= zero)
293 throw InputError("qsys_mapg1k: gamma shape and scale must be positive");
294 const T q = one + th * theta;
295 const T p = th * theta / q;
296 cn.push_back(exp(-al * log(q)));
297 for (std::size_t n = 1; n <= n0; ++n) {
298 const T nT = num_traits<T>::from_int(static_cast<long>(n));
299 cn.push_back(cn[n - 1] * p * (al + nT - one) / nT);
300 }
301 break;
302 }
304 if (svc.det <= zero)
305 throw InputError("qsys_mapg1k: deterministic service time must be positive");
306 const T m = theta * svc.det;
307 cn.push_back(exp(-m));
308 for (std::size_t n = 1; n <= n0; ++n)
309 cn.push_back(cn[n - 1] * m / num_traits<T>::from_int(static_cast<long>(n)));
310 break;
311 }
313 const std::size_t p = svc.ph_T.rows();
314 if (svc.ph_alpha.size() != p || svc.ph_T.cols() != p)
315 throw InputError("qsys_mapg1k: PH alpha and T are inconsistent");
316 Matrix<T> ThI(p, p);
317 for (std::size_t i = 0; i < p; ++i)
318 for (std::size_t j = 0; j < p; ++j)
319 ThI(i, j) = (i == j ? theta : zero) - svc.ph_T(i, j);
320 const Matrix<T> Minv = inverse(ThI);
321 const std::vector<T> e = ones<T>(p);
322 std::vector<T> t = mulvec(svc.ph_T, e);
323 for (T& v : t) v = -v;
324 std::vector<T> row = vecmul(svc.ph_alpha, Minv); // alpha M
325 for (std::size_t n = 0; n <= n0; ++n) {
326 T v = zero;
327 for (std::size_t i = 0; i < p; ++i) v += row[i] * t[i];
328 cn.push_back(v);
329 row = vecmul(row, Minv);
330 for (T& x : row) x *= theta;
331 }
332 break;
333 }
335 if (!svc.pdf) throw InputError("qsys_mapg1k: density service law has no pdf");
336 for (std::size_t n = 0; n <= n0; ++n) {
337 const T nT = num_traits<T>::from_int(static_cast<long>(n));
338 const T lfact = log(num_factorial<T>(static_cast<unsigned>(n)));
339 cn.push_back(density_moment(
340 svc,
341 [&](const T& x) {
342 return exp(-theta * x + nT * log(theta * x) - lfact);
343 },
344 qreltol));
345 }
346 break;
347 }
348 }
349 // series growth termination rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
350 T total = zero;
351 for (const T& v : cn) total += v;
352 while (cn.size() < nmaxCap) {
353 if (num_abs(T(one - total)) <= tol) break;
354 const std::size_t first = cn.size();
355 const std::size_t last = (first + 63 < nmaxCap) ? first + 63 : nmaxCap - 1;
356 T added = zero;
357 switch (svc.kind) {
358 case ServiceKind::Gamma: {
359 const T q = one + svc.scale * theta;
360 const T p = svc.scale * theta / q;
361 for (std::size_t n = first; n <= last; ++n) {
362 const T nT = num_traits<T>::from_int(static_cast<long>(n));
363 cn.push_back(cn[n - 1] * p * (svc.shape + nT - one) / nT);
364 added += cn.back();
365 }
366 break;
367 }
368 case ServiceKind::Deterministic: {
369 const T m = theta * svc.det;
370 for (std::size_t n = first; n <= last; ++n) {
371 cn.push_back(cn[n - 1] * m / num_traits<T>::from_int(static_cast<long>(n)));
372 added += cn.back();
373 }
374 break;
375 }
376 case ServiceKind::PhaseType: {
377 // block-restart rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
378 const std::size_t p = svc.ph_T.rows();
379 Matrix<T> ThI(p, p);
380 for (std::size_t i = 0; i < p; ++i)
381 for (std::size_t j = 0; j < p; ++j)
382 ThI(i, j) = (i == j ? theta : zero) - svc.ph_T(i, j);
383 const Matrix<T> Minv = inverse(ThI);
384 const std::vector<T> e = ones<T>(p);
385 std::vector<T> t = mulvec(svc.ph_T, e);
386 for (T& v : t) v = -v;
387 std::vector<T> row = vecmul(svc.ph_alpha, Minv);
388 for (std::size_t n = 0; n < first; ++n) {
389 row = vecmul(row, Minv);
390 for (T& x : row) x *= theta;
391 }
392 for (std::size_t n = first; n <= last; ++n) {
393 T v = zero;
394 for (std::size_t i = 0; i < p; ++i) v += row[i] * t[i];
395 cn.push_back(v);
396 added += v;
397 row = vecmul(row, Minv);
398 for (T& x : row) x *= theta;
399 }
400 break;
401 }
402 case ServiceKind::Density: {
403 for (std::size_t n = first; n <= last; ++n) {
404 const T nT = num_traits<T>::from_int(static_cast<long>(n));
405 const T lfact = log(num_factorial<T>(static_cast<unsigned>(n)));
406 cn.push_back(density_moment(
407 svc,
408 [&](const T& x) { return exp(-theta * x + nT * log(theta * x) - lfact); },
409 qreltol));
410 added += cn.back();
411 }
412 break;
413 }
414 }
415 total += added;
416 if (added <= eps_of<T>() * total) break;
417 }
418 out.residual = num_abs(T(one - total));
419 return out;
420}
421
422} // namespace mapg1k_detail
423
424/** Outputs [cn, Smean] of qsys_svclaw_coeffs.m, plus the truncation residual. */
425template <class T>
427 std::vector<T> cn; ///< c_n for n = 0..N
428 T Smean; ///< mean service time
429 T residual; ///< |1 - sum_n c_n|
430};
431
432/**
433 * qsys_svclaw_coeffs: uniformization coefficients of a service-time descriptor;
434 * see qsys_svclaw_coeffs.h. Defined here because qsys_mapg1k calls it.
435 */
436template <class T>
437SvclawCoeffsResult<T> qsys_svclaw_coeffs(const ServiceLaw<T>& svc, const T& theta, const T& tol,
438 std::size_t nmaxCap) {
439 const mapg1k_detail::ServiceCoefficients<T> sc =
440 mapg1k_detail::service_coefficients(svc, theta, tol, nmaxCap);
441 // line_warning(mfilename, ...) in qsys_svclaw_coeffs.m: printed unless the session is SILENT
442 if (sc.residual > num_traits<T>::from_double(1e-6) &&
444 char buf[160];
445 std::snprintf(buf, sizeof(buf),
446 "Uniformization series truncated at n=%d with residual %g; increase 'nmax'.",
447 static_cast<int>(sc.cn.size()) - 1, num_traits<T>::to_double(sc.residual));
448 std::cerr << "[LINE] Warning: qsys_svclaw_coeffs: " << buf << std::endl;
449 }
451 r.cn = sc.cn;
452 r.Smean = sc.mean;
453 r.residual = sc.residual;
454 return r;
455}
456
457/**
458 * MAP/G/1/K with tail drop.
459 *
460 * @param arrival arrival MAP (D0, D1)
461 * @param svc service law
462 * @param K buffer size in packets, K >= 1, the one in service included
463 * @param tol uniformization truncation tolerance
464 * @param nmaxCap cap on the uniformization order
465 */
466template <class T>
467MapG1kResult<T> qsys_mapg1k(const mam::Map<T>& arrival, const ServiceLaw<T>& svc, std::size_t K,
468 const T& tol, std::size_t nmaxCap) {
470 "qsys_mapg1k requires transcendental arithmetic");
471 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
472 const std::size_t M = arrival.D0.rows();
473 if (arrival.D0.cols() != M || arrival.D1.rows() != M || arrival.D1.cols() != M)
474 throw InputError("qsys_mapg1k: D0 and D1 must be square matrices of equal size");
475 if (K < 1) throw InputError("qsys_mapg1k: buffer size K must be a positive integer");
476 const Matrix<T>& D0 = arrival.D0;
477 const Matrix<T>& D1 = arrival.D1;
478
479 T theta = zero;
480 for (std::size_t i = 0; i < M; ++i) {
481 const T b = -D0(i, i);
482 if (b <= zero)
483 throw InputError("qsys_mapg1k: D0 must have strictly negative diagonal entries");
484 if (b > theta) theta = b;
485 }
486
487 // a truncated series warns inside qsys_svclaw_coeffs and the solve continues, as in the reference
488 const SvclawCoeffsResult<T> sc = qsys_svclaw_coeffs(svc, theta, tol, nmaxCap);
489 const std::vector<T>& cn = sc.cn;
490 const T Smean = sc.Smean;
491 const std::size_t nmax = cn.size() - 1;
492
493 // telescoping-form rationale: see _kb/03-api-layer.md (cpp port notes: qsys)
494 std::vector<T> dn(nmax + 1, zero);
495 {
496 T tail = zero;
497 for (std::size_t n = nmax + 1; n-- > 0;) {
498 dn[n] = tail / theta; // tail is sum_{k > n} c_k at this point
499 tail += cn[n];
500 }
501 }
502
503 // A_m and Q_m for m = 0..K-1, plus B0 = sum_m A_m and Qtot = sum_m Q_m.
504 const std::size_t mmax = K - 1;
505 std::vector<Matrix<T>> A(mmax + 1, Matrix<T>(M, M, zero));
506 std::vector<Matrix<T>> Q(mmax + 1, Matrix<T>(M, M, zero));
507 std::vector<Matrix<T>> Sn(mmax + 1, Matrix<T>(M, M, zero));
508 Sn[0] = eye<T>(M);
509 Matrix<T> B0(M, M, zero), Qtot(M, M, zero);
510 Matrix<T> Pn = eye<T>(M);
511 Matrix<T> Pt0(M, M), Pt1(M, M), PD(M, M);
512 for (std::size_t i = 0; i < M; ++i)
513 for (std::size_t j = 0; j < M; ++j) {
514 Pt0(i, j) = (i == j ? one : zero) + D0(i, j) / theta;
515 Pt1(i, j) = D1(i, j) / theta;
516 PD(i, j) = (i == j ? one : zero) + (D0(i, j) + D1(i, j)) / theta;
517 }
518 for (std::size_t n = 0; n <= nmax; ++n) {
519 const std::size_t mtop = (n < mmax) ? n : mmax;
520 for (std::size_t m = 0; m <= mtop; ++m)
521 for (std::size_t i = 0; i < M; ++i)
522 for (std::size_t j = 0; j < M; ++j) {
523 A[m](i, j) += Sn[m](i, j) * cn[n];
524 Q[m](i, j) += Sn[m](i, j) * dn[n];
525 }
526 for (std::size_t i = 0; i < M; ++i)
527 for (std::size_t j = 0; j < M; ++j) {
528 B0(i, j) += Pn(i, j) * cn[n];
529 Qtot(i, j) += Pn(i, j) * dn[n];
530 }
531 if (n < nmax) {
532 std::vector<Matrix<T>> Snew(mmax + 1, Matrix<T>(M, M, zero));
533 const std::size_t mt = (n + 1 < mmax) ? n + 1 : mmax;
534 for (std::size_t m = 0; m <= mt; ++m) {
535 Matrix<T> acc(M, M, zero);
536 if (m <= n) acc = matmul(Sn[m], Pt0);
537 if (m >= 1 && m - 1 <= n) {
538 const Matrix<T> add = matmul(Sn[m - 1], Pt1);
539 for (std::size_t i = 0; i < M; ++i)
540 for (std::size_t j = 0; j < M; ++j) acc(i, j) += add(i, j);
541 }
542 Snew[m] = acc;
543 }
544 Sn = Snew;
545 Pn = matmul(Pn, PD);
546 }
547 }
548
549 const std::vector<T> e = ones<T>(M);
550 Matrix<T> negD0(M, M);
551 for (std::size_t i = 0; i < M; ++i)
552 for (std::size_t j = 0; j < M; ++j) negD0(i, j) = -D0(i, j);
553 const Matrix<T> negD0inv = inverse(negD0);
554 const Matrix<T> Psi = matmul(negD0inv, D1); // phase at the arrival ending an idle period
555 const std::vector<T> idle = mulvec(negD0inv, e);
556
557 // Embedded chain at departure epochs, state (n,j) -> index n M + j.
558 Matrix<T> P(K * M, K * M, zero);
559 const std::size_t lastblk = (K - 1) * M;
560 for (std::size_t n = 1; n + 1 <= K; ++n) {
561 Matrix<T> Bacc = B0;
562 for (std::size_t m = 0; m + n + 1 <= K; ++m) {
563 const std::size_t col = (n - 1 + m) * M;
564 for (std::size_t i = 0; i < M; ++i)
565 for (std::size_t j = 0; j < M; ++j) {
566 P(n * M + i, col + j) += A[m](i, j);
567 Bacc(i, j) -= A[m](i, j);
568 }
569 }
570 for (std::size_t i = 0; i < M; ++i)
571 for (std::size_t j = 0; j < M; ++j) P(n * M + i, lastblk + j) += Bacc(i, j);
572 }
573 {
574 Matrix<T> Bacc = B0;
575 for (std::size_t m = 0; m + 2 <= K; ++m) {
576 const Matrix<T> blk = matmul(Psi, A[m]);
577 for (std::size_t i = 0; i < M; ++i)
578 for (std::size_t j = 0; j < M; ++j) {
579 P(i, m * M + j) += blk(i, j);
580 Bacc(i, j) -= A[m](i, j);
581 }
582 }
583 const Matrix<T> tailblk = matmul(Psi, Bacc);
584 for (std::size_t i = 0; i < M; ++i)
585 for (std::size_t j = 0; j < M; ++j) P(i, lastblk + j) += tailblk(i, j);
586 }
587
588 {
589 T rowdev = zero;
590 for (std::size_t i = 0; i < K * M; ++i) {
591 T s = zero;
592 for (std::size_t j = 0; j < K * M; ++j) s += P(i, j);
593 const T d = num_abs(T(s - one));
594 if (d > rowdev) rowdev = d;
595 }
596 if (rowdev > num_traits<T>::from_double(1e-8))
597 throw NumericError(ErrorKind{"NonConvergence"},
598 "qsys_mapg1k: embedded chain rows deviate from 1 by " +
600 "; the uniformization series for A_m has not converged, raise nmax");
601 }
602
603 const std::vector<T> sigma = mc::dtmc_solve(P);
604 std::vector<T> sigma0(sigma.begin(), sigma.begin() + M);
605
606 // Markov renewal reward over one inter-departure cycle.
607 T idleTime = zero;
608 for (std::size_t j = 0; j < M; ++j) idleTime += sigma0[j] * idle[j];
609 const T Ecyc = Smean + idleTime;
610 const T Tput = one / Ecyc;
611 const T p0 = idleTime / Ecyc;
612
613 // Expected time with the buffer full during a cycle, resolved by phase.
614 std::vector<Matrix<T>> Qcum(mmax + 1, Matrix<T>(M, M, zero));
615 {
616 Matrix<T> acc(M, M, zero);
617 for (std::size_t m = 0; m <= mmax; ++m) {
618 for (std::size_t i = 0; i < M; ++i)
619 for (std::size_t j = 0; j < M; ++j) acc(i, j) += Q[m](i, j);
620 Qcum[m] = acc;
621 }
622 }
623 std::vector<T> timeKvec(M, zero);
624 for (std::size_t n = 1; n + 1 <= K; ++n) {
625 const std::size_t rr = K - n - 1;
626 std::vector<T> row(sigma.begin() + n * M, sigma.begin() + (n + 1) * M);
627 Matrix<T> D = Qtot;
628 for (std::size_t i = 0; i < M; ++i)
629 for (std::size_t j = 0; j < M; ++j) D(i, j) -= Qcum[rr](i, j);
630 const std::vector<T> t = vecmul(row, D);
631 for (std::size_t j = 0; j < M; ++j) timeKvec[j] += t[j];
632 }
633 {
634 const std::vector<T> s0Psi = vecmul(sigma0, Psi);
635 Matrix<T> D = Qtot;
636 if (K >= 2)
637 for (std::size_t i = 0; i < M; ++i)
638 for (std::size_t j = 0; j < M; ++j) D(i, j) -= Qcum[K - 2](i, j);
639 const std::vector<T> t = vecmul(s0Psi, D);
640 for (std::size_t j = 0; j < M; ++j) timeKvec[j] += t[j];
641 }
642 std::vector<T> pKvec(M, zero);
643 T pK = zero;
644 for (std::size_t j = 0; j < M; ++j) {
645 pKvec[j] = timeKvec[j] / Ecyc;
646 pK += pKvec[j];
647 }
648
649 // Expected time at every level, from the same Q_m.
650 std::vector<T> timeL(K + 1, zero);
651 timeL[0] = idleTime;
652 for (std::size_t n = 1; n + 1 <= K; ++n) {
653 std::vector<T> row(sigma.begin() + n * M, sigma.begin() + (n + 1) * M);
654 for (std::size_t l = n; l + 1 <= K; ++l) {
655 const std::vector<T> t = vecmul(row, Q[l - n]);
656 for (std::size_t j = 0; j < M; ++j) timeL[l] += t[j];
657 }
658 }
659 {
660 const std::vector<T> s0Psi = vecmul(sigma0, Psi);
661 for (std::size_t l = 1; l + 1 <= K; ++l) {
662 const std::vector<T> t = vecmul(s0Psi, Q[l - 1]);
663 for (std::size_t j = 0; j < M; ++j) timeL[l] += t[j];
664 }
665 }
666 {
667 T s = zero;
668 for (std::size_t j = 0; j < M; ++j) s += timeKvec[j];
669 timeL[K] = s;
670 }
671 std::vector<T> plevel(K + 1, zero);
672 T mass = zero;
673 for (std::size_t l = 0; l <= K; ++l) {
674 plevel[l] = timeL[l] / Ecyc;
675 mass += plevel[l];
676 }
677 if (num_abs(T(mass - one)) > num_traits<T>::from_double(1e-8))
678 throw NumericError(ErrorKind{"NonConvergence"}, "qsys_mapg1k: the level distribution has mass " +
680 "; the Q_m series has not converged, raise nmax");
681 T meanQ = zero;
682 for (std::size_t l = 0; l <= K; ++l)
683 meanQ += num_traits<T>::from_int(static_cast<long>(l)) * plevel[l];
684
685 std::vector<T> p0vec = vecmul(sigma0, negD0inv);
686 for (T& v : p0vec) v /= Ecyc;
687
688 const T lambda = mam::map_lambda(arrival);
689
691 r.p0 = p0;
692 r.pK = pK;
693 r.throughput = Tput;
694 r.lossProbability = one - Tput / lambda;
695 r.lambda = lambda;
696 r.meanServiceTime = Smean;
697 r.utilization = one - p0;
698 r.rho = lambda * Smean;
699 r.meanQueueLength = meanQ;
700 r.nmax = nmax;
702 r.sigma = sigma;
703 r.pKvec = pKvec;
704 r.p0vec = p0vec;
705 r.plevel = plevel;
706 return r;
707}
708
709/** qsys_mapg1k with the reference defaults tol = 1e-12, nmax = 200000. */
710template <class T>
711MapG1kResult<T> qsys_mapg1k(const mam::Map<T>& arrival, const ServiceLaw<T>& svc, std::size_t K) {
712 return qsys_mapg1k(arrival, svc, K, T(num_traits<T>::from_double(1e-12)),
713 static_cast<std::size_t>(200000));
714}
715
716} // namespace qsys
717} // namespace line
718
719#endif // LINE_API_QSYS_QSYS_MAPG1K_H
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
The algorithm cannot produce a trustworthy answer on this instance: LINE:numerical:<kind>.
Definition error.h:110
static VerboseLevel get_verbose()
The session verbosity.
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.
Running progress log of a LINE solver run (the "solver console").
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.
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.
SvclawCoeffsResult< T > qsys_svclaw_coeffs(const ServiceLaw< T > &svc, const T &theta, const T &tol, std::size_t nmaxCap)
qsys_svclaw_coeffs: uniformization coefficients of a service-time descriptor; see qsys_svclaw_coeffs....
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:196
T num_abs(const T &v)
Definition number.h:184
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(),...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
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).
Outputs [cn, Smean] of qsys_svclaw_coeffs.m, plus the truncation residual.
std::vector< T > cn
c_n for n = 0..N