LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_mmint2.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_MMINT2_H
6
#define LINE_API_PFQN_PFQN_MMINT2_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* McKenna-Mitra integral form of the normalizing constant of a repairman
12
* model (one queueing station, R classes, per-class think time), in its three
13
* MATLAB quadratures.
14
*
15
* Templated port of matlab/src/api/pfqn/pfqn_mmint2.m,
16
* pfqn_mmint2_gausslegendre.m and pfqn_mmint2_gausslaguerre.m. All three
17
* evaluate
18
*
19
* G = 1/prod_r N_r! * int_0^inf u^{m-1} e^{-u} prod_r (Z_r + L_r u)^{N_r} du
20
*
21
* and differ only in the rule: adaptive Gauss-Kronrod on a truncated interval,
22
* a fixed Gauss-Legendre rule on [0, 1e6], or Gauss-Laguerre on [0, inf).
23
* The integrand is a polynomial times e^{-u}, so Gauss-Laguerre with enough
24
* nodes is exact up to rounding, which makes it the natural cross-check on the
25
* other two and on pfqn_ca.
26
*
27
* NODES. MATLAB loads a Julia-generated table (gausslegendre-data.mat,
28
* gausslaguerre-data.mat). A table cannot be carried across arithmetics -- it
29
* would pin every instantiation to the precision it was generated at -- so the
30
* rules are regenerated in T by pfqn_asympt_common.h.
31
*
32
* The Legendre form needs care. MATLAB's node count is
33
* n = max(300, min(tablesize, 2(sum N + m - 1) - 1)) and it takes the FIRST n
34
* entries of a 20000-point rule on [0, 1e6], which is not the same thing as a
35
* fresh n-point rule: the 20000-point prefix spans [0.0036, 557.8] and
36
* resolves the e^{-u} factor, whereas a genuine 300-point rule on [0, 1e6] has
37
* its first node at u = 13.7 and misses the mass entirely (it returns
38
* log G = -3.44 where the answer is 1.63). The port therefore generates the
39
* 20000-point rule and takes the same prefix, computing only the prefix since
40
* each Newton iteration is independent of the other nodes. `nodecap` is that
41
* table length and defaults to 20000, the length of MATLAB's
42
* gausslegendre-nodes.txt.
43
*
44
* TRUNCATION of the adaptive form. MATLAB integrates over
45
* [0, -log(1 - (1 - 1e-12))] = [0, 27.63...], i.e. the 1 - 10^-order quantile
46
* of the unit exponential, and asks for AbsTol 1e-12. The port keeps both
47
* constants. That truncation is the dominant error for large populations,
48
* where the polynomial factor pushes mass well beyond the cutoff; the tests
49
* record where it starts to bite.
50
*
51
* ARITHMETIC. Quadrature, so all three are gated on
52
* num_traits<T>::has_transcendental.
53
*/
54
55
#include <algorithm>
56
#include <cmath>
57
#include <cstddef>
58
#include <vector>
59
60
#include "
line/api/pfqn/pfqn_asympt_common.h
"
61
#include "
line/api/qsys/qsys_quadrature.h
"
62
#include "
line/num/number.h
"
63
#include "
line/util/error.h
"
64
65
namespace
line
{
66
namespace
pfqn
{
67
68
/** Return value of the McKenna-Mitra quadratures, mirroring [G, lG]. */
69
template
<
class
T>
70
struct
MmintResult
{
71
T
G
;
72
T
lG
;
73
};
74
75
/**
76
* Adaptive form (MATLAB pfqn_mmint2): Gauss-Kronrod on [0, 27.63] with
77
* absolute tolerance 1e-12.
78
*
79
* @param L (R) demand at the station, @param N (R) population,
80
* @param Z (R) think times
81
*/
82
template
<
class
T>
83
MmintResult<T>
pfqn_mmint2
(
const
std::vector<T>& L,
const
std::vector<T>& N,
84
const
std::vector<T>& Z) {
85
static_assert
(
num_traits<T>::has_transcendental
,
86
"pfqn_mmint2 requires transcendental arithmetic (quadrature of e^{-u} p(u))"
);
87
using
std::exp;
88
using
std::log;
89
const
std::size_t R = L.size();
90
if
(N.size() != R || Z.size() != R)
91
throw
InputError
(
"pfqn_mmint2: L, N and Z must have the same length"
);
92
const
T zero =
num_traits<T>::from_int
(0);
93
94
// The reference restricts the product to the classes with N_r > 0.
95
std::vector<std::size_t> nz;
96
for
(std::size_t r = 0; r < R; ++r)
97
if
(N[r] != zero) nz.push_back(r);
98
99
const
auto
f = [&](
const
T& u) {
100
using
std::exp;
101
T p = exp(T(-u));
102
for
(std::size_t k = 0; k < nz.size(); ++k) {
103
const
std::size_t r = nz[k];
104
const
double
nd =
num_traits<T>::to_double
(N[r]);
105
p *=
num_pow_int
(T(Z[r] + L[r] * u),
static_cast<
unsigned
>
(nd));
106
}
107
return
p;
108
};
109
110
const
int
order = 12;
111
const
T hi =
num_traits<T>::from_double
(-std::log(1.0 - (1.0 - std::pow(10.0, -order))));
112
const
T atol =
num_traits<T>::from_double
(std::pow(10.0, -order));
113
const
T I = qsys::detail::num_integral<T>(f, zero, hi,
num_traits<T>::from_double
(1e-12), atol);
114
if
(I <= zero)
throw
NumericError
(
"pfqn_mmint2: non-positive integral"
);
115
116
MmintResult<T>
res;
117
T lG = log(I);
118
for
(std::size_t r = 0; r < R; ++r) lG -= detail::num_factln<T>(N[r]);
119
res.
lG
= lG;
120
res.
G
= exp(lG);
121
return
res;
122
}
123
124
/**
125
* Gauss-Legendre form on [0, 1e6] (MATLAB pfqn_mmint2_gausslegendre).
126
*
127
* @param m station multiplicity, contributing the u^{m-1} factor
128
* @param nodecap size of the underlying tabulated rule, i.e. MATLAB's table
129
* length; the routine uses its first n nodes
130
* @param L (M) service demands
131
* @param N (1) population, single class
132
* @param Z (1) think time
133
*/
134
template
<
class
T>
135
MmintResult<T>
pfqn_mmint2_gausslegendre
(
const
std::vector<T>& L,
const
std::vector<T>& N,
136
const
std::vector<T>& Z,
int
m, std::size_t nodecap) {
137
static_assert
(
num_traits<T>::has_transcendental
,
138
"pfqn_mmint2_gausslegendre requires transcendental arithmetic (quadrature)"
);
139
using
std::exp;
140
using
std::log;
141
const
std::size_t R = L.size();
142
if
(N.size() != R || Z.size() != R)
143
throw
InputError
(
"pfqn_mmint2_gausslegendre: L, N and Z must have the same length"
);
144
if
(m < 1)
throw
InputError
(
"pfqn_mmint2_gausslegendre: multiplicity must be at least one"
);
145
const
T zero =
num_traits<T>::from_int
(0);
146
T Ntot = zero;
147
for
(
const
T& v : N) Ntot += v;
148
149
const
long
want = 2 * (
static_cast<
long
>
(
num_traits<T>::to_double
(Ntot)) + m - 1) - 1;
150
std::size_t n = 300;
151
const
std::size_t capped = std::min<std::size_t>(nodecap, want > 0 ?
static_cast<
std::size_t
>
(want) : 1);
152
if
(capped > n) n = capped;
153
if
(n > nodecap) n = nodecap;
154
155
// 20000-point table prefix rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
156
std::vector<T> x, w;
157
detail::gauss_legendre<T>(nodecap, zero,
num_traits<T>::from_double
(1e6), x, w, n);
158
159
std::vector<T> g(n);
160
for
(std::size_t i = 0; i < n; ++i) {
161
T y = zero;
162
for
(std::size_t r = 0; r < R; ++r) {
163
if
(N[r] == zero)
continue
;
164
y += N[r] * log(T(Z[r] + L[r] * x[i]));
165
}
166
g[i] = T(log(w[i]) - x[i] + y);
167
if
(m > 1) g[i] +=
num_traits<T>::from_int
(m - 1) * log(x[i]);
168
}
169
T coeff = zero;
170
for
(std::size_t r = 0; r < R; ++r) coeff -= detail::num_factln<T>(N[r]);
171
coeff -= detail::num_factln<T>(
num_traits<T>::from_int
(m - 1));
172
173
MmintResult<T>
res;
174
// stable logsumexp rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
175
res.
lG
= T(detail::logsumexp(g) + coeff);
176
res.
G
= exp(res.
lG
);
177
return
res;
178
}
179
180
template
<
class
T>
181
MmintResult<T>
pfqn_mmint2_gausslegendre
(
const
std::vector<T>& L,
const
std::vector<T>& N,
182
const
std::vector<T>& Z) {
183
// 20000 is the length of MATLAB's gausslegendre-nodes.txt.
184
return
pfqn_mmint2_gausslegendre
(L, N, Z, 1, 20000);
185
}
186
187
/**
188
* Gauss-Laguerre form (MATLAB pfqn_mmint2_gausslaguerre).
189
*
190
* @param npts node count; MATLAB uses the length of its tabulated rule
191
* @param L (M) service demands
192
* @param N (1) population, single class
193
* @param Z (1) think time
194
* @param m multiplicity of the queueing station
195
*/
196
template
<
class
T>
197
MmintResult<T>
pfqn_mmint2_gausslaguerre
(
const
std::vector<T>& L,
const
std::vector<T>& N,
198
const
std::vector<T>& Z,
int
m, std::size_t npts) {
199
static_assert
(
num_traits<T>::has_transcendental
,
200
"pfqn_mmint2_gausslaguerre requires transcendental arithmetic (quadrature)"
);
201
using
std::exp;
202
using
std::log;
203
const
std::size_t R = L.size();
204
if
(N.size() != R || Z.size() != R)
205
throw
InputError
(
"pfqn_mmint2_gausslaguerre: L, N and Z must have the same length"
);
206
if
(m < 1)
throw
InputError
(
"pfqn_mmint2_gausslaguerre: multiplicity must be at least one"
);
207
if
(npts < 2)
throw
InputError
(
"pfqn_mmint2_gausslaguerre: at least two nodes are required"
);
208
const
T zero =
num_traits<T>::from_int
(0);
209
210
std::vector<T> x, w;
211
detail::gauss_laguerre<T>(npts, x, w);
212
std::vector<T> g(npts);
213
for
(std::size_t i = 0; i < npts; ++i) {
214
T F = zero;
215
if
(m > 1) F +=
num_traits<T>::from_int
(m - 1) * log(x[i]);
216
for
(std::size_t r = 0; r < R; ++r) {
217
if
(N[r] == zero)
continue
;
218
F += N[r] * log(T(Z[r] + L[r] * x[i]));
219
}
220
g[i] = T(log(w[i]) + F);
221
}
222
T coeff = zero;
223
for
(std::size_t r = 0; r < R; ++r) coeff -= detail::num_factln<T>(N[r]);
224
coeff -= detail::num_factln<T>(
num_traits<T>::from_int
(m - 1));
225
226
MmintResult<T>
res;
227
res.
lG
= T(detail::logsumexp(g) + coeff);
228
res.
G
= exp(res.
lG
);
229
return
res;
230
}
231
232
template
<
class
T>
233
MmintResult<T>
pfqn_mmint2_gausslaguerre
(
const
std::vector<T>& L,
const
std::vector<T>& N,
234
const
std::vector<T>& Z) {
235
return
pfqn_mmint2_gausslaguerre
(L, N, Z, 1, 90);
236
}
237
238
}
// namespace pfqn
239
}
// namespace line
240
241
#endif
// LINE_API_PFQN_PFQN_MMINT2_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::NumericError::NumericError
NumericError(const std::string &what)
Definition
error.h:45
error.h
The exception types the port throws.
line::pfqn
Definition
cd_peak_scaling.h:43
line::pfqn::pfqn_mmint2_gausslegendre
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).
Definition
pfqn_mmint2.h:135
line::pfqn::pfqn_mmint2
MmintResult< T > pfqn_mmint2(const std::vector< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Adaptive form (MATLAB pfqn_mmint2): Gauss-Kronrod on [0, 27.63] with absolute tolerance 1e-12.
Definition
pfqn_mmint2.h:83
line::pfqn::pfqn_mmint2_gausslaguerre
MmintResult< T > pfqn_mmint2_gausslaguerre(const std::vector< T > &L, const std::vector< T > &N, const std::vector< T > &Z, int m, std::size_t npts)
Gauss-Laguerre form (MATLAB pfqn_mmint2_gausslaguerre).
Definition
pfqn_mmint2.h:197
line
Definition
aoi_dist2ph.h:52
line::num_pow_int
T num_pow_int(const T &base, unsigned e)
Integer power, valid in any field (no transcendental requirement).
Definition
number.h:192
number.h
Number-type abstraction for the templated API port.
pfqn_asympt_common.h
Shared scalar machinery for the integration / asymptotic members of the pfqn family (pfqn_le,...
qsys_quadrature.h
Adaptive quadrature for the qsys functions whose MATLAB originals call integral(),...
line::num_traits
Definition
number.h:111
line::pfqn::MmintResult
Return value of the McKenna-Mitra quadratures, mirroring [G, lG].
Definition
pfqn_mmint2.h:70
line::pfqn::MmintResult::G
T G
Definition
pfqn_mmint2.h:71
line::pfqn::MmintResult::lG
T lG
Definition
pfqn_mmint2.h:72
include
line
api
pfqn
pfqn_mmint2.h
Generated by
1.18.0