LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_rgfmc.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_API_PFQN_PFQN_RGFMC_H
6#define LINE_API_PFQN_PFQN_RGFMC_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Multiclass Recursion by Generating Functions (RGF), with think times.
12 *
13 * Templated port of matlab/src/api/pfqn/pfqn_rgfmc.m: the normalizing constant
14 * of a closed MULTICLASS product-form network by eliminating one class at a
15 * time by residues, finishing in the single-class convolution of pfqn_rgf.
16 *
17 * P. G. Harrison, S. Coury, "On the asymptotic behaviour of closed multiclass
18 * queueing networks", Perf. Eval. 47:131-138, 2002, Thm 1, expresses the
19 * generating function of a q-class network through those of (q-1)-class ones;
20 * P. G. Harrison, T. T. Lee, "A new recursive algorithm for computing
21 * generating functions in closed multi-class queueing networks", IEEE MASCOTS
22 * 2004, eqs. (4)-(5), turns it into the RGF algorithm, bottoming out in
23 * single-class constants memoised by load vector (Sec. 3.4).
24 *
25 * THINK TIMES ARE NOT IN EITHER PAPER. Both write the generating function as
26 * the RATIONAL prod_i (1 - rho_i z)^-m_i, every node a load-independent single
27 * server. An infinite server multiplies it by the ENTIRE exp(sum_r Z_r z_r),
28 * which breaks the step Thm 1 rests on: G_n(z') = -sum_i r_i holds only because
29 * the residues of n(z)/d(z) sum to zero when deg d >= deg n + 2 (Bertozzi and
30 * McKenna, SIAM Review 35(2):239-268, 1993, fact (IV), p. 246), and an
31 * exponential numerator does not decay at infinity. The delay is carried by
32 * their own repair, eqs. (3.19)-(3.21): only the first k_r+1 Taylor
33 * coefficients of exp(Z_r z_r) can reach the coefficient of z_r^k_r, so
34 * replacing the exponential by that polynomial is EXACT and leaves a rational
35 * integrand. The price is that the eliminated class's population re-enters the
36 * term count, which is precisely the population-insensitivity Harrison-Lee
37 * Sec. 4 advertises; the class kept for the base case pays nothing.
38 *
39 * DEGENERACY. Thm 1 assumes rho_iq != rho_lq and its Conclusion leaves the tied
40 * case open. Two affine forms name the SAME pole only when PROPORTIONAL, so
41 * fusing proportional forms into one factor of summed multiplicity disposes of
42 * it; a tie in the eliminated class alone leaves a form with no constant term,
43 * which the recursion carries unchanged.
44 *
45 * ARITHMETIC. The elimination is exact in exact arithmetic but is an
46 * ALTERNATING sum over residues, so near-coincident loads over an eliminated
47 * class destroy significance; the worst cancellation ratio is tracked and the
48 * routine REFUSES past maxcancel rather than returning a confidently wrong lG.
49 * Coefficients are carried in the log domain with a separate sign, so no
50 * Poisson weight or binomial is ever formed as a naive ratio; that is why the
51 * routine is gated on num_traits<T>::has_transcendental, as pfqn_rgf is.
52 */
53
54#include <algorithm>
55#include <cmath>
56#include <cstddef>
57#include <limits>
58#include <map>
59#include <sstream>
60#include <string>
61#include <vector>
62
65#include "line/num/number.h"
66#include "line/util/error.h"
67#include "line/util/matrix.h"
68
69namespace line {
70namespace pfqn {
71
72/** Return value of pfqn_rgfmc, mirroring [G, lG]. */
73template <class T>
75 T G; ///< normalizing constant
76 T lG; ///< its logarithm
77};
78
79namespace detail {
80
81/** coef * prod_t ( F[t][0] + sum_{p>=1} F[t][p] z_p ) ^ (-m[t]). */
82template <class T>
83struct RgfmcTerm {
84 T lc; ///< log|coefficient|
85 int sc; ///< sign of the coefficient
86 std::vector<std::vector<T> > F; ///< affine forms, one row per factor
87 std::vector<int> m; ///< pole orders
88};
89
90/** Signed log-domain sum; writes the worst cancellation ratio into cond. */
91template <class T>
92void rgfmc_slogsum(const std::vector<T>& lv, const std::vector<int>& sv, std::size_t n,
93 T& ls, int& sg, T& cond) {
94 using std::exp;
95 using std::log;
96 const T ninf = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
97 bool any = false;
98 T mx = ninf;
99 for (std::size_t i = 0; i < n; ++i) {
100 if (sv[i] != 0 && lv[i] > ninf && (!any || lv[i] > mx)) {
101 mx = lv[i];
102 any = true;
103 }
104 }
105 if (!any) {
106 ls = ninf;
107 sg = 0;
108 return;
109 }
110 T tot = num_traits<T>::from_int(0);
111 T abs = num_traits<T>::from_int(0);
112 std::size_t cnt = 0;
113 for (std::size_t i = 0; i < n; ++i) {
114 if (sv[i] == 0 || !(lv[i] > ninf)) continue;
115 const T e = exp(T(lv[i] - mx));
116 tot = T(tot + num_traits<T>::from_int(sv[i]) * e);
117 abs = T(abs + e);
118 ++cnt;
119 }
120 if (tot == num_traits<T>::from_int(0)) {
121 ls = ninf;
122 sg = 0;
123 return;
124 }
125 const T at = (tot < num_traits<T>::from_int(0)) ? T(-tot) : tot;
126 ls = T(mx + log(at));
127 sg = (tot > num_traits<T>::from_int(0)) ? 1 : -1;
128 if (cnt > 1) {
129 const T c = T(mx + log(abs) - ls);
130 if (c > cond) cond = c;
131 }
132}
133
134/** Signed log-domain linear convolution truncated at the common length. */
135template <class T>
136void rgfmc_slogconv(std::vector<T>& lu, std::vector<int>& su, const std::vector<T>& lv,
137 const std::vector<int>& sv, T& cond) {
138 const std::size_t n = lu.size();
139 std::vector<T> lo(n), tl(n);
140 std::vector<int> so(n), ts(n);
141 for (std::size_t k = 0; k < n; ++k) {
142 for (std::size_t j = 0; j <= k; ++j) {
143 tl[j] = T(lu[j] + lv[k - j]);
144 ts[j] = su[j] * sv[k - j];
145 }
146 rgfmc_slogsum(tl, ts, k + 1, lo[k], so[k], cond);
147 }
148 lu = lo;
149 su = so;
150}
151
152/** log C(n,r); never a factorial quotient. */
153template <class T>
154T rgfmc_lbinom(const T& n, const T& r) {
155 return T(num_factln<T>(n) - num_factln<T>(r) - num_factln<T>(T(n - r)));
156}
157
158/** Nonnegative integer rows of length parts summing to total. */
159inline std::vector<std::vector<int> > rgfmc_compositions(int total, int parts) {
160 std::vector<std::vector<int> > out;
161 if (parts == 0) {
162 if (total == 0) out.push_back(std::vector<int>());
163 return out;
164 }
165 if (parts == 1) {
166 out.push_back(std::vector<int>(1, total));
167 return out;
168 }
169 for (int first = 0; first <= total; ++first) {
170 std::vector<std::vector<int> > sub = rgfmc_compositions(total - first, parts - 1);
171 for (std::size_t i = 0; i < sub.size(); ++i) {
172 std::vector<int> row(1, first);
173 row.insert(row.end(), sub[i].begin(), sub[i].end());
174 out.push_back(row);
175 }
176 }
177 return out;
178}
179
180inline int rgfmc_signpow(bool negative, int k) { return (negative && (k % 2) == 1) ? -1 : 1; }
181
182/** Fuse PROPORTIONAL affine forms into one factor of summed multiplicity. */
183template <class T>
184void rgfmc_merge(std::vector<std::vector<T> >& F, std::vector<int>& m, const T& tol,
185 T& lc, int& sc) {
186 using std::log;
187 const T zero = num_traits<T>::from_int(0);
188 const std::size_t Tn = F.size();
189 std::vector<bool> used(Tn, false);
190 std::vector<std::vector<T> > oF;
191 std::vector<int> om;
192 for (std::size_t i = 0; i < Tn; ++i) {
193 if (used[i]) continue;
194 used[i] = true;
195 std::vector<T> Fi = F[i];
196 int mi = m[i];
197 std::size_t pi = 0;
198 for (std::size_t c = 1; c < Fi.size(); ++c) {
199 if (num_abs(Fi[c]) > num_abs(Fi[pi])) pi = c;
200 }
201 if (Fi[pi] == zero) throw InputError("pfqn_rgfmc: met an identically zero factor");
202 for (std::size_t j = i + 1; j < Tn; ++j) {
203 if (used[j]) continue;
204 T nj = zero;
205 for (std::size_t c = 0; c < F[j].size(); ++c) nj = std::max(nj, num_abs(F[j][c]));
206 if (!(nj > zero)) continue;
207 const T r = T(F[j][pi] / Fi[pi]);
208 if (r == zero) continue;
209 bool prop = true;
210 for (std::size_t c = 0; c < F[j].size(); ++c) {
211 if (num_abs(T(F[j][c] - r * Fi[c])) > tol * nj) {
212 prop = false;
213 break;
214 }
215 }
216 if (prop) {
217 lc = T(lc - num_traits<T>::from_int(m[j]) * log(num_abs(r)));
218 sc *= rgfmc_signpow(r < zero, m[j]);
219 mi += m[j];
220 used[j] = true;
221 }
222 }
223 oF.push_back(Fi);
224 om.push_back(mi);
225 }
226 F = oF;
227 m = om;
228}
229
230/** Eliminate the class in column col: Harrison-Coury Thm 1 as a partial fraction. */
231template <class T>
232std::vector<RgfmcTerm<T> > rgfmc_step(const std::vector<RgfmcTerm<T> >& terms, std::size_t col,
233 int kr, const T& Zr, const T& tol, std::size_t maxterms,
234 T& cond) {
235 using std::log;
236 const T zero = num_traits<T>::from_int(0);
237 std::vector<RgfmcTerm<T> > out;
238 for (std::size_t it = 0; it < terms.size(); ++it) {
239 std::vector<std::vector<T> > F = terms[it].F;
240 std::vector<int> m = terms[it].m;
241 T lc = terms[it].lc;
242 int sc = terms[it].sc;
243 rgfmc_merge(F, m, tol, lc, sc);
244 const std::size_t Tn = F.size();
245 std::vector<std::vector<T> > A(Tn, std::vector<T>(col, zero));
246 std::vector<T> B(Tn, zero);
247 std::vector<bool> isMono(Tn, false);
248 int shift = 0;
249 for (std::size_t i = 0; i < Tn; ++i) {
250 T scale = zero;
251 for (std::size_t c = 0; c < F[i].size(); ++c) scale = std::max(scale, num_abs(F[i][c]));
252 if (scale == zero) scale = num_traits<T>::from_int(1);
253 bool mono = true;
254 for (std::size_t c = 0; c < col; ++c) {
255 A[i][c] = F[i][c];
256 if (num_abs(F[i][c]) > tol * scale) mono = false;
257 }
258 B[i] = T(-F[i][col]);
259 isMono[i] = mono;
260 if (mono) {
261 lc = T(lc - num_traits<T>::from_int(m[i]) * log(num_abs(B[i])));
262 sc *= rgfmc_signpow(T(-B[i]) < zero, m[i]);
263 shift += m[i];
264 }
265 }
266 std::vector<std::size_t> S, P;
267 for (std::size_t i = 0; i < Tn; ++i) {
268 if (isMono[i]) continue;
269 if (B[i] != zero) S.push_back(i);
270 else P.push_back(i);
271 }
272 const int Ntot = kr + shift;
273 const int pmax = (Zr > zero) ? Ntot : 0;
274 for (int p = 0; p <= pmax; ++p) {
275 // Bertozzi-McKenna truncation, in logs: naive Z^p/p! overflows.
276 T lcz = lc;
277 if (p > 0) {
278 const T pT = num_traits<T>::from_int(p);
279 lcz = T(lc + pT * log(Zr) - num_factln<T>(pT));
280 }
281 const int n = Ntot - p;
282 if (S.empty()) {
283 if (n == 0) {
284 RgfmcTerm<T> nt;
285 nt.lc = lcz;
286 nt.sc = sc;
287 for (std::size_t a = 0; a < P.size(); ++a) {
288 nt.F.push_back(A[P[a]]);
289 nt.m.push_back(m[P[a]]);
290 }
291 out.push_back(nt);
292 }
293 continue;
294 }
295 for (std::size_t jj = 0; jj < S.size(); ++jj) {
296 const std::size_t j = S[jj];
297 std::vector<std::size_t> oth;
298 for (std::size_t a = 0; a < S.size(); ++a)
299 if (S[a] != j) oth.push_back(S[a]);
300 const std::size_t no = oth.size();
301 const T Bj = B[j];
302 std::vector<std::vector<T> > Cjl(no, std::vector<T>(col, zero));
303 for (std::size_t a = 0; a < no; ++a)
304 for (std::size_t c = 0; c < col; ++c)
305 Cjl[a][c] = T((A[oth[a]][c] * Bj - B[oth[a]] * A[j][c]) / Bj);
306 for (int k = 0; k < m[j]; ++k) {
307 const T nT = num_traits<T>::from_int(n);
308 const T lbase = T(lcz - num_traits<T>::from_int(k) * log(num_abs(Bj)) +
309 rgfmc_lbinom<T>(T(nT + num_traits<T>::from_int(m[j] - k - 1)), nT) +
310 nT * log(num_abs(Bj)));
311 const int sbase = sc * rgfmc_signpow(T(-Bj) < zero, k) * rgfmc_signpow(Bj < zero, n);
312 std::vector<std::vector<int> > comps = rgfmc_compositions(k, static_cast<int>(no));
313 for (std::size_t cc = 0; cc < comps.size(); ++cc) {
314 T lt = lbase;
315 int st = sbase;
316 for (std::size_t a = 0; a < no; ++a) {
317 if (comps[cc][a] > 0) {
318 const T jlT = num_traits<T>::from_int(comps[cc][a]);
319 lt = T(lt + rgfmc_lbinom<T>(T(num_traits<T>::from_int(m[oth[a]]) + jlT -
320 num_traits<T>::from_int(1)), jlT) +
321 jlT * log(num_abs(B[oth[a]])));
322 st *= rgfmc_signpow(B[oth[a]] < zero, comps[cc][a]);
323 }
324 }
325 RgfmcTerm<T> nt;
326 nt.lc = lt;
327 nt.sc = st;
328 nt.F.push_back(A[j]);
329 nt.m.push_back(n + m[j] - k);
330 for (std::size_t a = 0; a < no; ++a) {
331 nt.F.push_back(Cjl[a]);
332 nt.m.push_back(m[oth[a]] + comps[cc][a]);
333 }
334 for (std::size_t a = 0; a < P.size(); ++a) {
335 nt.F.push_back(A[P[a]]);
336 nt.m.push_back(m[P[a]]);
337 }
338 out.push_back(nt);
339 }
340 }
341 }
342 }
343 if (out.size() > maxterms)
344 throw InputError("pfqn_rgfmc: exceeded maxterms; the residue term count grows as "
345 "C(S+M-1,M-1) per further elimination, use method 'ca'");
346 }
347 return out;
348}
349
350/** [z^N] exp(Z z) prod_t (1 - p_t z)^-m_t, signed, Coury-Harrison Property 1. */
351template <class T>
352void rgfmc_base_kernel(const std::vector<T>& loads, const std::vector<int>& mults, int N,
353 const T& Z, T& lg, int& sg, T& cond) {
354 using std::log;
355 const T zero = num_traits<T>::from_int(0);
356 const T ninf = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
357 const std::size_t len = static_cast<std::size_t>(N) + 1;
358 std::vector<T> lv(len, ninf), lr(len, zero);
359 std::vector<int> sv(len, 0), sr(len, 1);
360 lv[0] = zero;
361 sv[0] = 1;
362 if (Z > zero) {
363 for (std::size_t k = 0; k < len; ++k) {
364 const T kT = num_traits<T>::from_int(static_cast<long>(k));
365 lr[k] = T(kT * log(Z) - num_factln<T>(kT));
366 sr[k] = 1;
367 }
368 rgfmc_slogconv(lv, sv, lr, sr, cond);
369 }
370 for (std::size_t a = 0; a < loads.size(); ++a) {
371 if (loads[a] == zero) continue;
372 const T ap = num_abs(loads[a]);
373 const int mm = mults[a];
374 for (std::size_t k = 0; k < len; ++k) {
375 const T kT = num_traits<T>::from_int(static_cast<long>(k));
376 if (mm == 1) {
377 lr[k] = T(kT * log(ap));
378 } else {
379 const T mT = num_traits<T>::from_int(mm);
380 lr[k] = T(num_lgamma<T>(T(kT + mT)) - num_factln<T>(kT) - num_lgamma<T>(mT) +
381 kT * log(ap));
382 }
383 sr[k] = (loads[a] > zero || (k % 2) == 0) ? 1 : -1;
384 }
385 rgfmc_slogconv(lv, sv, lr, sr, cond);
386 }
387 lg = lv[static_cast<std::size_t>(N)];
388 sg = sv[static_cast<std::size_t>(N)];
389}
390
391/** Single-class base case, memoised on (loads, multiplicities) per Sec. 3.4. */
392template <class T>
393void rgfmc_base(const std::vector<RgfmcTerm<T> >& terms, int k1, const T& Z1, const T& tol,
394 T& lg, int& sg, T& cond) {
395 using std::log;
396 const T zero = num_traits<T>::from_int(0);
397 std::map<std::string, std::pair<T, int> > cache;
398 std::vector<T> lv;
399 std::vector<int> sv;
400 for (std::size_t it = 0; it < terms.size(); ++it) {
401 std::vector<std::vector<T> > F = terms[it].F;
402 std::vector<int> m = terms[it].m;
403 T lc = terms[it].lc;
404 int sc = terms[it].sc;
405 rgfmc_merge(F, m, tol, lc, sc);
406 int shift = 0;
407 std::vector<T> loads;
408 std::vector<int> mults;
409 for (std::size_t a = 0; a < F.size(); ++a) {
410 const T a0 = F[a][0];
411 const T a1 = F[a][1];
412 const int ma = m[a];
413 const T sca = std::max(num_abs(a0), num_abs(a1));
414 if (sca == zero) throw InputError("pfqn_rgfmc: met an identically zero factor");
415 if (num_abs(a0) <= tol * sca) {
416 lc = T(lc - num_traits<T>::from_int(ma) * log(num_abs(a1)));
417 sc *= rgfmc_signpow(a1 < zero, ma);
418 shift += ma;
419 } else {
420 lc = T(lc - num_traits<T>::from_int(ma) * log(num_abs(a0)));
421 sc *= rgfmc_signpow(a0 < zero, ma);
422 if (num_abs(a1) > tol * sca) {
423 loads.push_back(T(-a1 / a0));
424 mults.push_back(ma);
425 }
426 }
427 }
428 const int Ntot = k1 + shift;
429 std::ostringstream key;
430 key << Ntot << '|' << num_traits<T>::to_double(Z1);
431 for (std::size_t a = 0; a < loads.size(); ++a)
432 key << '|' << num_traits<T>::to_double(loads[a]) << ':' << mults[a];
433 typename std::map<std::string, std::pair<T, int> >::iterator hit = cache.find(key.str());
434 T klg = zero;
435 int ksg = 0;
436 if (hit == cache.end()) {
437 rgfmc_base_kernel(loads, mults, Ntot, Z1, klg, ksg, cond);
438 cache[key.str()] = std::make_pair(klg, ksg);
439 } else {
440 klg = hit->second.first;
441 ksg = hit->second.second;
442 }
443 if (ksg != 0) {
444 lv.push_back(T(lc + klg));
445 sv.push_back(sc * ksg);
446 }
447 }
448 rgfmc_slogsum(lv, sv, lv.size(), lg, sg, cond);
449}
450
451} // namespace detail
452
453/**
454 * @brief Multiclass Recursion by Generating Functions (RGF), with think
455 * times.
456 *
457 * @param L (M x R) service demands
458 * @param N (R) populations, nonnegative integers
459 * @param Z (R) think times
460 * @param tol relative tolerance for calling two affine forms proportional
461 * @param maxterms cap on residue terms carried between eliminations
462 * @param maxcancel nats of cancellation tolerated before refusing
463 */
464template <class T>
465RgfmcResult<T> pfqn_rgfmc(const Matrix<T>& L, const std::vector<int>& N,
466 const std::vector<T>& Z, const T& tol, std::size_t maxterms,
467 const T& maxcancel) {
469 "pfqn_rgfmc requires transcendental arithmetic: the residue coefficients are "
470 "carried in the log domain so that no Poisson weight or binomial is ever formed "
471 "as a naive ratio. Use pfqn_ca for the same constant in exact arithmetic");
472 using std::exp;
473 using std::log;
474 const T zero = num_traits<T>::from_int(0);
475 const std::size_t Rall = L.cols();
476 if (N.size() != Rall || Z.size() != Rall)
477 throw InputError("pfqn_rgfmc: requires N and Z to match the number of columns of L");
478 std::vector<std::size_t> kc;
479 for (std::size_t r = 0; r < Rall; ++r) {
480 if (N[r] < 0 || Z[r] < zero) throw InputError("pfqn_rgfmc: requires nonnegative N and Z");
481 if (N[r] > 0) kc.push_back(r);
482 }
483 const std::size_t R = kc.size();
484 RgfmcResult<T> res;
485 if (R == 0) {
486 res.G = num_traits<T>::from_int(1);
487 res.lG = zero;
488 return res;
489 }
490 std::vector<std::size_t> kr;
491 for (std::size_t i = 0; i < L.rows(); ++i) {
492 bool any = false;
493 for (std::size_t c = 0; c < R; ++c) {
494 const T d = L(i, kc[c]);
495 if (d < zero) throw InputError("pfqn_rgfmc: requires nonnegative demands");
496 if (d > zero) any = true;
497 }
498 if (any) kr.push_back(i);
499 }
500 const std::size_t M = kr.size();
501 if (M == 0) {
502 T lG = zero;
503 for (std::size_t c = 0; c < R; ++c) {
504 const T nT = num_traits<T>::from_int(N[kc[c]]);
505 lG = T(lG + nT * log(Z[kc[c]]) - detail::num_factln<T>(nT));
506 }
507 res.lG = lG;
508 res.G = exp(lG);
509 return res;
510 }
511 if (R == 1) {
512 std::vector<T> col(M);
513 for (std::size_t i = 0; i < M; ++i)
514 col[i] = L(kr[i], kc[0]);
515 RgfResult<T> r1 = pfqn_rgf<T>(col, N[kc[0]], Z[kc[0]]);
516 res.G = r1.G;
517 res.lG = r1.lG;
518 return res;
519 }
520 // Base class = smallest population: that population is the degree the
521 // sign-indefinite base series is carried to, so it drives the cancellation.
522 std::vector<std::size_t> ord(R);
523 for (std::size_t c = 0; c < R; ++c) ord[c] = c;
524 std::stable_sort(ord.begin(), ord.end(),
525 [&](std::size_t a, std::size_t b) { return N[kc[a]] < N[kc[b]]; });
526 std::vector<std::vector<T> > Ls(M, std::vector<T>(R, zero));
527 std::vector<int> Ns(R);
528 std::vector<T> Zs(R);
529 for (std::size_t c = 0; c < R; ++c) {
530 Ns[c] = N[kc[ord[c]]];
531 Zs[c] = Z[kc[ord[c]]];
532 for (std::size_t i = 0; i < M; ++i)
533 Ls[i][c] = L(kr[i], kc[ord[c]]);
534 }
535 T lGscale = zero;
536 for (std::size_t c = 0; c < R; ++c) {
537 T cs = Zs[c];
538 for (std::size_t i = 0; i < M; ++i) cs = std::max(cs, Ls[i][c]);
539 if (!(cs > zero)) cs = num_traits<T>::from_int(1);
540 for (std::size_t i = 0; i < M; ++i) Ls[i][c] = T(Ls[i][c] / cs);
541 Zs[c] = T(Zs[c] / cs);
542 lGscale = T(lGscale + num_traits<T>::from_int(Ns[c]) * log(cs));
543 }
544 detail::RgfmcTerm<T> t0;
545 t0.lc = zero;
546 t0.sc = 1;
547 for (std::size_t i = 0; i < M; ++i) {
548 std::vector<T> row(R + 1, zero);
549 row[0] = num_traits<T>::from_int(1);
550 for (std::size_t c = 0; c < R; ++c) row[c + 1] = T(-Ls[i][c]);
551 t0.F.push_back(row);
552 t0.m.push_back(1);
553 }
554 std::vector<detail::RgfmcTerm<T> > terms(1, t0);
555 T cond = zero;
556 // F column p+1 carries class p, so eliminating class p means column p+1.
557 for (std::size_t col = R; col >= 2; --col) {
558 terms = detail::rgfmc_step(terms, col, Ns[col - 1], Zs[col - 1], tol, maxterms, cond);
559 if (terms.empty()) {
560 res.G = zero;
561 res.lG = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
562 return res;
563 }
564 }
565 T lg = zero;
566 int sg = 0;
567 detail::rgfmc_base(terms, Ns[0], Zs[0], tol, lg, sg, cond);
568 if (sg == 0) {
569 res.G = zero;
570 res.lG = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
571 return res;
572 }
573 if (sg < 0 || cond > maxcancel)
574 throw InputError("pfqn_rgfmc: the residue sum cancelled past the tolerated nats, so lG "
575 "carries no significant digits. The eliminated classes have "
576 "near-coincident loads over the stations; use method 'ca'");
577 res.lG = T(lg + lGscale);
578 res.G = exp(res.lG);
579 return res;
580}
581
582/** Overload with the reference defaults (tol 1e-12, 1e6 terms, 15 nats). */
583template <class T>
584RgfmcResult<T> pfqn_rgfmc(const Matrix<T>& L, const std::vector<int>& N,
585 const std::vector<T>& Z) {
586 return pfqn_rgfmc<T>(L, N, Z, num_traits<T>::from_double(1e-12),
587 static_cast<std::size_t>(1000000), num_traits<T>::from_double(15.0));
588}
589
590} // namespace pfqn
591} // namespace line
592
593#endif // LINE_API_PFQN_PFQN_RGFMC_H
InputError(const std::string &what)
Definition error.h:39
std::size_t size() const
Definition matrix.h:91
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
The exception types the port throws.
Dense matrix and non-owning view.
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
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
T num_abs(const T &v)
Definition number.h:172
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
Recursion by Generating Functions (RGF) for the normalizing constant of a SINGLE-CLASS closed product...
Return value of pfqn_rgf, mirroring [G, lG, lg].
Definition pfqn_rgf.h:56
T lG
its logarithm, i.e. lg[N]
Definition pfqn_rgf.h:58
T G
normalizing constant
Definition pfqn_rgf.h:57
Return value of pfqn_rgfmc, mirroring [G, lG].
Definition pfqn_rgfmc.h:74
T G
normalizing constant
Definition pfqn_rgfmc.h:75
T lG
its logarithm
Definition pfqn_rgfmc.h:76