5#ifndef LINE_API_MAM_QBD_RAP_H
6#define LINE_API_MAM_QBD_RAP_H
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);
111T vecnorminf(
const std::vector<T>& v) {
112 T best = num_traits<T>::from_int(0);
113 for (
const T& x : v) {
115 if (a > best) best = a;
122T rcond1(
const Matrix<T>& A) {
126 }
catch (
const NumericError&) {
127 return num_traits<T>::from_int(0);
129 const T na = qbd_detail::norm1(A);
130 const T ni = qbd_detail::norm1(Ainv);
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);
139 return num_traits<T>::from_double(2.220446049250313e-16);
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);
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]) {
159 std::size_t best = 0;
161 for (std::size_t i = 0; i < m; ++i) {
163 for (std::size_t j = 0; j < m; ++j) s +=
num_abs(T(A2(i, j)));
170 for (std::size_t j = 0; j < m; ++j) v[j] = A2(best, j);
172 for (
const T& x : v) ve += x;
173 if (
num_abs(T(ve)) < num_traits<T>::from_double(1e-12) * vecnorminf(v))
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");
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;
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)));
191 const Matrix<T> negA1 = mscale(A1, T(num_traits<T>::from_int(-1)));
192 if (rcond1(negA1) < working_eps<T>())
194 "qbd_rap: the local block A1 is singular, the iteration for G cannot be started");
195 const Matrix<T> negA1inv =
inverse(negA1);
197 Matrix<T> G(m, m, zero);
198 for (
unsigned it = 0; it < 200; ++it) {
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;
209 for (
unsigned it = 0; it < 100; ++it) {
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) {
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;
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];
233 const std::vector<T> Ge =
mulvec(G, e);
235 for (std::size_t i = 0; i < m; ++i) {
236 const T a =
num_abs(T(Ge[i] - e[i]));
239 if (!(res <= resTol) || ge > num_traits<T>::from_double(1e-8))
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.");
277 using namespace qbd_detail;
278 using namespace rap_detail;
280 const std::size_t m = A1.
rows();
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");
286 const std::vector<T> e =
ones<T>(m);
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];
296 const std::vector<T> ce =
mulvec(madd(madd(A0, A1), A2), e);
297 if (vecnorminf(ce) > conservTol)
299 "qbd_rap: the repeating blocks are not conservative, ||(A0+A1+A2)*e||_inf = " +
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)
305 "qbd_rap: the boundary blocks are not conservative, ||(B0+B1)*e||_inf = " +
307 ". A QBD with RAP components requires (B0+B1)*e = 0 at level 0.");
310 out.
G = qbd_rap_g(A0, A1, A2, blockScale);
311 out.
U = madd(A1,
matmul(A0, out.
G));
313 if (rcond1(negU) < working_eps<T>())
315 "qbd_rap: the matrix U = A1 + A0*G is singular, R = A0*inv(-U) does not exist");
320 for (std::size_t i = 0; i < m; ++i)
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)");
331 for (std::size_t i = 0; i < m; ++i)
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)
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)
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);
348 const std::vector<T> ImRinv_e =
mulvec(
inverse(msub(I, out.
R)), e);
350 for (std::size_t i = 0; i < m; ++i) denom += pihat0[i] * ImRinv_e[i];
352 throw NumericError(
"qbd_rap: the level-0 vector cannot be normalized");
354 for (std::size_t i = 0; i < m; ++i) out.
pi0[i] = pihat0[i] / denom;
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);
366 if (vecnorminf(bal) > balTol)
368 "qbd_rap: the boundary block B0 is inconsistent with the repeating blocks, "
369 "||pi0*B0 + pi1*A1 + pi2*A2||_inf = " +
371 ". The level-0 balance equation of Theorem 7 requires pi0*(B0-A0) = 0.");
375 std::vector<T> pin = out.
pi0;
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];
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];
396 return qbd_rap(A0, A1, A2, A0, A1,
static_cast<std::size_t
>(20));
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;
447 out.
core =
qbd_rap(out.
F, out.
L, out.
B, out.
F, B1,
static_cast<std::size_t
>(0));
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);
457 for (
const T& v : out.
core.pi0) sumpi += v;
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;
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];
477 for (std::size_t j = 0; j < m; ++j) p0 += out.
pqueue(0, j);
NumericError(const std::string &what)
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...
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.
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).
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.
QbdRapRap1Result< T > qbd_raprap1(const Map< T > &arrival, const Map< T > &service_in, const T &util)
RAP/RAP/1 queue (qbd_raprap1.m).
double spectral_radius(const Matrix< double > &A)
Largest modulus over the spectrum, i.e.
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
std::vector< double > svd_values(const Matrix< double > &A)
Singular values in descending order.
Matrix< T > eye(std::size_t n)
Identity of order n.
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).
std::size_t order() const
Everything qbd_raprap1 returns.
T UN
utilization, 1 - P(level 0)
T QN
mean queue length from the TRUNCATED level series
Matrix< T > pqueue
level vectors, one row per level kept
T eta
caudal characteristic, Sp(R)
QbdRapResult< T > core
the full qbd_rap answer, including the exact QN
T XN
throughput, the arrival rate of the RAP
Matrix< T > F
the QBD blocks
Everything qbd_rap returns.
T QN
exact mean queue length, pi0 R (I-R)^-2 e
Matrix< T > pqueue
(numLevels+1) x m, row n holding pi_n
std::vector< T > levelProb
marginal level probabilities, levels 0..numLevels
double spr
Sp(R), from a double eigensolve (see the header note).