LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_ncld.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_NCLD_H
6#define LINE_API_PFQN_NCLD_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Normalizing constant of a LOAD-DEPENDENT closed network: the dispatcher.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_ncld.m. The model reduction is the
14 * same one pfqn_nc performs -- drop the empty classes, rescale per class, drop
15 * the demand-free stations, peel off the classes confined to the delay -- with
16 * one addition: the rate lattice mu follows the stations through the station
17 * filter, so a dropped station takes its rates with it.
18 *
19 * DELAY FOLDING. pfqn_gld and pfqn_lldsingle take no think-time argument: an
20 * infinite server is an ordinary row whose rate lattice is mu(i,k) = k, for
21 * which the factorials cancel. When the model has a delay this port appends
22 * one such row per think-time row, exactly as the reference does with
23 * `Lz = [L;Z]; muz = [mu; repmat(1:size(mu,2),D,1)]`.
24 *
25 * DISPATCH. The exact ladder, which is the reference's `exact` branch and its
26 * `default` branch whenever the Choudhury-Leung-Whitt gate declines:
27 *
28 * R == 1 -> pfqn_lldsingle
29 * M == 1 with a delay -> pfqn_comomrm_ld
30 * M == 1 without a delay -> pfqn_comomrm_ld with a zero think time
31 * otherwise -> pfqn_gld
32 *
33 * and, beside it, the ladder that answers with a LOGARITHM, mirroring pfqn_nc's
34 * split between an exact `finish` and an estimator `finish_log`:
35 *
36 * clw -> pfqn_clw_lld (generating-function inversion)
37 * is -> pfqn_ld_is (sample-an-ordering importance sampling)
38 * panald -> pfqn_panaceald (Mitra-McKenna asymptotic expansion)
39 * rd -> pfqn_rd (recursive decomposition)
40 * nrp / nrl -> pfqn_nrp / pfqn_nrl (Norlund-Rice probit / logit)
41 * comomld -> pfqn_comomrm_ld, or pfqn_rd where CoMoM-LD does not apply
42 * divdiff -> pfqn_explicit_ld (divided difference over the LLD closed form)
43 *
44 * THE CLW GATE ON `default` IS A COST MODEL, NOT AN ACCURACY ONE. CLW and the
45 * exact convolution compute the same constant, so the gate -- at most 5 classes,
46 * at most 200 jobs, at most 2e7 predicted contour points -- only decides which
47 * is cheaper. It is consulted in transcendental arithmetic only; in an exact
48 * field `default` goes straight to the exact ladder, which changes the SPEED on
49 * a subset of models and never the value.
50 *
51 * Arithmetic: the exact ladder and the whole reduction are EXACT-CAPABLE. The
52 * log-domain ladder is refused by name outside transcendental arithmetic, on
53 * pfqn_nc's grounds: a contour inversion, a Monte Carlo estimate and a
54 * truncated asymptotic series have no meaning in the rational field, and
55 * silently substituting the exact convolution would answer with an algorithm
56 * the caller did not ask for.
57 */
58
59#include <cmath>
60#include <cstddef>
61#include <cstdint>
62#include <string>
63#include <vector>
64
79#include "line/num/number.h"
80#include "line/util/error.h"
81#include "line/util/matrix.h"
82
83namespace line {
84namespace pfqn {
85
86/** The load-dependent methods this port dispatches. */
87enum class NcldMethod {
88 Default, Exact, Is, Clw, Panald, Rd, Nrp, Nrl, Nre, Comomld, Divdiff
89};
90
91inline const char* ncld_method_name(NcldMethod m) {
92 switch (m) {
93 case NcldMethod::Default: return "default";
94 case NcldMethod::Exact: return "exact";
95 case NcldMethod::Is: return "is";
96 case NcldMethod::Clw: return "clw";
97 case NcldMethod::Panald: return "panald";
98 case NcldMethod::Rd: return "rd";
99 case NcldMethod::Nrp: return "nrp";
100 case NcldMethod::Nrl: return "nrl";
101 case NcldMethod::Nre: return "nre";
102 case NcldMethod::Comomld: return "comomld";
103 case NcldMethod::Divdiff: return "divdiff";
104 }
105 return "default";
106}
107
108/** Map a method name to its enum; false when the name is not one of them. */
109inline bool ncld_method_try(const std::string& s, NcldMethod& out) {
110 if (s == "default") { out = NcldMethod::Default; return true; }
111 if (s == "exact") { out = NcldMethod::Exact; return true; }
112 if (s == "is") { out = NcldMethod::Is; return true; }
113 if (s == "clw") { out = NcldMethod::Clw; return true; }
114 if (s == "pana" || s == "panald") { out = NcldMethod::Panald; return true; }
115 if (s == "rd") { out = NcldMethod::Rd; return true; }
116 if (s == "nrp") { out = NcldMethod::Nrp; return true; }
117 if (s == "nrl") { out = NcldMethod::Nrl; return true; }
118 if (s == "nre") { out = NcldMethod::Nre; return true; }
119 if (s == "comomld") { out = NcldMethod::Comomld; return true; }
120 if (s == "divdiff") { out = NcldMethod::Divdiff; return true; }
121 return false;
122}
123
124/** Map a method name to its enum; throws UnsupportedError on an unknown one. */
125inline NcldMethod ncld_method_of(const std::string& s) {
127 if (ncld_method_try(s, m)) return m;
128 throw UnsupportedError("pfqn_ncld: unrecognized method for solving load-dependent models: '" +
129 s + "'");
130}
131
132/** Refuse a load-dependent method in an arithmetic it has no meaning in. */
133inline void pfqn_ncld_refuse(const std::string& method) {
134 throw UnsupportedError(
135 "pfqn_ncld: method '" + method +
136 "' inverts a generating function, samples, or truncates an asymptotic series, and needs "
137 "transcendental arithmetic. Use 'default', 'exact' or 'comomld' for an exact constant.");
138}
139
140template <class T>
142 T G; ///< normalizing constant
143 double lG; ///< its logarithm
144 std::string method; ///< the algorithm actually used
145};
146
147/**
148 * @brief Normalizing constant of a LOAD-DEPENDENT closed network: the
149 * dispatcher.
150 *
151 * @param L (M x R) service demands
152 * @param N (R) populations, finite and nonnegative
153 * @param Z (K x R) think times
154 * @param mu (M x >=Nt) load-dependent rate lattice
155 * @param method_name requested algorithm, BY NAME. It is resolved lazily, where
156 * the reference resolves it: `compute_norm_const_ld` is the only place
157 * `pfqn_ncld.m` switches on `options.method`, and the four degenerate
158 * returns below never reach it. Resolving on entry instead refused a name
159 * the reference accepts -- `solver_nc_jointaggr` hands every per-station
160 * factor the caller's own NC method, and that factor is a single station
161 * with no think time, i.e. a closed form no algorithm name selects. It is
162 * what made `-s nc --method ls -a probsysaggr` throw where MATLAB answers.
163 * @param atol threshold below which a demand counts as zero; 0 for exact
164 * @param nopt sample count, seed and tolerance the log-domain ladder reads
165 */
166template <class T>
167NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
168 const Matrix<T>& mu, const std::string& method_name, const T& atol,
169 const NcOptions& nopt) {
170 const std::size_t R0 = N.size();
171 const T zero = num_traits<T>::from_int(0);
172 const T one = num_traits<T>::from_int(1);
173
174 NcldResult<T> res;
175 res.G = one;
176 res.lG = 0.0;
177 // The reference's `method = options.method` at entry: a degenerate return
178 // reports the caller's own word. A resolvable name is normalized ('pana'
179 // reads back 'panald'), which is what the enum overload used to give.
180 {
182 res.method = ncld_method_try(method_name, m0) ? ncld_method_name(m0) : method_name;
183 }
184
185 long Ntot = 0;
186 for (int v : N) {
187 if (v < 0) throw InputError("pfqn_ncld: negative population");
188 Ntot += v;
189 }
190 if (Ntot == 0) return res;
191 const std::size_t M0 = L.empty() ? 0 : L.rows();
192 if (M0 > 0 && L.cols() != R0)
193 throw InputError("pfqn_ncld: L and N disagree on the class count");
194 if (M0 > 0 && mu.rows() != M0)
195 throw InputError("pfqn_ncld: mu and L disagree on the station count");
196 if (M0 > 0 && static_cast<long>(mu.cols()) < Ntot)
197 throw InputError("pfqn_ncld: mu has fewer rate columns than the total population");
198 const std::size_t Nt = static_cast<std::size_t>(Ntot);
199
200 // ---- drop the empty classes ----------------------------------------------
201 std::vector<std::size_t> nnz;
202 for (std::size_t r = 0; r < R0; ++r)
203 if (N[r] > 0) nnz.push_back(r);
204 const std::size_t R1 = nnz.size();
205
206 // ---- rescale each class ---------------------------------------------------
207 std::vector<T> scalevec(R1, one);
208 Matrix<T> L1(M0, R1), Z1(Z.empty() ? 0 : Z.rows(), R1);
209 for (std::size_t k = 0; k < R1; ++k) {
210 const std::size_t r = nnz[k];
211 T mx = zero;
212 for (std::size_t i = 0; i < M0; ++i)
213 if (L(i, r) > mx) mx = L(i, r);
214 for (std::size_t i = 0; i < Z1.rows(); ++i)
215 if (Z(i, r) > mx) mx = Z(i, r);
216 if (mx > zero) scalevec[k] = mx;
217 for (std::size_t i = 0; i < M0; ++i) L1(i, k) = L(i, r) / scalevec[k];
218 for (std::size_t i = 0; i < Z1.rows(); ++i) Z1(i, k) = Z(i, r) / scalevec[k];
219 }
220 T Gscale = one;
221 for (std::size_t k = 0; k < R1; ++k)
222 Gscale *= num_pow_int(scalevec[k], static_cast<unsigned>(N[nnz[k]]));
223
224 // ---- drop the stations with no demand, taking their rates with them -------
225 std::vector<std::size_t> demSt;
226 for (std::size_t i = 0; i < M0; ++i) {
227 T rs = zero;
228 for (std::size_t k = 0; k < R1; ++k) rs += L1(i, k);
229 if (rs > atol) demSt.push_back(i);
230 }
231 const std::size_t M = demSt.size();
232 Matrix<T> L2(M, R1), mu2(M, Nt);
233 for (std::size_t a = 0; a < M; ++a) {
234 for (std::size_t k = 0; k < R1; ++k) L2(a, k) = L1(demSt[a], k);
235 for (std::size_t k = 0; k < Nt; ++k) mu2(a, k) = mu(demSt[a], k);
236 }
237
238 std::vector<int> N2(R1, 0);
239 for (std::size_t k = 0; k < R1; ++k) N2[k] = N[nnz[k]];
240
241 const auto delayG = [&](const std::vector<std::size_t>& cls) {
242 T g = one;
243 for (std::size_t k : cls) {
244 T zs = zero;
245 for (std::size_t i = 0; i < Z1.rows(); ++i) zs += Z1(i, k);
246 g *= num_pow_int(zs, static_cast<unsigned>(N2[k])) /
247 num_factorial<T>(static_cast<unsigned>(N2[k]));
248 }
249 return g;
250 };
251 const auto finish = [&](const T& gcore) {
252 res.G = Gscale * gcore;
254 return res;
255 };
256 // The log-domain finish, for pfqn_nc's reason: an estimator answers with lG,
257 // and exponentiating it to take its logarithm again loses the answer once lG
258 // passes ~709.
259 const auto finish_log = [&](double lgcore) {
260 res.lG = num_traits<T>::log_as_double(Gscale) + lgcore;
261 res.G = num_traits<T>::from_double(std::exp(res.lG));
262 return res;
263 };
264
265 T Ztot = zero;
266 for (std::size_t i = 0; i < Z1.rows(); ++i)
267 for (std::size_t k = 0; k < R1; ++k) Ztot += Z1(i, k);
268 T Lsum = zero;
269 for (std::size_t a = 0; a < M; ++a)
270 for (std::size_t k = 0; k < R1; ++k) Lsum += L2(a, k);
271
272 if (M == 0 || !(Lsum > atol)) {
273 std::vector<std::size_t> all(R1);
274 for (std::size_t k = 0; k < R1; ++k) all[k] = k;
275 return finish(Ztot > atol ? delayG(all) : one);
276 }
277 if (M == 1 && !(Ztot > atol)) {
278 // Single load-dependent station, no delay:
279 // G = (sum N)! / prod N_r! * prod L_r^{N_r} / prod_{k<=Nt} mu(k).
280 long tot = 0;
281 for (int v : N2) tot += v;
282 T g = num_factorial<T>(static_cast<unsigned>(tot));
283 for (std::size_t k = 0; k < R1; ++k)
284 g *= num_pow_int(L2(0, k), static_cast<unsigned>(N2[k])) /
285 num_factorial<T>(static_cast<unsigned>(N2[k]));
286 for (std::size_t k = 0; k < Nt; ++k) {
287 if (mu2(0, k) == zero) throw NumericError("pfqn_ncld: a load-dependent rate is zero");
288 g /= mu2(0, k);
289 }
290 return finish(g);
291 }
292
293 // ---- classes confined to the delay ----------------------------------------
294 std::vector<std::size_t> zdem, nzdem;
295 for (std::size_t k = 0; k < R1; ++k) {
296 T s = zero;
297 for (std::size_t a = 0; a < M; ++a) s += L2(a, k);
298 (s > atol ? nzdem : zdem).push_back(k);
299 }
300 const T Gzdem = zdem.empty() ? one : delayG(zdem);
301
302 const std::size_t Rc = nzdem.size();
303 Matrix<T> L3(M, Rc), Z3(Z1.rows(), Rc);
304 std::vector<int> N3(Rc, 0);
305 for (std::size_t a = 0; a < Rc; ++a) {
306 for (std::size_t i = 0; i < M; ++i) L3(i, a) = L2(i, nzdem[a]);
307 for (std::size_t i = 0; i < Z1.rows(); ++i) Z3(i, a) = Z1(i, nzdem[a]);
308 N3[a] = N2[nzdem[a]];
309 }
310 T Z3tot = zero;
311 for (std::size_t i = 0; i < Z3.rows(); ++i)
312 for (std::size_t a = 0; a < Rc; ++a) Z3tot += Z3(i, a);
313
314 // ---- the ladder that answers with a logarithm -----------------------------
315 // PAST THE DEGENERATE RETURNS, so this is the reference's
316 // `compute_norm_const_ld` and the first point at which the method name has
317 // to mean something. See the note on `method_name` above.
318 const NcldMethod method = ncld_method_of(method_name);
319 // The reference's cost model for 'default': CLW is preferred over the exact
320 // convolution only where it is cheaper, so the three gates are class count,
321 // total population and predicted contour points. `clw_defaults` supplies the
322 // same inner lattice l_j the cost is predicted from, so the two cannot drift.
323 long Nsum3 = 0;
324 for (int v : N3) Nsum3 += v;
325 bool default_clw = false;
326 // NOT CONSULTED IN AN EXACT FIELD, and that is what keeps `default` working
327 // there: the gate only decides which of two routes to the SAME constant is
328 // cheaper, so an exact backend takes the exact ladder rather than being
329 // refused for asking for a contour inversion it never named. Every caller of
330 // the four-argument overload -- pfqn_ncldmx among them -- arrives on
331 // `default`, so gating this at run time would refuse them under --arith
332 // exact.
333 if constexpr (num_traits<T>::has_transcendental) {
334 if (method == NcldMethod::Default && M > 1 && Rc >= 2 && Rc <= 5 && Nsum3 <= 200) {
335 std::vector<int> lat;
336 std::vector<double> gam;
337 // This cost estimate runs BEFORE any decomposition plan exists, so
338 // it asks for the defaults of the identity ordering. clw_defaults
339 // used to key the special cases on the chain POSITION and now keys
340 // them on `depth`; depth[j] = j+1 is the same thing for an unsplit
341 // ordering, so this reproduces the pre-decomposition defaults
342 // (l = 1, 2, 2, 3, ...) exactly.
343 std::vector<std::size_t> keep(Rc), depth(Rc);
344 for (std::size_t a = 0; a < Rc; ++a) {
345 keep[a] = a;
346 depth[a] = a + 1;
347 }
348 detail::clw_defaults(Rc, keep, depth, ClwOptions(), lat, gam);
349 double cost = 1.0;
350 for (std::size_t a = 0; a < Rc; ++a)
351 cost *= 2.0 * static_cast<double>(lat[a]) * static_cast<double>(N3[a]);
352 default_clw = cost <= 2e7; // ~2s at ~1e7 points/s
353 }
354 }
355 // CoMoM-LD carries only the delay-plus-identical-stations shape; outside it
356 // the reference warns and runs 'rd'. The substitution is visible in
357 // res.method, which is what the reference's warning conveys.
358 const bool comomld_falls_back =
359 method == NcldMethod::Comomld && M > 1 && Z3tot > num_traits<T>::from_double(1e-14);
360 const bool logdomain = default_clw || comomld_falls_back || method == NcldMethod::Is ||
361 method == NcldMethod::Clw || method == NcldMethod::Panald ||
362 method == NcldMethod::Rd || method == NcldMethod::Nrp ||
363 method == NcldMethod::Nrl || method == NcldMethod::Nre ||
364 method == NcldMethod::Divdiff;
365 if (logdomain) {
366 if constexpr (!num_traits<T>::has_transcendental) {
367 // `default` never reaches here: the gate above is compiled out, so
368 // every name arriving is one the caller asked for explicitly. A
369 // `comomld` that fell back is named WITH its fallback, since the
370 // caller's own word is exact-capable and the algorithm it resolved
371 // to is not.
373 comomld_falls_back
374 ? std::string("comomld (which falls back to 'rd' on a multi-station model "
375 "with a delay, CoMoM-LD carrying only the delay-plus-identical-"
376 "stations shape)")
377 : std::string(ncld_method_name(method)));
378 } else {
379 const double lgz = num_traits<T>::log_as_double(Gzdem);
380 // The aggregate think time per class, the reference's sum(Z,1).
381 std::vector<T> Zv(Rc, zero);
382 for (std::size_t i = 0; i < Z3.rows(); ++i)
383 for (std::size_t a = 0; a < Rc; ++a) Zv[a] += Z3(i, a);
384
385 if (default_clw || method == NcldMethod::Clw) {
386 res.method = "clw";
387 return finish_log(
388 lgz + num_traits<T>::to_double(pfqn_clw_lld(L3, N3, Zv, mu2, ClwOptions()).lG));
389 }
390 if (method == NcldMethod::Is) {
391 McRng rng(static_cast<std::uint64_t>(nopt.seed));
392 res.method = "is";
393 return finish_log(lgz + pfqn_ld_is(L3, N3, Zv, mu2, nopt.samples, rng).lG);
394 }
395 if (method == NcldMethod::Panald) {
396 const PanaceaLdResult<T> pa = pfqn_panaceald(L3, N3, Zv, mu2);
397 if (!pa.normalUsage)
398 // The reference raises here rather than returning the NaN,
399 // because normal usage is the DOMAIN of the expansion and
400 // not a numerical failure it could retry out of.
401 throw UnsupportedError(
402 std::string("pfqn_ncld: the 'panald' asymptotic expansion does not "
403 "apply to this model: ") +
404 (pa.reason ? pa.reason : "the expansion declined") +
405 ". Use 'exact', 'clw' or an approximate load-dependent method instead");
406 res.method = "panald";
407 return finish_log(lgz + num_traits<T>::to_double(pa.lG));
408 }
409 if (method == NcldMethod::Nrp) {
410 std::vector<T> Nv(Rc, zero);
411 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
412 res.method = "nrp";
413 return finish_log(lgz + num_traits<T>::to_double(pfqn_nrp(L3, Nv, Zv, mu2)));
414 }
415 if (method == NcldMethod::Nrl) {
416 std::vector<T> Nv(Rc, zero);
417 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
418 res.method = "nrl";
419 return finish_log(lgz + num_traits<T>::to_double(pfqn_nrl(L3, Nv, Zv, mu2)));
420 }
421 if (method == NcldMethod::Nre) {
422 std::vector<T> Nv(Rc, zero);
423 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
424 res.method = "nre";
425 return finish_log(lgz + num_traits<T>::to_double(pfqn_nre(L3, Nv, Zv, mu2)));
426 }
427 if (method == NcldMethod::Divdiff) {
428 // Divided-difference closed form with the limited load-dependent
429 // kernel of Casale-Harrison-Ong (Perform. Eval. 2021), Theorem 1.
430 // A think time would have to enter g_sigma, whose closed form
431 // covers queues only, so it is refused here as pfqn_nc refuses it
432 // in the fixed-rate case. The delay-CONFINED classes already left
433 // in Gzdem, so lgz still applies.
434 if (Z3tot > zero)
435 throw UnsupportedError(
436 "pfqn_ncld: the 'divdiff' method requires a model without think time, "
437 "which needs the integral form of Corollary 3.4. Use 'exact' or "
438 "'default'");
439 const ExplicitResult<T> ex = pfqn_explicit_ld(L3, N3, mu2);
440 // WARNINGS BECOME FLAGS on this side, so the loss is acted on here
441 // rather than printed: the reference warns past 15 digits and hands
442 // the number back, which is only safe because the user sees the
443 // warning. With no such channel, returning a constant double
444 // precision cannot carry would be a silent wrong answer.
445 if (!ex.valid || ex.lossDigits > 15)
446 throw NumericError(
447 "pfqn_ncld: the 'divdiff' closed form was exhausted by cancellation on "
448 "this model (" + std::to_string(ex.lossDigits) +
449 " decimal digits lost). Use 'exact', 'comomld' or 'rd', or merge the "
450 "near-tied scaled demands with a looser tolerance");
451 res.method = "divdiff.ld/" + ex.method;
452 return finish_log(lgz + num_traits<T>::to_double(ex.lG));
453 }
454 // 'rd', and the CoMoM-LD fallback onto it.
455 res.method = "rd";
456 return finish_log(lgz + pfqn_rd(L3, N3, Z3, mu2).lGN);
457 }
458 }
459
460 // An explicit 'comomld' that did NOT fall back goes straight to CoMoM-LD,
461 // whatever the station count: the reference calls it unconditionally on this
462 // side of the gate, where the exact ladder below would prefer pfqn_gld.
463 if (method == NcldMethod::Comomld) {
464 res.method = "comomld";
465 return finish(Gzdem * pfqn_comomrm_ld(L3, N3, Z3, mu2).G);
466 }
467
468 // ---- fold the delay rows into the demand matrix ----------------------------
469 const std::size_t D = Z3tot > atol ? Z3.rows() : 0;
470 Matrix<T> Lz(M + D, Rc), muz(M + D, Nt);
471 for (std::size_t i = 0; i < M; ++i) {
472 for (std::size_t a = 0; a < Rc; ++a) Lz(i, a) = L3(i, a);
473 for (std::size_t k = 0; k < Nt; ++k) muz(i, k) = mu2(i, k);
474 }
475 for (std::size_t d = 0; d < D; ++d) {
476 for (std::size_t a = 0; a < Rc; ++a) Lz(M + d, a) = Z3(d, a);
477 for (std::size_t k = 0; k < Nt; ++k)
478 muz(M + d, k) = num_traits<T>::from_int(static_cast<long>(k) + 1);
479 }
480
481 T gcore = one;
482 if (Rc == 1) {
483 int n1 = N3[0];
484 gcore = pfqn_lldsingle(Lz, n1, muz).G;
485 res.method = "exact/gld";
486 } else if (M == 1 && Z3tot > atol) {
487 gcore = pfqn_comomrm_ld(L3, N3, Z3, mu2).G;
488 res.method = "exact/comomld";
489 } else if (M == 1) {
490 gcore = pfqn_comomrm_ld(L3, N3, Matrix<T>(), mu2).G;
491 res.method = "exact/comomld";
492 } else {
493 gcore = pfqn_gld(Lz, N3, muz).G;
494 res.method = "exact/gld";
495 }
496
497 return finish(Gzdem * gcore);
498}
499
500/** Overload naming the algorithm by its enum, for a caller that already has one. */
501template <class T>
502NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
503 const Matrix<T>& mu, NcldMethod method, const T& atol,
504 const NcOptions& nopt) {
505 return pfqn_ncld(L, N, Z, mu, std::string(ncld_method_name(method)), atol, nopt);
506}
507
508/** Overload with the reference's default sample count, seed and tolerance. */
509template <class T>
510NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
511 const Matrix<T>& mu, const std::string& method_name, const T& atol) {
512 return pfqn_ncld(L, N, Z, mu, method_name, atol, NcOptions());
513}
514
515/** Overload with the reference's default sample count, seed and tolerance. */
516template <class T>
517NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
518 const Matrix<T>& mu, NcldMethod method, const T& atol) {
519 return pfqn_ncld(L, N, Z, mu, std::string(ncld_method_name(method)), atol, NcOptions());
520}
521
522/** Overload with the exact (zero-tolerance) filters. */
523template <class T>
524NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
525 const Matrix<T>& mu) {
526 return pfqn_ncld(L, N, Z, mu, std::string("default"), num_traits<T>::from_int(0), NcOptions());
527}
528
529} // namespace pfqn
530} // namespace line
531
532#endif // LINE_API_PFQN_NCLD_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
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
The exception types the port throws.
Dense matrix and non-owning view.
void pfqn_ncld_refuse(const std::string &method)
Refuse a load-dependent method in an arithmetic it has no meaning in.
Definition pfqn_ncld.h:133
RdResult< T > pfqn_rd(const Matrix< T > &L0, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu0, double tol, NcMethod method)
Reduction heuristic (RD) for the normalizing constant of a closed LOAD-DEPENDENT product-form network...
Definition pfqn_rd.h:101
NcResult< T > pfqn_ld_is(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu, std::size_t samples, McRng &rng)
Importance-sampling estimate of the normalizing constant of a closed LOAD-DEPENDENT product-form netw...
Definition pfqn_ld_is.h:81
PanaceaLdResult< T > pfqn_panaceald(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu, int terms)
PANACEA normal-usage asymptotic expansion for LOAD-DEPENDENT closed networks (Mitra and McKenna,...
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
NcldMethod
The load-dependent methods this port dispatches.
Definition pfqn_ncld.h:87
NcResult< T > pfqn_lldsingle(const Matrix< T > &L, int N, const Matrix< T > &mu)
Exact normalizing constant of a SINGLE-CLASS closed network whose stations are LIMITED load dependent...
NcldResult< T > pfqn_ncld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu, const std::string &method_name, const T &atol, const NcOptions &nopt)
Normalizing constant of a LOAD-DEPENDENT closed network: the dispatcher.
Definition pfqn_ncld.h:167
T pfqn_nre(const Matrix< T > &L0, const std::vector< T > &N, const std::vector< T > &Z, const Matrix< T > &alpha0)
Saddle-tilted Edgeworth approximation of log G for a limited load-dependent model.
Definition pfqn_nre.h:466
const char * ncld_method_name(NcldMethod m)
Definition pfqn_ncld.h:91
NcldMethod ncld_method_of(const std::string &s)
Map a method name to its enum; throws UnsupportedError on an unknown one.
Definition pfqn_ncld.h:125
bool ncld_method_try(const std::string &s, NcldMethod &out)
Map a method name to its enum; false when the name is not one of them.
Definition pfqn_ncld.h:109
T pfqn_nrp(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const Matrix< T > &alpha)
Norlund-Rice probit approximation of log G.
Definition pfqn_nrl.h:140
@ Divdiff
divided-difference closed form; no think time, no load dependence
Definition pfqn_nc.h:133
T pfqn_nrl(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const Matrix< T > &alpha)
Norlund-Rice logit approximation of log G.
Definition pfqn_nrl.h:130
NcResult< T > pfqn_gld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &mu)
Exact normalizing constant of a closed product-form network whose stations may be load dependent (gen...
Definition pfqn_gld.h:150
ComomRmResult< T > pfqn_comomrm_ld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &Z, const Matrix< T > &mu)
CoMoM for the repairman model with an arbitrary LOAD-DEPENDENT rate lattice at the single queueing st...
ClwResult< T > pfqn_clw_lld(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, const Matrix< T > &mu, const ClwOptions &opt)
Limited load-dependent form (matlab pfqn_clw_lld.m).
Definition pfqn_clw.h:807
ExplicitResult< T > pfqn_explicit_ld(const Matrix< T > &L, const std::vector< int > &N, const Matrix< T > &mu, 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 LIMITED LOAD-DEPENDENT network.
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:210
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:218
Number-type abstraction for the templated API port.
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 for the repairman model with an arbitrary LOAD-DEPENDENT rate lattice at the single queueing st...
Explicit closed-form normalizing constant of a multiclass LIMITED LOAD-DEPENDENT network.
Exact normalizing constant of a closed product-form network whose stations may be load dependent (gen...
Exact normalizing constant of a SINGLE-CLASS closed network whose stations are load dependent.
Importance-sampling estimate of the normalizing constant of a closed LOAD-DEPENDENT product-form netw...
Exact normalizing constant of a SINGLE-CLASS closed network whose stations are LIMITED load dependent...
Randomness scaffolding shared by the Monte Carlo normalizing-constant estimators (pfqn_mci,...
Normalizing constant of a product-form queueing network: the dispatcher.
Norlund-Rice inversion of the normalizing constant on a SADDLE-TILTED contour, with a second-order Ed...
Norlund-Rice inversion of the normalizing constant, in its logistic (NRL) and probit (NRP) substituti...
PANACEA normal-usage asymptotic expansion for LOAD-DEPENDENT closed networks (Mitra and McKenna,...
Reduction heuristic (RD) for the normalizing constant of a closed LOAD-DEPENDENT product-form network...
Optional lattice and aliasing parameters; empty means "use the CLW defaults".
Definition pfqn_clw.h:104
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
double lossDigits
decimal digits lost to cancellation
bool valid
False when a caller's cancellation budget was exceeded: lG and G are then meaningless and the caller ...
The options fields compute_norm_const reads beyond the method itself.
Definition pfqn_nc.h:215
unsigned long seed
SolverOptions('NC').seed.
Definition pfqn_nc.h:217
std::size_t samples
SolverOptions('NC').samples.
Definition pfqn_nc.h:216
T G
normalizing constant
Definition pfqn_ncld.h:142
std::string method
the algorithm actually used
Definition pfqn_ncld.h:144
double lG
its logarithm
Definition pfqn_ncld.h:143
Return value of pfqn_panaceald, mirroring [Gn, lGn] plus why it declined.
bool normalUsage
false wherever the reference returns NaN
const char * reason
which condition declined; nullptr when it applies