LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_sdr.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_SDR_H
6#define LINE_API_PFQN_SDR_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Product-form state-dependent routing.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_sdr*.m, from A. E. Krzesinski,
14 * "Multiclass Queueing Networks with State-Dependent Routing", Performance
15 * Evaluation 7(2):125-143, 1987, the multiclass generalization of D. Towsley,
16 * "Queuing Network Models with State-Dependent Routing", J. ACM 27(2):323-337,
17 * 1980.
18 *
19 * A network is split into a subnetwork Q(V,V) under SDR and its complement
20 * M-V. Q(V,V) has one entry centre e and one departure centre d, both outside
21 * it, and is partitioned into disjoint branches arranged in a hierarchy of
22 * nested subnetworks V_1 > ... > V_T. Each branch has one entry centre, one
23 * departure centre, and may hold several centres between them.
24 *
25 * Branch index 1 denotes the complement M-V and is unused, in the paper and in
26 * every codebase. The SDR branches are numbered 2..B, that is indices 1..B-1 of
27 * the zero-based arrays here. Keeping the paper's numbering is what lets d(t,b)
28 * be transcribed straight from the text.
29 *
30 * UNLIKE the MATLAB, JAR and Python twins this routine carries no logarithms.
31 * The unnormalized weight of eq. (16) is a product of field operations on the
32 * inputs, so the whole evaluation stays inside T and an exact rational backend
33 * returns an exact normalizing constant. The price is that a double backend can
34 * overflow where the log-domain twins would not; that bites only at populations
35 * far beyond what state enumeration can reach anyway.
36 */
37
38#include <algorithm>
39#include <cstddef>
40#include <string>
41#include <vector>
42
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/lu.h"
47#include "line/util/matrix.h"
48
49namespace line {
50namespace pfqn {
51
52/**
53 * Topology and coefficients of a state-dependent routing subnetwork.
54 *
55 * Centre indices are whatever space the caller uses consistently: node indices
56 * for the per-state routing probabilities, station indices for the product
57 * form.
58 */
59struct SdrStruct {
60 /** Entry centre e of Q(V,V). */
61 std::size_t entry = 0;
62 /** Departure centre d of Q(V,V); may equal `entry`. */
63 std::size_t departure = 0;
64 /** branch[b] holds the centres of branch b, b >= 1; branch[0] is unused. */
65 std::vector<std::vector<std::size_t>> branch;
66 /** entryOf[b] is the entry centre e(b) of branch b. */
67 std::vector<std::size_t> entryOf;
68 /** departureOf[b] is the departure centre d(b) of branch b. */
69 std::vector<std::size_t> departureOf;
70 /** level[b] is the unique t with B_b in V_t - V_{t+1}; level[0] is unused. */
71 std::vector<std::size_t> level;
72 /** Coefficients C_t of eq. (11), length T. */
73 std::vector<double> C;
74 /** Coefficients d_tb of eq. (11), T rows by B columns. */
76
77 bool empty() const { return branch.size() < 2; }
78};
79
80/** Derived coefficients of an SDR structure, eqs. (11)-(14). */
81struct SdrCoeff {
82 std::size_t T = 0;
83 std::size_t B = 0;
84 /** inA[t] holds the branch indices b with level[b] >= t, the set A_t. */
85 std::vector<std::vector<std::size_t>> inA;
86 /** D_tt = sum over A_t of d_tb; index 0 unused. */
87 std::vector<double> Dtt;
88 /** D_{t-1,t} = sum over A_t of d_{t-1,b}; indices 0 and 1 unused. */
89 std::vector<double> Dprev;
90 /** Largest branch population with delta nonnegative; -1 marks unbounded. */
91 std::vector<double> mmax;
92 /** Largest subnetwork population with omega nonnegative; -1 marks unbounded. */
93 std::vector<double> vmax;
94 const SdrStruct* sdr = nullptr;
95};
96
97/**
98 * Validates an SDR structure and returns its derived coefficients.
99 *
100 * The population bounds are consequences of the coefficients, not independent
101 * inputs: with C_t negative the routing enforces m_b <= d_tb/(-C_t) and
102 * v_t <= D_tt/(-C_t) by itself, because the cumulative Delta hits a zero factor
103 * exactly at the bound.
104 */
105inline SdrCoeff pfqn_sdrcoeff(const SdrStruct& sdr) {
106 const std::size_t B = sdr.branch.size();
107 const std::size_t T = sdr.C.size();
108 if (B < 2)
109 throw InputError("pfqn_sdrcoeff: an SDR structure must declare at least one branch "
110 "(branch indices start at 2)");
111 if (T < 1)
112 throw InputError("pfqn_sdrcoeff: an SDR structure must declare at least one level of "
113 "subnetwork nesting");
114 if (sdr.level.size() != B)
115 throw InputError("pfqn_sdrcoeff: level must have one entry per branch index");
116 if (sdr.entryOf.size() != B || sdr.departureOf.size() != B)
117 throw InputError("pfqn_sdrcoeff: entryOf and departureOf must have one entry per branch index");
118 if (sdr.d.rows() < T || sdr.d.cols() < B)
119 throw InputError("pfqn_sdrcoeff: the coefficient matrix d is too small for the declared "
120 "levels and branches");
121 for (std::size_t b = 1; b < B; ++b)
122 if (sdr.level[b] < 1 || sdr.level[b] > T)
123 throw InputError("pfqn_sdrcoeff: SDR branch levels must lie in 1..T");
124
125 // The nesting V_1 > ... > V_T must be strict, else two hierarchically
126 // adjacent subnetworks coincide and the ratio of eq. (10) is not the paper's.
127 for (std::size_t t = 1; t <= T; ++t) {
128 bool found = false;
129 for (std::size_t b = 1; b < B && !found; ++b) found = (sdr.level[b] == t);
130 if (!found)
131 throw InputError("pfqn_sdrcoeff: SDR level " + std::to_string(t) +
132 " carries no branch: the subnetwork nesting must be strict");
133 }
134
135 std::vector<std::size_t> seen;
136 for (std::size_t b = 1; b < B; ++b) {
137 if (sdr.branch[b].empty())
138 throw InputError("pfqn_sdrcoeff: SDR branch " + std::to_string(b + 1) + " is empty");
139 bool hasEntry = false, hasDeparture = false;
140 for (std::size_t k = 0; k < sdr.branch[b].size(); ++k) {
141 const std::size_t c = sdr.branch[b][k];
142 for (std::size_t q = 0; q < seen.size(); ++q)
143 if (seen[q] == c)
144 throw InputError("pfqn_sdrcoeff: SDR branches must be mutually disjoint");
145 if (c == sdr.entry || c == sdr.departure)
146 throw InputError("pfqn_sdrcoeff: the entry and departure centres of Q(V,V) must "
147 "not belong to any branch");
148 if (c == sdr.entryOf[b]) hasEntry = true;
149 if (c == sdr.departureOf[b]) hasDeparture = true;
150 seen.push_back(c);
151 }
152 if (!hasEntry || !hasDeparture)
153 throw InputError("pfqn_sdrcoeff: the entry and departure centres of SDR branch " +
154 std::to_string(b + 1) + " must belong to that branch");
155 }
156
157 SdrCoeff c;
158 c.T = T;
159 c.B = B;
160 c.sdr = &sdr;
161 c.inA.assign(T + 1, std::vector<std::size_t>());
162 c.Dtt.assign(T + 1, 0.0);
163 c.Dprev.assign(T + 1, 0.0);
164 for (std::size_t t = 1; t <= T; ++t) {
165 for (std::size_t b = 1; b < B; ++b)
166 if (sdr.level[b] >= t) {
167 c.inA[t].push_back(b);
168 c.Dtt[t] += sdr.d(t - 1, b);
169 if (t > 1) c.Dprev[t] += sdr.d(t - 2, b);
170 }
171 }
172 c.mmax.assign(B, -1.0);
173 for (std::size_t b = 1; b < B; ++b) {
174 const std::size_t t = sdr.level[b];
175 if (sdr.C[t - 1] < 0) c.mmax[b] = std::floor(sdr.d(t - 1, b) / (-sdr.C[t - 1]));
176 }
177 c.vmax.assign(T + 1, -1.0);
178 for (std::size_t t = 1; t <= T; ++t) {
179 if (sdr.C[t - 1] < 0) c.vmax[t] = std::floor(c.Dtt[t] / (-sdr.C[t - 1]));
180 if (t > 1 && sdr.C[t - 2] < 0) {
181 const double alt = std::floor(c.Dprev[t] / (-sdr.C[t - 2]));
182 c.vmax[t] = (c.vmax[t] < 0) ? alt : std::min(c.vmax[t], alt);
183 }
184 }
185 return c;
186}
187
188/**
189 * SDR routing probabilities of eq. (10).
190 *
191 * Entry b of the result is the probability of proceeding from the entry centre
192 * e of Q(V,V) to the entry centre of branch b; entry 0 is zero because branch
193 * index 1 denotes the complement M-V. The residual mass 1 - sum is the
194 * probability of proceeding directly to the departure centre d, that is of
195 * being denied entry into Q(V,V) and returned to e, which the paper calls the
196 * busy form of waiting (Sec. 2.5).
197 *
198 * The probabilities are chain independent: they read the total branch and
199 * subnetwork populations, not the per-chain ones. The chain-dependent form of
200 * eq. (1) has no published product form and is not implemented. A branch
201 * population beyond the bound SDR enforces itself is unreachable, and the
202 * probability returned there is zero.
203 *
204 * @param c derived coefficients from pfqn_sdrcoeff
205 * @param n per-centre total populations, indexed as the structure is
206 */
207inline std::vector<double> pfqn_sdrprob(const SdrCoeff& c, const std::vector<double>& n) {
208 const SdrStruct& sdr = *c.sdr;
209 std::vector<double> m(c.B, 0.0);
210 for (std::size_t b = 1; b < c.B; ++b)
211 for (std::size_t k = 0; k < sdr.branch[b].size(); ++k) m[b] += n[sdr.branch[b][k]];
212 std::vector<double> v(c.T + 1, 0.0);
213 for (std::size_t t = 1; t <= c.T; ++t)
214 for (std::size_t k = 0; k < c.inA[t].size(); ++k) v[t] += m[c.inA[t][k]];
215
216 std::vector<double> om(c.T + 1, 0.0), omprev(c.T + 1, 1.0);
217 for (std::size_t s = 1; s <= c.T; ++s) {
218 om[s] = sdr.C[s - 1] * v[s] + c.Dtt[s];
219 if (s > 1) omprev[s] = sdr.C[s - 2] * v[s] + c.Dprev[s];
220 }
221
222 std::vector<double> P(c.B, 0.0);
223 for (std::size_t b = 1; b < c.B; ++b) {
224 const std::size_t t = sdr.level[b];
225 bool closed = false;
226 for (std::size_t s = 1; s <= t && !closed; ++s)
227 closed = (om[s] <= 0.0); // eq. (10): the branch is closed to new arrivals
228 if (closed) continue;
229 const double delta = sdr.C[t - 1] * m[b] + sdr.d(t - 1, b);
230 if (delta <= 0.0) continue;
231 double ratio = 1.0;
232 for (std::size_t s = 1; s <= t; ++s) ratio *= omprev[s] / om[s];
233 P[b] = delta * ratio;
234 }
235 return P;
236}
237
238/** Probability of being denied entry and routed straight to the departure centre. */
239inline double pfqn_sdrped(const std::vector<double>& P) {
240 double s = 0.0;
241 for (std::size_t b = 0; b < P.size(); ++b) s += P[b];
242 return 1.0 - s;
243}
244
245/** Mean performance measures returned by pfqn_sdr. */
246template <class T>
247struct SdrResult {
248 Matrix<T> QN; ///< mean queue lengths, centres by chains
249 Matrix<T> XN; ///< per-centre chain throughputs
250 Matrix<T> UN; ///< mean number in service, XN elementwise times S
251 Matrix<T> RN; ///< mean response times at the centre, QN elementwise over XN
252 T G; ///< normalizing constant
253};
254
255namespace detail {
256
257/** All nonnegative integer m-vectors summing to n, appended to out. */
258inline void sdr_compositions(std::size_t n, std::size_t m,
259 std::vector<std::vector<std::size_t>>& out) {
260 if (m == 1) {
261 out.push_back(std::vector<std::size_t>(1, n));
262 return;
263 }
264 for (std::size_t k = 0; k <= n; ++k) {
265 std::vector<std::vector<std::size_t>> tail;
266 sdr_compositions(n - k, m - 1, tail);
267 for (std::size_t t = 0; t < tail.size(); ++t) {
268 std::vector<std::size_t> row;
269 row.reserve(m);
270 row.push_back(k);
271 row.insert(row.end(), tail[t].begin(), tail[t].end());
272 out.push_back(row);
273 }
274 }
275}
276
277} // namespace detail
278
279/**
280 * Exact product form of eq. (16), by summation over the reachable state space.
281 *
282 * P(n) is G^-1 times the product over centres of f_i(n_i), the product over
283 * levels of Omega_{t-1,t}(v_t)/Omega_tt(v_t), and the product over branches of
284 * Delta_tb(m_b), with f_i(n_i) = [n_i!/beta_i(n_i)] times the product over
285 * chains of gamma_ij^n_ij/n_ij! and gamma_ij = xi_ij/mu_ij.
286 *
287 * General in the branch topology: a branch may hold several interconnected
288 * centres. Only the paper's Section 4 MVA and convolution algorithm, not ported
289 * here, is restricted to single-centre branches.
290 *
291 * S and xi are required separately rather than as their product because under
292 * SDR the xi are not visit ratios, so the per-centre throughputs cannot be
293 * recovered from the demands alone.
294 *
295 * @param S (M x J) mean service times 1/mu_ij
296 * @param xi (M x J) coefficients of Section 3.2, see pfqn_sdrvisits
297 * @param N (J) chain populations
298 * @param sdr the routing structure, in centre indices
299 * @param alpha (M x sum(N)) load-dependent rate scalings, alpha(i, k-1) = alpha_i(k);
300 * empty for a fixed-rate centre. Use k for an infinite server and
301 * min(k, c) for a c-server centre
302 */
303template <class T>
304SdrResult<T> pfqn_sdr(const Matrix<T>& S, const Matrix<T>& xi, const std::vector<std::size_t>& N,
305 const SdrStruct& sdr, const Matrix<T>& alpha = Matrix<T>()) {
306 const std::size_t M = S.rows(), J = S.cols();
307 if (xi.rows() != M || xi.cols() != J)
308 throw InputError("pfqn_sdr: S and xi must have the same shape");
309 if (N.size() != J)
310 throw InputError("pfqn_sdr: the population vector must have one entry per chain");
311
312 const SdrCoeff c = pfqn_sdrcoeff(sdr);
313 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
314
315 std::size_t Ntot = 0;
316 for (std::size_t j = 0; j < J; ++j) Ntot += N[j];
317 for (std::size_t b = 1; b < c.B; ++b)
318 for (std::size_t k = 0; k < sdr.branch[b].size(); ++k)
319 if (sdr.branch[b][k] >= M)
320 throw InputError("pfqn_sdr: the SDR structure references a centre index beyond "
321 "the number of centres");
322
323 // beta_i(n) = alpha_i(n) beta_i(n-1), beta_i(0) = 1
324 Matrix<T> beta(M, Ntot + 1, one);
325 for (std::size_t i = 0; i < M; ++i)
326 for (std::size_t k = 1; k <= Ntot; ++k) {
327 const T a = (!alpha.empty() && i < alpha.rows() && (k - 1) < alpha.cols())
328 ? alpha(i, k - 1)
329 : one;
330 beta(i, k) = beta(i, k - 1) * a;
331 }
332
333 Matrix<T> gamma(M, J, zero);
334 for (std::size_t i = 0; i < M; ++i)
335 for (std::size_t j = 0; j < J; ++j) gamma(i, j) = xi(i, j) * S(i, j);
336
337 // Cumulative Delta and Omega. A zero factor is the SDR population bound
338 // closing the branch or the subnetwork, and it makes the weight vanish on
339 // its own; no separate capacity test is needed anywhere.
340 Matrix<T> Delta(c.B, Ntot + 1, one);
341 for (std::size_t b = 1; b < c.B; ++b) {
342 const std::size_t t = sdr.level[b];
343 for (std::size_t n = 1; n <= Ntot; ++n) {
344 const double f = sdr.C[t - 1] * static_cast<double>(n - 1) + sdr.d(t - 1, b);
345 Delta(b, n) = (f <= 0.0) ? zero : Delta(b, n - 1) * num_traits<T>::from_double(f);
346 }
347 }
348 Matrix<T> OmTT(c.T + 1, Ntot + 1, one), OmPrev(c.T + 1, Ntot + 1, one);
349 for (std::size_t t = 1; t <= c.T; ++t)
350 for (std::size_t n = 1; n <= Ntot; ++n) {
351 const double f = sdr.C[t - 1] * static_cast<double>(n - 1) + c.Dtt[t];
352 OmTT(t, n) = (f <= 0.0) ? zero : OmTT(t, n - 1) * num_traits<T>::from_double(f);
353 if (t > 1) {
354 const double g = sdr.C[t - 2] * static_cast<double>(n - 1) + c.Dprev[t];
355 OmPrev(t, n) = (g <= 0.0) ? zero : OmPrev(t, n - 1) * num_traits<T>::from_double(g);
356 }
357 }
358
359 std::vector<std::vector<std::size_t>> states;
360 states.push_back(std::vector<std::size_t>());
361 for (std::size_t j = 0; j < J; ++j) {
362 std::vector<std::vector<std::size_t>> comps;
363 detail::sdr_compositions(N[j], M, comps);
364 std::vector<std::vector<std::size_t>> next;
365 next.reserve(states.size() * comps.size());
366 for (std::size_t a = 0; a < states.size(); ++a)
367 for (std::size_t b = 0; b < comps.size(); ++b) {
368 std::vector<std::size_t> row = states[a];
369 row.insert(row.end(), comps[b].begin(), comps[b].end());
370 next.push_back(row);
371 }
372 states.swap(next);
373 }
374
375 // Factorials as exact integers of T, so a rational backend stays exact
376 std::vector<T> fact(Ntot + 1, one);
377 for (std::size_t k = 1; k <= Ntot; ++k)
378 fact[k] = fact[k - 1] * num_traits<T>::from_int(static_cast<long>(k));
379
380 SdrResult<T> res;
381 res.QN = Matrix<T>(M, J, zero);
382 res.XN = Matrix<T>(M, J, zero);
383 res.UN = Matrix<T>(M, J, zero);
384 res.RN = Matrix<T>(M, J, zero);
385
386 std::vector<T> w(states.size(), zero);
387 T Gs = zero;
388 for (std::size_t s = 0; s < states.size(); ++s) {
389 const std::vector<std::size_t>& flat = states[s];
390 std::vector<std::size_t> ni(M, 0);
391 for (std::size_t i = 0; i < M; ++i)
392 for (std::size_t j = 0; j < J; ++j) ni[i] += flat[j * M + i];
393
394 T weight = one;
395 bool alive = true;
396 for (std::size_t i = 0; i < M && alive; ++i) {
397 weight = weight * fact[ni[i]] / beta(i, ni[i]);
398 for (std::size_t j = 0; j < J; ++j) {
399 const std::size_t nij = flat[j * M + i];
400 if (nij == 0) continue;
401 if (!(gamma(i, j) > zero)) {
402 alive = false;
403 break;
404 }
405 for (std::size_t k = 0; k < nij; ++k) weight = weight * gamma(i, j);
406 weight = weight / fact[nij];
407 }
408 }
409 if (!alive) continue;
410
411 std::vector<std::size_t> m(c.B, 0);
412 for (std::size_t b = 1; b < c.B && alive; ++b) {
413 for (std::size_t k = 0; k < sdr.branch[b].size(); ++k) m[b] += ni[sdr.branch[b][k]];
414 if (Delta(b, m[b]) == zero) alive = false;
415 weight = weight * Delta(b, m[b]);
416 }
417 if (!alive) continue;
418 for (std::size_t t = 1; t <= c.T && alive; ++t) {
419 std::size_t v = 0;
420 for (std::size_t k = 0; k < c.inA[t].size(); ++k) v += m[c.inA[t][k]];
421 if (OmTT(t, v) == zero) {
422 alive = false;
423 break;
424 }
425 weight = weight / OmTT(t, v);
426 if (t > 1) weight = weight * OmPrev(t, v);
427 }
428 if (!alive) continue;
429 w[s] = weight;
430 Gs = Gs + weight;
431 }
432 if (Gs == zero)
433 throw InputError("pfqn_sdr: the SDR network has no reachable state at the given "
434 "populations: the routing coefficients forbid every state");
435 res.G = Gs;
436
437 for (std::size_t s = 0; s < states.size(); ++s) {
438 if (w[s] == zero) continue;
439 const T p = w[s] / Gs;
440 const std::vector<std::size_t>& flat = states[s];
441 for (std::size_t i = 0; i < M; ++i) {
442 std::size_t nitot = 0;
443 for (std::size_t j = 0; j < J; ++j) nitot += flat[j * M + i];
444 for (std::size_t j = 0; j < J; ++j) {
445 const std::size_t nij = flat[j * M + i];
446 if (nij > 0)
447 res.QN(i, j) =
448 res.QN(i, j) + p * num_traits<T>::from_int(static_cast<long>(nij));
449 if (nitot > 0 && S(i, j) > zero) {
450 const T a = (!alpha.empty() && i < alpha.rows() && (nitot - 1) < alpha.cols())
451 ? alpha(i, nitot - 1)
452 : one;
453 res.XN(i, j) = res.XN(i, j) +
454 p * a * num_traits<T>::from_int(static_cast<long>(nij)) /
455 num_traits<T>::from_int(static_cast<long>(nitot)) / S(i, j);
456 }
457 }
458 }
459 }
460 for (std::size_t i = 0; i < M; ++i)
461 for (std::size_t j = 0; j < J; ++j) {
462 res.UN(i, j) = res.XN(i, j) * S(i, j);
463 if (res.XN(i, j) > zero) res.RN(i, j) = res.QN(i, j) / res.XN(i, j);
464 }
465 return res;
466}
467
468namespace detail {
469
470/** Every population vector V with 0 <= V <= N, mixed-radix ordered. */
471inline std::vector<std::vector<std::size_t>> sdr_lattice(const std::vector<std::size_t>& N) {
472 std::size_t tot = 1;
473 for (std::size_t j = 0; j < N.size(); ++j) tot *= N[j] + 1;
474 std::vector<std::vector<std::size_t>> out(tot, std::vector<std::size_t>(N.size(), 0));
475 for (std::size_t r = 0; r < tot; ++r) {
476 std::size_t rem = r;
477 for (std::size_t j = 0; j < N.size(); ++j) {
478 out[r][j] = rem % (N[j] + 1);
479 rem /= (N[j] + 1);
480 }
481 }
482 return out;
483}
484
485/** Mixed-radix row of V in the lattice of sdr_lattice. */
486inline std::size_t sdr_key(const std::vector<std::size_t>& V, const std::vector<std::size_t>& N) {
487 std::size_t k = 0, mul = 1;
488 for (std::size_t j = 0; j < N.size(); ++j) {
489 k += V[j] * mul;
490 mul *= N[j] + 1;
491 }
492 return k;
493}
494
495/** Omega_{t-1,t}(v)/Omega_tt(v), the cumulative ratio of eq. (16). */
496inline double sdr_omega_cum(const SdrCoeff& c, std::size_t t, std::size_t v) {
497 double num = 1.0, den = 1.0;
498 for (std::size_t k = 0; k < v; ++k) {
499 const double f = -static_cast<double>(k) + c.Dtt[t];
500 if (f <= 0.0) return 0.0;
501 den *= f;
502 if (t > 1) {
503 const double g = -static_cast<double>(k) + c.Dprev[t];
504 if (g <= 0.0) return 0.0;
505 num *= g;
506 }
507 }
508 return num / den;
509}
510
511/**
512 * omega_{t-1,t}(v)/omega_tt(v), the single-step ratio of eq. (10). Distinct
513 * from sdr_omega_cum: the routing probability carries the lowercase omega, the
514 * normalizing constant the uppercase one.
515 */
516inline double sdr_omega_step(const SdrCoeff& c, std::size_t t, std::size_t v) {
517 const double den = -static_cast<double>(v) + c.Dtt[t];
518 if (den <= 0.0) return 0.0;
519 double num = 1.0; // omega_{0,1} is one
520 if (t > 1) {
521 num = -static_cast<double>(v) + c.Dprev[t];
522 if (num <= 0.0) return 0.0;
523 }
524 return num / den;
525}
526
527} // namespace detail
528
529/**
530 * Section 4 mean value analysis and convolution.
531 *
532 * Same inputs and outputs as pfqn_sdr, which evaluates eq. (16) exactly by
533 * state enumeration, so the two are directly comparable. This routine costs
534 * O(J T M (V_1...V_J)^2) rather than the size of the state space, at the price
535 * of two restrictions the paper itself imposes: every SDR branch must hold a
536 * single centre, and every C_t must be negative. A C_t other than -1 is
537 * rescaled internally, which leaves eqs. (10) and (16) unchanged because the
538 * level factors telescope.
539 *
540 * Two formulas of Section 4 are corrected here, both verified against
541 * pfqn_sdr. The initialise step of 4.2.2 divides by T_j(V-1_j,V_T) where the
542 * convolution identity G(V)/G(V-1_j) = 1/T_j(V) gives T_j(V,V_T); this
543 * implementation forms G = g_mva Omega_{T-1,T}/Omega_TT directly instead. And
544 * 4.2.3's T_ij = xi_ij [d_1i - Q_i] T_j drops the state-dependent omega ratios
545 * of eq. (10); the exact identity is T_ij = xi_ij T_j(N,M) E_{N-1_j}[P_{e,e(i)}].
546 *
547 * Unlike pfqn_sdr this routine divides by intermediate normalizing constants,
548 * so it needs a field with division but no transcendentals beyond the final
549 * logarithm of G.
550 */
551template <class T>
552SdrResult<T> pfqn_sdrmva(const Matrix<T>& S, const Matrix<T>& xi, const std::vector<std::size_t>& N,
553 const SdrStruct& sdr, const Matrix<T>& alpha = Matrix<T>()) {
554 const std::size_t M = S.rows(), J = S.cols();
555 if (xi.rows() != M || xi.cols() != J)
556 throw InputError("pfqn_sdrmva: S and xi must have the same shape");
557 if (N.size() != J)
558 throw InputError("pfqn_sdrmva: the population vector must have one entry per chain");
559
560 const SdrCoeff c0 = pfqn_sdrcoeff(sdr);
561 for (std::size_t b = 1; b < c0.B; ++b)
562 if (sdr.branch[b].size() != 1)
563 throw UnsupportedError("pfqn_sdrmva: every SDR branch must hold a single centre; the MVA "
564 "and convolution of Krzesinski (1987) Section 4 is stated that way "
565 "and its general case is in an unpublished technical report. Use "
566 "pfqn_sdr, which evaluates eq. (16) exactly for any branch topology");
567 for (std::size_t t = 0; t < c0.T; ++t)
568 if (sdr.C[t] >= 0.0)
569 throw UnsupportedError("pfqn_sdrmva: every C_t must be negative; Section 2.5 assumes it and "
570 "Section 4 is written for C_t = -1");
571
572 // Rescale each level to C_t = -1, which leaves eqs. (10) and (16) unchanged
573 SdrStruct sdr1 = sdr;
574 for (std::size_t t = 0; t < c0.T; ++t) {
575 const double k = -sdr.C[t];
576 sdr1.C[t] = -1.0;
577 for (std::size_t b = 0; b < sdr1.d.cols(); ++b) sdr1.d(t, b) = sdr.d(t, b) / k;
578 }
579 const SdrCoeff c = pfqn_sdrcoeff(sdr1);
580
581 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
582 std::size_t Ntot = 0;
583 for (std::size_t j = 0; j < J; ++j) Ntot += N[j];
584
585 Matrix<T> gamma(M, J, zero);
586 for (std::size_t i = 0; i < M; ++i)
587 for (std::size_t j = 0; j < J; ++j) gamma(i, j) = xi(i, j) * S(i, j);
588 Matrix<T> alp(M, Ntot > 0 ? Ntot : 1, one);
589 for (std::size_t i = 0; i < M; ++i)
590 for (std::size_t k = 0; k < alp.cols(); ++k)
591 if (!alpha.empty() && i < alpha.rows() && k < alpha.cols()) alp(i, k) = alpha(i, k);
592
593 const std::vector<std::vector<std::size_t>> latt = detail::sdr_lattice(N);
594 const std::size_t nl = latt.size();
595
596 std::vector<bool> inV(M, false);
597 std::vector<double> dvec(M, 0.0);
598 std::vector<bool> hasd(M, false);
599 std::vector<std::size_t> lvl(M, 0);
600 for (std::size_t b = 1; b < c.B; ++b) {
601 const std::size_t i = sdr1.branch[b][0];
602 inV[i] = true;
603 lvl[i] = sdr1.level[b];
604 dvec[i] = sdr1.d(sdr1.level[b] - 1, b);
605 hasd[i] = true;
606 }
607 std::vector<std::size_t> mv;
608 for (std::size_t i = 0; i < M; ++i)
609 if (!inV[i]) mv.push_back(i);
610
611 // Sec. 4.2.1, also used for the complement with delta = 1
612 struct SetOut {
613 std::vector<Matrix<T>> Q; // per lattice point, M x J
614 std::vector<std::vector<T>> Tp;
615 std::vector<T> g;
616 };
617 auto set_mva = [&](const std::vector<std::size_t>& cidx, bool sdrset) {
618 SetOut o;
619 o.Q.assign(nl, Matrix<T>(M, J, zero));
620 o.Tp.assign(nl, std::vector<T>(J, zero));
621 o.g.assign(nl, zero);
622 const std::size_t nc = cidx.size();
623 const std::size_t z = detail::sdr_key(std::vector<std::size_t>(J, 0), N);
624 if (nc == 0) {
625 for (std::size_t v = 0; v < nl; ++v) {
626 std::size_t tot = 0;
627 for (std::size_t j = 0; j < J; ++j) tot += latt[v][j];
628 o.g[v] = (tot == 0) ? one : zero;
629 }
630 return o;
631 }
632 // Ps[k][n][v] is P_k(n : V)
633 std::vector<std::vector<std::vector<T>>> Ps(
634 nc, std::vector<std::vector<T>>(Ntot + 1, std::vector<T>(nl, zero)));
635 o.g[z] = one;
636 for (std::size_t k = 0; k < nc; ++k) Ps[k][0][z] = one;
637
638 std::vector<std::size_t> ord(nl);
639 for (std::size_t v = 0; v < nl; ++v) ord[v] = v;
640 std::stable_sort(ord.begin(), ord.end(), [&](std::size_t a, std::size_t b) {
641 std::size_t sa = 0, sb = 0;
642 for (std::size_t j = 0; j < J; ++j) { sa += latt[a][j]; sb += latt[b][j]; }
643 return sa < sb;
644 });
645 for (std::size_t oi = 0; oi < nl; ++oi) {
646 const std::size_t v = ord[oi];
647 const std::vector<std::size_t>& V = latt[v];
648 std::size_t vv = 0;
649 for (std::size_t j = 0; j < J; ++j) vv += V[j];
650 if (vv == 0) continue;
651 std::vector<std::vector<T>> A(nc, std::vector<T>(J, zero));
652 std::vector<std::size_t> vm(J, 0);
653 std::vector<bool> hasm(J, false);
654 for (std::size_t j = 0; j < J; ++j) {
655 if (V[j] == 0) continue;
656 std::vector<std::size_t> Vm = V;
657 Vm[j] -= 1;
658 vm[j] = detail::sdr_key(Vm, N);
659 hasm[j] = true;
660 for (std::size_t k = 0; k < nc; ++k) {
661 T acc = zero;
662 for (std::size_t n = 1; n <= vv; ++n) {
663 const double df = sdrset ? (dvec[cidx[k]] - static_cast<double>(n - 1)) : 1.0;
664 if (df <= 0.0) break;
665 acc = acc + num_traits<T>::from_int(static_cast<long>(n)) *
666 num_traits<T>::from_double(df) / alp(cidx[k], n - 1) *
667 Ps[k][n - 1][vm[j]];
668 }
669 A[k][j] = acc;
670 }
671 }
672 for (std::size_t j = 0; j < J; ++j) {
673 if (!hasm[j]) continue;
674 T den = zero;
675 for (std::size_t k = 0; k < nc; ++k) den = den + gamma(cidx[k], j) * A[k][j];
676 if (den > zero)
677 o.Tp[v][j] = num_traits<T>::from_int(static_cast<long>(V[j])) / den;
678 }
679 for (std::size_t k = 0; k < nc; ++k)
680 for (std::size_t j = 0; j < J; ++j) {
681 if (!hasm[j]) continue;
682 o.Q[v](cidx[k], j) = gamma(cidx[k], j) * o.Tp[v][j] * A[k][j];
683 }
684 for (std::size_t k = 0; k < nc; ++k) {
685 T tot = zero;
686 for (std::size_t n = 1; n <= vv; ++n) {
687 const double df = sdrset ? (dvec[cidx[k]] - static_cast<double>(n - 1)) : 1.0;
688 if (df <= 0.0) break;
689 T acc = zero;
690 for (std::size_t j = 0; j < J; ++j) {
691 if (!hasm[j]) continue;
692 acc = acc + gamma(cidx[k], j) * o.Tp[v][j] * Ps[k][n - 1][vm[j]];
693 }
694 Ps[k][n][v] = num_traits<T>::from_double(df) / alp(cidx[k], n - 1) * acc;
695 tot = tot + Ps[k][n][v];
696 }
697 Ps[k][0][v] = one - tot;
698 }
699 for (std::size_t j = 0; j < J; ++j)
700 if (hasm[j] && o.Tp[v][j] > zero) {
701 o.g[v] = o.g[vm[j]] / o.Tp[v][j];
702 break;
703 }
704 }
705 return o;
706 };
707
708 const SetOut comp = set_mva(mv, false);
709
710 std::vector<T> Gin(nl, zero);
711 Gin[detail::sdr_key(std::vector<std::size_t>(J, 0), N)] = one;
712 std::vector<Matrix<T>> Qin(nl, Matrix<T>(M, J, zero));
713 std::vector<std::vector<T>> Ain(nl, std::vector<T>(M, zero));
714 for (std::size_t tt = c.T; tt >= 1; --tt) {
715 std::vector<std::size_t> St, inner;
716 for (std::size_t i = 0; i < M; ++i) {
717 if (inV[i] && lvl[i] == tt) St.push_back(i);
718 else if (inV[i] && lvl[i] > tt) inner.push_back(i);
719 }
720 const SetOut lev = set_mva(St, true);
721 std::vector<T> Gnew(nl, zero);
722 std::vector<Matrix<T>> Qnew(nl, Matrix<T>(M, J, zero));
723 std::vector<std::vector<T>> Tnew(nl, std::vector<T>(J, zero));
724 std::vector<std::vector<T>> Anew(nl, std::vector<T>(M, zero));
725 for (std::size_t v = 0; v < nl; ++v) {
726 const std::vector<std::size_t>& V = latt[v];
727 std::size_t vv = 0;
728 for (std::size_t j = 0; j < J; ++j) vv += V[j];
729 const double omr = detail::sdr_omega_cum(c, tt, vv);
730 if (omr == 0.0) continue;
731 std::vector<T> anum(M, zero);
732 const std::vector<std::vector<std::size_t>> sub = detail::sdr_lattice(V);
733 for (std::size_t q = 0; q < sub.size(); ++q) {
734 std::vector<std::size_t> VL(J);
735 for (std::size_t j = 0; j < J; ++j) VL[j] = V[j] - sub[q][j];
736 const std::size_t iL = detail::sdr_key(sub[q], N), iVL = detail::sdr_key(VL, N);
737 const T pb = num_traits<T>::from_double(omr) * lev.g[iVL] * Gin[iL];
738 if (pb == zero) continue;
739 Gnew[v] = Gnew[v] + pb;
740 for (std::size_t k = 0; k < St.size(); ++k)
741 for (std::size_t j = 0; j < J; ++j)
742 Qnew[v](St[k], j) = Qnew[v](St[k], j) + lev.Q[iVL](St[k], j) * pb;
743 for (std::size_t j = 0; j < J; ++j) Tnew[v][j] = Tnew[v][j] + lev.Tp[iVL][j] * pb;
744 for (std::size_t q2 = 0; q2 < inner.size(); ++q2) {
745 const std::size_t i = inner[q2];
746 for (std::size_t j = 0; j < J; ++j)
747 Qnew[v](i, j) = Qnew[v](i, j) + Qin[iL](i, j) * pb;
748 anum[i] = anum[i] + Ain[iL][i] * pb;
749 }
750 }
751 if (Gnew[v] > zero) {
752 for (std::size_t i = 0; i < M; ++i)
753 for (std::size_t j = 0; j < J; ++j) Qnew[v](i, j) = Qnew[v](i, j) / Gnew[v];
754 for (std::size_t j = 0; j < J; ++j) Tnew[v][j] = Tnew[v][j] / Gnew[v];
755 // eq. (10) carries, besides delta_ti, the single-step omega ratios
756 // down to the centre's own level. Conditioning on the population of
757 // Q(V,V_t) fixes v_t, so that ratio leaves the expectation and the
758 // rest recurses through the same convolution as the queue lengths.
759 const T st = num_traits<T>::from_double(detail::sdr_omega_step(c, tt, vv));
760 for (std::size_t k = 0; k < St.size(); ++k) {
761 T qi = zero;
762 for (std::size_t j = 0; j < J; ++j) qi = qi + Qnew[v](St[k], j);
763 Anew[v][St[k]] = st * (num_traits<T>::from_double(dvec[St[k]]) - qi);
764 }
765 for (std::size_t q2 = 0; q2 < inner.size(); ++q2)
766 Anew[v][inner[q2]] = st * anum[inner[q2]] / Gnew[v];
767 }
768 }
769 Gin = Gnew;
770 Qin = Qnew;
771 Ain = Anew;
772 if (tt == 1) break;
773 }
774
775 // Sec. 4.3, at every population because the throughputs read N - 1_j
776 std::vector<Matrix<T>> Qall(nl, Matrix<T>(M, J, zero));
777 std::vector<std::vector<T>> Tall(nl, std::vector<T>(J, zero));
778 std::vector<std::vector<T>> Aall(nl, std::vector<T>(M, zero));
779 std::vector<T> Gall(nl, zero);
780 for (std::size_t v = 0; v < nl; ++v) {
781 const std::vector<std::size_t>& Np = latt[v];
782 const std::vector<std::vector<std::size_t>> sub = detail::sdr_lattice(Np);
783 for (std::size_t q = 0; q < sub.size(); ++q) {
784 std::vector<std::size_t> C2(J);
785 for (std::size_t j = 0; j < J; ++j) C2[j] = Np[j] - sub[q][j];
786 const std::size_t iV = detail::sdr_key(sub[q], N), iC = detail::sdr_key(C2, N);
787 const T pb = comp.g[iC] * Gin[iV];
788 if (pb == zero) continue;
789 Gall[v] = Gall[v] + pb;
790 for (std::size_t k = 0; k < mv.size(); ++k)
791 for (std::size_t j = 0; j < J; ++j)
792 Qall[v](mv[k], j) = Qall[v](mv[k], j) + comp.Q[iC](mv[k], j) * pb;
793 for (std::size_t i = 0; i < M; ++i) {
794 if (!inV[i]) continue;
795 for (std::size_t j = 0; j < J; ++j)
796 Qall[v](i, j) = Qall[v](i, j) + Qin[iV](i, j) * pb;
797 Aall[v][i] = Aall[v][i] + Ain[iV][i] * pb;
798 }
799 for (std::size_t j = 0; j < J; ++j) Tall[v][j] = Tall[v][j] + comp.Tp[iC][j] * pb;
800 }
801 if (Gall[v] > zero) {
802 for (std::size_t i = 0; i < M; ++i) {
803 for (std::size_t j = 0; j < J; ++j) Qall[v](i, j) = Qall[v](i, j) / Gall[v];
804 Aall[v][i] = Aall[v][i] / Gall[v];
805 }
806 for (std::size_t j = 0; j < J; ++j) Tall[v][j] = Tall[v][j] / Gall[v];
807 }
808 }
809
810 const std::size_t vN = detail::sdr_key(N, N);
811 SdrResult<T> res;
812 res.QN = Qall[vN];
813 res.XN = Matrix<T>(M, J, zero);
814 res.UN = Matrix<T>(M, J, zero);
815 res.RN = Matrix<T>(M, J, zero);
816 res.G = Gall[vN];
817 for (std::size_t j = 0; j < J; ++j) {
818 if (N[j] == 0) continue;
819 std::vector<std::size_t> Nm = N;
820 Nm[j] -= 1;
821 const std::size_t vm = detail::sdr_key(Nm, N);
822 for (std::size_t i = 0; i < M; ++i)
823 res.XN(i, j) = inV[i] ? xi(i, j) * Tall[vN][j] * Aall[vm][i]
824 : xi(i, j) * Tall[vN][j];
825 }
826 for (std::size_t i = 0; i < M; ++i)
827 for (std::size_t j = 0; j < J; ++j) {
828 res.UN(i, j) = res.XN(i, j) * S(i, j);
829 if (res.XN(i, j) > zero) res.RN(i, j) = res.QN(i, j) / res.XN(i, j);
830 }
831 return res;
832}
833
834/**
835 * Coefficients xi of Section 3.2.
836 *
837 * P holds one centre-by-centre state-independent routing matrix per chain.
838 * Three rules fix the coefficients: the complement M-V obeys the ordinary
839 * traffic equations with the whole SDR subnetwork collapsed into a single
840 * e -> d arc of probability one; every branch obeys its own traffic equations
841 * driven by an injection of xi_e at its entry centre; and xi_e is one.
842 *
843 * The paper states xi_ij = xi_ej for the branch entry and departure centres and
844 * works out only single-centre branches. The traffic equations above are the
845 * reading that extends it: they return xi at the branch departure equal to
846 * xi_e because a customer leaves a branch only through it, and xi at the branch
847 * entry equal to xi_e whenever that centre takes no internal feedback. They have
848 * been checked against a brute-force CTMC on a branch that does take such
849 * feedback, where the literal rule fails.
850 *
851 * These xi are NOT relative visit counts: the rate at which customers enter a
852 * branch is state dependent, so a ratio of two xi carries no flow meaning.
853 */
854template <class T>
855Matrix<T> pfqn_sdrvisits(const SdrStruct& sdr, const std::vector<Matrix<T>>& P) {
856 const SdrCoeff c = pfqn_sdrcoeff(sdr);
857 if (P.empty()) throw InputError("pfqn_sdrvisits: no routing matrix was supplied");
858 const std::size_t M = P[0].rows(), J = P.size();
859 if (P[0].cols() != M)
860 throw InputError("pfqn_sdrvisits: the SIR routing matrices must be square");
861 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
862 Matrix<T> xi(M, J, zero);
863
864 std::vector<bool> inV(M, false);
865 for (std::size_t b = 1; b < c.B; ++b)
866 for (std::size_t k = 0; k < sdr.branch[b].size(); ++k) inV[sdr.branch[b][k]] = true;
867 std::vector<std::size_t> mv;
868 for (std::size_t i = 0; i < M; ++i)
869 if (!inV[i]) mv.push_back(i);
870 std::size_t ie = mv.size(), id = mv.size();
871 for (std::size_t k = 0; k < mv.size(); ++k) {
872 if (mv[k] == sdr.entry) ie = k;
873 if (mv[k] == sdr.departure) id = k;
874 }
875 if (ie == mv.size() || id == mv.size())
876 throw InputError("pfqn_sdrvisits: the entry and departure centres of Q(V,V) must lie "
877 "outside every branch");
878
879 for (std::size_t j = 0; j < J; ++j) {
880 const std::size_t nm = mv.size();
881 // Complement M-V with the SDR subnetwork collapsed into the arc e -> d
882 Matrix<T> Pmv(nm, nm, zero);
883 for (std::size_t a = 0; a < nm; ++a) {
884 if (a == ie) {
885 Pmv(a, id) = one;
886 continue;
887 }
888 T rs = zero;
889 for (std::size_t b = 0; b < nm; ++b) {
890 Pmv(a, b) = P[j](mv[a], mv[b]);
891 rs = rs + Pmv(a, b);
892 }
893 if (!(rs > zero))
894 throw InputError("pfqn_sdrvisits: the SIR routing does not keep customers inside "
895 "the complement M-V");
896 }
897 std::vector<T> xmv = mc::dtmc_solve(Pmv);
898 if (!(xmv[ie] > zero))
899 throw InputError("pfqn_sdrvisits: the entry centre of Q(V,V) is unreachable");
900 for (std::size_t a = 0; a < nm; ++a) xi(mv[a], j) = xmv[a] / xmv[ie];
901
902 // Each branch, driven by an injection of xi_e at its entry centre:
903 // xi_Bb = xi_e e_{e(b)} (I - P_bb)^-1, written as the transposed solve
904 for (std::size_t b = 1; b < c.B; ++b) {
905 const std::vector<std::size_t>& sb = sdr.branch[b];
906 const std::size_t nb = sb.size();
907 Matrix<T> Ab(nb, nb, zero);
908 std::vector<T> rb(nb, zero);
909 for (std::size_t a = 0; a < nb; ++a) {
910 for (std::size_t k = 0; k < nb; ++k)
911 Ab(k, a) = ((a == k) ? one : zero) - P[j](sb[a], sb[k]);
912 if (sb[a] == sdr.entryOf[b]) rb[a] = xi(sdr.entry, j);
913 }
914 std::vector<T> sol = solve(Ab, rb);
915 for (std::size_t a = 0; a < nb; ++a) xi(sb[a], j) = sol[a];
916 }
917 }
918 return xi;
919}
920
921} // namespace pfqn
922} // namespace line
923
924#endif // LINE_API_PFQN_SDR_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
UnsupportedError(const std::string &what)
Definition error.h:51
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
std::vector< double > pfqn_sdrprob(const SdrCoeff &c, const std::vector< double > &n)
SDR routing probabilities of eq.
Definition pfqn_sdr.h:207
SdrResult< T > pfqn_sdr(const Matrix< T > &S, const Matrix< T > &xi, const std::vector< std::size_t > &N, const SdrStruct &sdr, const Matrix< T > &alpha=Matrix< T >())
Exact product form of eq.
Definition pfqn_sdr.h:304
SdrCoeff pfqn_sdrcoeff(const SdrStruct &sdr)
Validates an SDR structure and returns its derived coefficients.
Definition pfqn_sdr.h:105
double pfqn_sdrped(const std::vector< double > &P)
Probability of being denied entry and routed straight to the departure centre.
Definition pfqn_sdr.h:239
SdrResult< T > pfqn_sdrmva(const Matrix< T > &S, const Matrix< T > &xi, const std::vector< std::size_t > &N, const SdrStruct &sdr, const Matrix< T > &alpha=Matrix< T >())
Section 4 mean value analysis and convolution.
Definition pfqn_sdr.h:552
@ Ab
the Akyildiz-Bolch weight function
Matrix< T > pfqn_sdrvisits(const SdrStruct &sdr, const std::vector< Matrix< T > > &P)
Coefficients xi of Section 3.2.
Definition pfqn_sdr.h:855
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
Number-type abstraction for the templated API port.
Derived coefficients of an SDR structure, eqs.
Definition pfqn_sdr.h:81
std::vector< double > Dprev
D_{t-1,t} = sum over A_t of d_{t-1,b}; indices 0 and 1 unused.
Definition pfqn_sdr.h:89
std::size_t B
Definition pfqn_sdr.h:83
const SdrStruct * sdr
Definition pfqn_sdr.h:94
std::vector< std::vector< std::size_t > > inA
inA[t] holds the branch indices b with level[b] >= t, the set A_t.
Definition pfqn_sdr.h:85
std::vector< double > Dtt
D_tt = sum over A_t of d_tb; index 0 unused.
Definition pfqn_sdr.h:87
std::vector< double > vmax
Largest subnetwork population with omega nonnegative; -1 marks unbounded.
Definition pfqn_sdr.h:93
std::vector< double > mmax
Largest branch population with delta nonnegative; -1 marks unbounded.
Definition pfqn_sdr.h:91
std::size_t T
Definition pfqn_sdr.h:82
Mean performance measures returned by pfqn_sdr.
Definition pfqn_sdr.h:247
T G
normalizing constant
Definition pfqn_sdr.h:252
Matrix< T > XN
per-centre chain throughputs
Definition pfqn_sdr.h:249
Matrix< T > QN
mean queue lengths, centres by chains
Definition pfqn_sdr.h:248
Matrix< T > RN
mean response times at the centre, QN elementwise over XN
Definition pfqn_sdr.h:251
Matrix< T > UN
mean number in service, XN elementwise times S
Definition pfqn_sdr.h:250
Topology and coefficients of a state-dependent routing subnetwork.
Definition pfqn_sdr.h:59
std::vector< std::size_t > departureOf
departureOf[b] is the departure centre d(b) of branch b.
Definition pfqn_sdr.h:69
bool empty() const
Definition pfqn_sdr.h:77
std::vector< std::size_t > entryOf
entryOf[b] is the entry centre e(b) of branch b.
Definition pfqn_sdr.h:67
std::vector< std::size_t > level
level[b] is the unique t with B_b in V_t - V_{t+1}; level[0] is unused.
Definition pfqn_sdr.h:71
Matrix< double > d
Coefficients d_tb of eq.
Definition pfqn_sdr.h:75
std::vector< double > C
Coefficients C_t of eq.
Definition pfqn_sdr.h:73
std::size_t departure
Departure centre d of Q(V,V); may equal entry.
Definition pfqn_sdr.h:63
std::vector< std::vector< std::size_t > > branch
branch[b] holds the centres of branch b, b >= 1; branch[0] is unused.
Definition pfqn_sdr.h:65
std::size_t entry
Entry centre e of Q(V,V).
Definition pfqn_sdr.h:61