LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
pfqn_rgf.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_RGF_H
6#define LINE_API_PFQN_PFQN_RGF_H
7
8/**
9 * @file
10 * @ingroup api_pfqn
11 * Recursion by Generating Functions (RGF) for the normalizing constant of a
12 * SINGLE-CLASS closed product-form network with replicated stations.
13 *
14 * Templated port of matlab/src/api/pfqn/pfqn_rgf.m, i.e. Property 1 of
15 * J. Coury and P. G. Harrison, "Asymptotic properties of queuing networks",
16 * IEE Proc.-Comput. Digit. Tech. 144(5):247-254, 1997.
17 *
18 * WHAT MAKES IT DIFFERENT FROM BUZEN. Convolution proceeds per GENERATING
19 * FUNCTION rather than per station: a group of m stations sharing one demand p
20 * collapses into the single negative-binomial sequence r(k) = C(k+m-1,k) p^k,
21 * so the group costs one convolution pass instead of m. The delay contributes
22 * the Poisson sequence r(k) = Z^k / k!. Convolving the G distinct sequences
23 * yields g(0..N) exactly, and lG = log g(N).
24 *
25 * Cost O(G N^2) against Buzen's O(M N), so RGF is the cheaper route precisely
26 * when the model is heavily replicated and the population moderate (G N < M).
27 *
28 * ARITHMETIC. The whole recursion runs in the LOG domain -- that is the point
29 * of the routine, since no intermediate can then overflow or underflow -- so it
30 * is gated on num_traits<T>::has_transcendental. A caller that wants the same
31 * constant in exact arithmetic wants pfqn_ca, which computes it by the ordinary
32 * convolution over the rationals.
33 *
34 * SINGLE CLASS BY CONSTRUCTION. Grouping stations by their demand only defines
35 * a sequence when the demand is a scalar, so a multiclass argument is refused
36 * here by name; `pfqn_nc`'s 'rgf' branch sends a multiclass model to pfqn_ca
37 * instead, which is what the reference does.
38 */
39
40#include <algorithm>
41#include <cmath>
42#include <cstddef>
43#include <limits>
44#include <vector>
45
47#include "line/num/number.h"
48#include "line/util/error.h"
49#include "line/util/matrix.h"
50
51namespace line {
52namespace pfqn {
53
54/** Return value of pfqn_rgf, mirroring [G, lG, lg]. */
55template <class T>
56struct RgfResult {
57 T G; ///< normalizing constant
58 T lG; ///< its logarithm, i.e. lg[N]
59 std::vector<T> lg; ///< log g(0), log g(1), ..., log g(N)
60};
61
62namespace detail {
63
64/** Log-domain linear convolution truncated at the common length (logconv.m). */
65template <class T>
66std::vector<T> rgf_logconv(const std::vector<T>& u, const std::vector<T>& v) {
67 const std::size_t n = u.size();
68 std::vector<T> c(n, num_traits<T>::from_double(-std::numeric_limits<double>::infinity()));
69 for (std::size_t k = 0; k < n; ++k) {
70 std::vector<T> t(k + 1);
71 for (std::size_t i = 0; i <= k; ++i) t[i] = T(u[i] + v[k - i]);
72 // logsumexp returns the maximum unchanged when every term is -inf,
73 // which is the reference's `if isinf(vm), c(k) = vm` branch.
74 c[k] = logsumexp(t);
75 }
76 return c;
77}
78
79} // namespace detail
80
81/**
82 * @brief Recursion by Generating Functions (RGF) for the normalizing constant
83 * of a SINGLE-CLASS closed product-form network with replicated
84 * stations.
85 *
86 * @param L (M) service demands of the queueing stations
87 * @param N population, a nonnegative integer
88 * @param Z aggregate delay demand (think time); zero for none
89 */
90template <class T>
91RgfResult<T> pfqn_rgf(const std::vector<T>& L, int N, const T& Z) {
93 "pfqn_rgf requires transcendental arithmetic: the recursion is carried in the "
94 "log domain so that no intermediate can overflow. Use pfqn_ca for the same "
95 "constant in exact arithmetic");
96 using std::exp;
97 using std::log;
98 const T zero = num_traits<T>::from_int(0);
99 const T ninf = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
100
101 if (N < 0) throw InputError("pfqn_rgf: requires a nonnegative integer population");
102 if (Z < zero) throw InputError("pfqn_rgf: requires a nonnegative think time");
103 for (const T& v : L)
104 if (v < zero) throw InputError("pfqn_rgf: requires nonnegative demands");
105
106 const std::size_t Np = static_cast<std::size_t>(N);
107 RgfResult<T> res;
108 // g(k) = 1 for the empty network, 0 elsewhere before any node is folded in.
109 res.lg.assign(Np + 1, ninf);
110 res.lg[0] = zero;
111
112 // Delay node: the Poisson sequence Z^k / k!.
113 if (Z > zero) {
114 std::vector<T> lr(Np + 1);
115 for (std::size_t k = 0; k <= Np; ++k) {
116 const T kT = num_traits<T>::from_int(static_cast<long>(k));
117 lr[k] = T(kT * log(Z) - detail::num_factln<T>(kT));
118 }
119 res.lg = detail::rgf_logconv(res.lg, lr);
120 }
121
122 // Queueing stations grouped by identical demand: one sequence per group.
123 // The reference groups through unique(L), which sorts; the multiplicity is
124 // all that enters the sequence, so sorting a copy reproduces it exactly.
125 std::vector<T> pos;
126 for (const T& v : L)
127 if (v > zero) pos.push_back(v);
128 std::sort(pos.begin(), pos.end(), [](const T& a, const T& b) { return a < b; });
129 std::size_t i = 0;
130 while (i < pos.size()) {
131 std::size_t j = i;
132 while (j < pos.size() && pos[j] == pos[i]) ++j;
133 const std::size_t m = j - i;
134 const T p = pos[i];
135 std::vector<T> lr(Np + 1);
136 for (std::size_t k = 0; k <= Np; ++k) {
137 const T kT = num_traits<T>::from_int(static_cast<long>(k));
138 if (m == 1) {
139 // 1 / (1 - p u)
140 lr[k] = T(kT * log(p));
141 } else {
142 const T mT = num_traits<T>::from_int(static_cast<long>(m));
143 lr[k] = T(detail::num_lgamma<T>(T(kT + mT)) - detail::num_factln<T>(kT) -
144 detail::num_lgamma<T>(mT) + kT * log(p));
145 }
146 }
147 res.lg = detail::rgf_logconv(res.lg, lr);
148 i = j;
149 }
150
151 res.lG = res.lg[Np];
152 res.G = exp(res.lG);
153 return res;
154}
155
156/** Overload without a delay. */
157template <class T>
158RgfResult<T> pfqn_rgf(const std::vector<T>& L, int N) {
159 return pfqn_rgf(L, N, num_traits<T>::from_int(0));
160}
161
162} // namespace pfqn
163} // namespace line
164
165#endif // LINE_API_PFQN_PFQN_RGF_H
InputError(const std::string &what)
Definition error.h:39
The exception types the port throws.
Dense matrix and non-owning view.
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
Number-type abstraction for the templated API port.
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
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
std::vector< T > lg
log g(0), log g(1), ..., log g(N)
Definition pfqn_rgf.h:59