LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_procomom.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_PFQN_PROCOMOM_H
6#define LINE_API_PFQN_PROCOMOM_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * ProCoMoM: marginal queue-length probabilities of a closed multiclass
12 * product-form network by the class-oriented method of moments.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_procomom.m and
15 * matlab/src/api/pfqn/pfqn_procomom2.m.
16 *
17 * pfqn_procomom carries, instead of the plain moments of pfqn_comom, the
18 * generating coefficients of ONE station's marginal distribution: pk(:, j+1)
19 * holds the basis coefficients of P(n_station = j). The basis is the same
20 * Dn = multichoose(R,M) layout as pfqn_comom, with the reference's UP shifts
21 * (Dn + e_s) where pfqn_comom uses down shifts, and the station of interest is
22 * rotated into the last row of L so the two extra couplings, DC (same level,
23 * one job less at the station) and DD (previous level), always refer to row M.
24 * Every station is solved in turn and its distribution normalized to sum 1.
25 *
26 * pfqn_procomom2 is the two-node special case, a queue plus a delay, where the
27 * whole recursion collapses to a product of bidiagonal transfer matrices,
28 *
29 * F = prod_r T_r^{N_r} / N_r!, T_r(row,row) = Z_r,
30 * T_r(row,row+1) = (n + m - 1) L_r / mu(n),
31 *
32 * and the unnormalized marginal is F e_last. No linear system is solved at all.
33 *
34 * SYSTEM SHAPE. The ProCoMoM matrix is NOT square: countrows(r) contributes
35 * M rows per basis vector in the propagation branch but M + r - 1 (or 1) in the
36 * equation branch, so for M = R = 2 the class-1 step is 6 x 6 and the class-2
37 * step is 7 x 6. The reference solves it with an economy QR when full rank and
38 * with a truncated SVD when not. Both are done here by util/lstsq.h in exact
39 * arithmetic: the normal equations give the identical least-squares vector, and
40 * a full-rank factorization gives the identical minimum-norm vector.
41 *
42 * RANK-DEFICIENCY REMEDY, as in the reference: when some generating system is
43 * rank deficient the whole problem is re-solved with L and Z perturbed by
44 * delta*(1+rand) at delta = Lscale*10^{-10,-8,-6,-4}, keeping the first
45 * attempt that is full rank, non-negative to -1e-6 and row-normalized to
46 * within 0.01 (the last attempt otherwise). The draws follow MATLAB's
47 * rng(23000,'twister') stream (mt19937, genrand_res53, restarted at every
48 * attempt), so the perturbed systems are the ones MATLAB solves. `rankdef`
49 * reports the rank state of the solution finally returned.
50 *
51 * Arithmetic: EXACT-CAPABLE. Field operations and least-squares solves only.
52 *
53 * REFERENCE DEFECTS in pfqn_procomom2.m, all reproducible, none corrected in
54 * MATLAB by this port (it does not edit MATLAB):
55 *
56 * 1. `pfqn_procomom2(L,N,Z)` raises "Not enough input arguments". The
57 * nargin < 4 branch evaluates `ones(m, sum(N)+1)`, but `m` is assigned only
58 * by the nargin < 5 branch BELOW it. Reproduce with
59 * pfqn_procomom2([0.4 0.6],[2 1],[1 2]).
60 * 2. Debug scaffolding runs on EVERY call: `QN=double(Q)` and a full
61 * `pfqn_mvald(...)` are executed without semicolons, so the routine prints
62 * QN, XNMVA, QNMVA and pik to the console and pays for an O(prod(N+1))
63 * load-dependent MVA it does not use. Visible in the output of the call
64 * above.
65 * 3. Dead branch: `if any(~isfinite(pk(1,1))) || ~isfinite(G) ... elseif
66 * ~isfinite(G)` -- the elseif is subsumed by the first test and can never
67 * be taken.
68 *
69 * This port implements the transfer-matrix method only, with no printing and
70 * no MVA call, and accepts the three-argument form.
71 */
72
73#include <cmath>
74#include <cstddef>
75#include <random>
76#include <vector>
77
80#include "line/num/number.h"
81#include "line/util/error.h"
82#include "line/util/lstsq.h"
83#include "line/util/matrix.h"
84
85namespace line {
86namespace pfqn {
87
88/** Return value of pfqn_procomom, mirroring [Pr, Q]. */
89template <class T>
91 Matrix<T> Pr; ///< (M x sumN+1) marginals, Pr(k, j) = P(n_k = j)
92 std::vector<T> Q; ///< (M) mean queue lengths
93 bool rankdef; ///< true when some ProCoMoM system was rank deficient
94};
95
96/**
97 * Marginal queue-length distributions of every station.
98 *
99 * @param L (M x R) demand matrix
100 * @param N (R) populations
101 * @param Z (R) think times
102 * @param atol tolerance below which a class demand counts as zero
103 */
104template <class T>
105ProcomomResult<T> pfqn_procomom(const Matrix<T>& L, const std::vector<int>& N,
106 const std::vector<T>& Z, const T& atol) {
107 const std::size_t M = L.rows(), R = L.cols();
108 if (M == 0 || R == 0) throw InputError("pfqn_procomom: empty demand matrix");
109 if (N.size() != R) throw InputError("pfqn_procomom: L and N disagree on the class count");
110 if (Z.size() != R) throw InputError("pfqn_procomom: L and Z disagree on the class count");
111
112 const T zero = num_traits<T>::from_int(0);
113 const T one = num_traits<T>::from_int(1);
114 int sumN = 0;
115 for (std::size_t r = 0; r < R; ++r) {
116 if (N[r] < 0) throw InputError("pfqn_procomom: negative population");
117 sumN += N[r];
118 }
119 const std::size_t NP = static_cast<std::size_t>(sumN) + 1;
120
121 // ---- per-class rescaling; normalized marginals are invariant to it -------------
122 Matrix<T> Ls(M, R, zero);
123 std::vector<T> Zs(R, zero);
124 for (std::size_t r = 0; r < R; ++r) {
125 T mx = L(0, r);
126 for (std::size_t i = 1; i < M; ++i)
127 if (L(i, r) > mx) mx = L(i, r);
128 if (mx < atol) mx = one;
129 for (std::size_t i = 0; i < M; ++i) Ls(i, r) = L(i, r) / mx;
130 Zs[r] = Z[r] / mx;
131 }
132
133 const std::vector<std::vector<int>> Dn =
134 detail::comom_basis(static_cast<int>(R), static_cast<int>(M));
135 const std::size_t numDn = Dn.size();
136 const std::size_t basisSize = numDn * M;
137 const std::vector<int> zeroDn(R, 0);
138
139 // zero-based basis column of (dn, i) with i = 1..M
140 const auto phash = [&](const std::vector<int>& dn, std::size_t i) -> long {
141 const int pos = matchrow(Dn, dn);
142 if (pos < 0) return -1;
143 return static_cast<long>(pos) * static_cast<long>(M) + static_cast<long>(i) - 1;
144 };
145
147 res.Pr = Matrix<T>(M, NP, zero);
148 res.Q.assign(M, zero);
149 res.rankdef = false;
150
151 // solve_all: every station's marginal from the demands handed in; reports
152 // whether any ProCoMoM system was rank deficient.
153 const auto solve_all = [&](const Matrix<T>& Lin, const std::vector<T>& Zin, Matrix<T>& Pr) -> bool {
154 bool rankdef = false;
155 Pr = Matrix<T>(M, NP, zero);
156 for (std::size_t station = 0; station < M; ++station) {
157 // rotate the station of interest into the last row
158 Matrix<T> Lr = Lin;
159 for (std::size_t r = 0; r < R; ++r) {
160 const T tmp = Lr(station, r);
161 Lr(station, r) = Lr(M - 1, r);
162 Lr(M - 1, r) = tmp;
163 }
164
165 Matrix<T> pk(basisSize, NP, zero);
166 for (std::size_t kk = 1; kk <= M; ++kk) pk(static_cast<std::size_t>(phash(zeroDn, kk)), 0) = one;
167
168 std::vector<int> Ncur(R, 0);
169 for (std::size_t r = 0; r < R; ++r) {
170 for (int Nr = 1; Nr <= N[r]; ++Nr) {
171 Ncur[r] = Nr;
172 const Matrix<T> pklast = pk;
173 pk = Matrix<T>(basisSize, NP, zero);
174
175 // ---- genpmatrix ------------------------------------------------
176 std::size_t numRows = 0;
177 for (std::size_t d = 0; d < numDn; ++d) {
178 int s1 = 0;
179 if (r + 1 <= R - 1)
180 for (std::size_t c = r; c + 1 < R; ++c) s1 += Dn[d][c];
181 if (r + 1 <= R - 1 && s1 > 0) {
182 numRows += M;
183 } else {
184 int s3 = 0;
185 for (std::size_t c = 0; c <= r; ++c) s3 += Dn[d][c];
186 numRows += (s3 < static_cast<int>(M)) ? (M + r) : 1;
187 }
188 }
189 Matrix<T> Ag(numRows, basisSize, zero), Bg(numRows, basisSize, zero),
190 DCg(numRows, basisSize, zero), DDg(numRows, basisSize, zero);
191 std::size_t row = 0;
192 for (std::size_t d = 0; d < numDn; ++d) {
193 const std::vector<int>& dv = Dn[d];
194 int s1 = 0;
195 if (r + 1 <= R - 1)
196 for (std::size_t c = r; c + 1 < R; ++c) s1 += dv[c];
197 if (r + 1 <= R - 1 && s1 > 0) {
198 int s2 = 0;
199 for (std::size_t c = r + 1; c + 1 < R; ++c) s2 += dv[c];
200 for (std::size_t k = 1; k <= M; ++k) {
201 const long cA = phash(dv, k);
202 Ag(row, static_cast<std::size_t>(cA)) = one;
203 if (s2 > 0) {
204 Bg(row, static_cast<std::size_t>(cA)) = one;
205 } else {
206 std::vector<int> sh = dv;
207 sh[r] -= 1;
208 const long cB = phash(sh, k);
209 if (cB >= 0) Bg(row, static_cast<std::size_t>(cB)) = one;
210 }
211 ++row;
212 }
213 } else {
214 int s3 = 0;
215 for (std::size_t c = 0; c <= r; ++c) s3 += dv[c];
216 if (s3 < static_cast<int>(M)) {
217 for (std::size_t k = 1; k + 1 <= M; ++k) {
218 Ag(row, static_cast<std::size_t>(phash(dv, k + 1))) = one;
219 Ag(row, static_cast<std::size_t>(phash(dv, 1))) = -one;
220 for (std::size_t s = 0; s < r; ++s) {
221 std::vector<int> sh = dv;
222 sh[s] += 1; // UP shift
223 const long c = phash(sh, k + 1);
224 if (c >= 0) Ag(row, static_cast<std::size_t>(c)) = -Lr(k - 1, s);
225 }
226 Bg(row, static_cast<std::size_t>(phash(dv, k + 1))) = Lr(k - 1, r);
227 ++row;
228 }
229 for (std::size_t s = 0; s < r; ++s) {
230 Ag(row, static_cast<std::size_t>(phash(dv, 1))) =
231 num_traits<T>::from_int(Ncur[s] - dv[s]);
232 std::vector<int> sh = dv;
233 sh[s] += 1; // UP shift
234 const long cb = phash(sh, 1);
235 if (cb >= 0) {
236 Ag(row, static_cast<std::size_t>(cb)) = -Zin[s];
237 DCg(row, static_cast<std::size_t>(cb)) = Lr(M - 1, s);
238 }
239 for (std::size_t k = 1; k + 1 <= M; ++k) {
240 const long c = phash(sh, k + 1);
241 if (c >= 0) Ag(row, static_cast<std::size_t>(c)) = -Lr(k - 1, s);
242 }
243 ++row;
244 }
245 }
246 // extra population constraint of class r, always present
247 Ag(row, static_cast<std::size_t>(phash(dv, 1))) =
248 num_traits<T>::from_int(Ncur[r] - dv[r]);
249 Bg(row, static_cast<std::size_t>(phash(dv, 1))) = Zin[r];
250 for (std::size_t k = 1; k + 1 <= M; ++k)
251 Bg(row, static_cast<std::size_t>(phash(dv, k + 1))) = Lr(k - 1, r);
252 DDg(row, static_cast<std::size_t>(phash(dv, 1))) = Lr(M - 1, r);
253 ++row;
254 }
255 }
256 if (row != numRows) throw NumericError(ErrorKind{"Degenerate"}, "pfqn_procomom: row count mismatch");
257
258 // ---- level recursion -------------------------------------------
259 int sumNcur = 0;
260 for (int x : Ncur) sumNcur += x;
261 const T tol = line::detail::lstsq_tolerance(Ag);
262 for (int n = 0; n <= sumNcur; ++n) {
263 std::vector<T> rhs(numRows, zero);
264 for (std::size_t i = 0; i < numRows; ++i) {
265 T s = zero;
266 for (std::size_t j = 0; j < basisSize; ++j)
267 s += Bg(i, j) * pklast(j, static_cast<std::size_t>(n));
268 if (n >= 1) {
269 const T nn = num_traits<T>::from_int(n);
270 T sc = zero, sd = zero;
271 for (std::size_t j = 0; j < basisSize; ++j) {
272 sc += DCg(i, j) * pk(j, static_cast<std::size_t>(n) - 1);
273 sd += DDg(i, j) * pklast(j, static_cast<std::size_t>(n) - 1);
274 }
275 s += nn * sc + nn * sd;
276 }
277 rhs[i] = s;
278 }
279 const LstsqResult<T> sol = lstsq(Ag, rhs, tol);
280 if (sol.rankdef) rankdef = true;
281 for (std::size_t j = 0; j < basisSize; ++j)
282 pk(j, static_cast<std::size_t>(n)) = sol.x[j];
283 }
284 }
285 }
286
287 const std::size_t outRow = static_cast<std::size_t>(phash(zeroDn, 1));
288 T total = zero;
289 for (std::size_t j = 0; j < NP; ++j) total += pk(outRow, j);
290 if (total != zero)
291 for (std::size_t j = 0; j < NP; ++j) Pr(station, j) = pk(outRow, j) / total;
292 }
293 return rankdef;
294 };
295
296 res.rankdef = solve_all(Ls, Zs, res.Pr);
297 if (res.rankdef) {
298 // MATLAB: try progressively larger perturbations of L and Z, drawn from
299 // rng(23000,'twister'), and keep the first admissible solution (the last
300 // attempt otherwise). The draws reproduce MATLAB's mt19937ar stream:
301 // genrand_res53 on std::mt19937 seeded with 23000, rand(M,R) filled
302 // column-major, rand(1,R) after it, the stream restarted every attempt.
303 T Lscale = zero;
304 for (std::size_t i = 0; i < M; ++i)
305 for (std::size_t r = 0; r < R; ++r) {
306 const T a = Ls(i, r) < zero ? T(zero - Ls(i, r)) : Ls(i, r);
307 if (a > Lscale) Lscale = a;
308 }
309 if (Lscale < atol) Lscale = one;
310 const double dexp[4] = {-10.0, -8.0, -6.0, -4.0};
311 for (int k = 0; k < 4; ++k) {
312 const T delta = T(Lscale * num_traits<T>::from_double(std::pow(10.0, dexp[k])));
313 std::mt19937 mt(23000u);
314 const auto matlab_rand = [&mt]() -> double {
315 const double a = static_cast<double>(mt() >> 5);
316 const double b = static_cast<double>(mt() >> 6);
317 return (a * 67108864.0 + b) / 9007199254740992.0;
318 };
319 Matrix<T> Lp = Ls;
320 std::vector<T> Zp = Zs;
321 for (std::size_t r = 0; r < R; ++r)
322 for (std::size_t i = 0; i < M; ++i)
323 Lp(i, r) = T(Ls(i, r) + delta * num_traits<T>::from_double(1.0 + matlab_rand()));
324 for (std::size_t r = 0; r < R; ++r)
325 Zp[r] = T(Zs[r] + delta * num_traits<T>::from_double(1.0 + matlab_rand()));
326 Matrix<T> Ptry;
327 const bool rd = solve_all(Lp, Zp, Ptry);
328 bool admissible = !rd;
329 for (std::size_t i = 0; i < M && admissible; ++i) {
330 T rowsum = zero;
331 for (std::size_t j = 0; j < NP; ++j) {
332 if (Ptry(i, j) < num_traits<T>::from_double(-1e-6)) admissible = false;
333 rowsum += Ptry(i, j);
334 }
335 const T dev = T(rowsum - one);
336 const T adev = dev < zero ? T(zero - dev) : dev;
337 if (!(adev < num_traits<T>::from_double(0.01))) admissible = false;
338 }
339 res.Pr = Ptry;
340 res.rankdef = rd;
341 if (admissible) break;
342 }
343 }
344
345 for (std::size_t i = 0; i < M; ++i) {
346 T q = zero;
347 for (std::size_t j = 0; j < NP; ++j) q += num_traits<T>::from_int(static_cast<long>(j)) * res.Pr(i, j);
348 res.Q[i] = q;
349 }
350 return res;
351}
352
353/** Overload with the reference's default tolerance. */
354template <class T>
355ProcomomResult<T> pfqn_procomom(const Matrix<T>& L, const std::vector<int>& N,
356 const std::vector<T>& Z) {
357 return pfqn_procomom(L, N, Z, num_traits<T>::from_double(1e-14));
358}
359
360/** Return value of pfqn_procomom2, mirroring [pk, lG, G, T, F, B]. */
361template <class T>
363 std::vector<T> pk; ///< (sumN+1) marginal, pk[n] = P(n jobs at the queue)
364 T G; ///< normalizing constant
365 double lG; ///< its logarithm
366 std::vector<Matrix<T>> Tr; ///< per-class transfer matrices
367 Matrix<T> F; ///< prod_r Tr^{N_r} / N_r!
368 Matrix<T> B; ///< prod_r Tr
369};
370
371/**
372 * Queue-plus-delay marginal by the transfer-matrix form of ProCoMoM.
373 *
374 * @param L (R) demands at the single queueing station
375 * @param N (R) populations
376 * @param Z (R) think times
377 * @param mu (sumN) load-dependent rates of the queue, mu[n-1] with n jobs;
378 * empty for the load-independent default
379 * @param m multiplicity of the queueing station
380 */
381template <class T>
382Procomom2Result<T> pfqn_procomom2(const std::vector<T>& L, const std::vector<int>& N,
383 const std::vector<T>& Z, const std::vector<T>& mu, int m) {
384 const std::size_t R = L.size();
385 if (R == 0) throw InputError("pfqn_procomom2: empty demand vector");
386 if (N.size() != R) throw InputError("pfqn_procomom2: L and N disagree on the class count");
387 if (Z.size() != R) throw InputError("pfqn_procomom2: L and Z disagree on the class count");
388 if (m < 1) throw InputError("pfqn_procomom2: the multiplicity must be at least one");
389
390 const T zero = num_traits<T>::from_int(0);
391 const T one = num_traits<T>::from_int(1);
392 int sumN = 0;
393 for (std::size_t r = 0; r < R; ++r) {
394 if (N[r] < 0) throw InputError("pfqn_procomom2: negative population");
395 sumN += N[r];
396 }
397 const std::size_t dim = static_cast<std::size_t>(sumN) + 1;
398
399 // mu_[n] is the reference's mu(1+n): mu_[0] = 1, mu_[n] the rate with n jobs.
400 std::vector<T> mu_(dim, one);
401 if (!mu.empty()) {
402 if (mu.size() < static_cast<std::size_t>(sumN))
403 throw InputError("pfqn_procomom2: mu must supply a rate for every population up to sum(N)");
404 for (std::size_t n = 1; n < dim; ++n) {
405 if (mu[n - 1] <= zero)
406 throw InputError("pfqn_procomom2: the load-dependent rates must be positive");
407 mu_[n] = mu[n - 1];
408 }
409 }
410
412 res.Tr.reserve(R);
413 for (std::size_t r = 0; r < R; ++r) {
414 Matrix<T> Tm(dim, dim, zero);
415 for (int n = sumN; n >= 1; --n) {
416 const std::size_t row = static_cast<std::size_t>(sumN - n);
417 Tm(row, row) = Z[r];
418 Tm(row, row + 1) =
419 num_traits<T>::from_int(n + m - 1) * L[r] / mu_[static_cast<std::size_t>(n)];
420 }
421 Tm(dim - 1, dim - 1) = Z[r];
422 res.Tr.push_back(Tm);
423 }
424
425 const auto matmul = [&](const Matrix<T>& A, const Matrix<T>& Bm) {
426 Matrix<T> C(dim, dim, zero);
427 for (std::size_t i = 0; i < dim; ++i)
428 for (std::size_t k = 0; k < dim; ++k) {
429 if (A(i, k) == zero) continue;
430 for (std::size_t j = 0; j < dim; ++j) C(i, j) += A(i, k) * Bm(k, j);
431 }
432 return C;
433 };
434
435 Matrix<T> F(dim, dim, zero), B(dim, dim, zero);
436 for (std::size_t i = 0; i < dim; ++i) {
437 F(i, i) = one;
438 B(i, i) = one;
439 }
440 for (std::size_t r = 0; r < R; ++r) {
441 Matrix<T> P(dim, dim, zero);
442 for (std::size_t i = 0; i < dim; ++i) P(i, i) = one;
443 for (int e = 0; e < N[r]; ++e) P = matmul(P, res.Tr[r]);
444 const T fac = num_factorial<T>(static_cast<unsigned>(N[r]));
445 for (std::size_t i = 0; i < dim; ++i)
446 for (std::size_t j = 0; j < dim; ++j) P(i, j) /= fac;
447 F = matmul(F, P);
448 B = matmul(B, res.Tr[r]);
449 }
450 res.F = F;
451 res.B = B;
452
453 // pk = (F e_last)', reversed so that pk[n] = P(n jobs at the queue)
454 std::vector<T> v(dim, zero);
455 for (std::size_t i = 0; i < dim; ++i) v[i] = F(i, dim - 1);
456 T G = zero;
457 for (std::size_t i = 0; i < dim; ++i) G += v[i];
458 res.G = G;
460 res.pk.assign(dim, zero);
461 if (G != zero)
462 for (std::size_t i = 0; i < dim; ++i) res.pk[dim - 1 - i] = v[i] / G;
463 return res;
464}
465
466/** Overload with the load-independent, unit-multiplicity defaults. */
467template <class T>
468Procomom2Result<T> pfqn_procomom2(const std::vector<T>& L, const std::vector<int>& N,
469 const std::vector<T>& Z) {
470 return pfqn_procomom2(L, N, Z, std::vector<T>(), 1);
471}
472
473} // namespace pfqn
474} // namespace line
475
476#endif // LINE_API_PFQN_PROCOMOM_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.
Least squares for a rectangular system, exact-capable.
Dense matrix and non-owning view.
ProcomomResult< T > pfqn_procomom(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &atol)
Marginal queue-length distributions of every station.
int matchrow(const std::vector< std::vector< int > > &rows, const std::vector< int > &row)
Position of row in rows, or -1 when absent.
Procomom2Result< T > pfqn_procomom2(const std::vector< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< T > &mu, int m)
Queue-plus-delay marginal by the transfer-matrix form of ProCoMoM.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
LstsqResult< T > lstsq(const Matrix< T > &A, const std::vector< T > &b, const T &tol)
Least-squares solution of A x = b, minimum-norm when A is rank deficient.
Definition lstsq.h:152
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:196
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
Number-type abstraction for the templated API port.
Integer-composition enumeration shared by the CoMoM and MVAC ports.
CoMoM (class-oriented method of moments), the general basis formulation, and the original repairman-m...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Outcome of lstsq: the solution and whether the system was rank deficient.
Definition lstsq.h:58
std::vector< T > x
Definition lstsq.h:59
Return value of pfqn_procomom2, mirroring [pk, lG, G, T, F, B].
Matrix< T > B
prod_r Tr
std::vector< T > pk
(sumN+1) marginal, pk[n] = P(n jobs at the queue)
T G
normalizing constant
std::vector< Matrix< T > > Tr
per-class transfer matrices
Matrix< T > F
prod_r Tr^{N_r} / N_r!
Return value of pfqn_procomom, mirroring [Pr, Q].
std::vector< T > Q
(M) mean queue lengths
bool rankdef
true when some ProCoMoM system was rank deficient
Matrix< T > Pr
(M x sumN+1) marginals, Pr(k, j) = P(n_k = j)