LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmap_compress.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_COMPRESS_H
6#define LINE_API_MAM_MMAP_COMPRESS_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Compression of a marked MAP into a smaller representation, and the two M3A
12 * primitives it is built from: the class-conditional backward moments and the
13 * probabilistic mixture of MAPs.
14 *
15 * Templated port of matlab/src/api/mam/mmap_compress.m (the 'default' /
16 * 'mixture' / 'mixture.order1' method), matlab/lib/m3a/m3a/mmap/
17 * mmap_backward_moment.m and mmap_mixture.m.
18 *
19 * ORDER-1 MIXTURE. One component per class, recombined by mmap_mixture with
20 * the class probabilities p_c as mixing weights. Component c must carry the
21 * law of the inter-arrival time CONDITIONED ON THE ARRIVAL THAT ENDS IT BEING
22 * OF CLASS c, because mmap_mixture marks the arrival LEAVING component c with
23 * class c, so the class of an arrival and the interval preceding it are both
24 * governed by the component active during that interval. That conditional law
25 * is the class-c BACKWARD moment set B(c, 1:3),
26 *
27 * B(c,k) = k! pie (-D0)^-(k+1) D1^(c) e / p_c,
28 *
29 * i.e. E[T^k | class of the ending arrival = c]. It is NOT the forward moment
30 * and it is NOT the class-c marginal MAP of mmap_maps, whose mean is
31 * 1/lambda_c, the time between successive class-c arrivals; mixing those with
32 * weights lambda_c/Lambda inflates the mean to K/Lambda.
33 *
34 * PRESERVED exactly: the aggregate moments 1..3, through the mixture law
35 * M_k = sum_c B(k,c) p_c (M1 always, since aph2_adjust never alters M1; M2 and
36 * M3 whenever the triple is APH(2)-feasible); the class probabilities p_c and
37 * hence the per-class rates lambda_c = p_c / M1; the marking consistency
38 * D1 = sum_c D1^(c); and MAP feasibility, since mmap_normalize closes the
39 * result.
40 *
41 * LOST by construction: every autocorrelation. mmap_mixture re-enters each
42 * component at its map_pie on every arrival, so the intervals are i.i.d. and
43 * the result is a RENEWAL process: acf -> 0, IDC -> the SCV-determined renewal
44 * value, and the class sequence becomes i.i.d. Retaining the class-transition
45 * matrix sigma is what the order-2 method buys with its K^2 components.
46 *
47 * ARITHMETIC. mmap_backward_moment and mmap_mixture are finite rational
48 * expressions in the descriptor entries and instantiate at Rational, which is
49 * what makes the preservation claims above CHECKABLE rather than merely
50 * plausible: at exact arithmetic the class probabilities of the compressed
51 * MMAP equal those of the original digit for digit, so any drift the tests see
52 * is a real modelling loss and not rounding. mmap_compress itself is gated on
53 * num_traits<T>::has_transcendental because aph2_fit is.
54 *
55 * NOT PORTED. The other five methods of mmap_compress.m are dispatch to
56 * separate fitting families that are outside this change:
57 * 'mixture.order2' (mmap_mixture_fit_mmap), 'mamap2' and 'mamap2.fb'
58 * (mamap2m_fit_mmap, mamap2m_fit_gamma_fb_mmap) and the four 'm3pp.*'
59 * variants (m3pp2m_fitc_theoretical, which needs the derivest numerical
60 * differentiation package). MmapCompressMethod names them so a caller gets
61 * UnsupportedError identifying the missing family rather than silently
62 * receiving the order-1 mixture.
63 */
64
65#include <cstddef>
66#include <vector>
67
72#include "line/num/number.h"
73#include "line/util/error.h"
74#include "line/util/linalg.h"
75#include "line/util/matrix.h"
76
77namespace line {
78namespace mam {
79
80/**
81 * Class-conditional backward moments of an MMAP (mmap_backward_moment.m).
82 *
83 * @param orders the moment orders to compute
84 * @param normalized true for B(c,k) with M_k = sum_c B(c,k) p_c, i.e. divided
85 * by the class probability p_c (the MATLAB default); false for the
86 * unnormalized form with M_k = sum_c B(c,k)
87 * @param m the marked MAP whose backward moments are taken
88 * @return B[c][h], the moment of order orders[h] for class c
89 */
90template <class T>
91std::vector<std::vector<T>> mmap_backward_moment(const Mmap<T>& m,
92 const std::vector<unsigned>& orders,
93 bool normalized) {
94 const std::size_t n = m.order();
95 const std::size_t C = m.classes();
96 const T zero = num_traits<T>::from_int(0);
97 const std::vector<T> pie = map_pie(m.map());
98 Matrix<T> negD0 = m.D0;
99 for (std::size_t i = 0; i < n; ++i)
100 for (std::size_t j = 0; j < n; ++j) negD0(i, j) = -negD0(i, j);
101 const Matrix<T> Minv = inverse(negD0);
102
103 std::vector<std::vector<T>> B(C, std::vector<T>(orders.size(), zero));
104 for (std::size_t c = 0; c < C; ++c) {
105 T pa = num_traits<T>::from_int(1);
106 if (normalized) {
107 const std::vector<T> t = vecmul(vecmul(pie, Minv), m.Dc[c]);
108 pa = zero;
109 for (const T& v : t) pa += v;
110 if (pa == zero)
111 throw NumericError(
112 "mmap_backward_moment: class with zero arrival probability cannot be "
113 "normalized");
114 }
115 for (std::size_t h = 0; h < orders.size(); ++h) {
116 const unsigned k = orders[h];
117 const std::vector<T> t = vecmul(vecmul(pie, matpow(Minv, k + 1)), m.Dc[c]);
118 T s = zero;
119 for (const T& v : t) s += v;
120 B[c][h] = num_factorial<T>(k) / pa * s;
121 }
122 }
123 return B;
124}
125
126/** mmap_backward_moment with the MATLAB default, normalized. */
127template <class T>
128std::vector<std::vector<T>> mmap_backward_moment(const Mmap<T>& m,
129 const std::vector<unsigned>& orders) {
130 return mmap_backward_moment(m, orders, true);
131}
132
133/**
134 * Probabilistic mixture of MAPs (mmap_mixture.m).
135 *
136 * The phase space is the disjoint union of the component phase spaces, the
137 * hidden generator is block diagonal, and on completion of an interval in
138 * component i the process jumps into component j with probability alpha_j,
139 * entering it at its own map_pie. The arrival that LEAVES component i is
140 * marked with class i, so the resulting MMAP has one class per component:
141 *
142 * D0 = blkdiag(D0^1, ..., D0^I)
143 * D1 block (i,j) = alpha_j (D1^i e) pie^j
144 * D1^(c) block (i,j) = D1 block (i,j) if i == c, else 0.
145 *
146 * The result is a renewal process by construction; see the header note.
147 */
148template <class T>
149Mmap<T> mmap_mixture(const std::vector<T>& alpha, const std::vector<Map<T>>& maps) {
150 const std::size_t I = maps.size();
151 if (I == 0) throw InputError("mmap_mixture: no components");
152 if (alpha.size() != I) throw InputError("mmap_mixture: one weight per component is required");
153 const T zero = num_traits<T>::from_int(0);
154
155 std::vector<std::size_t> sz(I), off(I);
156 std::size_t total = 0;
157 for (std::size_t i = 0; i < I; ++i) {
158 sz[i] = maps[i].order();
159 off[i] = total;
160 total += sz[i];
161 }
162
163 std::vector<std::vector<T>> pies(I);
164 for (std::size_t j = 0; j < I; ++j) pies[j] = map_pie(maps[j]);
165
166 Mmap<T> out;
167 out.D0 = Matrix<T>(total, total, zero);
168 out.D1 = Matrix<T>(total, total, zero);
169 out.Dc.assign(I, Matrix<T>(total, total, zero));
170
171 for (std::size_t i = 0; i < I; ++i) {
172 for (std::size_t a = 0; a < sz[i]; ++a)
173 for (std::size_t b = 0; b < sz[i]; ++b) out.D0(off[i] + a, off[i] + b) = maps[i].D0(a, b);
174 // The completion rate out of each phase of component i.
175 std::vector<T> t(sz[i], zero);
176 for (std::size_t a = 0; a < sz[i]; ++a)
177 for (std::size_t b = 0; b < sz[i]; ++b) t[a] += maps[i].D1(a, b);
178 for (std::size_t j = 0; j < I; ++j)
179 for (std::size_t a = 0; a < sz[i]; ++a)
180 for (std::size_t b = 0; b < sz[j]; ++b) {
181 const T v = alpha[j] * t[a] * pies[j][b];
182 out.D1(off[i] + a, off[j] + b) = v;
183 out.Dc[i](off[i] + a, off[j] + b) = v;
184 }
185 }
186 return mmap_normalize(out);
187}
188
189/** The compression methods of mmap_compress.m. */
191 MixtureOrder1, ///< 'default', 'mixture', 'mixture.order1'
192 MixtureOrder2, ///< 'mixture.order2', not ported
193 Mamap2, ///< 'mamap2', not ported
194 Mamap2Fb, ///< 'mamap2.fb', not ported
195 M3ppApproxCov, ///< 'm3pp.approx_cov', not ported
196 M3ppApproxAg, ///< 'm3pp.approx_ag', not ported
197 M3ppExactDelta, ///< 'm3pp.exact_delta', not ported
198 M3ppApproxDelta ///< 'm3pp.approx_delta', not ported
199};
200
201/**
202 * Compress an MMAP (mmap_compress.m).
203 *
204 * A class that never arrives (p_c <= 1e-14, the reference's
205 * GlobalConstants.Zero) gets Exp(1) as its component, exactly as in the
206 * reference: it carries zero mixture weight, so any proper MAP leaves the
207 * result unchanged and the 0/0 normalization of its backward moments is
208 * avoided.
209 */
210template <class T>
213 "mmap_compress requires transcendental arithmetic");
215 throw UnsupportedError(
216 "mmap_compress: only the order-1 mixture is ported; the 'mixture.order2', 'mamap2', "
217 "'mamap2.fb' and 'm3pp.*' methods dispatch to fitting families that are not part of "
218 "this port (mmap_mixture_fit_mmap, mamap2m_fit_mmap, mamap2m_fit_gamma_fb_mmap, "
219 "m3pp2m_fitc_theoretical)");
220
221 const std::size_t K = in.classes();
222 if (K == 0) throw InputError("mmap_compress: the MMAP has no classes");
223 const std::vector<T> p = mmap_pc(in);
224 std::vector<unsigned> orders;
225 orders.push_back(1u);
226 orders.push_back(2u);
227 orders.push_back(3u);
228
229 const T zeroTol = num_traits<T>::from_double(1e-14);
230 // never-arriving-class 0/0 row: see _kb/03-api-layer.md (cpp port notes: mam)
231 std::vector<Map<T>> comps;
232 comps.reserve(K);
233 for (std::size_t k = 0; k < K; ++k) {
234 if (p[k] <= zeroTol) {
235 comps.push_back(map_exponential(T(num_traits<T>::from_int(1))));
236 continue;
237 }
238 Mmap<T> one = in;
239 one.Dc.assign(1, in.Dc[k]); // same D0 and D1, hence the same pie
240 const std::vector<std::vector<T>> B = mmap_backward_moment(one, orders, true);
241 comps.push_back(aph2_fit(B[0][0], B[0][1], B[0][2]).aph);
242 }
243 return mmap_normalize(mmap_mixture(p, comps));
244}
245
246/** mmap_compress with the default method, the order-1 mixture. */
247template <class T>
251
252} // namespace mam
253} // namespace line
254
255#endif // LINE_API_MAM_MMAP_COMPRESS_H
APH(2) fit of three moments, with a fallback to adjusted moments (matlab/lib/m3a/m3a/aph2/aph2_fit....
InputError(const std::string &what)
Definition error.h:39
NumericError(const std::string &what)
Definition error.h:45
UnsupportedError(const std::string &what)
Definition error.h:51
The exception types the port throws.
Dense linear algebra over the templated number type: products, identity, inverse, and powers.
Markovian arrival process descriptors: stationary vectors, rate, moments, autocorrelation and the ind...
MAP constructors and structural transformations.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
Mmap< T > mmap_normalize(const Mmap< T > &in)
Clamp negative off-diagonal and per-class entries to zero and rebuild D1 and the diagonal of D0 from ...
MmapCompressMethod
The compression methods of mmap_compress.m.
@ M3ppApproxDelta
'm3pp.approx_delta', not ported
@ M3ppApproxAg
'm3pp.approx_ag', not ported
@ Mamap2
'mamap2', not ported
@ Mamap2Fb
'mamap2.fb', not ported
@ MixtureOrder1
'default', 'mixture', 'mixture.order1'
@ MixtureOrder2
'mixture.order2', not ported
@ M3ppExactDelta
'm3pp.exact_delta', not ported
@ M3ppApproxCov
'm3pp.approx_cov', not ported
Aph2FitResult< T > aph2_fit(const T &M1, const T &M2, const T &M3)
Fit an APH(2) to (M1, M2, M3), relaxing the moments if necessary.
Definition aph2_fit.h:42
Mmap< T > mmap_mixture(const std::vector< T > &alpha, const std::vector< Map< T > > &maps)
Probabilistic mixture of MAPs (mmap_mixture.m).
Map< T > map_exponential(const T &lambda)
Two-phase MAP constructor for a Poisson process of rate lambda.
Definition map_moment.h:213
Mmap< T > mmap_compress(const Mmap< T > &in, MmapCompressMethod method)
Compress an MMAP (mmap_compress.m).
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).
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
std::vector< T > mmap_pc(const Mmap< T > &m)
Class probabilities seen by an arriving job, pc = pie (-D0)^-1 D1^(c) e.
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 > matpow(const Matrix< T > &A, unsigned k)
Integer matrix power, by repeated squaring.
Definition linalg.h:89
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