LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmap_assemble.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_ASSEMBLE_H
6#define LINE_API_MAM_MMAP_ASSEMBLE_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * The MMAP assembly primitives `solver_mam_basic.m` builds its per-station
12 * arrival stream from: `mmap_exponential`, the probabilistic `mmap_mark`, the
13 * per-class `mmap_scale`, and `mmap_super_safe`.
14 *
15 * They are separate from `mmap_lambda.h` because each of them is a DIFFERENT
16 * function from the same-named one already there: `mmap_lambda.h`'s
17 * `mmap_mark` splits a MAP by per-PHASE weights and its `mmap_scale` takes a
18 * single target mean, which are the M3A signatures; the MAM solver calls the
19 * kpctoolbox ones, which split an MMAP by per-CLASS probabilities and target
20 * one mean per class. Naming them apart is deliberate: two functions with one
21 * name and two meanings is exactly the failure mode the parity notes record for
22 * `map_normalize`.
23 */
24
25#include <algorithm>
26#include <cmath>
27#include <cstddef>
28#include <limits>
29#include <numeric>
30#include <string>
31#include <vector>
32
36#include "line/num/number.h"
37#include "line/util/error.h"
38#include "line/util/linalg.h"
39#include "line/util/matrix.h"
40
41namespace line {
42namespace mam {
43
44namespace mmap_super_detail {
45
46/** mamap2m_fit_gamma_fb_mmap where the arithmetic can carry it. */
47template <class T>
48Mmap<T> compress_order2(const Mmap<T>& m) {
49 if constexpr (num_traits<T>::has_transcendental) {
51 } else {
52 throw UnsupportedError(
53 "mmap_super_safe: the order budget calls for the reference's MAMAP(2,m) compression "
54 "(mamap2m_fit_gamma_fb_mmap), which needs transcendental arithmetic; rerun with "
55 "--arith double or --arith real");
56 }
57}
58
59} // namespace mmap_super_detail
60
61/**
62 * Order-n MMAP with the given per-class arrival rates (mmap_exponential.m).
63 *
64 * The per-class matrix is `flip(eye(n)) * lambda_c`, so at n = 1 this is the
65 * ordinary marked Poisson stream and at n > 1 it is the n-phase cycle the
66 * reference uses as a neutral element of the superposition.
67 */
68template <class T>
69Mmap<T> mmap_exponential_vec(const std::vector<T>& lambda, std::size_t n = 1) {
70 const T zero = num_traits<T>::from_int(0);
71 if (n == 0) throw InputError("mmap_exponential_vec: order must be positive");
72 Mmap<T> m;
73 m.D0 = Matrix<T>(n, n, zero);
74 m.D1 = Matrix<T>(n, n, zero);
75 for (std::size_t c = 0; c < lambda.size(); ++c) {
76 Matrix<T> Dc(n, n, zero);
77 for (std::size_t i = 0; i < n; ++i) Dc(i, n - 1 - i) = lambda[c];
78 for (std::size_t i = 0; i < n; ++i)
79 for (std::size_t j = 0; j < n; ++j) m.D1(i, j) += Dc(i, j);
80 m.Dc.push_back(Dc);
81 }
82 return mmap_normalize(m);
83}
84
85/**
86 * Re-mark an MMAP by a (K x R) probability matrix (mmap_mark.m): a type-k
87 * arrival is reported as class r with probability prob(k,r).
88 */
89template <class T>
90Mmap<T> mmap_mark_probs(const Mmap<T>& in, const Matrix<T>& prob) {
91 const T zero = num_traits<T>::from_int(0);
92 const std::size_t K = prob.rows(), R = prob.cols();
93 if (K > in.classes())
94 throw InputError("mmap_mark_probs: the probability matrix has more input types than the "
95 "MMAP has classes");
96 Mmap<T> m;
97 m.D0 = in.D0;
98 m.D1 = in.D1;
99 const std::size_t n = in.order();
100 for (std::size_t r = 0; r < R; ++r) {
101 Matrix<T> Dr(n, n, zero);
102 for (std::size_t k = 0; k < K; ++k)
103 for (std::size_t i = 0; i < n; ++i)
104 for (std::size_t j = 0; j < n; ++j) Dr(i, j) += in.Dc[k](i, j) * prob(k, r);
105 m.Dc.push_back(Dr);
106 }
107 return m;
108}
109
110/**
111 * Retarget the per-class MEAN inter-arrival times (mmap_scale.m, vector form).
112 *
113 * Each class matrix is rescaled by (1/M_c)/lambda_c, then the MMAP is
114 * renormalized. The reference calls this "heuristic because it also affects the
115 * other classes"; the refinement loop that follows it in MATLAB is dead code
116 * behind an unconditional `return`, so the heuristic IS the function and is
117 * what is reproduced here. A class with zero rate is zeroed rather than divided.
118 */
119template <class T>
120Mmap<T> mmap_scale_perclass(const Mmap<T>& in, const std::vector<T>& M) {
121 const T zero = num_traits<T>::from_int(0);
122 const std::size_t C = in.classes();
123 if (M.size() != C) throw InputError("mmap_scale_perclass: one target mean per class is needed");
124 const std::vector<T> l = mmap_count_lambda(in);
125 Mmap<T> s;
126 s.D0 = in.D0;
127 s.D1 = Matrix<T>(in.order(), in.order(), zero);
128 for (std::size_t c = 0; c < C; ++c) {
129 Matrix<T> Dc = in.Dc[c];
130 const T f = (l[c] > zero) ? T(T(num_traits<T>::from_int(1) / M[c]) / l[c]) : zero;
131 for (std::size_t i = 0; i < Dc.rows(); ++i)
132 for (std::size_t j = 0; j < Dc.cols(); ++j) {
133 Dc(i, j) *= f;
134 s.D1(i, j) += Dc(i, j);
135 }
136 s.Dc.push_back(Dc);
137 }
138 return mmap_normalize(s);
139}
140
141namespace mmap_super_detail {
142
143/** 1-norm of D1, used to detect a component that carries no arrivals at all. */
144template <class T>
145double arrival_norm(const Mmap<T>& m) {
146 double best = 0.0;
147 for (std::size_t j = 0; j < m.D1.cols(); ++j) {
148 double col = 0.0;
149 for (std::size_t i = 0; i < m.D1.rows(); ++i)
150 col += std::fabs(num_traits<T>::to_double(m.D1(i, j)));
151 if (col > best) best = col;
152 }
153 return best;
154}
155
156/** The order-1 marked Poisson stream carrying this MMAP's per-class rates. */
157template <class T>
158Mmap<T> to_poisson(const Mmap<T>& m) {
160}
161
162} // namespace mmap_super_detail
163
164/**
165 * Order-bounded superposition of several MMAPs (mmap_super_safe.m).
166 *
167 * Components are superposed low-SCV first, and the product order is held at or
168 * below `maxorder` by replacing a component with its marked Poisson equivalent.
169 * The marks are permuted back into INPUT order afterwards, because `mmap_super`
170 * concatenates them in fold order while every caller reads mark k as its own
171 * k-th class; without that the SCV sort renames the classes.
172 *
173 * ORDER-2 COMPRESSION. When the order budget still allows an order-2 component,
174 * the reference compresses with `mamap2m_fit_gamma_fb_mmap` (mamap2m_fit.h), a
175 * MAMAP(2,m) fit of the forward and backward moments, and so does this port.
176 * That fit is transcendental, so at exact arithmetic the branch refuses by name
177 * rather than substituting the Poisson fallback, which would silently discard
178 * the component's variability. With the solver's default `space_max = 128` the
179 * branch needs an arrival stream of order above 128 (or a product above it
180 * with room for a 2-phase factor) to be reachable at all.
181 */
182template <class T>
183Mmap<T> mmap_super_safe(const std::vector<Mmap<T>>& in, std::size_t maxorder) {
184 if (maxorder == 0) throw InputError("mmap_super_safe: maxorder must be positive");
185 std::vector<Mmap<T>> parts;
186 for (const Mmap<T>& m : in) {
187 if (m.order() == 0) continue;
188 // A component with an all-zero D1 has zero rate and an absorbing phase
189 // generator, so map_scv would fail on it; canonicalize to the equivalent
190 // order-1 null, whose superposition is the identity.
191 if (m.order() > 1 && mmap_super_detail::arrival_norm(m) < 1e-13)
192 parts.push_back(mmap_exponential_vec(
193 std::vector<T>(m.classes(), num_traits<T>::from_int(0)), 1));
194 else
195 parts.push_back(m);
196 }
197 if (parts.empty()) throw InputError("mmap_super_safe: no components to superpose");
198
199 std::vector<std::size_t> order(parts.size());
200 std::iota(order.begin(), order.end(), 0u);
201 // MATLAB sorts by map_scv, and a ZERO-RATE component (the neutral element
202 // the MAM analyzer superposes to reshape a marking) has an infinite mean, so
203 // its SCV is NaN there and MATLAB's ascending sort puts NaN LAST. Calling
204 // map_scv on it here would divide by a zero rate, so the case is answered
205 // with +infinity, which sorts last for the same reason.
206 std::vector<double> scv(parts.size());
207 for (std::size_t i = 0; i < parts.size(); ++i)
208 scv[i] = mmap_super_detail::arrival_norm(parts[i]) > 0.0
209 ? num_traits<T>::to_double(map_scv(parts[i].map()))
210 : std::numeric_limits<double>::infinity();
211 std::stable_sort(order.begin(), order.end(),
212 [&scv](std::size_t a, std::size_t b) { return scv[a] < scv[b]; });
213
214 // Mark provenance: a zero-rate component sorts last (SCV +inf above), so a
215 // chain that never visits the station used to push its marks ahead of one
216 // that does, renaming both chains' classes.
217 std::vector<std::size_t> markbase(parts.size() + 1, 0);
218 for (std::size_t i = 0; i < parts.size(); ++i)
219 markbase[i + 1] = markbase[i] + parts[i].classes();
220 std::vector<std::size_t> outorder;
221
222 bool first = true;
223 Mmap<T> sup;
224 for (std::size_t idx : order) {
225 for (std::size_t j = 0; j < parts[idx].classes(); ++j)
226 outorder.push_back(markbase[idx] + j);
227 Mmap<T> cur = parts[idx];
228 if (cur.order() > maxorder) {
229 cur = (maxorder >= 2) ? mmap_super_detail::compress_order2(cur)
230 : mmap_super_detail::to_poisson(cur);
231 }
232 if (first) {
233 sup = (maxorder == 1) ? mmap_super_detail::to_poisson(cur) : cur;
234 first = false;
235 continue;
236 }
237 if (sup.order() * cur.order() > maxorder) {
238 sup = mmap_super(sup, (sup.order() * 2 <= maxorder)
239 ? mmap_super_detail::compress_order2(cur)
240 : mmap_super_detail::to_poisson(cur));
241 } else {
242 sup = mmap_super(sup, cur);
243 }
244 }
245 // Restore the caller's mark order.
246 if (sup.Dc.size() == outorder.size() &&
247 !std::is_sorted(outorder.begin(), outorder.end())) {
248 std::vector<Matrix<T>> reordered(outorder.size());
249 for (std::size_t j = 0; j < outorder.size(); ++j) reordered[outorder[j]] = sup.Dc[j];
250 sup.Dc.swap(reordered);
251 }
252 return sup;
253}
254
255} // namespace mam
256} // namespace line
257
258#endif // LINE_API_MAM_MMAP_ASSEMBLE_H
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
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.
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...
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 ...
Mmap< T > mmap_mark_probs(const Mmap< T > &in, const Matrix< T > &prob)
Re-mark an MMAP by a (K x R) probability matrix (mmap_mark.m): a type-k arrival is reported as class ...
Mmap< T > mmap_scale_perclass(const Mmap< T > &in, const std::vector< T > &M)
Retarget the per-class MEAN inter-arrival times (mmap_scale.m, vector form).
Mmap< T > mmap_exponential_vec(const std::vector< T > &lambda, std::size_t n=1)
Order-n MMAP with the given per-class arrival rates (mmap_exponential.m).
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....
T map_scv(const Map< T > &m)
Squared coefficient of variation.
Definition map_moment.h:140
Mmap< T > mmap_super(const Mmap< T > &a, const Mmap< T > &b)
Superposition of two MMAPs: the phase process is the product chain, and the class list of the result ...
Definition mmap_lambda.h:88
std::vector< T > mmap_count_lambda(const Mmap< T > &m)
Per-class arrival rates, lambda_c = theta D1^(c) e.
Mmap< T > mmap_super_safe(const std::vector< Mmap< T > > &in, std::size_t maxorder)
Order-bounded superposition of several MMAPs (mmap_super_safe.m).
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
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
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