LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mmapgk1_lcfspr.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_MMAPGK1_LCFSPR_H
6#define LINE_API_QSYS_QSYS_MMAPGK1_LCFSPR_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MMAP[K]/G[K]/1 LCFS-PR queue: K customer types with class-dependent
12 * GENERAL service under preemptive-resume last-come-first-served, fed by a
13 * marked Markovian arrival process. Port of qsys_mmapgk1_lcfspr.m.
14 *
15 * THE METHOD, Takine (2002). With C = D0, D_k = D^(k) and F_k the service law
16 * of type k, let Q be the minimal solution of
17 *
18 * Q = C + sum_k D_k F_k(Q), F_k(Q) = int_0^inf exp(Qx) dF_k(x),
19 *
20 * the generator of the arrival phase with the busy periods excised (He's U in
21 * qsys_mmapgk1.h), and kappa its stationary vector. With beta_k = D_k e E[S_k],
22 * Takine's Theorem 1 gives the rate matrices
23 *
24 * R_k = (beta_k kappa + D_k (I - F_k(Q))) (e kappa - Q)^-1, R = sum_k R_k,
25 *
26 * and the per-class numbers in system have the matrix product form
27 * y(n) = sum_k y(n - e_k) R_k, y(0) = (1 - rho) kappa. From it
28 *
29 * P(N = n) = (1 - rho) kappa R^n e,
30 * P(Y_k = m) = (1 - rho) kappa (A_k R_k)^m A_k e, A_k = (I - R + R_k)^-1,
31 * E[Y_k] = pi R_k (I - R)^-1 e,
32 *
33 * with pi the stationary vector of C + D. A type-k sojourn under LCFS-PR is the
34 * sub-busy period its own service opens, so
35 *
36 * E[exp(-s W_k)] = pi D_k F_k(Q*(s)) e / lambda_k,
37 * Q*(s) = C - sI + sum_j D_j F_j(Q*(s)),
38 *
39 * evaluated at COMPLEX s for the Euler inversion of the CDF. That is why this
40 * header carries a complex matrix transform: the real one in qsys_mmapgk1.h
41 * returns real(V diag(g) V^-1), which is right only for a real argument.
42 *
43 * Reference:
44 * T. Takine, "Matrix product-form solution for an LCFS-PR single-server queue
45 * with multiple arrival streams governed by a Markov chain", Queueing Systems
46 * 42(2):131-151, 2002.
47 */
48
49#include <algorithm>
50#include <cmath>
51#include <complex>
52#include <cstdio>
53#include <functional>
54#include <limits>
55#include <string>
56#include <vector>
57
59
60namespace line {
61namespace qsys {
62
63/** Return value of qsys_mmapgk1_lcfspr, mirroring the MATLAB struct. */
64template <class T>
66 std::vector<T> lambdas; ///< per-type arrival rates
67 T arrivalRate; ///< sum of the per-type rates
68 T utilization; ///< rho = sum_k lambda_k E[S_k]
69 Matrix<T> Q; ///< Takine's Q (He's U)
70 std::vector<T> kappa; ///< stationary vector of Q
71 std::vector<Matrix<T> > Rk; ///< the K rate matrices
72 Matrix<T> R; ///< sum of the R_k
73 std::vector<T> classMeanQueueLength; ///< E[Y_k]
74 T meanQueueLength; ///< E[N]
75 std::vector<T> meanSojournTime; ///< E[W_k]
76 std::vector<T> queueLengthDistr; ///< P(N = n), n = 0..nMax
77 std::vector<std::vector<T> > classQueueLengthDistr; ///< [type][m], P(Y_k = m)
78 /// s -> per-type E[exp(-s W_k)] at a complex argument
79 std::function<std::vector<std::complex<double> >(std::complex<double>)> respLST;
80 std::vector<std::vector<T> > respCDF; ///< [type][point], P(W_k <= t)
81 std::vector<T> respPoints; ///< the requested points
82 std::string analyzer; ///< "LINE:MMAP[K]/G[K]/1-LCFSPR"
83};
84
85/** The MATLAB name-value options. nMax < 0 means "not requested". */
86template <class T>
88 long nMax = -1;
89 std::vector<T> wPoints;
90 std::size_t quadNodes = 120;
91 double tol = 1e-12;
92};
93
95
96typedef std::complex<double> cdbl;
97typedef std::vector<std::vector<cdbl> > CMat;
98
99inline CMat czeros(std::size_t n) { return CMat(n, std::vector<cdbl>(n, cdbl(0.0, 0.0))); }
100
101inline CMat ceye(std::size_t n) {
102 CMat I = czeros(n);
103 for (std::size_t i = 0; i < n; ++i) I[i][i] = cdbl(1.0, 0.0);
104 return I;
105}
106
107inline CMat cmatmul(const CMat& A, const CMat& B) {
108 const std::size_t n = A.size(), m = B.empty() ? 0 : B[0].size(), p = B.size();
109 CMat C(n, std::vector<cdbl>(m, cdbl(0.0, 0.0)));
110 for (std::size_t i = 0; i < n; ++i)
111 for (std::size_t q = 0; q < p; ++q) {
112 const cdbl a = A[i][q];
113 if (a == cdbl(0.0, 0.0)) continue;
114 for (std::size_t j = 0; j < m; ++j) C[i][j] += a * B[q][j];
115 }
116 return C;
117}
118
119template <class T>
121 CMat C(A.rows(), std::vector<cdbl>(A.cols(), cdbl(0.0, 0.0)));
122 for (std::size_t i = 0; i < A.rows(); ++i)
123 for (std::size_t j = 0; j < A.cols(); ++j)
124 C[i][j] = cdbl(num_traits<T>::to_double(A(i, j)), 0.0);
125 return C;
126}
127
128/** X = A^-1 B over the complex field, column by column. */
129inline CMat csolve(const CMat& A, const CMat& B) {
130 const std::size_t n = A.size(), m = B.empty() ? 0 : B[0].size();
131 CMat X(n, std::vector<cdbl>(m, cdbl(0.0, 0.0)));
132 for (std::size_t j = 0; j < m; ++j) {
133 std::vector<cdbl> b(n);
134 for (std::size_t i = 0; i < n; ++i) b[i] = B[i][j];
135 const std::vector<cdbl> x = mmapgk1detail::complex_solve(A, b);
136 for (std::size_t i = 0; i < n; ++i) X[i][j] = x[i];
137 }
138 return X;
139}
140
141/** Complex matrix exponential, scaling and squaring over a Taylor series. */
142inline CMat cexpm(CMat A) {
143 const std::size_t n = A.size();
144 double nrm = 0.0;
145 for (std::size_t j = 0; j < n; ++j) {
146 double cs = 0.0;
147 for (std::size_t i = 0; i < n; ++i) cs += std::abs(A[i][j]);
148 nrm = std::max(nrm, cs);
149 }
150 int sq = 0;
151 if (nrm > 0.5) sq = static_cast<int>(std::ceil(std::log2(nrm / 0.5)));
152 const double scale = std::ldexp(1.0, -sq);
153 for (std::size_t i = 0; i < n; ++i)
154 for (std::size_t j = 0; j < n; ++j) A[i][j] *= scale;
155 CMat E = ceye(n), term = ceye(n);
156 for (int k = 1; k <= 30; ++k) {
157 term = cmatmul(term, A);
158 double tn = 0.0;
159 for (std::size_t i = 0; i < n; ++i)
160 for (std::size_t j = 0; j < n; ++j) {
161 term[i][j] /= static_cast<double>(k);
162 E[i][j] += term[i][j];
163 tn = std::max(tn, std::abs(term[i][j]));
164 }
165 if (tn < 1e-18) break;
166 }
167 for (int s = 0; s < sq; ++s) E = cmatmul(E, E);
168 return E;
169}
170
171/**
172 * U = V diag(ev) V^-1 for a COMPLEX matrix, the reference's `eig(U)` at a
173 * complex argument. The same LAPACK-free route as mmapgk1detail::eig_decomp
174 * (Faddeev-LeVerrier, Durand-Kerner, inverse iteration), with complex
175 * coefficients throughout. `rcond` is the reciprocal 1-norm condition number
176 * of V, 0 when V could not be inverted.
177 */
178inline void ceig_decomp(const CMat& U, std::vector<cdbl>& ev, CMat& V, CMat& Vinv, double& rcond) {
179 const std::size_t n = U.size();
180 ev.assign(n, cdbl(0.0, 0.0));
181 V = czeros(n);
182 Vinv = czeros(n);
183 rcond = 0.0;
184 if (n == 0) return;
185 std::vector<cdbl> c(n + 1, cdbl(0.0, 0.0));
186 c[0] = cdbl(1.0, 0.0);
187 CMat Mk = ceye(n);
188 for (std::size_t k = 1; k <= n; ++k) {
189 const CMat TM = cmatmul(U, Mk);
190 cdbl tr(0.0, 0.0);
191 for (std::size_t i = 0; i < n; ++i) tr += TM[i][i];
192 c[k] = -tr / static_cast<double>(k);
193 Mk = TM;
194 for (std::size_t i = 0; i < n; ++i) Mk[i][i] += c[k];
195 }
196 double scale = 0.0;
197 for (std::size_t i = 0; i < n; ++i)
198 for (std::size_t j = 0; j < n; ++j) scale = std::max(scale, std::abs(U[i][j]));
199 {
200 const cdbl seed(0.4, 0.9);
201 cdbl pw(1.0, 0.0);
202 for (std::size_t i = 0; i < n; ++i) {
203 ev[i] = pw * (scale > 0.0 ? scale : 1.0);
204 pw *= seed;
205 }
206 for (int it = 0; it < 2000; ++it) {
207 double move = 0.0;
208 for (std::size_t i = 0; i < n; ++i) {
209 cdbl den(1.0, 0.0);
210 for (std::size_t j = 0; j < n; ++j)
211 if (j != i) den *= (ev[i] - ev[j]);
212 if (std::abs(den) < 1e-300) continue;
213 cdbl pv(0.0, 0.0);
214 for (std::size_t q = 0; q <= n; ++q) pv = pv * ev[i] + c[q];
215 const cdbl step = pv / den;
216 ev[i] -= step;
217 move = std::max(move, std::abs(step));
218 }
219 if (move < 1e-15 * std::max(1.0, scale)) break;
220 }
221 }
222 double umax = 0.0;
223 for (std::size_t i = 0; i < n; ++i) umax = std::max(umax, std::abs(ev[i]));
224 for (std::size_t i = 0; i < n; ++i) {
225 const double eps = 1e-10 * std::max(1.0, umax);
226 const cdbl shift = ev[i] + cdbl(eps, eps);
227 std::vector<cdbl> v(n, cdbl(1.0 / std::sqrt(static_cast<double>(n)), 0.0));
228 for (int it = 0; it < 3; ++it) {
229 CMat A = U;
230 for (std::size_t p = 0; p < n; ++p) A[p][p] -= shift;
231 const std::vector<cdbl> w = mmapgk1detail::complex_solve(A, v);
232 double nrm = 0.0;
233 for (std::size_t q = 0; q < n; ++q) nrm += std::norm(w[q]);
234 nrm = std::sqrt(nrm);
235 if (!(nrm > 0.0) || !std::isfinite(nrm)) break;
236 for (std::size_t q = 0; q < n; ++q) v[q] = w[q] / nrm;
237 }
238 for (std::size_t q = 0; q < n; ++q) V[q][i] = v[q];
239 }
240 double normV = 0.0, normW = 0.0;
241 for (std::size_t j = 0; j < n; ++j) {
242 std::vector<cdbl> ej(n, cdbl(0.0, 0.0));
243 ej[j] = cdbl(1.0, 0.0);
244 const std::vector<cdbl> col = mmapgk1detail::complex_solve(V, ej);
245 double cs = 0.0, vs = 0.0;
246 for (std::size_t i = 0; i < n; ++i) {
247 Vinv[i][j] = col[i];
248 cs += std::abs(col[i]);
249 vs += std::abs(V[i][j]);
250 }
251 normW = std::max(normW, cs);
252 normV = std::max(normV, vs);
253 }
254 if (std::isfinite(normW) && normW > 0.0 && normV > 0.0) rcond = 1.0 / (normV * normW);
255}
256
257/** V diag(g) V^-1, kept complex. */
258inline CMat ceig_apply(const CMat& V, const CMat& Vinv, const std::vector<cdbl>& g) {
259 const std::size_t n = V.size();
260 CMat F = czeros(n);
261 for (std::size_t i = 0; i < n; ++i)
262 for (std::size_t j = 0; j < n; ++j) {
263 cdbl acc(0.0, 0.0);
264 for (std::size_t q = 0; q < n; ++q) acc += V[i][q] * g[q] * Vinv[q][j];
265 F[i][j] = acc;
266 }
267 return F;
268}
269
270/** int exp(Ut) dF(t) for a (D0, D1) phase-type law at a complex U, on the Kronecker sum. */
271template <class T>
272CMat cmatrix_lst_map(const mam::Map<T>& rep, const CMat& U) {
273 const std::size_t n = U.size();
274 const std::size_t ms = rep.D0.rows();
275 const std::vector<T> beta = mam::map_pie(rep);
276 const std::size_t N = n * ms;
277 CMat KS = czeros(N), Rhs(N, std::vector<cdbl>(n, cdbl(0.0, 0.0)));
278 for (std::size_t i = 0; i < n; ++i)
279 for (std::size_t j = 0; j < n; ++j)
280 for (std::size_t p = 0; p < ms; ++p) KS[i * ms + p][j * ms + p] += U[i][j];
281 for (std::size_t i = 0; i < n; ++i)
282 for (std::size_t p = 0; p < ms; ++p) {
283 double s0 = 0.0;
284 for (std::size_t q = 0; q < ms; ++q) {
285 const double d = num_traits<T>::to_double(rep.D0(p, q));
286 KS[i * ms + p][i * ms + q] += d;
287 s0 -= d;
288 }
289 Rhs[i * ms + p][i] = cdbl(s0, 0.0);
290 }
291 const CMat X = csolve(KS, Rhs);
292 CMat F = czeros(n);
293 for (std::size_t i = 0; i < n; ++i)
294 for (std::size_t j = 0; j < n; ++j) {
295 cdbl acc(0.0, 0.0);
296 for (std::size_t p = 0; p < ms; ++p)
297 acc += num_traits<T>::to_double(beta[p]) * X[i * ms + p][j];
298 F[i][j] = -acc;
299 }
300 return F;
301}
302
303/** The matrix transform of a Distribution object at a complex U. */
304template <class T>
305CMat cmatrix_lst(const lang::Distrib<T>& law, const CMat& U, std::size_t quad_nodes) {
306 const std::size_t n = U.size();
308 if (law.type == lang::ProcessType::DET) {
309 const double d = num_traits<T>::to_double(lang::dist_moment(law, 1u));
310 CMat Ud = U;
311 for (std::size_t i = 0; i < n; ++i)
312 for (std::size_t j = 0; j < n; ++j) Ud[i][j] *= d;
313 return cexpm(Ud);
314 }
315 {
316 std::vector<cdbl> ev;
317 CMat V, Vinv;
318 double rcond = 0.0;
319 ceig_decomp(U, ev, V, Vinv, rcond);
320 bool ok = rcond > 1e-12;
321 std::vector<cdbl> g(n, cdbl(0.0, 0.0));
322 if (ok) {
323 for (std::size_t i = 0; i < n; ++i) {
324 try {
325 g[i] = lang::dist_lst(law, -ev[i]);
326 } catch (const Error&) {
327 ok = false;
328 break;
329 }
330 if (!std::isfinite(g[i].real()) || !std::isfinite(g[i].imag())) {
331 ok = false;
332 break;
333 }
334 }
335 }
336 if (ok) return ceig_apply(V, Vinv, g);
337 }
338 std::vector<T> x, w;
339 mmapgk1detail::stieltjes_nodes(law, x, w, quad_nodes);
340 CMat F = czeros(n);
341 for (std::size_t i = 0; i < x.size(); ++i) {
342 const double xi = num_traits<T>::to_double(x[i]);
343 const double wi = num_traits<T>::to_double(w[i]);
344 if (wi == 0.0) continue;
345 CMat Ut = U;
346 for (std::size_t p = 0; p < n; ++p)
347 for (std::size_t q = 0; q < n; ++q) Ut[p][q] *= xi;
348 const CMat E = cexpm(Ut);
349 for (std::size_t p = 0; p < n; ++p)
350 for (std::size_t q = 0; q < n; ++q) F[p][q] += wi * E[p][q];
351 }
352 return F;
353}
354
355/** The matrix transform of a service law in any of its three forms, at a complex U. */
356template <class T>
357CMat cmatrix_lst(const MmapGk1Service<T>& svc, const CMat& U, std::size_t quad_nodes) {
358 switch (svc.kind) {
360 return cmatrix_lst_map(svc.map, U);
362 const std::size_t n = U.size();
363 std::vector<cdbl> ev;
364 CMat V, Vinv;
365 double rcond = 0.0;
366 ceig_decomp(U, ev, V, Vinv, rcond);
367 std::vector<cdbl> g(n);
368 for (std::size_t i = 0; i < n; ++i) g[i] = svc.transform.lst(-ev[i]);
369 return ceig_apply(V, Vinv, g);
370 }
371 default:
372 return cmatrix_lst(svc.dist, U, quad_nodes);
373 }
374}
375
376/**
377 * Per-type sojourn LST pi D_k F_k(Q*(s)) e / lambda_k, Q*(s) the minimal
378 * solution of Q* = C - sI + sum_j D_j F_j(Q*), iterated from C - sI.
379 */
380template <class T>
381std::vector<cdbl> sojourn_lst(const Matrix<T>& D0, const std::vector<Matrix<T> >& Dk,
382 const std::vector<MmapGk1Service<T> >& svc,
383 const std::vector<T>& piArr, const std::vector<T>& lambdas, cdbl s,
384 std::size_t quad_nodes, double tol) {
385 const std::size_t K = Dk.size(), ma = D0.rows();
386 if (std::abs(s) < 1e-14) return std::vector<cdbl>(K, cdbl(1.0, 0.0));
387 std::vector<CMat> Dc(K);
388 for (std::size_t k = 0; k < K; ++k) Dc[k] = to_cmat(Dk[k]);
389 CMat base = to_cmat(D0);
390 for (std::size_t i = 0; i < ma; ++i) base[i][i] -= s;
391 CMat Qs = base;
392 for (std::size_t it = 0; it < 10000; ++it) {
393 CMat Qnew = base;
394 for (std::size_t j = 0; j < K; ++j) {
395 const CMat P = cmatmul(Dc[j], cmatrix_lst(svc[j], Qs, quad_nodes));
396 for (std::size_t a = 0; a < ma; ++a)
397 for (std::size_t b = 0; b < ma; ++b) Qnew[a][b] += P[a][b];
398 }
399 double diff = 0.0;
400 for (std::size_t a = 0; a < ma; ++a)
401 for (std::size_t b = 0; b < ma; ++b) diff = std::max(diff, std::abs(Qnew[a][b] - Qs[a][b]));
402 Qs = Qnew;
403 if (diff <= tol) break;
404 }
405 std::vector<cdbl> w(K, cdbl(1.0, 0.0));
406 for (std::size_t k = 0; k < K; ++k) {
407 const double lam = num_traits<T>::to_double(lambdas[k]);
408 if (!(lam > 0.0)) continue;
409 const CMat F = cmatrix_lst(svc[k], Qs, quad_nodes);
410 // pi D_k F e
411 std::vector<cdbl> Fe(ma, cdbl(0.0, 0.0));
412 for (std::size_t a = 0; a < ma; ++a)
413 for (std::size_t b = 0; b < ma; ++b) Fe[a] += F[a][b];
414 cdbl acc(0.0, 0.0);
415 for (std::size_t a = 0; a < ma; ++a) {
416 cdbl row(0.0, 0.0);
417 for (std::size_t b = 0; b < ma; ++b) row += Dc[k][a][b] * Fe[b];
418 acc += num_traits<T>::to_double(piArr[a]) * row;
419 }
420 w[k] = acc / lam;
421 }
422 return w;
423}
424
425/** Abate-Whitt Euler inversion of a CDF from its LST, qsys_mmapgk1_invert.m. */
426inline double euler_invert(const std::function<cdbl(cdbl)>& lst, double t) {
427 if (t <= 0.0) return lst(cdbl(1e12, 0.0)).real();
428 const double A = 18.4;
429 const std::size_t nE = 15, mE = 11;
430 const double u = std::exp(A / 2) / t;
431 const double x = A / (2 * t);
432 std::vector<double> terms(nE + mE + 1, 0.0);
433 terms[0] = lst(cdbl(x, 0.0)).real() / x / 2.0;
434 for (std::size_t j = 1; j <= nE + mE; ++j) {
435 const cdbl s(x, M_PI * static_cast<double>(j) / t);
436 terms[j] = ((j % 2 == 0) ? 1.0 : -1.0) * (lst(s) / s).real();
437 }
438 double run = 0.0, F = 0.0;
439 std::vector<double> partial(terms.size(), 0.0);
440 for (std::size_t j = 0; j < terms.size(); ++j) {
441 run += terms[j];
442 partial[j] = run;
443 }
444 for (std::size_t j = 0; j <= mE; ++j)
445 F += mmapgk1detail::binom(mE, j) / std::pow(2.0, static_cast<double>(mE)) * partial[nE + j];
446 F = u * F;
447 return std::min(std::max(F, 0.0), 1.0);
448}
449
450} // namespace mmapgk1lcfsprdetail
451
452/**
453 * MMAP[K]/G[K]/1 LCFS-PR, per type, by Takine's matrix product form.
454 *
455 * @param MMAP LINE convention {D0, D1, D^(1), ..., D^(K)}
456 * @param svc K service laws, one per marked type, each a Distribution object,
457 * a (D0, D1) phase-type pair or a transform struct
458 * @param opt nMax (distributions on 0..nMax when >= 0), wPoints (sojourn CDF
459 * points), quadNodes (default 120), tol (default 1e-12)
460 */
461template <class T>
463 const std::vector<MmapGk1Service<T> >& svc,
466 "qsys_mmapgk1_lcfspr requires transcendental arithmetic");
467 using namespace mmapgk1detail;
469 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
470 if (MMAP.size() < 3)
471 throw InputError("qsys_mmapgk1_lcfspr: The MMAP must carry at least one marked arrival block.");
472 const std::size_t K = MMAP.size() - 2;
473 if (svc.size() != K)
474 throw InputError("qsys_mmapgk1_lcfspr: One service law per marked type is required (" +
475 std::to_string(svc.size()) + " given, " + std::to_string(K) + " types).");
476 const std::size_t quad = opt.quadNodes;
477 const double tol = opt.tol;
478 const Matrix<T>& D0 = MMAP[0];
479 const std::size_t ma = D0.rows();
480 const Matrix<T> I = eye<T>(ma);
481 const std::vector<T> e(ma, one);
482 std::vector<Matrix<T> > Dk;
483 Matrix<T> Dsum = D0;
484 for (std::size_t k = 0; k < K; ++k) {
485 Dk.push_back(MMAP[k + 2]);
486 for (std::size_t i = 0; i < ma; ++i)
487 for (std::size_t j = 0; j < ma; ++j) Dsum(i, j) += Dk[k](i, j);
488 }
489
490 const std::vector<T> piArr = stat_left_null(Dsum);
491 std::vector<T> lambdas(K, zero), meanS(K, zero);
492 T rho = zero;
493 for (std::size_t k = 0; k < K; ++k) {
494 const std::vector<T> De = mulvec(Dk[k], e);
495 T lam = zero;
496 for (std::size_t i = 0; i < ma; ++i) lam += piArr[i] * De[i];
497 lambdas[k] = lam;
498 meanS[k] = raw_moment(svc[k], 1);
499 rho += lam * meanS[k];
500 }
501 if (num_traits<T>::to_double(rho) >= 1.0 - std::numeric_limits<double>::epsilon()) {
502 char buf[64];
503 std::snprintf(buf, sizeof(buf), "%g", num_traits<T>::to_double(rho));
504 throw InputError(std::string("qsys_mmapgk1_lcfspr: The load ") + buf +
505 " of the system is not below one.");
506 }
507
508 // Q = C + sum_k D_k F_k(Q), iterated from C
509 Matrix<T> Q = D0;
510 std::vector<Matrix<T> > FQ(K);
511 for (std::size_t it = 0; it < 10000; ++it) {
512 Matrix<T> Qnew = D0;
513 for (std::size_t k = 0; k < K; ++k) {
514 FQ[k] = matrix_lst(svc[k], Q, quad);
515 const Matrix<T> P = matmul(Dk[k], FQ[k]);
516 for (std::size_t i = 0; i < ma; ++i)
517 for (std::size_t j = 0; j < ma; ++j) Qnew(i, j) += P(i, j);
518 }
519 double diff = 0.0;
520 for (std::size_t i = 0; i < ma; ++i)
521 for (std::size_t j = 0; j < ma; ++j)
522 diff = std::max(diff, std::fabs(num_traits<T>::to_double(Qnew(i, j) - Q(i, j))));
523 Q = Qnew;
524 if (diff <= tol) break;
525 }
526 // Q e = 0 at the fixed point but only to tolerance in the iterate: restore it
527 // so kappa and (e kappa - Q)^-1 are those of a proper generator.
528 for (std::size_t i = 0; i < ma; ++i) {
529 T rowsum = zero;
530 for (std::size_t j = 0; j < ma; ++j) rowsum += Q(i, j);
531 Q(i, i) -= rowsum;
532 }
533 for (std::size_t k = 0; k < K; ++k) FQ[k] = matrix_lst(svc[k], Q, quad);
534 const std::vector<T> kappa = stat_left_null(Q);
535
536 // Rate matrices (Takine, Theorem 1)
537 Matrix<T> ekQ(ma, ma, zero);
538 for (std::size_t i = 0; i < ma; ++i)
539 for (std::size_t j = 0; j < ma; ++j) ekQ(i, j) = kappa[j] - Q(i, j);
540 const Matrix<T> Z = inverse(ekQ);
541 std::vector<Matrix<T> > Rk(K);
542 Matrix<T> R(ma, ma, zero);
543 for (std::size_t k = 0; k < K; ++k) {
544 const std::vector<T> De = mulvec(Dk[k], e);
545 Matrix<T> IF = I;
546 for (std::size_t i = 0; i < ma; ++i)
547 for (std::size_t j = 0; j < ma; ++j) IF(i, j) -= FQ[k](i, j);
548 Matrix<T> Bk = matmul(Dk[k], IF);
549 for (std::size_t i = 0; i < ma; ++i)
550 for (std::size_t j = 0; j < ma; ++j) Bk(i, j) += De[i] * meanS[k] * kappa[j];
551 Rk[k] = matmul(Bk, Z);
552 for (std::size_t i = 0; i < ma; ++i)
553 for (std::size_t j = 0; j < ma; ++j) R(i, j) += Rk[k](i, j);
554 }
555 Matrix<T> IR = I;
556 for (std::size_t i = 0; i < ma; ++i)
557 for (std::size_t j = 0; j < ma; ++j) IR(i, j) -= R(i, j);
558 const std::vector<T> IRinve = mulvec(inverse(IR), e);
559
561 r.classMeanQueueLength.assign(K, zero);
562 r.meanSojournTime.assign(K, zero);
563 r.meanQueueLength = zero;
564 for (std::size_t k = 0; k < K; ++k) {
565 const std::vector<T> v = vecmul(piArr, Rk[k]);
566 T q = zero;
567 for (std::size_t i = 0; i < ma; ++i) q += v[i] * IRinve[i];
568 if (q < zero) q = zero;
569 r.classMeanQueueLength[k] = q;
570 r.meanSojournTime[k] = (lambdas[k] > zero) ? T(q / lambdas[k]) : zero;
571 r.meanQueueLength += q;
572 }
573
574 if (opt.nMax >= 0) {
575 const std::size_t nMax = static_cast<std::size_t>(opt.nMax);
576 std::vector<T> y0(ma, zero);
577 for (std::size_t i = 0; i < ma; ++i) y0[i] = (one - rho) * kappa[i];
578 r.queueLengthDistr.assign(nMax + 1, zero);
579 std::vector<T> v = y0;
580 for (std::size_t n = 0; n <= nMax; ++n) {
581 T s = zero;
582 for (std::size_t i = 0; i < ma; ++i) s += v[i];
583 r.queueLengthDistr[n] = (s > zero) ? s : zero;
584 v = vecmul(v, R);
585 }
586 r.classQueueLengthDistr.assign(K, std::vector<T>(nMax + 1, zero));
587 for (std::size_t k = 0; k < K; ++k) {
588 Matrix<T> M = IR;
589 for (std::size_t i = 0; i < ma; ++i)
590 for (std::size_t j = 0; j < ma; ++j) M(i, j) += Rk[k](i, j);
591 const Matrix<T> Ak = inverse(M);
592 const std::vector<T> Ake = mulvec(Ak, e);
593 const Matrix<T> ARk = matmul(Ak, Rk[k]);
594 v = y0;
595 for (std::size_t m = 0; m <= nMax; ++m) {
596 T s = zero;
597 for (std::size_t i = 0; i < ma; ++i) s += v[i] * Ake[i];
598 r.classQueueLengthDistr[k][m] = (s > zero) ? s : zero;
599 v = vecmul(v, ARk);
600 }
601 }
602 }
603
604 const std::vector<MmapGk1Service<T> > svcCopy = svc;
605 r.respLST = [D0, Dk, svcCopy, piArr, lambdas, quad, tol](cdbl s) {
606 return mmapgk1lcfsprdetail::sojourn_lst(D0, Dk, svcCopy, piArr, lambdas, s, quad, tol);
607 };
608
609 r.respPoints = opt.wPoints;
610 if (!opt.wPoints.empty()) {
611 r.respCDF.assign(K, std::vector<T>(opt.wPoints.size(), zero));
612 for (std::size_t it = 0; it < opt.wPoints.size(); ++it) {
613 const double t = num_traits<T>::to_double(opt.wPoints[it]);
614 for (std::size_t k = 0; k < K; ++k) {
615 const auto& lstAll = r.respLST;
616 const std::function<cdbl(cdbl)> f = [&lstAll, k](cdbl s) { return lstAll(s)[k]; };
617 r.respCDF[k][it] =
619 }
620 }
621 }
622
623 r.lambdas = lambdas;
624 r.arrivalRate = zero;
625 for (std::size_t k = 0; k < K; ++k) r.arrivalRate += lambdas[k];
626 r.utilization = rho;
627 r.Q = Q;
628 r.kappa = kappa;
629 r.Rk = Rk;
630 r.R = R;
631 r.analyzer = "LINE:MMAP[" + std::to_string(K) + "]/G[" + std::to_string(K) + "]/1-LCFSPR";
632 return r;
633}
634
635/** qsys_mmapgk1_lcfspr with the reference's defaults (no distributions, no CDF). */
636template <class T>
638 const std::vector<MmapGk1Service<T> >& svc) {
639 return qsys_mmapgk1_lcfspr(MMAP, svc, MmapGk1LcfsprOptions<T>());
640}
641
642/** qsys_mmapgk1_lcfspr on Distribution objects. */
643template <class T>
645 const std::vector<lang::Distrib<T> >& svc,
647 std::vector<MmapGk1Service<T> > laws;
648 laws.reserve(svc.size());
649 for (std::size_t k = 0; k < svc.size(); ++k) laws.push_back(MmapGk1Service<T>(svc[k]));
650 return qsys_mmapgk1_lcfspr(MMAP, laws, opt);
651}
652
653/** qsys_mmapgk1_lcfspr on Distribution objects with the reference's defaults. */
654template <class T>
656 const std::vector<lang::Distrib<T> >& svc) {
657 return qsys_mmapgk1_lcfspr(MMAP, svc, MmapGk1LcfsprOptions<T>());
658}
659
660} // namespace qsys
661} // namespace line
662
663#endif // LINE_API_QSYS_QSYS_MMAPGK1_LCFSPR_H
Base error for the multiprecision C++ port.
Definition error.h:73
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
mam::Map< T > dist_to_map(const Distrib< T > &d)
The (D0,D1) pair that reaches sn.proc.
T dist_lst(const Distrib< T > &d, const T &s)
sn.lst: the Laplace-Stieltjes transform E[exp(-sX)].
bool process_is_markovian(ProcessType p)
ProcessType.isMarkovian: true when sn.proc carries an exact matrix representation of the law,...
Definition lang_types.h:707
T dist_moment(const Distrib< T > &d, unsigned k)
The k-th raw moment.
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
CMat cmatrix_lst_map(const mam::Map< T > &rep, const CMat &U)
int exp(Ut) dF(t) for a (D0, D1) phase-type law at a complex U, on the Kronecker sum.
CMat csolve(const CMat &A, const CMat &B)
X = A^-1 B over the complex field, column by column.
CMat cmatmul(const CMat &A, const CMat &B)
std::vector< std::vector< cdbl > > CMat
void ceig_decomp(const CMat &U, std::vector< cdbl > &ev, CMat &V, CMat &Vinv, double &rcond)
U = V diag(ev) V^-1 for a COMPLEX matrix, the reference's eig(U) at a complex argument.
CMat ceig_apply(const CMat &V, const CMat &Vinv, const std::vector< cdbl > &g)
V diag(g) V^-1, kept complex.
double euler_invert(const std::function< cdbl(cdbl)> &lst, double t)
Abate-Whitt Euler inversion of a CDF from its LST, qsys_mmapgk1_invert.m.
std::vector< cdbl > sojourn_lst(const Matrix< T > &D0, const std::vector< Matrix< T > > &Dk, const std::vector< MmapGk1Service< T > > &svc, const std::vector< T > &piArr, const std::vector< T > &lambdas, cdbl s, std::size_t quad_nodes, double tol)
Per-type sojourn LST pi D_k F_k(Q*(s)) e / lambda_k, Q*(s) the minimal solution of Q* = C - sI + sum_...
CMat cexpm(CMat A)
Complex matrix exponential, scaling and squaring over a Taylor series.
CMat cmatrix_lst(const lang::Distrib< T > &law, const CMat &U, std::size_t quad_nodes)
The matrix transform of a Distribution object at a complex U.
CMat to_cmat(const Matrix< T > &A)
MmapGk1LcfsprResult< T > qsys_mmapgk1_lcfspr(const std::vector< Matrix< T > > &MMAP, const std::vector< MmapGk1Service< T > > &svc, const MmapGk1LcfsprOptions< T > &opt)
MMAP[K]/G[K]/1 LCFS-PR, per type, by Takine's matrix product form.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
The MMAP[K]/G[K]/1 FCFS queue: K customer types with class-dependent GENERAL service,...
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D0
Definition map_moment.h:54
The MATLAB name-value options.
Return value of qsys_mmapgk1_lcfspr, mirroring the MATLAB struct.
std::vector< T > meanSojournTime
E[W_k].
std::string analyzer
"LINE:MMAP[K]/G[K]/1-LCFSPR"
std::vector< T > lambdas
per-type arrival rates
std::vector< std::vector< T > > classQueueLengthDistr
[type][m], P(Y_k = m)
std::vector< T > kappa
stationary vector of Q
std::vector< std::vector< T > > respCDF
[type][point], P(W_k <= t)
std::vector< T > classMeanQueueLength
E[Y_k].
std::vector< T > queueLengthDistr
P(N = n), n = 0..nMax.
std::vector< T > respPoints
the requested points
T arrivalRate
sum of the per-type rates
Matrix< T > Q
Takine's Q (He's U).
T utilization
rho = sum_k lambda_k E[S_k]
std::function< std::vector< std::complex< double > >(std::complex< double >)> respLST
s -> per-type E[exp(-s W_k)] at a complex argument
std::vector< Matrix< T > > Rk
the K rate matrices
One marked type's service law, in any of the three forms the reference accepts: a Distribution object...
lang::Distrib< T > dist
MmapGk1Transform< T > transform