LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
qbd_r.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_R_H
6#define LINE_API_MAM_QBD_R_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Quasi-birth-death processes: the rate matrix R, the fundamental matrix G,
12 * the caudal characteristic, and the stationary distribution.
13 *
14 * Templated port of matlab/src/api/mam/qbd_R.m, qbd_R_logred.m, qbd_fundmat.m,
15 * and of the boundary solve of matlab/lib/thirdparty/QBDfiles/QBD_pi.m and the
16 * caudal characteristic of QBD_Caudal.m. Cross-checked against
17 * jar/src/main/java/jline/api/mam/Qbd_R.java and Qbd_R_logred.java.
18 *
19 * Block convention, level-independent QBD in continuous time:
20 *
21 * Q = | Lbar F 0 0 ... |
22 * | B L F 0 ... |
23 * | 0 B L F ... |
24 * | ... |
25 *
26 * with B the backward (downward) block, L the local block and F the forward
27 * (upward) block. R is the minimal non-negative solution of
28 *
29 * F + R L + R^2 B = 0,
30 *
31 * which is the equation A0 + R A1 + R^2 A2 = 0 with A0 = F, A1 = L, A2 = B.
32 * G is the minimal non-negative solution of B + L G + F G^2 = 0.
33 *
34 * ARITHMETIC. Three routines here compute R or G by a fixed-point iteration
35 * driven to a tolerance -- successive substitution, logarithmic reduction and
36 * cyclic reduction -- and one (qbd_caudal) brackets a spectral radius. None of
37 * them terminates in a finite number of field operations, so the value they
38 * return is an approximation no matter how the arithmetic is carried out;
39 * running them at exact rational arithmetic would produce a rational number
40 * with a denominator doubling at every cyclic-reduction step and still not the
41 * exact R. They are therefore gated on num_traits<T>::has_transcendental,
42 * which admits double and Real<D> and rejects Rational at compile time.
43 *
44 * Everything downstream of R is a finite rational computation and is left
45 * un-gated: qbd_R_residual, qbd_pi (a null-vector solve plus a geometric tail)
46 * and the moment formulas in qbd_mapmap1.h all instantiate at Rational. That
47 * split is the point of the port -- given R to whatever accuracy, the boundary
48 * probabilities and the queue-length moments carry no additional error.
49 */
50
51#include <cstddef>
52#include <vector>
53
54#include "line/num/number.h"
55#include "line/util/error.h"
56#include "line/util/linalg.h"
57#include "line/util/lu.h"
58#include "line/util/matrix.h"
59
60namespace line {
61namespace mam {
62
63namespace qbd_detail {
64
65/** Elementwise A - B. */
66template <class T>
67Matrix<T> msub(const Matrix<T>& A, const Matrix<T>& B) {
68 if (A.rows() != B.rows() || A.cols() != B.cols()) throw InputError("qbd: shape mismatch");
69 Matrix<T> C(A.rows(), A.cols());
70 for (std::size_t i = 0; i < A.rows(); ++i)
71 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) - B(i, j);
72 return C;
73}
74
75/** Elementwise A + B. */
76template <class T>
77Matrix<T> madd(const Matrix<T>& A, const Matrix<T>& B) {
78 if (A.rows() != B.rows() || A.cols() != B.cols()) throw InputError("qbd: shape mismatch");
79 Matrix<T> C(A.rows(), A.cols());
80 for (std::size_t i = 0; i < A.rows(); ++i)
81 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) + B(i, j);
82 return C;
83}
84
85/** Elementwise s*A. */
86template <class T>
87Matrix<T> mscale(const Matrix<T>& A, const T& s) {
88 Matrix<T> C(A.rows(), A.cols());
89 for (std::size_t i = 0; i < A.rows(); ++i)
90 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = A(i, j) * s;
91 return C;
92}
93
94/** MATLAB's norm(X,1): the largest absolute column sum. */
95template <class T>
96T norm1(const Matrix<T>& A) {
97 T best = num_traits<T>::from_int(0);
98 for (std::size_t j = 0; j < A.cols(); ++j) {
99 T s = num_traits<T>::from_int(0);
100 for (std::size_t i = 0; i < A.rows(); ++i) s += num_abs(T(A(i, j)));
101 if (s > best) best = s;
102 }
103 return best;
104}
105
106/** MATLAB's norm(X,inf): the largest absolute row sum. */
107template <class T>
108T norminf(const Matrix<T>& A) {
109 T best = num_traits<T>::from_int(0);
110 for (std::size_t i = 0; i < A.rows(); ++i) {
111 T s = num_traits<T>::from_int(0);
112 for (std::size_t j = 0; j < A.cols(); ++j) s += num_abs(T(A(i, j)));
113 if (s > best) best = s;
114 }
115 return best;
116}
117
118/**
119 * Stationary row vector of M: solves x M = 0 with sum(x) = 1 by replacing the
120 * last column of M with ones and solving the transposed system, as MATLAB's
121 * statvec does. Unlike mc::ctmc_solve this does NOT recompute the diagonal of
122 * M: the level-zero block Lbar + R B is already a generator by construction,
123 * and repairing its diagonal would silently absorb an error in R instead of
124 * letting it show up in the residual.
125 */
126template <class T>
127std::vector<T> statvec(const Matrix<T>& M) {
128 const std::size_t m = M.rows();
129 if (M.cols() != m) throw InputError("qbd statvec: matrix is not square");
130 if (m == 0) throw InputError("qbd statvec: empty matrix");
131 if (m == 1) return std::vector<T>{num_traits<T>::from_int(1)};
132 const T one = num_traits<T>::from_int(1);
133 const T zero = num_traits<T>::from_int(0);
134 Matrix<T> A(m, m);
135 for (std::size_t i = 0; i < m; ++i)
136 for (std::size_t j = 0; j < m; ++j) A(i, j) = (i == m - 1) ? one : M(j, i);
137 std::vector<T> b(m, zero);
138 b[m - 1] = one;
139 return solve(A, b);
140}
141
142} // namespace qbd_detail
143
144/**
145 * Residual of the defining equation of R, ||F + R L + R^2 B||_inf.
146 *
147 * Exact in rational arithmetic, which is what makes it a usable oracle: a
148 * residual computed in the same double arithmetic that produced R can be small
149 * simply because the error cancels, whereas evaluating the same expression on
150 * the exact rationals of B, L, F and on the exact rational lift of R measures
151 * only how far R itself is from a solution.
152 */
153template <class T>
154T qbd_R_residual(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F, const Matrix<T>& R) {
155 using namespace qbd_detail;
156 const Matrix<T> res = madd(madd(F, matmul(R, L)), matmul(matmul(R, R), B));
157 return norminf(res);
158}
159
160/** Residual of the defining equation of G, ||B + L G + F G^2||_inf. */
161template <class T>
162T qbd_G_residual(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F, const Matrix<T>& G) {
163 using namespace qbd_detail;
164 const Matrix<T> res = madd(madd(B, matmul(L, G)), matmul(F, matmul(G, G)));
165 return norminf(res);
166}
167
168/**
169 * R by successive substitutions (qbd_R.m): iterate R <- -(F + R^2 B) L^-1.
170 *
171 * Linearly convergent, at the rate of the caudal characteristic, so it is slow
172 * near saturation; qbd_fundmat is quadratically convergent and is what
173 * qbd_mapmap1 uses. Kept because it is the MATLAB entry point of the same name
174 * and because its simplicity makes it a useful independent check on the
175 * cyclic-reduction result.
176 *
177 * @param tol stopping tolerance on ||R_k - R_{k+1}||_1 (MATLAB uses 1e-12)
178 * @param B backward (level down) block A_2
179 * @param L local (within level) block A_1
180 * @param F forward (level up) block A_0
181 * @param iter_max iteration cap
182 */
183template <class T>
184Matrix<T> qbd_R(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F, unsigned iter_max,
185 const T& tol) {
186 static_assert(num_traits<T>::has_transcendental, "qbd_R requires transcendental arithmetic");
187 using namespace qbd_detail;
188 const Matrix<T> Linv = inverse(L);
189 const Matrix<T> Fil = matmul(F, Linv);
190 const Matrix<T> BiL = matmul(B, Linv);
191 const Matrix<T> negFil = mscale(Fil, T(num_traits<T>::from_int(-1)));
192 Matrix<T> R = negFil;
193 Matrix<T> Rprime = msub(negFil, matmul(matmul(R, R), BiL));
194 for (unsigned it = 0; it < iter_max; ++it) {
195 R = Rprime;
196 Rprime = msub(negFil, matmul(matmul(R, R), BiL));
197 if (norm1(msub(R, Rprime)) <= tol) break;
198 }
199 return Rprime;
200}
201
202/** qbd_R with the MATLAB defaults, 100000 iterations and tolerance 1e-12. */
203template <class T>
204Matrix<T> qbd_R(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F) {
205 return qbd_R(B, L, F, 100000u, T(num_traits<T>::from_double(1e-12)));
206}
207
208/**
209 * R by logarithmic reduction (qbd_R_logred.m).
210 *
211 * Builds the matrix S of the taboo probabilities of ever going down, doubling
212 * the horizon at every step, then recovers R = -F (L + F S)^-1. Quadratically
213 * convergent; the stopping test is on how close S e is to e, i.e. on how much
214 * mass of the downward passage is still unaccounted for.
215 */
216template <class T>
217Matrix<T> qbd_R_logred(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F, unsigned iter_max,
218 const T& tol) {
220 "qbd_R_logred requires transcendental arithmetic");
221 using namespace qbd_detail;
222 const std::size_t r = L.rows();
223 const Matrix<T> Linv = inverse(L);
224 const T minus = num_traits<T>::from_int(-1);
225 Matrix<T> iLF = mscale(matmul(Linv, F), minus);
226 Matrix<T> iLB = mscale(matmul(Linv, B), minus);
227 Matrix<T> T_ = iLF;
228 Matrix<T> S = iLB;
229 const Matrix<T> I = eye<T>(r);
230 const std::vector<T> e = ones<T>(r);
231 for (unsigned it = 0; it < iter_max; ++it) {
232 const Matrix<T> D = madd(matmul(iLF, iLB), matmul(iLB, iLF));
233 const Matrix<T> Minv = inverse(msub(I, D));
234 const Matrix<T> iLFn = matmul(Minv, matmul(iLF, iLF));
235 const Matrix<T> iLBn = matmul(Minv, matmul(iLB, iLB));
236 iLF = iLFn;
237 iLB = iLBn;
238 S = madd(S, matmul(T_, iLB));
239 T_ = matmul(T_, iLF);
240 // ||e - S e||_1 over the column vector, i.e. the sum of absolute
241 // deficits, exactly as MATLAB's norm(ones - S*ones, 1).
242 const std::vector<T> Se = mulvec(S, e);
243 T dev = num_traits<T>::from_int(0);
244 for (std::size_t i = 0; i < r; ++i) dev += num_abs(T(e[i] - Se[i]));
245 // T_ e is the mass still unaccounted for, without the rounding floor of e - S e;
246 // iterating on once it vanishes squares iLB until overflow and poisons S with NaN
247 const std::vector<T> Te = mulvec(T_, e);
248 T left = num_traits<T>::from_int(0);
249 for (std::size_t i = 0; i < r; ++i) left += num_abs(Te[i]);
250 if (dev <= tol || left <= tol) break;
251 }
252 const Matrix<T> U = madd(L, matmul(F, S));
253 return mscale(matmul(F, inverse(U)), minus);
254}
255
256/** qbd_R_logred with the MATLAB defaults, 100000 iterations and tolerance 1e-12. */
257template <class T>
258Matrix<T> qbd_R_logred(const Matrix<T>& B, const Matrix<T>& L, const Matrix<T>& F) {
259 return qbd_R_logred(B, L, F, 100000u, T(num_traits<T>::from_double(1e-12)));
260}
261
262/** G and R together, as returned by qbd_fundmat. */
263template <class T>
269
270/**
271 * G and R by cyclic reduction (qbd_fundmat.m, the Bini-Meini logarithmic
272 * reduction on the raw level blocks).
273 *
274 * The blocks are first uniformized by lambda = max(-diag(L)) into a discrete
275 * QBD (Bm, Lm, Fm), G is accumulated over doubling horizons, and R is
276 * recovered as R = Fm (I - (Lm + Fm G))^-1. The uniformization does not change
277 * either G or R: G is a probability matrix of the embedded jump chain, and the
278 * R of the discrete chain solves R = Fm + R Lm + R^2 Bm, which is F + R L +
279 * R^2 B = 0 after multiplying through by lambda.
280 *
281 * Quadratically convergent, so 50 iterations is a generous bound even at
282 * utilizations where successive substitution needs millions.
283 */
284template <class T>
286 unsigned iter_max, const T& tol) {
288 "qbd_fundmat requires transcendental arithmetic");
289 using namespace qbd_detail;
290 const std::size_t m = L.rows();
291 if (L.cols() != m || B.rows() != m || B.cols() != m || F.rows() != m || F.cols() != m)
292 throw InputError("qbd_fundmat: B, L and F must be square and of equal order");
293 const Matrix<T> I = eye<T>(m);
294
295 T lamb = num_traits<T>::from_int(0);
296 for (std::size_t i = 0; i < m; ++i) {
297 const T d = -L(i, i);
298 if (d > lamb) lamb = d;
299 }
300 if (lamb <= num_traits<T>::from_int(0))
301 throw NumericError(ErrorKind{"Degenerate"}, "qbd_fundmat: the local block has no negative diagonal entry");
302 const T inv_lamb = num_traits<T>::from_int(1) / lamb;
303 const Matrix<T> Bm = mscale(B, inv_lamb);
304 const Matrix<T> Lm = madd(mscale(L, inv_lamb), I);
305 const Matrix<T> Fm = mscale(F, inv_lamb);
306
307 Matrix<T> BF = inverse(msub(I, Lm));
308 Matrix<T> BB = matmul(BF, Fm);
309 BF = matmul(BF, Bm);
310 Matrix<T> G = BF;
311 Matrix<T> PI = BB;
312 T check = num_traits<T>::from_int(1);
313 unsigned numit = 0;
314 while (check > tol && numit < iter_max) {
315 const Matrix<T> Lstar = madd(matmul(BF, BB), matmul(BB, BF));
316 const Matrix<T> Bstar = matmul(BB, BB);
317 const Matrix<T> Fstar = matmul(BF, BF);
318 const Matrix<T> Minv = inverse(msub(I, Lstar));
319 BF = matmul(Minv, Fstar);
320 BB = matmul(Minv, Bstar);
321 G = madd(G, matmul(PI, BF));
322 PI = matmul(PI, BB);
323 const T nb = norminf(BB);
324 const T nf = norminf(BF);
325 check = nb < nf ? nb : nf;
326 ++numit;
327 }
328 QbdFundMat<T> out;
329 out.G = G;
330 out.R = matmul(Fm, inverse(msub(I, madd(Lm, matmul(Fm, G)))));
331 out.iterations = numit;
332 return out;
333}
334
335/** qbd_fundmat with the MATLAB defaults, 50 iterations and tolerance 1e-14. */
336template <class T>
338 return qbd_fundmat(B, L, F, 50u, T(num_traits<T>::from_double(1e-14)));
339}
340
341/**
342 * Caudal characteristic eta = sp(R), the decay rate of the queue-length tail.
343 *
344 * R is entrywise non-negative, so its spectral radius is its Perron root and
345 * is bracketed by the Collatz-Wielandt bounds
346 *
347 * min_i (R x)_i / x_i <= sp(R) <= max_i (R x)_i / x_i
348 *
349 * for any strictly positive x. Power iteration is run on I + R, which is
350 * aperiodic whenever R is irreducible and keeps the iterate strictly positive,
351 * and the midpoint of the bracket is returned once it is tighter than tol.
352 *
353 * This is the same quantity as MATLAB's QBD_Caudal, which brackets it by
354 * bisecting on the dominant eigenvalue of A(eta) = B + L eta + F eta^2, and as
355 * the JAR's spectralRadiusMapmap1, which takes the largest eigenvalue modulus
356 * of R from a full eigendecomposition. The bracket form is preferred here
357 * because it needs no eigensolver and reports its own accuracy.
358 */
359template <class T>
360T qbd_caudal(const Matrix<T>& R, unsigned iter_max, const T& tol) {
362 "qbd_caudal requires transcendental arithmetic");
363 const std::size_t n = R.rows();
364 if (R.cols() != n) throw InputError("qbd_caudal: matrix is not square");
365 if (n == 0) throw InputError("qbd_caudal: empty matrix");
366 const T zero = num_traits<T>::from_int(0);
367 std::vector<T> x = ones<T>(n);
368 T lo = zero, hi = zero;
369 for (unsigned it = 0; it < iter_max; ++it) {
370 const std::vector<T> Rx = mulvec(R, x);
371 lo = Rx[0] / x[0];
372 hi = lo;
373 for (std::size_t i = 1; i < n; ++i) {
374 const T q = Rx[i] / x[i];
375 if (q < lo) lo = q;
376 if (q > hi) hi = q;
377 }
378 if (T(hi - lo) <= tol) break;
379 // Advance with I + R: sp(I+R) = 1 + sp(R) and the iterate stays > 0.
380 T s = zero;
381 for (std::size_t i = 0; i < n; ++i) {
382 x[i] += Rx[i];
383 s += x[i];
384 }
385 if (s == zero) throw NumericError(ErrorKind{"Degenerate"}, "qbd_caudal: iterate collapsed to zero");
386 for (std::size_t i = 0; i < n; ++i) x[i] /= s;
387 }
388 return T((lo + hi) / num_traits<T>::from_int(2));
389}
390
391/** qbd_caudal with 10000 iterations and tolerance 1e-14. */
392template <class T>
394 return qbd_caudal(R, 10000u, T(num_traits<T>::from_double(1e-14)));
395}
396
397/**
398 * Stationary distribution of a QBD given R (QBD_pi.m, continuous-time branch,
399 * default boundary).
400 *
401 * The level-zero vector solves pi_0 (Lbar + R B) = 0 normalized by
402 * pi_0 (I - R)^-1 e = 1, and pi_{k+1} = pi_k R. Levels are generated until the
403 * accumulated mass exceeds 1 - mass_tol or max_levels is reached.
404 *
405 * Un-gated: given R this is a linear solve, a matrix inverse and a geometric
406 * recursion, all finite sequences of field operations. At Rational the level
407 * probabilities are the exact ones implied by the R that was supplied.
408 *
409 * @param B backward block (QBD_pi's B0)
410 * @param Lbar level-zero local block (QBD_pi's B1)
411 * @param R the rate matrix of the QBD
412 * @param max_levels level truncation used for the returned distribution
413 * @param mass_tol probability mass left above the truncation that is tolerated
414 * @return (levels x m) matrix, row k holding pi_k
415 */
416template <class T>
417Matrix<T> qbd_pi(const Matrix<T>& B, const Matrix<T>& Lbar, const Matrix<T>& R,
418 std::size_t max_levels, const T& mass_tol) {
419 using namespace qbd_detail;
420 const std::size_t m = R.rows();
421 if (R.cols() != m) throw InputError("qbd_pi: R is not square");
422 if (Lbar.rows() != m || Lbar.cols() != m || B.rows() != m || B.cols() != m)
423 throw InputError("qbd_pi: block orders disagree with R");
424 if (max_levels == 0) throw InputError("qbd_pi: max_levels must be positive");
425 const T one = num_traits<T>::from_int(1);
426 const T zero = num_traits<T>::from_int(0);
427
428 const Matrix<T> ImR = msub(eye<T>(m), R);
429 const Matrix<T> ImRinv = inverse(ImR);
430
431 std::vector<T> pi0 = statvec(madd(Lbar, matmul(R, B)));
432 // Normalize so that the whole chain has unit mass: sum_k pi_0 R^k e = 1.
433 const std::vector<T> t = vecmul(pi0, ImRinv);
434 T tot = zero;
435 for (const T& v : t) tot += v;
436 if (tot == zero) throw NumericError(ErrorKind{"Degenerate"}, "qbd_pi: degenerate normalization, sp(R) may exceed 1");
437 for (T& v : pi0) v /= tot;
438
439 std::vector<std::vector<T>> levels;
440 levels.push_back(pi0);
441 T mass = zero;
442 for (const T& v : pi0) mass += v;
443 while (levels.size() < max_levels && T(one - mass) > mass_tol) {
444 const std::vector<T> nxt = vecmul(levels.back(), R);
445 T add = zero;
446 for (const T& v : nxt) add += v;
447 levels.push_back(nxt);
448 mass += add;
449 }
450 Matrix<T> out(levels.size(), m);
451 for (std::size_t k = 0; k < levels.size(); ++k)
452 for (std::size_t j = 0; j < m; ++j) out(k, j) = levels[k][j];
453 return out;
454}
455
456/** qbd_pi with the MATLAB-side defaults, 20000 levels and mass tolerance 1e-10. */
457template <class T>
458Matrix<T> qbd_pi(const Matrix<T>& B, const Matrix<T>& Lbar, const Matrix<T>& R) {
459 return qbd_pi(B, Lbar, R, static_cast<std::size_t>(20000),
461}
462
463} // namespace mam
464} // namespace line
465
466#endif // LINE_API_MAM_QBD_R_H
InputError(const std::string &what)
Definition error.h:105
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:112
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.
T qbd_G_residual(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, const Matrix< T > &G)
Residual of the defining equation of G, ||B + L G + F G^2||_inf.
Definition qbd_r.h:162
T qbd_caudal(const Matrix< T > &R, unsigned iter_max, const T &tol)
Caudal characteristic eta = sp(R), the decay rate of the queue-length tail.
Definition qbd_r.h:360
QbdFundMat< T > qbd_fundmat(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
G and R by cyclic reduction (qbd_fundmat.m, the Bini-Meini logarithmic reduction on the raw level blo...
Definition qbd_r.h:285
Matrix< T > qbd_R_logred(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by logarithmic reduction (qbd_R_logred.m).
Definition qbd_r.h:217
Matrix< T > qbd_R(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, unsigned iter_max, const T &tol)
R by successive substitutions (qbd_R.m): iterate R <- -(F + R^2 B) L^-1.
Definition qbd_r.h:184
Matrix< T > qbd_pi(const Matrix< T > &B, const Matrix< T > &Lbar, const Matrix< T > &R, std::size_t max_levels, const T &mass_tol)
Stationary distribution of a QBD given R (QBD_pi.m, continuous-time branch, default boundary).
Definition qbd_r.h:417
T qbd_R_residual(const Matrix< T > &B, const Matrix< T > &L, const Matrix< T > &F, const Matrix< T > &R)
Residual of the defining equation of R, ||F + R L + R^2 B||_inf.
Definition qbd_r.h:154
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:184
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
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
G and R together, as returned by qbd_fundmat.
Definition qbd_r.h:264
Matrix< T > G
Definition qbd_r.h:265
Matrix< T > R
Definition qbd_r.h:266
unsigned iterations
Definition qbd_r.h:267