LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
me_cqn.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_CQN_H
6#define LINE_API_ME_ME_CQN_H
7
8/**
9 * @file
10 * @ingroup api_me
11 * Maximum-entropy algorithm for closed multiclass queueing networks.
12 *
13 * Templated port of matlab/src/api/me/me_cqn.m, cross-checked against
14 * jar/src/main/java/jline/api/nc/Me_cqn.java. Implements the two-stage
15 * algorithm of Kouvatsos (1994) Section 3.3 for networks of G/G/1 and
16 * G/G/inf queues:
17 *
18 * Stage 1 solves a PSEUDO-OPEN network at trial class throughputs X, using
19 * the GE-type fixed point of Section 3.2 on the class-composed streams,
20 * and moves X until sum_i L(i,r) = N(r). The update is damped and
21 * step-clamped, and X is capped below the saturation point of every
22 * single-server station, so the iteration cannot walk into an unstable
23 * pseudo-open network.
24 * Stage 2 builds the ME product form (3.8) from the Stage 1 Lagrangian
25 * coefficients, normalizes it by a multiclass convolution over the
26 * population lattice, and iterates the work-rate (flow) equations until
27 * the throughputs implied by the closed solution agree with those used to
28 * parametrize the building blocks.
29 *
30 * The coefficient functions f_i are evaluated in the log domain and rescaled
31 * by their maximum before the convolution, because they are products of up to
32 * sum(N) factors; the per-station scaling cancels in the marginals.
33 *
34 * ARITHMETIC: log, exp and a damped tolerance-stopped fixed point.
35 * static_assert(num_traits<T>::has_transcendental)
36 * This is the algorithm in the port that most repays extra precision: the
37 * convolution of the rescaled coefficients cancels heavily at high
38 * population, and the population constraint sum_i L(i,r) = N(r) is the
39 * observable that degrades first when it does.
40 */
41
42#include <algorithm>
43#include <cstddef>
44#include <vector>
45
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/matrix.h"
50
51namespace line {
52namespace me {
53
54namespace detail {
55
56/** Stage 1 output: the pseudo-open decomposition at the current flows. */
57template <class T>
58struct PseudoOpen {
59 Matrix<T> L, Cd, rho;
60};
61
62/**
63 * Scales the class throughputs uniformly so that every single-server station
64 * of the pseudo-open network stays below utilization 0.999.
65 */
66template <class T>
67void me_cqn_capacity_cap(std::vector<T>& X, const Matrix<T>& V, const Matrix<T>& mu,
68 const std::vector<long>& c, std::size_t M, std::size_t R) {
69 const T zero = num_traits<T>::from_int(0);
70 T maxrho = zero;
71 for (std::size_t i = 0; i < M; ++i) {
72 if (is_is(c, i)) continue;
73 T rho_i = zero;
74 for (std::size_t r = 0; r < R; ++r)
75 if (V(i, r) > zero && mu(i, r) > zero) rho_i += X[r] * V(i, r) / mu(i, r);
76 if (rho_i > maxrho) maxrho = rho_i;
77 }
78 const T cap = num_traits<T>::from_rational(999, 1000);
79 if (maxrho >= cap) {
80 const T f = cap / maxrho;
81 for (std::size_t r = 0; r < R; ++r) X[r] *= f;
82 }
83}
84
85/**
86 * GE-type fixed point of the open algorithm on the pseudo-open network: no
87 * external arrivals, flows given by lambda. The scvs are computed on the
88 * class-composed streams and disaggregated by thinning, which is what keeps
89 * the closed solution consistent with the single-class one when the classes
90 * are statistically identical.
91 */
92template <class T>
93PseudoOpen<T> me_cqn_pseudoopen(std::size_t M, std::size_t R, const Matrix<T>& lambda,
94 const Matrix<T>& mu, const Matrix<T>& mueff,
95 const Matrix<T>& Cseff, const std::vector<Matrix<T>>& Peff,
96 const Matrix<T>& selfp, const std::vector<long>& c,
97 const std::vector<char>& insens, Matrix<T>& Ca,
98 const MeOptions& opt) {
99 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
100 const T two = num_traits<T>::from_int(2);
101 const T tol = num_traits<T>::from_double(opt.tol);
102
103 Matrix<T> lameff(M, R, zero);
104 for (std::size_t i = 0; i < M; ++i)
105 for (std::size_t r = 0; r < R; ++r) lameff(i, r) = lambda(i, r) * (one - selfp(i, r));
106
107 PseudoOpen<T> po;
108 po.rho = Matrix<T>(M, R, zero);
109 for (std::size_t i = 0; i < M; ++i)
110 for (std::size_t r = 0; r < R; ++r) {
111 if (!(mu(i, r) > zero)) continue;
112 po.rho(i, r) = is_is(c, i) ? lameff(i, r) / mueff(i, r) : lambda(i, r) / mu(i, r);
113 }
114
115 // Class composition per station
116 std::vector<T> lam_a(M, zero), mu_a(M, zero), Cs_a(M, one);
117 for (std::size_t i = 0; i < M; ++i)
118 for (std::size_t r = 0; r < R; ++r) lam_a[i] += lameff(i, r);
119 for (std::size_t i = 0; i < M; ++i) {
120 if (!(lam_a[i] > zero)) continue;
121 T ES = zero, ES2 = zero;
122 for (std::size_t u = 0; u < R; ++u) {
123 if (!(lameff(i, u) > zero && mueff(i, u) > zero)) continue;
124 const T wu = lameff(i, u) / lam_a[i];
125 ES += wu / mueff(i, u);
126 ES2 += wu * (Cseff(i, u) + one) / (mueff(i, u) * mueff(i, u));
127 }
128 if (ES > zero) {
129 mu_a[i] = one / ES;
130 Cs_a[i] = ES2 / (ES * ES) - one;
131 }
132 }
133 Matrix<T> Pa(M, M, zero);
134 for (std::size_t j = 0; j < M; ++j) {
135 if (!(lam_a[j] > zero)) continue;
136 for (std::size_t i = 0; i < M; ++i) {
137 T num = zero;
138 for (std::size_t r = 0; r < R; ++r)
139 if (lameff(j, r) > zero) num += lameff(j, r) * Peff[r](j, i);
140 Pa(j, i) = num / lam_a[j];
141 }
142 }
143
144 // Fixed point on the aggregate arrival scvs, warm started from Ca
145 std::vector<T> Ca_a(M, one), Cd_a(M, one), L_a(M, zero);
146 for (std::size_t i = 0; i < M; ++i) {
147 if (!(lam_a[i] > zero)) continue;
148 for (std::size_t r = 0; r < R; ++r)
149 if (lameff(i, r) > zero) {
150 Ca_a[i] = one + (Ca(i, r) - one) * lam_a[i] / lameff(i, r);
151 break;
152 }
153 }
154 for (long it = 1; it <= opt.maxiter; ++it) {
155 const std::vector<T> Ca_old = Ca_a;
156 for (std::size_t i = 0; i < M; ++i) {
157 if (!(lam_a[i] > zero)) continue;
158 T rho_i = zero;
159 for (std::size_t r = 0; r < R; ++r) rho_i += po.rho(i, r);
160 if (is_is(c, i)) {
161 L_a[i] = lam_a[i] / mu_a[i];
162 Cd_a[i] = Ca_a[i];
163 } else if (rho_i < one) {
164 if (insens[i])
165 L_a[i] = rho_i / (one - rho_i);
166 else
167 L_a[i] = rho_i * (Ca_a[i] + one) / two +
168 rho_i * rho_i * (Ca_a[i] + Cs_a[i]) / (two * (one - rho_i));
169 Cd_a[i] = two * L_a[i] * (one - rho_i) + Ca_a[i] * (one - two * rho_i);
170 }
171 }
172 for (std::size_t i = 0; i < M; ++i) {
173 if (!(lam_a[i] > zero)) continue;
174 T sum_inv = zero;
175 for (std::size_t j = 0; j < M; ++j) {
176 if (!(Pa(j, i) > zero) || !(lam_a[j] > zero)) continue;
177 const T Cdji = one + Pa(j, i) * (Cd_a[j] - one);
178 sum_inv += (lam_a[j] * Pa(j, i) / lam_a[i]) / (Cdji + one);
179 }
180 if (sum_inv > zero) Ca_a[i] = -one + one / sum_inv;
181 }
182 T delta = zero;
183 for (std::size_t i = 0; i < M; ++i) {
184 const T d = num_abs(T(Ca_a[i] - Ca_old[i]));
185 if (d > delta) delta = d;
186 }
187 if (delta < tol) break;
188 }
189
190 // Disaggregation by thinning, then per-class mean queue lengths
191 po.L = Matrix<T>(M, R, zero);
192 po.Cd = Matrix<T>(M, R, one);
193 for (std::size_t i = 0; i < M; ++i) {
194 T rho_i = zero;
195 for (std::size_t r = 0; r < R; ++r) rho_i += po.rho(i, r);
196 for (std::size_t r = 0; r < R; ++r) {
197 if (!(lameff(i, r) > zero)) continue;
198 const T pr = lameff(i, r) / lam_a[i];
199 Ca(i, r) = one + pr * (Ca_a[i] - one);
200 po.Cd(i, r) = one + pr * (Cd_a[i] - one);
201 }
202 if (is_is(c, i)) {
203 for (std::size_t r = 0; r < R; ++r)
204 if (lameff(i, r) > zero && mueff(i, r) > zero)
205 po.L(i, r) = lameff(i, r) / mueff(i, r);
206 } else if (rho_i < one) {
207 if (insens[i]) {
208 for (std::size_t r = 0; r < R; ++r)
209 if (lameff(i, r) > zero && mueff(i, r) > zero)
210 po.L(i, r) = po.rho(i, r) / (one - rho_i);
211 } else {
212 T resid = zero;
213 for (std::size_t u = 0; u < R; ++u)
214 if (lameff(i, u) > zero && mueff(i, u) > zero)
215 resid += lameff(i, u) * (Cseff(i, u) + Ca(i, u)) /
216 (mueff(i, u) * mueff(i, u));
217 for (std::size_t r = 0; r < R; ++r)
218 if (lameff(i, r) > zero && mueff(i, r) > zero)
219 po.L(i, r) = po.rho(i, r) * (Ca(i, r) + one) / two +
220 lameff(i, r) * resid / (two * (one - rho_i));
221 }
222 }
223 }
224 return po;
225}
226
227/** Mixed-radix enumeration of the population lattice {0..N(1)} x ... */
228inline std::vector<std::vector<long>> me_cqn_lattice(const std::vector<long>& N) {
229 std::size_t PIdx = 1;
230 for (std::size_t r = 0; r < N.size(); ++r) PIdx *= static_cast<std::size_t>(N[r] + 1);
231 std::vector<std::vector<long>> Dec(PIdx, std::vector<long>(N.size(), 0));
232 for (std::size_t p = 0; p < PIdx; ++p) {
233 std::size_t q = p;
234 for (std::size_t r = 0; r < N.size(); ++r) {
235 const std::size_t sz = static_cast<std::size_t>(N[r] + 1);
236 Dec[p][r] = static_cast<long>(q % sz);
237 q /= sz;
238 }
239 }
240 return Dec;
241}
242
243/**
244 * Auxiliary functions f_i of the ME solution (3.8): the right-hand sides of
245 * (3.2) and (3.4) with the (1-rho) factor removed, evaluated from the Stage 1
246 * Lagrangian coefficients and rescaled by their maximum.
247 */
248template <class T>
249Matrix<T> me_cqn_coefficients(std::size_t M, std::size_t R, const std::vector<long>& N,
250 const std::vector<std::vector<long>>& Dec, const Matrix<T>& Lpo,
251 const Matrix<T>& rho_po, const Matrix<T>& lambda,
252 const Matrix<T>& mueff, const Matrix<T>& Cseff, const Matrix<T>& Ca,
253 const std::vector<long>& c, const Matrix<T>& selfp) {
254 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
255 const std::size_t PIdx = Dec.size();
256 Matrix<T> F(PIdx, M, zero);
257 Matrix<T> lameff(M, R, zero);
258 for (std::size_t i = 0; i < M; ++i)
259 for (std::size_t r = 0; r < R; ++r) lameff(i, r) = lambda(i, r) * (one - selfp(i, r));
260
261 for (std::size_t i = 0; i < M; ++i) {
262 if (is_is(c, i)) {
263 // GE/GE/inf: f(n) = prod_r prod_{k=1}^{n_r} g_r(k)
264 std::vector<std::vector<T>> logg(R);
265 std::vector<std::vector<char>> ok(R);
266 for (std::size_t r = 0; r < R; ++r) {
267 logg[r].assign(static_cast<std::size_t>(N[r]), zero);
268 ok[r].assign(static_cast<std::size_t>(N[r]), 0);
269 for (long j = 1; j <= N[r]; ++j) {
270 if (!(lameff(i, r) > zero && mueff(i, r) > zero)) continue;
271 const T den = num_traits<T>::from_int(j) * mueff(i, r) *
272 (Ca(i, r) + Cseff(i, r));
273 if (!(den > zero)) continue;
274 const T gj = (lameff(i, r) * (one + Cseff(i, r)) +
275 num_traits<T>::from_int(j - 1) * mueff(i, r) *
276 (Ca(i, r) - one)) /
277 den;
278 if (!(gj > zero)) continue;
279 logg[r][static_cast<std::size_t>(j - 1)] = num_log(gj);
280 ok[r][static_cast<std::size_t>(j - 1)] = 1;
281 }
282 }
283 for (std::size_t p = 0; p < PIdx; ++p) {
284 T val = zero;
285 bool good = true;
286 for (std::size_t r = 0; r < R && good; ++r)
287 for (long j = 1; j <= Dec[p][r]; ++j) {
288 if (!ok[r][static_cast<std::size_t>(j - 1)]) {
289 good = false;
290 break;
291 }
292 val += logg[r][static_cast<std::size_t>(j - 1)];
293 }
294 F(p, i) = good ? num_exp(val) : zero;
295 }
296 F(0, i) = one;
297 } else {
298 // GE/GE/1: f(n) = ((|n|-1)!/prod_r n_r!)
299 // * sum_r n_r (g_r x_r) x_r^{n_r-1} prod_{s!=r} x_s^{n_s}
300 T rho_i = zero, Li = zero;
301 for (std::size_t r = 0; r < R; ++r) {
302 rho_i += rho_po(i, r);
303 Li += Lpo(i, r);
304 }
305 std::vector<T> x(R, zero), gx(R, zero);
306 if (Li > zero && rho_i < one) {
307 for (std::size_t r = 0; r < R; ++r) {
308 if (!(lambda(i, r) > zero)) continue;
309 const T d = Lpo(i, r) - rho_po(i, r);
310 x[r] = d > zero ? d / Li : zero;
311 gx[r] = rho_po(i, r) * rho_i / ((one - rho_i) * Li);
312 }
313 }
314 for (std::size_t p = 0; p < PIdx; ++p) {
315 long ntot = 0;
316 bool absent = false;
317 for (std::size_t r = 0; r < R; ++r) {
318 ntot += Dec[p][r];
319 if (Dec[p][r] > 0 && !(lambda(i, r) > zero)) absent = true;
320 }
321 if (ntot == 0) {
322 F(p, i) = one;
323 continue;
324 }
325 if (absent) {
326 F(p, i) = zero; // a class that does not visit this station
327 continue;
328 }
329 T logmult = log_factorial<T>(ntot - 1);
330 for (std::size_t r = 0; r < R; ++r) logmult -= log_factorial<T>(Dec[p][r]);
331 T tot = zero;
332 for (std::size_t r = 0; r < R; ++r) {
333 if (!(Dec[p][r] > 0) || !(gx[r] > zero)) continue;
334 T lterm = num_log(num_traits<T>::from_int(Dec[p][r])) + num_log(gx[r]);
335 bool good = true;
336 for (std::size_t s = 0; s < R; ++s) {
337 const long es = (s == r) ? Dec[p][s] - 1 : Dec[p][s];
338 if (es <= 0) continue;
339 if (!(x[s] > zero)) {
340 good = false;
341 break;
342 }
343 lterm += num_traits<T>::from_int(es) * num_log(x[s]);
344 }
345 if (good) tot += num_exp(T(logmult + lterm));
346 }
347 F(p, i) = tot;
348 }
349 }
350 T fmax = zero;
351 for (std::size_t p = 0; p < PIdx; ++p)
352 if (F(p, i) > fmax) fmax = F(p, i);
353 if (fmax > zero)
354 for (std::size_t p = 0; p < PIdx; ++p) F(p, i) /= fmax;
355 if (!(F(0, i) > zero)) F(0, i) = num_traits<T>::from_double(1e-300);
356 }
357 return F;
358}
359
360/** Convolution of a partial normalizing constant with one station term. */
361template <class T>
362std::vector<T> me_cqn_convpair(const std::vector<T>& G, const std::vector<T>& f,
363 const std::vector<std::vector<long>>& Dec,
364 const std::vector<long>& N, const std::vector<std::size_t>& rad) {
365 const T zero = num_traits<T>::from_int(0);
366 const std::size_t PIdx = Dec.size();
367 std::vector<T> G2(PIdx, zero);
368 for (std::size_t p = 0; p < PIdx; ++p) {
369 if (f[p] == zero) continue;
370 for (std::size_t q = 0; q < PIdx; ++q) {
371 if (G[q] == zero) continue;
372 std::size_t idx = 0;
373 bool fits = true;
374 for (std::size_t r = 0; r < N.size(); ++r) {
375 const long t = Dec[p][r] + Dec[q][r];
376 if (t > N[r]) {
377 fits = false;
378 break;
379 }
380 idx += static_cast<std::size_t>(t) * rad[r];
381 }
382 if (fits) G2[idx] += f[p] * G[q];
383 }
384 }
385 return G2;
386}
387
388/** Return value of the convolution step: marginals and busy probabilities. */
389template <class T>
390struct MeCqnConv {
391 Matrix<T> L;
392 std::vector<T> U;
393};
394
395/**
396 * Normalizing constant by convolving the f_i over the population lattice, and
397 * the per-station marginals by prefix/suffix convolutions.
398 */
399template <class T>
400MeCqnConv<T> me_cqn_convolve(std::size_t M, std::size_t R, const std::vector<long>& N,
401 const std::vector<std::vector<long>>& Dec, const Matrix<T>& F) {
402 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
403 const std::size_t PIdx = Dec.size();
404 std::vector<std::size_t> rad(R, 1);
405 for (std::size_t r = 1; r < R; ++r)
406 rad[r] = rad[r - 1] * static_cast<std::size_t>(N[r - 1] + 1);
407
408 std::vector<std::vector<T>> Fcol(M, std::vector<T>(PIdx, zero));
409 for (std::size_t i = 0; i < M; ++i)
410 for (std::size_t p = 0; p < PIdx; ++p) Fcol[i][p] = F(p, i);
411
412 std::vector<T> G0(PIdx, zero);
413 G0[0] = one;
414 std::vector<std::vector<T>> Gpre(M + 1), Gsuf(M + 1);
415 Gpre[0] = G0;
416 for (std::size_t k = 0; k < M; ++k)
417 Gpre[k + 1] = me_cqn_convpair(Gpre[k], Fcol[k], Dec, N, rad);
418 Gsuf[M] = G0;
419 for (std::size_t k = M; k-- > 0;)
420 Gsuf[k] = me_cqn_convpair(Gsuf[k + 1], Fcol[k], Dec, N, rad);
421
422 const T Z = Gpre[M][PIdx - 1];
423 if (!(Z > zero)) throw NumericError("me_cqn: the normalizing constant vanished");
424
425 MeCqnConv<T> out;
426 out.L = Matrix<T>(M, R, zero);
427 out.U.assign(M, zero);
428 for (std::size_t i = 0; i < M; ++i) {
429 const std::vector<T> Grest = me_cqn_convpair(Gpre[i], Gsuf[i + 1], Dec, N, rad);
430 for (std::size_t p = 0; p < PIdx; ++p) {
431 if (!(F(p, i) > zero)) continue;
432 std::size_t q = 0;
433 for (std::size_t r = 0; r < R; ++r)
434 q += static_cast<std::size_t>(N[r] - Dec[p][r]) * rad[r];
435 const T pin = F(p, i) * Grest[q] / Z;
436 if (p > 0) out.U[i] += pin;
437 for (std::size_t r = 0; r < R; ++r)
438 if (Dec[p][r] > 0) out.L(i, r) += num_traits<T>::from_int(Dec[p][r]) * pin;
439 }
440 }
441 return out;
442}
443
444} // namespace detail
445
446/**
447 * @brief Maximum-entropy algorithm for closed multiclass queueing networks.
448 *
449 * @param M number of stations
450 * @param R number of classes
451 * @param N class populations (R)
452 * @param mu service rates (M x R)
453 * @param Cs service scvs (M x R)
454 * @param P routing, R matrices (M x M)
455 * @param c servers per station, 0 for an infinite-server station;
456 * finite values must be 1
457 * @param refstat_in reference station per class, or -1 for the first station the
458 * class is served at
459 * @param insens insensitive discipline flags per station
460 * @param opt tolerance and iteration budget
461 */
462template <class T>
463MeResult<T> me_cqn(std::size_t M, std::size_t R, const std::vector<long>& N, const Matrix<T>& mu,
464 const Matrix<T>& Cs, const std::vector<Matrix<T>>& P,
465 const std::vector<long>& c, const std::vector<long>& refstat_in,
466 const std::vector<char>& insens, const MeOptions& opt = MeOptions()) {
467 static_assert(num_traits<T>::has_transcendental, "me_cqn requires transcendental arithmetic");
468 detail::check_dims(M, R, mu, Cs, P, c, insens, "me_cqn");
469 if (N.size() != R) throw InputError("me_cqn: one population per class");
470 if (refstat_in.size() != R) throw InputError("me_cqn: one reference station per class");
471 for (std::size_t i = 0; i < M; ++i)
472 if (c[i] > 1) throw InputError("me_cqn: only single-server and IS stations are supported");
473 for (std::size_t r = 0; r < R; ++r)
474 if (N[r] < 0) throw InputError("me_cqn: populations must be nonnegative");
475
476 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
477 const T half = num_traits<T>::from_rational(1, 2);
478 const T tol = num_traits<T>::from_double(opt.tol);
479
480 // Feedback correction
481 std::vector<Matrix<T>> Peff = P;
482 Matrix<T> mueff = mu, Cseff = Cs, selfp(M, R, zero);
483 for (std::size_t i = 0; i < M; ++i)
484 for (std::size_t r = 0; r < R; ++r) {
485 const T pii = P[r](i, i);
486 if (!(pii > zero)) continue;
487 if (pii >= one) throw InputError("me_cqn: a self-loop probability of one");
488 selfp(i, r) = pii;
489 mueff(i, r) = mu(i, r) * (one - pii);
490 Cseff(i, r) = pii + (one - pii) * Cs(i, r);
491 for (std::size_t j = 0; j < M; ++j) Peff[r](i, j) = P[r](i, j) / (one - pii);
492 Peff[r](i, i) = zero;
493 }
494
495 // Visit ratios, normalized at the reference station of each class
496 Matrix<T> V(M, R, zero);
497 std::vector<long> refstat = refstat_in;
498 for (std::size_t r = 0; r < R; ++r) {
499 std::size_t ref;
500 if (refstat[r] < 0) {
501 std::size_t k = 0;
502 while (k < M && !(mu(k, r) > zero)) ++k;
503 if (k == M) throw InputError("me_cqn: a class is not served anywhere");
504 ref = k;
505 } else {
506 ref = static_cast<std::size_t>(refstat[r]);
507 if (ref >= M) throw InputError("me_cqn: the reference station is out of range");
508 }
509 refstat[r] = static_cast<long>(ref);
510 Matrix<T> A(M, M, zero);
511 std::vector<T> b(M, zero);
512 for (std::size_t i = 0; i < M; ++i)
513 for (std::size_t j = 0; j < M; ++j) A(i, j) = (i == j ? one : zero) - P[r](j, i);
514 for (std::size_t j = 0; j < M; ++j) A(ref, j) = zero;
515 A(ref, ref) = one;
516 b[ref] = one;
517 const std::vector<T> v = detail::linear_solve(A, b);
518 const T eps = num_traits<T>::from_double(1e-14);
519 for (std::size_t i = 0; i < M; ++i) V(i, r) = num_abs(v[i]) < eps ? zero : v[i];
520 }
521
522 // Stage 1: initial throughputs at half the single-station capacity bound
523 std::vector<T> X(R, zero);
524 for (std::size_t r = 0; r < R; ++r) {
525 bool have = false;
526 T capr = zero;
527 for (std::size_t i = 0; i < M; ++i) {
528 if (detail::is_is(c, i) || !(V(i, r) > zero) || !(mu(i, r) > zero)) continue;
529 const T cand = mu(i, r) / V(i, r);
530 if (!have || cand < capr) {
531 capr = cand;
532 have = true;
533 }
534 }
535 if (!have) capr = one; // IS-only class
536 X[r] = half * capr / num_traits<T>::from_int(static_cast<long>(R));
537 }
538
539 MeResult<T> out;
540 out.Ca = Matrix<T>(M, R, one);
541 Matrix<T> lambda(M, R, zero);
542 detail::PseudoOpen<T> po;
543 po.L = Matrix<T>(M, R, zero);
544 po.Cd = Matrix<T>(M, R, one);
545 po.rho = Matrix<T>(M, R, zero);
546
547 const long maxit1 = std::min<long>(opt.maxiter, 100);
548 for (long it1 = 1; it1 <= maxit1; ++it1) {
549 ++out.iter;
550 detail::me_cqn_capacity_cap(X, V, mu, c, M, R);
551 for (std::size_t i = 0; i < M; ++i)
552 for (std::size_t r = 0; r < R; ++r) lambda(i, r) = V(i, r) * X[r];
553 po = detail::me_cqn_pseudoopen(M, R, lambda, mu, mueff, Cseff, Peff, selfp, c, insens,
554 out.Ca, opt);
555 T err1 = zero;
556 std::vector<T> Ltot(R, zero);
557 for (std::size_t r = 0; r < R; ++r)
558 for (std::size_t i = 0; i < M; ++i) Ltot[r] += po.L(i, r);
559 for (std::size_t r = 0; r < R; ++r) {
560 if (!(N[r] > 0) || !(Ltot[r] > zero)) continue;
561 const T e = num_abs(T(Ltot[r] - num_traits<T>::from_int(N[r]))) /
563 if (e > err1) err1 = e;
564 }
565 if (err1 < tol) break;
566 const std::vector<T> Xold = X;
567 for (std::size_t r = 0; r < R; ++r) {
568 if (!(Ltot[r] > zero)) continue;
569 T fac = detail::num_sqrt(T(num_traits<T>::from_int(N[r]) / Ltot[r]));
570 const T lo = num_traits<T>::from_rational(1, 4), hi = num_traits<T>::from_int(4);
571 if (fac < lo) fac = lo;
572 if (fac > hi) fac = hi;
573 X[r] = half * X[r] + half * X[r] * fac;
574 }
575 // Stall guard: the stability cap can bind before the population
576 // target is met, and then X stops moving
577 std::vector<T> Xc = X;
578 detail::me_cqn_capacity_cap(Xc, V, mu, c, M, R);
579 T move = zero;
580 for (std::size_t r = 0; r < R; ++r) {
581 const T den = Xold[r] > num_traits<T>::from_double(1e-12)
582 ? Xold[r]
584 const T d = num_abs(T(Xc[r] - Xold[r])) / den;
585 if (d > move) move = d;
586 }
587 if (move < tol) break;
588 }
589
590 // Stage 2: closed ME solution by convolution, iterated on the flows
591 const std::vector<std::vector<long>> Dec = detail::me_cqn_lattice(N);
592 out.L = po.L;
593 out.rho = po.rho;
594 T err2 = zero;
595 for (long it2 = 1; it2 <= opt.maxiter; ++it2) {
596 ++out.iter;
597 const Matrix<T> F = detail::me_cqn_coefficients(M, R, N, Dec, po.L, po.rho, lambda, mueff,
598 Cseff, out.Ca, c, selfp);
599 const detail::MeCqnConv<T> conv = detail::me_cqn_convolve(M, R, N, Dec, F);
600 out.L = conv.L;
601 out.rho = Matrix<T>(M, R, zero);
602 std::vector<T> Xhat(R, zero);
603 for (std::size_t r = 0; r < R; ++r) {
604 T num = zero, den = zero;
605 for (std::size_t i = 0; i < M; ++i) {
606 if (!(lambda(i, r) > zero)) continue;
607 if (detail::is_is(c, i)) {
608 out.rho(i, r) = out.L(i, r);
609 num += out.L(i, r) * mueff(i, r) / (one - selfp(i, r));
610 } else {
611 T rho_i = zero;
612 for (std::size_t u = 0; u < R; ++u) rho_i += po.rho(i, u);
613 if (rho_i > zero) out.rho(i, r) = conv.U[i] * po.rho(i, r) / rho_i;
614 num += out.rho(i, r) * mu(i, r);
615 }
616 den += V(i, r);
617 }
618 if (den > zero) Xhat[r] = num / den;
619 }
620 err2 = zero;
621 for (std::size_t r = 0; r < R; ++r) {
622 if (!(X[r] > zero)) continue;
623 const T e = num_abs(T(Xhat[r] - X[r])) / X[r];
624 if (e > err2) err2 = e;
625 }
626 if (err2 < tol) {
627 out.converged = true;
628 break;
629 }
630 const std::vector<T> Xold = X;
631 for (std::size_t r = 0; r < R; ++r) X[r] = half * X[r] + half * Xhat[r];
632 detail::me_cqn_capacity_cap(X, V, mu, c, M, R);
633 T move = zero;
634 for (std::size_t r = 0; r < R; ++r) {
635 const T den = Xold[r] > num_traits<T>::from_double(1e-12)
636 ? Xold[r]
638 const T d = num_abs(T(X[r] - Xold[r])) / den;
639 if (d > move) move = d;
640 }
641 if (move < tol) break;
642 for (std::size_t i = 0; i < M; ++i)
643 for (std::size_t r = 0; r < R; ++r) lambda(i, r) = V(i, r) * X[r];
644 po = detail::me_cqn_pseudoopen(M, R, lambda, mu, mueff, Cseff, Peff, selfp, c, insens,
645 out.Ca, opt);
646 }
647
648 // Response times by Little's law on the visit-inclusive throughputs
649 out.lambda = Matrix<T>(M, R, zero);
650 for (std::size_t i = 0; i < M; ++i)
651 for (std::size_t r = 0; r < R; ++r) out.lambda(i, r) = V(i, r) * X[r];
652 out.W = Matrix<T>(M, R, zero);
653 for (std::size_t i = 0; i < M; ++i)
654 for (std::size_t r = 0; r < R; ++r)
655 if (out.lambda(i, r) > zero) out.W(i, r) = out.L(i, r) / out.lambda(i, r);
656 out.Cd = po.Cd;
657 out.X = X;
658 return out;
659}
660
661} // namespace me
662} // namespace line
663
664#endif // LINE_API_ME_ME_CQN_H
InputError(const std::string &what)
Definition error.h:39
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_cqn(std::size_t M, std::size_t R, const std::vector< long > &N, const Matrix< T > &mu, const Matrix< T > &Cs, const std::vector< Matrix< T > > &P, const std::vector< long > &c, const std::vector< long > &refstat_in, const std::vector< char > &insens, const MeOptions &opt=MeOptions())
Maximum-entropy algorithm for closed multiclass queueing networks.
Definition me_cqn.h:463
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