LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmapph1prio.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_MMAPPH1PRIO_H
6#define LINE_API_MAM_MMAPPH1PRIO_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * The MMAP[K]/PH[K]/1 priority queue, preemptive resume (`mmapph1prpr_*`) and
12 * non-preemptive (`mmapph1nppr_*`).
13 *
14 * Port of BUTools' MMAPPH1PRPR.m and MMAPPH1NPPR.m (matlab/lib/thirdparty/
15 * BUTools/queues), which `solver_mam_basic.m` calls at an FCFSPRPRIO or HOL
16 * station with all-distinct class priorities, and `solver_mam_passage_time.m`
17 * tabulates the sojourn CDF from. The algorithm is G. Horvath, "Efficient
18 * analysis of the MMAP[K]/PH[K]/1 priority queue", European Journal of
19 * Operational Research 246(1), 128-139, 2015: the workload of the classes at or
20 * above k is a fluid queue whose first-return matrix Psi (ADDA doubling,
21 * `mfq_fundamental`) yields the boundary vector, and the remaining sojourn time
22 * of a class-k job is the first passage time of a second fluid model over the
23 * higher classes. Moments come from a chain of Sylvester equations sharing
24 * their coefficient matrices; the CDF from an Erlangized Laplace inversion of
25 * order `erl_max_order`.
26 *
27 * CLASS ORDER IS BUTools' OWN: class 1 is the LOWEST priority and class K the
28 * highest. The caller permutes LINE's classes (lower classprio value = higher
29 * priority) into that order and maps the outputs back, as the reference does.
30 *
31 * Every entry point returns one row per class, in the order of the input.
32 *
33 * ARITHMETIC. The Riccati and QBD roots are tolerance-terminated iterations, so
34 * these are {Double, Real} and refuse exact/Rational at compile time, like
35 * `mmapph1fcfs`.
36 */
37
38#include <cmath>
39#include <cstddef>
40#include <vector>
41
45#include "line/api/mam/qbd_r.h"
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/expm.h"
50#include "line/util/linalg.h"
51#include "line/util/matrix.h"
52#include "line/util/sylvester.h"
53
54namespace line {
55namespace mam {
56
57/** Options shared by both priority analyzers (the BUTools 'erlMaxOrder' and 'prec'). */
59 std::size_t erl_max_order = 200; ///< Erlang order of the sojourn-time CDF inversion
60 double precision = 1e-14; ///< tolerance of the fluid and QBD fundamental matrices
61};
62
63namespace prio_detail {
64
65enum class Measure { StMoms, StDistr, NcMoms, NcDistr };
66
67template <class T>
68Matrix<T> zeros(std::size_t r, std::size_t c) {
69 return Matrix<T>(r, c, num_traits<T>::from_int(0));
70}
71
72template <class T>
73Matrix<T> eye(std::size_t n) {
74 Matrix<T> I = zeros<T>(n, n);
75 for (std::size_t i = 0; i < n; ++i) I(i, i) = num_traits<T>::from_int(1);
76 return I;
77}
78
79template <class T>
80Matrix<T> ones(std::size_t r, std::size_t c) {
81 return Matrix<T>(r, c, num_traits<T>::from_int(1));
82}
83
84template <class T>
85Matrix<T> add(const Matrix<T>& A, const Matrix<T>& B) {
86 Matrix<T> C = A;
87 for (std::size_t i = 0; i < A.rows(); ++i)
88 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) += B(i, j);
89 return C;
90}
91
92template <class T>
93Matrix<T> sub(const Matrix<T>& A, const Matrix<T>& B) {
94 Matrix<T> C = A;
95 for (std::size_t i = 0; i < A.rows(); ++i)
96 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) -= B(i, j);
97 return C;
98}
99
100template <class T>
101Matrix<T> scal(const Matrix<T>& A, const T& s) {
102 Matrix<T> C = A;
103 for (std::size_t i = 0; i < A.rows(); ++i)
104 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = T(C(i, j) * s);
105 return C;
106}
107
108template <class T>
109Matrix<T> mul(const Matrix<T>& A, const Matrix<T>& B) {
110 if (A.rows() == 0 || B.cols() == 0 || A.cols() == 0) return zeros<T>(A.rows(), B.cols());
111 return matmul(A, B);
112}
113
114template <class T>
115Matrix<T> mul(const Matrix<T>& A, const Matrix<T>& B, const Matrix<T>& C) {
116 return mul(mul(A, B), C);
117}
118
119/** inv(-A). */
120template <class T>
121Matrix<T> ninv(const Matrix<T>& A) {
122 return inverse(scal(A, T(num_traits<T>::from_int(-1))));
123}
124
125template <class T>
126Matrix<T> mpow(const Matrix<T>& A, std::size_t n) {
127 Matrix<T> P = eye<T>(A.rows());
128 for (std::size_t i = 0; i < n; ++i) P = mul(P, A);
129 return P;
130}
131
132template <class T>
133void setblk(Matrix<T>& A, std::size_t r0, std::size_t c0, const Matrix<T>& B) {
134 for (std::size_t i = 0; i < B.rows(); ++i)
135 for (std::size_t j = 0; j < B.cols(); ++j) A(r0 + i, c0 + j) = B(i, j);
136}
137
138template <class T>
139Matrix<T> getblk(const Matrix<T>& A, std::size_t r0, std::size_t c0, std::size_t nr,
140 std::size_t nc) {
141 Matrix<T> B = zeros<T>(nr, nc);
142 for (std::size_t i = 0; i < nr; ++i)
143 for (std::size_t j = 0; j < nc; ++j) B(i, j) = A(r0 + i, c0 + j);
144 return B;
145}
146
147template <class T>
148Matrix<T> hcat(const Matrix<T>& A, const Matrix<T>& B) {
149 const std::size_t r = A.cols() > 0 ? A.rows() : B.rows();
150 Matrix<T> C = zeros<T>(r, A.cols() + B.cols());
151 setblk(C, 0, 0, A);
152 setblk(C, 0, A.cols(), B);
153 return C;
154}
155
156template <class T>
157Matrix<T> vcat(const Matrix<T>& A, const Matrix<T>& B) {
158 const std::size_t c = A.rows() > 0 ? A.cols() : B.cols();
159 Matrix<T> C = zeros<T>(A.rows() + B.rows(), c);
160 setblk(C, 0, 0, A);
161 setblk(C, A.rows(), 0, B);
162 return C;
163}
164
165template <class T>
166Matrix<T> blkdiag(const Matrix<T>& A, const Matrix<T>& B) {
167 Matrix<T> C = zeros<T>(A.rows() + B.rows(), A.cols() + B.cols());
168 setblk(C, 0, 0, A);
169 setblk(C, A.rows(), A.cols(), B);
170 return C;
171}
172
173/** sum(A,2), as a column. */
174template <class T>
175Matrix<T> rowsum(const Matrix<T>& A) {
176 Matrix<T> s = zeros<T>(A.rows(), 1);
177 for (std::size_t i = 0; i < A.rows(); ++i)
178 for (std::size_t j = 0; j < A.cols(); ++j) s(i, 0) += A(i, j);
179 return s;
180}
181
182/** sum(A(:)). */
183template <class T>
184T total(const Matrix<T>& A) {
185 T s = num_traits<T>::from_int(0);
186 for (std::size_t i = 0; i < A.rows(); ++i)
187 for (std::size_t j = 0; j < A.cols(); ++j) s += A(i, j);
188 return s;
189}
190
191template <class T>
192Matrix<T> rowvec(const std::vector<T>& v) {
193 Matrix<T> r = zeros<T>(1, v.size());
194 for (std::size_t j = 0; j < v.size(); ++j) r(0, j) = v[j];
195 return r;
196}
197
198template <class T>
199T factorial(std::size_t n) {
200 T f = num_traits<T>::from_int(1);
201 for (std::size_t i = 2; i <= n; ++i) f = T(f * num_traits<T>::from_int((int)i));
202 return f;
203}
204
205/** BUTools MomsFromFactorialMoms: raw moments from factorial moments (signed Stirling numbers of the first kind). */
206template <class T>
207std::vector<T> moms_from_factorial_moms(const std::vector<T>& fm) {
208 std::vector<T> m(fm.size());
209 if (fm.empty()) return m;
210 m[0] = fm[0];
211 for (std::size_t i = 2; i <= fm.size(); ++i) {
212 // coefficients of x(x-1)...(x-i+1), c[j] multiplying x^j
213 std::vector<double> c(i + 1, 0.0);
214 c[0] = 1.0;
215 for (std::size_t r = 0; r < i; ++r) {
216 std::vector<double> nc(i + 1, 0.0);
217 for (std::size_t j = 0; j <= r; ++j) {
218 nc[j + 1] += c[j];
219 nc[j] -= static_cast<double>(r) * c[j];
220 }
221 c.swap(nc);
222 }
223 T v = fm[i - 1];
224 for (std::size_t j = 1; j < i; ++j) v -= T(num_traits<T>::from_double(c[j]) * m[j - 1]);
225 m[i - 1] = v;
226 }
227 return m;
228}
229
230/** The inputs of both analyzers in BUTools' layout: D{1} = D0, D{k+1} = class k. */
231template <class T>
232struct PrioInput {
233 std::size_t N = 0, K = 0;
234 Matrix<T> D0, sD, I;
235 std::vector<Matrix<T>> D; ///< D[k] is BUTools' D{k+1}
236 std::vector<Matrix<T>> sigma; ///< 1 x M[k]
237 std::vector<Matrix<T>> S; ///< M[k] x M[k]
238 std::vector<Matrix<T>> s; ///< M[k] x 1 exit vector
239 std::vector<std::size_t> M;
240 T prec;
241 std::size_t erl = 200;
242
243 std::size_t msum(std::size_t a, std::size_t b) const { // sum M[a..b-1]
244 std::size_t t = 0;
245 for (std::size_t i = a; i < b; ++i) t += M[i];
246 return t;
247 }
248};
249
250template <class T>
251PrioInput<T> prepare(const Mmap<T>& arrival, const std::vector<PhService<T>>& svc,
252 const PrioQueueOptions& opt, const char* who) {
253 static_assert(num_traits<T>::has_transcendental,
254 "the MMAP/PH/1 priority analyzers run tolerance-terminated Riccati iterations");
255 PrioInput<T> in;
256 in.K = svc.size();
257 if (in.K == 0) throw InputError(std::string(who) + ": at least one class is required");
258 if (arrival.classes() != in.K)
259 throw InputError(std::string(who) +
260 ": the arrival MMAP and the service list disagree on the number of classes");
261 if (opt.erl_max_order < 1) throw InputError(std::string(who) + ": erl_max_order must be >= 1");
262 in.N = arrival.order();
263 in.D0 = arrival.D0;
264 in.D = arrival.Dc;
265 in.I = eye<T>(in.N);
266 in.sD = in.D0;
267 for (std::size_t k = 0; k < in.K; ++k) in.sD = add(in.sD, in.D[k]);
268 for (std::size_t k = 0; k < in.K; ++k) {
269 const std::size_t m = svc[k].S.rows();
270 if (m == 0 || svc[k].S.cols() != m || svc[k].sigma.size() != m)
271 throw InputError(std::string(who) + ": class " + std::to_string(k + 1) +
272 " has a malformed PH service representation");
273 in.M.push_back(m);
274 in.sigma.push_back(rowvec(svc[k].sigma));
275 in.S.push_back(svc[k].S);
276 in.s.push_back(scal(rowsum(svc[k].S), T(num_traits<T>::from_int(-1))));
277 }
278 in.prec = num_traits<T>::from_double(opt.precision);
279 in.erl = opt.erl_max_order;
280 return in;
281}
282
283template <class T>
284FluidFundamental<T> fluid(const PrioInput<T>& in, const Matrix<T>& Fpp, const Matrix<T>& Fpm,
285 const Matrix<T>& Fmp, const Matrix<T>& Fmm) {
286 return mfq_fundamental(Fpp, Fpm, Fmp, Fmm, in.prec, 150u, RiccatiMethod::ADDA);
287}
288
289/** The fluid model of the remaining sojourn (PRPR) or waiting (NPPR) time, started from `inis`. */
290template <class T>
291struct SojournModel {
292 Matrix<T> Qspp, Qspm, Qsmp, Qsmm, inis, Psis;
293};
294
295/** P_n of the sojourn-moment recursion: C_n = -2n P_{n-1} + sum bino P_i Qsmp P_{n-i}. */
296template <class T>
297std::vector<Matrix<T>> st_moment_chain(const SojournModel<T>& sm, std::size_t n) {
298 const Matrix<T> A = add(sm.Qspp, mul(sm.Psis, sm.Qsmp));
299 const Matrix<T> B = add(sm.Qsmm, mul(sm.Qsmp, sm.Psis));
300 const SylvesterFactor<T> F(A, B);
301 std::vector<Matrix<T>> Pn{sm.Psis};
302 std::vector<Matrix<T>> QP{mul(sm.Qsmp, sm.Psis)};
303 for (std::size_t m = 1; m <= n; ++m) {
304 Matrix<T> C = scal(Pn[m - 1], T(num_traits<T>::from_int(-2 * (int)m)));
305 T bino = num_traits<T>::from_int(1);
306 for (std::size_t i = 1; i + 1 <= m; ++i) {
307 bino = T(bino * num_traits<T>::from_int((int)(m - i + 1)) /
308 num_traits<T>::from_int((int)i));
309 C = add(C, scal(mul(Pn[i], QP[m - i]), bino));
310 }
311 Pn.push_back(F.solve_lyap(C));
312 QP.push_back(mul(sm.Qsmp, Pn.back()));
313 }
314 return Pn;
315}
316
317/**
318 * The Erlangized first-passage terms at one CDF point: Psie and P_1..P_{L-1}
319 * of the lambda-shifted model, C_n = 2 lambda P_{n-1} + sum P_i Qsmp P_{n-i}.
320 * Returns sum(inis*P_n) for n = 0..L-1.
321 */
322template <class T>
323std::vector<T> erlang_terms(const PrioInput<T>& in, const SojournModel<T>& sm, const T& lambda) {
324 const std::size_t L = in.erl;
325 const Matrix<T> Ip = eye<T>(sm.Qspp.rows()), Im = eye<T>(sm.Qsmm.rows());
326 const Matrix<T> Qpp = sub(sm.Qspp, scal(Ip, lambda));
327 const Matrix<T> Qmm = sub(sm.Qsmm, scal(Im, lambda));
328 const Matrix<T> Psie = fluid(in, Qpp, sm.Qspm, sm.Qsmp, Qmm).Psi;
329 std::vector<T> out;
330 out.push_back(total(mul(sm.inis, Psie)));
331 const Matrix<T> A = add(Qpp, mul(Psie, sm.Qsmp));
332 const Matrix<T> B = add(Qmm, mul(sm.Qsmp, Psie));
333 const SylvesterFactor<T> F(A, B);
334 std::vector<Matrix<T>> Pn{Psie};
335 std::vector<Matrix<T>> QP{mul(sm.Qsmp, Psie)};
336 const T two_l = T(num_traits<T>::from_int(2) * lambda);
337 for (std::size_t n = 1; n + 1 <= L; ++n) {
338 Matrix<T> C = scal(Pn[n - 1], two_l);
339 for (std::size_t i = 1; i + 1 <= n; ++i) C = add(C, mul(Pn[i], QP[n - i]));
340 Pn.push_back(F.solve_lyap(C));
341 QP.push_back(mul(sm.Qsmp, Pn.back()));
342 out.push_back(total(mul(sm.inis, Pn.back())));
343 }
344 return out;
345}
346
347/** Departure-instant factorial moments to random-time moments, shared by both analyzers. */
348template <class T>
349std::vector<T> random_time_moms(const PrioInput<T>& in, std::size_t k,
350 const std::vector<Matrix<T>>& QLDPn, std::size_t n) {
351 const std::vector<T> piv = mc::ctmc_solve(in.sD);
352 const Matrix<T> pi = rowvec(piv);
353 const T lambdak = total(mul(pi, in.D[k]));
354 const Matrix<T> iTerm = inverse(sub(mul(ones<T>(in.N, 1), pi), in.sD));
355 const Matrix<T> dk1 = rowsum(in.D[k]);
356 std::vector<Matrix<T>> QLPn{pi};
357 std::vector<T> ql(n);
358 for (std::size_t m = 1; m <= n; ++m) {
359 const T mm = num_traits<T>::from_int((int)m);
360 const Matrix<T> a = sub(QLDPn[m - 1], scal(mul(QLPn[m - 1], in.D[k]), T(num_traits<T>::from_int(1) / lambdak)));
361 const T sumP = T(total(QLDPn[m]) + mm * total(mul(a, iTerm, dk1)));
362 const Matrix<T> b = sub(mul(QLPn[m - 1], in.D[k]), scal(QLDPn[m - 1], lambdak));
363 const Matrix<T> P = add(scal(pi, sumP), scal(mul(b, iTerm), mm));
364 QLPn.push_back(P);
365 ql[m - 1] = total(P);
366 }
367 return moms_from_factorial_moms(ql);
368}
369
370/** Departure-instant probabilities to random-time probabilities, shared by both analyzers. */
371template <class T>
372std::vector<T> random_time_probs(const PrioInput<T>& in, std::size_t k,
373 const std::vector<Matrix<T>>& dql) {
374 const Matrix<T> pi = rowvec(mc::ctmc_solve(in.sD));
375 const T lambdak = total(mul(pi, in.D[k]));
376 const Matrix<T> iTerm = ninv(sub(in.sD, in.D[k]));
377 std::vector<T> out;
378 Matrix<T> q = mul(scal(dql[0], lambdak), iTerm);
379 out.push_back(total(q));
380 for (std::size_t n = 1; n < dql.size(); ++n) {
381 q = mul(add(mul(q, in.D[k]), scal(sub(dql[n], dql[n - 1]), lambdak)), iTerm);
382 out.push_back(total(q));
383 }
384 return out;
385}
386
387/** The QBD tail both analyzers use for the number of jobs of the TOP class. */
388template <class T>
389std::vector<T> qbd_measure(const Matrix<T>& R, Matrix<T> p0, Measure what, std::size_t n) {
390 const Matrix<T> IR = sub(eye<T>(R.rows()), R);
391 const Matrix<T> iIR = inverse(IR);
392 p0 = scal(p0, T(num_traits<T>::from_int(1) / total(mul(p0, iIR))));
393 std::vector<T> out;
394 if (what == Measure::NcMoms) {
395 for (std::size_t i = 1; i <= n; ++i)
396 out.push_back(total(scal(mul(p0, mpow(R, i), mpow(iIR, i + 1)), factorial<T>(i))));
397 return moms_from_factorial_moms(out);
398 }
399 Matrix<T> v = p0;
400 out.push_back(total(v));
401 for (std::size_t i = 1; i < n; ++i) {
402 v = mul(v, R);
403 out.push_back(total(v));
404 }
405 return out;
406}
407
408template <class T>
409Matrix<T> qbd_R_of(const PrioInput<T>& in, const Matrix<T>& B, const Matrix<T>& L,
410 const Matrix<T>& F) {
411 return qbd_R_logred(B, L, F, 10000u, in.prec);
412}
413
414/** Sojourn moments / CDF of a class whose law is the PH (zeta, Z) with exit z. */
415template <class T>
416std::vector<T> ph_sojourn(const Matrix<T>& zeta, const Matrix<T>& Z, const Matrix<T>& z,
417 Measure what, std::size_t n, const std::vector<T>& pts) {
418 const Matrix<T> iZ = ninv(Z);
419 std::vector<T> out;
420 if (what == Measure::StMoms) {
421 for (std::size_t i = 1; i <= n; ++i)
422 out.push_back(T(factorial<T>(i) * total(mul(zeta, mpow(iZ, i + 1), z))));
423 } else {
424 const Matrix<T> I = eye<T>(Z.rows());
425 for (const T& t : pts)
426 out.push_back(total(mul(mul(zeta, iZ), sub(I, expm(scal(Z, t))), z)));
427 }
428 return out;
429}
430
431// ---------------------------------------------------------------- PRPR
432
433template <class T>
434std::vector<T> prpr_class(const PrioInput<T>& in, std::size_t k, Measure what, std::size_t n,
435 const std::vector<T>& pts) {
436 const std::size_t N = in.N, K = in.K;
437 const Matrix<T>& I = in.I;
438 // step 1. workload process of the classes k..K
439 const std::size_t sM = in.msum(k, K);
440 Matrix<T> Qwmm = in.D0;
441 for (std::size_t i = 0; i < k; ++i) Qwmm = add(Qwmm, in.D[i]);
442 Matrix<T> Qwpm = zeros<T>(N * sM, N), Qwmp = zeros<T>(N, N * sM), Qwpp = zeros<T>(N * sM, N * sM);
443 std::size_t kix = 0;
444 for (std::size_t i = k; i < K; ++i) {
445 setblk(Qwmp, 0, kix, kron(in.D[i], in.sigma[i]));
446 setblk(Qwpm, kix, 0, kron(I, in.s[i]));
447 setblk(Qwpp, kix, kix, kron(I, in.S[i]));
448 kix += N * in.M[i];
449 }
450 const FluidFundamental<T> fw = fluid(in, Qwpp, Qwpm, Qwmp, Qwmm);
451 const Matrix<T>& Kw = fw.K;
452 const Matrix<T> iKw = ninv(Kw);
453 const Matrix<T> Ua =
454 add(ones<T>(N, 1), scal(rowsum(mul(Qwmp, iKw)), T(num_traits<T>::from_int(2))));
455 Matrix<T> UwUa = hcat(fw.U, Ua); // N x (N+1); pm [Uw, Ua] = [0 .. 0, 1]
456 Matrix<T> At = zeros<T>(N + 1, N);
457 for (std::size_t i = 0; i < N; ++i)
458 for (std::size_t j = 0; j <= N; ++j) At(j, i) = UwUa(i, j);
459 std::vector<T> rhs(N + 1, num_traits<T>::from_int(0));
460 rhs[N] = num_traits<T>::from_int(1);
461 const Matrix<T> pm = rowvec(mfq_detail::normal_equations_solve(At, rhs));
462
463 Matrix<T> Bw = zeros<T>(N * sM, N);
464 setblk(Bw, 0, 0, kron(I, in.s[k]));
465 const Matrix<T> pmQ = mul(pm, Qwmp);
466 const Matrix<T> kappa = scal(pmQ, T(num_traits<T>::from_int(1) / total(mul(pmQ, iKw, Bw))));
467
468 if (k + 1 == K) {
469 if (what == Measure::StMoms || what == Measure::StDistr)
470 return ph_sojourn(kappa, Kw, rowsum(Bw), what, n, pts);
471 const std::size_t Mk = in.M[k];
472 const Matrix<T> IM = eye<T>(Mk);
473 const Matrix<T> sDk = sub(in.sD, in.D[k]);
474 const Matrix<T> L = add(kron(sDk, IM), kron(I, in.S[k]));
475 const Matrix<T> B = kron(I, mul(in.s[k], in.sigma[k]));
476 const Matrix<T> F = kron(in.D[k], IM);
477 const Matrix<T> L0 = kron(sDk, IM);
478 const Matrix<T> R = qbd_R_of(in, B, L, F);
479 const Matrix<T> p0 = rowvec(mc::ctmc_solve(add(L0, mul(R, B))));
480 return qbd_measure(R, p0, what, n);
481 }
482
483 // step 2. fluid model of the remaining sojourn time
484 SojournModel<T> sm;
485 sm.Qsmm = in.D0;
486 for (std::size_t i = 0; i <= k; ++i) sm.Qsmm = add(sm.Qsmm, in.D[i]);
487 const std::size_t Np = Kw.rows(), ext = N * in.msum(k + 1, K);
488 sm.Qspm = zeros<T>(Np + ext, N);
489 sm.Qsmp = zeros<T>(N, Np + ext);
490 sm.Qspp = zeros<T>(Np + ext, Np + ext);
491 setblk(sm.Qspp, 0, 0, Kw);
492 setblk(sm.Qspm, 0, 0, Bw);
493 kix = Np;
494 for (std::size_t i = k + 1; i < K; ++i) {
495 setblk(sm.Qsmp, 0, kix, kron(in.D[i], in.sigma[i]));
496 setblk(sm.Qspm, kix, 0, kron(I, in.s[i]));
497 setblk(sm.Qspp, kix, kix, kron(I, in.S[i]));
498 kix += N * in.M[i];
499 }
500 sm.inis = hcat(kappa, zeros<T>(1, ext));
501 sm.Psis = fluid(in, sm.Qspp, sm.Qspm, sm.Qsmp, sm.Qsmm).Psi;
502
503 std::vector<T> out;
504 if (what == Measure::StMoms) {
505 const std::vector<Matrix<T>> Pn = st_moment_chain(sm, n);
506 for (std::size_t m = 1; m <= n; ++m) {
507 T v = T(total(mul(sm.inis, Pn[m])) / num_traits<T>::from_double(std::pow(2.0, (double)m)));
508 if (m % 2 == 1) v = T(-v);
509 out.push_back(v);
510 }
511 } else if (what == Measure::StDistr) {
512 for (const T& t : pts) {
513 const T lambda = T(num_traits<T>::from_int((int)in.erl) / t / num_traits<T>::from_int(2));
514 T pr = num_traits<T>::from_int(0);
515 for (const T& v : erlang_terms(in, sm, lambda)) pr += v;
516 out.push_back(pr);
517 }
518 } else if (what == Measure::NcMoms) {
519 const Matrix<T> A = add(sm.Qspp, mul(sm.Psis, sm.Qsmp));
520 const Matrix<T> B = add(sm.Qsmm, mul(sm.Qsmp, sm.Psis));
521 const SylvesterFactor<T> F(A, B);
522 std::vector<Matrix<T>> P{sm.Psis};
523 std::vector<Matrix<T>> QP{mul(sm.Qsmp, sm.Psis)};
524 for (std::size_t m = 1; m <= n; ++m) {
525 Matrix<T> C = scal(mul(P[m - 1], in.D[k]), T(num_traits<T>::from_int((int)m)));
526 T bino = num_traits<T>::from_int(1);
527 for (std::size_t i = 1; i + 1 <= m; ++i) {
528 bino = T(bino * num_traits<T>::from_int((int)(m - i + 1)) /
529 num_traits<T>::from_int((int)i));
530 C = add(C, scal(mul(P[i], QP[m - i]), bino));
531 }
532 P.push_back(F.solve_lyap(C));
533 QP.push_back(mul(sm.Qsmp, P.back()));
534 }
535 std::vector<Matrix<T>> QLDPn;
536 for (const Matrix<T>& p : P) QLDPn.push_back(mul(sm.inis, p));
537 out = random_time_moms(in, k, QLDPn, n);
538 } else {
539 Matrix<T> sDk = in.D0;
540 for (std::size_t i = 0; i < k; ++i) sDk = add(sDk, in.D[i]);
541 const Matrix<T> Psid = fluid(in, sm.Qspp, sm.Qspm, sm.Qsmp, sDk).Psi;
542 const Matrix<T> A = add(sm.Qspp, mul(Psid, sm.Qsmp));
543 const Matrix<T> B = add(sDk, mul(sm.Qsmp, Psid));
544 const SylvesterFactor<T> F(A, B);
545 std::vector<Matrix<T>> P{Psid};
546 std::vector<Matrix<T>> QP{mul(sm.Qsmp, Psid)};
547 std::vector<Matrix<T>> dql{mul(sm.inis, Psid)};
548 for (std::size_t m = 1; m < n; ++m) {
549 Matrix<T> C = mul(P[m - 1], in.D[k]);
550 for (std::size_t i = 1; i + 1 <= m; ++i) C = add(C, mul(P[i], QP[m - i]));
551 P.push_back(F.solve_lyap(C));
552 QP.push_back(mul(sm.Qsmp, P.back()));
553 dql.push_back(mul(sm.inis, P.back()));
554 }
555 out = random_time_probs(in, k, dql);
556 }
557 return out;
558}
559
560// ---------------------------------------------------------------- NPPR
561
562template <class T>
563struct NpprModel {
564 Matrix<T> pm;
565 std::vector<Matrix<T>> Psiw, Qwmp, Qwzp, Qwpp, Qwmz, Qwpz, Qwzz, Qwmm, Qwpm, Qwzm;
566 std::vector<Matrix<T>> q0, qL; ///< q0[k], qL[k] for k = 1..K-1 (BUTools q0{k+1})
567 std::vector<T> lambda;
568};
569
570template <class T>
571NpprModel<T> nppr_build(const PrioInput<T>& in) {
572 const std::size_t N = in.N, K = in.K;
573 const Matrix<T>& I = in.I;
574 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
575 NpprModel<T> md;
576
577 // step 1. workload process of the joint queue
578 const std::size_t sM = in.msum(0, K);
579 Matrix<T> Qwpp = zeros<T>(N * sM, N * sM), Qwmp = zeros<T>(N, N * sM),
580 Qwpm = zeros<T>(N * sM, N);
581 std::size_t kix = 0;
582 for (std::size_t i = 0; i < K; ++i) {
583 setblk(Qwpp, kix, kix, kron(I, in.S[i]));
584 setblk(Qwmp, 0, kix, kron(in.D[i], in.sigma[i]));
585 setblk(Qwpm, kix, 0, kron(I, in.s[i]));
586 kix += N * in.M[i];
587 }
588 const FluidFundamental<T> fw = fluid(in, Qwpp, Qwpm, Qwmp, in.D0);
589 const Matrix<T> iKw = ninv(fw.K);
590 const Matrix<T> Ua = add(ones<T>(N, 1), scal(rowsum(mul(Qwmp, iKw)), T(num_traits<T>::from_int(2))));
591 const Matrix<T> UwUa = hcat(fw.U, Ua);
592 Matrix<T> At = zeros<T>(N + 1, N);
593 for (std::size_t i = 0; i < N; ++i)
594 for (std::size_t j = 0; j <= N; ++j) At(j, i) = UwUa(i, j);
595 std::vector<T> rhs(N + 1, zero);
596 rhs[N] = one;
597 md.pm = rowvec(mfq_detail::normal_equations_solve(At, rhs));
598 const T spm = total(md.pm);
599 const T half_idle = T((one - spm) / num_traits<T>::from_int(2));
600 const T ro = T(half_idle / (spm + half_idle));
601 const Matrix<T> kappa = scal(md.pm, T(one / spm));
602
603 const Matrix<T> pi = rowvec(mc::ctmc_solve(in.sD));
604 md.lambda.resize(K);
605 for (std::size_t i = 0; i < K; ++i) md.lambda[i] = total(mul(pi, in.D[i]));
606
607 // step 2. workload process of the classes k..K, one per k
608 for (std::size_t k = 0; k < K; ++k) {
609 const std::size_t Mlo = in.msum(0, k), Mhi = in.msum(k, K);
610 const std::size_t p = N * Mlo * Mhi + N * Mhi, z = N * Mlo;
611 Matrix<T> Qkwpp = zeros<T>(p, p), Qkwpz = zeros<T>(p, z), Qkwpm = zeros<T>(p, N);
612 Matrix<T> Qkwmz = zeros<T>(N, z), Qkwmp = zeros<T>(N, p);
613 Matrix<T> Dlo = in.D0;
614 for (std::size_t i = 0; i < k; ++i) Dlo = add(Dlo, in.D[i]);
615 Matrix<T> Qkwzp = zeros<T>(z, p), Qkwzm = zeros<T>(z, N), Qkwzz = zeros<T>(z, z);
616 kix = 0;
617 for (std::size_t i = k; i < K; ++i) {
618 std::size_t kix2 = 0;
619 for (std::size_t j = 0; j < k; ++j) {
620 const Matrix<T> IMj = eye<T>(in.M[j]);
621 setblk(Qkwpp, kix, kix, kron(I, kron(IMj, in.S[i])));
622 setblk(Qkwpz, kix, kix2, kron(I, kron(IMj, in.s[i])));
623 setblk(Qkwzp, kix2, kix, kron(in.D[i], kron(IMj, in.sigma[i])));
624 kix += N * in.M[j] * in.M[i];
625 kix2 += N * in.M[j];
626 }
627 }
628 for (std::size_t i = k; i < K; ++i) {
629 setblk(Qkwpp, kix, kix, kron(I, in.S[i]));
630 setblk(Qkwpm, kix, 0, kron(I, in.s[i]));
631 setblk(Qkwmp, 0, kix, kron(in.D[i], in.sigma[i]));
632 kix += N * in.M[i];
633 }
634 kix = 0;
635 for (std::size_t j = 0; j < k; ++j) {
636 setblk(Qkwzz, kix, kix, add(kron(Dlo, eye<T>(in.M[j])), kron(I, in.S[j])));
637 setblk(Qkwzm, kix, 0, kron(I, in.s[j]));
638 kix += N * in.M[j];
639 }
640 Matrix<T> Fpp = Qkwpp, Fpm = Qkwpm;
641 if (z > 0) {
642 const Matrix<T> iZ = ninv(Qkwzz);
643 Fpp = add(Fpp, mul(Qkwpz, iZ, Qkwzp));
644 Fpm = add(Fpm, mul(Qkwpz, iZ, Qkwzm));
645 }
646 md.Psiw.push_back(fluid(in, Fpp, Fpm, Qkwmp, Dlo).Psi);
647 md.Qwzp.push_back(Qkwzp);
648 md.Qwmp.push_back(Qkwmp);
649 md.Qwpp.push_back(Qkwpp);
650 md.Qwmz.push_back(Qkwmz);
651 md.Qwpz.push_back(Qkwpz);
652 md.Qwzz.push_back(Qkwzz);
653 md.Qwmm.push_back(Dlo);
654 md.Qwpm.push_back(Qkwpm);
655 md.Qwzm.push_back(Qkwzm);
656 }
657
658 // step 3. the phi vectors; phi[0] is BUTools' phi{1}
659 T lambdaS = zero;
660 for (const T& l : md.lambda) lambdaS += l;
661 const Matrix<T> iD0 = ninv(in.D0);
662 std::vector<Matrix<T>> phi{scal(mul(kappa, scal(in.D0, T(-one))), T((one - ro) / lambdaS))};
663 md.q0.assign(K, Matrix<T>());
664 md.qL.assign(K, Matrix<T>());
665 for (std::size_t k = 1; k < K; ++k) { // BUTools k = 1..K-1, counting the first k classes
666 Matrix<T> sDk = in.D0;
667 for (std::size_t j = 0; j < k; ++j) sDk = add(sDk, in.D[j]);
668 T lk = zero;
669 for (std::size_t j = 0; j < k; ++j) lk += md.lambda[j];
670 const T pk = T(lk / lambdaS - (one - ro) * total(mul(kappa, rowsum(sDk))) / lambdaS);
671 const Matrix<T>& Qwzpk = md.Qwzp[k];
672 std::vector<Matrix<T>> Ak(k), Gi(k);
673 std::size_t vix = 0;
674 for (std::size_t ii = 0; ii < k; ++ii) {
675 const std::size_t bs = N * in.M[ii];
676 const Matrix<T> V1 = getblk(Qwzpk, vix, 0, bs, Qwzpk.cols());
677 Gi[ii] = ninv(add(kron(sDk, eye<T>(in.M[ii])), kron(I, in.S[ii])));
678 Ak[ii] = mul(mul(kron(I, in.sigma[ii]), Gi[ii]),
679 add(kron(I, in.s[ii]), mul(V1, md.Psiw[k])));
680 vix += bs;
681 }
682 const Matrix<T> Bk = mul(md.Qwmp[k], md.Psiw[k]);
683 Matrix<T> ztag = mul(phi[0], add(sub(mul(iD0, in.D[k - 1], Ak[k - 1]), Ak[0]), mul(iD0, Bk)));
684 for (std::size_t i = 0; i + 1 < k; ++i)
685 ztag = add(ztag, add(mul(phi[i + 1], sub(Ak[i], Ak[i + 1])),
686 mul(mul(phi[0], iD0), mul(in.D[i], Ak[i]))));
687 Matrix<T> Mx = sub(eye<T>(N), Ak[k - 1]);
688 for (std::size_t i = 0; i < N; ++i) Mx(i, 0) = one;
689 Matrix<T> lhs = ztag;
690 lhs(0, 0) = pk;
691 phi.push_back(mul(lhs, inverse(Mx)));
692 md.q0[k] = mul(phi[0], iD0);
693 Matrix<T> qL;
694 for (std::size_t ii = 0; ii < k; ++ii) {
695 const Matrix<T> a = add(sub(phi[ii + 1], phi[ii]), mul(phi[0], iD0, in.D[ii]));
696 const Matrix<T> q = mul(mul(a, kron(I, in.sigma[ii])), Gi[ii]);
697 qL = (ii == 0) ? q : hcat(qL, q);
698 }
699 md.qL[k] = qL;
700 }
701 return md;
702}
703
704template <class T>
705std::vector<T> nppr_class(const PrioInput<T>& in, const NpprModel<T>& md, std::size_t k,
706 Measure what, std::size_t n, const std::vector<T>& pts) {
707 const std::size_t N = in.N, K = in.K;
708 const Matrix<T>& I = in.I;
709 const T one = num_traits<T>::from_int(1);
710 Matrix<T> sD0k = in.D0;
711 for (std::size_t i = 0; i < k; ++i) sD0k = add(sD0k, in.D[i]);
712 const bool hasz = md.Qwzz[k].rows() > 0;
713 const Matrix<T> iZz = hasz ? ninv(md.Qwzz[k]) : Matrix<T>();
714 Matrix<T> Kw = add(md.Qwpp[k], mul(md.Psiw[k], md.Qwmp[k]));
715 if (hasz) Kw = add(Kw, mul(md.Qwpz[k], iZz, md.Qwzp[k]));
716
717 if (k + 1 == K) {
718 if (what == Measure::StMoms || what == Measure::StDistr) {
719 Matrix<T> AM, BM, CM;
720 for (std::size_t i = 0; i < k; ++i) {
721 const Matrix<T> am = kron(ones<T>(N, 1), kron(eye<T>(in.M[i]), in.s[k]));
722 AM = (i == 0) ? am : blkdiag(AM, am);
723 BM = (i == 0) ? in.S[i] : blkdiag(BM, in.S[i]);
724 CM = (i == 0) ? in.s[i] : vcat(CM, in.s[i]);
725 }
726 const Matrix<T> AMz = vcat(AM, zeros<T>(N * in.M[k], AM.cols()));
727 const Matrix<T> Z = vcat(hcat(Kw, AMz), hcat(zeros<T>(BM.rows(), Kw.cols()), BM));
728 const Matrix<T> z =
729 vcat(vcat(zeros<T>(AM.rows(), 1), kron(ones<T>(N, 1), in.s[k])), CM);
730 const Matrix<T> iniw =
731 hcat(add(mul(md.q0[k], md.Qwmp[k]), mul(md.qL[k], md.Qwzp[k])),
732 zeros<T>(1, BM.rows()));
733 const Matrix<T> zeta = scal(iniw, T(one / total(mul(iniw, ninv(Z), z))));
734 return ph_sojourn(zeta, Z, z, what, n, pts);
735 }
736 const std::size_t sM = in.msum(0, K), c0 = N * in.msum(0, k);
737 Matrix<T> L = zeros<T>(N * sM, N * sM), B = zeros<T>(N * sM, N * sM),
738 F = zeros<T>(N * sM, N * sM);
739 std::size_t kix = 0;
740 for (std::size_t i = 0; i < K; ++i) {
741 const Matrix<T> IMi = eye<T>(in.M[i]);
742 setblk(F, kix, kix, kron(in.D[k], IMi));
743 setblk(L, kix, kix, add(kron(sD0k, IMi), kron(I, in.S[i])));
744 const Matrix<T> blk = kron(I, mul(in.s[i], in.sigma[k]));
745 if (i + 1 < K)
746 setblk(L, kix, c0, blk);
747 else
748 setblk(B, kix, c0, blk);
749 kix += N * in.M[i];
750 }
751 const Matrix<T> R = qbd_R_of(in, B, L, F);
752 const Matrix<T> p0 = hcat(md.qL[k], mul(md.q0[k], kron(I, in.sigma[k])));
753 return qbd_measure(R, p0, what, n);
754 }
755
756 // step 4.1 workload right before the arrivals of class k
757 Matrix<T> BM, CM, DM;
758 for (std::size_t i = 0; i < k; ++i) {
759 const Matrix<T> bm = kron(I, in.S[i]), cm = kron(I, in.s[i]),
760 dm = kron(in.D[k], eye<T>(in.M[i]));
761 BM = (i == 0) ? bm : blkdiag(BM, bm);
762 CM = (i == 0) ? cm : vcat(CM, cm);
763 DM = (i == 0) ? dm : blkdiag(DM, dm);
764 }
765 Matrix<T> Kwu = Kw, Bwu = mul(md.Psiw[k], in.D[k]), iniw, pwu;
766 if (k > 0) {
767 const Matrix<T> top =
768 mul(add(md.Qwpz[k], mul(md.Psiw[k], md.Qwmz[k])), iZz, DM);
769 Kwu = vcat(hcat(Kw, top), hcat(zeros<T>(BM.rows(), Kw.cols()), BM));
770 Bwu = vcat(Bwu, CM);
771 iniw = hcat(add(mul(md.q0[k], md.Qwmp[k]), mul(md.qL[k], md.Qwzp[k])), mul(md.qL[k], DM));
772 pwu = mul(md.q0[k], in.D[k]);
773 } else {
774 iniw = mul(md.pm, md.Qwmp[k]);
775 pwu = mul(md.pm, in.D[k]);
776 }
777 const T nrm = T(total(pwu) + total(mul(iniw, ninv(Kwu), Bwu)));
778 pwu = scal(pwu, T(one / nrm));
779 iniw = scal(iniw, T(one / nrm));
780
781 // step 4.2 fluid model whose first passage time is the WAITING time
782 SojournModel<T> sm;
783 const std::size_t KN = Kwu.rows(), ext = N * in.msum(k + 1, K);
784 sm.Qspp = zeros<T>(KN + ext, KN + ext);
785 sm.Qspm = zeros<T>(KN + ext, N);
786 sm.Qsmp = zeros<T>(N, KN + ext);
787 sm.Qsmm = add(sD0k, in.D[k]);
788 std::size_t kix = KN;
789 for (std::size_t i = k + 1; i < K; ++i) {
790 setblk(sm.Qspp, kix, kix, kron(I, in.S[i]));
791 setblk(sm.Qspm, kix, 0, kron(I, in.s[i]));
792 setblk(sm.Qsmp, 0, kix, kron(in.D[i], in.sigma[i]));
793 kix += N * in.M[i];
794 }
795 setblk(sm.Qspp, 0, 0, Kwu);
796 setblk(sm.Qspm, 0, 0, Bwu);
797 sm.inis = hcat(iniw, zeros<T>(1, ext));
798 sm.Psis = fluid(in, sm.Qspp, sm.Qspm, sm.Qsmp, sm.Qsmm).Psi;
799
800 const Matrix<T>& sig = in.sigma[k];
801 const Matrix<T>& Sk = in.S[k];
802 const std::size_t Mk = in.M[k];
803 std::vector<T> out;
804 if (what == Measure::StMoms) {
805 const std::vector<Matrix<T>> Pn = st_moment_chain(sm, n);
806 const Matrix<T> iSk = ninv(Sk);
807 Matrix<T> Pnr = scal(sig, total(mul(sm.inis, Pn[0])));
808 for (std::size_t m = 1; m <= n; ++m) {
809 T w = T(total(mul(sm.inis, Pn[m])) / num_traits<T>::from_double(std::pow(2.0, (double)m)));
810 if (m % 2 == 1) w = T(-w);
811 const Matrix<T> P =
812 add(scal(mul(Pnr, iSk), num_traits<T>::from_int((int)m)), scal(sig, w));
813 Pnr = P;
814 out.push_back(T(total(P) + total(pwu) * factorial<T>(m) * total(mul(sig, mpow(iSk, m)))));
815 }
816 } else if (what == Measure::StDistr) {
817 const std::size_t L = in.erl;
818 for (const T& t : pts) {
819 const T lambdae = T(num_traits<T>::from_int((int)L) / t / num_traits<T>::from_int(2));
820 const std::vector<T> terms = erlang_terms(in, sm, lambdae);
821 // tail[m] = 1 - sum(sigma * inv(I - S/(2 lambdae))^m), m = 1..L
822 const Matrix<T> G = inverse(sub(eye<T>(Mk), scal(Sk, T(one / (num_traits<T>::from_int(2) * lambdae)))));
823 std::vector<T> tail(L + 1, num_traits<T>::from_int(0));
824 Matrix<T> v = sig;
825 for (std::size_t m = 1; m <= L; ++m) {
826 v = mul(v, G);
827 tail[m] = T(one - total(v));
828 }
829 T pr = T((total(pwu) + terms[0]) * tail[L]);
830 for (std::size_t m = 1; m + 1 <= L; ++m) pr += T(terms[m] * tail[L - m]);
831 out.push_back(pr);
832 }
833 } else {
834 const Matrix<T> IMk = eye<T>(Mk);
835 const Matrix<T> G = ninv(add(kron(sub(in.sD, in.D[k]), IMk), kron(I, Sk)));
836 const Matrix<T> W = mul(G, kron(in.D[k], IMk));
837 const Matrix<T> iW = inverse(sub(eye<T>(W.rows()), W));
838 const Matrix<T> w = kron(I, sig);
839 const Matrix<T> omega = mul(G, kron(I, in.s[k]));
840 if (what == Measure::NcMoms) {
841 const Matrix<T> A = add(sm.Qspp, mul(sm.Psis, sm.Qsmp));
842 const Matrix<T> B = add(sm.Qsmm, mul(sm.Qsmp, sm.Psis));
843 const SylvesterFactor<T> F(A, B);
844 std::vector<Matrix<T>> Psii{sm.Psis};
845 std::vector<Matrix<T>> QP{mul(sm.Qsmp, sm.Psis)};
846 std::vector<Matrix<T>> QLDPn{mul(mul(sm.inis, sm.Psis), w, iW)};
847 for (std::size_t m = 1; m <= n; ++m) {
848 Matrix<T> C = scal(mul(Psii[m - 1], in.D[k]), T(num_traits<T>::from_int((int)m)));
849 T bino = one;
850 for (std::size_t i = 1; i + 1 <= m; ++i) {
851 bino = T(bino * num_traits<T>::from_int((int)(m - i + 1)) /
852 num_traits<T>::from_int((int)i));
853 C = add(C, scal(mul(Psii[i], QP[m - i]), bino));
854 }
855 Psii.push_back(F.solve_lyap(C));
856 QP.push_back(mul(sm.Qsmp, Psii.back()));
857 QLDPn.push_back(add(scal(mul(QLDPn[m - 1], iW, W), num_traits<T>::from_int((int)m)),
858 mul(mul(sm.inis, Psii.back()), w, iW)));
859 }
860 for (std::size_t m = 0; m <= n; ++m)
861 QLDPn[m] = mul(add(QLDPn[m], mul(mul(pwu, w), mpow(iW, m + 1), mpow(W, m))), omega);
862 // random_time_moms uses the model's own class-k rate, lambda(k)
863 out = random_time_moms(in, k, QLDPn, n);
864 } else {
865 const Matrix<T> Psid = fluid(in, sm.Qspp, sm.Qspm, sm.Qsmp, sD0k).Psi;
866 const Matrix<T> A = add(sm.Qspp, mul(Psid, sm.Qsmp));
867 const Matrix<T> B = add(sD0k, mul(sm.Qsmp, Psid));
868 const SylvesterFactor<T> F(A, B);
869 std::vector<Matrix<T>> P{Psid};
870 std::vector<Matrix<T>> QP{mul(sm.Qsmp, Psid)};
871 Matrix<T> XDn = mul(sm.inis, Psid, w);
872 const Matrix<T> pw = mul(pwu, w);
873 std::vector<Matrix<T>> dql{mul(add(XDn, pw), omega)};
874 Matrix<T> Wn = eye<T>(W.rows());
875 for (std::size_t m = 1; m < n; ++m) {
876 Matrix<T> C = mul(P[m - 1], in.D[k]);
877 for (std::size_t i = 1; i + 1 <= m; ++i) C = add(C, mul(P[i], QP[m - i]));
878 P.push_back(F.solve_lyap(C));
879 QP.push_back(mul(sm.Qsmp, P.back()));
880 XDn = add(mul(XDn, W), mul(sm.inis, P.back(), w));
881 Wn = mul(Wn, W);
882 dql.push_back(mul(add(XDn, mul(pw, Wn)), omega));
883 }
884 out = random_time_probs(in, k, dql);
885 }
886 }
887 return out;
888}
889
890template <class T>
891std::vector<std::vector<T>> run(bool preemptive, const Mmap<T>& arrival,
892 const std::vector<PhService<T>>& svc, Measure what,
893 std::size_t n, const std::vector<T>& pts,
894 const PrioQueueOptions& opt) {
895 const char* who = preemptive ? "mmapph1prpr" : "mmapph1nppr";
896 const PrioInput<T> in = prepare(arrival, svc, opt, who);
897 if (what == Measure::StDistr) {
898 for (const T& t : pts)
899 if (!(t > num_traits<T>::from_int(0)))
900 throw InputError(std::string(who) +
901 ": sojourn CDF points must be strictly positive (the Erlangization "
902 "divides by t)");
903 } else if (n == 0) {
904 throw InputError(std::string(who) + ": at least one moment or level is required");
905 }
906 std::vector<std::vector<T>> out(in.K);
907 if (preemptive) {
908 for (std::size_t k = 0; k < in.K; ++k) out[k] = prpr_class(in, k, what, n, pts);
909 } else {
910 if (in.K < 2)
911 throw InputError("mmapph1nppr: at least two classes are required (with one class the "
912 "queue is MMAPPH1FCFS)");
913 const NpprModel<T> md = nppr_build(in);
914 for (std::size_t k = 0; k < in.K; ++k) out[k] = nppr_class(in, md, k, what, n, pts);
915 }
916 return out;
917}
918
919} // namespace prio_detail
920
921/** Per-class moments 1..n of the number of jobs, MMAP[K]/PH[K]/1 preemptive resume priority. */
922template <class T>
923std::vector<std::vector<T>> mmapph1prpr_ncmoms(const Mmap<T>& arrival,
924 const std::vector<PhService<T>>& svc,
925 std::size_t n,
927 return prio_detail::run(true, arrival, svc, prio_detail::Measure::NcMoms, n, std::vector<T>(), opt);
928}
929
930/** Per-class P(number of jobs = 0..nmax-1), preemptive resume priority. */
931template <class T>
932std::vector<std::vector<T>> mmapph1prpr_ncdistr(const Mmap<T>& arrival,
933 const std::vector<PhService<T>>& svc,
934 std::size_t nmax,
936 return prio_detail::run(true, arrival, svc, prio_detail::Measure::NcDistr, nmax, std::vector<T>(), opt);
937}
938
939/** Per-class sojourn-time moments 1..n, preemptive resume priority. */
940template <class T>
941std::vector<std::vector<T>> mmapph1prpr_stmoms(const Mmap<T>& arrival,
942 const std::vector<PhService<T>>& svc,
943 std::size_t n,
945 return prio_detail::run(true, arrival, svc, prio_detail::Measure::StMoms, n, std::vector<T>(), opt);
946}
947
948/** Per-class sojourn-time CDF at the (strictly positive) points, preemptive resume priority. */
949template <class T>
950std::vector<std::vector<T>> mmapph1prpr_stdistr(const Mmap<T>& arrival,
951 const std::vector<PhService<T>>& svc,
952 const std::vector<T>& points,
954 return prio_detail::run(true, arrival, svc, prio_detail::Measure::StDistr, 0, points, opt);
955}
956
957/** Per-class moments 1..n of the number of jobs, MMAP[K]/PH[K]/1 non-preemptive priority. */
958template <class T>
959std::vector<std::vector<T>> mmapph1nppr_ncmoms(const Mmap<T>& arrival,
960 const std::vector<PhService<T>>& svc,
961 std::size_t n,
963 return prio_detail::run(false, arrival, svc, prio_detail::Measure::NcMoms, n, std::vector<T>(), opt);
964}
965
966/** Per-class P(number of jobs = 0..nmax-1), non-preemptive priority. */
967template <class T>
968std::vector<std::vector<T>> mmapph1nppr_ncdistr(const Mmap<T>& arrival,
969 const std::vector<PhService<T>>& svc,
970 std::size_t nmax,
972 return prio_detail::run(false, arrival, svc, prio_detail::Measure::NcDistr, nmax, std::vector<T>(), opt);
973}
974
975/** Per-class sojourn-time moments 1..n, non-preemptive priority. */
976template <class T>
977std::vector<std::vector<T>> mmapph1nppr_stmoms(const Mmap<T>& arrival,
978 const std::vector<PhService<T>>& svc,
979 std::size_t n,
981 return prio_detail::run(false, arrival, svc, prio_detail::Measure::StMoms, n, std::vector<T>(), opt);
982}
983
984/** Per-class sojourn-time CDF at the (strictly positive) points, non-preemptive priority. */
985template <class T>
986std::vector<std::vector<T>> mmapph1nppr_stdistr(const Mmap<T>& arrival,
987 const std::vector<PhService<T>>& svc,
988 const std::vector<T>& points,
990 return prio_detail::run(false, arrival, svc, prio_detail::Measure::StDistr, 0, points, opt);
991}
992
993} // namespace mam
994} // namespace line
995
996#endif // LINE_API_MAM_MMAPPH1PRIO_H
InputError(const std::string &what)
Definition error.h:39
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Dense matrix and non-owning view.
Core of the Markovian fluid queue: the fundamental matrices Psi, K, U and the matrix-exponential stat...
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
The MMAP[K]/PH[K]/1 FCFS queue: per-class mean number in system and per-class queue-length distributi...
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
FluidFundamental< T > mfq_fundamental(const Matrix< T > &Fpp, const Matrix< T > &Fpm, const Matrix< T > &Fmp, const Matrix< T > &Fmm, const T &precision, unsigned maxNumIt, RiccatiMethod method)
Psi, K and U of a fluid queue whose drifts have been normalized to +-1.
Definition mfq_solve.h:196
std::vector< std::vector< T > > mmapph1prpr_ncmoms(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t n, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class moments 1..n of the number of jobs, MMAP[K]/PH[K]/1 preemptive resume priority.
std::vector< std::vector< T > > mmapph1prpr_stdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, const std::vector< T > &points, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class sojourn-time CDF at the (strictly positive) points, preemptive resume priority.
std::vector< std::vector< T > > mmapph1nppr_stdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, const std::vector< T > &points, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class sojourn-time CDF at the (strictly positive) points, non-preemptive priority.
std::vector< std::vector< T > > mmapph1nppr_ncdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t nmax, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class P(number of jobs = 0..nmax-1), non-preemptive priority.
Matrix< T > qbd_R_logred(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by logarithmic reduction (qbd_R_logred.m).
Definition qbd_r.h:217
std::vector< std::vector< T > > mmapph1prpr_stmoms(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t n, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class sojourn-time moments 1..n, preemptive resume priority.
std::vector< std::vector< T > > mmapph1prpr_ncdistr(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t nmax, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class P(number of jobs = 0..nmax-1), preemptive resume priority.
std::vector< std::vector< T > > mmapph1nppr_stmoms(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t n, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class sojourn-time moments 1..n, non-preemptive priority.
std::vector< std::vector< T > > mmapph1nppr_ncmoms(const Mmap< T > &arrival, const std::vector< PhService< T > > &svc, std::size_t n, const PrioQueueOptions &opt=PrioQueueOptions())
Per-class moments 1..n of the number of jobs, MMAP[K]/PH[K]/1 non-preemptive priority.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
@ Bk
Birman-Kogan saddle point with bottleneck detection.
Definition pfqn_nc.h:115
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
One class's phase-type service law, He's (sigma_k, S_k).
Definition mmapph1fcfs.h:71
Options shared by both priority analyzers (the BUTools 'erlMaxOrder' and 'prec').
Definition mmapph1prio.h:58
std::size_t erl_max_order
Erlang order of the sojourn-time CDF inversion.
Definition mmapph1prio.h:59
double precision
tolerance of the fluid and QBD fundamental matrices
Definition mmapph1prio.h:60
The Sylvester equation A X + X B = C, and MATLAB's lyap(A,B,C).