LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qsys_mmapgk1.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_H
6#define LINE_API_QSYS_QSYS_MMAPGK1_H
7
8/**
9 * @file
10 * @ingroup api_qsys
11 * The MMAP[K]/G[K]/1 FCFS queue: K customer types with class-dependent GENERAL
12 * service, fed by a marked Markovian arrival process.
13 *
14 * WHY THIS IS NOT MMAPPH1FCFS. That routine needs every type's service to be
15 * PHASE TYPE, because it builds a QBD whose phase carries the service phase.
16 * Here the service laws are arbitrary and may differ in family across types --
17 * deterministic for one, uniform for another -- so no finite phase carries
18 * them, and the analysis has to run through transforms instead.
19 *
20 * THE METHOD, which is He's, theorem for theorem. FCFS makes the actual waiting
21 * time of a customer the WORKLOAD it finds on arrival, so everything follows
22 * from the joint transform of workload and arrival phase,
23 * f(s)_j = E[exp(-s V) 1{phase = j}], which by He's Theorem 4.1 (eq. 4.6)
24 * satisfies
25 *
26 * f(s) [ s I + D0 + sum_k Dk gk(s) ] = s v0, (*)
27 *
28 * with v0 the idle-phase vector, his y0. v0 needs NO root search: the matrix U
29 * solving U = D0 + sum_k Dk Fk(U), Fk(U) = int exp(U t) dFk(t), is his
30 * eq. (4.4), the generator of the underlying Markov process obtained by
31 * EXCISING the busy periods, and eq. (4.5) with Theorem 4.2 give y0 Q = 0 and
32 * y0 e = 1 - rho, i.e. v0 = (1 - rho) pi_U. The same vector is what the
33 * analyticity of (*) forces, since for every left eigenpair (w, u) of U one has
34 * w [D0 + sum_k Dk gk(-u) - u I] = 0, so the roots of the bracket in the closed
35 * right half plane are exactly s = -u over the spectrum of U; the two agree to
36 * 2.5e-13, and the stationary route is taken because it needs no complex
37 * eigenvector.
38 *
39 * The per-type actual waiting time is the workload seen by a type-k arrival,
40 * biased by that type's own arrival block, his Theorem 5.1 eq. (5.1) summed
41 * over the post-arrival phase:
42 *
43 * E[exp(-s Wk)] = f(s) Dk e / lambda_k.
44 *
45 * SCOPE. He allows an arrival to be a BATCH carrying a sequence of types, and
46 * his Theorem 5.3 then multiplies the transform by prod_{i<n} f*_{h_i}(s), the
47 * service of the customers ahead of the tagged one WITHIN its own batch. This
48 * header covers the single-customer-per-arrival case, his Special case 3.3,
49 * where that product is empty, which is exactly the MMAP convention LINE
50 * carries.
51 *
52 * MOMENTS WITHOUT INVERSION. Differentiating (*) at s = 0 gives
53 * sum_i C(j,i) f_i M_{j-i} = [j = 1] v0. M_0 = D is SINGULAR with right null
54 * vector e, so each order fixes f_j only up to a multiple of theta, and that
55 * multiple is what the NEXT order's solvability condition supplies. At j = 0
56 * the same condition reads theta M_1 e = v0 e, i.e. 1 - rho = 1 - rho, which is
57 * the identity that validates the whole setup.
58 *
59 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: the U iteration runs
60 * to a tolerance, and both the deterministic transform and the inversion need
61 * exp. The CDF is the Abate-Whitt Euler sum, which evaluates the transform OFF
62 * the real axis, so a small complex layer is carried here rather than in the
63 * distribution interface.
64 *
65 * Reference:
66 * Qi-Ming He, "The versatility of MMAP[K] and the MMAP[K]/G[K]/1 queue",
67 * Queueing Systems 38(4):397-418, 2001.
68 */
69
70#include <cmath>
71#include <complex>
72#include <limits>
73#include <cstddef>
74#include <vector>
75
79#include "line/num/number.h"
80#include "line/util/error.h"
81#include "line/util/expm.h"
82#include "line/util/linalg.h"
83#include "line/util/lu.h"
84#include "line/util/matrix.h"
85
86namespace line {
87namespace qsys {
88
89/** Return value of qsys_mmapgk1, mirroring the MATLAB struct. */
90template <class T>
92 std::vector<T> lambdas; ///< per-type arrival rates
93 T arrivalRate; ///< sum of the per-type rates
94 T utilization; ///< rho = sum_k lambda_k E[S_k]
95 std::vector<T> idleVector; ///< v0, summing to 1 - rho
96 std::vector<std::vector<T> > waitMoments; ///< [type][order], E[Wk^j]
97 std::vector<T> meanWaitingTime; ///< per-type E[Wq]
98 std::vector<T> meanSojournTime; ///< per-type E[Wq] + E[S]
99 T meanQueueLength; ///< E[N] by Little over all types
100 std::vector<std::vector<T> > waitCDF; ///< [type][point], P(Wk <= t)
101 std::vector<T> waitPoints; ///< the requested points
102};
103
104namespace mmapgk1detail {
105
106/** Binomial coefficient; declared ahead of the complex block that uses it. */
107inline double binom(std::size_t n, std::size_t k);
108
109/** Left null vector of G normalized to sum one. */
110template <class T>
111std::vector<T> stat_left_null(const Matrix<T>& G) {
112 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
113 const std::size_t n = G.rows();
114 Matrix<T> A(n, n, zero);
115 for (std::size_t i = 0; i < n; ++i)
116 for (std::size_t j = 0; j < n; ++j) A(j, i) = G(i, j);
117 for (std::size_t j = 0; j < n; ++j) A(n - 1, j) = one;
118 std::vector<T> b(n, zero);
119 b[n - 1] = one;
120 return line::solve(A, b);
121}
122
123/** Midpoint nodes and true CDF increments over the support of the law. */
124template <class T>
125void stieltjes_nodes(const lang::Distrib<T>& law, std::vector<T>& x, std::vector<T>& w) {
126 const std::size_t n_grid = 2400;
127 const T zero = num_traits<T>::from_int(0);
128 const double mean = num_traits<T>::to_double(lang::dist_moment(law, 1u));
129 double hi = mean * 60.0;
130 const double var = num_traits<T>::to_double((lang::dist_moment(law, 2u) - lang::dist_moment(law, 1u) * lang::dist_moment(law, 1u)));
131 if (std::isfinite(var) && var > 0.0) hi = std::max(hi, mean + 12.0 * std::sqrt(var));
132 const double step = hi / static_cast<double>(n_grid);
133 x.assign(n_grid, zero);
134 w.assign(n_grid, zero);
135 T mass = zero;
136 T prev = lang::dist_cdf(law, num_traits<T>::from_double(0.0));
137 for (std::size_t i = 0; i < n_grid; ++i) {
138 const T right = num_traits<T>::from_double((i + 1) * step);
139 const T cur = lang::dist_cdf(law, right);
140 x[i] = num_traits<T>::from_double((i + 0.5) * step);
141 w[i] = cur - prev;
142 mass += w[i];
143 prev = cur;
144 }
145 if (mass > zero)
146 for (std::size_t i = 0; i < n_grid; ++i) w[i] = w[i] / mass;
147}
148
149/**
150 * Raw moment E[S^j]; dist_moment already evaluates every family exactly.
151 *
152 * A HEAVY TAIL HAS NO MOMENT of high enough order, and dist_moment says so by
153 * THROWING (a Pareto of shape <= j). Here that is not an error but an answer:
154 * the moment recursion consumes M_j only from order j onwards, so an infinite
155 * M_3 leaves E[Wq] finite and makes E[Wq^2] infinite, which is the truth about
156 * such a queue. Letting the throw escape would lose the finite moments too.
157 */
158template <class T>
159T raw_moment(const lang::Distrib<T>& law, std::size_t j) {
160 try {
161 return lang::dist_moment(law, static_cast<unsigned>(j));
162 } catch (const NumericError&) {
163 return num_traits<T>::from_double(std::numeric_limits<double>::infinity());
164 }
165}
166
167/** Kronecker product; C++ has no shared templated kron. */
168template <class T>
169Matrix<T> gk_kron(const Matrix<T>& A, const Matrix<T>& B) {
170 Matrix<T> C(A.rows() * B.rows(), A.cols() * B.cols(), num_traits<T>::from_int(0));
171 for (std::size_t i = 0; i < A.rows(); ++i)
172 for (std::size_t j = 0; j < A.cols(); ++j)
173 for (std::size_t p = 0; p < B.rows(); ++p)
174 for (std::size_t q = 0; q < B.cols(); ++q)
175 C(i * B.rows() + p, j * B.cols() + q) = A(i, j) * B(p, q);
176 return C;
177}
178
179/** The matrix transform int_0^inf exp(U t) dF(t). */
180template <class T>
181Matrix<T> matrix_lst(const lang::Distrib<T>& law, const Matrix<T>& U) {
182 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
183 const std::size_t n = U.rows();
184 if (lang::process_is_markovian(law.type)) {
185 // The density is the SCALAR beta exp(St) s0, so the integral is exact on
186 // the Kronecker sum: int exp(Ut) x exp(St) dt = -(U (+) S)^-1.
187 const mam::Map<T> rep = lang::dist_to_map(law);
188 const Matrix<T>& S = rep.D0;
189 const std::size_t ms = S.rows();
190 const std::vector<T> beta = mam::map_pie(rep);
191 std::vector<T> s0(ms, zero);
192 for (std::size_t i = 0; i < ms; ++i) {
193 T r = zero;
194 for (std::size_t j = 0; j < ms; ++j) r += S(i, j);
195 s0[i] = -r;
196 }
197 Matrix<T> Ims = eye<T>(ms);
198 Matrix<T> KS = gk_kron(U, Ims);
199 const Matrix<T> In = eye<T>(n);
200 const Matrix<T> IkS = gk_kron(In, S);
201 for (std::size_t i = 0; i < KS.rows(); ++i)
202 for (std::size_t j = 0; j < KS.cols(); ++j) KS(i, j) += IkS(i, j);
203 Matrix<T> betaM(1, ms, zero);
204 for (std::size_t i = 0; i < ms; ++i) betaM(0, i) = beta[i];
205 Matrix<T> s0M(ms, 1, zero);
206 for (std::size_t i = 0; i < ms; ++i) s0M(i, 0) = s0[i];
207 const Matrix<T> L = gk_kron(In, betaM);
208 Matrix<T> R = gk_kron(In, s0M);
209 Matrix<T> X = matmul(inverse(KS), R);
210 for (std::size_t i = 0; i < X.rows(); ++i)
211 for (std::size_t j = 0; j < X.cols(); ++j) X(i, j) = -X(i, j);
212 return matmul(L, X);
213 }
214 if (law.type == lang::ProcessType::DET) {
215 Matrix<T> Ud = U;
216 const T d = lang::dist_moment(law, 1u);
217 for (std::size_t i = 0; i < n; ++i)
218 for (std::size_t j = 0; j < n; ++j) Ud(i, j) = Ud(i, j) * d;
219 return line::expm(Ud);
220 }
221 std::vector<T> x, w;
222 stieltjes_nodes(law, x, w);
223 Matrix<T> F(n, n, zero);
224 for (std::size_t i = 0; i < x.size(); ++i) {
225 Matrix<T> Ut = U;
226 for (std::size_t p = 0; p < n; ++p)
227 for (std::size_t q = 0; q < n; ++q) Ut(p, q) = Ut(p, q) * x[i];
228 const Matrix<T> E = line::expm(Ut);
229 for (std::size_t p = 0; p < n; ++p)
230 for (std::size_t q = 0; q < n; ++q) F(p, q) += w[i] * E(p, q);
231 }
232 (void)one;
233 return F;
234}
235
236typedef std::complex<double> cdbl;
237
238/** Gaussian elimination with partial pivoting over the complex field. */
239inline std::vector<cdbl> complex_solve(std::vector<std::vector<cdbl> > A, std::vector<cdbl> b) {
240 const std::size_t n = b.size();
241 for (std::size_t col = 0; col < n; ++col) {
242 std::size_t piv = col;
243 double best = std::abs(A[col][col]);
244 for (std::size_t r = col + 1; r < n; ++r) {
245 if (std::abs(A[r][col]) > best) { best = std::abs(A[r][col]); piv = r; }
246 }
247 if (piv != col) { std::swap(A[piv], A[col]); std::swap(b[piv], b[col]); }
248 for (std::size_t r = col + 1; r < n; ++r) {
249 const cdbl f = A[r][col] / A[col][col];
250 for (std::size_t c = col; c < n; ++c) A[r][c] -= f * A[col][c];
251 b[r] -= f * b[col];
252 }
253 }
254 std::vector<cdbl> x(n);
255 for (std::size_t row = n; row-- > 0;) {
256 cdbl s = b[row];
257 for (std::size_t c = row + 1; c < n; ++c) s -= A[row][c] * x[c];
258 x[row] = s / A[row][row];
259 }
260 return x;
261}
262
263/**
264 * Scalar Laplace-Stieltjes transform of a service law at a complex argument.
265 *
266 * One line, because the LANGUAGE LAYER owns this: dist_lst carries a complex
267 * overload beside the real one, with the closed forms, the phase-type solve and
268 * the CDF-increment fallback already tiered. A second implementation here would
269 * be a second thing to keep true.
270 */
271template <class T>
272cdbl scalar_lst(const lang::Distrib<T>& law, cdbl s) {
273 return lang::dist_lst(law, s);
274}
275
276
277/** E[exp(-s Wk)] at a complex argument. */
278template <class T>
279cdbl wait_lst(const Matrix<T>& D0, const std::vector<Matrix<T> >& Dk,
280 const std::vector<lang::Distrib<T> >& svc, const std::vector<T>& v0,
281 const std::vector<T>& lambdas, std::size_t k, cdbl s) {
282 const std::size_t ma = D0.rows();
283 std::vector<std::vector<cdbl> > M(ma, std::vector<cdbl>(ma, cdbl(0.0, 0.0)));
284 for (std::size_t i = 0; i < ma; ++i)
285 for (std::size_t j = 0; j < ma; ++j) M[i][j] = cdbl(num_traits<T>::to_double(D0(i, j)), 0.0);
286 for (std::size_t i = 0; i < ma; ++i) M[i][i] += s;
287 for (std::size_t q = 0; q < Dk.size(); ++q) {
288 const cdbl g = scalar_lst(svc[q], s);
289 for (std::size_t i = 0; i < ma; ++i)
290 for (std::size_t j = 0; j < ma; ++j)
291 M[i][j] += num_traits<T>::to_double(Dk[q](i, j)) * g;
292 }
293 // solve f M = s v0, i.e. M' f' = (s v0)'
294 std::vector<std::vector<cdbl> > A(ma, std::vector<cdbl>(ma, cdbl(0.0, 0.0)));
295 std::vector<cdbl> b(ma, cdbl(0.0, 0.0));
296 for (std::size_t i = 0; i < ma; ++i) {
297 for (std::size_t j = 0; j < ma; ++j) A[i][j] = M[j][i];
298 b[i] = s * num_traits<T>::to_double(v0[i]);
299 }
300 const std::vector<cdbl> f = complex_solve(A, b);
301 cdbl out(0.0, 0.0);
302 for (std::size_t i = 0; i < ma; ++i) {
303 double rowsum = 0.0;
304 for (std::size_t j = 0; j < ma; ++j) rowsum += num_traits<T>::to_double(Dk[k](i, j));
305 out += f[i] * rowsum;
306 }
307 return out / num_traits<T>::to_double(lambdas[k]);
308}
309
310/** Abate-Whitt Euler inversion of the type-k waiting time CDF. */
311template <class T>
312double euler_invert(const Matrix<T>& D0, const std::vector<Matrix<T> >& Dk,
313 const std::vector<lang::Distrib<T> >& svc, const std::vector<T>& v0,
314 const std::vector<T>& lambdas, std::size_t k, double t) {
315 if (t <= 0.0) return wait_lst(D0, Dk, svc, v0, lambdas, k, cdbl(1e12, 0.0)).real();
316 const double A = 18.4;
317 const std::size_t nE = 15, mE = 11;
318 const double u = std::exp(A / 2) / t;
319 const double x = A / (2 * t);
320 std::vector<double> terms(nE + mE + 1, 0.0);
321 terms[0] = wait_lst(D0, Dk, svc, v0, lambdas, k, cdbl(x, 0.0)).real() / x / 2.0;
322 for (std::size_t j = 1; j <= nE + mE; ++j) {
323 const cdbl s(x, M_PI * static_cast<double>(j) / t);
324 terms[j] = ((j % 2 == 0) ? 1.0 : -1.0) * (wait_lst(D0, Dk, svc, v0, lambdas, k, s) / s).real();
325 }
326 std::vector<double> partial(terms.size(), 0.0);
327 double run = 0.0;
328 for (std::size_t j = 0; j < terms.size(); ++j) { run += terms[j]; partial[j] = run; }
329 double F = 0.0;
330 for (std::size_t j = 0; j <= mE; ++j) F += binom(mE, j) / std::pow(2.0, static_cast<double>(mE)) * partial[nE + j];
331 F = u * F;
332 return std::min(std::max(F, 0.0), 1.0);
333}
334
335
336inline double binom(std::size_t n, std::size_t k) {
337 double r = 1.0;
338 for (std::size_t i = 1; i <= k; ++i) r = r * static_cast<double>(n - k + i) / static_cast<double>(i);
339 return std::floor(r + 0.5);
340}
341
342} // namespace mmapgk1detail
343
344/**
345 * MMAP[K]/G[K]/1 FCFS, per type.
346 *
347 * @param MMAP LINE convention {D0, D1, D^(1), ..., D^(K)}, D1 = sum_k D^(k)
348 * @param svc K service laws, one per marked type; families may differ
349 * @param w_points times at which to evaluate the per-type waiting time CDF
350 * @param num_w_moms how many per-type waiting time moments to return
351 * @param tol fixed point tolerance on U
352 * @param iter_max fixed point iteration cap
353 */
354template <class T>
355MmapGk1Result<T> qsys_mmapgk1(const std::vector<Matrix<T> >& MMAP,
356 const std::vector<lang::Distrib<T> >& svc,
357 const std::vector<T>& w_points, std::size_t num_w_moms,
358 double tol, std::size_t iter_max) {
360 "qsys_mmapgk1 requires transcendental arithmetic");
361 using namespace mmapgk1detail;
362 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
363 if (MMAP.size() < 3) throw InputError("qsys_mmapgk1: the MMAP must carry a marked block");
364 const std::size_t K = MMAP.size() - 2;
365 if (svc.size() != K) throw InputError("qsys_mmapgk1: one service law per marked type");
366 const Matrix<T>& D0 = MMAP[0];
367 const std::size_t ma = D0.rows();
368 std::vector<Matrix<T> > Dk;
369 Matrix<T> Dsum = D0;
370 for (std::size_t k = 0; k < K; ++k) {
371 Dk.push_back(MMAP[k + 2]);
372 for (std::size_t i = 0; i < ma; ++i)
373 for (std::size_t j = 0; j < ma; ++j) Dsum(i, j) += Dk[k](i, j);
374 }
375
376 const std::vector<T> theta = stat_left_null(Dsum);
377 std::vector<T> lambdas(K, zero), mean_s(K, zero);
378 T rho = zero;
379 for (std::size_t k = 0; k < K; ++k) {
380 T lam = zero;
381 for (std::size_t i = 0; i < ma; ++i)
382 for (std::size_t j = 0; j < ma; ++j) lam += theta[i] * Dk[k](i, j);
383 lambdas[k] = lam;
384 mean_s[k] = lang::dist_moment(svc[k], 1u);
385 rho += lam * mean_s[k];
386 }
387 if (rho >= one) throw InputError("qsys_mmapgk1: load rho must be strictly less than 1");
388
389 Matrix<T> U = D0;
390 for (std::size_t it = 0; it < iter_max; ++it) {
391 Matrix<T> Unew = D0;
392 for (std::size_t k = 0; k < K; ++k) {
393 const Matrix<T> Fk = matrix_lst(svc[k], U);
394 const Matrix<T> P = matmul(Dk[k], Fk);
395 for (std::size_t i = 0; i < ma; ++i)
396 for (std::size_t j = 0; j < ma; ++j) Unew(i, j) += P(i, j);
397 }
398 double diff = 0.0;
399 for (std::size_t i = 0; i < ma; ++i)
400 for (std::size_t j = 0; j < ma; ++j)
401 diff = std::max(diff, std::fabs(num_traits<T>::to_double(Unew(i, j) - U(i, j))));
402 U = Unew;
403 if (diff <= tol) break;
404 }
405 // U e = 0 EXACTLY: a property of the fixed point, not of the iterate, and
406 // the residue at the tolerance above would move the stationary solve.
407 for (std::size_t i = 0; i < ma; ++i) {
408 T rowsum = zero;
409 for (std::size_t j = 0; j < ma; ++j) rowsum += U(i, j);
410 U(i, i) -= rowsum;
411 }
412 std::vector<T> v0 = stat_left_null(U);
413 for (std::size_t i = 0; i < ma; ++i) v0[i] = v0[i] * (one - rho);
414
415 // Moment recursion, see the header comment
416 std::vector<Matrix<T> > Mder;
417 for (std::size_t j = 0; j <= num_w_moms + 1; ++j) {
418 Matrix<T> Mj(ma, ma, zero);
419 if (j == 0) {
420 Mj = D0;
421 for (std::size_t k = 0; k < K; ++k)
422 for (std::size_t i = 0; i < ma; ++i)
423 for (std::size_t q = 0; q < ma; ++q) Mj(i, q) += Dk[k](i, q);
424 } else {
425 for (std::size_t k = 0; k < K; ++k) {
426 const T mom = raw_moment(svc[k], j);
427 const double sign = (j % 2 == 0) ? 1.0 : -1.0;
428 for (std::size_t i = 0; i < ma; ++i)
429 for (std::size_t q = 0; q < ma; ++q)
430 Mj(i, q) += num_traits<T>::from_double(sign) * Dk[k](i, q) * mom;
431 }
432 if (j == 1)
433 for (std::size_t i = 0; i < ma; ++i) Mj(i, i) += one;
434 }
435 Mder.push_back(Mj);
436 }
437 const std::vector<T> e(ma, one);
438 T denom = zero;
439 {
440 const std::vector<T> v = mulvec(Mder[1], e);
441 for (std::size_t i = 0; i < ma; ++i) denom += theta[i] * v[i];
442 }
443 std::vector<std::vector<T> > fder;
444 fder.push_back(theta);
445 // [M_0, e] with f_j^p e = 0 pins the particular solution; solved in the
446 // least squares sense, since the system is one equation over-determined.
447 Matrix<T> Abase(ma + 1, ma, zero);
448 for (std::size_t i = 0; i < ma; ++i)
449 for (std::size_t j = 0; j < ma; ++j) Abase(j, i) = Mder[0](i, j);
450 for (std::size_t i = 0; i < ma; ++i) Abase(ma, i) = one;
451 Matrix<T> AtA(ma, ma, zero);
452 for (std::size_t i = 0; i < ma; ++i)
453 for (std::size_t j = 0; j < ma; ++j) {
454 T s = zero;
455 for (std::size_t q = 0; q < ma + 1; ++q) s += Abase(q, i) * Abase(q, j);
456 AtA(i, j) = s;
457 }
458 const Matrix<T> AtAinv = inverse(AtA);
459 for (std::size_t j = 1; j <= num_w_moms; ++j) {
460 std::vector<T> rhs(ma, zero);
461 if (j == 1) for (std::size_t i = 0; i < ma; ++i) rhs[i] = v0[i];
462 for (std::size_t i = 0; i < j; ++i) {
463 const double c = binom(j, i);
464 for (std::size_t q = 0; q < ma; ++q) {
465 T acc = zero;
466 for (std::size_t p = 0; p < ma; ++p) acc += fder[i][p] * Mder[j - i](p, q);
467 rhs[q] -= num_traits<T>::from_double(c) * acc;
468 }
469 }
470 std::vector<T> rhsAug(ma + 1, zero);
471 for (std::size_t q = 0; q < ma; ++q) rhsAug[q] = rhs[q];
472 std::vector<T> Atb(ma, zero);
473 for (std::size_t i = 0; i < ma; ++i) {
474 T s = zero;
475 for (std::size_t q = 0; q < ma + 1; ++q) s += Abase(q, i) * rhsAug[q];
476 Atb[i] = s;
477 }
478 const std::vector<T> fp = mulvec(AtAinv, Atb);
479 T acc2 = zero;
480 for (std::size_t i = 0; i < j; ++i) {
481 const std::vector<T> v = mulvec(Mder[j + 1 - i], e);
482 T inner = zero;
483 for (std::size_t p = 0; p < ma; ++p) inner += fder[i][p] * v[p];
484 acc2 += num_traits<T>::from_double(binom(j + 1, i)) * inner;
485 }
486 T fpM1e = zero;
487 {
488 const std::vector<T> v = mulvec(Mder[1], e);
489 for (std::size_t p = 0; p < ma; ++p) fpM1e += fp[p] * v[p];
490 }
491 const T cfree = (-acc2 / num_traits<T>::from_int(static_cast<long>(j + 1)) - fpM1e) / denom;
492 std::vector<T> fj(ma, zero);
493 for (std::size_t p = 0; p < ma; ++p) fj[p] = fp[p] + cfree * theta[p];
494 fder.push_back(fj);
495 }
496
498 r.lambdas = lambdas;
499 r.arrivalRate = zero;
500 for (std::size_t k = 0; k < K; ++k) r.arrivalRate += lambdas[k];
501 r.utilization = rho;
502 r.idleVector = v0;
503 r.waitMoments.assign(K, std::vector<T>(num_w_moms, zero));
504 r.meanWaitingTime.assign(K, zero);
505 r.meanSojournTime.assign(K, zero);
506 r.meanQueueLength = zero;
507 for (std::size_t k = 0; k < K; ++k) {
508 for (std::size_t j = 1; j <= num_w_moms; ++j) {
509 const std::vector<T> v = mulvec(Dk[k], e);
510 T inner = zero;
511 for (std::size_t p = 0; p < ma; ++p) inner += fder[j][p] * v[p];
512 const double sign = (j % 2 == 0) ? 1.0 : -1.0;
513 r.waitMoments[k][j - 1] = num_traits<T>::from_double(sign) * inner / lambdas[k];
514 }
515 r.meanWaitingTime[k] = num_w_moms >= 1 ? r.waitMoments[k][0] : zero;
516 r.meanSojournTime[k] = r.meanWaitingTime[k] + mean_s[k];
517 r.meanQueueLength += lambdas[k] * r.meanSojournTime[k];
518 }
519
520 r.waitPoints = w_points;
521 if (!w_points.empty()) {
522 r.waitCDF.assign(K, std::vector<T>(w_points.size(), zero));
523 for (std::size_t k = 0; k < K; ++k) {
524 for (std::size_t it = 0; it < w_points.size(); ++it) {
525 const double t = num_traits<T>::to_double(w_points[it]);
527 euler_invert(D0, Dk, svc, v0, lambdas, k, t));
528 }
529 }
530 }
531 return r;
532}
533
534
535template <class T>
536MmapGk1Result<T> qsys_mmapgk1(const std::vector<Matrix<T> >& MMAP,
537 const std::vector<lang::Distrib<T> >& svc) {
538 return qsys_mmapgk1(MMAP, svc, std::vector<T>(), static_cast<std::size_t>(3), 1e-12,
539 static_cast<std::size_t>(10000));
540}
541
542} // namespace qsys
543} // namespace line
544
545#endif // LINE_API_QSYS_QSYS_MMAPGK1_H
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
What refreshProcessRepresentations and refreshLST compute FROM a distribution: the (D0,...
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
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.
mam::Map< T > dist_to_map(const Distrib< T > &d)
T dist_lst(const Distrib< T > &d, const T &s)
sn.lst: the Laplace-Stieltjes transform E[exp(-sX)].
T dist_cdf(const Distrib< T > &d, const T &x)
F(x) = P{X <= x}, MATLAB's Distribution.evalCDF.
bool process_is_markovian(ProcessType p)
ProcessType.isMarkovian: true when sn.proc carries an exact matrix representation of the law,...
Definition lang_types.h:610
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
MmapGk1Result< T > qsys_mmapgk1(const std::vector< Matrix< T > > &MMAP, const std::vector< lang::Distrib< T > > &svc, const std::vector< T > &w_points, std::size_t num_w_moms, double tol, std::size_t iter_max)
MMAP[K]/G[K]/1 FCFS, per type.
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
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
Number-type abstraction for the templated API port.
Return value of qsys_mmapgk1, mirroring the MATLAB struct.
T utilization
rho = sum_k lambda_k E[S_k]
std::vector< T > lambdas
per-type arrival rates
std::vector< T > waitPoints
the requested points
std::vector< T > meanWaitingTime
per-type E[Wq]
std::vector< T > meanSojournTime
per-type E[Wq] + E[S]
std::vector< std::vector< T > > waitCDF
[type][point], P(Wk <= t)
T arrivalRate
sum of the per-type rates
std::vector< T > idleVector
v0, summing to 1 - rho
T meanQueueLength
E[N] by Little over all types.
std::vector< std::vector< T > > waitMoments
[type][order], E[Wk^j]