LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_le.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_LE_H
6
#define LINE_API_PFQN_PFQN_LE_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* Logistic expansion (LE) asymptotic approximation of the normalizing constant
12
* of a closed product-form network.
13
*
14
* Templated port of matlab/src/api/pfqn/pfqn_le.m (Casale, "Accelerating
15
* performance inference over closed systems by asymptotic methods",
16
* SIGMETRICS 2017), including its four local functions pfqn_le_fpi,
17
* pfqn_le_fpiZ, pfqn_le_hessian and pfqn_le_hessianZ, which are exported here
18
* because pfqn_ls needs the same mode and Hessian.
19
*
20
* The integral representation of G is mapped to the simplex by a logistic
21
* transformation and evaluated by Laplace's method at the mode u* of the
22
* transformed integrand, giving
23
*
24
* log G = multinomialln([N, M-1]) + factln(M-1) + (M-1) log sqrt(2 pi)
25
* - log sqrt(det A) + sum_i log u*_i + sum_r N_r log(u*' L(:,r))
26
*
27
* with A the Hessian at the mode, and the analogous Z > 0 form in which the
28
* mode carries an extra scale variable v*. This is Cas17 eq. (34) as published;
29
* pfqn_ble (pfqn_ble.h) adds the eps->0 bias correction derived there.
30
*
31
* ARITHMETIC. Laplace's method is an asymptotic approximation and the formula
32
* itself is a sum of logarithms, so the routine is gated on
33
* num_traits<T>::has_transcendental: it has no meaning in exact arithmetic,
34
* and instantiating it there would silently produce a value that is not the
35
* normalizing constant.
36
*
37
* FIXED POINT. The mode is found by the same 1-norm fixed-point iteration as
38
* MATLAB, stopped at 1e-10; the tolerance is a double constant converted into
39
* T, so a Real<D> instantiation iterates to the same point, not further. That
40
* is deliberate: matching MATLAB is the contract, and the Laplace error
41
* dominates the fixed-point residual by many orders of magnitude anyway.
42
*/
43
44
#include <cmath>
45
#include <cstddef>
46
#include <vector>
47
48
#include "
line/api/pfqn/pfqn_asympt_common.h
"
49
#include "
line/lang/lang_types.h
"
50
#include "
line/num/number.h
"
51
#include "
line/util/error.h
"
52
#include "
line/util/matrix.h
"
53
54
namespace
line
{
55
namespace
pfqn
{
56
57
/** Mode of the logistic-transformed integrand, Z = 0 case (pfqn_le_fpi). */
58
template
<
class
T>
59
std::vector<T>
pfqn_le_fpi
(
const
Matrix<T>
& L,
const
std::vector<T>& N) {
60
static_assert
(
num_traits<T>::has_transcendental
,
"pfqn_le_fpi requires transcendental arithmetic"
);
61
const
std::size_t M = L.
rows
(), R = L.
cols
();
62
if
(N.size() != R)
throw
InputError
(
"pfqn_le_fpi: L and N disagree on the class count"
);
63
T Ntot =
num_traits<T>::from_int
(0);
64
for
(
const
T& v : N) Ntot += v;
65
const
T eta = T(Ntot +
num_traits<T>::from_int
(
static_cast<
long
>
(M)));
66
std::vector<T> u(M, T(
num_traits<T>::from_int
(1) /
num_traits<T>::from_int
(
static_cast<
long
>
(M))));
67
std::vector<T> u1(M);
68
const
T tol =
num_traits<T>::from_double
(1e-10);
69
for
(
int
it = 0; it < 100000; ++it) {
70
u1 = u;
71
std::vector<T> uL(R,
num_traits<T>::from_int
(0));
72
for
(std::size_t r = 0; r < R; ++r)
73
for
(std::size_t i = 0; i < M; ++i) uL[r] += u1[i] * L(i, r);
74
for
(std::size_t i = 0; i < M; ++i) {
75
T ui = T(
num_traits<T>::from_int
(1) / eta);
76
for
(std::size_t r = 0; r < R; ++r) {
77
if
(uL[r] ==
num_traits<T>::from_int
(0))
continue
;
78
ui += T(N[r] / eta) * L(i, r) * u1[i] / uL[r];
79
}
80
u[i] = ui;
81
}
82
T d =
num_traits<T>::from_int
(0);
83
for
(std::size_t i = 0; i < M; ++i) d +=
num_abs
(T(u[i] - u1[i]));
84
if
(d <= tol)
break
;
85
}
86
return
u;
87
}
88
89
/** Mode of the logistic-transformed integrand, Z > 0 case (pfqn_le_fpiZ). */
90
template
<
class
T>
91
void
pfqn_le_fpiZ
(
const
Matrix<T>
& L,
const
std::vector<T>& N,
const
std::vector<T>& Z,
92
std::vector<T>& u, T& v) {
93
static_assert
(
num_traits<T>::has_transcendental
,
"pfqn_le_fpiZ requires transcendental arithmetic"
);
94
const
std::size_t M = L.
rows
(), R = L.
cols
();
95
if
(N.size() != R || Z.size() != R)
96
throw
InputError
(
"pfqn_le_fpiZ: L, N and Z disagree on the class count"
);
97
T Ntot =
num_traits<T>::from_int
(0);
98
for
(
const
T& x : N) Ntot += x;
99
const
T eta = T(Ntot +
num_traits<T>::from_int
(
static_cast<
long
>
(M)));
100
u.assign(M, T(
num_traits<T>::from_int
(1) /
num_traits<T>::from_int
(
static_cast<
long
>
(M))));
101
// Note: eq. (35) in the SIGMETRICS 2017 paper has a spurious +1 in the v
102
// equation; the correct stationary point is v = eta - sum_r xi_r*Z_r.
103
v = eta;
104
const
T tol =
num_traits<T>::from_double
(1e-10);
105
std::vector<T> u1(M);
106
for
(
int
it = 0; it < 100000; ++it) {
107
u1 = u;
108
std::vector<T> uL(R,
num_traits<T>::from_int
(0));
109
for
(std::size_t r = 0; r < R; ++r)
110
for
(std::size_t i = 0; i < M; ++i) uL[r] += u1[i] * L(i, r);
111
for
(std::size_t i = 0; i < M; ++i) {
112
T ui = T(
num_traits<T>::from_int
(1) / eta);
113
for
(std::size_t r = 0; r < R; ++r) {
114
const
T den = T(Z[r] + v * uL[r]);
115
if
(den ==
num_traits<T>::from_int
(0))
continue
;
116
ui += T(N[r] / eta) * T(Z[r] + v * L(i, r)) * u1[i] / den;
117
}
118
u[i] = ui;
119
}
120
T vnew = eta;
121
for
(std::size_t r = 0; r < R; ++r) {
122
const
T den = T(Z[r] + v * uL[r]);
123
if
(den ==
num_traits<T>::from_int
(0))
continue
;
124
vnew -= T(N[r] / den) * Z[r];
125
}
126
T d =
num_traits<T>::from_int
(0);
127
for
(std::size_t i = 0; i < M; ++i) d +=
num_abs
(T(u[i] - u1[i]));
128
const
T dv =
num_abs
(T(vnew - v));
129
v = vnew;
130
if
(T(d + dv) <= tol)
break
;
131
}
132
}
133
134
/** Hessian of the Z = 0 logistic integrand at the mode ((M-1) x (M-1)). */
135
template
<
class
T>
136
Matrix<T>
pfqn_le_hessian
(
const
Matrix<T>
& L,
const
std::vector<T>& N,
const
std::vector<T>& u0) {
137
const
std::size_t M = L.
rows
(), R = L.
cols
();
138
if
(M < 2)
throw
InputError
(
"pfqn_le_hessian: at least two stations are required"
);
139
T Ntot =
num_traits<T>::from_int
(0);
140
for
(
const
T& x : N) Ntot += x;
141
const
T eta = T(Ntot +
num_traits<T>::from_int
(
static_cast<
long
>
(M)));
142
std::vector<T> uL(R,
num_traits<T>::from_int
(0));
143
for
(std::size_t r = 0; r < R; ++r)
144
for
(std::size_t i = 0; i < M; ++i) uL[r] += u0[i] * L(i, r);
145
146
Matrix<T>
H(M - 1, M - 1,
num_traits<T>::from_int
(0));
147
for
(std::size_t i = 0; i + 1 < M; ++i) {
148
for
(std::size_t j = 0; j + 1 < M; ++j) {
149
if
(i != j) {
150
T h = T(-eta * u0[i] * u0[j]);
151
for
(std::size_t r = 0; r < R; ++r)
152
h += N[r] * L(i, r) * L(j, r) * T(u0[i] * u0[j]) / T(uL[r] * uL[r]);
153
H(i, j) = h;
154
}
else
{
155
T rest =
num_traits<T>::from_int
(0);
156
for
(std::size_t k = 0; k < M; ++k)
157
if
(k != i) rest += u0[k];
158
T h = T(eta * u0[i] * rest);
159
for
(std::size_t r = 0; r < R; ++r) {
160
T restL =
num_traits<T>::from_int
(0);
161
for
(std::size_t k = 0; k < M; ++k)
162
if
(k != i) restL += u0[k] * L(k, r);
163
h -= N[r] * L(i, r) * u0[i] * restL / T(uL[r] * uL[r]);
164
}
165
H(i, i) = h;
166
}
167
}
168
}
169
return
H;
170
}
171
172
/** Hessian of the Z > 0 logistic integrand at the mode (M x M). */
173
template
<
class
T>
174
Matrix<T>
pfqn_le_hessianZ
(
const
Matrix<T>
& L,
const
std::vector<T>& N,
const
std::vector<T>& Z,
175
const
std::vector<T>& u,
const
T& v) {
176
const
std::size_t K = L.
rows
(), R = L.
cols
();
177
T Ntot =
num_traits<T>::from_int
(0);
178
for
(
const
T& x : N) Ntot += x;
179
const
T eta = T(Ntot +
num_traits<T>::from_int
(
static_cast<
long
>
(K)));
180
std::vector<T> uL(R,
num_traits<T>::from_int
(0));
181
for
(std::size_t r = 0; r < R; ++r)
182
for
(std::size_t i = 0; i < K; ++i) uL[r] += u[i] * L(i, r);
183
std::vector<T> csi(R);
184
// csi2N is csi[r]*csi[r]/N[r] rewritten as N[r]/c[r]^2. Identical where both are
185
// defined, but 0 rather than 0/0 for an empty class, which oner() makes routine
186
// in the mean-value pipeline of pfqn_nc.
187
std::vector<T> csi2N(R);
188
for
(std::size_t r = 0; r < R; ++r) {
189
const
T c = T(Z[r] + v * uL[r]);
190
csi[r] = T(N[r] / c);
191
csi2N[r] = T(N[r] / T(c * c));
192
}
193
Matrix<T>
Lhat(K, R);
194
for
(std::size_t k = 0; k < K; ++k)
195
for
(std::size_t r = 0; r < R; ++r) Lhat(k, r) = T(Z[r] + v * L(k, r));
196
197
Matrix<T>
A(K, K,
num_traits<T>::from_int
(0));
198
for
(std::size_t i = 0; i < K; ++i)
199
for
(std::size_t j = 0; j < K; ++j) {
200
if
(i == j)
continue
;
201
T a = T(-eta * u[i] * u[j]);
202
for
(std::size_t r = 0; r < R; ++r)
203
a += csi2N[r] * Lhat(i, r) * Lhat(j, r) * T(u[i] * u[j]);
204
A(i, j) = a;
205
}
206
for
(std::size_t i = 0; i < K; ++i) {
207
T s =
num_traits<T>::from_int
(0);
208
for
(std::size_t j = 0; j < K; ++j)
209
if
(j != i) s += A(i, j);
210
A(i, i) = T(-s);
211
}
212
// MATLAB truncates A to (K-1)x(K-1) and then writes row/column K, so the
213
// assembled matrix keeps order K with its last row and column rebuilt.
214
Matrix<T>
B(K, K,
num_traits<T>::from_int
(0));
215
for
(std::size_t i = 0; i + 1 < K; ++i)
216
for
(std::size_t j = 0; j + 1 < K; ++j) B(i, j) = A(i, j);
217
T akk =
num_traits<T>::from_int
(1);
218
for
(std::size_t r = 0; r < R; ++r)
219
akk -= csi2N[r] * Z[r] * uL[r];
220
B(K - 1, K - 1) = T(v * akk);
221
for
(std::size_t i = 0; i + 1 < K; ++i) {
222
T a =
num_traits<T>::from_int
(0);
223
for
(std::size_t r = 0; r < R; ++r)
224
a += v * u[i] * T(csi2N[r] * Lhat(i, r) * uL[r] - csi[r] * L(i, r));
225
B(i, K - 1) = a;
226
B(K - 1, i) = a;
227
}
228
return
B;
229
}
230
231
/** Return value of pfqn_le, mirroring [Gn, lGn]. */
232
template
<
class
T>
233
struct
LeResult
{
234
T
G
;
235
T
lG
;
236
};
237
238
/**
239
* Logistic expansion estimate of the normalizing constant.
240
*
241
* @param L (M x R) demands, @param N (R) population, @param Z (R) think times
242
* (pass an empty vector or all zeros for the Z = 0 branch)
243
*/
244
template
<
class
T>
245
LeResult<T>
pfqn_le
(
const
Matrix<T>
& L,
const
std::vector<T>& N,
const
std::vector<T>& Z) {
246
static_assert
(
num_traits<T>::has_transcendental
,
247
"pfqn_le requires transcendental arithmetic (Laplace approximation of an integral)"
);
248
using
std::exp;
249
using
std::log;
250
using
std::sqrt;
251
const
std::size_t M = L.
rows
(), R = L.
cols
();
252
const
T zero =
num_traits<T>::from_int
(0);
253
LeResult<T>
res;
254
255
T Ntot = zero, Lsum = zero, Zsum = zero;
256
for
(
const
T& x : N) Ntot += x;
257
for
(std::size_t i = 0; i < M; ++i)
258
for
(std::size_t r = 0; r < R; ++r) Lsum += L(i, r);
259
for
(
const
T& x : Z) Zsum += x;
260
261
// Degenerate branch: no queueing stations, the delay carries everything.
262
if
(M == 0 || N.empty() || Ntot == zero ||
263
num_traits<T>::to_double
(Lsum) < 1e-4) {
264
T lG = zero;
265
for
(std::size_t r = 0; r < R && r < N.size(); ++r) {
266
lG -= detail::num_factln<T>(N[r]);
267
if
(!Z.empty() && Z[r] > zero) lG += N[r] * log(Z[r]);
268
}
269
res.
lG
= lG;
270
res.
G
= exp(lG);
271
return
res;
272
}
273
274
const
T twopi =
num_traits<T>::from_double
(6.283185307179586476925286766559);
275
if
(Z.empty() ||
num_traits<T>::to_double
(Zsum) <
lang::GlobalConstants::Zero
) {
276
const
std::vector<T> umax =
pfqn_le_fpi
(L, N);
277
const
Matrix<T>
A =
pfqn_le_hessian
(L, N, umax);
278
T S = zero;
279
for
(std::size_t r = 0; r < R; ++r) {
280
T uL = zero;
281
for
(std::size_t i = 0; i < M; ++i) uL += umax[i] * L(i, r);
282
S += N[r] * log(uL);
283
}
284
// multinomialln([N, M-1]) = factln(sum N + M-1) - sum factln(N) - factln(M-1)
285
T mln = detail::num_factln<T>(T(Ntot +
num_traits<T>::from_int
(
static_cast<
long
>
(M) - 1)));
286
for
(std::size_t r = 0; r < R; ++r) mln -= detail::num_factln<T>(N[r]);
287
mln -= detail::num_factln<T>(
num_traits<T>::from_int
(
static_cast<
long
>
(M) - 1));
288
T lG = T(mln + detail::num_factln<T>(
num_traits<T>::from_int
(
static_cast<
long
>
(M) - 1)));
289
lG +=
num_traits<T>::from_int
(
static_cast<
long
>
(M) - 1) * log(sqrt(twopi));
290
lG -= log(sqrt(detail::pfqn_det(A)));
291
for
(std::size_t i = 0; i < M; ++i) lG += log(umax[i]);
292
lG += S;
293
res.
lG
= lG;
294
res.
G
= exp(lG);
295
return
res;
296
}
297
298
std::vector<T> umax;
299
T vmax = zero;
300
pfqn_le_fpiZ
(L, N, Z, umax, vmax);
301
const
Matrix<T>
A =
pfqn_le_hessianZ
(L, N, Z, umax, vmax);
302
T S = zero;
303
for
(std::size_t r = 0; r < R; ++r) {
304
T uL = zero;
305
for
(std::size_t i = 0; i < M; ++i) uL += umax[i] * L(i, r);
306
S += N[r] * log(T(Z[r] + vmax * uL));
307
}
308
T lG = zero;
309
for
(std::size_t r = 0; r < R; ++r) lG -= detail::num_factln<T>(N[r]);
310
lG -= vmax;
311
lG +=
num_traits<T>::from_int
(
static_cast<
long
>
(M)) * log(vmax);
312
lG +=
num_traits<T>::from_int
(
static_cast<
long
>
(M)) * log(sqrt(twopi));
313
lG -= log(sqrt(detail::pfqn_det(A)));
314
for
(std::size_t i = 0; i < M; ++i) lG += log(umax[i]);
315
lG += S;
316
res.
lG
= lG;
317
res.
G
= exp(lG);
318
return
res;
319
}
320
321
template
<
class
T>
322
LeResult<T>
pfqn_le
(
const
Matrix<T>
& L,
const
std::vector<T>& N) {
323
return
pfqn_le
(L, N, std::vector<T>());
324
}
325
326
}
// namespace pfqn
327
}
// namespace line
328
329
#endif
// LINE_API_PFQN_PFQN_LE_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.
lang_types.h
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
matrix.h
Dense matrix and non-owning view.
line::pfqn
Definition
cd_peak_scaling.h:43
line::pfqn::pfqn_le_fpiZ
void pfqn_le_fpiZ(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, std::vector< T > &u, T &v)
Mode of the logistic-transformed integrand, Z > 0 case (pfqn_le_fpiZ).
Definition
pfqn_le.h:91
line::pfqn::pfqn_le_fpi
std::vector< T > pfqn_le_fpi(const Matrix< T > &L, const std::vector< T > &N)
Mode of the logistic-transformed integrand, Z = 0 case (pfqn_le_fpi).
Definition
pfqn_le.h:59
line::pfqn::pfqn_le_hessianZ
Matrix< T > pfqn_le_hessianZ(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z, const std::vector< T > &u, const T &v)
Hessian of the Z > 0 logistic integrand at the mode (M x M).
Definition
pfqn_le.h:174
line::pfqn::pfqn_le
LeResult< T > pfqn_le(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &Z)
Logistic expansion estimate of the normalizing constant.
Definition
pfqn_le.h:245
line::pfqn::pfqn_le_hessian
Matrix< T > pfqn_le_hessian(const Matrix< T > &L, const std::vector< T > &N, const std::vector< T > &u0)
Hessian of the Z = 0 logistic integrand at the mode ((M-1) x (M-1)).
Definition
pfqn_le.h:136
line
Definition
aoi_dist2ph.h:52
line::num_abs
T num_abs(const T &v)
Definition
number.h:172
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::lang::GlobalConstants::Zero
static constexpr double Zero
Definition
lang_types.h:670
line::num_traits
Definition
number.h:111
line::pfqn::LeResult
Return value of pfqn_le, mirroring [Gn, lGn].
Definition
pfqn_le.h:233
line::pfqn::LeResult::lG
T lG
Definition
pfqn_le.h:235
line::pfqn::LeResult::G
T G
Definition
pfqn_le.h:234
include
line
api
pfqn
pfqn_le.h
Generated by
1.18.0