LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mamap2m_fit.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_MAMAP2M_FIT_H
6#define LINE_API_MAM_MAMAP2M_FIT_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Fit a MAMAP(2,m): a second-order acyclic MAP marked with m classes, matching
12 * the forward and backward per-class moments.
13 *
14 * Templated port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit_fb_multiclass.m,
15 * mamap2m_fit_gamma_fb.m and mamap2m_fit_trace.m.
16 *
17 * As in `maph2m_fit.h`, the TIMING and the MARKING separate: an AMAP(2) fixed by
18 * (M1, M2, M3, gamma) carries the inter-arrival law and its autocorrelation
19 * decay, and the class marking splits the THREE arrival flows of the canonical
20 * acyclic form among the m classes. The per-class forward and backward moments
21 * are affine in that split,
22 *
23 * q(j,c) = fF(c) q_f(j,c) + fB(c) q_b(j,c) + q_0(j,c),
24 *
25 * so the fit is again a convex quadratic program, here in 2k variables (a
26 * forward and a backward moment per class) under three equality constraints, one
27 * per flow, and 6k inequalities keeping every q in [0,1].
28 *
29 * SIX BRANCHES, and which one fires is decided by the AMAP's own degeneracies,
30 * not by the data. With h1, h2 the phase means, r1 the branch probability out of
31 * phase one and r2 the restart probability into phase two:
32 *
33 * 1. POISSON. The AMAP has collapsed to one state, or a denominator of the
34 * coefficients has vanished; only the class probabilities survive.
35 * 2. DEGENERATE PHASE-TYPE (form 2, r2 = 0, r1 = 1). All three flows see the
36 * same class law p.
37 * 3. CANONICAL PHASE-TYPE (form 1, r2 = 0). The MAP is really an APH(2), so the
38 * problem IS `maph2m_fit_multiclass` and is delegated to it.
39 * 4. NON-CANONICAL PHASE-TYPE (r1 = 1). Only the forward moments are
40 * identifiable; the first flow is split uniformly.
41 * 5. DEGENERATE MMAP (form 2, r2 = 0). Either the forward or the backward
42 * moments are fitted, whichever the weights prefer, and the third flow is
43 * split uniformly.
44 * 6. GENERAL. The joint (F, B) program above.
45 *
46 * WHICH CANONICAL FORM. `form 1` (D1(1,2) = 0) carries a positive
47 * autocorrelation decay and `form 2` (D1(1,1) = 0) a negative one; the
48 * coefficient sets differ and are not interchangeable. Anything else is refused,
49 * because the coefficients were derived for these two forms only.
50 *
51 * THE SOLVER IS NOT quadprog; see the same note in `maph2m_fit.h`. The
52 * acceptance is the specification: the class probabilities are reproduced, the
53 * inter-arrival law is the AMAP's, and the forward and backward moments approach
54 * their targets as far as the feasibility of the split allows.
55 *
56 * ARITHMETIC: transcendental, through the fitters and the solver.
57 */
58
59#include <cmath>
60#include <cstddef>
61#include <vector>
62
78#include "line/num/number.h"
79#include "line/util/auglag.h"
80#include "line/util/error.h"
81#include "line/util/matrix.h"
82
83namespace line {
84namespace mam {
85
86/** The fitted MAMAP and the moments it achieved. */
87template <class T>
90 std::vector<T> fF; ///< achieved per-class forward moments
91 std::vector<T> fB; ///< achieved per-class backward moments
92};
93
94namespace mamapdetail {
95
96/**
97 * The shared quadratic program: minimize sum_v w(v) (x(v)/target(v) - 1)^2
98 * subject to the equality rows summing each flow's split to one and the
99 * inequality rows keeping every q in [0,1].
100 */
101template <class T, class QFun>
102std::vector<T> solve_split(const std::vector<T>& target, const std::vector<T>& w,
103 std::size_t nflows, std::size_t k, QFun qof) {
104 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
105 const std::size_t nv = target.size();
106
107 auto fobj = [&](const std::vector<T>& x) {
108 T s = zero;
109 for (std::size_t v = 0; v < nv; ++v) {
110 const T r = T(x[v] / target[v] - one);
111 s += w[v] * r * r;
112 }
113 return s;
114 };
115 auto heq = [&](const std::vector<T>& x) {
116 std::vector<T> v(nflows, zero);
117 for (std::size_t j = 0; j < nflows; ++j) {
118 T s = zero;
119 for (std::size_t c = 0; c < k; ++c) s += qof(x, j, c);
120 v[j] = T(s - one);
121 }
122 return v;
123 };
124 auto gineq = [&](const std::vector<T>& x) {
125 std::vector<T> v;
126 v.reserve(2 * nflows * k);
127 for (std::size_t c = 0; c < k; ++c)
128 for (std::size_t j = 0; j < nflows; ++j) {
129 const T qq = qof(x, j, c);
130 v.push_back(T(qq - one));
131 v.push_back(-qq);
132 }
133 return v;
134 };
135
136 std::vector<T> x0 = target;
137 std::vector<Bound<T>> bounds(nv);
138 for (std::size_t v = 0; v < nv; ++v) {
139 bounds[v].lo = num_traits<T>::from_double(1e-6);
140 bounds[v].hi = num_traits<T>::from_double(1e6);
141 if (x0[v] < bounds[v].lo) x0[v] = bounds[v].lo;
142 if (x0[v] > bounds[v].hi) x0[v] = bounds[v].hi;
143 }
144 return auglag(fobj, heq, gineq, x0, bounds).x;
145}
146
147/** The reference's clamp-and-renormalize on each flow's split. */
148template <class T>
149void fix_split(std::vector<std::vector<T>>& q, std::size_t k) {
150 const T zero = num_traits<T>::from_int(0);
151 for (std::size_t j = 0; j < q.size(); ++j) {
152 T s = zero;
153 for (std::size_t c = 0; c < k; ++c) {
154 if (q[j][c] < zero) q[j][c] = zero;
155 s += q[j][c];
156 }
157 if (!(num_traits<T>::to_double(s) > 0.0))
158 throw NumericError("mamap2m_fit: a flow split lost all its mass");
159 for (std::size_t c = 0; c < k; ++c) q[j][c] = T(q[j][c] / s);
160 }
161}
162
163} // namespace mamapdetail
164
165/**
166 * Mark a canonical acyclic AMAP(2) with m classes, matching the forward and
167 * backward moments.
168 *
169 * @param map the AMAP(2), in one of the two canonical acyclic forms
170 * @param p per-class probabilities
171 * @param F per-class target forward moments
172 * @param B per-class target backward moments
173 * @param classWeights per-class weights; empty means uniform
174 * @param fbWeights the forward and backward weights; empty means (1, 1)
175 */
176template <class T>
177Mamap2mFitResult<T> mamap2m_fit_fb_multiclass(const Map<T>& map, const std::vector<T>& p,
178 const std::vector<T>& F, const std::vector<T>& B,
179 const std::vector<T>& classWeights = std::vector<T>(),
180 const std::vector<T>& fbWeights = std::vector<T>()) {
182 "mamap2m_fit_fb_multiclass solves a quadratic program");
183 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
184 const T two = num_traits<T>::from_int(2);
185 if (map.D0.rows() != 2)
186 throw InputError("mamap2m_fit_fb_multiclass: the underlying MAP must be second order");
187 if (!(num_abs(T(map.D0(1, 0))) <= zero))
188 throw InputError("mamap2m_fit_fb_multiclass: the underlying MAP must be acyclic");
189
190 int form;
191 if (map.D1(0, 1) == zero)
192 form = 1;
193 else if (map.D1(0, 0) == zero)
194 form = 2;
195 else
196 throw InputError(
197 "mamap2m_fit_fb_multiclass: the underlying MAP must be in canonical acyclic form "
198 "(D1(1,2) = 0 for a positive decay, D1(1,1) = 0 for a negative one)");
199
200 const std::size_t k = p.size();
201 if (k == 0) throw InputError("mamap2m_fit_fb_multiclass: no classes given");
202 if (F.size() != k || B.size() != k)
203 throw InputError(
204 "mamap2m_fit_fb_multiclass: one forward and one backward moment per class is required");
205 std::vector<T> cw = classWeights;
206 if (cw.empty()) cw.assign(k, one);
207 std::vector<T> fbw = fbWeights;
208 if (fbw.empty()) fbw.assign(2, one);
209
210 const T h1 = T(-one / map.D0(0, 0));
211 const T h2 = T(-one / map.D0(1, 1));
212 const T r1 = T(map.D0(0, 1) * h1);
213 const T r2 = T(map.D1(1, 1) * h2);
214 const double dt = 1e-8;
215 const double dr1 = num_traits<T>::to_double(r1), dr2 = num_traits<T>::to_double(r2);
216 const double dh1 = num_traits<T>::to_double(h1), dh2 = num_traits<T>::to_double(h2);
217
219 out.mmap.D0 = map.D0;
220 out.mmap.D1 = map.D1;
221 out.mmap.Dc.assign(k, Matrix<T>(2, 2, zero));
222
223 auto finish = [&](std::vector<std::vector<T>>& q) {
224 mamapdetail::fix_split(q, k);
225 for (std::size_t c = 0; c < k; ++c) {
226 if (form == 1) {
227 out.mmap.Dc[c](0, 0) = T(out.mmap.D1(0, 0) * q[0][c]);
228 out.mmap.Dc[c](1, 0) = T(out.mmap.D1(1, 0) * q[1][c]);
229 out.mmap.Dc[c](1, 1) = T(out.mmap.D1(1, 1) * q[2][c]);
230 } else {
231 out.mmap.Dc[c](0, 1) = T(out.mmap.D1(0, 1) * q[0][c]);
232 out.mmap.Dc[c](1, 0) = T(out.mmap.D1(1, 0) * q[1][c]);
233 out.mmap.Dc[c](1, 1) = T(out.mmap.D1(1, 1) * q[2][c]);
234 }
235 }
236 const std::vector<unsigned> one_order(1, 1u);
237 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
238 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
239 out.fF.assign(k, zero);
240 out.fB.assign(k, zero);
241 for (std::size_t c = 0; c < k; ++c) {
242 out.fF[c] = fm(c, 0);
243 out.fB[c] = bm[c][0];
244 }
245 };
246
247 // 1. POISSON: the coefficients have a vanishing denominator, so nothing but
248 // the class probabilities is identifiable.
249 const bool poisson1 =
250 form == 1 && (dr1 < dt || dr2 > 1.0 - dt || std::fabs(dh2 - dh1 * dr2) < dt ||
251 std::fabs(dh1 - dh2 + dh2 * dr1) < dt);
252 const bool poisson2 =
253 form == 2 && (dr2 > 1.0 - dt || std::fabs(dh1 - dh2 + dh2 * dr1) < dt ||
254 std::fabs(dh1 - dh2 - dh1 * dr1 + dh1 * dr1 * dr2) < dt);
255 if (poisson1 || poisson2) {
256 out.mmap = mamapdetail::marked_poisson(map_mean(map), p);
257 const std::vector<unsigned> one_order(1, 1u);
258 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
259 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
260 out.fF.assign(k, zero);
261 out.fB.assign(k, zero);
262 for (std::size_t c = 0; c < k; ++c) {
263 out.fF[c] = fm(c, 0);
264 out.fB[c] = bm[c][0];
265 }
266 return out;
267 }
268
269 // 2. DEGENERATE PHASE-TYPE: all three flows carry the same class law.
270 if (form == 2 && dr2 < dt && std::fabs(1.0 - dr1) < dt) {
271 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
272 for (std::size_t c = 0; c < k; ++c) q[0][c] = q[1][c] = q[2][c] = p[c];
273 finish(q);
274 return out;
275 }
276
277 // 3. CANONICAL PHASE-TYPE: the MAP is an APH(2); delegate.
278 if (form == 1 && dr2 < dt) {
279 Map<T> aph = map;
280 aph.D1(1, 1) = zero;
281 aph = map_normalize(aph);
282 const Maph2mFitResult<T> r = maph2m_fit_multiclass(aph, p, B, cw);
283 out.mmap = r.maph;
284 const std::vector<unsigned> one_order(1, 1u);
285 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
286 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
287 out.fF.assign(k, zero);
288 out.fB.assign(k, zero);
289 for (std::size_t c = 0; c < k; ++c) {
290 out.fF[c] = fm(c, 0);
291 out.fB[c] = bm[c][0];
292 }
293 return out;
294 }
295
296 // 4. NON-CANONICAL PHASE-TYPE: only the forward moments are identifiable.
297 if (std::fabs(1.0 - dr1) < dt) {
298 std::vector<std::vector<T>> qf(2, std::vector<T>(k, zero)),
299 q0(2, std::vector<T>(k, zero));
300 for (std::size_t c = 0; c < k; ++c) {
301 qf[0][c] = T(p[c] * (-one / ((h1 + h2 * (r1 - one)) * (r2 - one) *
302 (r1 + r2 - r1 * r2))));
303 q0[0][c] = T(p[c] * (h2 / ((r2 - one) * (r1 + r2 - r1 * r2) *
304 (h1 - h2 + h2 * r1))));
305 qf[1][c] = T(p[c] * (-one / (r2 * (h1 + h2 * (r1 - one)) * (r1 + r2 - r1 * r2))));
306 q0[1][c] = T(p[c] * ((h1 + h2 * r1) /
307 (r2 * (r1 + r2 - r1 * r2) * (h1 - h2 + h2 * r1))));
308 }
309 std::vector<T> w(k, zero);
310 for (std::size_t c = 0; c < k; ++c) w[c] = T(cw[c] * fbw[0]);
311 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
312 return T(x[c] * qf[j][c] + q0[j][c]);
313 };
314 const std::vector<T> x = mamapdetail::solve_split(F, w, 2, k, qof);
315 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
316 const T uni = T(one / num_traits<T>::from_int(static_cast<long>(k)));
317 for (std::size_t c = 0; c < k; ++c) {
318 q[0][c] = uni;
319 q[1][c] = T(x[c] * qf[0][c] + q0[0][c]);
320 q[2][c] = T(x[c] * qf[1][c] + q0[1][c]);
321 }
322 finish(q);
323 return out;
324 }
325
326 // 5. DEGENERATE MMAP (gamma < 0): forward or backward, per the weights.
327 if (form == 2 && dr2 < dt) {
328 const bool doForward = !(fbw[0] < fbw[1]);
329 std::vector<std::vector<T>> qc(2, std::vector<T>(k, zero)),
330 q0(2, std::vector<T>(k, zero));
331 for (std::size_t c = 0; c < k; ++c) {
332 if (doForward) {
333 qc[0][c] = T(p[c] * (-(r1 - two) / ((h1 + h2 * (r1 - one)) * (r1 - one))));
334 q0[0][c] = T(p[c] * (one - (h1 + h2) / ((r1 - one) * (h1 - h2 + h2 * r1))));
335 qc[1][c] = T(p[c] * (-(r1 - two) / (h1 + h2 * (r1 - one))));
336 q0[1][c] = T(p[c] * ((h2 * (r1 - two)) / (h1 - h2 + h2 * r1)));
337 } else {
338 qc[0][c] = T(p[c] * (-(r1 - two) / ((h2 + h1 * (r1 - one)) * (r1 - one))));
339 q0[0][c] = T(p[c] * (one - (h1 + h2) / ((r1 - one) * (h2 - h1 + h1 * r1))));
340 qc[1][c] = T(p[c] * (-(r1 - two) / (h2 + h1 * (r1 - one))));
341 q0[1][c] = T(p[c] * ((h1 * (r1 - two)) / (h2 - h1 + h1 * r1)));
342 }
343 }
344 std::vector<T> w(k, zero);
345 for (std::size_t c = 0; c < k; ++c) w[c] = T(cw[c] * (doForward ? fbw[0] : fbw[1]));
346 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
347 return T(x[c] * qc[j][c] + q0[j][c]);
348 };
349 const std::vector<T> x = mamapdetail::solve_split(doForward ? F : B, w, 2, k, qof);
350 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
351 const T uni = T(one / num_traits<T>::from_int(static_cast<long>(k)));
352 for (std::size_t c = 0; c < k; ++c) {
353 q[0][c] = T(x[c] * qc[0][c] + q0[0][c]);
354 q[1][c] = T(x[c] * qc[1][c] + q0[1][c]);
355 q[2][c] = uni;
356 }
357 finish(q);
358 return out;
359 }
360
361 // 6. GENERAL: the joint (F, B) program in 2k variables.
362 std::vector<std::vector<T>> qf(3, std::vector<T>(k, zero)), qb(3, std::vector<T>(k, zero)),
363 q0(3, std::vector<T>(k, zero));
364 for (std::size_t c = 0; c < k; ++c) {
365 if (form == 1) {
366 const T z = T(r1 * r2 - r2 + one);
367 qf[0][c] = zero;
368 qb[0][c] = T(-(p[c] * z) / ((h2 - h1 * r2) * (r1 - one) * (r2 - one)));
369 q0[0][c] = T((p[c] * (h1 + h2 - h1 * r2) * z) /
370 ((h2 - h1 * r2) * (r1 - one) * (r2 - one)));
371 qf[1][c] = T(-(p[c] * z) / (r1 * (h1 + h2 * (r1 - one)) * (r2 - one)));
372 qb[1][c] = T(-(p[c] * z) / (r1 * (h2 - h1 * r2) * (r2 - one)));
373 q0[1][c] = T((p[c] * z) / ((r1 - one) * (r2 - one)) +
374 (h1 * p[c] * z) / (r1 * (h2 - h1 * r2) * (r2 - one)) -
375 (h1 * p[c] * z) / (r1 * (h1 + h2 * (r1 - one)) * (r1 - one) * (r2 - one)));
376 qf[2][c] = T(-(p[c] * z) / (r1 * r2 * (h1 - h2 + h2 * r1)));
377 qb[2][c] = zero;
378 q0[2][c] = T((p[c] * (h1 + h2 * r1) * z) / (r1 * r2 * (h1 - h2 + h2 * r1)));
379 } else {
380 const T z = T(r1 + r2 - r1 * r2 - two);
381 const T d2 = T(h1 - h2 - h1 * r1 + h1 * r1 * r2);
382 qf[0][c] = zero;
383 qb[0][c] = T(-(p[c] * z) / ((r1 - one) * (r2 - one) * d2));
384 q0[0][c] = T((p[c] * (h2 + h1 * r1 - h1 * r1 * r2) * z) /
385 ((r1 - one) * (r2 - one) * d2));
386 qf[1][c] = T((p[c] * z) / ((r2 - one) * (h1 - h2 + h2 * r1)));
387 qb[1][c] = zero;
388 q0[1][c] = T(-(h2 * p[c] * z) / ((r2 - one) * (h1 - h2 + h2 * r1)));
389 qf[2][c] = T((p[c] * z) / (r2 * (h1 + h2 * (r1 - one))));
390 qb[2][c] = T((p[c] * z) / (r2 * d2));
391 q0[2][c] = T((h1 * p[c] * z) / (r2 * (h1 + h2 * (r1 - one)) * (r1 - one)) -
392 (h1 * p[c] * z) / (r2 * d2) - (p[c] * z) / (r2 * (r1 - one)));
393 }
394 }
395
396 // The variables interleave: x[2c] is F(c), x[2c+1] is B(c).
397 std::vector<T> target(2 * k, zero), w(2 * k, zero);
398 for (std::size_t c = 0; c < k; ++c) {
399 target[2 * c] = F[c];
400 target[2 * c + 1] = B[c];
401 w[2 * c] = T(cw[c] * fbw[0]);
402 w[2 * c + 1] = T(cw[c] * fbw[1]);
403 }
404 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
405 return T(x[2 * c] * qf[j][c] + x[2 * c + 1] * qb[j][c] + q0[j][c]);
406 };
407 const std::vector<T> x = mamapdetail::solve_split(target, w, 3, k, qof);
408 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
409 for (std::size_t c = 0; c < k; ++c)
410 for (std::size_t j = 0; j < 3; ++j) q[j][c] = qof(x, j, c);
411 finish(q);
412 return out;
413}
414
415/**
416 * Fit a MAMAP(2,m) to three moments, the decay rate, the class probabilities and
417 * the forward and backward moments, over every AMAP(2) form.
418 */
419template <class T>
420Mmap<T> mamap2m_fit_gamma_fb(const T& M1, const T& M2, const T& M3, const T& GAMMA,
421 const std::vector<T>& p, const std::vector<T>& F,
422 const std::vector<T>& B) {
423 const Amap2FitGammaResult<T> a = amap2_fit_gamma(M1, M2, M3, GAMMA);
424 if (a.amaps.empty() || (a.amaps.size() == 1 && a.amaps[0].order() == 1))
425 return mamapdetail::marked_poisson(M1, p);
426
427 Mmap<T> best;
428 double bestErr = 0.0;
429 bool have = false;
430 for (std::size_t j = 0; j < a.amaps.size(); ++j) {
432 try {
433 r = mamap2m_fit_fb_multiclass(a.amaps[j], p, F, B);
434 } catch (const Error&) {
435 continue;
436 }
437 double err = 0.0;
438 for (std::size_t c = 0; c < p.size(); ++c) {
439 const double df = num_traits<T>::to_double(T(r.fF[c] / F[c])) - 1.0;
440 const double db = num_traits<T>::to_double(T(r.fB[c] / B[c])) - 1.0;
441 err += df * df + db * db;
442 }
443 if (!have || err < bestErr) {
444 bestErr = err;
445 best = r.mmap;
446 have = true;
447 }
448 }
449 if (!have)
450 throw NumericError(
451 "mamap2m_fit_gamma_fb: no AMAP(2) form admits a valid class split for the requested "
452 "class probabilities and forward/backward moments");
453 return best;
454}
455
456/**
457 * The full `mamap2m_fit` dispatcher.
458 *
459 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit.m. It chooses WHICH pair of
460 * descriptors to match from the AMAP's degeneracies and from the caller's
461 * weights over (forward, backward, sigma):
462 *
463 * - more than two classes: F+B, since the sigma fitters are two-class only;
464 * - a negligible gamma: the process is renewal, so a MAPH is fitted instead;
465 * - otherwise one AMAP(2) form at a time, taking F+B, F+S or B+S as the
466 * degeneracy allows and the weights prefer, and keeping the form whose
467 * achieved descriptors land closest.
468 *
469 * The sigma branches call `mamap22_fit_fs_multiclass` and
470 * `mamap22_fit_bs_multiclass` (mamap22_fit_fs.h, mamap22_fit_bs.h); they fire
471 * when the weights prefer sigma over forward or backward, and on the
472 * degenerate AMAP shapes. With the reference's DEFAULT weights (1,1,1) F+B is
473 * preferred.
474 *
475 * @param fbsWeights the (forward, backward, sigma) weights; empty means (1,1,1)
476 */
477template <class T>
478Mmap<T> mamap2m_fit(const T& M1, const T& M2, const T& M3, const T& GAMMA,
479 const std::vector<T>& p, const std::vector<T>& F, const std::vector<T>& B,
480 const Matrix<T>& S, const std::vector<T>& fbsWeights = std::vector<T>()) {
481 const T one = num_traits<T>::from_int(1);
482 std::vector<T> w = fbsWeights;
483 if (w.empty()) w.assign(3, one);
484 if (w.size() != 3) throw InputError("mamap2m_fit: three (F, B, S) weights are required");
485 const Matrix<T>& S_or_empty = S;
486
487 const double gammatol = 1e-4, degentol = 1e-8;
488 if (p.size() > 2) return mamap2m_fit_gamma_fb(M1, M2, M3, GAMMA, p, F, B);
489 if (std::fabs(num_traits<T>::to_double(GAMMA)) < gammatol) return maph2m_fit(M1, M2, M3, p, B);
490
491 const Amap2FitGammaResult<T> a = amap2_fit_gamma(M1, M2, M3, GAMMA);
492 if (a.amaps.empty() || (a.amaps.size() == 1 && a.amaps[0].order() == 1))
493 return mamapdetail::marked_poisson(M1, p);
494
495 const bool preferFB = !(w[0] < w[2]) && !(w[1] < w[2]);
496 const bool preferFS = !preferFB && !(w[0] < w[1]);
497
498 Mmap<T> best;
499 double bestErr = 0.0;
500 bool have = false;
501 for (std::size_t j = 0; j < a.amaps.size(); ++j) {
502 const Map<T>& mp = a.amaps[j];
503 if (mp.order() != 2) continue;
504 const T h1 = T(-one / mp.D0(0, 0)), h2 = T(-one / mp.D0(1, 1));
505 const T r1 = T(h1 * mp.D0(0, 1)), r2 = T(h2 * mp.D1(1, 1));
506 const double dh1 = num_traits<T>::to_double(h1), dh2 = num_traits<T>::to_double(h2);
507 const double dr1 = num_traits<T>::to_double(r1), dr2 = num_traits<T>::to_double(r2);
508 const bool posGamma = num_traits<T>::to_double(GAMMA) > 0.0;
509
510 // The reference's degeneracy ladder: several shapes identify only one
511 // of the two moments, and there sigma is the second descriptor.
512 bool needsFs = false, needsBs = false;
513 if (posGamma) {
514 if (std::fabs(dh2 - dh1 * dr2) < degentol) needsFs = true;
515 else if (std::fabs(dh1 - dh2 + dh2 * dr1) < degentol) needsBs = true;
516 else if (1.0 - dr1 < degentol) needsFs = true;
517 } else {
518 if (std::fabs(dh1 - dh2 - dh1 * dr1 + dh1 * dr1 * dr2) < degentol) needsFs = true;
519 else if (std::fabs(dh1 - dh2 + dh2 * dr1) < degentol) needsBs = true;
520 }
521 // Outside the degeneracies the weights decide, as in the reference.
522 if (!needsFs && !needsBs && !preferFB) {
523 if (preferFS) needsFs = true;
524 else needsBs = true;
525 }
526
527 Mmap<T> cand;
528 std::vector<T> cF, cB;
529 try {
530 if (needsFs) {
531 const Mamap22FsFitResult<T> rf = mamap22_fit_fs_multiclass(mp, p, F, S_or_empty);
532 cand = rf.mmap;
533 } else if (needsBs) {
534 const Mamap22FitResult<T> rb = mamap22_fit_bs_multiclass(mp, p, B, S_or_empty);
535 cand = rb.mmap;
536 } else {
537 const Mamap2mFitResult<T> r = mamap2m_fit_fb_multiclass(mp, p, F, B);
538 cand = r.mmap;
539 }
540 } catch (const Error&) {
541 continue;
542 }
543 // Score every candidate the same way, on the descriptors it achieved.
544 const std::vector<unsigned> ord1(1, 1u);
545 const Matrix<T> fmc = mmap_forward_moment(cand, ord1, true);
546 const std::vector<std::vector<T>> bmc = mmap_backward_moment(cand, ord1, true);
547 const Matrix<T> fsc = mmap_sigma(cand);
548 double err = 0.0;
549 for (std::size_t c = 0; c < p.size(); ++c) {
550 const double df = num_traits<T>::to_double(T(F[c] / fmc(c, 0))) - 1.0;
551 const double db = num_traits<T>::to_double(T(B[c] / bmc[c][0])) - 1.0;
552 err += num_traits<T>::to_double(w[0]) * df * df +
553 num_traits<T>::to_double(w[1]) * db * db;
554 }
555 if (S_or_empty.rows() > 0 && fsc.rows() > 0) {
556 const double ds = num_traits<T>::to_double(T(S_or_empty(0, 0) / fsc(0, 0))) - 1.0;
557 err += num_traits<T>::to_double(w[2]) * ds * ds;
558 }
559 if (!have || err < bestErr) {
560 bestErr = err;
561 best = cand;
562 have = true;
563 }
564 }
565 if (!have)
566 throw NumericError(
567 "mamap2m_fit: no AMAP(2) form admits a valid class split for the requested "
568 "descriptors");
569 return best;
570}
571
572namespace mamapdetail {
573
574/** The descriptors mamap2m_fit_mmap.m and mamap2m_fit_gamma_fb_mmap.m read off an MMAP. */
575template <class T>
576struct MmapDescriptors {
577 T M1, M2, M3, GAMMA;
578 std::vector<T> p, F, B;
579};
580
581template <class T>
582MmapDescriptors<T> mmap_descriptors(const Mmap<T>& mm) {
583 MmapDescriptors<T> d;
584 const Map<T> agg = mm.map();
585 d.M1 = map_moment(agg, 1u);
586 d.M2 = map_moment(agg, 2u);
587 d.M3 = map_moment(agg, 3u);
588 d.GAMMA = map_gamma(agg);
589 d.p = mmap_pc(mm);
590 const std::vector<unsigned> ord1(1, 1u);
591 const Matrix<T> Fm = mmap_forward_moment(mm, ord1, true);
592 const std::vector<std::vector<T>> Bm = mmap_backward_moment(mm, ord1, true);
593 for (std::size_t c = 0; c < mm.classes(); ++c) {
594 d.F.push_back(Fm(c, 0));
595 d.B.push_back(Bm[c][0]);
596 }
597 return d;
598}
599
600} // namespace mamapdetail
601
602/**
603 * Fit a MAMAP(2,m) to the characteristics of an MMAP (mamap2m_fit_mmap.m): its
604 * three moments, decay rate, class probabilities, first forward and backward
605 * moments and class transition matrix. This is `mmap_compress`'s 'mamap2'.
606 *
607 * @param fbsWeights the (forward, backward, sigma) weights; empty means (1,1,1)
608 */
609template <class T>
610Mmap<T> mamap2m_fit_mmap(const Mmap<T>& mm, const std::vector<T>& fbsWeights = std::vector<T>()) {
611 if (mm.classes() == 0) throw InputError("mamap2m_fit_mmap: the MMAP has no classes");
612 const mamapdetail::MmapDescriptors<T> d = mamapdetail::mmap_descriptors(mm);
613 return mamap2m_fit(d.M1, d.M2, d.M3, d.GAMMA, d.p, d.F, d.B, mmap_sigma(mm), fbsWeights);
614}
615
616/**
617 * Fit a MAMAP(2,m) to an MMAP through the (F, B) pair alone
618 * (mamap2m_fit_gamma_fb_mmap.m). This is `mmap_compress`'s 'mamap2.fb' and the
619 * order-2 compression of `mmap_super_safe`.
620 */
621template <class T>
623 if (mm.classes() == 0) throw InputError("mamap2m_fit_gamma_fb_mmap: the MMAP has no classes");
624 const mamapdetail::MmapDescriptors<T> d = mamapdetail::mmap_descriptors(mm);
625 return mamap2m_fit_gamma_fb(d.M1, d.M2, d.M3, d.GAMMA, d.p, d.F, d.B);
626}
627
628/**
629 * Fit a MAMAP(2,m) from a marked trace through the (F, B) pair alone.
630 *
631 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit_gamma_fb_trace.m: the
632 * descriptors are the class probabilities and the per-class FORWARD and
633 * BACKWARD first moments, with no sigma and no descriptor-pair selection.
634 * `mamap2m_fit_trace` is the full dispatcher of the reference.
635 *
636 * @param Tv the inter-arrival times
637 * @param A the class of each arrival, 1-based
638 */
639template <class T>
640Mmap<T> mamap2m_fit_gamma_fb_trace(const std::vector<T>& Tv, const std::vector<int>& A) {
641 if (Tv.empty() || Tv.size() != A.size())
642 throw InputError(
643 "mamap2m_fit_gamma_fb_trace: the trace and its labels must agree in length");
644 const T zero = num_traits<T>::from_int(0);
645 T m1 = zero, m2 = zero, m3 = zero;
646 for (std::size_t i = 0; i < Tv.size(); ++i) {
647 const T x = Tv[i];
648 m1 += x;
649 m2 += x * x;
650 m3 += x * x * x;
651 }
652 const T n = num_traits<T>::from_int(static_cast<long>(Tv.size()));
653 const std::vector<unsigned> one_order(1, 1u);
654 const std::vector<T> p = trace::mtrace_pc<T>(A);
655 const Matrix<T> fm = trace::mtrace_forward_moment(Tv, A, one_order);
656 const Matrix<T> bm = trace::mtrace_backward_moment(Tv, A, one_order);
657 std::vector<T> F(p.size(), zero), B(p.size(), zero);
658 for (std::size_t c = 0; c < p.size(); ++c) {
659 F[c] = fm(c, 0);
660 B[c] = bm(c, 0);
661 }
662 return mamap2m_fit_gamma_fb(T(m1 / n), T(m2 / n), T(m3 / n),
663 line::trace::trace_gamma(Tv).gamma, p, F, B);
664}
665
666/**
667 * Fit a MAPH(2,m) or MAMAP(2,m) matching the characteristics of a marked
668 * trace.
669 *
670 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit_trace.m: the class
671 * probabilities are always matched exactly; the remaining two characteristics
672 * default to the forward and backward moments unless the underlying AMAP(2)
673 * is degenerate, and `fbsWeights` moves the preference among (forward,
674 * backward, sigma). Unlike `mamap2m_fit_gamma_fb_trace` this computes the
675 * class transition probabilities (`mtrace_sigma`) and dispatches through the
676 * full `mamap2m_fit` descriptor selection.
677 *
678 * @param Tv the inter-arrival times
679 * @param A the class of each arrival, 1-based
680 * @param fbsWeights the (forward, backward, sigma) weights; empty means (1,1,1)
681 */
682template <class T>
683Mmap<T> mamap2m_fit_trace(const std::vector<T>& Tv, const std::vector<int>& A,
684 const std::vector<T>& fbsWeights = std::vector<T>()) {
685 if (Tv.empty() || Tv.size() != A.size())
686 throw InputError("mamap2m_fit_trace: the trace and its labels must agree in length");
687 const T zero = num_traits<T>::from_int(0);
688 T m1 = zero, m2 = zero, m3 = zero;
689 for (std::size_t i = 0; i < Tv.size(); ++i) {
690 const T x = Tv[i];
691 m1 += x;
692 m2 += x * x;
693 m3 += x * x * x;
694 }
695 const T n = num_traits<T>::from_int(static_cast<long>(Tv.size()));
696 const std::vector<unsigned> one_order(1, 1u);
697 const std::vector<T> p = trace::mtrace_pc<T>(A);
698 const Matrix<T> fm = trace::mtrace_forward_moment(Tv, A, one_order);
699 const Matrix<T> bm = trace::mtrace_backward_moment(Tv, A, one_order);
700 std::vector<T> F(p.size(), zero), B(p.size(), zero);
701 for (std::size_t c = 0; c < p.size(); ++c) {
702 F[c] = fm(c, 0);
703 B[c] = bm(c, 0);
704 }
706 return mamap2m_fit(T(m1 / n), T(m2 / n), T(m3 / n), line::trace::trace_gamma(Tv).gamma, p,
707 F, B, S, fbsWeights);
708}
709
710} // namespace mam
711} // namespace line
712
713#endif // LINE_API_MAM_MAMAP2M_FIT_H
AMAP(2) fit of three moments and the autocorrelation decay rate (matlab/lib/m3a/m3a/amap2/amap2_fit_g...
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
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Fit a MAMAP(2,2) matching the FORWARD moment and the class TRANSITION probability sigma.
The marked Poisson process every MAMAP fitter falls back to.
Autocorrelation decay rate of a MAP: the gamma of the geometric model rho(k) = rho0 * gamma^k with rh...
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Fit a MAPH(2,m): a second-order acyclic phase-type marked with m classes.
Dense matrix and non-owning view.
Class-conditional backward moments of an MMAP (matlab/lib/m3a/m3a/mmap/mmap_backward_moment....
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
Marked MAP statistics: embedded chains, class-transition probabilities, forward and cross moments,...
Backward moments of a marked trace: the moments of the inter-arrival time that PRECEDES an event of e...
Forward moments of a marked trace: the moments of the inter-arrival time that FOLLOWS an event of eac...
Class probabilities of a marked trace, p_c = count_c / N.
One-step class transition frequencies of a marked trace,.
Mmap< T > maph2m_fit(const T &M1, const T &M2, const T &M3, const std::vector< T > &p, const std::vector< T > &B)
Fit a MAPH(2,m) to three moments, the class probabilities and the per-class backward moments,...
Definition maph2m_fit.h:228
Amap2FitGammaResult< T > amap2_fit_gamma(const T &M1, const T &M2, const T &M3, const T &GAMMA, const T &cvtol)
Fit an AMAP(2) to (M1, M2, M3, GAMMA).
Mmap< T > mamap2m_fit_trace(const std::vector< T > &Tv, const std::vector< int > &A, const std::vector< T > &fbsWeights=std::vector< T >())
Fit a MAPH(2,m) or MAMAP(2,m) matching the characteristics of a marked trace.
Matrix< T > mmap_forward_moment(const Mmap< T > &mm, const std::vector< unsigned > &orders, bool normalize)
Forward moments: MOMENTS(a,h) is the order-orders[h] moment of the interval ENDING with a class-a arr...
Definition mmap_stats.h:290
Mmap< T > mamap2m_fit(const T &M1, const T &M2, const T &M3, const T &GAMMA, const std::vector< T > &p, const std::vector< T > &F, const std::vector< T > &B, const Matrix< T > &S, const std::vector< T > &fbsWeights=std::vector< T >())
The full mamap2m_fit dispatcher.
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
Mmap< T > mamap2m_fit_gamma_fb_mmap(const Mmap< T > &mm)
Fit a MAMAP(2,m) to an MMAP through the (F, B) pair alone (mamap2m_fit_gamma_fb_mmap....
std::vector< std::vector< T > > mmap_backward_moment(const Mmap< T > &m, const std::vector< unsigned > &orders, bool normalized)
Class-conditional backward moments of an MMAP (mmap_backward_moment.m).
Mamap22FsFitResult< T > mamap22_fit_fs_multiclass(const Map< T > &map, const std::vector< T > &p, const std::vector< T > &F, const Matrix< T > &S, const std::vector< T > &classWeights=std::vector< T >(), const std::vector< T > &fsWeights=std::vector< T >(), bool adjust=true)
Mamap22FitResult< T > mamap22_fit_bs_multiclass(const Map< T > &map, const std::vector< T > &p, const std::vector< T > &B, const Matrix< T > &S, const std::vector< T > &classWeights=std::vector< T >(), const std::vector< T > &bsWeights=std::vector< T >(), bool adjust=true)
Matrix< T > mmap_sigma(const Mmap< T > &mm)
sigma(i,j) = pie E_i E_j 1, the probability that two consecutive marks are (i,j).
Definition mmap_stats.h:140
Mmap< T > mamap2m_fit_gamma_fb(const T &M1, const T &M2, const T &M3, const T &GAMMA, const std::vector< T > &p, const std::vector< T > &F, const std::vector< T > &B)
Fit a MAMAP(2,m) to three moments, the decay rate, the class probabilities and the forward and backwa...
Mmap< T > mamap2m_fit_mmap(const Mmap< T > &mm, const std::vector< T > &fbsWeights=std::vector< T >())
Fit a MAMAP(2,m) to the characteristics of an MMAP (mamap2m_fit_mmap.m): its three moments,...
Mamap2mFitResult< T > mamap2m_fit_fb_multiclass(const Map< T > &map, const std::vector< T > &p, const std::vector< T > &F, const std::vector< T > &B, const std::vector< T > &classWeights=std::vector< T >(), const std::vector< T > &fbWeights=std::vector< T >())
Mark a canonical acyclic AMAP(2) with m classes, matching the forward and backward moments.
std::vector< T > mmap_pc(const Mmap< T > &m)
Class probabilities seen by an arriving job, pc = pie (-D0)^-1 D1^(c) e.
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...
T map_moment(const Map< T > &m, unsigned k)
Raw moment of order k of the inter-arrival time: k!
Definition map_moment.h:118
Mmap< T > mamap2m_fit_gamma_fb_trace(const std::vector< T > &Tv, const std::vector< int > &A)
Fit a MAMAP(2,m) from a marked trace through the (F, B) pair alone.
Maph2mFitResult< T > maph2m_fit_multiclass(const Map< T > &aph, const std::vector< T > &p, const std::vector< T > &B, const std::vector< T > &classWeights=std::vector< T >())
Mark a canonical acyclic APH(2) with m classes.
Definition maph2m_fit.h:89
T map_gamma(const Map< T > &m, long limit=1000)
Autocorrelation decay rate of a MAP (map_gamma.m).
Definition map_gamma.h:192
Matrix< T > mtrace_forward_moment(const std::vector< T > &Tv, const std::vector< int > &A, const std::vector< unsigned > &orders, bool norm=true)
Forward moments of a marked trace: the moments of the inter-arrival time that FOLLOWS an event of eac...
Matrix< T > mtrace_backward_moment(const std::vector< T > &Tv, const std::vector< int > &A, const std::vector< unsigned > &orders, bool norm=true)
Backward moments of a marked trace: the moments of the inter-arrival time that PRECEDES an event of e...
TraceGammaResult< T > trace_gamma(const std::vector< T > &S, long limit=1000, const std::vector< T > &grid=std::vector< T >())
Autocorrelation decay rate of a trace: the gamma of the geometric model rho(k) = rho0 * gamma^k,...
Definition trace_gamma.h:73
std::vector< T > mtrace_pc(const std::vector< int > &A)
Class probabilities of a marked trace, p_c = count_c / N.
Definition mtrace_pc.h:42
Matrix< T > mtrace_sigma(const std::vector< int > &L)
One-step class transition frequencies of a marked trace, sigma(i,j) = #{t : A_t = i,...
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
T num_abs(const T &v)
Definition number.h:198
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
Number-type abstraction for the templated API port.
Result of amap2_fit_gamma.
std::vector< Map< T > > amaps
every exact solution found
The fitted MAMAP(2,2), what it achieved, and whether the fit was exact.
The forward-plus-sigma result; warning carries the reference's diagnostic.
The fitted MAMAP and the moments it achieved.
Definition mamap2m_fit.h:88
std::vector< T > fF
achieved per-class forward moments
Definition mamap2m_fit.h:90
std::vector< T > fB
achieved per-class backward moments
Definition mamap2m_fit.h:91
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
std::size_t order() const
Definition map_moment.h:57
The fitted MAPH and the backward moments it actually achieved.
Definition maph2m_fit.h:75
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
std::size_t classes() const
Definition mmap_lambda.h:51
Autocorrelation decay rate of a trace: the gamma of the geometric model rho(k) = rho0 * gamma^k,...