LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_mvaoi.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_MVAOI_H
6#define LINE_API_PFQN_MVAOI_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Mean-value analysis of a closed network with order-independent (OI) stations,
12 * the composition-dependent generalization of Conditional MVA.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_mvaoi.m. The network is an
15 * aggregated delay node, any number of load-independent single-server queues,
16 * and any number of OI / pass-and-swap stations with empty swap graph. It
17 * returns the same exact throughputs and queue lengths as pfqn_ncoi WITHOUT
18 * forming any normalizing constant or joint marginal.
19 *
20 * The recursion carries, per OI station i, the shift vector s_i, the OI
21 * occupancy already committed at the bottom of that station; S is the K x R
22 * matrix of those rows and Nn = N - sum_i s_i the jobs still free. For a single
23 * OI station and no LI queue,
24 *
25 * Q^{(S)}(Nn) = sum_r U_r^{(S)}(Nn) ( e_r + Q^{(S + e_r@@i)}(Nn - e_r) )
26 * U_r^{(S)}(Nn) = D_r^{(S)}(Nn) X_r^{(S)}(Nn)
27 * D_r^{(S)}(Nn) = (1/mu_i(s_i + e_r)) rho^{(S)}_{i,r}(Nn - e_r), Nn_r = 1
28 * D_r^{(S)}(Nn) = [X_r^{(S)}/X_r^{(S+e_r@@i)}](Nn - e_r) D_r^{(S)}(Nn-e_r), Nn_r >= 2
29 * rho^{(S)}_{i,r}(M) = rho^{(S)}_{i,r}(M - e_s) X_s^{(S)}(M)/X_s^{(S+e_r@@i)}(M)
30 *
31 * with each OI station keeping its own D, rho and Q driven by the common
32 * throughput, and the population-conservation identity aggregating every
33 * station. States (S, Nn) are processed by increasing sum(Nn), so every
34 * reference lands at a strictly smaller free population.
35 *
36 * THE THROUGHPUT CLOSURE IS NOT A LINEAR SOLVE, and that is deliberate in the
37 * reference. The OI queue recurrence couples X_s (s != r) through the
38 * off-diagonal of A, so A X = Nn is not a per-class Little's-law ratio as in
39 * canonical CMVA. Rather than solving the system, the reference decouples it
40 * with the product-form throughput-ratio identity at fixed shift,
41 * X_s(Nn)/X_r(Nn) = X_s(Nn-e_r)/X_r(Nn-e_s), giving a scalar per-class formula
42 * fed by one-job-less throughputs already in the cache. The port keeps that
43 * form; substituting a linear solve would change the numbers.
44 *
45 * Soi, the mean number of IN-SERVICE jobs per class, is the only output that is
46 * not a pure mean-value quantity. It is a distributional statistic and comes
47 * from the OI count marginal
48 * pM_i(n|k) = (1/mu_i(n)) sum_r X_r(k) pM_i(n - e_r | k - e_r),
49 * pM_i(0|k) = 1 - sum_{n != 0} pM_i(n|k),
50 * assembled from the zero-shift throughputs the mean-value recursion has
51 * already cached, weighted by pfqn_oi_insvc's E[sir_r | n]. Still no
52 * normalizing constant. It is computed only when `want_soi` is set, mirroring
53 * the reference's nargout >= 5 guard.
54 *
55 * Arithmetic: EXACT-CAPABLE. Additions, multiplications, divisions and
56 * comparisons in the field of the inputs; no logarithm, no tolerance, no
57 * iteration to convergence. The OI rate handle must return a T.
58 *
59 * REFERENCE DEFECTS: none found. Agreement with pfqn_mvaoi_marg, which reaches
60 * the same numbers by an entirely different (marginal-distribution, iterated)
61 * route, is to ~2e-14 on every model tried, the gap being that routine's
62 * fixed-point tolerance rather than a disagreement.
63 */
64
65#include <algorithm>
66#include <cstddef>
67#include <functional>
68#include <map>
69#include <vector>
70
73#include "line/num/number.h"
74#include "line/util/error.h"
75#include "line/util/matrix.h"
76
77namespace line {
78namespace pfqn {
79
80/** Return value of pfqn_mvaoi, mirroring [X, Qoi, Qli, Qdelay, Soi]. */
81template <class T>
83 std::vector<T> X; ///< (R) per-class throughput
84 Matrix<T> Qoi; ///< (K x R) per-class queue length at each OI station
85 Matrix<T> Qli; ///< (J x R) per-class queue length at each LI queue
86 std::vector<T> Qdelay; ///< (R) per-class queue length at the delay node
87 Matrix<T> Soi; ///< (K x R) per-class mean in-service jobs, if requested
88};
89
90namespace detail {
91
92/** Lattice key for a state (S, Nn); S is K x R row-major, Nn is R. */
93inline std::vector<int> mvaoi_key(const Matrix<int>& S, const std::vector<int>& Nn) {
94 std::vector<int> key;
95 key.reserve(S.rows() * S.cols() + Nn.size());
96 for (std::size_t i = 0; i < S.rows(); ++i)
97 for (std::size_t r = 0; r < S.cols(); ++r) key.push_back(S(i, r));
98 key.insert(key.end(), Nn.begin(), Nn.end());
99 return key;
100}
101
102} // namespace detail
103
104/**
105 * @brief Mean-value analysis of a closed network with order-independent (OI)
106 * stations, the composition-dependent generalization of Conditional
107 * MVA.
108 *
109 * @param Z (R) think-time demands of the aggregated delay node
110 * @param N (R) closed populations
111 * @param mu (K) OI rate handles; mu[i](n) is the total rate of station i
112 * at the per-class occupancy n
113 * @param Dli (J x R) demands of the load-independent single-server queues
114 * @param visits (K x R) per-OI-station class visit ratios v_{i,r}; they enter the
115 * class-r demand base case theta_{i,r}(N_r=1) = v_{i,r}/mu_i(...),
116 * the N_r >= 2 ratio case cancelling them. Empty for unit visits;
117 * ms-promoted stations pass ones, their visits already folded into
118 * the rate handle by the caller
119 * @param want_soi compute Soi (the reference's nargout >= 5 branch)
120 */
121template <class T>
122MvaoiResult<T> pfqn_mvaoi(const std::vector<T>& Z, const std::vector<int>& N,
123 const std::vector<std::function<T(const std::vector<int>&)>>& mu,
124 const Matrix<T>& Dli, const Matrix<T>& visits, bool want_soi) {
125 const std::size_t R = N.size();
126 if (R == 0) throw InputError("pfqn_mvaoi: empty population vector");
127 if (Z.size() != R) throw InputError("pfqn_mvaoi: Z and N disagree on the class count");
128 if (mu.empty()) throw InputError("pfqn_mvaoi: mu must be a nonempty list of OI rate handles");
129 const std::size_t K = mu.size();
130 for (std::size_t i = 0; i < K; ++i)
131 if (!mu[i]) throw InputError("pfqn_mvaoi: each mu[i] must be callable");
132 const std::size_t J = Dli.empty() ? 0 : Dli.rows();
133 if (J > 0 && Dli.cols() != R)
134 throw InputError("pfqn_mvaoi: Dli and N disagree on the class count");
135 if (!visits.empty() && (visits.rows() != K || visits.cols() != R))
136 throw InputError("pfqn_mvaoi: visits must be K x R");
137 for (std::size_t r = 0; r < R; ++r)
138 if (N[r] < 0) throw InputError("pfqn_mvaoi: pfqn_mvaoi requires finite closed populations");
139
140 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
141
142 std::map<std::vector<int>, std::vector<T>> Xc; // X^{(S)}(Nn)
143 std::map<std::vector<int>, Matrix<T>> Qlc; // Qli^{(S)}(Nn)
144 std::vector<std::map<std::vector<int>, std::vector<T>>> Dc(K), Qc(K);
145 std::vector<std::map<std::vector<int>, std::vector<std::pair<bool, T>>>> Rc(K);
146
147 const Matrix<int> zeroS(K, R, 0);
148
149 // (S,Nn) composition rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
150 std::vector<std::vector<std::vector<int>>> comps(R);
151 for (std::size_t r = 0; r < R; ++r)
152 comps[r] = multichoose_rows(static_cast<int>(K) + 2, N[r]);
153 std::vector<std::pair<int, std::pair<Matrix<int>, std::vector<int>>>> states;
154 {
155 std::vector<std::size_t> idx(R, 0);
156 bool more = true;
157 while (more) {
158 Matrix<int> S(K, R, 0);
159 std::vector<int> Nn(R, 0);
160 int tot = 0;
161 for (std::size_t r = 0; r < R; ++r) {
162 const std::vector<int>& c = comps[r][idx[r]];
163 for (std::size_t i = 0; i < K; ++i) S(i, r) = c[i];
164 Nn[r] = c[K];
165 tot += Nn[r];
166 }
167 states.push_back(std::make_pair(tot, std::make_pair(S, Nn)));
168 std::size_t d = 0;
169 for (; d < R; ++d) {
170 if (++idx[d] < comps[d].size()) break;
171 idx[d] = 0;
172 }
173 more = (d < R);
174 }
175 }
176 std::stable_sort(states.begin(), states.end(),
177 [](const std::pair<int, std::pair<Matrix<int>, std::vector<int>>>& x,
178 const std::pair<int, std::pair<Matrix<int>, std::vector<int>>>& y) {
179 return x.first < y.first;
180 });
181
182 // rho^{(S)}_{i,r}(M), memoized per OI station
183 std::function<T(std::size_t, std::size_t, const Matrix<int>&, const std::vector<int>&)> rho_fn =
184 [&](std::size_t i, std::size_t r, const Matrix<int>& S, const std::vector<int>& Mv) -> T {
185 const std::vector<int> rkey = detail::mvaoi_key(S, Mv);
186 auto it = Rc[i].find(rkey);
187 if (it != Rc[i].end() && it->second[r].first) return it->second[r].second;
188 int tot = 0;
189 for (int x : Mv) tot += x;
190 if (tot == 0) {
191 if (it == Rc[i].end())
192 it = Rc[i].insert(std::make_pair(rkey, std::vector<std::pair<bool, T>>(
193 R, std::make_pair(false, zero))))
194 .first;
195 it->second[r] = std::make_pair(true, one);
196 return one;
197 }
198 std::size_t rs = R;
199 for (std::size_t t = 0; t < R; ++t)
200 if (t != r && Mv[t] > 0) {
201 rs = t;
202 break;
203 }
204 if (rs == R) throw NumericError("pfqn_mvaoi: rho recursion has no class to decrement");
205 const std::vector<T>& xu = Xc.at(detail::mvaoi_key(S, Mv));
206 Matrix<int> Sp = S;
207 Sp(i, r) += 1;
208 const std::vector<T>& xu2 = Xc.at(detail::mvaoi_key(Sp, Mv));
209 T ratio = zero;
210 if (xu2[rs] > zero) ratio = xu[rs] / xu2[rs];
211 std::vector<int> Mm = Mv;
212 Mm[rs] -= 1;
213 const T v = rho_fn(i, r, S, Mm) * ratio;
214 it = Rc[i].find(rkey);
215 if (it == Rc[i].end())
216 it = Rc[i].insert(std::make_pair(
217 rkey, std::vector<std::pair<bool, T>>(R, std::make_pair(false, zero))))
218 .first;
219 it->second[r] = std::make_pair(true, v);
220 return v;
221 };
222
223 for (std::size_t p = 0; p < states.size(); ++p) {
224 const Matrix<int>& S = states[p].second.first;
225 const std::vector<int>& Nn = states[p].second.second;
226 const std::vector<int> key = detail::mvaoi_key(S, Nn);
227 if (states[p].first == 0) {
228 // No free jobs. Stored for EVERY shift S, not only S = 0, because
229 // Qsub references (S + e_s@@i, Nn - e_s) and reaches these.
230 Xc[key] = std::vector<T>(R, zero);
231 Qlc[key] = Matrix<T>(J, R, zero);
232 for (std::size_t i = 0; i < K; ++i) {
233 Dc[i][key] = std::vector<T>(R, zero);
234 Qc[i][key] = std::vector<T>(R, zero);
235 }
236 continue;
237 }
238
239 Matrix<T> Dt(K, R, zero);
240 std::vector<Matrix<T>> Qsub(K, Matrix<T>(R, R, zero));
241 for (std::size_t i = 0; i < K; ++i) {
242 for (std::size_t r = 0; r < R; ++r) {
243 if (Nn[r] == 0) continue;
244 std::vector<int> Nr = Nn;
245 Nr[r] -= 1;
246 if (Nn[r] == 1) {
247 std::vector<int> occ(R, 0);
248 for (std::size_t t = 0; t < R; ++t) occ[t] = S(i, t);
249 occ[r] += 1;
250 const T mur = mu[i](occ);
251 const T vir = visits.empty() ? one : visits(i, r);
252 if (mur > zero) Dt(i, r) = (vir / mur) * rho_fn(i, r, S, Nr);
253 } else {
254 Matrix<int> Sp = S;
255 Sp(i, r) += 1;
256 const std::vector<T>& xs = Xc.at(detail::mvaoi_key(S, Nr));
257 const std::vector<T>& xs2 = Xc.at(detail::mvaoi_key(Sp, Nr));
258 if (xs2[r] > zero) {
259 const std::vector<T>& Dprev = Dc[i].at(detail::mvaoi_key(S, Nr));
260 Dt(i, r) = (xs[r] / xs2[r]) * Dprev[r];
261 }
262 }
263 }
264 for (std::size_t s = 0; s < R; ++s) {
265 if (Nn[s] == 0) continue;
266 Matrix<int> Ss = S;
267 Ss(i, s) += 1;
268 std::vector<int> Ns = Nn;
269 Ns[s] -= 1;
270 const std::vector<T>& q = Qc[i].at(detail::mvaoi_key(Ss, Ns));
271 for (std::size_t t = 0; t < R; ++t) Qsub[i](s, t) = q[t];
272 }
273 }
274
275 // LI-queue arrival-theorem coefficients
276 Matrix<T> betaLI(J, R, zero);
277 for (std::size_t r = 0; r < R; ++r) {
278 if (Nn[r] == 0) continue;
279 std::vector<int> Nr = Nn;
280 Nr[r] -= 1;
281 const Matrix<T>& Qli_prev = Qlc.at(detail::mvaoi_key(S, Nr));
282 for (std::size_t j = 0; j < J; ++j) {
283 T s = zero;
284 for (std::size_t t = 0; t < R; ++t) s += Qli_prev(j, t);
285 betaLI(j, r) = Dli(j, r) * (one + s);
286 }
287 }
288
289 // population conservation A X = Nn over the classes with Nn_r > 0
290 std::vector<std::size_t> idx;
291 for (std::size_t r = 0; r < R; ++r)
292 if (Nn[r] > 0) idx.push_back(r);
293 const std::size_t m = idx.size();
294 Matrix<T> A(m, m, zero);
295 for (std::size_t a = 0; a < m; ++a) {
296 const std::size_t r = idx[a];
297 for (std::size_t b = 0; b < m; ++b) {
298 const std::size_t s = idx[b];
299 T val = zero;
300 if (s == r) {
301 val = Z[r];
302 for (std::size_t j = 0; j < J; ++j) val += betaLI(j, r);
303 for (std::size_t i = 0; i < K; ++i) val += Dt(i, r) * (one + Qsub[i](r, r));
304 } else {
305 for (std::size_t i = 0; i < K; ++i) val += Dt(i, s) * Qsub[i](s, r);
306 }
307 A(a, b) = val;
308 }
309 }
310
311 std::vector<T> Xk(R, zero);
312 for (std::size_t a = 0; a < m; ++a) {
313 const std::size_t r = idx[a];
314 T denom = A(a, a);
315 std::vector<int> Nr = Nn;
316 Nr[r] -= 1;
317 const std::vector<T>& Xner = Xc.at(detail::mvaoi_key(S, Nr));
318 for (std::size_t b = 0; b < m; ++b) {
319 if (b == a) continue;
320 const std::size_t s = idx[b];
321 std::vector<int> Ns = Nn;
322 Ns[s] -= 1;
323 const std::vector<T>& Xnes = Xc.at(detail::mvaoi_key(S, Ns));
324 if (Xnes[r] > zero) denom += A(a, b) * (Xner[s] / Xnes[r]);
325 }
326 if (denom > zero) Xk[r] = num_traits<T>::from_int(Nn[r]) / denom;
327 }
328
329 Matrix<T> Qk_li(J, R, zero);
330 for (std::size_t r = 0; r < R; ++r) {
331 if (Nn[r] == 0) continue;
332 for (std::size_t j = 0; j < J; ++j) Qk_li(j, r) = Xk[r] * betaLI(j, r);
333 }
334 for (std::size_t i = 0; i < K; ++i) {
335 std::vector<T> U(R, zero), Qi(R, zero), Drow(R, zero);
336 for (std::size_t r = 0; r < R; ++r) {
337 Drow[r] = Dt(i, r);
338 U[r] = Dt(i, r) * Xk[r];
339 }
340 for (std::size_t r = 0; r < R; ++r) {
341 T s = U[r];
342 for (std::size_t t = 0; t < R; ++t) s += U[t] * Qsub[i](t, r);
343 Qi[r] = s;
344 }
345 Qc[i][key] = Qi;
346 Dc[i][key] = Drow;
347 }
348 Xc[key] = Xk;
349 Qlc[key] = Qk_li;
350 }
351
352 const std::vector<int> keyN = detail::mvaoi_key(zeroS, N);
353 MvaoiResult<T> res;
354 res.X = Xc.at(keyN);
355 res.Qoi = Matrix<T>(K, R, zero);
356 for (std::size_t i = 0; i < K; ++i) {
357 const std::vector<T>& q = Qc[i].at(keyN);
358 for (std::size_t r = 0; r < R; ++r) res.Qoi(i, r) = q[r];
359 }
360 res.Qli = Qlc.at(keyN);
361 res.Qdelay.assign(R, zero);
362 for (std::size_t r = 0; r < R; ++r) res.Qdelay[r] = res.X[r] * Z[r];
363 if (!want_soi) return res;
364
365 // ---- mean number of in-service jobs per class at each OI station ----------------
366 std::vector<std::size_t> shp(R), stride(R, 1);
367 std::size_t total = 1;
368 for (std::size_t d = 0; d < R; ++d) shp[d] = static_cast<std::size_t>(N[d]) + 1;
369 for (std::size_t d = 1; d < R; ++d) stride[d] = stride[d - 1] * shp[d - 1];
370 for (std::size_t d = 0; d < R; ++d) total *= shp[d];
371 std::vector<std::vector<int>> subs(total, std::vector<int>(R, 0));
372 std::vector<int> ssum(total, 0);
373 for (std::size_t i = 0; i < total; ++i) {
374 std::size_t li = i;
375 for (std::size_t d = 0; d < R; ++d) {
376 subs[i][d] = static_cast<int>(li % shp[d]);
377 li /= shp[d];
378 ssum[i] += subs[i][d];
379 }
380 }
381 std::vector<std::size_t> ord(total);
382 for (std::size_t i = 0; i < total; ++i) ord[i] = i;
383 std::stable_sort(ord.begin(), ord.end(),
384 [&](std::size_t a, std::size_t b) { return ssum[a] < ssum[b]; });
385
386 Matrix<T> Xlat(total, R, zero);
387 for (std::size_t i = 0; i < total; ++i) {
388 const std::vector<T>& x = Xc.at(detail::mvaoi_key(zeroS, subs[i]));
389 for (std::size_t r = 0; r < R; ++r) Xlat(i, r) = x[r];
390 }
391
392 res.Soi = Matrix<T>(K, R, zero);
393 for (std::size_t mm = 0; mm < K; ++mm) {
394 const OiInsvcResult<T> gm = pfqn_oi_insvc<T>(mu[mm], N);
395 std::vector<T> muv(total, zero);
396 for (std::size_t i = 0; i < total; ++i)
397 if (ssum[i] > 0) muv[i] = mu[mm](subs[i]);
398 Matrix<T> pMv(total, total, zero);
399 pMv(0, 0) = one;
400 for (std::size_t bb = 0; bb < total; ++bb) {
401 const std::size_t b = ord[bb];
402 if (ssum[b] == 0) continue;
403 T acc0 = zero;
404 for (std::size_t aa = 0; aa < total; ++aa) {
405 const std::size_t a = ord[aa];
406 if (ssum[a] == 0) continue;
407 bool fits = true;
408 for (std::size_t r = 0; r < R; ++r)
409 if (subs[a][r] > subs[b][r]) {
410 fits = false;
411 break;
412 }
413 if (!fits || !(muv[a] > zero)) continue;
414 T acc = zero;
415 for (std::size_t r = 0; r < R; ++r)
416 if (subs[a][r] > 0)
417 acc += Xlat(b, r) * pMv(a - stride[r], b - stride[r]);
418 pMv(a, b) = acc / muv[a];
419 acc0 += pMv(a, b);
420 }
421 pMv(0, b) = one - acc0; // empty-state probability by complement
422 }
423 const std::size_t idxN = total - 1;
424 for (std::size_t r = 0; r < R; ++r) {
425 T s = zero;
426 for (std::size_t a = 0; a < total; ++a) s += pMv(a, idxN) * gm.g(a, r);
427 res.Soi(mm, r) = s;
428 }
429 }
430 return res;
431}
432
433/** Overload with unit visits. */
434template <class T>
435MvaoiResult<T> pfqn_mvaoi(const std::vector<T>& Z, const std::vector<int>& N,
436 const std::vector<std::function<T(const std::vector<int>&)>>& mu,
437 const Matrix<T>& Dli, bool want_soi) {
438 return pfqn_mvaoi(Z, N, mu, Dli, Matrix<T>(), want_soi);
439}
440
441/** Overload without the in-service means. */
442template <class T>
443MvaoiResult<T> pfqn_mvaoi(const std::vector<T>& Z, const std::vector<int>& N,
444 const std::vector<std::function<T(const std::vector<int>&)>>& mu,
445 const Matrix<T>& Dli, const Matrix<T>& visits) {
446 return pfqn_mvaoi(Z, N, mu, Dli, visits, false);
447}
448
449/** Overload without the in-service means, unit visits. */
450template <class T>
451MvaoiResult<T> pfqn_mvaoi(const std::vector<T>& Z, const std::vector<int>& N,
452 const std::vector<std::function<T(const std::vector<int>&)>>& mu,
453 const Matrix<T>& Dli) {
454 return pfqn_mvaoi(Z, N, mu, Dli, Matrix<T>(), false);
455}
456
457} // namespace pfqn
458} // namespace line
459
460#endif // LINE_API_PFQN_MVAOI_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
std::vector< std::vector< int > > multichoose_rows(int n, int k)
All n-vectors of nonnegative integers summing to k, in MATLAB multichoose(n,k) order.
OiInsvcResult< T > pfqn_oi_insvc(const std::function< T(const std::vector< int > &)> &oirate, const std::vector< int > &N)
Conditional mean number of IN-SERVICE jobs per class at an order-independent station.
MvaoiResult< T > pfqn_mvaoi(const std::vector< T > &Z, const std::vector< int > &N, const std::vector< std::function< T(const std::vector< int > &)> > &mu, const Matrix< T > &Dli, const Matrix< T > &visits, bool want_soi)
Mean-value analysis of a closed network with order-independent (OI) stations, the composition-depende...
Definition pfqn_mvaoi.h:122
Number-type abstraction for the templated API port.
Integer-composition enumeration shared by the CoMoM and MVAC ports.
Conditional mean number of IN-SERVICE jobs per class at an order-independent station.
Return value of pfqn_mvaoi, mirroring [X, Qoi, Qli, Qdelay, Soi].
Definition pfqn_mvaoi.h:82
Matrix< T > Qoi
(K x R) per-class queue length at each OI station
Definition pfqn_mvaoi.h:84
Matrix< T > Qli
(J x R) per-class queue length at each LI queue
Definition pfqn_mvaoi.h:85
std::vector< T > Qdelay
(R) per-class queue length at the delay node
Definition pfqn_mvaoi.h:86
std::vector< T > X
(R) per-class throughput
Definition pfqn_mvaoi.h:83
Matrix< T > Soi
(K x R) per-class mean in-service jobs, if requested
Definition pfqn_mvaoi.h:87
Return value of pfqn_oi_insvc, mirroring [g, Xi, Phi].
Matrix< T > g
(prod(N+1) x R) E[sir_r | n]