LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_cub.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_CUB_H
6
#define LINE_API_PFQN_PFQN_CUB_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* Normalizing constant by Grundmann-Moeller cubature over the simplex.
12
*
13
* Templated port of matlab/src/api/pfqn/pfqn_cub.m together with its two local
14
* functions simplexquad and grnmol (Grundmann and Moller, SIAM J. Numer. Anal.
15
* 15 (1978) 282-290). With Z = 0 the constant is a single integral of
16
* prod_r (u' L(:,r))^{N_r} over the (M-1)-simplex; the degree-(2s+1) rule is
17
* EXACT once s >= ceil((sum N - 1)/2), because the integrand is then a
18
* polynomial of degree sum(N) <= 2s+1. With Z > 0 an outer integral over the
19
* McKenna-Mitra scale variable v is added on a uniform grid, which is where
20
* the method stops being exact.
21
*
22
* The `grnmol` rule is exported, because it is the only working
23
* Grundmann-Moeller implementation in the reference tree; see the note on
24
* pfqn_grnmol in the report accompanying this port.
25
*
26
* ARITHMETIC. The rule itself is a weighted sum of integrand values at
27
* rational barycentric points, so at Z = 0 and full degree the whole
28
* computation would be exact in a field -- were it not for the
29
* exp(gammaln(1+sum N+M-1) - sum gammaln(1+N)) prefactor, which MATLAB forms in
30
* logarithms. Since that prefactor is a ratio of factorials it could be formed
31
* exactly, but the reference does not, and reproducing the reference is the
32
* contract; the routine is therefore gated on
33
* num_traits<T>::has_transcendental, and the exactness claim above is about
34
* the cubature, not about the returned scalar.
35
*/
36
37
#include <cmath>
38
#include <cstddef>
39
#include <functional>
40
#include <vector>
41
42
#include "
line/api/pfqn/pfqn_asympt_common.h
"
43
#include "
line/num/number.h
"
44
#include "
line/util/error.h
"
45
#include "
line/util/matrix.h
"
46
47
namespace
line
{
48
namespace
pfqn
{
49
50
/** Return value of pfqn_cub, mirroring [Gn, lGn]. */
51
template
<
class
T>
52
struct
CubResult
{
53
T
G
;
54
T
lG
;
55
};
56
57
/**
58
* Grundmann-Moeller rule of degrees 1, 3, ..., 2s+1 over the n-simplex with
59
* vertices the columns of the identity (MATLAB's grnmol on V = eye(n,n+1)).
60
*
61
* @param f integrand, evaluated on the n free barycentric coordinates
62
* @param n simplex dimension
63
* @param s maximum rule order
64
* @param tol relative stopping tolerance between consecutive degrees
65
* @return the successive estimates, the last of which is the answer
66
*/
67
template
<
class
T>
68
std::vector<T>
grnmol
(
const
std::function<T(
const
std::vector<T>&)>& f, std::size_t n,
int
s,
69
const
T& tol) {
70
// exactness-in-any-field rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
71
if
(n == 0)
throw
InputError
(
"grnmol: zero-dimensional simplex"
);
72
const
T zero =
num_traits<T>::from_int
(0);
73
std::vector<T> Q, Qv;
74
const
T Vol = T(
num_traits<T>::from_int
(1) /
num_factorial<T>
(
static_cast<
unsigned
>
(n)));
75
int
d = 0;
76
while
(
true
) {
77
const
long
m =
static_cast<
long
>
(n) + 2 * d + 1;
78
std::vector<long> al(n, 1);
79
long
alz = 2 * d + 1;
80
T Qs = zero;
81
while
(
true
) {
82
// barycentric evaluation point rationale: see _kb/03-api-layer.md (cpp port notes: pfqn)
83
std::vector<T> x(n);
84
x[0] = T(
num_traits<T>::from_int
(alz) /
num_traits<T>::from_int
(m));
85
for
(std::size_t j = 1; j < n; ++j)
86
x[j] = T(
num_traits<T>::from_int
(al[j - 1]) /
num_traits<T>::from_int
(m));
87
Qs += f(x);
88
for
(std::size_t j = 0; j < n; ++j) {
89
alz -= 2;
90
if
(alz > 0) {
91
al[j] += 2;
92
break
;
93
}
94
alz += al[j] + 1;
95
al[j] = 1;
96
}
97
if
(alz == 2 * d + 1)
break
;
98
}
99
++d;
100
Qv.push_back(T(Vol * Qs));
101
T q = zero;
102
T p =
num_traits<T>::from_int
(2);
103
for
(
long
k =
static_cast<
long
>
(n) + 1; k <= m; ++k) p /=
num_traits<T>::from_int
(2 * k);
104
for
(
int
i = 1; i <= d; ++i) {
105
q +=
num_pow_int
(
num_traits<T>::from_int
(m + 2 - 2 * i),
static_cast<
unsigned
>
(2 * d - 1)) *
106
p * Qv[
static_cast<
std::size_t
>
(d - i)];
107
p = T(-p *
num_traits<T>::from_int
(m + 1 - i) /
num_traits<T>::from_int
(i));
108
}
109
Q.push_back(q);
110
// MATLAB's test is abs(Q(d)-Q(d-1)) < tol*Q(d-1), with Q(d-1) SIGNED,
111
// so a negative previous estimate never stops the loop. Kept as is.
112
if
(d > s || (d > 1 &&
num_abs
(T(Q[
static_cast<
std::size_t
>
(d) - 1] -
113
Q[
static_cast<
std::size_t
>
(d) - 2])) <
114
T(tol * Q[
static_cast<
std::size_t
>
(d) - 2])))
115
break
;
116
}
117
return
Q;
118
}
119
120
/**
121
* @brief Normalizing constant by Grundmann-Moeller cubature over the simplex.
122
*
123
* @param L (M x R) demands
124
* @param N (R) population
125
* @param Z (R) think times, empty or all zero for the exact branch
126
* @param order rule degree; the default ceil((sum N - 1)/2) makes the Z = 0
127
* branch exact
128
* @param atol absolute tolerance, also the zero test on sum(Z)
129
*/
130
template
<
class
T>
131
CubResult<T>
pfqn_cub
(
const
Matrix<T>
& L,
const
std::vector<int>& N,
const
std::vector<T>& Z,
132
int
order,
const
T& atol) {
133
static_assert
(
num_traits<T>::has_transcendental
,
134
"pfqn_cub requires transcendental arithmetic (the factorial prefactor is formed "
135
"in logarithms, and the Z > 0 branch is a quadrature)"
);
136
using
std::exp;
137
using
std::log;
138
const
std::size_t M = L.
rows
(), R = L.
cols
();
139
if
(N.size() != R)
throw
InputError
(
"pfqn_cub: L and N disagree on the class count"
);
140
const
T zero =
num_traits<T>::from_int
(0), one =
num_traits<T>::from_int
(1);
141
CubResult<T>
res;
142
143
long
Nt = 0;
144
for
(
int
v : N) {
145
if
(v < 0)
throw
InputError
(
"pfqn_cub: negative population"
);
146
Nt += v;
147
}
148
if
(M == 0 || N.empty() || Nt == 0) {
149
res.
G
= one;
150
res.
lG
= zero;
151
return
res;
152
}
153
if
(M == 1)
throw
InputError
(
"pfqn_cub: the simplex is degenerate for a single station"
);
154
155
T Zsum = zero;
156
for
(
const
T& v : Z) Zsum += v;
157
158
if
(Z.empty() || Zsum < atol) {
159
// Integrand prod_r (u' L(:,r))^{N_r} on the (M-1)-simplex.
160
const
std::function<T(
const
std::vector<T>&)> f = [&](
const
std::vector<T>& x) {
161
T last = one;
162
for
(
const
T& v : x) last -= v;
163
T prod = one;
164
for
(std::size_t r = 0; r < R; ++r) {
165
if
(N[r] == 0)
continue
;
166
T uL = zero;
167
for
(std::size_t i = 0; i + 1 < M; ++i) uL += x[i] * L(i, r);
168
uL += last * L(M - 1, r);
169
prod *=
num_pow_int
(uL,
static_cast<
unsigned
>
(N[r]));
170
}
171
return
prod;
172
};
173
const
std::vector<T> Q =
grnmol<T>
(f, M - 1, order, atol);
174
T coeff = detail::num_factln<T>(
num_traits<T>::from_int
(Nt +
static_cast<
long
>
(M) - 1));
175
for
(std::size_t r = 0; r < R; ++r)
176
coeff -= detail::num_factln<T>(
num_traits<T>::from_int
(N[r]));
177
res.
G
= T(Q.back() * exp(coeff));
178
res.
lG
= log(res.
G
);
179
return
res;
180
}
181
182
// Z > 0: outer McKenna-Mitra integral on a uniform grid of 1e4 steps.
183
const
long
steps = 10000;
184
const
T vmax =
num_traits<T>::from_int
(10 * Nt);
185
const
T dv = T(vmax /
num_traits<T>::from_int
(steps));
186
T Gn = zero;
187
for
(
long
k = 0; k <= steps; ++k) {
188
const
T v = T(dv *
num_traits<T>::from_int
(k));
189
Matrix<T>
Lv(M, R);
190
for
(std::size_t i = 0; i < M; ++i)
191
for
(std::size_t r = 0; r < R; ++r) Lv(i, r) = T(L(i, r) * v + Z[r]);
192
const
std::function<T(
const
std::vector<T>&)> f = [&](
const
std::vector<T>& x) {
193
T last = one;
194
for
(
const
T& val : x) last -= val;
195
T s = zero;
196
for
(std::size_t r = 0; r < R; ++r) {
197
if
(N[r] == 0)
continue
;
198
T uL = zero;
199
for
(std::size_t i = 0; i + 1 < M; ++i) uL += x[i] * Lv(i, r);
200
uL += last * Lv(M - 1, r);
201
s +=
num_traits<T>::from_int
(N[r]) * log(uL);
202
}
203
return
T(exp(s));
204
};
205
const
std::vector<T> Q =
grnmol<T>
(f, M - 1, order, atol);
206
const
T dG =
207
T(exp(T(-v)) *
num_pow_int
(v,
static_cast<
unsigned
>
(M - 1)) * Q.back() * dv);
208
Gn += dG;
209
if
(k > 0 && Gn > zero && T(dG / Gn) < atol)
break
;
210
}
211
T coeff = zero;
212
for
(std::size_t r = 0; r < R; ++r) coeff -= detail::num_factln<T>(
num_traits<T>::from_int
(N[r]));
213
res.
G
= T(Gn * exp(coeff));
214
res.
lG
= log(res.
G
);
215
return
res;
216
}
217
218
template
<
class
T>
219
CubResult<T>
pfqn_cub
(
const
Matrix<T>
& L,
const
std::vector<int>& N,
const
std::vector<T>& Z) {
220
long
Nt = 0;
221
for
(
int
v : N) Nt += v;
222
const
int
order =
static_cast<
int
>
((Nt - 1 + 1) / 2);
// ceil((Nt-1)/2)
223
return
pfqn_cub
(L, N, Z, order,
num_traits<T>::from_double
(1e-8));
224
}
225
226
}
// namespace pfqn
227
}
// namespace line
228
229
#endif
// LINE_API_PFQN_PFQN_CUB_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::Matrix
Definition
matrix.h:56
line::Matrix::cols
std::size_t cols() const
Definition
matrix.h:90
line::Matrix::rows
std::size_t rows() const
Definition
matrix.h:89
error.h
The exception types the port throws.
matrix.h
Dense matrix and non-owning view.
line::pfqn
Definition
cd_peak_scaling.h:43
line::pfqn::grnmol
std::vector< T > grnmol(const std::function< T(const std::vector< T > &)> &f, std::size_t n, int s, const T &tol)
Grundmann-Moeller rule of degrees 1, 3, ..., 2s+1 over the n-simplex with vertices the columns of the...
Definition
pfqn_cub.h:68
line::pfqn::pfqn_cub
CubResult< T > pfqn_cub(const Matrix< T > &L, const std::vector< int > &N, const std::vector< T > &Z, int order, const T &atol)
Normalizing constant by Grundmann-Moeller cubature over the simplex.
Definition
pfqn_cub.h:131
line
Definition
aoi_dist2ph.h:52
line::num_factorial
T num_factorial(unsigned n)
Factorial as a value of T.
Definition
number.h:184
line::num_abs
T num_abs(const T &v)
Definition
number.h:172
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,...
line::num_traits
Definition
number.h:111
line::pfqn::CubResult
Return value of pfqn_cub, mirroring [Gn, lGn].
Definition
pfqn_cub.h:52
line::pfqn::CubResult::lG
T lG
Definition
pfqn_cub.h:54
line::pfqn::CubResult::G
T G
Definition
pfqn_cub.h:53
include
line
api
pfqn
pfqn_cub.h
Generated by
1.18.0