LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmap_stats.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_STATS_H
6#define LINE_API_MAM_MMAP_STATS_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Marked MAP statistics: embedded chains, class-transition probabilities,
12 * forward and cross moments, counting means and covariances.
13 *
14 * Templated port of the M3A MMAP statistics in matlab/lib/m3a/m3a/mmap:
15 * mmap_pie.m, mmap_embedded.m, mmap_maps.m, mmap_timereverse.m, mmap_sigma.m,
16 * mmap_sigma2.m, mmap_count_mean.m, mmap_count_idc.m, mmap_count_mcov.m,
17 * mmap_idc.m, mmap_cross_moment.m, mmap_forward_moment.m and mmap_sum.m.
18 *
19 * The embedded per-class matrix is E_c = (-D0)^-1 D1^(c). It is SUBstochastic,
20 * not stochastic: its row sums give the probability that the next arrival is of
21 * class c, which is what makes sum_c E_c the embedded chain of the aggregate
22 * MAP and E_c on its own the class-c defective kernel. Reading E_c as a
23 * transition matrix and renormalizing it is the classic way to get the
24 * per-class moments wrong.
25 *
26 * mmap_issym and mmap_shorten have no C++ counterpart: the first asks whether
27 * the MATLAB cell holds symbolic entries, which here is the template parameter,
28 * and the second reorders a MATLAB cell into BUTools order, which the Mmap
29 * struct already encodes by construction.
30 */
31
32#include <cstddef>
33#include <vector>
34
39#include "line/num/number.h"
40#include "line/util/error.h"
41#include "line/util/linalg.h"
42#include "line/util/lu.h"
43#include "line/util/matrix.h"
44
45namespace line {
46namespace mam {
47
48namespace detail {
49
50/** E_c = (-D0)^-1 D1^(c), the defective embedded kernel of class c. */
51template <class T>
52Matrix<T> mmap_embedded_class(const Mmap<T>& mm, std::size_t c) {
53 Matrix<T> negD0 = mm.D0;
54 for (std::size_t i = 0; i < negD0.rows(); ++i)
55 for (std::size_t j = 0; j < negD0.cols(); ++j) negD0(i, j) = -negD0(i, j);
56 return matmul(inverse(negD0), mm.Dc[c]);
57}
58
59} // namespace detail
60
61/** Embedded per-class kernels E_c = (-D0)^-1 D1^(c). */
62template <class T>
63std::vector<Matrix<T>> mmap_embedded(const Mmap<T>& mm) {
64 std::vector<Matrix<T>> Pc;
65 Pc.reserve(mm.classes());
66 for (std::size_t c = 0; c < mm.classes(); ++c) Pc.push_back(detail::mmap_embedded_class(mm, c));
67 return Pc;
68}
69
70/** The C MAPs seen by each class, MAP_c = (D0 + D1 - D1^(c), D1^(c)). */
71template <class T>
72std::vector<Map<T>> mmap_maps(const Mmap<T>& mm) {
73 std::vector<Map<T>> maps;
74 maps.reserve(mm.classes());
75 for (std::size_t c = 0; c < mm.classes(); ++c) {
76 Matrix<T> A = mm.D0;
77 for (std::size_t i = 0; i < A.rows(); ++i)
78 for (std::size_t j = 0; j < A.cols(); ++j) A(i, j) += mm.D1(i, j) - mm.Dc[c](i, j);
79 maps.push_back(Map<T>{A, mm.Dc[c]});
80 }
81 return maps;
82}
83
84/**
85 * Stationary phase distribution seen just after a class-c arrival, one row per
86 * class. The row is the left invariant vector of the STOCHASTIC matrix
87 * P_c = (-D0 - D1 + D1^(c))^-1 D1^(c), which is the chain watched only at
88 * class-c epochs, and is not E_c.
89 */
90template <class T>
92 const std::size_t n = mm.order();
93 const std::size_t C = mm.classes();
95 for (std::size_t c = 0; c < C; ++c) {
97 for (std::size_t i = 0; i < n; ++i)
98 for (std::size_t j = 0; j < n; ++j)
99 B(i, j) = -mm.D0(i, j) - mm.D1(i, j) + mm.Dc[c](i, j);
100 const Matrix<T> Pc = matmul(inverse(B), mm.Dc[c]);
102 for (std::size_t i = 0; i + 1 < n; ++i)
103 for (std::size_t j = 0; j < n; ++j)
104 A(i, j) = Pc(j, i) - (i == j ? num_traits<T>::from_int(1)
106 for (std::size_t j = 0; j < n; ++j) A(n - 1, j) = num_traits<T>::from_int(1);
107 std::vector<T> b(n, num_traits<T>::from_int(0));
108 b[n - 1] = num_traits<T>::from_int(1);
109 const std::vector<T> x = solve(A, b);
110 for (std::size_t j = 0; j < n; ++j) pie(c, j) = x[j];
111 }
112 return pie;
113}
114
115/** Time-reversed MMAP, D^-1 M' D with D = diag(map_prob) applied to every matrix. */
116template <class T>
118 const std::size_t n = mm.order();
119 const std::vector<T> piq = map_prob(mm.map());
120 Mmap<T> out;
121 out.D0 = Matrix<T>(n, n, num_traits<T>::from_int(0));
122 out.D1 = Matrix<T>(n, n, num_traits<T>::from_int(0));
123 for (std::size_t i = 0; i < n; ++i)
124 for (std::size_t j = 0; j < n; ++j) {
125 out.D0(i, j) = mm.D0(j, i) * piq[j] / piq[i];
126 out.D1(i, j) = mm.D1(j, i) * piq[j] / piq[i];
127 }
128 out.Dc.reserve(mm.classes());
129 for (std::size_t c = 0; c < mm.classes(); ++c) {
131 for (std::size_t i = 0; i < n; ++i)
132 for (std::size_t j = 0; j < n; ++j) R(i, j) = mm.Dc[c](j, i) * piq[j] / piq[i];
133 out.Dc.push_back(R);
134 }
135 return out;
136}
137
138/** sigma(i,j) = pie E_i E_j 1, the probability that two consecutive marks are (i,j). */
139template <class T>
141 const std::size_t C = mm.classes();
142 const std::vector<T> alpha = map_pie(mm.map());
143 const std::vector<Matrix<T>> E = mmap_embedded(mm);
144 Matrix<T> sigma(C, C, num_traits<T>::from_int(0));
145 for (std::size_t i = 0; i < C; ++i) {
146 const std::vector<T> start = vecmul(alpha, E[i]);
147 for (std::size_t j = 0; j < C; ++j) {
148 const std::vector<T> v = vecmul(start, E[j]);
149 T acc = num_traits<T>::from_int(0);
150 for (std::size_t k = 0; k < v.size(); ++k) acc += v[k];
151 sigma(i, j) = acc;
152 }
153 }
154 return sigma;
155}
156
157/** sigma2(i,j,h) = pie E_i E_j E_h 1, indexed as sigma2[i][j][h]. */
158template <class T>
159std::vector<std::vector<std::vector<T>>> mmap_sigma2(const Mmap<T>& mm) {
160 const std::size_t C = mm.classes();
161 const std::vector<T> alpha = map_pie(mm.map());
162 const std::vector<Matrix<T>> E = mmap_embedded(mm);
163 std::vector<std::vector<std::vector<T>>> sigma(
164 C, std::vector<std::vector<T>>(C, std::vector<T>(C, num_traits<T>::from_int(0))));
165 for (std::size_t i = 0; i < C; ++i) {
166 const std::vector<T> starti = vecmul(alpha, E[i]);
167 for (std::size_t j = 0; j < C; ++j) {
168 const std::vector<T> startj = vecmul(starti, E[j]);
169 for (std::size_t h = 0; h < C; ++h) {
170 const std::vector<T> v = vecmul(startj, E[h]);
171 T acc = num_traits<T>::from_int(0);
172 for (std::size_t k = 0; k < v.size(); ++k) acc += v[k];
173 sigma[i][j][h] = acc;
174 }
175 }
176 }
177 return sigma;
178}
179
180/** Per-class mean of the counting process over a window of length t. */
181template <class T>
182std::vector<T> mmap_count_mean(const Mmap<T>& mm, const T& t) {
183 const std::size_t n = mm.order();
184 const std::size_t C = mm.classes();
185 const std::vector<T> theta = map_prob(mm.map());
186 std::vector<T> mk(C, num_traits<T>::from_int(0));
187 for (std::size_t c = 0; c < C; ++c) {
188 T acc = num_traits<T>::from_int(0);
189 for (std::size_t i = 0; i < n; ++i)
190 for (std::size_t j = 0; j < n; ++j) acc += theta[i] * mm.Dc[c](i, j);
191 mk[c] = acc * t;
192 }
193 return mk;
194}
195
196/** Per-class index of dispersion of counts over a window of length t. */
197template <class T>
198std::vector<T> mmap_count_idc(const Mmap<T>& mm, const T& t) {
199 const std::vector<T> m = mmap_count_mean(mm, t);
200 const std::vector<T> v = mmap_count_var(mm, t);
201 std::vector<T> idc(m.size(), num_traits<T>::from_int(0));
202 for (std::size_t c = 0; c < m.size(); ++c) idc[c] = v[c] / m[c];
203 return idc;
204}
205
206/** Asymptotic per-class index of dispersion, evaluated at t = 1e6 / sum_c lambda_c. */
207template <class T>
208std::vector<T> mmap_idc(const Mmap<T>& mm) {
209 const std::vector<T> lam = mmap_lambda(mm);
210 T total = num_traits<T>::from_int(0);
211 for (std::size_t c = 0; c < lam.size(); ++c) total += lam[c];
212 const T tinf = num_traits<T>::from_int(1000000) / total;
213 return mmap_count_idc(mm, tinf);
214}
215
216/**
217 * Covariance matrix of the per-class counts over a window of length t. The
218 * off-diagonal entries come from the polarization identity on the pooled
219 * classes, since only the variance of a single mark is available in closed form.
220 */
221template <class T>
222Matrix<T> mmap_count_mcov(const Mmap<T>& mm, const T& t) {
223 const std::size_t C = mm.classes();
224 const std::vector<T> mV = mmap_count_var(mm, t);
226 for (std::size_t i = 0; i < C; ++i) S(i, i) = mV[i];
228 for (std::size_t i = 0; i < C; ++i)
229 for (std::size_t j = 0; j < C; ++j) {
230 if (i == j) continue;
231 Mmap<T> pooled;
232 pooled.D0 = mm.D0;
233 pooled.D1 = mm.D1;
234 Matrix<T> A = mm.Dc[i];
235 for (std::size_t r = 0; r < A.rows(); ++r)
236 for (std::size_t s = 0; s < A.cols(); ++s) A(r, s) += mm.Dc[j](r, s);
237 Matrix<T> B = mm.D1;
238 for (std::size_t r = 0; r < B.rows(); ++r)
239 for (std::size_t s = 0; s < B.cols(); ++s) B(r, s) -= A(r, s);
240 pooled.Dc.push_back(A);
241 pooled.Dc.push_back(B);
242 const std::vector<T> pV = mmap_count_var(pooled, t);
243 S(i, j) = half * (pV[0] - mV[i] - mV[j]);
244 }
245 return S;
246}
247
248/**
249 * Cross moments of order k: MC(i,j) is E[T^k] of the interval that FOLLOWS a
250 * class-i arrival, conditioned on that next arrival being of class j.
251 */
252template <class T>
253Matrix<T> mmap_cross_moment(const Mmap<T>& mm, unsigned k) {
254 const std::size_t C = mm.classes();
255 const std::vector<T> pie = map_pie(mm.map());
256 const std::vector<Matrix<T>> E = mmap_embedded(mm);
257 Matrix<T> negD0 = mm.D0;
258 for (std::size_t i = 0; i < negD0.rows(); ++i)
259 for (std::size_t j = 0; j < negD0.cols(); ++j) negD0(i, j) = -negD0(i, j);
260 const Matrix<T> M = inverse(negD0);
261 const Matrix<T> Mk1 = matpow(M, k + 1);
262 std::vector<T> TG(C, num_traits<T>::from_int(0));
263 for (std::size_t i = 0; i < C; ++i) {
264 const std::vector<T> v = vecmul(pie, E[i]);
265 for (std::size_t r = 0; r < v.size(); ++r) TG[i] += v[r];
266 }
268 const T fk = num_factorial<T>(k);
269 for (std::size_t i = 0; i < C; ++i) {
270 std::vector<T> start = vecmul(pie, E[i]);
271 for (std::size_t r = 0; r < start.size(); ++r) start[r] = start[r] / TG[i];
272 for (std::size_t j = 0; j < C; ++j) {
273 const std::vector<T> num = vecmul(vecmul(start, Mk1), mm.Dc[j]);
274 const std::vector<T> den = vecmul(start, E[j]);
276 for (std::size_t r = 0; r < num.size(); ++r) sn += num[r];
277 for (std::size_t r = 0; r < den.size(); ++r) sd += den[r];
278 MC(i, j) = fk * sn / sd;
279 }
280 }
281 return MC;
282}
283
284/**
285 * Forward moments: MOMENTS(a,h) is the order-orders[h] moment of the interval
286 * ENDING with a class-a arrival. With normalize false the per-class probability
287 * is left in, which returns the unnormalized contribution instead.
288 */
289template <class T>
290Matrix<T> mmap_forward_moment(const Mmap<T>& mm, const std::vector<unsigned>& orders,
291 bool normalize) {
292 const std::size_t C = mm.classes();
293 const std::vector<T> pie = map_pie(mm.map());
294 const std::vector<Matrix<T>> E = mmap_embedded(mm);
295 Matrix<T> negD0 = mm.D0;
296 for (std::size_t i = 0; i < negD0.rows(); ++i)
297 for (std::size_t j = 0; j < negD0.cols(); ++j) negD0(i, j) = -negD0(i, j);
298 const Matrix<T> M = inverse(negD0);
299 Matrix<T> out(C, orders.size(), num_traits<T>::from_int(0));
300 for (std::size_t a = 0; a < C; ++a) {
301 const std::vector<T> start = vecmul(pie, E[a]);
302 T pa = num_traits<T>::from_int(1);
303 if (normalize) {
305 for (std::size_t r = 0; r < start.size(); ++r) pa += start[r];
306 }
307 for (std::size_t h = 0; h < orders.size(); ++h) {
308 const std::vector<T> v = vecmul(start, matpow(M, orders[h]));
309 T acc = num_traits<T>::from_int(0);
310 for (std::size_t r = 0; r < v.size(); ++r) acc += v[r];
311 out(a, h) = num_factorial<T>(orders[h]) / pa * acc;
312 }
313 }
314 return out;
315}
316
317/** Default normalization, matching the two-argument MATLAB call. */
318template <class T>
319Matrix<T> mmap_forward_moment(const Mmap<T>& mm, const std::vector<unsigned>& orders) {
320 return mmap_forward_moment(mm, orders, true);
321}
322
323/** MMAP of the sum of n independent copies, a block bidiagonal concatenation. */
324template <class T>
325Mmap<T> mmap_sum(const Mmap<T>& mm, unsigned n) {
326 if (n == 0) throw InputError("mmap_sum: n must be positive");
327 const std::size_t ns = mm.order();
328 const std::size_t C = mm.classes();
329 const std::size_t N = ns * n;
330 const T zero = num_traits<T>::from_int(0);
331 Mmap<T> out;
332 out.D0 = Matrix<T>(N, N, zero);
333 out.D1 = Matrix<T>(N, N, zero);
334 out.Dc.assign(C, Matrix<T>(N, N, zero));
335 for (unsigned b = 0; b < n; ++b) {
336 const std::size_t off = b * ns;
337 for (std::size_t i = 0; i < ns; ++i)
338 for (std::size_t j = 0; j < ns; ++j) out.D0(off + i, off + j) = mm.D0(i, j);
339 if (b + 1 < n) {
340 for (std::size_t i = 0; i < ns; ++i)
341 for (std::size_t j = 0; j < ns; ++j) out.D0(off + i, off + ns + j) = mm.D1(i, j);
342 } else {
343 for (std::size_t i = 0; i < ns; ++i)
344 for (std::size_t j = 0; j < ns; ++j) {
345 out.D1(off + i, j) = mm.D1(i, j);
346 for (std::size_t c = 0; c < C; ++c) out.Dc[c](off + i, j) = mm.Dc[c](i, j);
347 }
348 }
349 }
350 return out;
351}
352
353} // namespace mam
354} // namespace line
355
356#endif
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
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.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Per-class variance of the counting process of a marked MAP.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
std::vector< T > mmap_lambda(const Mmap< T > &m)
Alias kept for parity with the MATLAB name.
std::vector< Matrix< T > > mmap_embedded(const Mmap< T > &mm)
Embedded per-class kernels E_c = (-D0)^-1 D1^(c).
Definition mmap_stats.h:63
std::vector< std::vector< std::vector< T > > > mmap_sigma2(const Mmap< T > &mm)
sigma2(i,j,h) = pie E_i E_j E_h 1, indexed as sigma2[i][j][h].
Definition mmap_stats.h:159
Matrix< T > mmap_count_mcov(const Mmap< T > &mm, const T &t)
Covariance matrix of the per-class counts over a window of length t.
Definition mmap_stats.h:222
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 > mmap_timereverse(const Mmap< T > &mm)
Time-reversed MMAP, D^-1 M' D with D = diag(map_prob) applied to every matrix.
Definition mmap_stats.h:117
std::vector< T > map_prob(const Map< T > &m)
Stationary distribution of the phase process, pi (D0 + D1) = 0.
Definition map_moment.h:73
std::vector< T > mmap_count_idc(const Mmap< T > &mm, const T &t)
Per-class index of dispersion of counts over a window of length t.
Definition mmap_stats.h:198
std::vector< T > mmap_count_mean(const Mmap< T > &mm, const T &t)
Per-class mean of the counting process over a window of length t.
Definition mmap_stats.h:182
std::vector< T > mmap_idc(const Mmap< T > &mm)
Asymptotic per-class index of dispersion, evaluated at t = 1e6 / sum_c lambda_c.
Definition mmap_stats.h:208
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
std::vector< Map< T > > mmap_maps(const Mmap< T > &mm)
The C MAPs seen by each class, MAP_c = (D0 + D1 - D1^(c), D1^(c)).
Definition mmap_stats.h:72
std::vector< T > map_pie(const Map< T > &m)
Phase distribution seen by an arriving job, pie = pi D1 / (pi D1 e).
Definition map_moment.h:89
Matrix< T > mmap_pie(const Mmap< T > &mm)
Stationary phase distribution seen just after a class-c arrival, one row per class.
Definition mmap_stats.h:91
Mmap< T > mmap_sum(const Mmap< T > &mm, unsigned n)
MMAP of the sum of n independent copies, a block bidiagonal concatenation.
Definition mmap_stats.h:325
Matrix< T > mmap_cross_moment(const Mmap< T > &mm, unsigned k)
Cross moments of order k: MC(i,j) is E[T^k] of the interval that FOLLOWS a class-i arrival,...
Definition mmap_stats.h:253
std::vector< T > mmap_count_var(const Mmap< T > &mm, const T &t)
Per-class variance of the counting process of a marked MAP.
T num_factorial(unsigned n)
Factorial as a value of T.
Definition number.h:184
std::vector< T > vecmul(const std::vector< T > &v, const Matrix< T > &A)
Row vector times matrix, v A.
Definition linalg.h:50
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
Matrix< T > matpow(const Matrix< T > &A, unsigned k)
Integer matrix power, by repeated squaring.
Definition linalg.h:89
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
Number-type abstraction for the templated API port.
A MAP as the pair of matrices (D0, D1).
Definition map_moment.h:53
An MMAP: the underlying MAP plus the per-class arrival matrices.
Definition mmap_lambda.h:45
Map< T > map() const
Definition mmap_lambda.h:52
std::size_t classes() const
Definition mmap_lambda.h:51
Matrix< T > D0
Definition mmap_lambda.h:46
Matrix< T > D1
Definition mmap_lambda.h:47
std::vector< Matrix< T > > Dc
per-class matrices, sum_c Dc = D1
Definition mmap_lambda.h:48
std::size_t order() const
Definition mmap_lambda.h:50