LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
pfqn_lldfun.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_LLDFUN_H
6
#define LINE_API_PFQN_LLDFUN_H
7
8
/**
9
* @file
10
* @ingroup api_pfqn
11
* AMVA-QD limited-load-dependence function.
12
*
13
* Templated port of matlab/src/api/pfqn/pfqn_lldfun.m, cross-checked against
14
* jar/src/main/java/jline/api/pfqn/ld/Pfqn_lldfun.java. Returns, for every
15
* station i, the reciprocal service-capacity multiplier at the (fractional)
16
* queue length n(i), combining
17
*
18
* - the multiserver term 1/softmin(n_i, c_i, alpha), a smooth approximation
19
* of 1/min(n_i, c_i), with alpha = 20 as in the reference. A delay station
20
* (c_i infinite) contributes 1; the caller handles the 1/n_i itself.
21
* - the generic load-dependence term 1/alpha_i(n_i), obtained by CLAMPED
22
* LINEAR interpolation of the lattice lldscaling(i, 1..smax) at
23
* min(max(n_i,1), smax). Linear is exact for the piecewise-linear
24
* min(1:N, c) lattice that load dependence is overwhelmingly used to
25
* express, whereas a cubic spline overshoots between knots and extrapolates
26
* to negative rates past the lattice. A station whose lattice row is
27
* constant is skipped, exactly as `range(lldscaling(i,:)) > 0` does.
28
*
29
* softmin. The reference rewrites the literal weighted average
30
* (x e^{-ax} + y e^{-ay})/(e^{-ax} + e^{-ay}) as lo + gap w/(1+w) with
31
* w = e^{-a gap}, so the exponent argument is never positive and the limit
32
* w -> 0 returns min(x,y) exactly; that form is reproduced here, including its
33
* cutoff at a*gap > 745 where exp underflows to zero in double.
34
*
35
* Arithmetic: TRANSCENDENTAL-GATED, and here on a genuine transcendental, not
36
* on a tolerance: softmin evaluates exp(-alpha |n_i - c_i|), which is not an
37
* element of the field generated by the inputs for any alpha and gap of
38
* interest. There is no exact formulation of a soft minimum.
39
*/
40
41
#include <cmath>
42
#include <cstddef>
43
#include <limits>
44
#include <vector>
45
46
#include "
line/num/number.h
"
47
#include "
line/util/error.h
"
48
#include "
line/util/matrix.h
"
49
50
namespace
line
{
51
namespace
pfqn
{
52
53
namespace
detail {
54
55
/** matlab/src/util/softmin.m, in the overflow-free form. */
56
template
<
class
T>
57
T softmin(
const
T& x,
const
T& y,
double
alpha) {
58
const
T lo = x < y ? x : y;
59
const
T hi = x < y ? y : x;
60
const
T gap = hi - lo;
61
// exp(-t) underflows to exactly 0 for t > 745.13; beyond that softmin is min.
62
if
(!(num_traits<T>::to_double(gap) < 745.0 / alpha))
return
lo;
63
using
std::exp;
64
const
T w = exp(T(-num_traits<T>::from_double(alpha) * gap));
65
return
lo + gap * w / (num_traits<T>::from_int(1) + w);
66
}
67
68
}
// namespace detail
69
70
/**
71
* @brief AMVA-QD limited-load-dependence function.
72
*
73
* @param n (M) queue lengths, possibly fractional
74
* @param lldscaling (M x smax) rate lattice; empty for none
75
* @param nservers (M) server counts; a non-finite entry marks a delay
76
* station. Empty to skip the multiserver term entirely,
77
* which is what omitting the argument does in MATLAB.
78
* @return (M) reciprocals of the effective rate multipliers
79
*/
80
template
<
class
T>
81
std::vector<T>
pfqn_lldfun
(
const
std::vector<T>& n,
const
Matrix<T>
& lldscaling,
82
const
std::vector<double>& nservers) {
83
static_assert
(
num_traits<T>::has_transcendental
,
84
"pfqn_lldfun requires transcendental arithmetic"
);
85
86
const
std::size_t M = n.size();
87
const
T one =
num_traits<T>::from_int
(1);
88
std::vector<T> r(M, one);
89
if
(!nservers.empty() && nservers.size() != M)
90
throw
InputError
(
"pfqn_lldfun: server-count vector has the wrong station count"
);
91
if
(!lldscaling.
empty
() && lldscaling.
rows
() != M)
92
throw
InputError
(
"pfqn_lldfun: rate lattice has the wrong station count"
);
93
const
std::size_t smax = lldscaling.
empty
() ? 0 : lldscaling.
cols
();
94
const
double
alpha = 20.0;
95
96
for
(std::size_t i = 0; i < M; ++i) {
97
if
(!nservers.empty()) {
98
if
(!std::isfinite(nservers[i])) {
99
// Delay station: the 1/n_i is applied by the caller.
100
r[i] = one;
101
}
else
{
102
const
T c =
num_traits<T>::from_double
(nservers[i]);
103
const
T sm = detail::softmin(n[i], c, alpha);
104
if
(sm ==
num_traits<T>::from_int
(0)) {
105
// The soft minimum collapses only when both arguments do.
106
const
T m = n[i] < c ? n[i] : c;
107
if
(m ==
num_traits<T>::from_int
(0))
108
throw
NumericError
(
"pfqn_lldfun: zero queue length and zero server count"
);
109
r[i] = r[i] / m;
110
}
else
{
111
r[i] = r[i] / sm;
112
}
113
}
114
}
115
if
(smax > 0) {
116
T lo = lldscaling(i, 0), hi = lldscaling(i, 0);
117
for
(std::size_t k = 1; k < smax; ++k) {
118
if
(lldscaling(i, k) < lo) lo = lldscaling(i, k);
119
if
(lldscaling(i, k) > hi) hi = lldscaling(i, k);
120
}
121
// A CONSTANT ROW IS A NO-OP ONLY WHEN IT IS ONE. The test here used to be
122
// `hi > lo`, which skipped alpha(n) = c for EVERY c: a station declaring a
123
// uniform rate multiplier was then solved at its UNSCALED rate, while the
124
// exact recursions (solver_mvald, pfqn_mvaldmx) and SolverCTMC applied it, so
125
// AMVA contradicted them on the same model -- silently, and by the whole
126
// factor c. Only a row of ones divides by 1.
127
if
(hi != one || lo != one) {
128
if
(smax == 1) {
129
// One column IS the constant alpha(n): nothing to interpolate, and the
130
// two-knot arithmetic below has no second knot to read.
131
if
(lldscaling(i, 0) ==
num_traits<T>::from_int
(0))
132
throw
NumericError
(
"pfqn_lldfun: the rate multiplier is zero"
);
133
r[i] = r[i] / lldscaling(i, 0);
134
continue
;
135
}
136
// Clamp into [1, smax] and interpolate linearly between knots.
137
T x = n[i];
138
const
T xlo = one, xhi =
num_traits<T>::from_int
(
static_cast<
long
>
(smax));
139
if
(x < xlo) x = xlo;
140
if
(x > xhi) x = xhi;
141
const
double
xd =
num_traits<T>::to_double
(x);
142
std::size_t k0 =
static_cast<
std::size_t
>
(xd) - 1;
143
if
(k0 + 1 >= smax) k0 = smax - 2;
144
const
T t = x -
num_traits<T>::from_int
(
static_cast<
long
>
(k0) + 1);
145
const
T val =
146
lldscaling(i, k0) + t * (lldscaling(i, k0 + 1) - lldscaling(i, k0));
147
if
(val ==
num_traits<T>::from_int
(0))
148
throw
NumericError
(
"pfqn_lldfun: the interpolated rate multiplier is zero"
);
149
r[i] = r[i] / val;
150
}
151
}
152
}
153
return
r;
154
}
155
156
/** Overload without the multiserver term, matching the two-argument MATLAB call. */
157
template
<
class
T>
158
std::vector<T>
pfqn_lldfun
(
const
std::vector<T>& n,
const
Matrix<T>
& lldscaling) {
159
return
pfqn_lldfun
(n, lldscaling, std::vector<double>());
160
}
161
162
}
// namespace pfqn
163
}
// namespace line
164
165
#endif
// LINE_API_PFQN_LLDFUN_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
line::Matrix::empty
bool empty() const
Definition
matrix.h:92
line::NumericError::NumericError
NumericError(const std::string &what)
Definition
error.h:45
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::pfqn_lldfun
std::vector< T > pfqn_lldfun(const std::vector< T > &n, const Matrix< T > &lldscaling, const std::vector< double > &nservers)
AMVA-QD limited-load-dependence function.
Definition
pfqn_lldfun.h:81
line
Conservation laws of a layered queueing network, enumerated from its structure.
Definition
aoi_dist2ph.h:52
number.h
Number-type abstraction for the templated API port.
line::num_traits
Definition
number.h:137
include
line
api
pfqn
pfqn_lldfun.h
Generated by
1.18.0