LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_debemva.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_DEBEMVA_H
6#define LINE_API_PFQN_PFQN_DEBEMVA_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * debeMVA: MVA-based approximation of closed networks with finite (BAS) buffers.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_debemva.m, i.e. debeMVA (Algorithms
14 * 1 and 2) of V. de Nitto Persone and A. Di Lonardo, "Approximating finite
15 * resources: An approach based on MVA", Performance Evaluation 131:1-21, 2019.
16 *
17 * Closed single-class network of M stations. A queue is a single exponential
18 * server with total capacity K[i]; a delay is an infinite server of any
19 * distribution, never full. Blocking is Blocking-After-Service with
20 * first-blocked-first-unblocked release.
21 *
22 * MVA on k = 1..N with residence time S*(z + n(k-1)) + BT at a queue and
23 * S + BT/k at a delay. The arrival job function z (start 1) and the blocking
24 * time BT (start 0) persist across populations. Algorithm 2 first adjusts them
25 * around the slowest finite stations once k > K[j]+1; each pass then moves the
26 * share 1-K[j]/k of z[j] upstream for every station with n[j] > K[j]+tol,
27 * charges the blocking time P(i,j)*S[j]^2/(S[i]+S[j]), and restores any queue
28 * that fell below its value at k-1 by taking the difference from its upstream
29 * stations. tol = 0 is the paper's strict test. A capacity K[i] >= N is
30 * treated as unbounded.
31 *
32 * Results follow the paper, so a blocked job is counted at the SOURCE, whereas
33 * LINE's CTMC/JMT/LDES count it at the full destination.
34 *
35 * ARITHMETIC. Only field operations and comparisons, so every T is admitted,
36 * Rational included. Capacities are doubles because an unbounded one is +inf.
37 */
38
39#include <cmath>
40#include <cstddef>
41#include <limits>
42#include <string>
43#include <utility>
44#include <vector>
45
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace pfqn {
52
53/** Return value of pfqn_debemva, mirroring [X,Q,U,R,BT,z,theta,iter,converged]; vectors are (M). */
54template <class T>
56 std::vector<T> X; ///< throughput of each station
57 std::vector<T> Q; ///< mean number of jobs, blocked jobs at the source
58 std::vector<T> U; ///< utilization X.*S, blocked time excluded
59 std::vector<T> R; ///< mean residence time per visit, blocking time included
60 std::vector<T> BT; ///< blocking time accumulated at each station
61 std::vector<T> z; ///< arrival job function at each station
62 long theta = 0; ///< number of capacity violations corrected (the paper's theta)
63 long iter = 0; ///< total number of passes over all populations
64 bool converged = true; ///< false if a population exhausted iter_max passes
65};
66
67/**
68 * Runs debeMVA.
69 *
70 * @param N population (positive integer)
71 * @param S (M) mean service time per visit
72 * @param P (M x M) station routing matrix, row-stochastic and irreducible
73 * @param K (M) capacity, +inf for unbounded; ignored at delays
74 * @param isdelay (M) true at infinite-server stations, empty for none
75 * @param tol slack on the capacity test n[j] > K[j]+tol (the paper: 0)
76 * @param iter_max maximum passes at one population
77 */
78template <class T>
79DebemvaResult<T> pfqn_debemva(long N, const std::vector<T>& S, const Matrix<T>& P,
80 const std::vector<double>& K, std::vector<bool> isdelay,
81 double tol = 0.0, long iter_max = 100000) {
82 using nt = num_traits<T>;
83 const std::size_t M = S.size();
84 const T zero = nt::from_int(0);
85 const T one = nt::from_int(1);
86 const double inf = std::numeric_limits<double>::infinity();
87 if (isdelay.empty()) isdelay.assign(M, false);
88 const std::string caller = "pfqn_debemva: ";
89 if (N <= 0)
90 throw InputError(ErrorKind{"Argument"}, caller + "debeMVA requires a positive integer population N.");
91 if (P.rows() != M || P.cols() != M || K.size() != M || isdelay.size() != M)
92 throw InputError(ErrorKind{"Argument"},
93 caller + "debeMVA requires S, K and isdelay of length M and an M x M routing matrix P.");
94 std::vector<double> B(M, inf);
95 for (std::size_t i = 0; i < M; ++i) {
96 const double s = nt::to_double(S[i]);
97 if (!(s >= 0.0) || !std::isfinite(s))
98 throw InputError(ErrorKind{"Argument"}, caller + "debeMVA requires finite nonnegative service times.");
99 if (!isdelay[i] && !(K[i] >= 1.0))
100 throw InputError(ErrorKind{"Argument"},
101 caller + "debeMVA requires every queue capacity K(i) to be at least 1.");
102 B[i] = (isdelay[i] || K[i] >= static_cast<double>(N)) ? inf : K[i];
103 double row = 0.0;
104 for (std::size_t j = 0; j < M; ++j) {
105 const double p = nt::to_double(P(i, j));
106 if (p < 0.0)
107 throw InputError(ErrorKind{"Argument"}, caller + "debeMVA requires a row-stochastic routing matrix P.");
108 row += p;
109 }
110 if (std::fabs(row - 1.0) > 1e-8)
111 throw InputError(ErrorKind{"Argument"}, caller + "debeMVA requires a row-stochastic routing matrix P.");
112 }
113 std::vector<std::vector<bool>> reach(M, std::vector<bool>(M, false));
114 for (std::size_t i = 0; i < M; ++i)
115 for (std::size_t j = 0; j < M; ++j) reach[i][j] = i == j || P(i, j) > zero;
116 const int squarings = static_cast<int>(std::ceil(std::log2(static_cast<double>(M < 2 ? 2 : M)))) + 1;
117 for (int h = 0; h < squarings; ++h) {
118 std::vector<std::vector<bool>> next(M, std::vector<bool>(M, false));
119 for (std::size_t i = 0; i < M; ++i)
120 for (std::size_t j = 0; j < M; ++j) {
121 bool r = reach[i][j];
122 for (std::size_t l = 0; l < M && !r; ++l) r = reach[i][l] && reach[l][j];
123 next[i][j] = r;
124 }
125 reach.swap(next);
126 }
127 for (std::size_t i = 0; i < M; ++i)
128 for (std::size_t j = 0; j < M; ++j)
129 if (!reach[i][j])
130 throw InputError(ErrorKind{"Argument"},
131 caller + "debeMVA requires a routing matrix that is irreducible on the stations.");
132 // minimum total capacity of a directed cycle of finite stations (Floyd-Warshall)
133 std::vector<std::vector<double>> W(M, std::vector<double>(M, inf));
134 for (std::size_t i = 0; i < M; ++i)
135 for (std::size_t j = 0; j < M; ++j)
136 if (i != j && P(i, j) > zero && std::isfinite(B[j])) W[i][j] = B[j];
137 for (std::size_t h = 0; h < M; ++h)
138 for (std::size_t i = 0; i < M; ++i)
139 for (std::size_t j = 0; j < M; ++j)
140 if (W[i][h] + W[h][j] < W[i][j]) W[i][j] = W[i][h] + W[h][j];
141 double cyc = inf;
142 for (std::size_t i = 0; i < M; ++i)
143 if (W[i][i] < cyc) cyc = W[i][i];
144 if (static_cast<double>(N) >= cyc)
145 throw UnsupportedError(ErrorKind{"Structure"},
146 caller + "debeMVA requires N below the total capacity " + std::to_string(cyc) +
147 " of every cycle of finite stations, to rule out a deadlock of the blocking network; N = " +
148 std::to_string(N) + ".");
149
150 // visit ratios e = e*P, normalized at station 1; every output is invariant to the scale.
151 // A = P' - I with row 1 replaced by e_1, solved by Gaussian elimination with partial pivoting.
152 Matrix<T> A(M, M, zero);
153 std::vector<T> e(M, zero);
154 for (std::size_t i = 0; i < M; ++i)
155 for (std::size_t j = 0; j < M; ++j) A(i, j) = T(P(j, i) - (i == j ? one : zero));
156 for (std::size_t j = 0; j < M; ++j) A(0, j) = j == 0 ? one : zero;
157 e[0] = one;
158 for (std::size_t c = 0; c < M; ++c) {
159 std::size_t piv = c;
160 for (std::size_t r = c + 1; r < M; ++r)
161 if (std::fabs(nt::to_double(A(r, c))) > std::fabs(nt::to_double(A(piv, c)))) piv = r;
162 if (piv != c) {
163 for (std::size_t j = 0; j < M; ++j) std::swap(A(c, j), A(piv, j));
164 std::swap(e[c], e[piv]);
165 }
166 for (std::size_t r = 0; r < M; ++r) {
167 if (r == c || A(r, c) == zero) continue;
168 const T f = T(A(r, c) / A(c, c));
169 for (std::size_t j = c; j < M; ++j) A(r, j) = T(A(r, j) - f * A(c, j));
170 e[r] = T(e[r] - f * e[c]);
171 }
172 }
173 for (std::size_t i = 0; i < M; ++i) e[i] = T(e[i] / A(i, i));
174
175 // capacities as T, valid where B is finite
176 std::vector<T> Bt(M, zero);
177 std::vector<bool> fin(M, false);
178 std::vector<int> nd(M, 0);
179 bool anyFin = false;
180 T smax = zero;
181 for (std::size_t i = 0; i < M; ++i) {
182 fin[i] = std::isfinite(B[i]);
183 if (fin[i]) {
184 Bt[i] = nt::from_double(B[i]);
185 if (!anyFin || S[i] > smax) smax = S[i];
186 anyFin = true;
187 }
188 for (std::size_t l = 0; l < M; ++l)
189 if (P(i, l) > zero) ++nd[i];
190 }
191 std::vector<bool> slow(M, false);
192 for (std::size_t i = 0; i < M; ++i) slow[i] = fin[i] && S[i] == smax;
193 const T tolT = nt::from_double(tol);
194
196 std::vector<T> z(M, one), bt(M, zero), n(M, zero), t(S), X(M, zero), beta(M, zero);
197 std::vector<bool> first(M, false);
198 for (long k = 1; k <= N; ++k) {
199 const std::vector<T> nprev = n;
200 const T kT = nt::from_int(k);
201 for (std::size_t j = 0; j < M; ++j)
202 beta[j] = (fin[j] && B[j] < static_cast<double>(k)) ? T(one - Bt[j] / kT) : zero;
203 // Algorithm 2: initialization around the slowest finite stations
204 for (std::size_t j = 0; j < M; ++j) {
205 if (slow[j] && static_cast<double>(k) > B[j] + 1.0 && !first[j]) {
206 first[j] = true;
207 for (std::size_t i = 0; i < M; ++i)
208 if (i != j && P(i, j) > zero) {
209 z[i] = T(z[i] + P(i, j) * z[j] * beta[j]);
210 bt[i] = T(bt[i] + P(i, j) * S[j] * T(S[j] / T(S[j] + S[i])));
211 }
212 z[j] = T(z[j] - z[j] * beta[j]);
213 }
214 }
215 for (std::size_t j = 0; j < M; ++j) {
216 if (!(slow[j] && static_cast<double>(k) > B[j] + 1.0)) continue;
217 for (std::size_t i = 0; i < M; ++i) {
218 if (i == j || !(P(i, j) > zero) || nd[i] <= 1) continue;
219 for (std::size_t l = 0; l < M; ++l)
220 if (l != j && P(i, l) > zero) z[l] = T(z[l] + z[i] * beta[j] * P(i, l));
221 z[i] = T(z[i] - z[i] * beta[j]);
222 }
223 }
224 // Algorithm 1: passes until no capacity is exceeded
225 long passes = 0;
226 while (true) {
227 ++passes;
228 ++res.iter;
229 T denom = zero;
230 for (std::size_t i = 0; i < M; ++i) {
231 t[i] = isdelay[i] ? T(S[i] + bt[i] / kT) : T(S[i] * (z[i] + nprev[i]) + bt[i]);
232 denom = T(denom + e[i] * t[i]);
233 }
234 for (std::size_t i = 0; i < M; ++i) {
235 X[i] = T(kT * e[i] / denom);
236 n[i] = T(t[i] * X[i]);
237 }
238 bool rep = false;
239 for (std::size_t j = 0; j < M; ++j) {
240 if (fin[j] && n[j] > T(Bt[j] + tolT)) {
241 rep = true;
242 ++res.theta;
243 for (std::size_t i = 0; i < M; ++i)
244 if (i != j && P(i, j) > zero && (isdelay[i] || !fin[i] || n[i] < Bt[i])) {
245 z[i] = T(z[i] + P(i, j) * z[j] * beta[j]);
246 bt[i] = T(bt[i] + P(i, j) * S[j] * T(S[j] / T(S[j] + S[i])));
247 }
248 z[j] = T(z[j] - z[j] * beta[j]);
249 } else if (n[j] < nprev[j]) {
250 const T d = T(nprev[j] - n[j]);
251 n[j] = nprev[j];
252 T den = zero;
253 for (std::size_t i = 0; i < M; ++i)
254 if (i != j && P(i, j) > zero) den = T(den + P(i, j));
255 if (den > zero)
256 for (std::size_t i = 0; i < M; ++i)
257 if (i != j && P(i, j) > zero) n[i] = T(n[i] - P(i, j) * d / den);
258 }
259 }
260 if (!rep) break;
261 if (passes >= iter_max) {
262 res.converged = false;
263 break;
264 }
265 }
266 }
267 res.X = X;
268 res.Q = n;
269 res.R = t;
270 res.BT = bt;
271 res.z = z;
272 res.U.assign(M, zero);
273 for (std::size_t i = 0; i < M; ++i) res.U[i] = T(X[i] * S[i]);
274 return res;
275}
276
277} // namespace pfqn
278} // namespace line
279
280#endif // LINE_API_PFQN_PFQN_DEBEMVA_H
InputError(const std::string &what)
Definition error.h:105
UnsupportedError(const std::string &what)
Definition error.h:119
The exception types the port throws.
Dense matrix and non-owning view.
DebemvaResult< T > pfqn_debemva(long N, const std::vector< T > &S, const Matrix< T > &P, const std::vector< double > &K, std::vector< bool > isdelay, double tol=0.0, long iter_max=100000)
Runs debeMVA.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Return value of pfqn_debemva, mirroring [X,Q,U,R,BT,z,theta,iter,converged]; vectors are (M).
std::vector< T > z
arrival job function at each station
std::vector< T > Q
mean number of jobs, blocked jobs at the source
std::vector< T > U
utilization X.*S, blocked time excluded
std::vector< T > X
throughput of each station
std::vector< T > R
mean residence time per visit, blocking time included
long iter
total number of passes over all populations
long theta
number of capacity violations corrected (the paper's theta)
std::vector< T > BT
blocking time accumulated at each station
bool converged
false if a population exhausted iter_max passes