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(ErrorKind{"Method"}, "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(ErrorKind{"Arithmetic"},
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').
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(ErrorKind{"Degenerate"}, "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 keys
339 // its special cases on `depth`, and depth[j] = j+1 on an unsplit
340 // ordering gives l = 1, 2, 2, 3, ...
341 std::vector<std::size_t> keep(Rc), depth(Rc);
342 for (std::size_t a = 0; a < Rc; ++a) {
343 keep[a] = a;
344 depth[a] = a + 1;
345 }
346 detail::clw_defaults(Rc, keep, depth, ClwOptions(), lat, gam);
347 double cost = 1.0;
348 for (std::size_t a = 0; a < Rc; ++a)
349 cost *= 2.0 * static_cast<double>(lat[a]) * static_cast<double>(N3[a]);
350 default_clw = cost <= 2e7; // ~2s at ~1e7 points/s
351 }
352 }
353 // CoMoM-LD carries only the delay-plus-identical-stations shape; outside it
354 // the reference warns and runs 'rd'. The substitution is visible in
355 // res.method, which is what the reference's warning conveys.
356 const bool comomld_falls_back =
357 method == NcldMethod::Comomld && M > 1 && Z3tot > num_traits<T>::from_double(1e-14);
358 const bool logdomain = default_clw || comomld_falls_back || method == NcldMethod::Is ||
359 method == NcldMethod::Clw || method == NcldMethod::Panald ||
360 method == NcldMethod::Rd || method == NcldMethod::Nrp ||
361 method == NcldMethod::Nrl || method == NcldMethod::Nre ||
362 method == NcldMethod::Divdiff;
363 if (logdomain) {
364 if constexpr (!num_traits<T>::has_transcendental) {
365 // `default` never reaches here: the gate above is compiled out, so
366 // every name arriving is one the caller asked for explicitly. A
367 // `comomld` that fell back is named WITH its fallback, since the
368 // caller's own word is exact-capable and the algorithm it resolved
369 // to is not.
371 comomld_falls_back
372 ? std::string("comomld (which falls back to 'rd' on a multi-station model "
373 "with a delay, CoMoM-LD carrying only the delay-plus-identical-"
374 "stations shape)")
375 : std::string(ncld_method_name(method)));
376 } else {
377 const double lgz = num_traits<T>::log_as_double(Gzdem);
378 // The aggregate think time per class, the reference's sum(Z,1).
379 std::vector<T> Zv(Rc, zero);
380 for (std::size_t i = 0; i < Z3.rows(); ++i)
381 for (std::size_t a = 0; a < Rc; ++a) Zv[a] += Z3(i, a);
382
383 if (default_clw || method == NcldMethod::Clw) {
384 res.method = "clw";
385 return finish_log(
386 lgz + num_traits<T>::to_double(pfqn_clw_lld(L3, N3, Zv, mu2, ClwOptions()).lG));
387 }
388 if (method == NcldMethod::Is) {
389 McRng rng(static_cast<std::uint64_t>(nopt.seed));
390 res.method = "is";
391 return finish_log(lgz + pfqn_ld_is(L3, N3, Zv, mu2, nopt.samples, rng).lG);
392 }
393 if (method == NcldMethod::Panald) {
394 const PanaceaLdResult<T> pa = pfqn_panaceald(L3, N3, Zv, mu2);
395 if (!pa.normalUsage)
396 // The reference raises here rather than returning the NaN,
397 // because normal usage is the DOMAIN of the expansion and
398 // not a numerical failure it could retry out of.
399 throw UnsupportedError(ErrorKind{"LoadDependence"},
400 std::string("pfqn_ncld: the 'panald' asymptotic expansion does not "
401 "apply to this model: ") +
402 (pa.reason ? pa.reason : "the expansion declined") +
403 ". Use 'exact', 'clw' or an approximate load-dependent method instead");
404 res.method = "panald";
405 return finish_log(lgz + num_traits<T>::to_double(pa.lG));
406 }
407 if (method == NcldMethod::Nrp) {
408 std::vector<T> Nv(Rc, zero);
409 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
410 res.method = "nrp";
411 return finish_log(lgz + num_traits<T>::to_double(pfqn_nrp(L3, Nv, Zv, mu2)));
412 }
413 if (method == NcldMethod::Nrl) {
414 std::vector<T> Nv(Rc, zero);
415 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
416 res.method = "nrl";
417 return finish_log(lgz + num_traits<T>::to_double(pfqn_nrl(L3, Nv, Zv, mu2)));
418 }
419 if (method == NcldMethod::Nre) {
420 std::vector<T> Nv(Rc, zero);
421 for (std::size_t a = 0; a < Rc; ++a) Nv[a] = num_traits<T>::from_int(N3[a]);
422 res.method = "nre";
423 return finish_log(lgz + num_traits<T>::to_double(pfqn_nre(L3, Nv, Zv, mu2)));
424 }
425 if (method == NcldMethod::Divdiff) {
426 // Divided-difference closed form with the limited load-dependent
427 // kernel of Casale-Harrison-Ong (Perform. Eval. 2021), Theorem 1.
428 // A think time would have to enter g_sigma, whose closed form
429 // covers queues only, so it is refused here as pfqn_nc refuses it
430 // in the fixed-rate case. The delay-CONFINED classes already left
431 // in Gzdem, so lgz still applies.
432 if (Z3tot > zero)
433 throw UnsupportedError(ErrorKind{"DelayStation"},
434 "pfqn_ncld: the 'divdiff' method requires a model without think time, "
435 "which needs the integral form of Corollary 3.4. Use 'exact' or "
436 "'default'");
437 const ExplicitResult<T> ex = pfqn_explicit_ld(L3, N3, mu2);
438 // WARNINGS BECOME FLAGS on this side, so the loss is acted on here
439 // rather than printed: the reference warns past 15 digits and hands
440 // the number back, which is only safe because the user sees the
441 // warning. With no such channel, returning a constant double
442 // precision cannot carry would be a silent wrong answer.
443 if (!ex.valid || ex.lossDigits > 15)
444 throw NumericError(ErrorKind{"PrecisionLoss"},
445 "pfqn_ncld: the 'divdiff' closed form was exhausted by cancellation on "
446 "this model (" + std::to_string(ex.lossDigits) +
447 " decimal digits lost). Use 'exact', 'comomld' or 'rd', or merge the "
448 "near-tied scaled demands with a looser tolerance");
449 res.method = "divdiff.ld/" + ex.method;
450 return finish_log(lgz + num_traits<T>::to_double(ex.lG));
451 }
452 // 'rd', and the CoMoM-LD fallback onto it.
453 res.method = "rd";
454 return finish_log(lgz + pfqn_rd(L3, N3, Z3, mu2).lGN);
455 }
456 }
457
458 // An explicit 'comomld' that did NOT fall back goes straight to CoMoM-LD,
459 // whatever the station count: the reference calls it unconditionally on this
460 // side of the gate, where the exact ladder below would prefer pfqn_gld.
461 if (method == NcldMethod::Comomld) {
462 res.method = "comomld";
463 return finish(Gzdem * pfqn_comomrm_ld(L3, N3, Z3, mu2).G);
464 }
465
466 // ---- fold the delay rows into the demand matrix ----------------------------
467 const std::size_t D = Z3tot > atol ? Z3.rows() : 0;
468 Matrix<T> Lz(M + D, Rc), muz(M + D, Nt);
469 for (std::size_t i = 0; i < M; ++i) {
470 for (std::size_t a = 0; a < Rc; ++a) Lz(i, a) = L3(i, a);
471 for (std::size_t k = 0; k < Nt; ++k) muz(i, k) = mu2(i, k);
472 }
473 for (std::size_t d = 0; d < D; ++d) {
474 for (std::size_t a = 0; a < Rc; ++a) Lz(M + d, a) = Z3(d, a);
475 for (std::size_t k = 0; k < Nt; ++k)
476 muz(M + d, k) = num_traits<T>::from_int(static_cast<long>(k) + 1);
477 }
478
479 T gcore = one;
480 if (Rc == 1) {
481 int n1 = N3[0];
482 gcore = pfqn_lldsingle(Lz, n1, muz).G;
483 res.method = "exact/gld";
484 } else if (M == 1 && Z3tot > atol) {
485 gcore = pfqn_comomrm_ld(L3, N3, Z3, mu2).G;
486 res.method = "exact/comomld";
487 } else if (M == 1) {
488 gcore = pfqn_comomrm_ld(L3, N3, Matrix<T>(), mu2).G;
489 res.method = "exact/comomld";
490 } else {
491 gcore = pfqn_gld(Lz, N3, muz).G;
492 res.method = "exact/gld";
493 }
494
495 return finish(Gzdem * gcore);
496}
497
498/** Overload naming the algorithm by its enum, for a caller that already has one. */
499template <class T>
500NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
501 const Matrix<T>& mu, NcldMethod method, const T& atol,
502 const NcOptions& nopt) {
503 return pfqn_ncld(L, N, Z, mu, std::string(ncld_method_name(method)), atol, nopt);
504}
505
506/** Overload with the reference's default sample count, seed and tolerance. */
507template <class T>
508NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
509 const Matrix<T>& mu, const std::string& method_name, const T& atol) {
510 return pfqn_ncld(L, N, Z, mu, method_name, atol, NcOptions());
511}
512
513/** Overload with the reference's default sample count, seed and tolerance. */
514template <class T>
515NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
516 const Matrix<T>& mu, NcldMethod method, const T& atol) {
517 return pfqn_ncld(L, N, Z, mu, std::string(ncld_method_name(method)), atol, NcOptions());
518}
519
520/** Overload with the exact (zero-tolerance) filters. */
521template <class T>
522NcldResult<T> pfqn_ncld(const Matrix<T>& L, const std::vector<int>& N, const Matrix<T>& Z,
523 const Matrix<T>& mu) {
524 return pfqn_ncld(L, N, Z, mu, std::string("default"), num_traits<T>::from_int(0), NcOptions());
525}
526
527} // namespace pfqn
528} // namespace line
529
530#endif // LINE_API_PFQN_NCLD_H
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
NumericError(const std::string &what)
Definition error.h:112
UnsupportedError(const std::string &what)
Definition error.h:119
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:144
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:196
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition number.h:204
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...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
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].
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