LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
hyperexp_fit_longtail.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_MAM_HYPEREXP_FIT_LONGTAIL_H
6
#define LINE_API_MAM_HYPEREXP_FIT_LONGTAIL_H
7
8
/**
9
* @file
10
* @ingroup api_mam
11
* Fitting a hyperexponential to a long-tail distribution.
12
*
13
* Templated port of matlab/src/api/mam/hyperexp_fit_longtail.m, cross-checked
14
* against jar/src/main/java/jline/api/mam/HyperexpFitLongtail.java.
15
*
16
* WHY MOMENTS ARE THE WRONG HANDLE. A Pareto law with tail index below 2 has
17
* infinite variance, so no two- or three-moment fit exists at all; and even when
18
* the moments are finite, matching them says nothing about the several ORDERS OF
19
* MAGNITUDE of time scale over which a long-tail law acts. This procedure
20
* matches the CCDF ITSELF at points spread across those decades.
21
*
22
* THE RECURSION, with lambda_1 < ... < lambda_k. In the far tail only the
23
* slowest component survives, so it can be fitted there alone:
24
*
25
* lambda_1 = ln(F^c(c_1)/F^c(b c_1))/((b-1)c_1) (4.4)
26
* p_1 = F^c(c_1) exp(lambda_1 c_1) (4.5)
27
*
28
* subtract it and repeat one decade lower (4.6)-(4.11); the last component takes
29
* the remaining probability, p_k = 1 - sum_{j<k} p_j, and its rate follows from
30
* the ccdf at c_k (4.12)-(4.14). This is Prony's method applied to a ccdf.
31
*
32
* DEFAULTS. (b, decade) = (1.5, 4) rather than the paper's illustrative (2, 10):
33
* the fit is exact AT the fitting arguments and free between them, and measured
34
* on a Weibull(0.3) the tighter grid cuts the worst between-point error from
35
* about 54% to 12%, at the cost of more components.
36
*
37
* ARITHMETIC. Logarithms and exponentials throughout: transcendental only.
38
*
39
* Reference: A. Feldmann, W. Whitt (1998). Fitting mixtures of exponentials to
40
* long-tail distributions to analyze network performance models. Performance
41
* Evaluation 31, 245-279, Section 4.
42
*/
43
44
#include <cmath>
45
#include <cstddef>
46
#include <functional>
47
#include <vector>
48
49
#include "
line/num/number.h
"
50
#include "
line/util/error.h
"
51
52
namespace
line
{
53
namespace
mam
{
54
55
/** Outcome of the long-tail hyperexponential fit. */
56
template
<
class
T>
57
struct
HyperexpLongtailResult
{
58
std::vector<T>
p
;
///< mixing probabilities, summing to 1
59
std::vector<T>
lambda
;
///< rates, increasing
60
std::vector<T>
points
;
///< the fitting arguments c_i
61
T
mean
;
///< mean of the fitted law
62
T
targetMean
;
///< mean of the original law over the covered range
63
T
coverageLow
;
///< c_k, the smallest constrained argument
64
T
coverageHigh
;
///< b c_1, the largest
65
T
maxRelError
;
///< worst relative error at the fitting arguments
66
T
maxRelErrorGrid
;
///< worst relative error on a log grid across the coverage
67
};
68
69
namespace
detail {
70
71
/** Smallest t with F^c(t) <= prob, by doubling then bisection. */
72
template
<
class
T,
class
Ccdf>
73
T hefit_quantile(Ccdf&& ccdf,
const
T& prob) {
74
const
T two =
num_traits<T>::from_int
(2);
75
T hi =
num_traits<T>::from_int
(1);
76
while
(ccdf(hi) > prob) {
77
hi *= two;
78
if
(hi >
num_traits<T>::from_double
(1e15))
79
throw
InputError
(
"hyperexp_fit_longtail: the ccdf does not decay, so there is no tail "
80
"to fit"
);
81
}
82
T lo =
num_traits<T>::from_int
(0);
83
for
(
int
i = 0; i < 200; ++i) {
84
const
T mid = (lo + hi) / two;
85
if
(ccdf(mid) > prob) {
86
lo = mid;
87
}
else
{
88
hi = mid;
89
}
90
}
91
return
(lo + hi) / two;
92
}
93
94
}
// namespace detail
95
96
/**
97
* The recursion at a fixed component count.
98
*
99
* @param ccdf F^c(t) = P(X > t)
100
* @param k number of exponential components
101
* @param c1 the largest fitting argument
102
* @param b the within-scale spacing, 1 < b < decade
103
* @param decade the ratio between successive fitting arguments
104
*/
105
template
<
class
T,
class
Ccdf>
106
HyperexpLongtailResult<T>
hyperexp_fit_longtail_k
(Ccdf&& ccdf, std::size_t k,
const
T& c1,
107
const
T& b,
const
T& decade) {
108
static_assert
(
num_traits<T>::has_transcendental
,
109
"hyperexp_fit_longtail needs logarithms and exponentials"
);
110
using
std::exp;
111
using
std::log;
112
const
T zero =
num_traits<T>::from_int
(0);
113
const
T one =
num_traits<T>::from_int
(1);
114
if
(k < 1)
throw
InputError
(
"hyperexp_fit_longtail: at least one component is required"
);
115
if
(b <= one)
throw
InputError
(
"hyperexp_fit_longtail: the spacing b must exceed 1"
);
116
if
(decade <= b)
117
throw
InputError
(
"hyperexp_fit_longtail: the decade ratio must exceed the spacing b, or "
118
"the fitting arguments would interleave"
);
119
120
std::vector<T> cs(k);
121
cs[0] = c1;
122
for
(std::size_t i = 1; i < k; ++i) cs[i] = cs[i - 1] / decade;
123
124
std::vector<T> p(k, zero), lam(k, zero);
125
for
(std::size_t i = 0; i < k; ++i) {
126
const
T ci = cs[i];
127
// Eqs. (4.6)-(4.7): what the already-fitted, slower components leave.
128
T residC = ccdf(ci), residBC = ccdf(T(b * ci));
129
for
(std::size_t j = 0; j < i; ++j) {
130
residC -= p[j] * exp(-lam[j] * ci);
131
residBC -= p[j] * exp(-lam[j] * b * ci);
132
}
133
if
(i + 1 < k) {
134
if
(residC <= zero || residBC <= zero || residC <= residBC)
135
throw
InputError
(
"hyperexp_fit_longtail: the residual ccdf is not positive and "
136
"decreasing at a fitting argument. The recursion needs the "
137
"arguments well separated, c_i/c_(i+1) >> b; widen decade, lower "
138
"k, or move c1 further into the tail"
);
139
lam[i] = log(residC / residBC) / ((b - one) * ci);
// eq. (4.10)
140
p[i] = residC * exp(lam[i] * ci);
// eq. (4.11)
141
}
else
{
142
// Eqs. (4.12)-(4.14): the last component takes the rest of the mass.
143
T rest = one;
144
for
(std::size_t j = 0; j < i; ++j) rest -= p[j];
145
if
(rest <= zero)
146
throw
InputError
(
"hyperexp_fit_longtail: the fitted components already carry all "
147
"the probability, so the last one has none left"
);
148
if
(residC <= zero)
149
throw
InputError
(
"hyperexp_fit_longtail: the residual ccdf has gone non-positive "
150
"at the last fitting argument"
);
151
p[i] = rest;
152
lam[i] = log(p[i] / residC) / ci;
// eq. (4.14)
153
}
154
if
(lam[i] <= zero)
155
throw
InputError
(
"hyperexp_fit_longtail: a non-positive rate came out of the fit; the "
156
"ccdf is not decaying fast enough for this many components"
);
157
}
158
159
auto
fitted = [&](
const
T& t) {
160
T v = zero;
161
for
(std::size_t j = 0; j < k; ++j) v += p[j] * exp(-lam[j] * t);
162
return
v;
163
};
164
HyperexpLongtailResult<T>
r;
165
r.
p
= p;
166
r.
lambda
= lam;
167
r.
points
= cs;
168
r.
mean
= zero;
169
for
(std::size_t j = 0; j < k; ++j) r.
mean
+= p[j] / lam[j];
170
r.
coverageLow
= cs[k - 1];
171
r.
coverageHigh
= cs[0] * b;
172
// The target mean over the covered range, by the trapezoid rule: a k too
173
// small to reach the body shows up here and nowhere else.
174
const
std::size_t gn = 20000;
175
const
T h = r.
coverageHigh
/
num_traits<T>::from_int
(
static_cast<
long
>
(gn));
176
T acc = (ccdf(zero) + ccdf(r.
coverageHigh
)) /
num_traits<T>::from_int
(2);
177
for
(std::size_t i = 1; i < gn; ++i)
178
acc += ccdf(T(
num_traits<T>::from_int
(
static_cast<
long
>
(i)) * h));
179
r.
targetMean
= acc * h;
180
r.
maxRelError
= zero;
181
for
(std::size_t i = 0; i < k; ++i) {
182
for
(
int
j = 0; j < 2; ++j) {
183
const
T t = j == 0 ? cs[i] : T(b * cs[i]);
184
const
T target = ccdf(t);
185
if
(target > zero) {
186
const
T e =
num_abs
(T(fitted(t) - target)) / target;
187
if
(e > r.
maxRelError
) r.
maxRelError
= e;
188
}
189
}
190
}
191
// The fit is exact at the fitting arguments by construction; this says
192
// whether it also holds BETWEEN them.
193
r.
maxRelErrorGrid
= zero;
194
const
T loLog = log(r.
coverageLow
), hiLog = log(r.
coverageHigh
);
195
for
(
int
i = 0; i < 200; ++i) {
196
const
T t = exp(loLog + (hiLog - loLog) *
num_traits<T>::from_rational
(i, 199));
197
const
T target = ccdf(t);
198
if
(target >
num_traits<T>::from_double
(1e-300)) {
199
const
T e =
num_abs
(T(fitted(t) - target)) / target;
200
if
(e > r.
maxRelErrorGrid
) r.
maxRelErrorGrid
= e;
201
}
202
}
203
return
r;
204
}
205
206
/**
207
* The fit with the component count chosen automatically: one per decade between
208
* the 0.9 quantile and the 1e-6 quantile, retrying with fewer when the
209
* recursion runs out of probability near the body.
210
*
211
* @param ccdf F^c(t) = P(X > t)
212
* @param b the within-scale spacing
213
* @param decade the ratio between successive fitting arguments
214
*/
215
template
<
class
T,
class
Ccdf>
216
HyperexpLongtailResult<T>
hyperexp_fit_longtail
(Ccdf&& ccdf,
217
const
T& b =
num_traits<T>::from_rational
(3, 2),
218
const
T& decade =
num_traits<T>::from_int
(4)) {
219
using
std::log;
220
const
T top = detail::hefit_quantile<T>(ccdf,
num_traits<T>::from_double
(1e-6));
221
const
T body = detail::hefit_quantile<T>(ccdf,
num_traits<T>::from_rational
(9, 10));
222
if
(body <=
num_traits<T>::from_int
(0) || top <= body)
223
throw
InputError
(
"hyperexp_fit_longtail: the ccdf gives no usable range of time scales"
);
224
long
k0 =
static_cast<
long
>
(std::llround(
num_traits<T>::to_double
(T(log(top / body) / log(decade))))) + 1;
225
if
(k0 < 2) k0 = 2;
226
// The recursion needs each component to dominate at its own scale. Near the
227
// body of a law with a lot of mass there (a Pareto, say) that fails and the
228
// remaining probability runs out; back off one component at a time.
229
for
(
long
k = k0; k >= 2; --k) {
230
try
{
231
return
hyperexp_fit_longtail_k<T>
(ccdf,
static_cast<
std::size_t
>
(k), top, b, decade);
232
}
catch
(
const
InputError
&) {
233
continue
;
234
}
235
}
236
throw
InputError
(
"hyperexp_fit_longtail: no component count admits the recursion; the ccdf may "
237
"not be long-tailed enough for this scheme"
);
238
}
239
240
}
// namespace mam
241
}
// namespace line
242
243
#endif
// LINE_API_MAM_HYPEREXP_FIT_LONGTAIL_H
line::InputError
Malformed or inconsistent input (dimensions, negative populations, ...).
Definition
error.h:37
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
error.h
The exception types the port throws.
line::mam
Definition
amap2_adjust_gamma.h:78
line::mam::hyperexp_fit_longtail_k
HyperexpLongtailResult< T > hyperexp_fit_longtail_k(Ccdf &&ccdf, std::size_t k, const T &c1, const T &b, const T &decade)
The recursion at a fixed component count.
Definition
hyperexp_fit_longtail.h:106
line::mam::hyperexp_fit_longtail
HyperexpLongtailResult< T > hyperexp_fit_longtail(Ccdf &&ccdf, const T &b=num_traits< T >::from_rational(3, 2), const T &decade=num_traits< T >::from_int(4))
The fit with the component count chosen automatically: one per decade between the 0....
Definition
hyperexp_fit_longtail.h:216
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.
line::mam::HyperexpLongtailResult
Outcome of the long-tail hyperexponential fit.
Definition
hyperexp_fit_longtail.h:57
line::mam::HyperexpLongtailResult::mean
T mean
mean of the fitted law
Definition
hyperexp_fit_longtail.h:61
line::mam::HyperexpLongtailResult::lambda
std::vector< T > lambda
rates, increasing
Definition
hyperexp_fit_longtail.h:59
line::mam::HyperexpLongtailResult::coverageLow
T coverageLow
c_k, the smallest constrained argument
Definition
hyperexp_fit_longtail.h:63
line::mam::HyperexpLongtailResult::coverageHigh
T coverageHigh
b c_1, the largest
Definition
hyperexp_fit_longtail.h:64
line::mam::HyperexpLongtailResult::points
std::vector< T > points
the fitting arguments c_i
Definition
hyperexp_fit_longtail.h:60
line::mam::HyperexpLongtailResult::maxRelError
T maxRelError
worst relative error at the fitting arguments
Definition
hyperexp_fit_longtail.h:65
line::mam::HyperexpLongtailResult::p
std::vector< T > p
mixing probabilities, summing to 1
Definition
hyperexp_fit_longtail.h:58
line::mam::HyperexpLongtailResult::maxRelErrorGrid
T maxRelErrorGrid
worst relative error on a log grid across the coverage
Definition
hyperexp_fit_longtail.h:66
line::mam::HyperexpLongtailResult::targetMean
T targetMean
mean of the original law over the covered range
Definition
hyperexp_fit_longtail.h:62
line::num_traits
Definition
number.h:111
include
line
api
mam
hyperexp_fit_longtail.h
Generated by
1.18.0