LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
me_oqn.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_ME_ME_OQN_H
6#define LINE_API_ME_ME_OQN_H
7
8/**
9 * @file
10 * @ingroup api_me
11 * Maximum-entropy algorithm for open multiclass queueing networks.
12 *
13 * Templated port of matlab/src/api/me/me_oqn.m, cross-checked against
14 * jar/src/main/java/jline/api/nc/Me_oqn.java. Implements Kouvatsos (1994)
15 * Section 3.2 with the GE/GE/c building block of eq. (3.9) and the GE/GE/inf
16 * block, i.e. a GE-type decomposition fixed point:
17 *
18 * 1. feedback correction: a self-loop of probability p_ii gives a geometric
19 * number of passes, so mu <- mu (1-p_ii), Cs <- p_ii + (1-p_ii) Cs and
20 * the residual routing is renormalized;
21 * 2. job flow balance lambda = lambda0 + P' lambda on the ORIGINAL routing,
22 * so lambda counts revisits, while lambda_eff = lambda (1 - p_ii) is the
23 * rate seen by the corrected queue;
24 * 3. mean queue lengths from the multiclass GE/GE/1/FCFS formula of
25 * Section 3.1.1, the product-form formula at insensitive stations, or
26 * eq. (3.9) on the class-aggregated stream at multiserver stations;
27 * 4. departure scvs from eq. (3.6) with the marginal utilization of (3.3);
28 * 5. arrival scvs by the GE merging formula (3.7) applied to the thinned
29 * departure streams; iterate 3-5 to convergence.
30 *
31 * DIVERGENCE from the references on unstable input: MATLAB raises a warning
32 * and returns L = Inf at any finite-server station with total utilization
33 * >= 1, and continues to iterate the scvs on top of that. This port raises
34 * NumericError instead: the templated backends have no infinity (and no
35 * warning channel), and an Inf queue length propagated into me_mqn's
36 * inflation step would silently poison the open-class results.
37 *
38 * ARITHMETIC: a tolerance-stopped fixed point.
39 * static_assert(num_traits<T>::has_transcendental)
40 */
41
42#include <cstddef>
43#include <vector>
44
46#include "line/num/number.h"
47#include "line/util/error.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace me {
52
53namespace detail {
54
55/**
56 * Mean queue length of a stable GE/GE/c/FCFS queue, the exact ME solution of
57 * Kouvatsos (1994) eq. (3.9).
58 */
59template <class T>
60T ge_gec_mql(const T& lambda, const T& Ca, const T& mu, const T& Cs, long c) {
61 const T one = num_traits<T>::from_int(1), two = num_traits<T>::from_int(2);
62 const T alpha2 = two / (Cs + one);
63 const T alpha1 = one - alpha2;
64 const T beta2 = two / (Ca + one);
65 const T beta1 = one - beta2;
66 const T lambda2 = beta2 * lambda;
67 const T mu2 = alpha2 * mu;
68
69 std::vector<T> g(static_cast<std::size_t>(c));
70 for (long j = 1; j <= c - 1; ++j) {
71 const T jt = num_traits<T>::from_int(j);
72 const T den = jt * mu2 * (one - alpha1 * beta1);
73 if (den == num_traits<T>::from_int(0))
74 throw NumericError("me_oqn: degenerate GE/GE/c building block");
75 g[static_cast<std::size_t>(j - 1)] =
76 (lambda2 + num_traits<T>::from_int(j - 1) * mu2 * beta1) * alpha2 / den;
77 }
78 const T ct = num_traits<T>::from_int(c);
79 const T denc = lambda2 * alpha1 + ct * mu2;
80 if (denc == num_traits<T>::from_int(0))
81 throw NumericError("me_oqn: degenerate GE/GE/c building block");
82 g[static_cast<std::size_t>(c - 1)] =
83 (lambda2 + num_traits<T>::from_int(c - 1) * mu2 * beta1) * alpha2 / denc;
84 const T x = (lambda2 + ct * mu2 * beta1) / denc;
85 if (x >= one) throw NumericError("me_oqn: the multiserver station is unstable");
86
87 std::vector<T> Gn(static_cast<std::size_t>(c));
88 T acc = num_traits<T>::from_int(1);
89 for (long j = 0; j < c; ++j) {
90 acc *= g[static_cast<std::size_t>(j)];
91 Gn[static_cast<std::size_t>(j)] = acc;
92 }
93 T Z = one;
94 for (long j = 0; j < c - 1; ++j) Z += Gn[static_cast<std::size_t>(j)];
95 Z += Gn[static_cast<std::size_t>(c - 1)] / (one - x);
96 T S1 = num_traits<T>::from_int(0);
97 for (long n = 1; n <= c - 1; ++n)
98 S1 += num_traits<T>::from_int(n) * Gn[static_cast<std::size_t>(n - 1)];
99 const T S2 = Gn[static_cast<std::size_t>(c - 1)] *
100 (ct / (one - x) + x / ((one - x) * (one - x)));
101 return (S1 + S2) / Z;
102}
103
104} // namespace detail
105
106/**
107 * @brief Maximum-entropy algorithm for open multiclass queueing networks.
108 *
109 * @param M number of stations
110 * @param R number of classes
111 * @param lambda0 external arrival rates (M x R)
112 * @param Ca0 external arrival scvs (M x R)
113 * @param mu service rates (M x R)
114 * @param Cs service scvs (M x R)
115 * @param P routing, R matrices (M x M), P[r](j,i)
116 * @param c servers per station, 0 for an infinite-server station
117 * @param insens insensitive discipline flags per station
118 * @param opt tolerance and iteration budget
119 */
120template <class T>
121MeResult<T> me_oqn(std::size_t M, std::size_t R, const Matrix<T>& lambda0, const Matrix<T>& Ca0,
122 const Matrix<T>& mu, const Matrix<T>& Cs, const std::vector<Matrix<T>>& P,
123 const std::vector<long>& c, const std::vector<char>& insens,
124 const MeOptions& opt = MeOptions()) {
125 static_assert(num_traits<T>::has_transcendental, "me_oqn requires transcendental arithmetic");
126 detail::check_dims(M, R, mu, Cs, P, c, insens, "me_oqn");
127 if (lambda0.rows() != M || lambda0.cols() != R || Ca0.rows() != M || Ca0.cols() != R)
128 throw InputError("me_oqn: lambda0 and Ca0 must be M x R");
129
130 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
131 const T two = num_traits<T>::from_int(2);
132 const T tol = num_traits<T>::from_double(opt.tol);
133
134 // Step 1: feedback correction
135 std::vector<Matrix<T>> Peff = P;
136 Matrix<T> mueff = mu, Cseff = Cs;
137 for (std::size_t i = 0; i < M; ++i) {
138 for (std::size_t r = 0; r < R; ++r) {
139 const T pii = P[r](i, i);
140 if (!(pii > zero)) continue;
141 if (pii >= one) throw InputError("me_oqn: a self-loop probability of one");
142 mueff(i, r) = mu(i, r) * (one - pii);
143 Cseff(i, r) = pii + (one - pii) * Cs(i, r);
144 for (std::size_t j = 0; j < M; ++j) Peff[r](i, j) = P[r](i, j) / (one - pii);
145 Peff[r](i, i) = zero;
146 }
147 }
148
149 // Step 3: job flow balance on the original routing
150 MeResult<T> out;
151 out.lambda = Matrix<T>(M, R, zero);
152 Matrix<T> lameff(M, R, zero);
153 for (std::size_t r = 0; r < R; ++r) {
154 Matrix<T> A(M, M, zero);
155 std::vector<T> b(M, zero);
156 for (std::size_t i = 0; i < M; ++i) {
157 for (std::size_t j = 0; j < M; ++j) A(i, j) = (i == j ? one : zero) - P[r](j, i);
158 b[i] = lambda0(i, r);
159 }
160 const std::vector<T> x = detail::linear_solve(A, b);
161 for (std::size_t i = 0; i < M; ++i) {
162 out.lambda(i, r) = x[i];
163 lameff(i, r) = x[i] * (one - P[r](i, i));
164 }
165 }
166
167 // Utilizations
168 out.rho = Matrix<T>(M, R, zero);
169 for (std::size_t i = 0; i < M; ++i)
170 for (std::size_t r = 0; r < R; ++r) {
171 if (!(mu(i, r) > zero)) continue;
172 if (detail::is_is(c, i))
173 out.rho(i, r) = lameff(i, r) / mueff(i, r);
174 else
175 out.rho(i, r) = out.lambda(i, r) / (num_traits<T>::from_int(c[i]) * mu(i, r));
176 }
177 for (std::size_t i = 0; i < M; ++i) {
178 if (detail::is_is(c, i)) continue;
179 T s = zero;
180 for (std::size_t r = 0; r < R; ++r) s += out.rho(i, r);
181 if (s >= one) throw NumericError("me_oqn: the network is unstable, utilization >= 1");
182 }
183
184 out.Ca = Matrix<T>(M, R, one);
185 out.Cd = Matrix<T>(M, R, one);
186 out.L = Matrix<T>(M, R, zero);
187
188 T delta = zero;
189 for (long it = 1; it <= opt.maxiter; ++it) {
190 out.iter = it;
191 const Matrix<T> Caold = out.Ca;
192
193 // Step 4: mean queue lengths
194 for (std::size_t i = 0; i < M; ++i) {
195 T rho_i = zero;
196 for (std::size_t r = 0; r < R; ++r) rho_i += out.rho(i, r);
197 if (detail::is_is(c, i)) {
198 for (std::size_t r = 0; r < R; ++r)
199 if (lameff(i, r) > zero && mueff(i, r) > zero)
200 out.L(i, r) = lameff(i, r) / mueff(i, r);
201 } else if (c[i] == 1) {
202 if (insens[i]) {
203 for (std::size_t r = 0; r < R; ++r)
204 if (lameff(i, r) > zero && mueff(i, r) > zero)
205 out.L(i, r) = out.rho(i, r) / (one - rho_i);
206 } else {
207 T resid = zero;
208 for (std::size_t u = 0; u < R; ++u)
209 if (lameff(i, u) > zero && mueff(i, u) > zero)
210 resid += lameff(i, u) * (Cseff(i, u) + out.Ca(i, u)) /
211 (mueff(i, u) * mueff(i, u));
212 for (std::size_t r = 0; r < R; ++r)
213 if (lameff(i, r) > zero && mueff(i, r) > zero)
214 out.L(i, r) = out.rho(i, r) * (out.Ca(i, r) + one) / two +
215 lameff(i, r) * resid / (two * (one - rho_i));
216 }
217 } else {
218 T lam_a = zero;
219 for (std::size_t u = 0; u < R; ++u)
220 if (lameff(i, u) > zero && mueff(i, u) > zero) lam_a += lameff(i, u);
221 if (!(lam_a > zero)) continue;
222 T inv_a = zero, ES = zero, ES2 = zero;
223 for (std::size_t u = 0; u < R; ++u) {
224 if (!(lameff(i, u) > zero && mueff(i, u) > zero)) continue;
225 const T wu = lameff(i, u) / lam_a;
226 inv_a += wu / (out.Ca(i, u) + one);
227 ES += wu / mueff(i, u);
228 ES2 += wu * (Cseff(i, u) + one) / (mueff(i, u) * mueff(i, u));
229 }
230 const T Ca_a = -one + one / inv_a;
231 const T Cs_a = ES2 / (ES * ES) - one;
232 const T L_a = detail::ge_gec_mql(lam_a, Ca_a, T(one / ES), Cs_a, c[i]);
233 const T Lq_a = L_a - lam_a * ES;
234 for (std::size_t r = 0; r < R; ++r)
235 if (lameff(i, r) > zero && mueff(i, r) > zero)
236 out.L(i, r) = num_traits<T>::from_int(c[i]) * out.rho(i, r) +
237 (lameff(i, r) / lam_a) * Lq_a;
238 }
239 }
240
241 // Step 5a: departure scvs
242 for (std::size_t j = 0; j < M; ++j) {
243 T rho_j = zero;
244 for (std::size_t r = 0; r < R; ++r) rho_j += out.rho(j, r);
245 for (std::size_t r = 0; r < R; ++r) {
246 if (!(lameff(j, r) > zero)) continue;
247 if (detail::is_is(c, j)) {
248 out.Cd(j, r) = out.Ca(j, r);
249 } else if (c[j] == 1) {
250 const T den = out.L(j, r) + rho_j - out.rho(j, r);
251 const T rhohat = den == zero ? zero : out.rho(j, r) * out.L(j, r) / den;
252 out.Cd(j, r) = two * out.L(j, r) * (one - rhohat) +
253 out.Ca(j, r) * (one - two * rhohat);
254 } else {
255 out.Cd(j, r) = rho_j * (one - rho_j) + (one - rho_j) * out.Ca(j, r) +
256 rho_j * rho_j * Cseff(j, r);
257 }
258 }
259 }
260
261 // Step 5b: arrival scvs by GE merging with thinning
262 for (std::size_t i = 0; i < M; ++i) {
263 for (std::size_t r = 0; r < R; ++r) {
264 if (!(lameff(i, r) > zero)) continue;
265 T sum_inv = zero;
266 for (std::size_t j = 0; j < M; ++j) {
267 const T pji = Peff[r](j, i);
268 if (!(pji > zero) || !(lameff(j, r) > zero)) continue;
269 const T Cdji = one + pji * (out.Cd(j, r) - one);
270 sum_inv += (lameff(j, r) * pji / lameff(i, r)) / (Cdji + one);
271 }
272 if (lambda0(i, r) > zero)
273 sum_inv += (lambda0(i, r) / lameff(i, r)) / (Ca0(i, r) + one);
274 if (sum_inv > zero) out.Ca(i, r) = -one + one / sum_inv;
275 }
276 }
277
278 delta = zero;
279 for (std::size_t i = 0; i < M; ++i)
280 for (std::size_t r = 0; r < R; ++r) {
281 const T d = num_abs(T(out.Ca(i, r) - Caold(i, r)));
282 if (d > delta) delta = d;
283 }
284 if (delta < tol) {
285 out.converged = true;
286 break;
287 }
288 }
289
290 // Step 6: response times by Little's law on the visit-inclusive rates
291 out.W = Matrix<T>(M, R, zero);
292 for (std::size_t i = 0; i < M; ++i)
293 for (std::size_t r = 0; r < R; ++r)
294 if (out.lambda(i, r) > zero) out.W(i, r) = out.L(i, r) / out.lambda(i, r);
295
296 out.X.assign(R, zero);
297 for (std::size_t r = 0; r < R; ++r)
298 for (std::size_t i = 0; i < M; ++i) out.X[r] += lambda0(i, r);
299 return out;
300}
301
302} // namespace me
303} // namespace line
304
305#endif // LINE_API_ME_ME_OQN_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
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense matrix and non-owning view.
Shared declarations for the maximum-entropy (Kouvatsos) queueing network algorithms.
MeResult< T > me_oqn(std::size_t M, std::size_t R, const Matrix< T > &lambda0, const Matrix< T > &Ca0, const Matrix< T > &mu, const Matrix< T > &Cs, const std::vector< Matrix< T > > &P, const std::vector< long > &c, const std::vector< char > &insens, const MeOptions &opt=MeOptions())
Maximum-entropy algorithm for open multiclass queueing networks.
Definition me_oqn.h:121
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Iteration control, mirroring the MATLAB options struct.
Definition me_types.h:58
Mean-value results shared by the open, closed and mixed algorithms.
Definition me_types.h:157
Matrix< T > rho
utilizations (M x R)
Definition me_types.h:163
Matrix< T > L
mean queue lengths (M x R)
Definition me_types.h:158
Matrix< T > W
mean response times (M x R)
Definition me_types.h:159
Matrix< T > Ca
arrival scvs (M x R)
Definition me_types.h:160
Matrix< T > Cd
departure scvs (M x R)
Definition me_types.h:161
std::vector< T > X
class throughputs (R)
Definition me_types.h:164
long iter
fixed-point iterations performed
Definition me_types.h:165
Matrix< T > lambda
per-station throughputs (M x R)
Definition me_types.h:162