LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmap_k_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_MMAP_K_FIT_H
6#define LINE_API_MAM_MMAP_K_FIT_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * EXACT inverses of the class-marking map: solve for the split directly instead
12 * of optimizing it.
13 *
14 * Templated port of matlab/lib/m3a/m3a/mamap2m/mmap2k_fit.m and mmap3k_fit.m.
15 *
16 * `mamap2m_fit_fb_multiclass` treats the split as a quadratic program because
17 * the per-class targets are generally unreachable. When the number of free
18 * marking parameters EQUALS the number of characteristics being matched, the
19 * system is square and can simply be solved; these two functions do that, and
20 * report whether the solution landed inside the unit box.
21 *
22 * - `mmap2k_fit` inverts an AMAP(2)'s three flows against (p, F, B) per class
23 * in closed form, one class at a time, and falls back to
24 * `mamap2m_fit_gamma_fb` when no AMAP(2) form gives a feasible split.
25 * - `mmap3k_fit` does it for a MAP of any order by building the marking system
26 * M q = y explicitly: row (a,b) of M is pie (-D0)^-a Dc (-D0)^-b e, which is
27 * the characteristic that row matches, and the target vector y carries
28 * p, p F, p B and p B2 for the orders (1,0), (1,1), (2,0) and (3,0).
29 *
30 * EXACT IS REPORTED, NOT ASSUMED. Both return a flag saying whether the solved
31 * split was feasible; when it was not, the entries are clamped into [0,1] and
32 * the flag is false, so a caller can tell an exact fit from a projected one.
33 * Silently clamping and calling the result exact is the failure mode these
34 * functions exist to avoid.
35 *
36 * THE DEGENERATE LOCUS IS REFUSED, not worked around. `mmap2k_fit` skips an
37 * AMAP form whose denominators vanish; `mmap3k_fit` refuses outright when the
38 * marking matrix is singular, because there the characteristics do not
39 * determine the split and any answer would be arbitrary.
40 *
41 * ARITHMETIC: transcendental, through amap2_fit_gamma and the linear solves.
42 */
43
44#include <cmath>
45#include <cstddef>
46#include <vector>
47
52#include "line/num/number.h"
53#include "line/util/error.h"
54#include "line/util/linalg.h"
55#include "line/util/lu.h"
56#include "line/util/matrix.h"
57
58namespace line {
59namespace mam {
60
61/** A marked MAP and whether the marking system was solved exactly. */
62template <class T>
65 bool exact = false;
66};
67
68namespace kfitdetail {
69
70/** The closed-form marking inverse of one class, for the two canonical forms. */
71template <class T>
72bool marking_inverse(int form, const T& h1, const T& h2, const T& r1, const T& r2, const T& p,
73 const T& Fc, const T& Bc, double degentol, T* q1, T* q2, T* q3) {
74 const T one = num_traits<T>::from_int(1), two = num_traits<T>::from_int(2);
75 auto small = [&](const T& v) { return std::fabs(num_traits<T>::to_double(v)) < degentol; };
76
77 if (form == 1) {
78 const T d1 = T((r2 - one) * (r1 - one) * (h1 * r2 - h2));
79 const T d2 = T((r2 - one) * r1);
80 const T d3 = T(r1 * r2 * (h1 + h2 * r1 - h2));
81 const T e1 = T(h1 + h2 * r1 - h2), e2 = T(h1 * r2 - h2);
82 if (small(d1) || small(d2) || small(d3) || small(e1) || small(e2)) return false;
83 const T W = T(r1 * r2 - r2 + one);
84 *q1 = T(p * W * ((h1 * r2 - h1 - h2) + Bc) / d1);
85 *q2 = T(p * W *
86 ((h1 * h1 * (r2 - one) + h1 * h2 * r1 * (r2 - one) - h2 * h2 * r1) / (e1 * e2) -
87 Fc / e1 + Bc / e2) /
88 d2);
89 *q3 = T(p * W * ((h1 + h2 * r1) - Fc) / d3);
90 } else {
91 const T U = T(h1 * r1 * r2 - h1 * r1 + h1 - h2);
92 const T d1 = T((r2 - one) * (r1 - one) * U);
93 const T e1 = T(h1 + h2 * r1 - h2);
94 const T d2 = T((r2 - one) * e1);
95 if (small(d1) || small(d2) || small(r2) || small(U) || small(e1)) return false;
96 const T V = T(r1 * r2 - r1 - r2 + two);
97 *q1 = T(p * V * ((h1 * r1 * r2 - h1 * r1 - h2) + Bc) / d1);
98 *q2 = T(p * V * (h2 - Fc) / d2);
99 *q3 = T(p * V *
100 ((h1 * h1 + h1 * h2 * r1 * r2 - h2 * h2) / (e1 * U) - Fc / e1 - Bc / U) / r2);
101 }
102 return true;
103}
104
105/** The characteristic orders (a, b) the marking system matches, by MAP order. */
106inline std::vector<std::pair<unsigned, unsigned>> marking_orders(std::size_t n, std::size_t z) {
107 std::vector<std::pair<unsigned, unsigned>> o;
108 if (n == 2) {
109 o.push_back(std::make_pair(1u, 0u));
110 o.push_back(std::make_pair(1u, 1u));
111 o.push_back(std::make_pair(2u, 0u));
112 } else if (n == 3) {
113 o.push_back(std::make_pair(1u, 0u));
114 o.push_back(std::make_pair(1u, 1u));
115 o.push_back(std::make_pair(2u, 0u));
116 o.push_back(std::make_pair(3u, 0u));
117 } else {
118 o.push_back(std::make_pair(1u, 0u));
119 o.push_back(std::make_pair(1u, 1u));
120 unsigned a = 2;
121 while (o.size() < n + 1) o.push_back(std::make_pair(a++, 0u));
122 }
123 if (o.size() > z) o.resize(z);
124 return o;
125}
126
127} // namespace kfitdetail
128
129/**
130 * Exact marking of an AMAP(2) against per-class (p, F, B).
131 *
132 * @param M1,M2,M3 the first three moments of the inter-arrival time
133 * @param GAMMA the autocorrelation decay rate
134 * @param P,F,B per-class probabilities, forward and backward moments
135 */
136template <class T>
137MmapKFitResult<T> mmap2k_fit(const T& M1, const T& M2, const T& M3, const T& GAMMA,
138 const std::vector<T>& P, const std::vector<T>& F,
139 const std::vector<T>& B) {
140 static_assert(num_traits<T>::has_transcendental, "mmap2k_fit inverts a moment system");
141 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
142 const std::size_t K = P.size();
143 if (K == 0) throw InputError("mmap2k_fit: no classes given");
144 if (F.size() != K || B.size() != K)
145 throw InputError("mmap2k_fit: P, F and B must have the same length");
146 const double degentol = 1e-8, feastol = 1e-8;
147
148 const Amap2FitGammaResult<T> a = amap2_fit_gamma(M1, M2, M3, GAMMA);
149 bool haveBest = false;
150 double bestViol = 0.0;
151 Map<T> bestMap;
152 int bestForm = 1;
153 std::vector<std::vector<T>> bestQ;
154
155 for (std::size_t j = 0; j < a.amaps.size(); ++j) {
156 const Map<T>& mp = a.amaps[j];
157 if (mp.order() != 2) continue;
158 if (!(num_abs(T(mp.D0(1, 0))) <= zero)) continue;
159 int form;
160 if (mp.D1(0, 1) == zero) form = 1;
161 else if (mp.D1(0, 0) == zero) form = 2;
162 else continue;
163
164 const T h1 = T(-one / mp.D0(0, 0)), h2 = T(-one / mp.D0(1, 1));
165 const T r1 = T(mp.D0(0, 1) * h1), r2 = T(mp.D1(1, 1) * h2);
166
167 std::vector<std::vector<T>> q(3, std::vector<T>(K, zero));
168 bool ok = true;
169 for (std::size_t c = 0; c < K && ok; ++c)
170 ok = kfitdetail::marking_inverse(form, h1, h2, r1, r2, P[c], F[c], B[c], degentol,
171 &q[0][c], &q[1][c], &q[2][c]);
172 if (!ok) continue;
173
174 // Feasibility: inside the unit box, and each flow's split summing to one.
175 double viol = 0.0;
176 for (std::size_t jj = 0; jj < 3; ++jj) {
177 T s = zero;
178 for (std::size_t c = 0; c < K; ++c) {
179 const double v = num_traits<T>::to_double(q[jj][c]);
180 viol = std::max(viol, std::max(0.0, -v));
181 viol = std::max(viol, std::max(0.0, v - 1.0));
182 s += q[jj][c];
183 }
184 viol = std::max(viol, std::fabs(num_traits<T>::to_double(s) - 1.0));
185 }
186 if (!haveBest || viol < bestViol) {
187 haveBest = true;
188 bestViol = viol;
189 bestMap = mp;
190 bestForm = form;
191 bestQ = q;
192 }
193 }
194
196 if (haveBest && bestViol <= feastol) {
197 out.mmap.D0 = bestMap.D0;
198 out.mmap.D1 = bestMap.D1;
199 out.mmap.Dc.assign(K, Matrix<T>(2, 2, zero));
200 for (std::size_t c = 0; c < K; ++c) {
201 for (std::size_t jj = 0; jj < 3; ++jj) {
202 if (bestQ[jj][c] < zero) bestQ[jj][c] = zero;
203 if (bestQ[jj][c] > one) bestQ[jj][c] = one;
204 }
205 if (bestForm == 1) {
206 out.mmap.Dc[c](0, 0) = T(bestMap.D1(0, 0) * bestQ[0][c]);
207 out.mmap.Dc[c](1, 0) = T(bestMap.D1(1, 0) * bestQ[1][c]);
208 out.mmap.Dc[c](1, 1) = T(bestMap.D1(1, 1) * bestQ[2][c]);
209 } else {
210 out.mmap.Dc[c](0, 1) = T(bestMap.D1(0, 1) * bestQ[0][c]);
211 out.mmap.Dc[c](1, 0) = T(bestMap.D1(1, 0) * bestQ[1][c]);
212 out.mmap.Dc[c](1, 1) = T(bestMap.D1(1, 1) * bestQ[2][c]);
213 }
214 }
215 out.exact = true;
216 return out;
217 }
218 out.mmap = mamap2m_fit_gamma_fb(M1, M2, M3, GAMMA, P, F, B);
219 out.exact = false;
220 return out;
221}
222
223/**
224 * Exact marking of an arbitrary MAP by solving the marking system directly.
225 *
226 * @param D0,D1 the underlying MAP
227 * @param P,F,B per-class probabilities, forward and backward moments
228 * @param B2 per-class second-order backward moments; required once the MAP
229 * has more than three arrival entries to mark
230 */
231template <class T>
232MmapKFitResult<T> mmap3k_fit(const Matrix<T>& D0, const Matrix<T>& D1, const std::vector<T>& P,
233 const std::vector<T>& F, const std::vector<T>& B,
234 const std::vector<T>& B2 = std::vector<T>()) {
235 static_assert(num_traits<T>::has_transcendental, "mmap3k_fit inverts a moment system");
236 const T zero = num_traits<T>::from_int(0), one = num_traits<T>::from_int(1);
237 const std::size_t K = P.size(), n = D0.rows();
238 if (K == 0) throw InputError("mmap3k_fit: no classes given");
239 if (F.size() != K || B.size() != K)
240 throw InputError("mmap3k_fit: P, F and B must have the same length");
241 if (n == 0 || D1.rows() != n) throw InputError("mmap3k_fit: D0 and D1 disagree");
242 const double feastol = 1e-8;
243
244 // The entries of D1 that can carry a mark.
245 std::vector<std::pair<std::size_t, std::size_t>> nz;
246 for (std::size_t i = 0; i < n; ++i)
247 for (std::size_t j = 0; j < n; ++j)
248 if (!(D1(i, j) == zero)) nz.push_back(std::make_pair(i, j));
249 const std::size_t z = nz.size();
250 if (z == 0) throw InputError("mmap3k_fit: the MAP has no arrival transitions to mark");
251
252 const std::vector<std::pair<unsigned, unsigned>> orders = kfitdetail::marking_orders(n, z);
253 std::vector<T> b2 = B2;
254 if (b2.empty()) {
255 for (std::size_t i = 0; i < orders.size(); ++i)
256 if (orders[i].first == 3)
257 throw InputError(
258 "mmap3k_fit: this MAP's marking system includes the third-order backward "
259 "characteristic, so the second-order backward moments B2 are required");
260 b2.assign(K, zero);
261 }
262
263 Matrix<T> negD0(n, n, zero);
264 for (std::size_t i = 0; i < n; ++i)
265 for (std::size_t j = 0; j < n; ++j) negD0(i, j) = -D0(i, j);
266 const Matrix<T> A = inverse(negD0);
267
268 // The stationary law at arrival epochs: pie (A D1) = pie, sum pie = 1.
269 const Matrix<T> Pemb = matmul(A, D1);
270 Matrix<T> Tm(n, n, zero);
271 for (std::size_t i = 0; i < n; ++i)
272 for (std::size_t j = 0; j < n; ++j) Tm(i, j) = T(Pemb(j, i) - (i == j ? one : zero));
273 for (std::size_t j = 0; j < n; ++j) Tm(n - 1, j) = one;
274 std::vector<T> rhs(n, zero);
275 rhs[n - 1] = one;
276 const std::vector<T> pie = solve(Tm, rhs);
277
278 // M(ii, jj) = pie A^a Dc_jj A^b e for the characteristic (a, b) of row ii.
279 std::vector<Matrix<T>> Apow(1, eye<T>(n));
280 std::size_t maxpow = 0;
281 for (std::size_t i = 0; i < orders.size(); ++i)
282 maxpow = std::max<std::size_t>(maxpow, std::max(orders[i].first, orders[i].second));
283 for (std::size_t k = 1; k <= maxpow; ++k) Apow.push_back(matmul(Apow[k - 1], A));
284
285 Matrix<T> M(z, z, zero);
286 for (std::size_t jj = 0; jj < z; ++jj) {
287 Matrix<T> Dc(n, n, zero);
288 Dc(nz[jj].first, nz[jj].second) = D1(nz[jj].first, nz[jj].second);
289 for (std::size_t ii = 0; ii < orders.size(); ++ii) {
290 const Matrix<T> L = matmul(Apow[orders[ii].first], Dc);
291 const Matrix<T> R = matmul(L, Apow[orders[ii].second]);
292 T acc = zero;
293 for (std::size_t a = 0; a < n; ++a)
294 for (std::size_t b = 0; b < n; ++b) acc += pie[a] * R(a, b);
295 M(ii, jj) = acc;
296 }
297 }
298
299 // The degenerate locus shows up as a singular marking matrix; `solve`
300 // reports it, and it is rethrown by name rather than left as a bare
301 // linear-algebra failure the caller cannot act on.
302 std::vector<std::vector<T>> q(z, std::vector<T>(K, zero));
303 for (std::size_t c = 0; c < K; ++c) {
304 std::vector<T> y(z, zero);
305 for (std::size_t ii = 0; ii < orders.size(); ++ii) {
306 const unsigned a = orders[ii].first, b = orders[ii].second;
307 if (a == 1 && b == 0) y[ii] = P[c];
308 else if (a == 1 && b == 1) y[ii] = T(P[c] * F[c]);
309 else if (a == 2 && b == 0) y[ii] = T(P[c] * B[c]);
310 else if (a == 3 && b == 0) y[ii] = T(P[c] * b2[c]);
311 else
312 throw InputError(
313 "mmap3k_fit: no target is supplied for one of the marking characteristics");
314 }
315 std::vector<T> x;
316 try {
317 x = solve(M, y);
318 } catch (const NumericError&) {
319 throw NumericError(
320 "mmap3k_fit: the underlying MAP is on the degenerate locus of the marking system, "
321 "where the characteristics do not determine the split");
322 }
323 for (std::size_t jj = 0; jj < z; ++jj) q[jj][c] = x[jj];
324 }
325
326 double viol = 0.0;
327 for (std::size_t jj = 0; jj < z; ++jj) {
328 T s = zero;
329 for (std::size_t c = 0; c < K; ++c) {
330 const double v = num_traits<T>::to_double(q[jj][c]);
331 viol = std::max(viol, std::max(0.0, -v));
332 viol = std::max(viol, std::max(0.0, v - 1.0));
333 s += q[jj][c];
334 }
335 viol = std::max(viol, std::fabs(num_traits<T>::to_double(s) - 1.0));
336 }
337
339 out.exact = viol <= feastol;
340 out.mmap.D0 = D0;
341 out.mmap.D1 = D1;
342 out.mmap.Dc.assign(K, Matrix<T>(n, n, zero));
343 for (std::size_t c = 0; c < K; ++c)
344 for (std::size_t jj = 0; jj < z; ++jj) {
345 T v = q[jj][c];
346 if (v < zero) v = zero;
347 if (v > one) v = one;
348 out.mmap.Dc[c](nz[jj].first, nz[jj].second) =
349 T(D1(nz[jj].first, nz[jj].second) * v);
350 }
351 return out;
352}
353
354} // namespace mam
355} // namespace line
356
357#endif // LINE_API_MAM_MMAP_K_FIT_H
AMAP(2) fit of three moments and the autocorrelation decay rate (matlab/lib/m3a/m3a/amap2/amap2_fit_g...
InputError(const std::string &what)
Definition error.h:39
std::size_t rows() const
Definition matrix.h:89
The algorithm cannot proceed on this instance (singular matrix, ...).
Definition error.h:43
NumericError(const std::string &what)
Definition error.h:45
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
LU factorization with partial pivoting, templated on the number type.
Fit a MAMAP(2,m): a second-order acyclic MAP marked with m classes, matching the forward and backward...
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
MmapKFitResult< T > mmap2k_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)
Exact marking of an AMAP(2) against per-class (p, F, B).
Definition mmap_k_fit.h:137
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).
MmapKFitResult< T > mmap3k_fit(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &P, const std::vector< T > &F, const std::vector< T > &B, const std::vector< T > &B2=std::vector< T >())
Exact marking of an arbitrary MAP by solving the marking system directly.
Definition mmap_k_fit.h:232
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...
T num_abs(const T &v)
Definition number.h:172
Matrix< T > inverse(const Matrix< T > &A)
Inverse by LU with one factorization and n back substitutions.
Definition linalg.h:72
Matrix< T > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Definition lu.h:158
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
Result of amap2_fit_gamma.
std::vector< Map< T > > amaps
every exact solution found
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
A marked MAP and whether the marking system was solved exactly.
Definition mmap_k_fit.h:63
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45