LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_mvablo.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_MVABLO_H
6#define LINE_API_PFQN_PFQN_MVABLO_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * MVABLO: approximate MVA for closed networks with Blocking-After-Service (BAS).
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_mvablo.m, i.e. MVABLO of
14 * I. F. Akyildiz, "Mean Value Analysis for Blocking Queueing Networks",
15 * IEEE TSE 14(4):418-428, 1988 (Sec. III-IV).
16 *
17 * Closed single-class network of M stations with exponential service. A queue is
18 * a single server with total capacity K[i] (waiting plus in service). Blocking is
19 * Blocking-After-Service (BAS): a job completing service at j whose next station
20 * i is full stays at j, and j's server stops, until i releases a job. Delay (infinite-server) stations are a LINE extension: no
21 * queueing term, never full, but they can be blocked.
22 *
23 * The population recursion is exact MVA until a mean queue length exceeds its
24 * capacity, Q[i] > K[i]+tol. The pass is then repeated with station i marked full
25 * (its arriving job's own "+1" term is dropped, eq. 15) and each upstream station
26 * j charged the blocking time S[i]*P(j,i)*e[j]/e[i] (eq. 14). Passes repeat until
27 * no capacity is exceeded; the full marks and blocking times carry over to the
28 * next population, as in the paper.
29 *
30 * Results follow the paper, so a blocked job is counted at the SOURCE, whereas
31 * LINE's CTMC/JMT/LDES count it at the full destination.
32 *
33 * ARITHMETIC. Only field operations and comparisons, so every T is admitted,
34 * Rational included. Capacities are doubles because an unbounded one is +inf.
35 */
36
37#include <cmath>
38#include <cstddef>
39#include <limits>
40#include <string>
41#include <utility>
42#include <vector>
43
44#include "line/num/number.h"
45#include "line/util/error.h"
46#include "line/util/matrix.h"
47
48namespace line {
49namespace pfqn {
50
51/** Return value of pfqn_mvablo, mirroring [X,Q,U,R,BT,iter,converged]; vectors are (M). */
52template <class T>
54 std::vector<T> X; ///< throughput of each station
55 std::vector<T> Q; ///< mean number of jobs, blocked jobs at the source
56 std::vector<T> U; ///< utilization X.*S, blocked time excluded
57 std::vector<T> R; ///< mean residence time per visit, blocking time included
58 Matrix<T> BT; ///< (M x M) per-visit blocking time at j caused by a full i
59 int iter = 0; ///< total number of passes over all populations
60 bool converged = true; ///< false if a population exhausted iter_max passes (deadlock)
61};
62
63/**
64 * Runs MVABLO.
65 *
66 * @param N population (positive integer)
67 * @param S (M) mean service time per visit
68 * @param P (M x M) station routing matrix, row-stochastic and irreducible
69 * @param K (M) capacity, +inf for unbounded; ignored at delays
70 * @param isdelay (M) true at infinite-server stations, empty for none
71 * @param tol slack on the capacity test Q[i] > K[i]+tol
72 * @param iter_max maximum passes at one population
73 */
74template <class T>
75MvabloResult<T> pfqn_mvablo(long N, const std::vector<T>& S, const Matrix<T>& P,
76 const std::vector<double>& K, std::vector<bool> isdelay,
77 double tol = 1e-4, int iter_max = 1000) {
78 using nt = num_traits<T>;
79 const std::size_t M = S.size();
80 const T zero = nt::from_int(0);
81 const T one = nt::from_int(1);
82 if (isdelay.empty()) isdelay.assign(M, false);
83 const std::string caller = "pfqn_mvablo: ";
84 if (N <= 0)
85 throw InputError(ErrorKind{"Argument"}, caller + "MVABLO requires a positive integer population N.");
86 if (P.rows() != M || P.cols() != M || K.size() != M || isdelay.size() != M)
87 throw InputError(ErrorKind{"Argument"},
88 caller + "MVABLO requires S, K and isdelay of length M and an M x M routing matrix P.");
89 double sumK = 0.0;
90 bool anyDelay = false;
91 for (std::size_t i = 0; i < M; ++i) {
92 const double s = nt::to_double(S[i]);
93 if (!(s >= 0.0) || !std::isfinite(s))
94 throw InputError(ErrorKind{"Argument"}, caller + "MVABLO requires finite nonnegative service times.");
95 if (!isdelay[i] && !(K[i] >= 1.0))
96 throw InputError(ErrorKind{"Argument"},
97 caller + "MVABLO requires every queue capacity K(i) to be at least 1.");
98 sumK += K[i];
99 anyDelay = anyDelay || isdelay[i];
100 double row = 0.0;
101 for (std::size_t j = 0; j < M; ++j) {
102 const double p = nt::to_double(P(i, j));
103 if (p < 0.0)
104 throw InputError(ErrorKind{"Argument"}, caller + "MVABLO requires a row-stochastic routing matrix P.");
105 row += p;
106 }
107 if (std::fabs(row - 1.0) > 1e-8)
108 throw InputError(ErrorKind{"Argument"}, caller + "MVABLO requires a row-stochastic routing matrix P.");
109 }
110 std::vector<std::vector<bool>> reach(M, std::vector<bool>(M, false));
111 for (std::size_t i = 0; i < M; ++i)
112 for (std::size_t j = 0; j < M; ++j) reach[i][j] = i == j || P(i, j) > zero;
113 const int squarings = static_cast<int>(std::ceil(std::log2(static_cast<double>(M < 2 ? 2 : M)))) + 1;
114 for (int h = 0; h < squarings; ++h) {
115 std::vector<std::vector<bool>> next(M, std::vector<bool>(M, false));
116 for (std::size_t i = 0; i < M; ++i)
117 for (std::size_t j = 0; j < M; ++j) {
118 bool r = reach[i][j];
119 for (std::size_t l = 0; l < M && !r; ++l) r = reach[i][l] && reach[l][j];
120 next[i][j] = r;
121 }
122 reach.swap(next);
123 }
124 for (std::size_t i = 0; i < M; ++i)
125 for (std::size_t j = 0; j < M; ++j)
126 if (!reach[i][j])
127 throw InputError(ErrorKind{"Argument"},
128 caller + "MVABLO requires a routing matrix that is irreducible on the stations.");
129 if (!anyDelay && static_cast<double>(N) >= sumK)
130 throw UnsupportedError(ErrorKind{"Structure"},
131 caller + "MVABLO requires N < sum(K) = " + std::to_string(sumK) +
132 " to rule out a deadlock of the blocking network; N = " + std::to_string(N) + ".");
133
134 // visit ratios e = e*P, normalized at station 1; every output is invariant to the scale.
135 // A = P' - I with row 1 replaced by e_1, solved by Gaussian elimination with partial pivoting.
136 Matrix<T> A(M, M, zero);
137 std::vector<T> e(M, zero);
138 for (std::size_t i = 0; i < M; ++i)
139 for (std::size_t j = 0; j < M; ++j) A(i, j) = T(P(j, i) - (i == j ? one : zero));
140 for (std::size_t j = 0; j < M; ++j) A(0, j) = j == 0 ? one : zero;
141 e[0] = one;
142 for (std::size_t c = 0; c < M; ++c) {
143 std::size_t piv = c;
144 for (std::size_t r = c + 1; r < M; ++r)
145 if (std::fabs(nt::to_double(A(r, c))) > std::fabs(nt::to_double(A(piv, c)))) piv = r;
146 if (piv != c) {
147 for (std::size_t j = 0; j < M; ++j) std::swap(A(c, j), A(piv, j));
148 std::swap(e[c], e[piv]);
149 }
150 for (std::size_t r = 0; r < M; ++r) {
151 if (r == c || A(r, c) == zero) continue;
152 const T f = T(A(r, c) / A(c, c));
153 for (std::size_t j = c; j < M; ++j) A(r, j) = T(A(r, j) - f * A(c, j));
154 e[r] = T(e[r] - f * e[c]);
155 }
156 }
157 for (std::size_t i = 0; i < M; ++i) e[i] = T(e[i] / A(i, i));
158
159 std::vector<T> q(M, zero), z(M, one), t(M, zero);
160 Matrix<T> bt(M, M, zero);
161 MvabloResult<T> res;
162 T lambda = zero;
163 for (long n = 1; n <= N; ++n) {
164 const std::vector<T> qprev = q;
165 int passes = 0;
166 while (true) {
167 ++passes;
168 ++res.iter;
169 T denom = zero;
170 for (std::size_t i = 0; i < M; ++i) {
171 T bsum = zero;
172 for (std::size_t j = 0; j < M; ++j) bsum = T(bsum + bt(i, j));
173 t[i] = isdelay[i] ? T(S[i] + bsum) : T(S[i] * (z[i] + qprev[i]) + bsum);
174 denom = T(denom + e[i] * t[i]);
175 }
176 lambda = T(nt::from_int(n) / denom);
177 std::vector<bool> full(M, false);
178 bool anyFull = false;
179 for (std::size_t i = 0; i < M; ++i) {
180 q[i] = T(e[i] * lambda * t[i]);
181 full[i] = !isdelay[i] && std::isfinite(K[i]) && q[i] > nt::from_double(K[i] + tol);
182 anyFull = anyFull || full[i];
183 }
184 if (!anyFull) break;
185 if (passes >= iter_max) {
186 res.converged = false;
187 break;
188 }
189 for (std::size_t i = 0; i < M; ++i) {
190 if (!full[i]) continue;
191 z[i] = zero;
192 for (std::size_t j = 0; j < M; ++j)
193 if (j != i && P(j, i) > zero) bt(j, i) = T(bt(j, i) + S[i] * P(j, i) * e[j] / e[i]);
194 }
195 }
196 }
197 res.X.assign(M, zero);
198 res.U.assign(M, zero);
199 for (std::size_t i = 0; i < M; ++i) {
200 res.X[i] = T(e[i] * lambda);
201 res.U[i] = T(res.X[i] * S[i]);
202 }
203 res.Q = q;
204 res.R = t;
205 res.BT = bt;
206 return res;
207}
208
209} // namespace pfqn
210} // namespace line
211
212#endif // LINE_API_PFQN_PFQN_MVABLO_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.
MvabloResult< T > pfqn_mvablo(long N, const std::vector< T > &S, const Matrix< T > &P, const std::vector< double > &K, std::vector< bool > isdelay, double tol=1e-4, int iter_max=1000)
Runs MVABLO.
Definition pfqn_mvablo.h:75
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_mvablo, mirroring [X,Q,U,R,BT,iter,converged]; vectors are (M).
Definition pfqn_mvablo.h:53
std::vector< T > Q
mean number of jobs, blocked jobs at the source
Definition pfqn_mvablo.h:55
int iter
total number of passes over all populations
Definition pfqn_mvablo.h:59
Matrix< T > BT
(M x M) per-visit blocking time at j caused by a full i
Definition pfqn_mvablo.h:58
std::vector< T > X
throughput of each station
Definition pfqn_mvablo.h:54
std::vector< T > U
utilization X.*S, blocked time excluded
Definition pfqn_mvablo.h:56
bool converged
false if a population exhausted iter_max passes (deadlock)
Definition pfqn_mvablo.h:60
std::vector< T > R
mean residence time per visit, blocking time included
Definition pfqn_mvablo.h:57