LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
map_anfit.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_MAP_ANFIT_H
6#define LINE_API_MAM_MAP_ANFIT_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Fit a superposition of interrupted Poisson processes to a Hurst parameter.
12 *
13 * Templated port of matlab/lib/kpctoolbox/map/map_anfit.m (Andersen-Nielsen).
14 * The construction builds d two-state IPPs whose switching rates form a
15 * geometric ladder k(2,i) = a^(1-i) k(2,1) with a = 10^(n/(d-1)), and whose
16 * weights phi(i) are chosen so that the aggregate variance-time curve follows
17 * t^beta with beta = 2 - 2H over n decades. Superposing them with an optional
18 * Poisson remainder gives a MAP whose autocorrelation decays like a fractional
19 * process over that range, which is what makes it a long-range-dependence
20 * surrogate rather than a moment fit.
21 *
22 * The phi ladder is built by the reference's backward recursion
23 * D = a^(i beta) - sum_{j<i} phi(d-j)^2 exp(1 - a^(i-j)),
24 * phi(d-i) = sqrt(D) when D >= 0, else 0 and the depth d is grown by one,
25 * which is a greedy fill: a level that cannot carry positive variance is set to
26 * zero and the ladder is extended instead of failing.
27 *
28 * REFERENCE INCONSISTENCY, resolved in favour of the least-squares branch.
29 * `map_anfit.m` builds the Poisson remainder as `map_exponential(lP)`, and the
30 * global `map_exponential` takes a MEAN, whereas the file's own `objfun` builds
31 * the SAME component as `{[-lP], [lP]}`, i.e. with lP as a RATE. The two cannot
32 * both be right. The rate reading is taken here, for two reasons: `objfun` is
33 * unambiguous, and lP = 0 -- which the `ls < L` branch produces deliberately to
34 * mean "no Poisson remainder" -- is the null stream under the rate reading and a
35 * division by zero under the mean reading.
36 *
37 * THE LEAST-SQUARES BRANCH replaces MATLAB's `fmincon` with
38 * `line/util/auglag.h`, whose header carries the acceptance contract for that
39 * substitution. It minimizes the 2-norm between the fitted autocorrelation and
40 * the supplied one over the per-IPP ratios r(i), subject to the reference's
41 * feasibility constraint ls(i) >= sqrt(k1(i) k2(i) / r(i)), which is what keeps
42 * the second IPP rate non-negative.
43 *
44 * ARITHMETIC: transcendental.
45 */
46
47#include <cmath>
48#include <cstddef>
49#include <vector>
50
54#include "line/num/number.h"
55#include "line/util/auglag.h"
56#include "line/util/error.h"
57#include "line/util/matrix.h"
58
59namespace line {
60namespace mam {
61
62namespace anfitdetail {
63
64/** Superposition of two MAPs: the Kronecker sum of each block. */
65template <class T>
66Map<T> super2(const Map<T>& a, const Map<T>& b) {
67 Map<T> m;
68 m.D0 = krons(a.D0, b.D0);
69 m.D1 = krons(a.D1, b.D1);
70 return map_normalize(m);
71}
72
73/** One interrupted Poisson process: on/off switching c1, c2 and rate l. */
74template <class T>
75Map<T> ipp(const T& c1, const T& c2, const T& l) {
76 const T zero = num_traits<T>::from_int(0);
77 Map<T> m;
78 m.D0 = Matrix<T>(2, 2, zero);
79 m.D1 = Matrix<T>(2, 2, zero);
80 m.D0(0, 1) = c1;
81 m.D0(1, 0) = c2;
82 m.D1(0, 0) = l;
83 return map_normalize(m);
84}
85
86/** The ladder weights phi(1..d), growing d when a level cannot be filled. */
87template <class T>
88std::vector<double> phi_ladder(double beta, double n, std::size_t ds, std::size_t* dOut,
89 double* aOut) {
90 std::size_t d = ds;
91 for (std::size_t guard = 0; guard < 64; ++guard) {
92 const double a = std::pow(10.0, n / static_cast<double>(d - 1));
93 std::vector<double> phi(d + 1, 0.0); // 1-based, as the reference indexes it
94 phi[d] = 1.0;
95 bool grew = false;
96 for (std::size_t i = 1; i < d; ++i) {
97 double S = 0.0;
98 for (std::size_t j = 0; j < i; ++j)
99 S += phi[d - j] * phi[d - j] *
100 std::exp(1.0 - std::pow(a, static_cast<double>(i - j)));
101 const double D = std::pow(a, static_cast<double>(i) * beta) - S;
102 if (D < 0.0) {
103 phi[d - i] = 0.0;
104 // The reference grows the ladder while it still has room to.
105 if (ds > d - 1) {
106 ++d;
107 grew = true;
108 break;
109 }
110 } else {
111 phi[d - i] = std::sqrt(D);
112 }
113 }
114 if (grew) continue;
115 *dOut = d;
116 *aOut = a;
117 std::vector<double> out(d + 1, 0.0);
118 for (std::size_t i = 1; i <= d; ++i) out[i] = phi[i];
119 return out;
120 }
121 throw NumericError("map_anfit: the phi ladder did not terminate");
122}
123
124} // namespace anfitdetail
125
126/** What map_anfit returns: the fitted MAP and the ladder it was built on. */
127template <class T>
130 std::size_t d = 0; ///< number of IPPs actually used
131 std::vector<T> switching; ///< k(2,i), the switching-rate ladder
132 std::vector<T> rates; ///< l(i), the on-state arrival rates
133 T poisson_rate = num_traits<T>::from_int(0); ///< lP, the Poisson remainder
134};
135
136/**
137 * @brief Fit a superposition of interrupted Poisson processes to a Hurst
138 * parameter.
139 *
140 * @param ls target arrival rate
141 * @param rho target burstiness parameter, below 1/2 for the construction to hold
142 * @param H Hurst parameter; beta = 2 - 2H
143 * @param n number of decades the variance-time curve should follow t^beta
144 * @param ds starting number of IPPs; grown when a ladder level cannot be filled
145 */
146template <class T>
147MapAnfitResult<T> map_anfit(const T& ls, const T& rho, const T& H, double n, std::size_t ds) {
148 static_assert(num_traits<T>::has_transcendental, "map_anfit builds a geometric rate ladder");
149 if (ds < 2) throw InputError("map_anfit: at least two IPPs are required");
150 const double lsd = num_traits<T>::to_double(ls), rhod = num_traits<T>::to_double(rho);
151 if (!(lsd > 0.0)) throw InputError("map_anfit: the arrival rate must be positive");
152 const double beta = 2.0 - 2.0 * num_traits<T>::to_double(H);
153
154 std::size_t d = ds;
155 double a = 0.0;
156 const std::vector<double> phi = anfitdetail::phi_ladder<T>(beta, n, ds, &d, &a);
157
158 const double k21 = 0.8;
159 std::vector<double> k2(d + 1, 0.0);
160 for (std::size_t i = 1; i <= d; ++i) k2[i] = std::pow(a, 1.0 - static_cast<double>(i)) * k21;
161
162 double S = 0.0, phisum = 0.0;
163 for (std::size_t i = 1; i <= d; ++i) {
164 const double kappa = k2[i], e = std::exp(-kappa);
165 S += phi[i] * phi[i] / (kappa * kappa) *
166 ((1.0 - e) * (1.0 - e) - 2.0 * rhod * (kappa - (1.0 - e)));
167 phisum += phi[i];
168 }
169 if (!(S > 0.0))
170 throw NumericError(
171 "map_anfit: the ladder carries no variance at this (rho, H, n); the construction has "
172 "no interrupted-Poisson superposition for these targets");
173 const double eta = std::sqrt(4.0 * rhod * lsd) / std::sqrt(S);
174 const double L = eta * phisum / 2.0;
175
176 std::vector<double> c1(d + 1, 0.0), c2v(d + 1, 0.0), l(d + 1, 0.0);
177 double lP = 0.0;
178 if (lsd < L) {
179 for (std::size_t i = 1; i <= d; ++i) {
180 c1[i] = L * L / (lsd * lsd + L * L) * k2[i];
181 c2v[i] = k2[i] - c1[i];
182 l[i] = phi[i] * (lsd * lsd + L * L) / (lsd * phisum);
183 }
184 } else {
185 lP = lsd - L;
186 for (std::size_t i = 1; i <= d; ++i) {
187 c2v[i] = 0.5 * k2[i];
188 c1[i] = c2v[i];
189 l[i] = eta * phi[i];
190 }
191 }
192
193 // The Poisson remainder, as a RATE; see the header note. lP = 0 gives the
194 // null stream, which is the identity for superposition.
195 Map<T> acc;
196 acc.D0 = Matrix<T>(1, 1, num_traits<T>::from_double(-lP));
197 acc.D1 = Matrix<T>(1, 1, num_traits<T>::from_double(lP));
198 for (std::size_t i = 1; i <= d; ++i)
199 acc = anfitdetail::super2(acc, anfitdetail::ipp(num_traits<T>::from_double(c1[i]),
202
204 out.map = map_normalize(acc);
205 out.d = d;
207 for (std::size_t i = 1; i <= d; ++i) {
208 out.switching.push_back(num_traits<T>::from_double(k2[i]));
209 out.rates.push_back(num_traits<T>::from_double(l[i]));
210 }
211 return out;
212}
213
214/**
215 * The least-squares variant: after the deterministic construction, the per-IPP
216 * ratios are tuned so the fitted autocorrelation matches a supplied one.
217 *
218 * @param SA target autocorrelation values
219 * @param SAlags the lags they were measured at
220 */
221template <class T>
222MapAnfitResult<T> map_anfit_lsq(const T& ls, const T& rho, const T& H, double n, std::size_t ds,
223 const std::vector<T>& SA, const std::vector<unsigned>& SAlags,
224 unsigned iter_max = 100) {
225 static_assert(num_traits<T>::has_transcendental, "map_anfit_lsq minimizes over the ladder");
226 if (SA.size() != SAlags.size())
227 throw InputError("map_anfit_lsq: the autocorrelation values and lags must agree in length");
228 if (SA.empty()) throw InputError("map_anfit_lsq: no autocorrelation targets given");
229
230 MapAnfitResult<T> base = map_anfit(ls, rho, H, n, ds);
231 const std::size_t d = base.d;
232 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
233
234 // The reference's per-IPP characteristics, from the deterministic fit.
235 std::vector<double> k1(d, 0.0), k2(d, 0.0), lsi(d, 0.0);
236 for (std::size_t i = 0; i < d; ++i) {
237 const double li = num_traits<T>::to_double(base.rates[i]);
238 const double kk = num_traits<T>::to_double(base.switching[i]);
239 // c1 = c2 = kk/2 in the branch the reference derives these from.
240 const double c1 = 0.5 * kk, c2 = 0.5 * kk;
241 k1[i] = li * li * (c1 * c2) / std::pow(c1 + c2, 3.0);
242 k2[i] = kk;
243 lsi[i] = (c2 * li) / (c1 + c2);
244 }
245
246 auto build = [&](const std::vector<T>& r) {
247 Map<T> acc;
248 acc.D0 = Matrix<T>(1, 1, T(-base.poisson_rate));
249 acc.D1 = Matrix<T>(1, 1, base.poisson_rate);
250 for (std::size_t i = 0; i < d; ++i) {
251 const double ri = num_traits<T>::to_double(r[i]);
252 if (!(ri > 0.0)) throw NumericError("map_anfit_lsq: a ratio left the feasible region");
253 Map<T> m;
254 m.D0 = Matrix<T>(2, 2, zero);
255 m.D1 = Matrix<T>(2, 2, zero);
256 m.D0(0, 1) = num_traits<T>::from_double(k2[i] * ri / (1.0 + ri));
257 m.D0(1, 0) = num_traits<T>::from_double(k2[i] / (1.0 + ri));
258 m.D1(0, 0) = num_traits<T>::from_double(lsi[i] + std::sqrt(k1[i] * k2[i] * ri));
259 m.D1(1, 1) = num_traits<T>::from_double(lsi[i] - std::sqrt(k1[i] * k2[i] / ri));
260 acc = anfitdetail::super2(acc, map_normalize(m));
261 }
262 return map_normalize(acc);
263 };
264
265 auto fobj = [&](const std::vector<T>& r) {
266 T s = zero;
267 try {
268 const std::vector<T> acf = map_acf(build(r), SAlags);
269 for (std::size_t i = 0; i < SA.size(); ++i) {
270 const T diff = T(acf[i] - SA[i]);
271 s += diff * diff;
272 }
273 } catch (const Error&) {
274 return num_traits<T>::from_double(1e30); // outside the feasible region
275 }
276 return s;
277 };
278 auto heq = [&](const std::vector<T>&) { return std::vector<T>(); };
279 auto gineq = [&](const std::vector<T>& r) {
280 std::vector<T> g(d, zero);
281 for (std::size_t i = 0; i < d; ++i) {
282 const double ri = num_traits<T>::to_double(r[i]);
283 const double lim = ri > 0.0 ? std::sqrt(k1[i] * k2[i] / ri) : 1e30;
284 g[i] = num_traits<T>::from_double(lim - lsi[i]); // <= 0
285 }
286 return g;
287 };
288
289 std::vector<T> r0(d, one), bestx;
290 std::vector<Bound<T>> bounds(d);
291 for (std::size_t i = 0; i < d; ++i) {
293 1.0 + (lsi[i] > 0.0 ? k1[i] * k2[i] / (lsi[i] * lsi[i]) : 1.0));
294 bounds[i].lo = num_traits<T>::from_double(1e-7);
295 bounds[i].hi = num_traits<T>::from_double(1e7);
296 }
298 opt.max_outer = iter_max;
299 const AugLagResult<T> res = auglag(fobj, heq, gineq, r0, bounds, opt);
300
301 MapAnfitResult<T> out = base;
302 out.map = build(res.x);
303 return out;
304}
305
306} // namespace mam
307} // namespace line
308
309#endif // LINE_API_MAM_MAP_ANFIT_H
Augmented Lagrangian method for equality- and inequality-constrained minimization,...
Base error for the multiprecision C++ port.
Definition error.h:31
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
std::vector< T > map_acf(const Map< T > &m, const std::vector< unsigned > &lags)
Autocorrelation coefficients of the inter-arrival times at the given lags,.
Definition map_moment.h:168
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
Definition mmap_lambda.h:71
MapAnfitResult< T > map_anfit_lsq(const T &ls, const T &rho, const T &H, double n, std::size_t ds, const std::vector< T > &SA, const std::vector< unsigned > &SAlags, unsigned iter_max=100)
The least-squares variant: after the deterministic construction, the per-IPP ratios are tuned so the ...
Definition map_anfit.h:222
MapAnfitResult< T > map_anfit(const T &ls, const T &rho, const T &H, double n, std::size_t ds)
Fit a superposition of interrupted Poisson processes to a Hurst parameter.
Definition map_anfit.h:147
Map< T > map_normalize(const Map< T > &in)
Clamp negative off-diagonal entries of D0 and negative entries of D1 to zero, then rebuild the diagon...
AugLagResult< T > auglag(F f, H h, G g, const std::vector< T > &x0, const std::vector< Bound< T > > &bounds, const AugLagOptions< T > &opt)
Augmented Lagrangian with a scalar objective and a simplex inner solver.
Definition auglag.h:141
AugLagOptions< T > auglag_defaults()
Defaults: rho0 = 10, growth 10, feasibility 1e-10, 50 outer iterations.
Definition auglag.h:81
Number-type abstraction for the templated API port.
Tuning of the outer multiplier iteration.
Definition auglag.h:68
Outcome of a constrained solve.
Definition auglag.h:96
std::vector< T > x
best point found
Definition auglag.h:97
What map_anfit returns: the fitted MAP and the ladder it was built on.
Definition map_anfit.h:128
T poisson_rate
lP, the Poisson remainder
Definition map_anfit.h:133
std::vector< T > switching
k(2,i), the switching-rate ladder
Definition map_anfit.h:131
std::size_t d
number of IPPs actually used
Definition map_anfit.h:130
std::vector< T > rates
l(i), the on-state arrival rates
Definition map_anfit.h:132
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
Matrix< T > D1
Definition map_moment.h:55
Matrix< T > D0
Definition map_moment.h:54