LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
etaqa.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_LIB_SMC_ETAQA_H
6#define LINE_LIB_SMC_ETAQA_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * ETAQA: the aggregated stationary vector and the queue-length moments of an
12 * M/G/1-type and of a GI/M/1-type Markov chain. Port of MAMSolver's
13 * `MG1_G_ETAQA.m`, `MG1_pi_ETAQA.m`, `MG1_qlen_ETAQA.m`, `GIM1_R_ETAQA.m`,
14 * `GIM1_pi_ETAQA.m` and `GIM1_qlen_ETAQA.m` (`matlab/lib/thirdparty/MAMSolver`).
15 *
16 * WHAT ETAQA IS. A structured infinite chain has a stationary vector
17 * pi = (pi_0, pi_1, pi_2, ...) with one block per level. Matrix-geometric
18 * methods compute every block; ETAQA instead solves a FINITE linear system for
19 * exactly three aggregates -- pi_0, pi_1 and pi* = sum_{j>=2} pi_j -- by
20 * replacing the infinitely many balance equations for levels 2 and above by
21 * their sum. The aggregate system is (mb + 2m) x (mb + 2m) and is EXACT: no
22 * level is truncated and no tail is fitted, which is what separates ETAQA from
23 * a level-truncated direct solve. Riska and Smirni, Perform. Eval. 54(2), 2003.
24 *
25 * The moments come the same way. `..._qlen_ETAQA` never forms pi_j either: it
26 * propagates the vectors r^(k) = sum_{j>=2} j^k pi_j through a recurrence whose
27 * left-hand side is one fixed m x m system, so the n-th moment costs n solves
28 * of the same size regardless of how heavy the tail is.
29 *
30 * WHY THE LAST COLUMN IS ALWAYS DROPPED. The aggregate balance equations are
31 * linearly dependent, exactly as the balance equations of any generator are, so
32 * the system is rank deficient by one and the normalization e^T pi = 1 supplies
33 * the missing equation. The M/G/1 side finds WHICH column to drop with a rank
34 * test (a redundant one exists but is not always the last), the GI/M/1 side
35 * simply drops the final column. Both then prepend the column of ones.
36 *
37 * DOUBLE ONLY: see the note in `lib/smc/mg1.h`. The rank test is an SVD and
38 * the fundamental matrices come from LAPACK-backed and FFT-backed iterations.
39 *
40 * REFERENCE DEFECTS REPRODUCED VERBATIM, because these functions are the
41 * numerical reference for the JAR and Python ports and a silent repair here
42 * would be a divergence nobody could see:
43 *
44 * 1. `MG1_G_ETAQA` tests whether its input is a DTMC by setting a flag named
45 * `isdicrete` and reading one named `isdiscrete`, so the flag never changes
46 * and the continuous-to-discrete uniformization ALWAYS runs. For the
47 * generators LINE passes it that is the correct branch anyway; a genuine
48 * stochastic input would be divided by -min(diag(A1)), which is <= 0.
49 *
50 * 2. `MG1_qlen_ETAQA` builds `F0j` from block dega down to block 2 but indexes
51 * it from 1, so `F0j(:,(j-1)*m+1:j*m)` is the tail sum starting at j+1 and
52 * not at j. The moment is therefore the reference's moment, off-by-one
53 * index and all.
54 *
55 * 3. `GIM1_qlen_ETAQA` initializes `leftr_part1 = A(3)`, a SCALAR read at
56 * linear index 3 of the stacked A rather than the third block, and MATLAB
57 * then broadcasts it over the m x m accumulator. The port reproduces both
58 * the value and the broadcast, including the case where the loop that would
59 * turn it into a matrix does not run and the scalar survives as a vector.
60 */
61
62#include <algorithm>
63#include <cmath>
64#include <cstddef>
65#include <string>
66#include <vector>
67
68#include "line/lib/smc/mg1.h"
69#include "line/util/eig.h"
70#include "line/util/error.h"
71#include "line/util/linalg.h"
72#include "line/util/lu.h"
73#include "line/util/matrix.h"
74
75namespace line {
76namespace smc {
77
78namespace etaqa_detail {
79
80/** MATLAB's `x / A` for a row vector x and a square A: solves x A = b. */
81inline std::vector<double> right_divide(const std::vector<double>& b, const Matrix<double>& A) {
82 if (A.rows() != A.cols()) throw InputError("ETAQA: right division by a non-square matrix");
83 if (b.size() != A.rows()) throw InputError("ETAQA: right division with mismatched length");
84 return solve(A.transpose(), b);
85}
86
87/** Row vector times matrix. */
88inline std::vector<double> vmul(const std::vector<double>& v, const Matrix<double>& A) {
89 return vecmul(v, A);
90}
91
92/** Sum of the entries of a vector. */
93inline double vsum(const std::vector<double>& v) {
94 double s = 0.0;
95 for (double x : v) s += x;
96 return s;
97}
98
99/** v A e, the row vector reduced to a scalar by a matrix and a column of ones. */
100inline double vmul_e(const std::vector<double>& v, const Matrix<double>& A) {
101 const std::vector<double> r = vmul(v, A);
102 return vsum(r);
103}
104
105/** The binomial coefficient the reference computes as a ratio of factorials. */
106inline double bino(std::size_t n, std::size_t k) {
107 double num = 1.0, den = 1.0;
108 for (std::size_t i = 1; i <= n; ++i) num *= static_cast<double>(i);
109 for (std::size_t i = 1; i <= k; ++i) den *= static_cast<double>(i);
110 for (std::size_t i = 1; i <= n - k; ++i) den *= static_cast<double>(i);
111 return num / den;
112}
113
114/** Horizontal concatenation of matrices with the same number of rows. */
115inline Matrix<double> hcat2(const std::vector<Matrix<double>>& parts) {
116 std::size_t cols = 0;
117 for (const Matrix<double>& p : parts) cols += p.cols();
118 const std::size_t rows = parts.empty() ? 0 : parts[0].rows();
119 Matrix<double> out(rows, cols, 0.0);
120 std::size_t off = 0;
121 for (const Matrix<double>& p : parts) {
122 for (std::size_t i = 0; i < rows; ++i)
123 for (std::size_t j = 0; j < p.cols(); ++j) out(i, off + j) = p(i, j);
124 off += p.cols();
125 }
126 return out;
127}
128
129/** Vertical concatenation of matrices with the same number of columns. */
130inline Matrix<double> vcat2(const std::vector<Matrix<double>>& parts) {
131 std::size_t rows = 0;
132 for (const Matrix<double>& p : parts) rows += p.rows();
133 const std::size_t cols = parts.empty() ? 0 : parts[0].cols();
134 Matrix<double> out(rows, cols, 0.0);
135 std::size_t off = 0;
136 for (const Matrix<double>& p : parts) {
137 for (std::size_t i = 0; i < p.rows(); ++i)
138 for (std::size_t j = 0; j < cols; ++j) out(off + i, j) = p(i, j);
139 off += p.rows();
140 }
141 return out;
142}
143
144/** Columns `[from, to)` of A. */
145inline Matrix<double> cols_of(const Matrix<double>& A, std::size_t from, std::size_t to) {
146 Matrix<double> out(A.rows(), to - from, 0.0);
147 for (std::size_t i = 0; i < A.rows(); ++i)
148 for (std::size_t j = from; j < to; ++j) out(i, j - from) = A(i, j);
149 return out;
150}
151
152/** Rows `[from, to)` of A. */
153inline Matrix<double> rows_of(const Matrix<double>& A, std::size_t from, std::size_t to) {
154 Matrix<double> out(to - from, A.cols(), 0.0);
155 for (std::size_t i = from; i < to; ++i)
156 for (std::size_t j = 0; j < A.cols(); ++j) out(i - from, j) = A(i, j);
157 return out;
158}
159
160/**
161 * Block i (i >= 1) of a GI/M/1-type vertical stack whose boundary occupies the
162 * first `mb` rows: `M(mb+(i-1)*m+1 : mb+i*m, :)`. The blocks are m x mb, which
163 * is not m x m when the boundary has a different size, so this cannot go
164 * through the uniform block splitter.
165 */
166inline Matrix<double> vblock_after(const Matrix<double>& M, std::size_t mb, std::size_t m,
167 std::size_t i) {
168 return rows_of(M, mb + (i - 1) * m, mb + i * m);
169}
170
171/** A with column `drop` removed. */
172inline Matrix<double> drop_col(const Matrix<double>& A, std::size_t drop) {
173 Matrix<double> out(A.rows(), A.cols() - 1, 0.0);
174 for (std::size_t i = 0; i < A.rows(); ++i) {
175 std::size_t c = 0;
176 for (std::size_t j = 0; j < A.cols(); ++j) {
177 if (j == drop) continue;
178 out(i, c++) = A(i, j);
179 }
180 }
181 return out;
182}
183
184} // namespace etaqa_detail
185
186// ---------------------------------------------------------------------------
187// MG1_G_ETAQA.m
188// ---------------------------------------------------------------------------
189
190/**
191 * G of an M/G/1-type chain, uniformized first. Port of `MG1_G_ETAQA.m`.
192 *
193 * `A` is the wide `[A0 A1 ... Amax]`. The generator is turned into the
194 * transition matrix of the uniformized chain by dividing through by
195 * -min(diag(A1)) and adding the identity back onto A1, which is what cyclic
196 * reduction expects; see defect 1 in the header for why that branch is
197 * unconditional.
198 */
200 const std::size_t r = A.rows();
201 if (A.cols() % r != 0) throw InputError("MG1_G_ETAQA: A is not a block sequence of A's width");
202 Matrix<double> An = A;
203 // The reference's isdiscrete flag is write-only (`isdicrete`), so this
204 // uniformization always runs.
205 double t = An(0, r + 0);
206 for (std::size_t i = 1; i < r; ++i) t = std::min(t, An(i, r + i));
207 if (t > 0.0)
208 throw InputError(
209 "MG1_G_ETAQA: this is not a stochastic matrix, neither continuous nor discrete; "
210 "every row must sum to 0 or 1");
211 for (std::size_t i = 0; i < An.rows(); ++i)
212 for (std::size_t j = 0; j < An.cols(); ++j) An(i, j) /= (-t);
213 for (std::size_t i = 0; i < r; ++i) An(i, r + i) += 1.0;
214
215 return mg1_cr(blocks_of(An, r));
216}
217
218// ---------------------------------------------------------------------------
219// MG1_pi_ETAQA.m
220// ---------------------------------------------------------------------------
221
222/**
223 * Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of an M/G/1-type chain.
224 * Port of `MG1_pi_ETAQA.m`.
225 *
226 * `B` may be empty, in which case the boundary repeats the repetitive blocks.
227 * `C0` is the reference's 'Boundary' option, the block that takes level 1 back
228 * to a boundary of a different size; pass an empty matrix for the default A0.
229 */
230inline std::vector<double> mg1_pi_etaqa(const Matrix<double>& Bin, const Matrix<double>& Ain,
231 const Matrix<double>& G,
232 const Matrix<double>& C0in = Matrix<double>()) {
233 using namespace etaqa_detail;
234 Matrix<double> A = Ain;
235 const std::size_t m = A.rows();
236 const std::size_t dega = A.cols() / m - 1;
237 const Matrix<double> A0 = cols_of(A, 0, m);
238
240 std::size_t mb = 0, degb = 0;
241 if (Bin.empty()) {
242 mb = m;
243 degb = dega;
244 B = A;
245 } else {
246 B = Bin;
247 mb = B.rows();
248 if ((B.cols() - mb) % m != 0)
249 throw InputError("MG1_pi_ETAQA: matrix B has an incorrect number of columns");
250 degb = (B.cols() - mb) / m;
251 }
252 Matrix<double> C0 = C0in.empty() ? A0 : C0in;
253 if (C0in.empty() && mb != m)
254 throw InputError(
255 "MG1_pi_ETAQA: the Boundary option must be used since a dimension of B0 is not "
256 "identical to A0");
257 if (!C0in.empty() && (C0in.rows() != m || C0in.cols() != mb))
258 throw InputError("MG1_pi_ETAQA: the boundary parameter value has an incorrect dimension");
259
260 // A transition matrix is turned into a generator, as the reference tests it.
261 const std::vector<double> brs = rowsums(B);
262 double tot = 0.0;
263 for (double x : brs) tot += x;
264 if (tot > 1e-12 && (tot - static_cast<double>(mb)) < 1e-12) {
265 for (std::size_t i = 0; i < mb; ++i) B(i, i) -= 1.0;
266 for (std::size_t i = 0; i < m; ++i) A(i, m + i) -= 1.0;
267 }
268
269 const Blocks Ab = blocks_of(A, m);
270 const Drift d = mg1_drift(Ab);
271 if (d.value >= 1.0)
272 throw NumericError(
273 "MG1_pi_ETAQA: the Markov chain characterized by A is not positive recurrent (drift = " +
274 std::to_string(d.value) + ")");
275
276 // Shat(j) = B(j) + B(j+1) G + B(j+2) G^2 + ..., j = 1..degb.
277 std::vector<Matrix<double>> Shat;
278 Shat.push_back(cols_of(B, mb + (degb - 1) * m, mb + degb * m));
279 for (std::size_t i = degb; i-- > 1;) {
280 const Matrix<double> temp =
281 madd(cols_of(B, mb + (i - 1) * m, mb + i * m), matmul(Shat.front(), G));
282 Shat.insert(Shat.begin(), temp);
283 }
284
285 // S(j) = A(j) + A(j+1) G + A(j+2) G^2 + ..., j = 1..dega.
286 if (dega <= 1)
287 throw InputError(
288 "MG1_pi_ETAQA: the number of repetitive state blocks is less than 2, this is not an "
289 "irreducible Markov chain");
290 std::vector<Matrix<double>> S;
291 S.push_back(Ab[dega]);
292 for (std::size_t i = dega; i-- > 1;) {
293 const Matrix<double> temp = madd(Ab[i], matmul(S.front(), G));
294 S.insert(S.begin(), temp);
295 }
296
297 const Matrix<double> zmb(m, mb, 0.0), zmm(m, m, 0.0);
298 Matrix<double> Firstc(mb + 2 * m, 1, 1.0);
299 std::vector<Matrix<double>> secondp;
300 secondp.push_back(cols_of(B, 0, mb));
301 secondp.push_back(C0);
302 secondp.push_back(zmb);
303 const Matrix<double> Secondc = vcat2(secondp);
304
305 if (Shat.size() < 2) Shat.push_back(Matrix<double>(mb, m, 0.0));
306 if (S.size() < 2) S.push_back(Matrix<double>(m, m, 0.0));
307
308 std::vector<Matrix<double>> thirdp;
309 thirdp.push_back(madd(cols_of(B, mb, mb + m), matmul(Shat[1], G)));
310 thirdp.push_back(madd(Ab[1], matmul(S[1], G)));
311 thirdp.push_back(zmm);
312 const Matrix<double> Thirdc = vcat2(thirdp);
313
314 Matrix<double> Bsum(mb, m, 0.0), Shat_sum(mb, m, 0.0);
315 if (degb <= 2) {
316 if (degb == 2) Bsum = cols_of(B, mb + m, mb + 2 * m);
317 } else {
318 for (std::size_t i = 2; i <= degb - 1; ++i) {
319 Bsum = madd(Bsum, cols_of(B, mb + (i - 1) * m, mb + i * m));
320 Shat_sum = madd(Shat_sum, Shat[i]);
321 }
322 Bsum = madd(Bsum, cols_of(B, mb + (degb - 1) * m, B.cols()));
323 }
324
325 Matrix<double> Asum(m, m, 0.0), Ssum(m, m, 0.0);
326 if (dega >= 3) {
327 for (std::size_t i = 2; i <= dega - 1; ++i) {
328 Ssum = madd(Ssum, S[i]);
329 Asum = madd(Asum, Ab[i]);
330 }
331 Asum = madd(Asum, Ab[dega]);
332 } else if (dega == 2) {
333 Asum = madd(Asum, Ab[dega]);
334 } else {
335 throw InputError(
336 "MG1_pi_ETAQA: the number of repetitive state blocks is less than 3, the Markov chain "
337 "is reducible");
338 }
339
340 std::vector<Matrix<double>> fourthp;
341 fourthp.push_back(madd(Bsum, matmul(Shat_sum, G)));
342 fourthp.push_back(madd(Asum, matmul(Ssum, G)));
343 fourthp.push_back(madd(madd(Asum, Ab[1]), matmul(madd(Ssum, S[1]), G)));
344 const Matrix<double> Fourthc = vcat2(fourthp);
345
346 std::vector<Matrix<double>> xparts;
347 xparts.push_back(Secondc);
348 xparts.push_back(Thirdc);
349 xparts.push_back(Fourthc);
350 Matrix<double> Xtemp = hcat2(xparts);
351
352 // Drop the first column whose removal leaves the rank unchanged.
353 const std::size_t full = matrix_rank(Xtemp);
354 const std::size_t n = mb + 2 * m;
355 std::size_t drop = n - 1;
356 for (std::size_t i = 0; i < n; ++i) {
357 if (matrix_rank(drop_col(Xtemp, i)) == full) {
358 drop = i;
359 break;
360 }
361 }
362 Xtemp = drop_col(Xtemp, drop);
363
364 std::vector<Matrix<double>> xnewp;
365 xnewp.push_back(Firstc);
366 xnewp.push_back(Xtemp);
367 const Matrix<double> Xnew = hcat2(xnewp);
368
369 std::vector<double> rside(n, 0.0);
370 rside[0] = 1.0;
371 return right_divide(rside, Xnew);
372}
373
374// ---------------------------------------------------------------------------
375// MG1_qlen_ETAQA.m
376// ---------------------------------------------------------------------------
377
378/**
379 * n-th moment of the level (the queue length) of an M/G/1-type chain from the
380 * ETAQA aggregates. Port of `MG1_qlen_ETAQA.m`.
381 */
382inline double mg1_qlen_etaqa(const Matrix<double>& Bin, const Matrix<double>& Ain,
383 const std::vector<double>& pi, std::size_t n,
384 const Matrix<double>& C0in = Matrix<double>()) {
385 using namespace etaqa_detail;
386 const Matrix<double>& A = Ain;
387 const std::size_t m = A.rows();
388 const std::size_t dega = A.cols() / m - 1;
389 const Blocks Ab = blocks_of(A, m);
390
392 std::size_t mb = 0, degb = 0;
393 if (Bin.empty()) {
394 mb = m;
395 degb = dega;
396 B = A;
397 } else {
398 B = Bin;
399 mb = B.rows();
400 if ((B.cols() - mb) % m != 0)
401 throw InputError("MG1_qlen_ETAQA: matrix B has an incorrect number of columns");
402 degb = (B.cols() - mb) / m;
403 }
404 if (C0in.empty() && mb != m)
405 throw InputError(
406 "MG1_qlen_ETAQA: the Boundary option must be used since the column size of B0 is not "
407 "identical to A0");
408 if (!C0in.empty() && (C0in.rows() != m || C0in.cols() != mb))
409 throw InputError("MG1_qlen_ETAQA: the boundary parameter value has an incorrect dimension");
410
411 double mass = 0.0;
412 for (double x : pi) mass += x;
413 if (std::fabs(mass - 1.0) > 1e-10)
414 throw InputError("MG1_qlen_ETAQA: the input probability vector does not sum up to 1");
415 if ((pi.size() - mb) % m != 0)
416 throw InputError(
417 "MG1_qlen_ETAQA: the probability vector has an incorrect number of columns");
418
419 const std::vector<double> pi0(pi.begin(), pi.begin() + mb);
420 const std::vector<double> pi1(pi.begin() + mb, pi.begin() + mb + m);
421 const std::vector<double> pistar(pi.begin() + mb + m, pi.begin() + mb + 2 * m);
422
423 // lsleft = [(A0+...+Amax) without its last column, (F11 - A0) e].
424 Matrix<double> Asum = Ab[0];
425 for (std::size_t i = 1; i <= dega; ++i) Asum = madd(Asum, Ab[i]);
426 Matrix<double> F11 = Ab[2];
427 for (std::size_t i = 3; i <= dega; ++i)
428 F11 = madd(F11, mscale(Ab[i], static_cast<double>(i - 1)));
429 Matrix<double> lsleft(m, m, 0.0);
430 for (std::size_t i = 0; i < m; ++i) {
431 for (std::size_t j = 0; j + 1 < m; ++j) lsleft(i, j) = Asum(i, j);
432 double s = 0.0;
433 for (std::size_t j = 0; j < m; ++j) s += F11(i, j) - Ab[0](i, j);
434 lsleft(i, m - 1) = s;
435 }
436
437 // Fhat0(j) = sum_{l>=j} B(l), j = 1..degb.
438 std::vector<Matrix<double>> Fhat0j;
439 Fhat0j.push_back(cols_of(B, mb + (degb - 1) * m, B.cols()));
440 for (std::size_t j = degb; j-- > 1;)
441 Fhat0j.insert(Fhat0j.begin(),
442 madd(cols_of(B, mb + (j - 1) * m, mb + j * m), Fhat0j.front()));
443
444 // F0(j) = sum_{l>=j+1} A(l): built from block dega down to block 2 but
445 // indexed from 1, which is the off-by-one of defect 2 in the header.
446 std::vector<Matrix<double>> F0j;
447 F0j.push_back(Ab[dega]);
448 for (std::size_t j = dega; j-- > 2;) F0j.insert(F0j.begin(), madd(Ab[j], F0j.front()));
449
450 std::vector<std::vector<double>> r;
451 r.push_back(pistar);
452
453 // frestsaver(l) = sum_{j>=2} j^l A(j); fcrestsaver(l) = sum_j j^l F0j(j).
454 std::vector<Matrix<double>> frestsaver, fcrestsaver;
455 for (std::size_t l = 1; l <= n; ++l) {
456 Matrix<double> t1(m, m, 0.0);
457 for (std::size_t j = 2; j <= dega; ++j)
458 t1 = madd(t1, mscale(Ab[j], std::pow(static_cast<double>(j), static_cast<double>(l))));
459 frestsaver.push_back(t1);
460 Matrix<double> t2(m, m, 0.0);
461 for (std::size_t j = 1; j <= dega; ++j)
462 if (j <= F0j.size())
463 t2 = madd(t2, mscale(F0j[j - 1],
464 std::pow(static_cast<double>(j), static_cast<double>(l))));
465 fcrestsaver.push_back(t2);
466 }
467
468 for (std::size_t k = 1; k <= n; ++k) {
469 const double dk = static_cast<double>(k);
470 Matrix<double> fhatkM(mb, m, 0.0);
471 for (std::size_t j = 1; j <= degb; ++j)
472 fhatkM = madd(fhatkM, mscale(cols_of(B, mb + (j - 1) * m, mb + j * m),
473 std::pow(static_cast<double>(j + 1), dk)));
474 const std::vector<double> fhatk = vmul(pi0, fhatkM);
475
476 Matrix<double> fkM(m, m, 0.0);
477 for (std::size_t j = 2; j <= dega; ++j)
478 fkM = madd(fkM, mscale(Ab[j], std::pow(static_cast<double>(j + 1), dk)));
479 fkM = madd(mscale(Ab[1], std::pow(2.0, dk)), fkM);
480 const std::vector<double> fk = vmul(pi1, fkM);
481
482 std::vector<double> frest(m, 0.0);
483 for (std::size_t l = 1; l <= k; ++l) {
484 const Matrix<double> M = madd(Ab[1], frestsaver[l - 1]);
485 const std::vector<double> t = vmul(r[k - l], M);
486 for (std::size_t j = 0; j < m; ++j) frest[j] += bino(k, l) * t[j];
487 }
488
489 std::vector<double> bk(m, 0.0);
490 for (std::size_t j = 0; j < m; ++j) bk[j] = -fhatk[j] - fk[j] - frest[j];
491
492 Matrix<double> fchatkM(mb, m, 0.0);
493 for (std::size_t j = 2; j <= degb; ++j)
494 fchatkM = madd(fchatkM, mscale(Fhat0j[j - 1], std::pow(static_cast<double>(j), dk)));
495 const double fchatk = vmul_e(pi0, fchatkM);
496
497 Matrix<double> fckM(m, m, 0.0);
498 for (std::size_t j = 1; j + 1 <= dega; ++j)
499 if (j <= F0j.size())
500 fckM = madd(fckM,
501 mscale(F0j[j - 1], std::pow(static_cast<double>(j + 1), dk)));
502 const double fck = vmul_e(pi1, fckM);
503
504 double fcrest = 0.0;
505 for (std::size_t l = 1; l <= k; ++l)
506 fcrest += bino(k, l) * vmul_e(r[k - l], fcrestsaver[l - 1]);
507
508 const double ck = -fchatk - fck - fcrest;
509
510 std::vector<double> rside(m, 0.0);
511 for (std::size_t j = 0; j + 1 < m; ++j) rside[j] = bk[j];
512 rside[m - 1] = ck;
513 r.push_back(right_divide(rside, lsleft));
514 }
515
516 return vsum(r.back()) + vsum(pi1);
517}
518
519// ---------------------------------------------------------------------------
520// GIM1_R_ETAQA.m
521// ---------------------------------------------------------------------------
522
523/**
524 * R of a GI/M/1-type chain, uniformized first. Port of `GIM1_R_ETAQA.m`.
525 *
526 * `A` is the VERTICAL stack `[A0; A1; ...; Amax]`, which is how a GI/M/1-type
527 * sequence is written; the reference transposes that stack into the horizontal
528 * one `GIM1_R` wants, and asks for the automatic dual with functional
529 * iterations.
530 */
532 const std::size_t s = A.cols();
533 if (A.rows() % s != 0)
534 throw InputError("GIM1_R_ETAQA: A is not a vertical stack of square blocks");
535 Matrix<double> An = A;
536 double t = An(s + 0, 0);
537 for (std::size_t i = 1; i < s; ++i) t = std::min(t, An(s + i, i));
538 const bool isdiscrete = t > 0.0;
539 if (!isdiscrete) {
540 if (t > 0.0)
541 throw InputError(
542 "GIM1_R_ETAQA: this is not a stochastic matrix, neither continuous nor discrete");
543 for (std::size_t i = 0; i < An.rows(); ++i)
544 for (std::size_t j = 0; j < An.cols(); ++j) An(i, j) /= (-t);
545 for (std::size_t i = 0; i < s; ++i) An(s + i, i) += 1.0;
546 }
547 return gim1_r(vblocks_of(An, s), "A", "FI");
548}
549
550// ---------------------------------------------------------------------------
551// GIM1_pi_ETAQA.m
552// ---------------------------------------------------------------------------
553
554/**
555 * Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of a GI/M/1-type chain.
556 * Port of `GIM1_pi_ETAQA.m`. `B` and `A` are vertical stacks; `B0` is the
557 * reference's 'Boundary' option (pass empty for the default A0).
558 */
559inline std::vector<double> gim1_pi_etaqa(const Matrix<double>& Bin, const Matrix<double>& Ain,
560 const Matrix<double>& R,
561 const Matrix<double>& B0in = Matrix<double>()) {
562 using namespace etaqa_detail;
563 Matrix<double> B = Bin, A = Ain;
564 const std::size_t m = R.rows();
565 const std::size_t mb = B.cols();
566 if ((B.rows() - mb) % m != 0)
567 throw InputError("GIM1_pi_ETAQA: input matrix B has an incorrect number of rows");
568 const std::size_t degb = (B.rows() - mb) / m;
569 if (A.rows() % m != 0)
570 throw InputError("GIM1_pi_ETAQA: input matrix A has an incorrect number of rows");
571 const std::size_t dega = A.rows() / m - 1;
572
573 const Matrix<double> ImR = msub(eye<double>(m), R);
574 const Matrix<double> temp0 = inverse(ImR);
575 bool all_cols_negative = true;
576 for (std::size_t j = 0; j < m && all_cols_negative; ++j) {
577 bool any = false;
578 for (std::size_t i = 0; i < m; ++i)
579 if (temp0(i, j) < -100.0 * 2.220446049250313e-16) any = true;
580 all_cols_negative = any;
581 }
582 if (all_cols_negative)
583 throw NumericError(
584 "GIM1_pi_ETAQA: the spectral radius of R is not below 1, GIM1 is not positive "
585 "recurrent");
586
588 if (!B0in.empty()) {
589 if (B0in.rows() != mb)
590 throw InputError("GIM1_pi_ETAQA: Boundary has an incorrect number of rows");
591 if (B0in.cols() != m)
592 throw InputError("GIM1_pi_ETAQA: Boundary has an incorrect number of columns");
593 B0 = B0in;
594 } else {
595 B0 = Matrix<double>(m, A.cols(), 0.0);
596 for (std::size_t i = 0; i < m; ++i)
597 for (std::size_t j = 0; j < A.cols(); ++j) B0(i, j) = A(i, j);
598 }
599
600 // A transition matrix is turned into a generator, as the reference tests it.
601 Matrix<double> Btop(mb, B.cols(), 0.0);
602 for (std::size_t i = 0; i < mb; ++i)
603 for (std::size_t j = 0; j < B.cols(); ++j) Btop(i, j) = B(i, j);
604 const Matrix<double> test = madd(Btop, B0);
605 const std::vector<double> trs = rowsums(test);
606 double tot = 0.0;
607 for (double x : trs) tot += x - 1.0;
608 if (std::fabs(tot) < 1e-10) {
609 for (std::size_t i = 0; i < mb; ++i) B(i, i) -= 1.0;
610 for (std::size_t i = 0; i < m; ++i) A(m + i, i) -= 1.0;
611 }
612
613 const Blocks Ab = vblocks_of(A, m);
614
615 const Matrix<double> Firstc(mb + 2 * m, 1, 1.0);
616
617 // sum_{i>=2} R^(i-2) (I - R) B(i)
618 Matrix<double> temp = msub(eye<double>(m), R);
619 Matrix<double> tempsum(m, mb, 0.0);
620 for (std::size_t i = 2; i <= degb; ++i) {
621 tempsum = madd(tempsum, matmul(temp, vblock_after(B, mb, m, i)));
622 temp = matmul(R, temp);
623 }
624 std::vector<Matrix<double>> secondp;
625 secondp.push_back(Btop);
626 secondp.push_back(vblock_after(B, mb, m, 1));
627 secondp.push_back(tempsum);
628 const Matrix<double> Secondc = vcat2(secondp);
629
630 // sum_{i>=2} R^(i-2) (I - R) A(i)
631 temp = msub(eye<double>(m), R);
632 tempsum = Matrix<double>(m, m, 0.0);
633 for (std::size_t i = 2; i <= dega; ++i) {
634 tempsum = madd(tempsum, matmul(temp, Ab[i]));
635 temp = matmul(R, temp);
636 }
637 std::vector<Matrix<double>> thirdp;
638 thirdp.push_back(B0);
639 thirdp.push_back(Ab[1]);
640 thirdp.push_back(tempsum);
641 const Matrix<double> Thirdc = vcat2(thirdp);
642
643 // sum_{i>=2} R^(i-1) A(i)
644 temp = R;
645 tempsum = Matrix<double>(m, m, 0.0);
646 for (std::size_t i = 2; i <= dega; ++i) {
647 tempsum = madd(tempsum, matmul(temp, Ab[i]));
648 temp = matmul(R, temp);
649 }
650 std::vector<Matrix<double>> fourthp;
651 fourthp.push_back(Matrix<double>(mb, m, 0.0));
652 fourthp.push_back(Ab[0]);
653 fourthp.push_back(madd(madd(Ab[0], Ab[1]), tempsum));
654 Matrix<double> Fourthc = vcat2(fourthp);
655 Fourthc = cols_of(Fourthc, 0, Fourthc.cols() - 1);
656
657 std::vector<Matrix<double>> xparts;
658 xparts.push_back(Firstc);
659 xparts.push_back(Secondc);
660 xparts.push_back(Thirdc);
661 xparts.push_back(Fourthc);
662 const Matrix<double> X = hcat2(xparts);
663
664 std::vector<double> rside(mb + 2 * m, 0.0);
665 rside[0] = 1.0;
666 return right_divide(rside, X);
667}
668
669// ---------------------------------------------------------------------------
670// GIM1_qlen_ETAQA.m
671// ---------------------------------------------------------------------------
672
673/**
674 * n-th moment of the level of a GI/M/1-type chain from the ETAQA aggregates.
675 * Port of `GIM1_qlen_ETAQA.m`, including the scalar `A(3)` of defect 3.
676 */
677inline double gim1_qlen_etaqa(const Matrix<double>& Bin, const Matrix<double>& Ain,
678 const Matrix<double>& R, const std::vector<double>& pi,
679 std::size_t n, const Matrix<double>& B0in = Matrix<double>()) {
680 using namespace etaqa_detail;
681 Matrix<double> B = Bin, A = Ain;
682 const std::size_t m = R.rows();
683 const std::size_t mb = B.cols();
684 if ((B.rows() - mb) % m != 0)
685 throw InputError("GIM1_qlen_ETAQA: input matrix B has an incorrect number of rows");
686 const std::size_t degb = (B.rows() - mb) / m;
687 if (A.rows() % m != 0)
688 throw InputError("GIM1_qlen_ETAQA: input matrix A has an incorrect number of rows");
689 const std::size_t dega = A.rows() / m - 1;
690
691 const Matrix<double> temp0 = inverse(msub(eye<double>(m), R));
692 bool all_cols_negative = true;
693 for (std::size_t j = 0; j < m && all_cols_negative; ++j) {
694 bool any = false;
695 for (std::size_t i = 0; i < m; ++i)
696 if (temp0(i, j) < -100.0 * 2.220446049250313e-16) any = true;
697 all_cols_negative = any;
698 }
699 if (all_cols_negative)
700 throw NumericError(
701 "GIM1_qlen_ETAQA: the spectral radius of R is not below 1, GIM1 is not positive "
702 "recurrent");
703
705 if (!B0in.empty()) {
706 if (B0in.rows() != mb || B0in.cols() != m)
707 throw InputError("GIM1_qlen_ETAQA: Boundary has an incorrect dimension");
708 B0 = B0in;
709 } else {
710 B0 = Matrix<double>(m, A.cols(), 0.0);
711 for (std::size_t i = 0; i < m; ++i)
712 for (std::size_t j = 0; j < A.cols(); ++j) B0(i, j) = A(i, j);
713 }
714
715 const std::vector<double> pi0(pi.begin(), pi.begin() + mb);
716 const std::vector<double> pi1(pi.begin() + mb, pi.begin() + mb + m);
717 const std::vector<double> pistar(pi.begin() + mb + m, pi.begin() + mb + 2 * m);
718
719 if (n == 0) return 1.0;
720
721 // The scalar of defect 3, read at column-major linear index 3 of A.
722 const double a3 = A(2 % A.rows(), 2 / A.rows());
723
724 Matrix<double> Btop(mb, B.cols(), 0.0);
725 for (std::size_t i = 0; i < mb; ++i)
726 for (std::size_t j = 0; j < B.cols(); ++j) Btop(i, j) = B(i, j);
727 const Matrix<double> test = madd(Btop, B0);
728 const std::vector<double> trs = rowsums(test);
729 double tot = 0.0;
730 for (double x : trs) tot += x - 1.0;
731 if (std::fabs(tot) < 1e-10) {
732 for (std::size_t i = 0; i < mb; ++i) B(i, i) -= 1.0;
733 for (std::size_t i = 0; i < m; ++i) A(m + i, i) -= 1.0;
734 }
735
736 const Blocks Ab = vblocks_of(A, m);
737
738 Matrix<double> Rpower = eye<double>(m);
739 Matrix<double> lsum = madd(Ab[0], Ab[1]);
740 for (std::size_t i = 2; i <= dega; ++i) {
741 lsum = madd(lsum, matmul(Rpower, Ab[i]));
742 Rpower = matmul(R, Rpower);
743 }
744
745 std::vector<double> leftr(m, 0.0);
746 if (degb >= 2 && dega >= 2) {
747 // leftr_part1 starts as the SCALAR a3 and is broadcast over the m x m
748 // accumulator only if the loop below runs at all.
749 const bool loop_runs = dega >= 3;
750 Matrix<double> part1(m, m, a3), part2(m, m, 0.0);
751 Rpower = R;
752 for (std::size_t i = 1; i + 2 <= dega; ++i) {
753 part1 = madd(part1, matmul(Rpower, Ab[i + 2]));
754 part2 = madd(part2, mscale(matmul(Rpower, Ab[i + 2]), static_cast<double>(i)));
755 Rpower = matmul(R, Rpower);
756 }
757 for (std::size_t i = 0; i < m; ++i) {
758 double s = 0.0;
759 if (loop_runs) {
760 for (std::size_t j = 0; j < m; ++j) s += part1(i, j) + part2(i, j);
761 } else {
762 s = a3; // scalar * ones(m,1), never expanded to a matrix
763 }
764 for (std::size_t j = 0; j < m; ++j) s -= Ab[0](i, j);
765 leftr[i] = s;
766 }
767 } else if (degb == 1 && dega != 1) {
768 Matrix<double> acc(m, m, 0.0);
769 Rpower = eye<double>(m);
770 for (std::size_t i = 2; i <= dega; ++i) {
771 acc = madd(acc, mscale(matmul(Rpower, Ab[i]), static_cast<double>(i - 1)));
772 Rpower = matmul(R, Rpower);
773 }
774 for (std::size_t i = 0; i < m; ++i) {
775 double s = 0.0;
776 for (std::size_t j = 0; j < m; ++j) s += acc(i, j) - Ab[0](i, j);
777 leftr[i] = s;
778 }
779 } else {
780 throw InputError(
781 "GIM1_qlen_ETAQA: the number of A blocks is not enough, this is a reducible Markov "
782 "chain");
783 }
784
785 Matrix<double> lsleft(m, m, 0.0);
786 for (std::size_t i = 0; i < m; ++i) {
787 for (std::size_t j = 0; j + 1 < m; ++j) lsleft(i, j) = lsum(i, j);
788 lsleft(i, m - 1) = leftr[i];
789 }
790
791 std::vector<std::vector<double>> r;
792 r.push_back(pistar);
793
794 std::vector<std::vector<double>> reuse; // rsidepart2reuse, one column per k
795
796 for (std::size_t k = 1; k <= n; ++k) {
797 const double dk = static_cast<double>(k);
798 std::vector<double> bk(m, 0.0);
799 {
800 const std::vector<double> t0 = vmul(pi0, B0);
801 const Matrix<double> M =
802 madd(mscale(Ab[1], std::pow(2.0, dk)), mscale(Ab[0], std::pow(3.0, dk)));
803 const std::vector<double> t1 = vmul(pi1, M);
804 for (std::size_t j = 0; j < m; ++j) bk[j] = -(std::pow(2.0, dk) * t0[j] + t1[j]);
805 for (std::size_t l = 1; l <= k; ++l) {
806 const Matrix<double> Ml =
807 madd(Ab[1], mscale(Ab[0], std::pow(2.0, static_cast<double>(l))));
808 const std::vector<double> t = vmul(r[k - l], Ml);
809 for (std::size_t j = 0; j < m; ++j) bk[j] -= bino(k, l) * t[j];
810 }
811 }
812
813 Matrix<double> tempsum(m, m, 0.0);
814 Rpower = R;
815 for (std::size_t i = 1; i + 2 <= dega; ++i) {
816 double t = 0.0;
817 for (std::size_t z = 1; z <= i; ++z) t += std::pow(static_cast<double>(z), dk);
818 tempsum = madd(tempsum, mscale(matmul(Rpower, Ab[i + 2]), t));
819 Rpower = matmul(Rpower, R);
820 }
821 std::vector<double> col(m, 0.0);
822 for (std::size_t i = 0; i < m; ++i) {
823 double s = 0.0;
824 for (std::size_t j = 0; j < m; ++j) s += Ab[0](i, j) - tempsum(i, j);
825 col[i] = s;
826 }
827 reuse.push_back(col);
828
829 double ck = std::pow(2.0, dk) * vmul_e(pi1, Ab[0]);
830 Matrix<double> tempsum2(m, mb, 0.0);
831 Rpower = R;
832 for (std::size_t i = 2; i <= degb; ++i) {
833 double t = 0.0;
834 for (std::size_t z = 2; z <= i; ++z) t += std::pow(static_cast<double>(z), dk);
835 tempsum2 = madd(tempsum2, mscale(matmul(Rpower, vblock_after(B, mb, m, i)), t));
836 Rpower = matmul(R, Rpower);
837 }
838 ck -= vmul_e(pi1, tempsum2);
839 for (std::size_t l = 1; l <= k; ++l) {
840 double s = 0.0;
841 for (std::size_t j = 0; j < m; ++j) s += r[k - l][j] * reuse[l - 1][j];
842 ck += bino(k, l) * s;
843 }
844
845 std::vector<double> rside(m, 0.0);
846 for (std::size_t j = 0; j + 1 < m; ++j) rside[j] = bk[j];
847 rside[m - 1] = ck;
848 r.push_back(right_divide(rside, lsleft));
849 }
850
851 return vsum(r.back()) + vsum(pi1);
852}
853
854} // namespace smc
855} // namespace line
856
857#endif // LINE_LIB_SMC_ETAQA_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
bool empty() const
Definition matrix.h:92
Matrix transpose() const
Definition matrix.h:110
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.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
The M/G/1-type and GI/M/1-type fundamental-matrix solvers of MAMSolver / SMCSolver,...
Blocks vblocks_of(const Matrix< double > &A, std::size_t r)
Splits a vertical stack into its blocks of r rows.
Definition mg1.h:192
Blocks blocks_of(const Matrix< double > &A, std::size_t m)
Splits the wide [A0 A1 ... Amax] into its m x m blocks.
Definition mg1.h:158
Matrix< T > msub(const Matrix< T > &A, const Matrix< T > &B)
A - B.
Definition mg1.h:94
double mg1_qlen_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const std::vector< double > &pi, std::size_t n, const Matrix< double > &C0in=Matrix< double >())
n-th moment of the level (the queue length) of an M/G/1-type chain from the ETAQA aggregates.
Definition etaqa.h:382
Matrix< double > mg1_g_etaqa(const Matrix< double > &A)
G of an M/G/1-type chain, uniformized first.
Definition etaqa.h:199
Matrix< double > gim1_r(const Blocks &Ain, const std::string &dual, const std::string &algor)
R of a GI/M/1-type Markov chain, through the G of its dual.
Definition mg1.h:838
double gim1_qlen_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &R, const std::vector< double > &pi, std::size_t n, const Matrix< double > &B0in=Matrix< double >())
n-th moment of the level of a GI/M/1-type chain from the ETAQA aggregates.
Definition etaqa.h:677
Matrix< double > mg1_cr(const Blocks &Ain, const Mg1CrOptions &opts=Mg1CrOptions())
Cyclic reduction for M/G/1-type Markov chains [Bini, Meini].
Definition mg1.h:575
std::vector< double > rowsums(const Matrix< double > &A)
sum(A,2), the row sums, as a column held in a vector.
Definition mg1.h:112
Matrix< double > gim1_r_etaqa(const Matrix< double > &A)
R of a GI/M/1-type chain, uniformized first.
Definition etaqa.h:531
Drift mg1_drift(const Blocks &A)
drift = theta * beta with beta = (Amax)e + (Amax+Amax-1)e + ..., the expected level increment per tra...
Definition mg1.h:254
std::vector< double > mg1_pi_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &G, const Matrix< double > &C0in=Matrix< double >())
Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of an M/G/1-type chain.
Definition etaqa.h:230
std::vector< Matrix< double > > Blocks
Definition mg1.h:75
Matrix< T > madd(const Matrix< T > &A, const Matrix< T > &B)
A + B.
Definition mg1.h:83
std::vector< double > gim1_pi_etaqa(const Matrix< double > &Bin, const Matrix< double > &Ain, const Matrix< double > &R, const Matrix< double > &B0in=Matrix< double >())
Aggregated stationary vector [pi0, pi1, pi2+pi3+...] of a GI/M/1-type chain.
Definition etaqa.h:559
Matrix< double > mscale(const Matrix< double > &A, double c)
c * A.
Definition mg1.h:104
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 > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
std::size_t matrix_rank(const Matrix< double > &A)
Numerical rank at the standard max(m,n) eps sigma_1 threshold.
Definition eig.h:329
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
The drift of an M/G/1-type sequence, and the invariant vector it uses.
Definition mg1.h:244
double value
Definition mg1.h:245