LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_bk.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_BK_H
6#define LINE_API_PFQN_PFQN_BK_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Birman-Kogan asymptotic evaluation of closed networks with many stations.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_bk.m, pfqn_bkue.m and
14 * pfqn_bklc.m. Birman and Kogan (Communications in Statistics. Stochastic
15 * Models 8(3):543-563, 1992) evaluate the multichain partition function by the
16 * saddle point method applied to the Cauchy inversion of its generating
17 * function. Three algorithms live here:
18 *
19 * pfqn_bk Propositions 1 and 3 with Algorithm 1. Stations that serve a
20 * single chain and appear only once (the paper's dedicated
21 * single servers) stay OUTSIDE the exponent as O(1) algebraic
22 * factors, so their poles may be crossed by the saddle point;
23 * Algorithm 1 detects those chains and pins their coordinate on
24 * the pole, where the residue rather than the saddle carries the
25 * mass. The rest are the paper's large groups of identical
26 * stations and are exponentiated.
27 * pfqn_bkue The van der Waerden uniform expansion of Section 4, which
28 * keeps one dominant pole and the saddle in a single erfc
29 * formula and so stays accurate on both sides of the crossing.
30 * pfqn_bklc Algorithm 2, the load concealment reduction of a
31 * multichain network to single chain problems.
32 *
33 * ARITHMETIC. Logarithms, an error function and a Newton iteration, so gated on
34 * num_traits<T>::has_transcendental.
35 */
36
37#include <algorithm>
38#include <cmath>
39#include <cstddef>
40#include <limits>
41#include <string>
42#include <vector>
43
45#include "line/num/number.h"
46#include "line/util/error.h"
47#include "line/util/lu.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace pfqn {
52
53/** Return value of pfqn_bk, mirroring [G, lG, X, U, A, B]. */
54template <class T>
55struct BkResult {
56 T G;
57 T lG;
58 std::vector<T> X; ///< the saddle point coordinates
59 Matrix<T> U; ///< (M x R) utilizations
60 std::vector<std::size_t> A; ///< chains whose dedicated station is not saturated
61 std::vector<std::size_t> B; ///< chains whose dedicated station is a bottleneck
62};
63
64/** Return value of pfqn_bklc. */
65template <class T>
66struct BkLcResult {
67 std::vector<T> X;
70 int it;
71};
72
73namespace detail {
74
75/** Number of stations sharing each demand row, up to relative rounding. */
76template <class T>
77std::vector<std::size_t> bk_multiplicity(const std::vector<std::vector<T>>& L) {
78 const std::size_t M = L.size();
79 std::vector<std::size_t> mult(M, 1);
80 if (M == 0) return mult;
81 const std::size_t R = L[0].size();
82 const double tol = 1e-8; // GlobalConstants.FineTol
83 for (std::size_t i = 0; i < M; ++i) {
84 if (mult[i] > 1) continue;
85 for (std::size_t j = i + 1; j < M; ++j) {
86 double scale = 1.0, diff = 0.0;
87 for (std::size_t r = 0; r < R; ++r) {
88 const double a = num_traits<T>::to_double(L[i][r]);
89 const double b = num_traits<T>::to_double(L[j][r]);
90 scale = std::max(scale, std::max(std::abs(a), std::abs(b)));
91 diff = std::max(diff, std::abs(a - b));
92 }
93 if (diff <= tol * scale) {
94 ++mult[i];
95 ++mult[j];
96 }
97 }
98 }
99 return mult;
100}
101
102/** Exponent of the integrand, groups only (eq. 11 and 23 in unscaled variables). */
103template <class T>
104T bk_psi(const std::vector<T>& z, const std::vector<std::vector<T>>& Lg, const std::vector<T>& N,
105 const std::vector<T>& Z) {
106 using std::log;
107 const T one = num_traits<T>::from_int(1);
109 for (std::size_t r = 0; r < z.size(); ++r) f += Z[r] * z[r] - N[r] * log(z[r]);
110 for (std::size_t i = 0; i < Lg.size(); ++i) {
112 for (std::size_t r = 0; r < z.size(); ++r) u += Lg[i][r] * z[r];
113 f -= log(one - u);
114 }
115 return f;
116}
117
118template <class T>
119std::vector<T> bk_grad(const std::vector<T>& z, const std::vector<std::vector<T>>& Lg,
120 const std::vector<T>& N, const std::vector<T>& Z) {
121 const T one = num_traits<T>::from_int(1);
122 const std::size_t R = z.size();
123 std::vector<T> g(R);
124 for (std::size_t r = 0; r < R; ++r) g[r] = Z[r] - N[r] / z[r];
125 for (std::size_t i = 0; i < Lg.size(); ++i) {
126 T u = num_traits<T>::from_int(0);
127 for (std::size_t r = 0; r < R; ++r) u += Lg[i][r] * z[r];
128 const T d = one / (one - u);
129 for (std::size_t r = 0; r < R; ++r) g[r] += d * Lg[i][r];
130 }
131 return g;
132}
133
134template <class T>
135Matrix<T> bk_hessian(const std::vector<T>& z, const std::vector<std::vector<T>>& Lg,
136 const std::vector<T>& N) {
137 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
138 const std::size_t R = z.size();
139 Matrix<T> H(R, R, zero);
140 for (std::size_t r = 0; r < R; ++r) H(r, r) = N[r] / (z[r] * z[r]);
141 for (std::size_t i = 0; i < Lg.size(); ++i) {
142 T u = zero;
143 for (std::size_t r = 0; r < R; ++r) u += Lg[i][r] * z[r];
144 const T d = one / (one - u);
145 const T d2 = d * d;
146 for (std::size_t r = 0; r < R; ++r)
147 for (std::size_t s = 0; s < R; ++s) H(r, s) += d2 * Lg[i][r] * Lg[i][s];
148 }
149 return H;
150}
151
152} // namespace detail
153
154/**
155 * Birman-Kogan saddle point normalizing constant with bottleneck detection.
156 *
157 * @param L (M x R) service demands
158 * @param N (R) population
159 * @param Z (R) think times, may be empty
160 */
161template <class T>
162BkResult<T> pfqn_bk(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z) {
164 "pfqn_bk requires transcendental arithmetic (saddle point expansion of log G)");
165 using std::exp;
166 using std::log;
167 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
168 const T fineTol = num_traits<T>::from_double(1e-8);
169
170 BkResult<T> res;
171 const std::size_t M = L.rows(), R = L.cols();
172 res.G = one;
173 res.lG = zero;
174 res.X.assign(R, zero);
175 res.U = Matrix<T>(M, R, zero);
176 T Ntot = zero;
177 for (std::size_t r = 0; r < N.size(); ++r) Ntot += N[r];
178 if (L.empty() || N.empty() || !(Ntot > zero)) {
179 for (std::size_t r = 0; r < R; ++r) res.A.push_back(r);
180 return res;
181 }
182 if (N.size() != R) throw InputError("pfqn_bk: L and N disagree on the class count");
183 std::vector<T> Zv = Z;
184 if (Zv.empty()) Zv.assign(R, zero);
185 if (Zv.size() != R) throw InputError("pfqn_bk: L and Z disagree on the class count");
186
187 // An empty class contributes a factor of 1 and has no saddle coordinate.
188 std::size_t nkeep = 0;
189 for (std::size_t r = 0; r < R; ++r)
190 if (N[r] > zero) ++nkeep;
191 if (nkeep > 0 && nkeep < R) {
192 Matrix<T> Lk(M, nkeep, zero);
193 std::vector<T> Nk, Zk;
194 std::vector<std::size_t> map;
195 std::size_t c = 0;
196 for (std::size_t r = 0; r < R; ++r) {
197 if (!(N[r] > zero)) continue;
198 for (std::size_t i = 0; i < M; ++i) Lk(i, c) = L(i, r);
199 Nk.push_back(N[r]);
200 Zk.push_back(Zv[r]);
201 map.push_back(r);
202 ++c;
203 }
204 BkResult<T> red = pfqn_bk(Lk, Nk, Zk);
205 res.G = red.G;
206 res.lG = red.lG;
207 for (std::size_t k = 0; k < map.size(); ++k) {
208 res.X[map[k]] = red.X[k];
209 for (std::size_t i = 0; i < M; ++i) res.U(i, map[k]) = red.U(i, k);
210 }
211 for (std::size_t k = 0; k < red.A.size(); ++k) res.A.push_back(map[red.A[k]]);
212 for (std::size_t k = 0; k < red.B.size(); ++k) res.B.push_back(map[red.B[k]]);
213 return res;
214 }
215
216 // stations with no demand at all do not enter the generating function
217 std::vector<std::vector<T>> Lq;
218 for (std::size_t i = 0; i < M; ++i) {
219 T s = zero;
220 for (std::size_t r = 0; r < R; ++r) s += L(i, r);
221 if (!(s > zero)) continue;
222 std::vector<T> row(R);
223 for (std::size_t r = 0; r < R; ++r) row[r] = L(i, r);
224 Lq.push_back(row);
225 }
226
227 // Dedicated station of each chain: single chain, no identical twin, no think
228 // time, and only when the model holds a group of identical stations, since it
229 // is against M_j >> 1 replicas that a lone station is an O(1) factor.
230 const double inf = std::numeric_limits<double>::infinity();
231 std::vector<double> mu(R, inf);
232 std::vector<long> poleRow(R, -1);
233 std::vector<bool> isPole(Lq.size(), false);
234 std::vector<std::size_t> mult = detail::bk_multiplicity(Lq);
235 bool hasGroup = false;
236 for (std::size_t i = 0; i < mult.size(); ++i)
237 if (mult[i] > 1) hasGroup = true;
238 if (hasGroup) {
239 for (std::size_t i = 0; i < Lq.size(); ++i) {
240 if (mult[i] > 1) continue;
241 std::size_t nz = 0, cnt = 0;
242 for (std::size_t r = 0; r < R; ++r)
243 if (Lq[i][r] > zero) {
244 nz = r;
245 ++cnt;
246 }
247 if (cnt != 1) continue;
248 if (Zv[nz] > fineTol) continue;
249 const double cand = 1.0 / num_traits<T>::to_double(Lq[i][nz]);
250 if (cand < mu[nz]) {
251 mu[nz] = cand;
252 poleRow[nz] = static_cast<long>(i);
253 }
254 }
255 for (std::size_t r = 0; r < R; ++r)
256 if (poleRow[r] >= 0) isPole[static_cast<std::size_t>(poleRow[r])] = true;
257 }
258 std::vector<std::vector<T>> Lg;
259 for (std::size_t i = 0; i < Lq.size(); ++i)
260 if (!isPole[i]) Lg.push_back(Lq[i]);
261
262 // Algorithm 1 as an active set method on the strictly convex psi.
263 std::vector<T> muT(R);
264 for (std::size_t r = 0; r < R; ++r)
265 muT[r] = std::isinf(mu[r]) ? num_traits<T>::from_double(0.0)
267 std::vector<bool> onBound(R, false);
268 std::vector<T> z(R);
269 for (std::size_t r = 0; r < R; ++r) {
270 T den = Zv[r];
271 for (std::size_t i = 0; i < Lg.size(); ++i) den += Lg[i][r];
272 if (!(den > zero)) den = fineTol;
273 z[r] = N[r] / den;
274 if (!std::isinf(mu[r])) {
275 const T cap = num_traits<T>::from_double(0.99 * mu[r]);
276 if (z[r] > cap) z[r] = cap;
277 }
278 }
279 for (int it = 0; it < 200 && !Lg.empty(); ++it) {
280 T umax = zero;
281 for (std::size_t i = 0; i < Lg.size(); ++i) {
282 T u = zero;
283 for (std::size_t r = 0; r < R; ++r) u += Lg[i][r] * z[r];
284 if (u > umax) umax = u;
285 }
286 if (num_traits<T>::to_double(umax) < 0.9) break;
287 for (std::size_t r = 0; r < R; ++r) z[r] = z[r] * num_traits<T>::from_double(0.7);
288 }
289 for (std::size_t outer = 0; outer <= R; ++outer) {
290 for (std::size_t r = 0; r < R; ++r)
291 if (onBound[r]) z[r] = muT[r];
292 std::vector<std::size_t> freeIdx;
293 for (std::size_t r = 0; r < R; ++r)
294 if (!onBound[r]) freeIdx.push_back(r);
295 if (freeIdx.empty()) break;
296 const std::size_t nf = freeIdx.size();
297 for (int it = 0; it < 500; ++it) {
298 std::vector<T> g = detail::bk_grad(z, Lg, N, Zv);
299 double gn = 0.0;
300 for (std::size_t i = 0; i < nf; ++i) {
301 const double v = num_traits<T>::to_double(g[freeIdx[i]]);
302 gn += v * v;
303 }
304 gn = std::sqrt(gn);
305 if (gn <= 1e-12 * std::max(1.0, num_traits<T>::to_double(Ntot))) break;
306 Matrix<T> H = detail::bk_hessian(z, Lg, N);
307 Matrix<T> Hf(nf, nf, zero);
308 std::vector<T> rhs(nf);
309 for (std::size_t i = 0; i < nf; ++i) {
310 for (std::size_t j = 0; j < nf; ++j) Hf(i, j) = H(freeIdx[i], freeIdx[j]);
311 rhs[i] = zero - g[freeIdx[i]];
312 }
313 std::vector<T> dz;
314 try {
315 dz = solve(Hf, rhs);
316 } catch (const std::exception&) {
317 break;
318 }
319 double alpha = 1.0;
320 bool ok = false;
321 std::vector<T> zt(R);
322 while (alpha >= 1e-14) {
323 zt = z;
324 for (std::size_t i = 0; i < nf; ++i)
325 zt[freeIdx[i]] = z[freeIdx[i]] + num_traits<T>::from_double(alpha) * dz[i];
326 ok = true;
327 for (std::size_t i = 0; i < nf && ok; ++i) {
328 const std::size_t r = freeIdx[i];
329 if (!(zt[r] > zero)) ok = false;
330 if (ok && !std::isinf(mu[r]) && zt[r] > muT[r]) ok = false;
331 }
332 for (std::size_t i = 0; i < Lg.size() && ok; ++i) {
333 T u = zero;
334 for (std::size_t r = 0; r < R; ++r) u += Lg[i][r] * zt[r];
335 if (!(u < one)) ok = false;
336 }
337 if (ok) break;
338 alpha /= 2;
339 }
340 if (!ok) break;
341 z = zt;
342 }
343 // a chain whose descent direction still pushes past its pole is in B
344 std::vector<T> g = detail::bk_grad(z, Lg, N, Zv);
345 bool any = false;
346 for (std::size_t i = 0; i < nf; ++i) {
347 const std::size_t r = freeIdx[i];
348 if (std::isinf(mu[r])) continue;
349 if (num_traits<T>::to_double(z[r]) >= mu[r] * (1 - 1e-9) &&
350 num_traits<T>::to_double(g[r]) < 0) {
351 onBound[r] = true;
352 any = true;
353 }
354 }
355 if (!any) break;
356 }
357 for (std::size_t r = 0; r < R; ++r)
358 if (onBound[r]) z[r] = muT[r];
359
360 res.X = z;
361 for (std::size_t r = 0; r < R; ++r) {
362 for (std::size_t i = 0; i < M; ++i) {
363 T u = L(i, r) * z[r];
364 if (onBound[r] && u > one) u = one;
365 res.U(i, r) = u;
366 }
367 if (onBound[r])
368 res.B.push_back(r);
369 else
370 res.A.push_back(r);
371 }
372
373 const T psi0 = detail::bk_psi(z, Lg, N, Zv);
374 T lG;
375 if (res.A.empty()) { // eq. (25): the residues carry everything
376 lG = psi0;
377 } else {
378 Matrix<T> H = detail::bk_hessian(z, Lg, N);
379 const std::size_t nf = res.A.size();
380 Matrix<T> Haa(nf, nf, zero);
381 for (std::size_t i = 0; i < nf; ++i)
382 for (std::size_t j = 0; j < nf; ++j) Haa(i, j) = H(res.A[i], res.A[j]);
383 Matrix<T> LU = Haa;
384 lu_factor(LU);
385 T logdet = zero;
386 for (std::size_t i = 0; i < nf; ++i) logdet += log(num_abs(LU(i, i)));
387 lG = psi0 - num_traits<T>::from_double(0.5 * static_cast<double>(nf) * std::log(2 * M_PI)) -
388 num_traits<T>::from_double(0.5) * logdet;
389 for (std::size_t i = 0; i < nf; ++i) {
390 const std::size_t r = res.A[i];
391 lG -= log(z[r]);
392 if (!std::isinf(mu[r])) lG -= log(one - z[r] / muT[r]);
393 }
394 }
395 res.lG = lG;
396 res.G = exp(lG);
397 return res;
398}
399
400/**
401 * Scaled complementary error function exp(x^2)*erfc(x) for x >= 0. The direct
402 * product overflows past x ~ 26, where the asymptotic series is already exact to
403 * double precision.
404 */
405inline double bk_erfcx(double x) {
406 if (x < 25.0) return std::exp(x * x) * std::erfc(x);
407 const double y = 1.0 / (2.0 * x * x);
408 double term = 1.0, sum = 1.0;
409 for (int k = 1; k <= 12; ++k) {
410 term *= -(2 * k - 1) * y;
411 sum += term;
412 }
413 return sum / (x * std::sqrt(M_PI));
414}
415
416namespace detail {
417
418template <class T>
419T bk_h1(const T& z, const std::vector<T>& D, const T& N, const T& Z) {
420 using std::log;
421 const T one = num_traits<T>::from_int(1);
422 T f = Z * z - N * log(z);
423 for (std::size_t i = 0; i < D.size(); ++i) f -= log(one - D[i] * z);
424 return f;
425}
426
427template <class T>
428T bk_h1d1(const T& z, const std::vector<T>& D, const T& N, const T& Z) {
429 const T one = num_traits<T>::from_int(1);
430 T g = Z - N / z;
431 for (std::size_t i = 0; i < D.size(); ++i) g += D[i] / (one - D[i] * z);
432 return g;
433}
434
435template <class T>
436T bk_h1d2(const T& z, const std::vector<T>& D, const T& N) {
437 const T one = num_traits<T>::from_int(1);
438 T h = N / (z * z);
439 for (std::size_t i = 0; i < D.size(); ++i) {
440 const T d = one - D[i] * z;
441 h += D[i] * D[i] / (d * d);
442 }
443 return h;
444}
445
446template <class T>
447T bk_h1d3(const T& z, const std::vector<T>& D, const T& N) {
448 const T one = num_traits<T>::from_int(1), two = num_traits<T>::from_int(2);
449 T h = num_traits<T>::from_int(-2) * N / (z * z * z);
450 for (std::size_t i = 0; i < D.size(); ++i) {
451 const T d = one - D[i] * z;
452 h += two * D[i] * D[i] * D[i] / (d * d * d);
453 }
454 return h;
455}
456
457template <class T>
458T bk_saddle1(const std::vector<T>& D, const T& N, const T& Z) {
459 if (D.empty()) return N / Z;
460 double dmax = 0.0;
461 for (std::size_t i = 0; i < D.size(); ++i)
462 dmax = std::max(dmax, num_traits<T>::to_double(D[i]));
463 const double hi = 1.0 / dmax;
464 T z = num_traits<T>::from_double(0.5 * hi);
465 for (int it = 0; it < 200; ++it) {
466 const T g = bk_h1d1(z, D, N, Z);
467 if (std::abs(num_traits<T>::to_double(g)) <=
468 1e-14 * std::max(1.0, num_traits<T>::to_double(N)))
469 break;
470 const T dz = (num_traits<T>::from_int(0) - g) / bk_h1d2(z, D, N);
471 double alpha = 1.0;
472 while (true) {
473 const double zt = num_traits<T>::to_double(z) + alpha * num_traits<T>::to_double(dz);
474 if (zt > 0 && zt < hi) break;
475 alpha /= 2;
476 if (alpha < 1e-14) break;
477 }
478 if (alpha < 1e-14) break;
479 z += num_traits<T>::from_double(alpha) * dz;
480 }
481 return z;
482}
483
484} // namespace detail
485
486/**
487 * Birman-Kogan uniform (van der Waerden) expansion for a single chain.
488 *
489 * @param L (M) service demands, single class
490 * @param N population
491 * @param Z think time
492 */
493template <class T>
494BkResult<T> pfqn_bkue(const std::vector<T>& L, const T& N, const T& Z) {
496 "pfqn_bkue requires transcendental arithmetic (uniform expansion of log G)");
497 using std::exp;
498 using std::log;
499 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
500 BkResult<T> res;
501 res.G = one;
502 res.lG = zero;
503 if (!(N > zero)) return res;
504 std::vector<T> Lv;
505 for (std::size_t i = 0; i < L.size(); ++i)
506 if (L[i] > zero) Lv.push_back(L[i]);
507 if (Lv.empty()) {
508 res.lG = N * log(Z) - num_traits<T>::from_double(std::lgamma(num_traits<T>::to_double(N) + 1.0));
509 res.G = exp(res.lG);
510 return res;
511 }
512 std::size_t ipole = 0;
513 for (std::size_t i = 1; i < Lv.size(); ++i)
514 if (Lv[i] > Lv[ipole]) ipole = i;
515 const double dmax = num_traits<T>::to_double(Lv[ipole]);
516 const double tolL = 1e-8 * std::max(1.0, dmax);
517 std::size_t ties = 0;
518 std::vector<double> sorted;
519 for (std::size_t i = 0; i < Lv.size(); ++i) {
520 const double v = num_traits<T>::to_double(Lv[i]);
521 if (std::abs(v - dmax) <= tolL) ++ties;
522 sorted.push_back(v);
523 }
524 std::sort(sorted.begin(), sorted.end());
525 bool hasGroup = false;
526 for (std::size_t i = 1; i < sorted.size(); ++i)
527 if (sorted[i] - sorted[i - 1] <= tolL) hasGroup = true;
528 const bool hasPole = (ties == 1) && hasGroup;
529 std::vector<T> D;
530 T zp = zero;
531 if (hasPole) {
532 for (std::size_t i = 0; i < Lv.size(); ++i)
533 if (i != ipole) D.push_back(Lv[i]);
534 zp = one / Lv[ipole];
535 } else {
536 D = Lv;
537 }
538 const T z0 = detail::bk_saddle1(D, N, Z);
539 const T h2 = detail::bk_h1d2(z0, D, N);
540 const T h3 = detail::bk_h1d3(z0, D, N);
541 if (!hasPole) {
542 // No pole to keep out of the exponent: the expansion degenerates to the
543 // plain saddle point, and the third derivative term goes with the pole it
544 // corrects.
545 res.lG = detail::bk_h1(z0, D, N, Z) - log(z0) -
547 res.G = exp(res.lG);
548 return res;
549 }
550 const T t2 = (one / z0 + h3 / (num_traits<T>::from_int(6) * h2)) /
552 std::sqrt(2 * M_PI * num_traits<T>::to_double(h2)));
553 const double b2 = std::max(0.0, num_traits<T>::to_double(detail::bk_h1(zp, D, N, Z)) -
554 num_traits<T>::to_double(detail::bk_h1(z0, D, N, Z)));
555 if (num_traits<T>::to_double(zp) >= num_traits<T>::to_double(z0)) { // saddle before the pole
556 res.lG = detail::bk_h1(z0, D, N, Z) +
557 log(num_traits<T>::from_double(0.5 * bk_erfcx(std::sqrt(b2))) + t2);
558 } else { // the pole has been crossed and its residue leads
559 res.lG = detail::bk_h1(zp, D, N, Z) +
560 log(num_traits<T>::from_double(1.0 - 0.5 * std::erfc(std::sqrt(b2))) +
561 t2 * num_traits<T>::from_double(std::exp(-b2)));
562 }
563 res.G = exp(res.lG);
564 return res;
565}
566
567/**
568 * Birman-Kogan load concealment algorithm (Algorithm 2).
569 *
570 * @param L (M x R) service demands
571 * @param N (R) population
572 * @param Z (R) think times, may be empty
573 * @param method single chain solver, "mva" (default) or "ue"
574 * @param tol convergence tolerance on the throughputs
575 * @param maxiter maximum number of sweeps
576 */
577template <class T>
578BkLcResult<T> pfqn_bklc(const Matrix<T>& L, const std::vector<T>& N, const std::vector<T>& Z,
579 const std::string& method = "mva", double tol = 1e-10,
580 int maxiter = 1000) {
582 "pfqn_bklc requires transcendental arithmetic");
583 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
584 const std::size_t M = L.rows(), R = L.cols();
585 BkLcResult<T> res;
586 res.X.assign(R, zero);
587 res.Q = Matrix<T>(M, R, zero);
588 res.U = Matrix<T>(M, R, zero);
589 res.it = 0;
590 T Ntot = zero;
591 for (std::size_t r = 0; r < N.size(); ++r) Ntot += N[r];
592 if (L.empty() || !(Ntot > zero)) return res;
593 std::vector<T> Zv = Z;
594 if (Zv.empty()) Zv.assign(R, zero);
595 const bool ue = (method == "ue");
596 if (tol <= 0) tol = 1e-10;
597 if (maxiter <= 0) maxiter = 1000;
598
599 // Step 1: the saddle point utilizations of Corollary 1 seed the iteration
600 std::vector<T> X(R, zero);
601 try {
602 BkResult<T> seed = pfqn_bk(L, N, Zv);
603 for (std::size_t r = 0; r < R; ++r) {
604 const double v = num_traits<T>::to_double(seed.X[r]);
605 X[r] = (std::isfinite(v) && v >= 0) ? seed.X[r] : zero;
606 }
607 } catch (const std::exception&) {
608 }
609 for (std::size_t r = 0; r < R; ++r) {
610 T cap = zero, sum = zero;
611 for (std::size_t i = 0; i < M; ++i) {
612 if (L(i, r) > cap) cap = L(i, r);
613 sum += L(i, r);
614 }
615 if (!(X[r] > zero) && N[r] > zero) X[r] = N[r] / (Zv[r] + sum);
616 if (cap > zero && X[r] > one / cap) X[r] = one / cap;
617 }
618
619 Matrix<T> Q(M, R, zero);
620 for (res.it = 1; res.it <= maxiter; ++res.it) {
621 std::vector<T> Xold = X;
622 for (std::size_t l = 0; l < R; ++l) {
623 if (!(N[l] > zero)) {
624 X[l] = zero;
625 for (std::size_t i = 0; i < M; ++i) Q(i, l) = zero;
626 continue;
627 }
628 // Step 2a: residual capacity left to chain l at every station
629 std::vector<T> D(M);
630 for (std::size_t i = 0; i < M; ++i) {
631 T busy = zero;
632 for (std::size_t k = 0; k < R; ++k)
633 if (k != l) busy += L(i, k) * X[k];
634 T A = one - busy;
636 D[i] = L(i, l) / A;
637 }
638 // Step 2b: solve the single chain network with the thinned rates
639 if (ue) {
640 std::vector<T> Qi(M, zero);
641 T lgPrev = zero;
642 T Xl = zero;
643 const int Nl = static_cast<int>(std::llround(num_traits<T>::to_double(N[l])));
644 for (int nn = 1; nn <= Nl; ++nn) {
645 const T lgn = pfqn_bkue(D, num_traits<T>::from_int(nn), Zv[l]).lG;
647 std::exp(num_traits<T>::to_double(lgPrev) - num_traits<T>::to_double(lgn)));
648 for (std::size_t i = 0; i < M; ++i) Qi[i] = D[i] * Xl * (one + Qi[i]);
649 lgPrev = lgn;
650 }
651 X[l] = Xl;
652 for (std::size_t i = 0; i < M; ++i) Q(i, l) = Qi[i];
653 } else {
654 Matrix<T> Dm(M, 1, zero);
655 for (std::size_t i = 0; i < M; ++i) Dm(i, 0) = D[i];
656 std::vector<int> Nl(1, static_cast<int>(std::llround(num_traits<T>::to_double(N[l]))));
657 Matrix<T> Zl(1, 1, Zv[l]);
658 MvaResult<T> mva = pfqn_mva(Dm, Nl, Zl);
659 X[l] = mva.XN[0];
660 for (std::size_t i = 0; i < M; ++i) Q(i, l) = mva.QN(i, 0);
661 }
662 }
663 double diff = 0.0, xmax = 1.0;
664 for (std::size_t r = 0; r < R; ++r) {
665 diff = std::max(diff, std::abs(num_traits<T>::to_double(X[r]) -
666 num_traits<T>::to_double(Xold[r])));
667 xmax = std::max(xmax, std::abs(num_traits<T>::to_double(X[r])));
668 }
669 if (diff <= tol * xmax) break;
670 }
671 if (res.it > maxiter) res.it = maxiter;
672 res.X = X;
673 res.Q = Q;
674 for (std::size_t r = 0; r < R; ++r)
675 for (std::size_t i = 0; i < M; ++i) res.U(i, r) = L(i, r) * X[r];
676 return res;
677}
678
679} // namespace pfqn
680} // namespace line
681
682#endif // LINE_API_PFQN_PFQN_BK_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
bool empty() const
Definition matrix.h:92
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
MvaResult< T > pfqn_mva(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &mi)
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
Definition pfqn_mva.h:71
BkResult< T > pfqn_bk(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Birman-Kogan saddle point normalizing constant with bottleneck detection.
Definition pfqn_bk.h:162
BkLcResult< T > pfqn_bklc(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::string &method="mva", double tol=1e-10, int maxiter=1000)
Birman-Kogan load concealment algorithm (Algorithm 2).
Definition pfqn_bk.h:578
BkResult< T > pfqn_bkue(const std::vector< T > &L, const T &N, const T &Z)
Birman-Kogan uniform (van der Waerden) expansion for a single chain.
Definition pfqn_bk.h:494
double bk_erfcx(double x)
Scaled complementary error function exp(x^2)*erfc(x) for x >= 0.
Definition pfqn_bk.h:405
T num_abs(const T &v)
Definition number.h:172
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Number-type abstraction for the templated API port.
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
Return value of pfqn_bklc.
Definition pfqn_bk.h:66
std::vector< T > X
Definition pfqn_bk.h:67
Return value of pfqn_bk, mirroring [G, lG, X, U, A, B].
Definition pfqn_bk.h:55
std::vector< std::size_t > A
chains whose dedicated station is not saturated
Definition pfqn_bk.h:60
Matrix< T > U
(M x R) utilizations
Definition pfqn_bk.h:59
std::vector< T > X
the saddle point coordinates
Definition pfqn_bk.h:58
std::vector< std::size_t > B
chains whose dedicated station is a bottleneck
Definition pfqn_bk.h:61