LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_nc.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_NC_H
6#define LINE_API_PFQN_NC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Normalizing constant of a product-form queueing network: the dispatcher.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_nc.m. The routine is two things
14 * at once, and they are kept separate here:
15 *
16 * 1. a MODEL REDUCTION, which folds the open classes into rescaled demands,
17 * drops empty and demand-free classes and stations, normalizes the demands
18 * per class, and closes the degenerate cases (single station, M identical
19 * replicas, delay only) in closed form. This is where most of the file is,
20 * and it is exact.
21 * 2. a DISPATCH to one of the normalizing-constant algorithms.
22 *
23 * OPEN CLASSES. MATLAB marks an open class by N_r = Inf. There is no infinity
24 * in an exact field, so this port marks it by a NEGATIVE population, which is
25 * also what the .qn interchange format uses. Each station's demands are
26 * inflated by 1/(1 - sum_r lambda_r L(i,r)) and the open queue lengths are
27 * read off directly, exactly as in the reference. Note that the reference
28 * leaves lGopen at zero (the line accumulating sum log Ut is commented out),
29 * so the returned constant is the CLOSED-CONDITIONAL one; that convention is
30 * preserved, because changing it would silently rescale every caller's result.
31 *
32 * ARITHMETIC, NOT ALGORITHMS, IS WHAT IS REFUSED. The whole ladder of
33 * `compute_norm_const` is dispatched here: ca, clw, cub/gm, kt, bkt, lekt, le, ble, dir, aghq, ls, is,
34 * mci, imci, sampling, mmint2/gleint, pana, propfair, rgf, mva, exact, comom
35 * and recal. Every one of them but ca / exact / recal / mva / comom is an
36 * asymptotic or Monte Carlo estimator formed in logarithms, so it is compiled
37 * only when `num_traits<T>::has_transcendental` and refused BY NAME otherwise
38 * rather than silently redirected to the convolution: a normalizing constant
39 * computed by an algorithm the caller did not ask for is indistinguishable
40 * from the right answer until it is wrong. NcMethod::Default's multi-station
41 * branch (cub for sum(N) < 1e3, le above it) is refused on the same grounds in
42 * exact arithmetic. `rgf` is the one two-sided name: on a SINGLE-CLASS model it
43 * is the log-domain generating-function recursion and is refused in an exact
44 * field, while a multiclass request reduces to ca and stays exact, reported as
45 * 'rgf/ca'.
46 *
47 * Arithmetic: EXACT-CAPABLE for methods Ca, Exact, Recal, Mva, Comom and for
48 * the whole reduction. The reference's tolerance-based filters
49 * (options.tol, GlobalConstants.FineTol) become exact zero tests, for the same
50 * reason as in pfqn_unique: a tolerant filter perturbs the constant and has no
51 * meaning in the rational field. Pass a positive atol to recover the tolerant
52 * behaviour in any arithmetic.
53 *
54 * WHY THE INEXACT BRANCHES REPORT lG AND NOT G. An estimator returns the LOG
55 * constant; exponentiating it and taking the log again loses the answer
56 * outright once lG passes ~709. Those branches therefore accumulate lG
57 * additively (log Gscale + log Gzdem + lG_core) and derive G from it, while
58 * the exact branches keep the exact product and take its log, as before.
59 */
60
61#include <cmath>
62#include <cstddef>
63#include <string>
64#include <vector>
65
92#include "line/num/number.h"
93#include "line/util/error.h"
94#include "line/util/matrix.h"
96
97namespace line {
98namespace pfqn {
99
100/** The methods this port dispatches, one per `compute_norm_const` case. */
101enum class NcMethod {
102 Default,
103 Adaptive, ///< the reference groups 'adaptive' with 'default'
105 Exact,
111 Gm, ///< the reference's alias of 'cub'
113 Bkt, ///< KT minus the exact Stirling remainder of each Laplaced class (BKT)
114 Lekt, ///< the estimator Ble and Bkt both compute, on the cheaper side
115 Bk, ///< Birman-Kogan saddle point with bottleneck detection
116 Bkue, ///< Birman-Kogan uniform (van der Waerden) expansion, single chain
117 Lc, ///< Birman-Kogan Algorithm 2, single chain subproblems by MVA
118 LcUe, ///< Algorithm 2 with the uniform expansion as the single chain solver
120 Ble, ///< LE plus the empirical eps->0 correction
121 Aghq, ///< adaptive Gauss-Hermite over the simplex; q=1 is Le
126 Mcmc, ///< Chen-O'Cinneide regularization; supplies X and Q, never a constant
129 Gleint, ///< the reference's alias of 'mmint2'
132 Rgf, ///< recursion by generating functions; residues beyond one class
133 Divdiff, ///< divided-difference closed form; no think time, no load dependence
134 Ger ///< residue closed form; free in the eliminated class populations
135};
136
137inline const char* nc_method_name(NcMethod m) {
138 switch (m) {
139 case NcMethod::Default: return "default";
140 case NcMethod::Adaptive: return "adaptive";
141 case NcMethod::Ca: return "ca";
142 case NcMethod::Exact: return "exact";
143 case NcMethod::Recal: return "recal";
144 case NcMethod::Mva: return "mva";
145 case NcMethod::Comom: return "comom";
146 case NcMethod::Clw: return "clw";
147 case NcMethod::Cub: return "cub";
148 case NcMethod::Gm: return "gm";
149 case NcMethod::Kt: return "kt";
150 case NcMethod::Bkt: return "bkt";
151 case NcMethod::Lekt: return "lekt";
152 case NcMethod::Bk: return "bk";
153 case NcMethod::Bkue: return "bkue";
154 case NcMethod::Lc: return "lc";
155 case NcMethod::LcUe: return "lc.ue";
156 case NcMethod::Le: return "le";
157 case NcMethod::Ble: return "ble";
158 case NcMethod::Aghq: return "aghq";
159 case NcMethod::Ls: return "ls";
160 case NcMethod::Is: return "is";
161 case NcMethod::Mci: return "mci";
162 case NcMethod::Imci: return "imci";
163 case NcMethod::Mcmc: return "mcmc";
164 case NcMethod::Sampling: return "sampling";
165 case NcMethod::Mmint2: return "mmint2";
166 case NcMethod::Gleint: return "gleint";
167 case NcMethod::Pana: return "pana";
168 case NcMethod::Propfair: return "propfair";
169 case NcMethod::Rgf: return "rgf";
170 case NcMethod::Divdiff: return "divdiff";
171 case NcMethod::Ger: return "ger";
172 }
173 return "default";
174}
175
176/** Map a method name to its enum; throws UnsupportedError on an unknown one. */
177inline NcMethod nc_method_of(const std::string& s) {
178 if (s == "default") return NcMethod::Default;
179 if (s == "adaptive") return NcMethod::Adaptive;
180 if (s == "ca") return NcMethod::Ca;
181 if (s == "exact") return NcMethod::Exact;
182 if (s == "recal") return NcMethod::Recal;
183 if (s == "mva") return NcMethod::Mva;
184 if (s == "comom") return NcMethod::Comom;
185 if (s == "clw") return NcMethod::Clw;
186 if (s == "cub") return NcMethod::Cub;
187 if (s == "gm") return NcMethod::Gm;
188 if (s == "kt") return NcMethod::Kt;
189 if (s == "bkt") return NcMethod::Bkt;
190 if (s == "lekt") return NcMethod::Lekt;
191 if (s == "bk") return NcMethod::Bk;
192 if (s == "bkue") return NcMethod::Bkue;
193 if (s == "lc") return NcMethod::Lc;
194 if (s == "lc.ue") return NcMethod::LcUe;
195 if (s == "le") return NcMethod::Le;
196 if (s == "ble") return NcMethod::Ble;
197 if (s == "aghq") return NcMethod::Aghq;
198 if (s == "ls") return NcMethod::Ls;
199 if (s == "is") return NcMethod::Is;
200 if (s == "mci") return NcMethod::Mci;
201 if (s == "imci") return NcMethod::Imci;
202 if (s == "mcmc") return NcMethod::Mcmc;
203 if (s == "sampling") return NcMethod::Sampling;
204 if (s == "mmint2") return NcMethod::Mmint2;
205 if (s == "gleint") return NcMethod::Gleint;
206 if (s == "pana") return NcMethod::Pana;
207 if (s == "propfair") return NcMethod::Propfair;
208 if (s == "rgf") return NcMethod::Rgf;
209 if (s == "divdiff") return NcMethod::Divdiff;
210 if (s == "ger") return NcMethod::Ger;
211 throw UnsupportedError(ErrorKind{"Method"}, "pfqn_nc: unrecognized method '" + s + "'");
212}
213
214/** The `options` fields `compute_norm_const` reads beyond the method itself. */
215struct NcOptions {
216 std::size_t samples = 100000; ///< SolverOptions('NC').samples
217 unsigned long seed = 23000; ///< SolverOptions('NC').seed
218 double tol = 1e-6; ///< handed to pfqn_comomrm
219 /// options.config.aghq_nodes: nodes per simplex direction of the adaptive
220 /// Gauss-Hermite rule. q = 1 reproduces pfqn_le and the rule costs q^(M-1)
221 /// evaluations, so the default stays small.
222 std::size_t aghq_nodes = 3;
223 /// options.config.mcmc_batches: batches pfqn_mcmc splits its run into
225 /// options.config.mcmc_burnin: warm-up fraction pfqn_mcmc discards
227};
228
229/**
230 * Refuse a method in an arithmetic it has no meaning in.
231 * Kept as a function so the message is identical wherever it is raised.
232 */
233inline void pfqn_nc_refuse(const std::string& method) {
234 throw UnsupportedError(ErrorKind{"Arithmetic"}, "pfqn_nc: method '" + method +
235 "' is an asymptotic or Monte Carlo estimator formed in logarithms and "
236 "needs transcendental arithmetic. "
237 "Use 'ca', 'exact', 'recal', 'mva' or 'comom' for an exact constant.");
238}
239
240template <class T>
242 T G; ///< normalizing constant, exact when the method is
243 double lG; ///< its logarithm
244 std::vector<T> X; ///< (R) per-class throughput, empty when not produced
245 Matrix<T> Q; ///< (M x R) queue lengths, empty when not produced
246 std::string method; ///< the algorithm actually used
247 /**
248 * False when the METHOD DECLINED THE MODEL, which is the reference's
249 * `lG = []`: pana outside normal usage, or mmint2/gleint on a model with
250 * more than one queueing station. MATLAB warns and returns empty, and
251 * `getAvg` then renders a table of ZEROS while still reporting a completed
252 * analysis; the caller reproduces that by returning an all-zero solution.
253 *
254 * This is NOT the same as a refusal. A refusal throws, and the reference
255 * throws too -- `comom` on a multi-queue model is `line_error` in
256 * `pfqn_nc.m`, added deliberately with the comment that "a silently zeroed
257 * result is worse than no result". Only the two branches above decline.
258 */
259 bool valid = true;
260};
261
262/**
263 * @brief Normalizing constant of a product-form queueing network: the
264 * dispatcher.
265 *
266 * @param lambda (R) arrival rates; zero on the closed classes, may be empty
267 * @param L (M x R) service demands
268 * @param N (R) populations; a NEGATIVE entry marks an open class
269 * @param Z (K x R) think times, summed over rows
270 * @param method requested algorithm
271 * @param atol threshold below which a demand counts as zero; 0 for exact
272 * @param nopt sample count, seed and tolerance the estimators read
273 */
274template <class T>
275NcDispatchResult<T> pfqn_nc(const std::vector<T>& lambda, const Matrix<T>& L,
276 const std::vector<int>& N, const Matrix<T>& Z, NcMethod method,
277 const T& atol, const NcOptions& nopt) {
278 const std::size_t R = N.size();
279 const T zero = num_traits<T>::from_int(0);
280 const T one = num_traits<T>::from_int(1);
281
283 res.G = one;
284 res.lG = 0.0;
285 // Every return BEFORE the dispatch reports 'exact', because every one of
286 // them is a closed form rather than an algorithm: the reference sets this
287 // up front and lets the dispatch overwrite it.
288 res.method = "exact";
289
290 if (R == 0) {
291 res.G = zero;
292 res.lG = -std::numeric_limits<double>::infinity();
293 return res;
294 }
295 if (!L.empty() && L.cols() != R)
296 throw InputError("pfqn_nc: L and N disagree on the class count");
297
298 const std::size_t M0 = L.empty() ? 0 : L.rows();
299 std::vector<T> lam(R, zero);
300 for (std::size_t r = 0; r < R && r < lambda.size(); ++r) lam[r] = lambda[r];
301
302 // ---- open classes: inflate the demands, read off the open queue lengths --
303 Matrix<T> Lw = L;
304 Matrix<T> Qopen(M0, R, zero);
305 std::vector<std::size_t> ocl;
306 for (std::size_t r = 0; r < R; ++r)
307 if (N[r] < 0) ocl.push_back(r);
308 for (std::size_t i = 0; i < M0; ++i) {
309 T u = one;
310 for (std::size_t r = 0; r < R; ++r) u -= lam[r] * L(i, r);
311 if (u == zero) {
312 // A station the open classes fill exactly. This is not a pathology:
313 // it is what a SOURCE row looks like, whose chain demand is 1/lambda
314 // by construction. The reference divides by the zero, gets Inf and
315 // NaN, and its demand filter (Lmax./Lsum > FineTol, false on NaN)
316 // drops the row a few lines later. Dropping it here reaches the same
317 // constant without carrying non-finite values through the reduction.
318 for (std::size_t r = 0; r < R; ++r) Lw(i, r) = zero;
319 continue;
320 }
321 for (std::size_t r = 0; r < R; ++r) {
322 Lw(i, r) = L(i, r) / u;
323 Qopen(i, r) = lam[r] * Lw(i, r) / u;
324 }
325 }
326
327 // Closed populations, with the open classes zeroed out.
328 std::vector<int> Nc(R, 0);
329 for (std::size_t r = 0; r < R; ++r) Nc[r] = N[r] > 0 ? N[r] : 0;
330 long Ntot = 0;
331 for (int v : Nc) Ntot += v;
332 if (Ntot == 0) return res; // lG = 0, G = 1
333
334 const auto colsum = [&](const Matrix<T>& A, std::size_t r) {
335 T s = zero;
336 for (std::size_t i = 0; i < A.rows(); ++i) s += A(i, r);
337 return s;
338 };
339
340 // ---- drop the empty classes ---------------------------------------------
341 std::vector<std::size_t> nnz;
342 for (std::size_t r = 0; r < R; ++r)
343 if (Nc[r] > 0) nnz.push_back(r);
344 const std::size_t R1 = nnz.size();
345
346 // per-class rescaling-order rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
347 std::vector<T> scalevec(R1, one);
348 Matrix<T> L1(M0, R1), Z1(Z.empty() ? 0 : Z.rows(), R1);
349 for (std::size_t k = 0; k < R1; ++k) {
350 const std::size_t r = nnz[k];
351 T mx = zero;
352 for (std::size_t i = 0; i < M0; ++i)
353 if (Lw(i, r) > mx) mx = Lw(i, r);
354 for (std::size_t i = 0; i < Z1.rows(); ++i)
355 if (Z(i, r) > mx) mx = Z(i, r);
356 if (mx > zero) scalevec[k] = mx;
357 for (std::size_t i = 0; i < M0; ++i) L1(i, k) = Lw(i, r) / scalevec[k];
358 for (std::size_t i = 0; i < Z1.rows(); ++i) Z1(i, k) = Z(i, r) / scalevec[k];
359 }
360 // Gscale is the factor the rescaling removed: prod_r scalevec_r^{N_r}.
361 T Gscale = one;
362 for (std::size_t k = 0; k < R1; ++k)
363 Gscale *= num_pow_int(scalevec[k], static_cast<unsigned>(Nc[nnz[k]]));
364 // The LOG path must never take the log of that product: scalevec = 3 with
365 // N = 600 leaves double range and turned a perfectly good lG = 862 into inf.
366 // Accumulate sum_r N_r log(scalevec_r) directly instead. The exact branches
367 // keep using Gscale itself, where the product is representable by
368 // construction.
369 double lGscale = 0.0;
371 for (std::size_t k = 0; k < R1; ++k)
372 lGscale += static_cast<double>(Nc[nnz[k]]) *
373 std::log(num_traits<T>::to_double(scalevec[k]));
374 }
375
376 // ---- drop the stations with no demand at all -----------------------------
377 std::vector<std::size_t> demSt;
378 for (std::size_t i = 0; i < M0; ++i) {
379 T rs = zero;
380 for (std::size_t k = 0; k < R1; ++k) rs += L1(i, k);
381 if (rs > atol) demSt.push_back(i);
382 }
383 Matrix<T> L2(demSt.size(), R1);
384 for (std::size_t a = 0; a < demSt.size(); ++a)
385 for (std::size_t k = 0; k < R1; ++k) L2(a, k) = L1(demSt[a], k);
386 const std::size_t M = demSt.size();
387
388 std::vector<int> N2(R1, 0);
389 for (std::size_t k = 0; k < R1; ++k) N2[k] = Nc[nnz[k]];
390
391 // Aggregate think times, one row.
392 Matrix<T> Zag(Z1.rows() == 0 ? 0 : 1, R1);
393 T Ztot = zero;
394 for (std::size_t k = 0; k < R1; ++k) {
395 T s = zero;
396 for (std::size_t i = 0; i < Z1.rows(); ++i) s += Z1(i, k);
397 if (Zag.rows() > 0) Zag(0, k) = s;
398 Ztot += s;
399 }
400
401 // Delay-only constant prod_r Z_r^{N_r}/N_r!, needed by several branches.
402 const auto delayG = [&](const std::vector<std::size_t>& cls) {
403 T g = one;
404 for (std::size_t k : cls) {
405 T zs = zero;
406 for (std::size_t i = 0; i < Z1.rows(); ++i) zs += Z1(i, k);
407 g *= num_pow_int(zs, static_cast<unsigned>(N2[k])) /
408 num_factorial<T>(static_cast<unsigned>(N2[k]));
409 }
410 return g;
411 };
412 // The open-class measures ride along with the CLOSED ones: the reference
413 // assembles X and Q only when the dispatched method produced them as a
414 // by-product (which is `mva` alone) and returns both empty otherwise, so a
415 // caller can test emptiness to decide whether it must derive the measures
416 // itself. Attaching Qopen to an otherwise empty result would answer that
417 // test wrongly and silence the caller's own open-chain formula.
418 bool have_measures = false;
419 const auto attach_open = [&]() {
420 if (ocl.empty() || !have_measures) return;
421 res.Q = Matrix<T>(M0, R, zero);
422 for (std::size_t i = 0; i < M0; ++i)
423 for (std::size_t r : ocl) res.Q(i, r) = Qopen(i, r);
424 res.X.assign(R, zero);
425 for (std::size_t r : ocl) res.X[r] = lam[r];
426 };
427 const auto finish = [&](const T& gcore) {
428 res.G = Gscale * gcore;
430 attach_open();
431 return res;
432 };
433 // The estimator finish: lG is the answer and G is derived from it, so a
434 // constant past the double range still reports a usable logarithm.
435 const auto finish_log = [&](double lgcore) {
436 res.lG = lGscale + lgcore;
437 res.G = num_traits<T>::from_double(std::exp(res.lG));
438 attach_open();
439 return res;
440 };
441
442 // ---- degenerate cases, in closed form ------------------------------------
443 T Lsum = zero;
444 for (std::size_t i = 0; i < M; ++i)
445 for (std::size_t k = 0; k < R1; ++k) Lsum += L2(i, k);
446
447 if (M == 0 || !(Lsum > atol)) {
448 // All demands zero: the whole population sits in the delay.
449 std::vector<std::size_t> all(R1);
450 for (std::size_t k = 0; k < R1; ++k) all[k] = k;
451 return finish(Ztot > atol ? delayG(all) : one);
452 }
453 if (M == 1 && !(Ztot > atol)) {
454 // Single station, no delay: G = (sum N)! / prod N_r! * prod L_r^{N_r}.
455 long tot = 0;
456 for (int v : N2) tot += v;
457 T g = num_factorial<T>(static_cast<unsigned>(tot));
458 for (std::size_t k = 0; k < R1; ++k)
459 g *= num_pow_int(L2(0, k), static_cast<unsigned>(N2[k])) /
460 num_factorial<T>(static_cast<unsigned>(N2[k]));
461 return finish(g);
462 }
463 if (!(Ztot > atol)) {
464 // M identical replicas, no delay: the multiset-count closed form.
465 bool identical = true;
466 for (std::size_t i = 1; i < M && identical; ++i)
467 for (std::size_t k = 0; k < R1; ++k)
468 if (L2(i, k) != L2(0, k)) {
469 identical = false;
470 break;
471 }
472 if (identical) {
473 long tot = 0;
474 for (int v : N2) tot += v;
475 T g = num_factorial<T>(static_cast<unsigned>(tot + M - 1)) /
476 num_factorial<T>(static_cast<unsigned>(M - 1));
477 for (std::size_t k = 0; k < R1; ++k)
478 g *= num_pow_int(L2(0, k), static_cast<unsigned>(N2[k])) /
479 num_factorial<T>(static_cast<unsigned>(N2[k]));
480 return finish(g);
481 }
482 }
483
484 // ---- classes whose jobs never leave the delay -----------------------------
485 std::vector<std::size_t> zdem, nzdem;
486 for (std::size_t k = 0; k < R1; ++k) {
487 T s = zero;
488 for (std::size_t i = 0; i < M; ++i) s += L2(i, k);
489 (s > atol ? nzdem : zdem).push_back(k);
490 }
491 const T Gzdem = zdem.empty() ? one : delayG(zdem);
492
493 Matrix<T> L3(M, nzdem.size());
494 Matrix<T> Z3(Zag.rows(), nzdem.size());
495 std::vector<int> N3(nzdem.size(), 0);
496 for (std::size_t a = 0; a < nzdem.size(); ++a) {
497 for (std::size_t i = 0; i < M; ++i) L3(i, a) = L2(i, nzdem[a]);
498 for (std::size_t i = 0; i < Zag.rows(); ++i) Z3(i, a) = Zag(i, nzdem[a]);
499 N3[a] = N2[nzdem[a]];
500 }
501
502 // Mean values produced as a by-product come back in the REDUCED, SCALED and
503 // REORDERED problem: demand-bearing stations only, nonzero-demand classes
504 // only, on demands divided by scalevec. Undo all three, or the caller reads
505 // a permuted throughput of the wrong magnitude.
506 const auto attach_measures = [&](const std::vector<T>& Xr, const Matrix<T>& Qr) {
507 res.X.assign(R, num_traits<T>::from_int(0));
508 res.Q = Matrix<T>(M0, R, num_traits<T>::from_int(0));
509 for (std::size_t a = 0; a < nzdem.size() && a < Xr.size(); ++a) {
510 const std::size_t r = nnz[nzdem[a]];
511 res.X[r] = Xr[a] / scalevec[nzdem[a]];
512 for (std::size_t i = 0; i < M && i < Qr.rows(); ++i) res.Q(demSt[i], r) = Qr(i, a);
513 }
514 };
515
516 const std::size_t Rc = nzdem.size();
517 T Z3tot = zero;
518 for (std::size_t i = 0; i < Z3.rows(); ++i)
519 for (std::size_t a = 0; a < Rc; ++a) Z3tot += Z3(i, a);
520
521 // Chain-level views the estimators want: Z summed over its rows, and the
522 // population as field elements rather than ints.
523 std::vector<T> Zv(Rc, zero);
524 for (std::size_t i = 0; i < Z3.rows(); ++i)
525 for (std::size_t a = 0; a < Rc; ++a) Zv[a] += Z3(i, a);
526 std::vector<T> Nv(Rc, zero);
527 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
528 long Nsum3 = 0;
529 for (int v : N3) Nsum3 += v;
530
531 // ---- the estimator ladder -------------------------------------------------
532 // These are the branches of `compute_norm_const` that answer with a log:
533 // asymptotic expansions, quadratures and Monte Carlo estimators. They are
534 // separated from the exact switch below because they must return through
535 // finish_log, and because the whole group is meaningless -- not merely
536 // inaccurate -- in an exact field, where it is refused by name.
537 const bool default_multi =
538 (method == NcMethod::Default || method == NcMethod::Adaptive) && M > 1;
539 const bool default_big_repairman = (method == NcMethod::Default ||
540 method == NcMethod::Adaptive) &&
541 M == 1 && Z3tot > atol && Nsum3 >= 10000;
542 const bool estimator =
543 default_multi || default_big_repairman || method == NcMethod::Clw ||
544 method == NcMethod::Cub || method == NcMethod::Gm || method == NcMethod::Kt ||
545 method == NcMethod::Bkt || method == NcMethod::Lekt ||
546 method == NcMethod::Bk || method == NcMethod::Bkue ||
547 method == NcMethod::Lc || method == NcMethod::LcUe ||
548 method == NcMethod::Le || method == NcMethod::Ble || method == NcMethod::Ls ||
549 method == NcMethod::Aghq ||
550 method == NcMethod::Is ||
551 method == NcMethod::Mci || method == NcMethod::Imci ||
552 method == NcMethod::Mcmc || method == NcMethod::Sampling ||
553 method == NcMethod::Mmint2 || method == NcMethod::Gleint ||
554 method == NcMethod::Pana || method == NcMethod::Propfair ||
555 // 'rgf' answers with a log in both arities: the grouped convolution at
556 // one class, the residue recursion of Harrison-Coury Thm 1 beyond it.
557 method == NcMethod::Rgf ||
558 // 'divdiff' evaluates alternating sums as signed log-sum-exps, so it
559 // answers with a log like the estimators do, exact though it is.
560 method == NcMethod::Divdiff;
561 if (estimator) {
562 if constexpr (!num_traits<T>::has_transcendental) {
563 pfqn_nc_refuse(default_multi || default_big_repairman
564 ? std::string("default (the multi-station cub / le branch)")
565 : std::string(nc_method_name(method)));
566 } else {
567 const double lgz = num_traits<T>::log_as_double(Gzdem);
568 McRng rng(static_cast<std::uint64_t>(nopt.seed));
569 const T fineTol = num_traits<T>::from_double(1e-8); // GlobalConstants.FineTol
570 // The order the reference raises as far as a fixed cost budget allows.
571 const auto cub_budget_order = [&]() {
572 const double Cmax = static_cast<double>(M * Rc) * 125000.0;
573 const int maxorder =
574 static_cast<int>(std::min<double>(std::ceil((Nsum3 - 1) / 2.0), 16.0));
575 double tot = 0.0;
576 int order = 0;
577 while (order < maxorder) {
578 const double next =
579 static_cast<double>(Rc) *
580 nck(static_cast<int>(M) + 2 * (order + 1), static_cast<int>(M) - 1);
581 if (tot + next > Cmax) break;
582 ++order;
583 tot += next;
584 }
585 // Cmax prices neither the Grundmann-Moeller node count nor the
586 // think-time v-integration, so the order is re-priced against
587 // the true evaluation count and lowered until it fits.
588 double Zsum = 0.0;
589 for (std::size_t a = 0; a < Rc; ++a) Zsum += num_traits<T>::to_double(Zv[a]);
590 while (order > 0 &&
591 pfqn_cub_evals(static_cast<int>(M), order, Zsum) > CUB_MAX_EVALS)
592 --order;
593 return order;
594 };
595 switch (method) {
598 // ONE ESTIMATOR ANSWERS THE WHOLE FAMILY. The
599 // divided-difference closed form of Casale (SIGMETRICS 2017)
600 // is exact here and was briefly tried first on
601 // M>1 && Rc==1 && sum(Z)==0, but the default route does not
602 // serve a single constant: the analyzer differences it at
603 // N-e_r for X and at the AUGMENTED shape for Q, one extra
604 // class holding one job at station i. That shape has Rc+1
605 // classes, which the closed form refuses at any sizeable
606 // population (the outer sum's cancellation), so it kept the
607 // cubature while G(N) turned exact. Mixing the two costs more
608 // than either: on mqn_singleserver_ps the closed-form G(N)
609 // under cubature numerators left sum_i Q_i at 99.500 of
610 // N=100, and the conservation rescale then moved the entire
611 // cubature error into X, 0.5% against the 0.06% the cubature
612 // ratio carries on its own. 'divdiff' stays a NAMED method,
613 // where the caller owns the whole family.
614 if (M > 1 && Nsum3 < 1000) {
615 res.method = "cub";
616 return finish_log(
618 pfqn_cub(L3, N3, Zv, cub_budget_order(), fineTol).lG));
619 }
620 // BLE on the default path: strictly better on lG and it
621 // cancels in G(N-e_r)/G(N). "le" stays the published form.
622 res.method = "ble";
623 // Birman-Kogan Algorithm 2 supplies the MEAN VALUES here.
624 // The caller's fallback differences lG at R+M*R reduced
625 // populations, which on many stations is both dearer and
626 // ~300x less accurate than the load concealment fixed point. Gated
627 // on the station count, since load concealment is mean
628 // field in M: see _kb/06-solver-catalog.md.
629 if (L3.rows() >= 10 && L3.cols() > 1) {
630 bool closed = !Nv.empty();
631 T tot = num_traits<T>::from_int(0);
632 for (std::size_t r = 0; r < Nv.size(); ++r) {
633 if (Nv[r] < num_traits<T>::from_int(0)) closed = false;
634 tot = tot + Nv[r];
635 }
636 if (closed && tot > num_traits<T>::from_int(0)) {
637 BkLcResult<T> thin = pfqn_bklc(L3, Nv, Zv, "mva", 1e-10, 1000);
638 attach_measures(thin.X, thin.Q);
639 res.method = "ble/lc";
640 }
641 }
642 return finish_log(lgz +
643 num_traits<T>::to_double(pfqn_ble(L3, Nv, Zv).lG));
644 case NcMethod::Clw:
645 res.method = "clw";
646 return finish_log(lgz + num_traits<T>::to_double(pfqn_clw(L3, N3, Zv).lG));
647 case NcMethod::Cub:
648 case NcMethod::Gm: {
649 // The reference's exact order for the Z = 0 branch.
650 const int order = static_cast<int>(std::ceil((Nsum3 - 1) / 2.0));
651 res.method = nc_method_name(method);
652 return finish_log(
653 lgz + num_traits<T>::to_double(pfqn_cub(L3, N3, Zv, order, fineTol).lG));
654 }
655 case NcMethod::Kt:
656 res.method = "kt";
657 return finish_log(lgz + num_traits<T>::to_double(pfqn_kt(L3, Nv, Zv).lG));
658 case NcMethod::Bkt:
659 // KT minus the exact Stirling remainder of each Laplaced class
660 res.method = "bkt";
661 return finish_log(lgz + num_traits<T>::to_double(pfqn_bkt(L3, Nv, Zv).lG));
662 case NcMethod::Lekt:
663 // the estimator ble and bkt both compute, on the cheaper side
664 res.method = "lekt";
665 return finish_log(lgz + num_traits<T>::to_double(pfqn_lekt(L3, Nv, Zv).lG));
666 case NcMethod::Bk:
667 res.method = "bk";
668 return finish_log(lgz + num_traits<T>::to_double(pfqn_bk(L3, Nv, Zv).lG));
669 case NcMethod::Bkue: {
670 // The uniform expansion is single chain by construction; the
671 // multichain fallback is the saddle point of the same paper,
672 // which is also how the analyzer reaches this branch, since it
673 // conditions on a station population with an auxiliary class.
674 if (L3.cols() > 1) {
675 res.method = "bkue/bk";
676 return finish_log(lgz + num_traits<T>::to_double(pfqn_bk(L3, Nv, Zv).lG));
677 }
678 std::vector<T> Dv(L3.rows());
679 for (std::size_t i = 0; i < L3.rows(); ++i) Dv[i] = L3(i, 0);
680 res.method = "bkue";
681 return finish_log(
683 pfqn_bkue(Dv, Nv[0], Zv.empty() ? num_traits<T>::from_int(0) : Zv[0]).lG));
684 }
685 case NcMethod::Lc:
686 case NcMethod::LcUe: {
687 // Algorithm 2 returns mean values, not a multichain constant;
688 // the saddle point that seeds it supplies lG on the same
689 // asymptotics. The fixed point converges linearly and slowly,
690 // so it is iterated to the method's own accuracy rather than to
691 // a solver-level reporting tolerance.
693 L3, Nv, Zv, method == NcMethod::LcUe ? "ue" : "mva", 1e-10, 1000);
694 attach_measures(thin.X, thin.Q);
695 res.method = nc_method_name(method);
696 return finish_log(lgz + num_traits<T>::to_double(pfqn_bk(L3, Nv, Zv).lG));
697 }
698 case NcMethod::Le:
699 res.method = "le";
700 return finish_log(lgz + num_traits<T>::to_double(pfqn_le(L3, Nv, Zv).lG));
701 case NcMethod::Ble:
702 // LE plus the empirical eps->0 correction; see _kb/03-api-layer.md
703 res.method = "ble";
704 return finish_log(lgz + num_traits<T>::to_double(pfqn_ble(L3, Nv, Zv).lG));
705 case NcMethod::Aghq:
706 // adaptive Gauss-Hermite over the simplex; q=1 would be "le";
707 // options.config.aghq_nodes overrides the node count.
708 res.method = "aghq";
709 return finish_log(lgz + num_traits<T>::to_double(
710 pfqn_aghq(L3, Nv, Zv,
711 nopt.aghq_nodes < 1 ? 1 : nopt.aghq_nodes).lG));
712 case NcMethod::Ls:
713 res.method = "ls";
714 return finish_log(
715 lgz + num_traits<T>::to_double(pfqn_ls(L3, Nv, Zv, nopt.samples, rng).lG));
716 case NcMethod::Is:
717 res.method = "is";
718 return finish_log(lgz + pfqn_is(L3, N3, Zv, nopt.samples, rng).lG);
719 case NcMethod::Mci:
720 res.method = "mci";
721 return finish_log(
722 lgz + pfqn_mci(L3, N3, Zv, nopt.samples, MciVariant::Mci, rng).lG);
723 case NcMethod::Imci:
724 res.method = "imci";
725 return finish_log(
726 lgz + pfqn_mci(L3, N3, Zv, nopt.samples, MciVariant::Imci, rng).lG);
727 case NcMethod::Mcmc: {
728 // Chen-O'Cinneide REGULARIZATION (TOMACS 8(3), 1998). The chain is
729 // simulated on the regularized network, which shares the steady-state
730 // distribution of the original one, so X and Q come back through the
731 // X/Q channel like 'lc'. What it does NOT return is the constant
732 // itself: the algorithm estimates the RATIOS G(N-e_r)/G(N), never G,
733 // so the lG below is the BLE expansion and is not part of the paper.
734 // It cancels out of every mean value the analyzer reports; only
735 // getProbNormConstAggr reads it.
736 const McmcResult<T> mc = pfqn_mcmc(L3, N3, Zv, std::vector<double>(),
737 nopt.samples, nopt.mcmc_batches,
738 nopt.mcmc_burnin, rng);
739 attach_measures(mc.X, mc.Q);
740 res.method = "mcmc";
741 if (L3.rows() > 1)
742 return finish_log(lgz + num_traits<T>::to_double(pfqn_ble(L3, Nv, Zv).lG));
743 return finish_log(lgz + pfqn_comomrm(L3, N3, Z3, 1).lG);
744 }
746 // The reference picks by shape: one station is a repairman
747 // integral, more stations than classes favour the Monte
748 // Carlo integral, and otherwise the logistic sampler.
749 if (M == 1) {
750 res.method = "sampling";
751 return finish_log(
752 lgz + pfqn_mmsample2(L3, N3, Zv, nopt.samples, rng).lG);
753 }
754 if (M > Rc) {
755 res.method = "imci";
756 return finish_log(
757 lgz + pfqn_mci(L3, N3, Zv, nopt.samples, MciVariant::Imci, rng).lG);
758 }
759 res.method = "ls";
760 return finish_log(
761 lgz + num_traits<T>::to_double(pfqn_ls(L3, Nv, Zv, nopt.samples, rng).lG));
762 case NcMethod::Mmint2:
763 case NcMethod::Gleint: {
764 if (M > 1) {
765 // The reference warns and returns lG = []; see `valid`.
766 res.method = nc_method_name(method);
767 res.valid = false;
768 res.lG = 0.0;
769 res.G = zero;
770 return res;
771 }
772 std::vector<T> Lrow(Rc, zero);
773 for (std::size_t a = 0; a < Rc; ++a) Lrow[a] = L3(0, a);
774 res.method = nc_method_name(method);
775 return finish_log(
777 pfqn_mmint2_gausslegendre(Lrow, Nv, Zv).lG));
778 }
779 case NcMethod::Pana: {
780 const PanaceaResult<T> pa = pfqn_panacea(L3, N3, Zv);
781 res.method = "pana";
782 if (!pa.normalUsage) {
783 // The reference warns and returns lG = []; see the
784 // `valid` field. Deliberate.
785 res.valid = false;
786 res.lG = 0.0;
787 res.G = zero;
788 return res;
789 }
790 return finish_log(lgz + num_traits<T>::to_double(pa.lG));
791 }
793 res.method = "propfair";
794 return finish_log(lgz +
796 case NcMethod::Divdiff: {
797 // Divided-difference closed form, Eqs. (15) and (16).
798 // Load-independent single-server queues only: a think time
799 // needs the integral form of Corollary 3.4, which is not
800 // implemented. Unlike the default route this one keeps
801 // whatever the expression returns, since a caller that named
802 // the method has no fallback.
803 if (Z3tot > zero)
804 throw InputError(
805 "pfqn_nc: the 'divdiff' method requires a model without think time, "
806 "which needs the integral form of Corollary 3.4. Use 'ca' or "
807 "'default'");
808 const ExplicitResult<T> ex = pfqn_explicit(L3, N3);
809 res.method = "divdiff/" + ex.method;
810 return finish_log(lgz + num_traits<T>::to_double(ex.lG));
811 }
812 case NcMethod::Rgf: {
813 if (Rc == 1) {
814 std::vector<T> Lcol(M, zero);
815 for (std::size_t i = 0; i < M; ++i) Lcol[i] = L3(i, 0);
816 res.method = "rgf";
817 return finish_log(
818 lgz + num_traits<T>::to_double(pfqn_rgf(Lcol, N3[0], Zv[0]).lG));
819 }
820 // Multiclass: the residue recursion, with think times carried
821 // by the Bertozzi-McKenna truncation neither RGF paper has.
822 // That sum is ALTERNATING, so pfqn_rgfmc refuses rather than
823 // return a wrong lG; the exact convolution answers those and
824 // the reported method says so.
825 try {
826 const T lg = pfqn_rgfmc<T>(L3, N3, Zv).lG;
827 res.method = "rgf";
828 return finish_log(lgz + num_traits<T>::to_double(lg));
829 } catch (const InputError&) {
830 Matrix<T> Zm(1, Rc);
831 for (std::size_t r = 0; r < Rc; ++r) Zm(0, r) = Zv[r];
832 res.method = "rgf/ca";
833 return finish_log(
834 lgz + num_traits<T>::to_double(pfqn_ca<T>(L3, N3, Zm).lG));
835 }
836 }
837 default:
838 break;
839 }
840 }
841 }
842
843 // ---- dispatch -------------------------------------------------------------
844 T gcore = one;
845 switch (method) {
846 case NcMethod::Ca:
847 gcore = pfqn_ca(L3, N3, Z3).G;
848 res.method = "ca";
849 break;
850 case NcMethod::Rgf:
851 // Multiclass only: the single-class recursion returned above. The
852 // aggregate marginals reach this branch too, since the analyzer
853 // augments a single-class model with an auxiliary class to
854 // condition on a station population.
855 gcore = pfqn_ca(L3, N3, Z3).G;
856 res.method = "rgf/ca";
857 break;
858 case NcMethod::Ger:
859 // Residue closed form of the same generating function 'clw' inverts
860 // numerically. It sits in the EXACT switch, not among the estimators,
861 // because every operation is a product, a quotient or a binomial
862 // coefficient: in the exact backend it agrees with pfqn_ca bit for bit.
863 // A class eliminated by residues enters only as a pole ORDER, so its
864 // population is free; the term count grows as C(S+M-1,M-1) per further
865 // elimination instead, and the maxterms cap REFUSES rather than
866 // truncating.
867 gcore = pfqn_gerasimov(L3, N3, Z3).G;
868 res.method = "ger";
869 break;
870 case NcMethod::Mva: {
871 // MVA by-product means rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
872 const MvaResult<T> mva = pfqn_mva(L3, N3, Z3);
873 gcore = mva.G;
874 res.X.assign(R, zero);
875 res.Q = Matrix<T>(M0, R, zero);
876 for (std::size_t a = 0; a < Rc; ++a) {
877 const std::size_t r = nnz[nzdem[a]];
878 res.X[r] = mva.XN[a] / scalevec[nzdem[a]];
879 for (std::size_t i = 0; i < M; ++i) res.Q(demSt[i], r) = mva.QN(i, a);
880 }
881 res.method = "mva";
882 have_measures = true;
883 break;
884 }
885 case NcMethod::Recal:
886 if (Z3tot > atol)
887 throw UnsupportedError(ErrorKind{"DelayStation"},
888 "pfqn_nc: RECAL is available only for models with zero think time; this model "
889 "has a delay");
890 gcore = pfqn_recal(L3, N3, Z3).G;
891 res.method = "recal";
892 break;
893 case NcMethod::Exact: {
894 // RECAL routing rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
895 long tot = 0;
896 for (int v : N3) tot += v;
897 if (M >= Rc || tot > 10 || Z3tot > atol) {
898 gcore = pfqn_ca(L3, N3, Z3).G;
899 res.method = "exact/ca";
900 } else {
901 gcore = pfqn_recal(L3, N3, Z3).G;
902 res.method = "exact/recal";
903 }
904 break;
905 }
906 case NcMethod::Comom:
907 // THIS THROW IS THE REFERENCE'S OWN (pfqn_nc.m), though it sits a few
908 // lines from branches that DECLINE by returning an empty lG (see
909 // `valid`). Do not "harmonise" it into a decline: a silently zeroed
910 // result is worse than no result. The reference does both on purpose.
911 if (Rc > 1 && M > 1)
912 throw InputError(
913 "pfqn_nc: the 'comom' method supports a single queueing station, but this "
914 "model has more. Use 'default', 'ca' or 'exact'.");
915 if (Rc > 1) {
916 gcore = pfqn_comomrm(L3, N3, Z3).G;
917 res.method = "comom";
918 } else {
919 gcore = pfqn_ca(L3, N3, Z3).G;
920 res.method = "ca";
921 }
922 break;
925 if (M > 1) {
926 // unported cub/le routing rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
927 pfqn_nc_refuse("default (multi-station branch: cub / le)");
928 } else if (!(Z3tot > atol)) {
929 // REFERENCE DEFECT (single-queue-no-delay lG): see _kb/03-api-layer.md (cpp port notes: pfqn)
930 long tot = 0;
931 for (int v : N3) tot += v;
932 T g = num_factorial<T>(static_cast<unsigned>(tot));
933 for (std::size_t a = 0; a < Rc; ++a)
934 g *= num_pow_int(L3(0, a), static_cast<unsigned>(N3[a])) /
935 num_factorial<T>(static_cast<unsigned>(N3[a]));
936 gcore = g;
937 res.method = "exact";
938 } else {
939 gcore = pfqn_comomrm(L3, N3, Z3).G;
940 res.method = "comom";
941 }
942 break;
943 default:
944 // Every remaining name is an estimator and returned above.
946 }
947
948 return finish(Gzdem * gcore);
949}
950
951/** Overload with the reference's default sample count, seed and tolerance. */
952template <class T>
953NcDispatchResult<T> pfqn_nc(const std::vector<T>& lambda, const Matrix<T>& L,
954 const std::vector<int>& N, const Matrix<T>& Z, NcMethod method,
955 const T& atol) {
956 return pfqn_nc(lambda, L, N, Z, method, atol, NcOptions());
957}
958
959/** Overload with the exact (zero-tolerance) filters and no open classes. */
960template <class T>
961NcDispatchResult<T> pfqn_nc(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
962 NcMethod method) {
963 return pfqn_nc(std::vector<T>(), L, N, Z, method, num_traits<T>::from_int(0), NcOptions());
964}
965
966} // namespace pfqn
967} // namespace line
968
969#endif // LINE_API_PFQN_NC_H
Malformed or inconsistent input (dimensions, negative populations, ...): LINE:input:<kind>.
Definition error.h:103
InputError(const std::string &what)
Definition error.h:105
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
UnsupportedError(const std::string &what)
Definition error.h:119
The exception types the port throws.
Dense matrix and non-owning view.
NcResult< T > pfqn_mci(const Matrix< T > &D, const std::vector< int > &N, const std::vector< T > &Z, std::size_t samples, MciVariant variant, McRng &rng)
Monte Carlo Integration estimate of the normalizing constant of a closed product-form network (Ross,...
Definition pfqn_mci.h:111
NcResult< T > pfqn_recal(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const std::vector< int > &m0)
RECAL (REcursive CALculation) for the exact normalizing constant of a closed product-form network (Co...
Definition pfqn_recal.h:188
BktResult< T > pfqn_bkt(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Knessl-Tier expansion with the Stirling-remainder correction (BKT).
Definition pfqn_bkt.h:78
RgfmcResult< T > pfqn_rgfmc(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const T &tol, std::size_t maxterms, const T &maxcancel)
Multiclass Recursion by Generating Functions (RGF), with think times.
Definition pfqn_rgfmc.h:465
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
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
AghqResult< T > pfqn_aghq(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, std::size_t q)
Definition pfqn_aghq.h:59
NcResult< T > pfqn_mmsample2(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, std::size_t samples, McRng &rng)
Sampled McKenna-Mitra integral form of the normalizing constant of a repairman (single-queue plus del...
const char * nc_method_name(NcMethod m)
Definition pfqn_nc.h:137
PropfairResult< T > pfqn_propfair(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Proportionally fair allocation estimate of the normalizing constant (Schweitzer 1979; Walton,...
LektResult< T > pfqn_lekt(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
The common corrected asymptotic expansion (LE-KT), computed on the cheaper side.
Definition pfqn_lekt.h:87
NcMethod nc_method_of(const std::string &s)
Map a method name to its enum; throws UnsupportedError on an unknown one.
Definition pfqn_nc.h:177
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
LsResult< T > pfqn_ls(const Matrix< T > &L0, const std::vector< T > &N, const std::vector< T > &Z, std::size_t I, McRng &rng)
Logistic-sampling estimate of the normalizing constant of a closed product-form network.
Definition pfqn_ls.h:118
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
constexpr double CUB_MAX_EVALS
GlobalConstants.CubMaxEvals: the integrand-evaluation budget above which pfqn_nc lowers the cubature ...
ComomResult< T > pfqn_comomrm(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, int m)
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
void pfqn_nc_refuse(const std::string &method)
Refuse a method in an arithmetic it has no meaning in.
Definition pfqn_nc.h:233
constexpr double MCMC_DEFAULT_BURNIN
Warm-up fraction discarded before accumulation starts.
Definition pfqn_mcmc.h:139
MmintResult< T > pfqn_mmint2_gausslegendre(const std::vector< T > &L, const std::vector< T > &N, const std::vector< T > &Z, int m, std::size_t nodecap)
Gauss-Legendre form on [0, 1e6] (MATLAB pfqn_mmint2_gausslegendre).
BleResult< T > pfqn_ble(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Logistic expansion estimate of the normalizing constant, bias-corrected.
Definition pfqn_ble.h:59
NcResult< T > pfqn_ca(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z)
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Definition pfqn_ca.h:120
PanaceaResult< T > pfqn_panacea(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, int terms)
PANACEA normal-usage asymptotic expansion of the normalizing constant (Ramakrishnan and Mitra,...
@ Imci
gamma = max(0.01, 1 - U), the MonteQueue 2.0 recommendation
Definition pfqn_mci.h:92
@ Mci
gamma = 1/sqrt(max N) where U > 0.9, else 1 - U
Definition pfqn_mci.h:93
RgfResult< T > pfqn_rgf(const std::vector< T > &L, int N, const T &Z)
Recursion by Generating Functions (RGF) for the normalizing constant of a SINGLE-CLASS closed product...
Definition pfqn_rgf.h:91
ClwResult< T > pfqn_clw(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< long > &m, const ClwOptions &opt)
Choudhury-Leung-Whitt normalization constant by numerical inversion of the generating function (JACM ...
Definition pfqn_clw.h:608
constexpr std::size_t MCMC_DEFAULT_BATCHES
Schmeiser (1982), the batch count used in the tables of the paper.
Definition pfqn_mcmc.h:137
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
NcMethod
The methods this port dispatches, one per compute_norm_const case.
Definition pfqn_nc.h:101
@ Rgf
recursion by generating functions; residues beyond one class
Definition pfqn_nc.h:132
@ Adaptive
the reference groups 'adaptive' with 'default'
Definition pfqn_nc.h:103
@ Gm
the reference's alias of 'cub'
Definition pfqn_nc.h:111
@ Bkt
KT minus the exact Stirling remainder of each Laplaced class (BKT).
Definition pfqn_nc.h:113
@ Bkue
Birman-Kogan uniform (van der Waerden) expansion, single chain.
Definition pfqn_nc.h:116
@ Aghq
adaptive Gauss-Hermite over the simplex; q=1 is Le
Definition pfqn_nc.h:121
@ Lekt
the estimator Ble and Bkt both compute, on the cheaper side
Definition pfqn_nc.h:114
@ Gleint
the reference's alias of 'mmint2'
Definition pfqn_nc.h:129
@ Ger
residue closed form; free in the eliminated class populations
Definition pfqn_nc.h:134
@ Mcmc
Chen-O'Cinneide regularization; supplies X and Q, never a constant.
Definition pfqn_nc.h:126
@ Bk
Birman-Kogan saddle point with bottleneck detection.
Definition pfqn_nc.h:115
@ Ble
LE plus the empirical eps->0 correction.
Definition pfqn_nc.h:120
@ Divdiff
divided-difference closed form; no think time, no load dependence
Definition pfqn_nc.h:133
@ Lc
Birman-Kogan Algorithm 2, single chain subproblems by MVA.
Definition pfqn_nc.h:117
@ LcUe
Algorithm 2 with the uniform expansion as the single chain solver.
Definition pfqn_nc.h:118
McmcResult< T > pfqn_mcmc(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const std::vector< double > &s, std::size_t samples, std::size_t nbatches, double burnin, McRng &rng)
Chen-O'Cinneide REGULARIZATION: a Markov chain Monte Carlo estimator of the class throughputs X(r) = ...
Definition pfqn_mcmc.h:158
KtResult< T > pfqn_kt(const Matrix< T > &L0, const std::vector< T > &N0, const std::vector< T > &Z0)
Knessl-Tier asymptotic expansion of the normalizing constant.
Definition pfqn_kt.h:172
ExplicitResult< T > pfqn_explicit(const Matrix< T > &L, const std::vector< int > &N, double tol=std::numeric_limits< double >::epsilon(), const std::string &method="auto", double maxloss=std::numeric_limits< double >::infinity())
Explicit closed-form normalizing constant of a multiclass closed network.
LeResult< T > pfqn_le(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Logistic expansion estimate of the normalizing constant.
Definition pfqn_le.h:245
double pfqn_cub_evals(int M, int order, double Zsum)
Integrand-evaluation count of pfqn_cub, and the budget pfqn_nc prices it against.
NcResult< T > pfqn_is(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, std::size_t samples, McRng &rng)
Importance-sampling estimate of the normalizing constant of a closed LOAD-INDEPENDENT product-form ne...
Definition pfqn_is.h:50
NcResult< T > pfqn_gerasimov(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, double tol=1e-12, std::size_t maxterms=200000)
Exact normalizing constant of a closed multiclass product-form network by ITERATED RESIDUES of its ra...
CubResult< T > pfqn_cub(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, int order, const T &atol)
Normalizing constant by Grundmann-Moeller cubature over the simplex.
Definition pfqn_cub.h:131
NcDispatchResult< T > pfqn_nc(const std::vector< T > &lambda, const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, NcMethod method, const T &atol, const NcOptions &nopt)
Normalizing constant of a product-form queueing network: the dispatcher.
Definition pfqn_nc.h:275
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:196
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:204
double nck(int n, int k)
Binomial coefficient with a thread-local memo table (mp_pfqn util/nck.c).
Definition population.h:69
Number-type abstraction for the templated API port.
Adaptive Gauss-Hermite quadrature of the simplex factor of the McKenna-Mitra integral.
Birman-Kogan asymptotic evaluation of closed networks with many stations.
Knessl-Tier expansion with the Stirling-remainder correction (BKT).
Logistic expansion with the eps->0 bias correction (BLE).
Convolution algorithm for the exact normalizing constant of a closed product-form network (Buzen 1973...
Choudhury-Leung-Whitt normalization constant by numerical inversion of the generating function (JACM ...
CoMoM (class-oriented method of moments) for the finite repairman model: one queueing station of mult...
Normalizing constant by Grundmann-Moeller cubature over the simplex.
Integrand-evaluation count of pfqn_cub, and the budget pfqn_nc prices it against.
Explicit closed-form normalizing constant of a multiclass closed network.
Gerasimov's residue (closed-form) normalizing constant, generalized to R classes.
Importance-sampling estimate of the normalizing constant of a closed LOAD-INDEPENDENT product-form ne...
Knessl-Tier asymptotic expansion of the normalizing constant.
Logistic expansion (LE) asymptotic approximation of the normalizing constant of a closed product-form...
The common corrected asymptotic expansion (LE-KT), computed on the cheaper side.
Logistic-sampling estimate of the normalizing constant of a closed product-form network.
Monte Carlo Integration estimate of the normalizing constant of a closed product-form network (Ross,...
Chen-O'Cinneide REGULARIZATION: a Markov chain Monte Carlo estimator of the class throughputs X(r) = ...
McKenna-Mitra integral form of the normalizing constant of a repairman model (one queueing station,...
Sampled McKenna-Mitra integral form of the normalizing constant of a repairman (single-queue plus del...
Exact Mean Value Analysis for closed product-form networks (Reiser and Lavenberg 1980).
PANACEA normal-usage asymptotic expansion of the normalizing constant (Ramakrishnan and Mitra,...
Proportionally fair allocation estimate of the normalizing constant (Schweitzer 1979; Walton,...
RECAL (REcursive CALculation) for the exact normalizing constant of a closed product-form network (Co...
Recursion by Generating Functions (RGF) for the normalizing constant of a SINGLE-CLASS closed product...
Multiclass Recursion by Generating Functions (RGF), with think times.
Population-vector enumeration and combinatorics.
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
Return value of pfqn_bklc.
Definition pfqn_bk.h:66
std::vector< T > X
Definition pfqn_bk.h:67
Return value of pfqn_explicit, mirroring [lG, G, method, lossDigits].
std::string method
expression used, "distinct" (Eq. 15) or "repeated" (Eq. 16)
T lG
logarithm of the normalizing constant
Estimates of pfqn_mcmc together with their batch-means intervals.
Definition pfqn_mcmc.h:122
std::vector< T > X
(R) per-class throughput, empty when not produced
Definition pfqn_nc.h:244
std::string method
the algorithm actually used
Definition pfqn_nc.h:246
Matrix< T > Q
(M x R) queue lengths, empty when not produced
Definition pfqn_nc.h:245
bool valid
False when the METHOD DECLINED THE MODEL, which is the reference's lG = []: pana outside normal usage...
Definition pfqn_nc.h:259
T G
normalizing constant, exact when the method is
Definition pfqn_nc.h:242
double lG
its logarithm
Definition pfqn_nc.h:243
The options fields compute_norm_const reads beyond the method itself.
Definition pfqn_nc.h:215
double mcmc_burnin
options.config.mcmc_burnin: warm-up fraction pfqn_mcmc discards
Definition pfqn_nc.h:226
unsigned long seed
SolverOptions('NC').seed.
Definition pfqn_nc.h:217
std::size_t mcmc_batches
options.config.mcmc_batches: batches pfqn_mcmc splits its run into
Definition pfqn_nc.h:224
std::size_t aghq_nodes
options.config.aghq_nodes: nodes per simplex direction of the adaptive Gauss-Hermite rule.
Definition pfqn_nc.h:222
std::size_t samples
SolverOptions('NC').samples.
Definition pfqn_nc.h:216
double tol
handed to pfqn_comomrm
Definition pfqn_nc.h:218
Return value of pfqn_panacea, mirroring [Gn, lGn].
bool normalUsage
false where MATLAB returns NaN (min alpha < 0)