LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_oqnrc.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_PFQN_OQNRC_H
6#define LINE_API_PFQN_PFQN_OQNRC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * OQNRC: approximate MVA for open queueing networks with restricted capacity.
12 *
13 * Port of matlab/src/api/pfqn/pfqn_oqnrc.m, i.e. the algorithm of
14 * R. Buitenhek, G.-J. van Houtum, H. Zijm, "Approximate MVA algorithms for open
15 * queueing networks with restricted capacity", University of Twente, 1997.
16 *
17 * An open network whose inner system holds at most Ncap[g] jobs of the classes
18 * in token pool g. Blocked arrivals wait outside (WAITQ, the paper) or are lost
19 * (DROP). Each pool is a closed token chain with a load-dependent
20 * synchronization station, nu(1) = lam_g/q_g, q_g = 1 - lam_g/TH_g^C(N_g)
21 * (q_g = 1 under DROP), solved by the approximate MVA recursion of the paper's
22 * Appendix over the population lattice. FCFS multi-server stations use the
23 * residual-service approximation, PS and INF are exact.
24 *
25 * ARITHMETIC. double only: the recursion is an approximation and the WAITQ
26 * external queue needs a geometric tail.
27 */
28
29#include <algorithm>
30#include <cmath>
31#include <cstddef>
32#include <cstdio>
33#include <string>
34#include <vector>
35
36#include "line/util/error.h"
37#include "line/util/matrix.h"
38
39namespace line {
40namespace pfqn {
41
42/** Result of pfqn_oqnrc. Station matrices are M x R, class vectors length R. */
44 Matrix<double> Q, T, U, R; ///< queue length, throughput, utilization, response time per visit
45 std::vector<double> X; ///< carried throughput of each class
46 std::vector<double> Qext; ///< mean class-r arrivals waiting outside (WAITQ)
47 std::vector<double> Ploss; ///< loss probability of each class (DROP)
48 int iter = 0; ///< multi-pool fixed point iterations
49};
50
51namespace oqnrc_detail {
52
53/** Approximate MVA over the population lattice 0..Npop (pool 0 fastest). */
54struct Lattice {
55 const std::vector<double>& lamg;
56 const std::vector<std::vector<double>>& D;
57 const std::vector<std::vector<double>>& V;
58 const std::vector<std::vector<double>>& ES;
59 const std::vector<std::vector<double>>& ESrem;
61 const std::vector<int>& pool;
62 const std::vector<double>& c;
63 const std::vector<bool>& isdelay;
64 const std::vector<bool>& isps;
65 std::vector<std::vector<double>> TH;
66 std::vector<std::size_t> stride;
67 std::vector<std::vector<double>> ETr;
68
69 void run(const std::vector<int>& Npop, const std::vector<bool>& hasSync, const std::vector<double>& q,
70 bool wantET) {
71 const std::size_t M = D.size();
72 const std::size_t G = Npop.size();
73 const std::size_t Rn = pool.size();
74 stride.assign(G, 0);
75 std::size_t P = 1;
76 for (std::size_t g = 0; g < G; ++g) {
77 stride[g] = P;
78 P *= static_cast<std::size_t>(Npop[g] + 1);
79 }
80 int cmax = 1;
81 for (std::size_t m = 0; m < M; ++m)
82 if (!isdelay[m]) cmax = std::max(cmax, static_cast<int>(c[m]));
83 const std::size_t cm1 = static_cast<std::size_t>(cmax);
84 // flat storage: pm[(idx*M + m)*cmax + l], Qh/ELh[(idx*M + m)*G + h]
85 std::vector<double> pm(P * M * cm1, 0.0), Qh(P * M * G, 0.0), ELh(P * M * G, 0.0);
86 for (std::size_t m = 0; m < M; ++m) pm[m * cm1] = 1.0;
87 std::vector<double> sp0(P * G, 1.0), sQ(P * G, 0.0), sL(P * G, 0.0);
88 TH.assign(P, std::vector<double>(G, 0.0));
89 ETr.assign(M, std::vector<double>(Rn, 0.0));
90 auto qh = [&](std::size_t i, std::size_t m, std::size_t h) -> double& { return Qh[(i * M + m) * G + h]; };
91 auto el = [&](std::size_t i, std::size_t m, std::size_t h) -> double& { return ELh[(i * M + m) * G + h]; };
92 auto pp = [&](std::size_t i, std::size_t m, std::size_t l) -> double& { return pm[(i * M + m) * cm1 + l]; };
93 std::vector<int> n(G, 0);
94 for (std::size_t idx = 1; idx < P; ++idx) {
95 std::size_t rr = idx;
96 for (std::size_t gg = G; gg-- > 0;) {
97 n[gg] = static_cast<int>(rr / stride[gg]);
98 rr -= static_cast<std::size_t>(n[gg]) * stride[gg];
99 }
100 for (std::size_t g = 0; g < G; ++g) {
101 if (n[g] == 0) continue;
102 const std::size_t prev = idx - stride[g];
103 double den = 0.0;
104 for (std::size_t m = 0; m < M; ++m) {
105 if (V[m][g] <= 0) continue;
106 double base = 0.0, fac = 1.0;
107 if (isdelay[m]) {
108 // exact: no queueing
109 } else if (isps[m]) {
110 double tot = 0;
111 for (std::size_t h = 0; h < G; ++h) tot += qh(prev, m, h) + el(prev, m, h);
112 fac = 1 + tot / c[m];
113 } else {
114 for (std::size_t h = 0; h < G; ++h)
115 base += qh(prev, m, h) * ESrem[m][h] + el(prev, m, h) * ES[m][h] / c[m];
116 }
117 den += V[m][g] * (base + fac * ES[m][g]);
118 if (wantET && idx == P - 1)
119 for (std::size_t r = 0; r < Rn; ++r)
120 if (static_cast<std::size_t>(pool[r]) == g) ETr[m][r] = base + fac * S(m, r);
121 }
122 if (hasSync[g])
123 den += (sL[prev * G + g] + sQ[prev * G + g] + sp0[prev * G + g] * q[g]) / lamg[g];
124 TH[idx][g] = n[g] / den;
125 }
126 for (std::size_t m = 0; m < M; ++m) {
127 if (isdelay[m]) continue;
128 const int cm = static_cast<int>(c[m]);
129 for (std::size_t h = 0; h < G; ++h) {
130 if (n[h] == 0 || D[m][h] <= 0) continue;
131 const std::size_t prev = idx - stride[h];
132 double Qp = 0, ELp = 0;
133 for (std::size_t k = 0; k < G; ++k) {
134 Qp += qh(prev, m, k);
135 ELp += el(prev, m, k);
136 }
137 const double coef = D[m][h] * TH[idx][h];
138 qh(idx, m, h) = coef / cm * (Qp + pp(prev, m, static_cast<std::size_t>(cm - 1)));
139 el(idx, m, h) = coef / cm * (ELp + Qp);
140 for (int l = 1; l < cm; ++l)
141 pp(idx, m, static_cast<std::size_t>(l)) += coef / l * pp(prev, m, static_cast<std::size_t>(l - 1));
142 }
143 double s = 0;
144 for (int l = 1; l < cm; ++l) s += pp(idx, m, static_cast<std::size_t>(l));
145 for (std::size_t h = 0; h < G; ++h) s += qh(idx, m, h);
146 pp(idx, m, 0) = std::max(0.0, 1 - s);
147 }
148 for (std::size_t g = 0; g < G; ++g) {
149 if (hasSync[g] && n[g] > 0) {
150 const std::size_t prev = idx - stride[g];
151 const double ETs = (sL[prev * G + g] + sQ[prev * G + g] + sp0[prev * G + g] * q[g]) / lamg[g];
152 sQ[idx * G + g] =
153 std::min(1.0, TH[idx][g] / lamg[g] * (sQ[prev * G + g] + sp0[prev * G + g] * q[g]));
154 sL[idx * G + g] = TH[idx][g] * ETs;
155 sp0[idx * G + g] = 1 - sQ[idx * G + g];
156 }
157 }
158 }
159 }
160};
161
162} // namespace oqnrc_detail
163
164/**
165 * Runs OQNRC.
166 *
167 * @param lam external arrival rate of each class (length R)
168 * @param A arrival rate of class r at station m (M x R)
169 * @param S mean service time (M x R)
170 * @param SCV squared coefficient of variation of the service time (M x R)
171 * @param nservers servers per station, +inf at a delay (length M)
172 * @param isps true at processor-sharing stations (length M)
173 * @param pool token pool of each class, 0-based (length R)
174 * @param Ncap tokens of each pool (length G)
175 * @param waitq true when blocked arrivals wait, false when they are lost
176 * @param tol multi-pool fixed point tolerance
177 * @param iter_max multi-pool fixed point iteration cap
178 */
179inline OqnrcResult pfqn_oqnrc(const std::vector<double>& lam, const Matrix<double>& A, const Matrix<double>& S,
180 const Matrix<double>& SCV, const std::vector<double>& nservers,
181 const std::vector<bool>& isps, const std::vector<int>& pool,
182 const std::vector<int>& Ncap, bool waitq, double tol, int iter_max) {
183 const std::size_t M = A.rows();
184 const std::size_t R = A.cols();
185 const std::size_t G = Ncap.size();
186 for (std::size_t g = 0; g < G; ++g)
187 if (Ncap[g] < 1)
188 throw InputError(ErrorKind{"Argument"},
189 "pfqn_oqnrc: Every token pool needs a positive integer number of tokens.");
190 std::vector<double> lamg(G, 0.0);
191 for (std::size_t r = 0; r < R; ++r) lamg[static_cast<std::size_t>(pool[r])] += lam[r];
192 for (std::size_t g = 0; g < G; ++g)
193 if (!(lamg[g] > 0))
194 throw InputError(ErrorKind{"Argument"}, "pfqn_oqnrc: Every token pool needs a positive arrival rate.");
195 std::vector<bool> isdelay(M, false);
196 for (std::size_t m = 0; m < M; ++m) isdelay[m] = std::isinf(nservers[m]);
197 std::vector<std::vector<double>> D(M, std::vector<double>(G, 0.0)), V = D, ES = D, ESrem = D;
198 for (std::size_t g = 0; g < G; ++g)
199 for (std::size_t m = 0; m < M; ++m) {
200 double asum = 0, as = 0, as2 = 0;
201 for (std::size_t r = 0; r < R; ++r) {
202 if (static_cast<std::size_t>(pool[r]) != g) continue;
203 const double a = A(m, r), s = S(m, r);
204 asum += a;
205 as += a * s;
206 as2 += a * (1 + SCV(m, r)) * s * s;
207 }
208 if (asum <= 0) continue;
209 V[m][g] = asum / lamg[g];
210 D[m][g] = as / lamg[g];
211 ES[m][g] = D[m][g] / V[m][g];
212 if (!isdelay[m] && ES[m][g] > 0) {
213 const double c = nservers[m];
214 const double ES2 = as2 / asum;
215 ESrem[m][g] = (c - 1) / (c + 1) * ES[m][g] / c + 2 / (c + 1) * (1 / c) * ES2 / (2 * ES[m][g]);
216 }
217 }
218 oqnrc_detail::Lattice cs{lamg, D, V, ES, ESrem, S, pool, nservers, isdelay, isps, {}, {}, {}};
219
220 std::vector<double> q(G, 1.0);
221 std::vector<std::vector<double>> THC(G);
222 int it = 0;
223 if (waitq) {
224 for (it = 1; it <= iter_max; ++it) {
225 std::vector<double> qnew = q;
226 for (std::size_t g = 0; g < G; ++g) {
227 std::vector<bool> hasSync(G, true);
228 hasSync[g] = false;
229 cs.run(Ncap, hasSync, q, false);
230 std::size_t base = 0;
231 for (std::size_t h = 0; h < G; ++h)
232 if (h != g) base += static_cast<std::size_t>(Ncap[h]) * cs.stride[h];
233 THC[g].assign(static_cast<std::size_t>(Ncap[g]), 0.0);
234 for (int k = 1; k <= Ncap[g]; ++k)
235 THC[g][static_cast<std::size_t>(k - 1)] = cs.TH[base + static_cast<std::size_t>(k) * cs.stride[g]][g];
236 const double thN = THC[g].back();
237 if (lamg[g] >= thN * (1 - 1e-12)) {
238 char buf[320];
239 std::snprintf(buf, sizeof(buf),
240 "pfqn_oqnrc: Token pool %zu is unstable: its arrival rate %g is not below the "
241 "throughput %g of the region holding N=%d tokens, so the queue of waiting "
242 "arrivals grows without bound.",
243 g + 1, lamg[g], thN, Ncap[g]);
244 throw NumericError(ErrorKind{"Instability"}, buf);
245 }
246 qnew[g] = 1 - lamg[g] / thN;
247 }
248 double delta = 0;
249 for (std::size_t g = 0; g < G; ++g) delta = std::max(delta, std::fabs(qnew[g] - q[g]));
250 q = qnew;
251 if (G == 1 || delta < tol) break;
252 }
253 if (it > iter_max) it = iter_max;
254 }
255 cs.run(Ncap, std::vector<bool>(G, true), q, true);
256 const std::vector<double> THN = cs.TH.back();
257
258 std::vector<double> Xg(G, 0.0), Qg(G, 0.0), Pg(G, 0.0);
259 for (std::size_t g = 0; g < G; ++g) {
260 if (waitq) {
261 Xg[g] = lamg[g];
262 double pik = 1.0, sum = 0.0;
263 for (int k = 0; k < Ncap[g]; ++k) {
264 sum += pik;
265 pik *= lamg[g] / THC[g][static_cast<std::size_t>(k)];
266 }
267 const double rho = lamg[g] / THC[g].back();
268 const double Z = sum + pik / (1 - rho);
269 Qg[g] = pik * rho / ((1 - rho) * (1 - rho)) / Z;
270 } else {
271 Xg[g] = THN[g];
272 Pg[g] = std::max(0.0, 1 - THN[g] / lamg[g]);
273 }
274 }
275 OqnrcResult res;
276 res.Q = Matrix<double>(M, R, 0.0);
277 res.T = Matrix<double>(M, R, 0.0);
278 res.U = Matrix<double>(M, R, 0.0);
279 res.R = Matrix<double>(M, R, 0.0);
280 res.X.assign(R, 0.0);
281 res.Qext.assign(R, 0.0);
282 res.Ploss.assign(R, 0.0);
283 for (std::size_t r = 0; r < R; ++r) {
284 const std::size_t g = static_cast<std::size_t>(pool[r]);
285 for (std::size_t m = 0; m < M; ++m) {
286 const double t = A(m, r) * Xg[g] / lamg[g];
287 const double qq = THN[g] * A(m, r) / lamg[g] * cs.ETr[m][r];
288 res.T(m, r) = t;
289 res.Q(m, r) = qq;
290 res.U(m, r) = t * S(m, r) / (isdelay[m] ? 1.0 : nservers[m]);
291 res.R(m, r) = t > 0 ? qq / t : 0.0;
292 }
293 res.X[r] = lam[r] * Xg[g] / lamg[g];
294 res.Qext[r] = Qg[g] * lam[r] / lamg[g];
295 res.Ploss[r] = Pg[g];
296 }
297 res.iter = it;
298 return res;
299}
300
301} // namespace pfqn
302} // namespace line
303
304#endif // LINE_API_PFQN_PFQN_OQNRC_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 matrix and non-owning view.
OqnrcResult pfqn_oqnrc(const std::vector< double > &lam, const Matrix< double > &A, const Matrix< double > &S, const Matrix< double > &SCV, const std::vector< double > &nservers, const std::vector< bool > &isps, const std::vector< int > &pool, const std::vector< int > &Ncap, bool waitq, double tol, int iter_max)
Runs OQNRC.
Definition pfqn_oqnrc.h:179
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Result of pfqn_oqnrc.
Definition pfqn_oqnrc.h:43
Matrix< double > Q
Definition pfqn_oqnrc.h:44
int iter
multi-pool fixed point iterations
Definition pfqn_oqnrc.h:48
std::vector< double > Ploss
loss probability of each class (DROP)
Definition pfqn_oqnrc.h:47
std::vector< double > X
carried throughput of each class
Definition pfqn_oqnrc.h:45
Matrix< double > U
Definition pfqn_oqnrc.h:44
Matrix< double > R
queue length, throughput, utilization, response time per visit
Definition pfqn_oqnrc.h:44
Matrix< double > T
Definition pfqn_oqnrc.h:44
std::vector< double > Qext
mean class-r arrivals waiting outside (WAITQ)
Definition pfqn_oqnrc.h:46
Approximate MVA over the population lattice 0..Npop (pool 0 fastest).
Definition pfqn_oqnrc.h:54
const std::vector< double > & lamg
Definition pfqn_oqnrc.h:55
const std::vector< std::vector< double > > & D
Definition pfqn_oqnrc.h:56
const std::vector< std::vector< double > > & V
Definition pfqn_oqnrc.h:57
std::vector< std::vector< double > > TH
Definition pfqn_oqnrc.h:65
std::vector< std::vector< double > > ETr
Definition pfqn_oqnrc.h:67
const std::vector< std::vector< double > > & ES
Definition pfqn_oqnrc.h:58
const std::vector< bool > & isdelay
Definition pfqn_oqnrc.h:63
const std::vector< int > & pool
Definition pfqn_oqnrc.h:61
std::vector< std::size_t > stride
Definition pfqn_oqnrc.h:66
const std::vector< std::vector< double > > & ESrem
Definition pfqn_oqnrc.h:59
void run(const std::vector< int > &Npop, const std::vector< bool > &hasSync, const std::vector< double > &q, bool wantET)
Definition pfqn_oqnrc.h:69
const std::vector< bool > & isps
Definition pfqn_oqnrc.h:64
const Matrix< double > & S
Definition pfqn_oqnrc.h:60
const std::vector< double > & c
Definition pfqn_oqnrc.h:62