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
77#include "line/num/number.h"
78#include "line/util/auglag.h"
79#include "line/util/error.h"
80#include "line/util/matrix.h"
81
82namespace line {
83namespace mam {
84
85/** The fitted MAMAP and the moments it achieved. */
86template <class T>
89 std::vector<T> fF; ///< achieved per-class forward moments
90 std::vector<T> fB; ///< achieved per-class backward moments
91};
92
93namespace mamapdetail {
94
95/**
96 * The shared quadratic program: minimize sum_v w(v) (x(v)/target(v) - 1)^2
97 * subject to the equality rows summing each flow's split to one and the
98 * inequality rows keeping every q in [0,1].
99 */
100template <class T, class QFun>
101std::vector<T> solve_split(const std::vector<T>& target, const std::vector<T>& w,
102 std::size_t nflows, std::size_t k, QFun qof) {
103 const T one = num_traits<T>::from_int(1), zero = num_traits<T>::from_int(0);
104 const std::size_t nv = target.size();
105
106 auto fobj = [&](const std::vector<T>& x) {
107 T s = zero;
108 for (std::size_t v = 0; v < nv; ++v) {
109 const T r = T(x[v] / target[v] - one);
110 s += w[v] * r * r;
111 }
112 return s;
113 };
114 auto heq = [&](const std::vector<T>& x) {
115 std::vector<T> v(nflows, zero);
116 for (std::size_t j = 0; j < nflows; ++j) {
117 T s = zero;
118 for (std::size_t c = 0; c < k; ++c) s += qof(x, j, c);
119 v[j] = T(s - one);
120 }
121 return v;
122 };
123 auto gineq = [&](const std::vector<T>& x) {
124 std::vector<T> v;
125 v.reserve(2 * nflows * k);
126 for (std::size_t c = 0; c < k; ++c)
127 for (std::size_t j = 0; j < nflows; ++j) {
128 const T qq = qof(x, j, c);
129 v.push_back(T(qq - one));
130 v.push_back(-qq);
131 }
132 return v;
133 };
134
135 std::vector<T> x0 = target;
136 std::vector<Bound<T>> bounds(nv);
137 for (std::size_t v = 0; v < nv; ++v) {
138 bounds[v].lo = num_traits<T>::from_double(1e-6);
139 bounds[v].hi = num_traits<T>::from_double(1e6);
140 if (x0[v] < bounds[v].lo) x0[v] = bounds[v].lo;
141 if (x0[v] > bounds[v].hi) x0[v] = bounds[v].hi;
142 }
143 return auglag(fobj, heq, gineq, x0, bounds).x;
144}
145
146/** The reference's clamp-and-renormalize on each flow's split. */
147template <class T>
148void fix_split(std::vector<std::vector<T>>& q, std::size_t k) {
149 const T zero = num_traits<T>::from_int(0);
150 for (std::size_t j = 0; j < q.size(); ++j) {
151 T s = zero;
152 for (std::size_t c = 0; c < k; ++c) {
153 if (q[j][c] < zero) q[j][c] = zero;
154 s += q[j][c];
155 }
156 if (!(num_traits<T>::to_double(s) > 0.0))
157 throw NumericError("mamap2m_fit: a flow split lost all its mass");
158 for (std::size_t c = 0; c < k; ++c) q[j][c] = T(q[j][c] / s);
159 }
160}
161
162} // namespace mamapdetail
163
164/**
165 * Mark a canonical acyclic AMAP(2) with m classes, matching the forward and
166 * backward moments.
167 *
168 * @param map the AMAP(2), in one of the two canonical acyclic forms
169 * @param p per-class probabilities
170 * @param F per-class target forward moments
171 * @param B per-class target backward moments
172 * @param classWeights per-class weights; empty means uniform
173 * @param fbWeights the forward and backward weights; empty means (1, 1)
174 */
175template <class T>
176Mamap2mFitResult<T> mamap2m_fit_fb_multiclass(const Map<T>& map, const std::vector<T>& p,
177 const std::vector<T>& F, const std::vector<T>& B,
178 const std::vector<T>& classWeights = std::vector<T>(),
179 const std::vector<T>& fbWeights = std::vector<T>()) {
181 "mamap2m_fit_fb_multiclass solves a quadratic program");
182 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
183 const T two = num_traits<T>::from_int(2);
184 if (map.D0.rows() != 2)
185 throw InputError("mamap2m_fit_fb_multiclass: the underlying MAP must be second order");
186 if (!(num_abs(T(map.D0(1, 0))) <= zero))
187 throw InputError("mamap2m_fit_fb_multiclass: the underlying MAP must be acyclic");
188
189 int form;
190 if (map.D1(0, 1) == zero)
191 form = 1;
192 else if (map.D1(0, 0) == zero)
193 form = 2;
194 else
195 throw InputError(
196 "mamap2m_fit_fb_multiclass: the underlying MAP must be in canonical acyclic form "
197 "(D1(1,2) = 0 for a positive decay, D1(1,1) = 0 for a negative one)");
198
199 const std::size_t k = p.size();
200 if (k == 0) throw InputError("mamap2m_fit_fb_multiclass: no classes given");
201 if (F.size() != k || B.size() != k)
202 throw InputError(
203 "mamap2m_fit_fb_multiclass: one forward and one backward moment per class is required");
204 std::vector<T> cw = classWeights;
205 if (cw.empty()) cw.assign(k, one);
206 std::vector<T> fbw = fbWeights;
207 if (fbw.empty()) fbw.assign(2, one);
208
209 const T h1 = T(-one / map.D0(0, 0));
210 const T h2 = T(-one / map.D0(1, 1));
211 const T r1 = T(map.D0(0, 1) * h1);
212 const T r2 = T(map.D1(1, 1) * h2);
213 const double dt = 1e-8;
214 const double dr1 = num_traits<T>::to_double(r1), dr2 = num_traits<T>::to_double(r2);
215 const double dh1 = num_traits<T>::to_double(h1), dh2 = num_traits<T>::to_double(h2);
216
218 out.mmap.D0 = map.D0;
219 out.mmap.D1 = map.D1;
220 out.mmap.Dc.assign(k, Matrix<T>(2, 2, zero));
221
222 auto finish = [&](std::vector<std::vector<T>>& q) {
223 mamapdetail::fix_split(q, k);
224 for (std::size_t c = 0; c < k; ++c) {
225 if (form == 1) {
226 out.mmap.Dc[c](0, 0) = T(out.mmap.D1(0, 0) * q[0][c]);
227 out.mmap.Dc[c](1, 0) = T(out.mmap.D1(1, 0) * q[1][c]);
228 out.mmap.Dc[c](1, 1) = T(out.mmap.D1(1, 1) * q[2][c]);
229 } else {
230 out.mmap.Dc[c](0, 1) = T(out.mmap.D1(0, 1) * q[0][c]);
231 out.mmap.Dc[c](1, 0) = T(out.mmap.D1(1, 0) * q[1][c]);
232 out.mmap.Dc[c](1, 1) = T(out.mmap.D1(1, 1) * q[2][c]);
233 }
234 }
235 const std::vector<unsigned> one_order(1, 1u);
236 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
237 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
238 out.fF.assign(k, zero);
239 out.fB.assign(k, zero);
240 for (std::size_t c = 0; c < k; ++c) {
241 out.fF[c] = fm(c, 0);
242 out.fB[c] = bm[c][0];
243 }
244 };
245
246 // 1. POISSON: the coefficients have a vanishing denominator, so nothing but
247 // the class probabilities is identifiable.
248 const bool poisson1 =
249 form == 1 && (dr1 < dt || dr2 > 1.0 - dt || std::fabs(dh2 - dh1 * dr2) < dt ||
250 std::fabs(dh1 - dh2 + dh2 * dr1) < dt);
251 const bool poisson2 =
252 form == 2 && (dr2 > 1.0 - dt || std::fabs(dh1 - dh2 + dh2 * dr1) < dt ||
253 std::fabs(dh1 - dh2 - dh1 * dr1 + dh1 * dr1 * dr2) < dt);
254 if (poisson1 || poisson2) {
255 out.mmap = mamapdetail::marked_poisson(map_mean(map), p);
256 const std::vector<unsigned> one_order(1, 1u);
257 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
258 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
259 out.fF.assign(k, zero);
260 out.fB.assign(k, zero);
261 for (std::size_t c = 0; c < k; ++c) {
262 out.fF[c] = fm(c, 0);
263 out.fB[c] = bm[c][0];
264 }
265 return out;
266 }
267
268 // 2. DEGENERATE PHASE-TYPE: all three flows carry the same class law.
269 if (form == 2 && dr2 < dt && std::fabs(1.0 - dr1) < dt) {
270 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
271 for (std::size_t c = 0; c < k; ++c) q[0][c] = q[1][c] = q[2][c] = p[c];
272 finish(q);
273 return out;
274 }
275
276 // 3. CANONICAL PHASE-TYPE: the MAP is an APH(2); delegate.
277 if (form == 1 && dr2 < dt) {
278 Map<T> aph = map;
279 aph.D1(1, 1) = zero;
280 aph = map_normalize(aph);
281 const Maph2mFitResult<T> r = maph2m_fit_multiclass(aph, p, B, cw);
282 out.mmap = r.maph;
283 const std::vector<unsigned> one_order(1, 1u);
284 const Matrix<T> fm = mmap_forward_moment(out.mmap, one_order, true);
285 const std::vector<std::vector<T>> bm = mmap_backward_moment(out.mmap, one_order, true);
286 out.fF.assign(k, zero);
287 out.fB.assign(k, zero);
288 for (std::size_t c = 0; c < k; ++c) {
289 out.fF[c] = fm(c, 0);
290 out.fB[c] = bm[c][0];
291 }
292 return out;
293 }
294
295 // 4. NON-CANONICAL PHASE-TYPE: only the forward moments are identifiable.
296 if (std::fabs(1.0 - dr1) < dt) {
297 std::vector<std::vector<T>> qf(2, std::vector<T>(k, zero)),
298 q0(2, std::vector<T>(k, zero));
299 for (std::size_t c = 0; c < k; ++c) {
300 qf[0][c] = T(p[c] * (-one / ((h1 + h2 * (r1 - one)) * (r2 - one) *
301 (r1 + r2 - r1 * r2))));
302 q0[0][c] = T(p[c] * (h2 / ((r2 - one) * (r1 + r2 - r1 * r2) *
303 (h1 - h2 + h2 * r1))));
304 qf[1][c] = T(p[c] * (-one / (r2 * (h1 + h2 * (r1 - one)) * (r1 + r2 - r1 * r2))));
305 q0[1][c] = T(p[c] * ((h1 + h2 * r1) /
306 (r2 * (r1 + r2 - r1 * r2) * (h1 - h2 + h2 * r1))));
307 }
308 std::vector<T> w(k, zero);
309 for (std::size_t c = 0; c < k; ++c) w[c] = T(cw[c] * fbw[0]);
310 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
311 return T(x[c] * qf[j][c] + q0[j][c]);
312 };
313 const std::vector<T> x = mamapdetail::solve_split(F, w, 2, k, qof);
314 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
315 const T uni = T(one / num_traits<T>::from_int(static_cast<long>(k)));
316 for (std::size_t c = 0; c < k; ++c) {
317 q[0][c] = uni;
318 q[1][c] = T(x[c] * qf[0][c] + q0[0][c]);
319 q[2][c] = T(x[c] * qf[1][c] + q0[1][c]);
320 }
321 finish(q);
322 return out;
323 }
324
325 // 5. DEGENERATE MMAP (gamma < 0): forward or backward, per the weights.
326 if (form == 2 && dr2 < dt) {
327 const bool doForward = !(fbw[0] < fbw[1]);
328 std::vector<std::vector<T>> qc(2, std::vector<T>(k, zero)),
329 q0(2, std::vector<T>(k, zero));
330 for (std::size_t c = 0; c < k; ++c) {
331 if (doForward) {
332 qc[0][c] = T(p[c] * (-(r1 - two) / ((h1 + h2 * (r1 - one)) * (r1 - one))));
333 q0[0][c] = T(p[c] * (one - (h1 + h2) / ((r1 - one) * (h1 - h2 + h2 * r1))));
334 qc[1][c] = T(p[c] * (-(r1 - two) / (h1 + h2 * (r1 - one))));
335 q0[1][c] = T(p[c] * ((h2 * (r1 - two)) / (h1 - h2 + h2 * r1)));
336 } else {
337 qc[0][c] = T(p[c] * (-(r1 - two) / ((h2 + h1 * (r1 - one)) * (r1 - one))));
338 q0[0][c] = T(p[c] * (one - (h1 + h2) / ((r1 - one) * (h2 - h1 + h1 * r1))));
339 qc[1][c] = T(p[c] * (-(r1 - two) / (h2 + h1 * (r1 - one))));
340 q0[1][c] = T(p[c] * ((h1 * (r1 - two)) / (h2 - h1 + h1 * r1)));
341 }
342 }
343 std::vector<T> w(k, zero);
344 for (std::size_t c = 0; c < k; ++c) w[c] = T(cw[c] * (doForward ? fbw[0] : fbw[1]));
345 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
346 return T(x[c] * qc[j][c] + q0[j][c]);
347 };
348 const std::vector<T> x = mamapdetail::solve_split(doForward ? F : B, w, 2, k, qof);
349 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
350 const T uni = T(one / num_traits<T>::from_int(static_cast<long>(k)));
351 for (std::size_t c = 0; c < k; ++c) {
352 q[0][c] = T(x[c] * qc[0][c] + q0[0][c]);
353 q[1][c] = T(x[c] * qc[1][c] + q0[1][c]);
354 q[2][c] = uni;
355 }
356 finish(q);
357 return out;
358 }
359
360 // 6. GENERAL: the joint (F, B) program in 2k variables.
361 std::vector<std::vector<T>> qf(3, std::vector<T>(k, zero)), qb(3, std::vector<T>(k, zero)),
362 q0(3, std::vector<T>(k, zero));
363 for (std::size_t c = 0; c < k; ++c) {
364 if (form == 1) {
365 const T z = T(r1 * r2 - r2 + one);
366 qf[0][c] = zero;
367 qb[0][c] = T(-(p[c] * z) / ((h2 - h1 * r2) * (r1 - one) * (r2 - one)));
368 q0[0][c] = T((p[c] * (h1 + h2 - h1 * r2) * z) /
369 ((h2 - h1 * r2) * (r1 - one) * (r2 - one)));
370 qf[1][c] = T(-(p[c] * z) / (r1 * (h1 + h2 * (r1 - one)) * (r2 - one)));
371 qb[1][c] = T(-(p[c] * z) / (r1 * (h2 - h1 * r2) * (r2 - one)));
372 q0[1][c] = T((p[c] * z) / ((r1 - one) * (r2 - one)) +
373 (h1 * p[c] * z) / (r1 * (h2 - h1 * r2) * (r2 - one)) -
374 (h1 * p[c] * z) / (r1 * (h1 + h2 * (r1 - one)) * (r1 - one) * (r2 - one)));
375 qf[2][c] = T(-(p[c] * z) / (r1 * r2 * (h1 - h2 + h2 * r1)));
376 qb[2][c] = zero;
377 q0[2][c] = T((p[c] * (h1 + h2 * r1) * z) / (r1 * r2 * (h1 - h2 + h2 * r1)));
378 } else {
379 const T z = T(r1 + r2 - r1 * r2 - two);
380 const T d2 = T(h1 - h2 - h1 * r1 + h1 * r1 * r2);
381 qf[0][c] = zero;
382 qb[0][c] = T(-(p[c] * z) / ((r1 - one) * (r2 - one) * d2));
383 q0[0][c] = T((p[c] * (h2 + h1 * r1 - h1 * r1 * r2) * z) /
384 ((r1 - one) * (r2 - one) * d2));
385 qf[1][c] = T((p[c] * z) / ((r2 - one) * (h1 - h2 + h2 * r1)));
386 qb[1][c] = zero;
387 q0[1][c] = T(-(h2 * p[c] * z) / ((r2 - one) * (h1 - h2 + h2 * r1)));
388 qf[2][c] = T((p[c] * z) / (r2 * (h1 + h2 * (r1 - one))));
389 qb[2][c] = T((p[c] * z) / (r2 * d2));
390 q0[2][c] = T((h1 * p[c] * z) / (r2 * (h1 + h2 * (r1 - one)) * (r1 - one)) -
391 (h1 * p[c] * z) / (r2 * d2) - (p[c] * z) / (r2 * (r1 - one)));
392 }
393 }
394
395 // The variables interleave: x[2c] is F(c), x[2c+1] is B(c).
396 std::vector<T> target(2 * k, zero), w(2 * k, zero);
397 for (std::size_t c = 0; c < k; ++c) {
398 target[2 * c] = F[c];
399 target[2 * c + 1] = B[c];
400 w[2 * c] = T(cw[c] * fbw[0]);
401 w[2 * c + 1] = T(cw[c] * fbw[1]);
402 }
403 auto qof = [&](const std::vector<T>& x, std::size_t j, std::size_t c) {
404 return T(x[2 * c] * qf[j][c] + x[2 * c + 1] * qb[j][c] + q0[j][c]);
405 };
406 const std::vector<T> x = mamapdetail::solve_split(target, w, 3, k, qof);
407 std::vector<std::vector<T>> q(3, std::vector<T>(k, zero));
408 for (std::size_t c = 0; c < k; ++c)
409 for (std::size_t j = 0; j < 3; ++j) q[j][c] = qof(x, j, c);
410 finish(q);
411 return out;
412}
413
414/**
415 * Fit a MAMAP(2,m) to three moments, the decay rate, the class probabilities and
416 * the forward and backward moments, over every AMAP(2) form.
417 */
418template <class T>
419Mmap<T> mamap2m_fit_gamma_fb(const T& M1, const T& M2, const T& M3, const T& GAMMA,
420 const std::vector<T>& p, const std::vector<T>& F,
421 const std::vector<T>& B) {
422 const Amap2FitGammaResult<T> a = amap2_fit_gamma(M1, M2, M3, GAMMA);
423 if (a.amaps.empty() || (a.amaps.size() == 1 && a.amaps[0].order() == 1))
424 return mamapdetail::marked_poisson(M1, p);
425
426 Mmap<T> best;
427 double bestErr = 0.0;
428 bool have = false;
429 for (std::size_t j = 0; j < a.amaps.size(); ++j) {
431 try {
432 r = mamap2m_fit_fb_multiclass(a.amaps[j], p, F, B);
433 } catch (const Error&) {
434 continue;
435 }
436 double err = 0.0;
437 for (std::size_t c = 0; c < p.size(); ++c) {
438 const double df = num_traits<T>::to_double(T(r.fF[c] / F[c])) - 1.0;
439 const double db = num_traits<T>::to_double(T(r.fB[c] / B[c])) - 1.0;
440 err += df * df + db * db;
441 }
442 if (!have || err < bestErr) {
443 bestErr = err;
444 best = r.mmap;
445 have = true;
446 }
447 }
448 if (!have)
449 throw NumericError(
450 "mamap2m_fit_gamma_fb: no AMAP(2) form admits a valid class split for the requested "
451 "class probabilities and forward/backward moments");
452 return best;
453}
454
455/**
456 * The full `mamap2m_fit` dispatcher.
457 *
458 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit.m. It chooses WHICH pair of
459 * descriptors to match from the AMAP's degeneracies and from the caller's
460 * weights over (forward, backward, sigma):
461 *
462 * - more than two classes: F+B, since the sigma fitters are two-class only;
463 * - a negligible gamma: the process is renewal, so a MAPH is fitted instead;
464 * - otherwise one AMAP(2) form at a time, taking F+B, F+S or B+S as the
465 * degeneracy allows and the weights prefer, and keeping the form whose
466 * achieved descriptors land closest.
467 *
468 * The sigma branches call `mamap22_fit_fs_multiclass` and
469 * `mamap22_fit_bs_multiclass` (mamap22_fit_fs.h, mamap22_fit_bs.h); they fire
470 * when the weights prefer sigma over forward or backward, and on the
471 * degenerate AMAP shapes. With the reference's DEFAULT weights (1,1,1) F+B is
472 * preferred.
473 *
474 * @param fbsWeights the (forward, backward, sigma) weights; empty means (1,1,1)
475 */
476template <class T>
477Mmap<T> mamap2m_fit(const T& M1, const T& M2, const T& M3, const T& GAMMA,
478 const std::vector<T>& p, const std::vector<T>& F, const std::vector<T>& B,
479 const Matrix<T>& S, const std::vector<T>& fbsWeights = std::vector<T>()) {
480 const T one = num_traits<T>::from_int(1);
481 std::vector<T> w = fbsWeights;
482 if (w.empty()) w.assign(3, one);
483 if (w.size() != 3) throw InputError("mamap2m_fit: three (F, B, S) weights are required");
484 const Matrix<T>& S_or_empty = S;
485
486 const double gammatol = 1e-4, degentol = 1e-8;
487 if (p.size() > 2) return mamap2m_fit_gamma_fb(M1, M2, M3, GAMMA, p, F, B);
488 if (std::fabs(num_traits<T>::to_double(GAMMA)) < gammatol) return maph2m_fit(M1, M2, M3, p, B);
489
490 const Amap2FitGammaResult<T> a = amap2_fit_gamma(M1, M2, M3, GAMMA);
491 if (a.amaps.empty() || (a.amaps.size() == 1 && a.amaps[0].order() == 1))
492 return mamapdetail::marked_poisson(M1, p);
493
494 const bool preferFB = !(w[0] < w[2]) && !(w[1] < w[2]);
495 const bool preferFS = !preferFB && !(w[0] < w[1]);
496
497 Mmap<T> best;
498 double bestErr = 0.0;
499 bool have = false;
500 for (std::size_t j = 0; j < a.amaps.size(); ++j) {
501 const Map<T>& mp = a.amaps[j];
502 if (mp.order() != 2) continue;
503 const T h1 = T(-one / mp.D0(0, 0)), h2 = T(-one / mp.D0(1, 1));
504 const T r1 = T(h1 * mp.D0(0, 1)), r2 = T(h2 * mp.D1(1, 1));
505 const double dh1 = num_traits<T>::to_double(h1), dh2 = num_traits<T>::to_double(h2);
506 const double dr1 = num_traits<T>::to_double(r1), dr2 = num_traits<T>::to_double(r2);
507 const bool posGamma = num_traits<T>::to_double(GAMMA) > 0.0;
508
509 // The reference's degeneracy ladder: several shapes identify only one
510 // of the two moments, and there sigma is the second descriptor.
511 bool needsFs = false, needsBs = false;
512 if (posGamma) {
513 if (std::fabs(dh2 - dh1 * dr2) < degentol) needsFs = true;
514 else if (std::fabs(dh1 - dh2 + dh2 * dr1) < degentol) needsBs = true;
515 else if (1.0 - dr1 < degentol) needsFs = true;
516 } else {
517 if (std::fabs(dh1 - dh2 - dh1 * dr1 + dh1 * dr1 * dr2) < degentol) needsFs = true;
518 else if (std::fabs(dh1 - dh2 + dh2 * dr1) < degentol) needsBs = true;
519 }
520 // Outside the degeneracies the weights decide, as in the reference.
521 if (!needsFs && !needsBs && !preferFB) {
522 if (preferFS) needsFs = true;
523 else needsBs = true;
524 }
525
526 Mmap<T> cand;
527 std::vector<T> cF, cB;
528 try {
529 if (needsFs) {
530 const Mamap22FsFitResult<T> rf = mamap22_fit_fs_multiclass(mp, p, F, S_or_empty);
531 cand = rf.mmap;
532 } else if (needsBs) {
533 const Mamap22FitResult<T> rb = mamap22_fit_bs_multiclass(mp, p, B, S_or_empty);
534 cand = rb.mmap;
535 } else {
536 const Mamap2mFitResult<T> r = mamap2m_fit_fb_multiclass(mp, p, F, B);
537 cand = r.mmap;
538 }
539 } catch (const Error&) {
540 continue;
541 }
542 // Score every candidate the same way, on the descriptors it achieved.
543 const std::vector<unsigned> ord1(1, 1u);
544 const Matrix<T> fmc = mmap_forward_moment(cand, ord1, true);
545 const std::vector<std::vector<T>> bmc = mmap_backward_moment(cand, ord1, true);
546 const Matrix<T> fsc = mmap_sigma(cand);
547 double err = 0.0;
548 for (std::size_t c = 0; c < p.size(); ++c) {
549 const double df = num_traits<T>::to_double(T(F[c] / fmc(c, 0))) - 1.0;
550 const double db = num_traits<T>::to_double(T(B[c] / bmc[c][0])) - 1.0;
551 err += num_traits<T>::to_double(w[0]) * df * df +
552 num_traits<T>::to_double(w[1]) * db * db;
553 }
554 if (S_or_empty.rows() > 0 && fsc.rows() > 0) {
555 const double ds = num_traits<T>::to_double(T(S_or_empty(0, 0) / fsc(0, 0))) - 1.0;
556 err += num_traits<T>::to_double(w[2]) * ds * ds;
557 }
558 if (!have || err < bestErr) {
559 bestErr = err;
560 best = cand;
561 have = true;
562 }
563 }
564 if (!have)
565 throw NumericError(
566 "mamap2m_fit: no AMAP(2) form admits a valid class split for the requested "
567 "descriptors");
568 return best;
569}
570
571/**
572 * Fit a MAMAP(2,m) from a marked trace through the (F, B) pair alone.
573 *
574 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit_gamma_fb_trace.m: the
575 * descriptors are the class probabilities and the per-class FORWARD and
576 * BACKWARD first moments, with no sigma and no descriptor-pair selection.
577 * `mamap2m_fit_trace` is the full dispatcher of the reference.
578 *
579 * @param Tv the inter-arrival times
580 * @param A the class of each arrival, 1-based
581 */
582template <class T>
583Mmap<T> mamap2m_fit_gamma_fb_trace(const std::vector<T>& Tv, const std::vector<int>& A) {
584 if (Tv.empty() || Tv.size() != A.size())
585 throw InputError(
586 "mamap2m_fit_gamma_fb_trace: the trace and its labels must agree in length");
587 const T zero = num_traits<T>::from_int(0);
588 T m1 = zero, m2 = zero, m3 = zero;
589 for (std::size_t i = 0; i < Tv.size(); ++i) {
590 const T x = Tv[i];
591 m1 += x;
592 m2 += x * x;
593 m3 += x * x * x;
594 }
595 const T n = num_traits<T>::from_int(static_cast<long>(Tv.size()));
596 const std::vector<unsigned> one_order(1, 1u);
597 const std::vector<T> p = trace::mtrace_pc<T>(A);
598 const Matrix<T> fm = trace::mtrace_forward_moment(Tv, A, one_order);
599 const Matrix<T> bm = trace::mtrace_backward_moment(Tv, A, one_order);
600 std::vector<T> F(p.size(), zero), B(p.size(), zero);
601 for (std::size_t c = 0; c < p.size(); ++c) {
602 F[c] = fm(c, 0);
603 B[c] = bm(c, 0);
604 }
605 return mamap2m_fit_gamma_fb(T(m1 / n), T(m2 / n), T(m3 / n),
606 line::trace::trace_gamma(Tv).gamma, p, F, B);
607}
608
609/**
610 * Fit a MAPH(2,m) or MAMAP(2,m) matching the characteristics of a marked
611 * trace.
612 *
613 * Port of matlab/lib/m3a/m3a/mamap2m/mamap2m_fit_trace.m: the class
614 * probabilities are always matched exactly; the remaining two characteristics
615 * default to the forward and backward moments unless the underlying AMAP(2)
616 * is degenerate, and `fbsWeights` moves the preference among (forward,
617 * backward, sigma). Unlike `mamap2m_fit_gamma_fb_trace` this computes the
618 * class transition probabilities (`mtrace_sigma`) and dispatches through the
619 * full `mamap2m_fit` descriptor selection.
620 *
621 * @param Tv the inter-arrival times
622 * @param A the class of each arrival, 1-based
623 * @param fbsWeights the (forward, backward, sigma) weights; empty means (1,1,1)
624 */
625template <class T>
626Mmap<T> mamap2m_fit_trace(const std::vector<T>& Tv, const std::vector<int>& A,
627 const std::vector<T>& fbsWeights = std::vector<T>()) {
628 if (Tv.empty() || Tv.size() != A.size())
629 throw InputError("mamap2m_fit_trace: the trace and its labels must agree in length");
630 const T zero = num_traits<T>::from_int(0);
631 T m1 = zero, m2 = zero, m3 = zero;
632 for (std::size_t i = 0; i < Tv.size(); ++i) {
633 const T x = Tv[i];
634 m1 += x;
635 m2 += x * x;
636 m3 += x * x * x;
637 }
638 const T n = num_traits<T>::from_int(static_cast<long>(Tv.size()));
639 const std::vector<unsigned> one_order(1, 1u);
640 const std::vector<T> p = trace::mtrace_pc<T>(A);
641 const Matrix<T> fm = trace::mtrace_forward_moment(Tv, A, one_order);
642 const Matrix<T> bm = trace::mtrace_backward_moment(Tv, A, one_order);
643 std::vector<T> F(p.size(), zero), B(p.size(), zero);
644 for (std::size_t c = 0; c < p.size(); ++c) {
645 F[c] = fm(c, 0);
646 B[c] = bm(c, 0);
647 }
649 return mamap2m_fit(T(m1 / n), T(m2 / n), T(m3 / n), line::trace::trace_gamma(Tv).gamma, p,
650 F, B, S, fbsWeights);
651}
652
653} // namespace mam
654} // namespace line
655
656#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.
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.
Compression of a marked MAP into a smaller representation, and the two M3A primitives it is built fro...
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
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...
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.
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...
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
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,...
T num_abs(const T &v)
Definition number.h:172
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:87
std::vector< T > fF
achieved per-class forward moments
Definition mamap2m_fit.h:89
std::vector< T > fB
achieved per-class backward moments
Definition mamap2m_fit.h:90
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
Autocorrelation decay rate of a trace: the gamma of the geometric model rho(k) = rho0 * gamma^k,...