LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qbd_rap.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_QBD_RAP_H
6#define LINE_API_MAM_QBD_RAP_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Quasi-birth-death processes with rational arrival process components, and
12 * the RAP/RAP/1 queue built on top of them.
13 *
14 * Templated port of matlab/src/api/mam/qbd_rap.m (including its local
15 * qbd_rap_g) and matlab/src/api/mam/qbd_raprap1.m, following
16 * N. G. Bean and B. F. Nielsen, "Quasi-Birth-and-Death Processes with Rational
17 * Arrival Process Components", Stochastic Models 26(3), 2010, 309-334. The
18 * equilibrium construction is their Theorem 7 and the stability test their
19 * Corollary 8.
20 *
21 * The process is given by its repeating blocks (A0, A1, A2) -- A0 up, A1
22 * local, A2 down -- and its boundary blocks (B0, B1). Unlike a Markovian QBD
23 * the blocks need not be nonnegative; they are only required to be
24 * conservative, (A0 + A1 + A2) e = 0 and (B0 + B1) e = 0. The analysis rests
25 * on the prediction-process interpretation of a RAP, which is what lets a QBD
26 * argument survive the loss of nonnegativity.
27 *
28 * Theorem 7, step by step:
29 * 1. G solves A0 G^2 + A1 G + A2 = 0.
30 * 2. U = A1 + A0 G.
31 * 3. R = A0 (-U)^-1.
32 * 4. pihat0 (B1 + R A2) = 0 with pihat0 e = 1.
33 * 5. pi_0 = K pihat0 with K chosen so pi_0 (I - R)^-1 e = 1.
34 * 6. pi_n = pi_0 R^n.
35 * Positive recurrence holds iff Sp(R) < 1 and step 4 has a unique solution.
36 *
37 * COMPUTING G. The blocks are not nonnegative, so logarithmic and cyclic
38 * reduction carry no convergence guarantee, and the paper leaves the general
39 * case explicitly open (Section 6). Two paths, exactly as in the reference:
40 * - A2 of rank one, A2 = u v: then G = e v / (v e) solves the equation in
41 * closed form. Conservativity gives (A0 + A1) e = -A2 e = -u (v e), and G
42 * is idempotent, so A0 G^2 + A1 G = (A0 + A1) e v/(v e) = -u v = -A2.
43 * This is the case of the paper's own example.
44 * - otherwise, natural functional iteration G <- (-A1)^-1 (A2 + A0 G^2) as a
45 * warm start, then Newton on the Sylvester-form Jacobian
46 * (A0 G + A1) H + A0 H G = -(A0 G^2 + A1 G + A2), solved through its
47 * Kronecker expansion (I (x) (A0 G + A1) + G^T (x) A0) vec(H).
48 * An unconverged G is never returned: the residual and the constraint G e = e
49 * are both checked and a failure raises NumericError carrying both numbers.
50 *
51 * DIVERGENCES FROM THE REFERENCE, all in how a quantity is EXTRACTED rather
52 * than in what it is, and all tested:
53 * - the right factor v of a rank-one A2 is taken as the row of A2 with the
54 * largest infinity norm instead of the top right singular vector. G
55 * depends on v only through v/(v e), and every nonzero row of a rank-one
56 * matrix is a scalar multiple of v, so the two agree exactly; this keeps
57 * the step inside the templated arithmetic instead of routing it through
58 * a double-precision SVD. The rank test itself still uses the singular
59 * values (util/eig.h), matching the reference's sv(2) <= 1e-10 sv(1).
60 * - the boundary vector of step 4 is obtained from the linear system
61 * x V = 0, sum(x) = 1 (qbd_detail::statvec) rather than from the last
62 * right singular vector of V^T. The solution is unique up to scale
63 * precisely when the reference's own second-smallest-singular-value test
64 * passes, so the accept/reject decision is unchanged, but the vector is
65 * computed at the working precision instead of in double.
66 * - rcond(-U) is replaced by the EXACT reciprocal 1-norm condition number
67 * 1 / (||X||_1 ||X^-1||_1). MATLAB's rcond only estimates that quantity.
68 *
69 * ARITHMETIC. Gated on num_traits<T>::has_transcendental: the general G is a
70 * fixed-point iteration plus Newton driven to a tolerance, and Sp(R) is the
71 * modulus of an eigenvalue, which is algebraic and not rational. Sp(R) is
72 * computed by converting R to double and calling LAPACK (util/eig.h), so at
73 * Real<D> the STABILITY GATE is only double-accurate; every returned quantity
74 * is computed at the full working precision. The gate is a comparison against
75 * 1 - 1e-12 and any model that close to the null-recurrent boundary has an
76 * unbounded queue anyway, which is why the precision loss is confined there.
77 */
78
79#include <cmath>
80#include <cstddef>
81#include <string>
82#include <vector>
83
87#include "line/api/mam/qbd_r.h"
88#include "line/num/number.h"
89#include "line/util/eig.h"
90#include "line/util/error.h"
91#include "line/util/linalg.h"
92#include "line/util/matrix.h"
93
94namespace line {
95namespace mam {
96
97namespace rap_detail {
98
99/** Frobenius norm. */
100template <class T>
101T normfro(const Matrix<T>& A) {
102 T s = num_traits<T>::from_int(0);
103 for (std::size_t i = 0; i < A.rows(); ++i)
104 for (std::size_t j = 0; j < A.cols(); ++j) s += A(i, j) * A(i, j);
105 using std::sqrt;
106 return T(sqrt(s));
107}
108
109/** Largest absolute entry of a vector, MATLAB's norm(v, inf) for a vector. */
110template <class T>
111T vecnorminf(const std::vector<T>& v) {
112 T best = num_traits<T>::from_int(0);
113 for (const T& x : v) {
114 const T a = num_abs(T(x));
115 if (a > best) best = a;
116 }
117 return best;
118}
119
120/** Exact reciprocal 1-norm condition number, the quantity MATLAB's rcond estimates. */
121template <class T>
122T rcond1(const Matrix<T>& A) {
123 Matrix<T> Ainv;
124 try {
125 Ainv = inverse(A);
126 } catch (const NumericError&) {
127 return num_traits<T>::from_int(0);
128 }
129 const T na = qbd_detail::norm1(A);
130 const T ni = qbd_detail::norm1(Ainv);
131 const T p = na * ni;
132 if (p == num_traits<T>::from_int(0)) return num_traits<T>::from_int(0);
133 return T(num_traits<T>::from_int(1) / p);
134}
135
136/** Machine epsilon of the working type, the reference's `eps`. */
137template <class T>
138T working_eps() {
139 return num_traits<T>::from_double(2.220446049250313e-16);
140}
141
142/** Solves A0 G^2 + A1 G + A2 = 0 for G (the local qbd_rap_g of qbd_rap.m). */
143template <class T>
144Matrix<T> qbd_rap_g(const Matrix<T>& A0, const Matrix<T>& A1, const Matrix<T>& A2,
145 const T& blockScale) {
146 using namespace qbd_detail;
147 const std::size_t m = A1.rows();
148 const std::vector<T> e = ones<T>(m);
149 const T resTol = num_traits<T>::from_double(1e-10) * blockScale;
150 const T zero = num_traits<T>::from_int(0);
151
152 // Rank-one A2 admits the closed form G = e v / (v e).
153 Matrix<double> A2d(m, m);
154 for (std::size_t i = 0; i < m; ++i)
155 for (std::size_t j = 0; j < m; ++j) A2d(i, j) = num_traits<T>::to_double(A2(i, j));
156 const std::vector<double> sv = svd_values(A2d);
157 if (sv.size() > 1 && sv[0] > 0.0 && sv[1] <= 1e-10 * sv[0]) {
158 // Any nonzero row of a rank-one matrix spans its row space.
159 std::size_t best = 0;
160 T bestn = zero;
161 for (std::size_t i = 0; i < m; ++i) {
162 T s = zero;
163 for (std::size_t j = 0; j < m; ++j) s += num_abs(T(A2(i, j)));
164 if (s > bestn) {
165 bestn = s;
166 best = i;
167 }
168 }
169 std::vector<T> v(m);
170 for (std::size_t j = 0; j < m; ++j) v[j] = A2(best, j);
171 T ve = zero;
172 for (const T& x : v) ve += x;
173 if (num_abs(T(ve)) < num_traits<T>::from_double(1e-12) * vecnorminf(v))
174 throw NumericError(
175 "qbd_rap: A2 has rank one but its right factor v satisfies v*e = 0, so the "
176 "closed form G = e*v/(v*e) is undefined");
177 Matrix<T> G(m, m);
178 for (std::size_t i = 0; i < m; ++i)
179 for (std::size_t j = 0; j < m; ++j) G(i, j) = v[j] / ve;
180 const T res = normfro(madd(madd(matmul(A0, matmul(G, G)), matmul(A1, G)), A2));
181 if (res > resTol)
182 throw NumericError(
183 "qbd_rap: the rank-one closed form for G leaves a residual "
184 "||A0*G^2 + A1*G + A2||_F = " +
185 std::to_string(num_traits<T>::to_double(res)) +
186 ", above the roundoff level " +
187 std::to_string(num_traits<T>::to_double(resTol)));
188 return G;
189 }
190
191 const Matrix<T> negA1 = mscale(A1, T(num_traits<T>::from_int(-1)));
192 if (rcond1(negA1) < working_eps<T>())
193 throw NumericError(
194 "qbd_rap: the local block A1 is singular, the iteration for G cannot be started");
195 const Matrix<T> negA1inv = inverse(negA1);
196
197 Matrix<T> G(m, m, zero);
198 for (unsigned it = 0; it < 200; ++it) {
199 const Matrix<T> Gnew = matmul(negA1inv, madd(A2, matmul(A0, matmul(G, G))));
200 const T nG = normfro(G);
201 const T scale = nG > num_traits<T>::from_int(1) ? nG : T(num_traits<T>::from_int(1));
202 const bool done = normfro(msub(Gnew, G)) <= num_traits<T>::from_double(1e-14) * scale;
203 G = Gnew;
204 if (done) break;
205 }
206
207 // Newton on F(G) = A0 G^2 + A1 G + A2, through the Kronecker expansion of
208 // the Sylvester operator. vec is COLUMN-major, matching MATLAB's res(:).
209 for (unsigned it = 0; it < 100; ++it) {
210 const Matrix<T> res = madd(madd(matmul(A0, matmul(G, G)), matmul(A1, G)), A2);
211 if (normfro(res) <= resTol) break;
212 const Matrix<T> M = madd(matmul(A0, G), A1);
213 Matrix<T> J(m * m, m * m, zero);
214 for (std::size_t i = 0; i < m; ++i)
215 for (std::size_t j = 0; j < m; ++j)
216 for (std::size_t pp = 0; pp < m; ++pp)
217 for (std::size_t q = 0; q < m; ++q) {
218 T val = zero;
219 if (j == q) val += M(i, pp);
220 val += G(q, j) * A0(i, pp);
221 J(i + j * m, pp + q * m) = val;
222 }
223 if (rcond1(J) < working_eps<T>()) break;
224 std::vector<T> rhs(m * m);
225 for (std::size_t i = 0; i < m; ++i)
226 for (std::size_t j = 0; j < m; ++j) rhs[i + j * m] = -res(i, j);
227 const std::vector<T> y = solve(J, rhs);
228 for (std::size_t i = 0; i < m; ++i)
229 for (std::size_t j = 0; j < m; ++j) G(i, j) += y[i + j * m];
230 }
231
232 const T res = normfro(madd(madd(matmul(A0, matmul(G, G)), matmul(A1, G)), A2));
233 const std::vector<T> Ge = mulvec(G, e);
234 T ge = zero;
235 for (std::size_t i = 0; i < m; ++i) {
236 const T a = num_abs(T(Ge[i] - e[i]));
237 if (a > ge) ge = a;
238 }
239 if (!(res <= resTol) || ge > num_traits<T>::from_double(1e-8))
240 throw NumericError(
241 "qbd_rap: could not compute the matrix G for this QBD with RAP components: residual "
242 "||A0*G^2 + A1*G + A2||_F = " +
243 std::to_string(num_traits<T>::to_double(res)) + " against a tolerance of " +
244 std::to_string(num_traits<T>::to_double(resTol)) + ", and ||G*e-e||_inf = " +
245 std::to_string(num_traits<T>::to_double(ge)) +
246 ". The blocks are not nonnegative, so neither the functional iteration nor Newton's "
247 "method is guaranteed to converge; the justification of algorithms for G in this "
248 "setting is an open problem in Section 6 of Bean and Nielsen (2010). Supply a model "
249 "with a rank-one A2, for which G is available in closed form.");
250 return G;
251}
252
253} // namespace rap_detail
254
255/** Everything qbd_rap returns. */
256template <class T>
258 std::vector<T> levelProb; ///< marginal level probabilities, levels 0..numLevels
259 T QN; ///< exact mean queue length, pi0 R (I-R)^-2 e
261 double spr; ///< Sp(R), from a double eigensolve (see the header note)
262 Matrix<T> pqueue; ///< (numLevels+1) x m, row n holding pi_n
263 std::vector<T> pi0;
264};
265
266/**
267 * Equilibrium analysis of a QBD with RAP components (qbd_rap.m).
268 *
269 * @param A0,A1,A2 repeating blocks, up / local / down
270 * @param B0,B1 boundary up and local blocks at level 0
271 * @param numLevels highest level reported
272 */
273template <class T>
274QbdRapResult<T> qbd_rap(const Matrix<T>& A0, const Matrix<T>& A1, const Matrix<T>& A2,
275 const Matrix<T>& B0, const Matrix<T>& B1, std::size_t numLevels) {
276 static_assert(num_traits<T>::has_transcendental, "qbd_rap requires transcendental arithmetic");
277 using namespace qbd_detail;
278 using namespace rap_detail;
279
280 const std::size_t m = A1.rows();
281 if (A1.cols() != m || A0.rows() != m || A0.cols() != m || A2.rows() != m || A2.cols() != m ||
282 B0.rows() != m || B0.cols() != m || B1.rows() != m || B1.cols() != m)
283 throw InputError("qbd_rap: all QBD blocks must be square and of the same order");
284 if (m == 0) throw InputError("qbd_rap: empty blocks");
285
286 const std::vector<T> e = ones<T>(m);
287 const Matrix<T> I = eye<T>(m);
288 T blockScale = num_traits<T>::from_int(1);
289 {
290 const T c[3] = {normfro(A0), normfro(A1), normfro(A2)};
291 for (int k = 0; k < 3; ++k)
292 if (c[k] > blockScale) blockScale = c[k];
293 }
294 const T conservTol = num_traits<T>::from_double(1e-8) * blockScale;
295
296 const std::vector<T> ce = mulvec(madd(madd(A0, A1), A2), e);
297 if (vecnorminf(ce) > conservTol)
298 throw InputError(
299 "qbd_rap: the repeating blocks are not conservative, ||(A0+A1+A2)*e||_inf = " +
300 std::to_string(num_traits<T>::to_double(vecnorminf(ce))) +
301 ". A QBD with RAP components requires (A0+A1+A2)*e = 0.");
302 const std::vector<T> be = mulvec(madd(B0, B1), e);
303 if (vecnorminf(be) > conservTol)
304 throw InputError(
305 "qbd_rap: the boundary blocks are not conservative, ||(B0+B1)*e||_inf = " +
306 std::to_string(num_traits<T>::to_double(vecnorminf(be))) +
307 ". A QBD with RAP components requires (B0+B1)*e = 0 at level 0.");
308
309 QbdRapResult<T> out;
310 out.G = qbd_rap_g(A0, A1, A2, blockScale);
311 out.U = madd(A1, matmul(A0, out.G));
312 const Matrix<T> negU = mscale(out.U, T(num_traits<T>::from_int(-1)));
313 if (rcond1(negU) < working_eps<T>())
314 throw NumericError(
315 "qbd_rap: the matrix U = A1 + A0*G is singular, R = A0*inv(-U) does not exist");
316 out.R = matmul(A0, inverse(negU));
317
318 // Corollary 8(i). See the header note on the precision of this gate.
319 Matrix<double> Rd(m, m);
320 for (std::size_t i = 0; i < m; ++i)
321 for (std::size_t j = 0; j < m; ++j) Rd(i, j) = num_traits<T>::to_double(out.R(i, j));
322 out.spr = spectral_radius(Rd);
323 if (out.spr >= 1.0 - 1e-12)
324 throw NumericError("qbd_rap: the process is not positive recurrent, Sp(R) = " +
325 std::to_string(out.spr) +
326 " >= 1 (Corollary 8 of Bean and Nielsen, 2010)");
327
328 // Step 4, with the reference's singular-value diagnostics on V.
329 const Matrix<T> V = madd(B1, matmul(out.R, A2));
330 Matrix<double> Vd(m, m);
331 for (std::size_t i = 0; i < m; ++i)
332 for (std::size_t j = 0; j < m; ++j) Vd(i, j) = num_traits<T>::to_double(V(i, j));
333 const std::vector<double> sv = svd_values(Vd);
334 const double nullTol = 1e-8 * (sv[0] > 1.0 ? sv[0] : 1.0);
335 if (sv[m - 1] > nullTol)
336 throw NumericError(
337 "qbd_rap: the boundary equation x*(B1 + R*A2) = 0 has no nontrivial solution "
338 "(smallest singular value " +
339 std::to_string(sv[m - 1]) + " against tolerance " + std::to_string(nullTol) +
340 "), so the process is not positive recurrent (Corollary 8(ii))");
341 if (m > 1 && sv[m - 2] <= nullTol)
342 throw NumericError(
343 "qbd_rap: the boundary equation x*(B1 + R*A2) = 0 has a solution space of dimension "
344 "greater than one, the equilibrium vector is not unique");
345 const std::vector<T> pihat0 = statvec(V);
346
347 // Step 5.
348 const std::vector<T> ImRinv_e = mulvec(inverse(msub(I, out.R)), e);
349 T denom = num_traits<T>::from_int(0);
350 for (std::size_t i = 0; i < m; ++i) denom += pihat0[i] * ImRinv_e[i];
351 if (denom == num_traits<T>::from_int(0))
352 throw NumericError("qbd_rap: the level-0 vector cannot be normalized");
353 out.pi0.resize(m);
354 for (std::size_t i = 0; i < m; ++i) out.pi0[i] = pihat0[i] / denom;
355
356 // Consistency of the supplied boundary up-block.
357 const std::vector<T> pi0R = vecmul(out.pi0, out.R);
358 const std::vector<T> pi0R2 = vecmul(pi0R, out.R);
359 std::vector<T> bal = vecmul(out.pi0, B0);
360 const std::vector<T> t1 = vecmul(pi0R, A1);
361 const std::vector<T> t2 = vecmul(pi0R2, A2);
362 for (std::size_t i = 0; i < m; ++i) bal[i] += t1[i] + t2[i];
363 const T pnorm = vecnorminf(out.pi0);
364 const T balTol =
365 conservTol * (pnorm > num_traits<T>::from_int(1) ? pnorm : num_traits<T>::from_int(1));
366 if (vecnorminf(bal) > balTol)
367 throw NumericError(
368 "qbd_rap: the boundary block B0 is inconsistent with the repeating blocks, "
369 "||pi0*B0 + pi1*A1 + pi2*A2||_inf = " +
370 std::to_string(num_traits<T>::to_double(vecnorminf(bal))) +
371 ". The level-0 balance equation of Theorem 7 requires pi0*(B0-A0) = 0.");
372
373 // Step 6.
374 out.pqueue = Matrix<T>(numLevels + 1, m);
375 std::vector<T> pin = out.pi0;
376 out.levelProb.assign(numLevels + 1, num_traits<T>::from_int(0));
377 for (std::size_t n = 0; n <= numLevels; ++n) {
378 for (std::size_t j = 0; j < m; ++j) {
379 out.pqueue(n, j) = pin[j];
380 out.levelProb[n] += pin[j];
381 }
382 pin = vecmul(pin, out.R);
383 }
384
385 // Exact mean queue length, sum_n n pi0 R^n e = pi0 R (I-R)^-2 e.
386 const Matrix<T> ImRinv = inverse(msub(I, out.R));
387 const std::vector<T> w = mulvec(ImRinv, mulvec(ImRinv, e));
389 for (std::size_t i = 0; i < m; ++i) out.QN += pi0R[i] * w[i];
390 return out;
391}
392
393/** qbd_rap with the reference defaults, B0 = A0, B1 = A1 and 20 levels. */
394template <class T>
395QbdRapResult<T> qbd_rap(const Matrix<T>& A0, const Matrix<T>& A1, const Matrix<T>& A2) {
396 return qbd_rap(A0, A1, A2, A0, A1, static_cast<std::size_t>(20));
397}
398
399/** Everything qbd_raprap1 returns. */
400template <class T>
402 T XN; ///< throughput, the arrival rate of the RAP
403 T QN; ///< mean queue length from the TRUNCATED level series
404 T UN; ///< utilization, 1 - P(level 0)
405 Matrix<T> pqueue; ///< level vectors, one row per level kept
407 T eta; ///< caudal characteristic, Sp(R)
408 Matrix<T> B, L, F; ///< the QBD blocks
409 QbdRapResult<T> core; ///< the full qbd_rap answer, including the exact QN
410};
411
412/**
413 * RAP/RAP/1 queue (qbd_raprap1.m).
414 *
415 * The two RAPs are independent, so the QBD phase space is the product of the
416 * two phase spaces with the ARRIVAL phase major, phase index (a-1) ns + s.
417 * The Kronecker factors must not be swapped: downstream consumers index
418 * pqueue by that convention.
419 *
420 * The truncation rule of the level series is the one of QBD_pi with
421 * MaxNumComp 100: accumulate until the mass reaches 1 - 1e-10, capped at 101
422 * level vectors. qbd_rap returns the exact mean queue length in closed form,
423 * but QN here is deliberately the TRUNCATED sum, because that is the
424 * documented return value and the JAR and Python ports must cut the tail at
425 * the same point; core.QN carries the closed form for comparison.
426 *
427 * @param util if positive, the service RAP is rescaled to mean util/lambda_a
428 * @param arrival the arrival RAP
429 * @param service_in the service RAP, rescaled to the requested utilization
430 */
431template <class T>
432QbdRapRap1Result<T> qbd_raprap1(const Map<T>& arrival, const Map<T>& service_in, const T& util) {
434 "qbd_raprap1 requires transcendental arithmetic");
435 const std::size_t na = arrival.order();
436 const std::size_t ns = service_in.order();
437 Map<T> service = service_in;
439 service = map_scale(service, T(util / map_lambda(arrival)));
440
442 out.F = kron(arrival.D1, eye<T>(ns));
443 out.L = qbd_detail::madd(kron(arrival.D0, eye<T>(ns)), kron(eye<T>(na), service.D0));
444 out.B = kron(eye<T>(na), service.D1);
445 const Matrix<T> B1 = kron(arrival.D0, eye<T>(ns));
446
447 out.core = qbd_rap(out.F, out.L, out.B, out.F, B1, static_cast<std::size_t>(0));
448 out.R = out.core.R;
449 out.G = out.core.G;
450 out.eta = num_traits<T>::from_double(out.core.spr);
451
452 const std::size_t m = na * ns;
453 const std::size_t maxNumComp = 100;
454 std::vector<std::vector<T>> levels;
455 levels.push_back(out.core.pi0);
456 T sumpi = num_traits<T>::from_int(0);
457 for (const T& v : out.core.pi0) sumpi += v;
458 const T target = num_traits<T>::from_int(1) - num_traits<T>::from_double(1e-10);
459 while (sumpi < target && levels.size() < 1 + maxNumComp) {
460 const std::vector<T> nxt = vecmul(levels.back(), out.R);
461 levels.push_back(nxt);
462 for (const T& v : nxt) sumpi += v;
463 }
464
465 out.pqueue = Matrix<T>(levels.size(), m);
467 for (std::size_t n = 0; n < levels.size(); ++n) {
469 for (std::size_t j = 0; j < m; ++j) {
470 out.pqueue(n, j) = levels[n][j];
471 lp += levels[n][j];
472 }
473 out.QN += num_traits<T>::from_int(static_cast<long>(n)) * lp;
474 }
475
476 T p0 = num_traits<T>::from_int(0);
477 for (std::size_t j = 0; j < m; ++j) p0 += out.pqueue(0, j);
478 out.UN = num_traits<T>::from_int(1) - p0;
479 out.XN = map_lambda(arrival);
480 return out;
481}
482
483/** qbd_raprap1 without rescaling the service process. */
484template <class T>
485QbdRapRap1Result<T> qbd_raprap1(const Map<T>& arrival, const Map<T>& service) {
486 return qbd_raprap1(arrival, service, T(num_traits<T>::from_int(0)));
487}
488
489} // namespace mam
490} // namespace line
491
492#endif // LINE_API_MAM_QBD_RAP_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
QbdRapResult< T > qbd_rap(const Matrix< T > &A0, const Matrix< T > &A1, const Matrix< T > &A2, const Matrix< T > &B0, const Matrix< T > &B1, std::size_t numLevels)
Equilibrium analysis of a QBD with RAP components (qbd_rap.m).
Definition qbd_rap.h:274
Map< T > map_scale(const Map< T > &in, const T &new_mean)
Rescale time so that the mean inter-arrival time becomes new_mean.
T map_lambda(const Map< T > &m)
Stationary arrival rate, lambda = pi D1 e.
Definition map_moment.h:79
QbdRapRap1Result< T > qbd_raprap1(const Map< T > &arrival, const Map< T > &service_in, const T &util)
RAP/RAP/1 queue (qbd_raprap1.m).
Definition qbd_rap.h:432
T num_abs(const T &v)
Definition number.h:172
double spectral_radius(const Matrix< double > &A)
Largest modulus over the spectrum, i.e.
Definition eig.h:97
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
Definition linalg.h:62
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
std::vector< double > svd_values(const Matrix< double > &A)
Singular values in descending order.
Definition eig.h:128
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
Quasi-birth-death processes: the rate matrix R, the fundamental matrix G, the caudal characteristic,...
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54
std::size_t order() const
Definition map_moment.h:57
Everything qbd_raprap1 returns.
Definition qbd_rap.h:401
T UN
utilization, 1 - P(level 0)
Definition qbd_rap.h:404
T QN
mean queue length from the TRUNCATED level series
Definition qbd_rap.h:403
Matrix< T > pqueue
level vectors, one row per level kept
Definition qbd_rap.h:405
T eta
caudal characteristic, Sp(R)
Definition qbd_rap.h:407
QbdRapResult< T > core
the full qbd_rap answer, including the exact QN
Definition qbd_rap.h:409
T XN
throughput, the arrival rate of the RAP
Definition qbd_rap.h:402
Matrix< T > F
the QBD blocks
Definition qbd_rap.h:408
Everything qbd_rap returns.
Definition qbd_rap.h:257
T QN
exact mean queue length, pi0 R (I-R)^-2 e
Definition qbd_rap.h:259
Matrix< T > pqueue
(numLevels+1) x m, row n holding pi_n
Definition qbd_rap.h:262
std::vector< T > pi0
Definition qbd_rap.h:263
std::vector< T > levelProb
marginal level probabilities, levels 0..numLevels
Definition qbd_rap.h:258
double spr
Sp(R), from a double eigensolve (see the header note).
Definition qbd_rap.h:261