LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
mmap_lambda.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_LAMBDA_H
6#define LINE_API_MAM_MMAP_LAMBDA_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Marked MAP (MMAP) algebra: per-class rates, class probabilities,
12 * superposition, normalization and scaling.
13 *
14 * Templated port of the M3A/kpctoolbox MMAP primitives (mmap_count_lambda.m,
15 * mmap_lambda.m, mmap_pc.m, mmap_super.m, mmap_normalize.m, mmap_scale.m,
16 * mmap_mark.m, mmap_hide.m, mmap_isfeasible.m).
17 *
18 * An MMAP is the tuple (D0, D1, D1^(1), ..., D1^(C)): D1 is the aggregate
19 * arrival matrix and the per-class matrices partition it, sum_c D1^(c) = D1.
20 * Every quantity here is rational in the entries, so the exact instantiation
21 * carries the partition identity exactly; that identity is precisely what
22 * marking and splitting operations break when they are wrong, and a rounded
23 * check cannot tell a broken partition from an accumulation of error.
24 *
25 * Superposition uses the Kronecker sum, as in MATLAB's krons: for generators
26 * A and B, krons(A,B) = kron(A, I) + kron(I, B), so the phase process of the
27 * superposition is the product chain.
28 */
29
30#include <cstddef>
31#include <vector>
32
35#include "line/num/number.h"
36#include "line/util/error.h"
37#include "line/util/linalg.h"
38#include "line/util/matrix.h"
39
40namespace line {
41namespace mam {
42
43/** An MMAP: the underlying MAP plus the per-class arrival matrices. */
44template <class T>
45struct Mmap {
48 std::vector<Matrix<T>> Dc; ///< per-class matrices, sum_c Dc = D1
49
50 std::size_t order() const { return D0.rows(); }
51 std::size_t classes() const { return Dc.size(); }
52 Map<T> map() const { return Map<T>{D0, D1}; }
53};
54
55/** Kronecker product. */
56template <class T>
57Matrix<T> kron(const Matrix<T>& A, const Matrix<T>& B) {
58 Matrix<T> C(A.rows() * B.rows(), A.cols() * B.cols(), num_traits<T>::from_int(0));
59 for (std::size_t i = 0; i < A.rows(); ++i)
60 for (std::size_t j = 0; j < A.cols(); ++j) {
61 if (A(i, j) == num_traits<T>::from_int(0)) continue;
62 for (std::size_t k = 0; k < B.rows(); ++k)
63 for (std::size_t l = 0; l < B.cols(); ++l)
64 C(i * B.rows() + k, j * B.cols() + l) = A(i, j) * B(k, l);
65 }
66 return C;
67}
68
69/** Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B). */
70template <class T>
71Matrix<T> krons(const Matrix<T>& A, const Matrix<T>& B) {
72 if (A.rows() != A.cols() || B.rows() != B.cols())
73 throw InputError("krons: both operands must be square");
74 const Matrix<T> left = kron(A, eye<T>(B.rows()));
75 const Matrix<T> right = kron(eye<T>(A.rows()), B);
76 Matrix<T> C(left.rows(), left.cols());
77 for (std::size_t i = 0; i < C.rows(); ++i)
78 for (std::size_t j = 0; j < C.cols(); ++j) C(i, j) = left(i, j) + right(i, j);
79 return C;
80}
81
82/**
83 * Superposition of two MMAPs: the phase process is the product chain, and the
84 * class list of the result is the concatenation of the two class lists
85 * (mmap_super.m, 'default' option).
86 */
87template <class T>
88Mmap<T> mmap_super(const Mmap<T>& a, const Mmap<T>& b) {
89 const std::size_t na = a.order(), nb = b.order();
90 const T zero = num_traits<T>::from_int(0);
91 Mmap<T> s;
92 s.D0 = krons(a.D0, b.D0);
93 s.D1 = krons(a.D1, b.D1);
94 const Matrix<T> zeroA(na, na, zero), zeroB(nb, nb, zero);
95 for (std::size_t c = 0; c < a.classes(); ++c) s.Dc.push_back(krons(a.Dc[c], zeroB));
96 for (std::size_t c = 0; c < b.classes(); ++c) s.Dc.push_back(krons(zeroA, b.Dc[c]));
97 return s;
98}
99
100/** Per-class arrival rates, lambda_c = theta D1^(c) e. */
101template <class T>
102std::vector<T> mmap_count_lambda(const Mmap<T>& m) {
103 const std::vector<T> theta = map_prob(m.map());
104 std::vector<T> lk(m.classes(), num_traits<T>::from_int(0));
105 for (std::size_t c = 0; c < m.classes(); ++c) {
106 const std::vector<T> t = vecmul(theta, m.Dc[c]);
107 for (const T& v : t) lk[c] += v;
108 }
109 return lk;
110}
111
112/** Alias kept for parity with the MATLAB name. */
113template <class T>
114std::vector<T> mmap_lambda(const Mmap<T>& m) {
115 return mmap_count_lambda(m);
116}
117
118/** Class probabilities seen by an arriving job, pc = pie (-D0)^-1 D1^(c) e. */
119template <class T>
120std::vector<T> mmap_pc(const Mmap<T>& m) {
121 Matrix<T> negD0 = m.D0;
122 for (std::size_t i = 0; i < negD0.rows(); ++i)
123 for (std::size_t j = 0; j < negD0.cols(); ++j) negD0(i, j) = -negD0(i, j);
124 const Matrix<T> inv = inverse(negD0);
125 const std::vector<T> pie = map_pie(m.map());
126 std::vector<T> pc(m.classes(), num_traits<T>::from_int(0));
127 for (std::size_t c = 0; c < m.classes(); ++c) {
128 const std::vector<T> t = vecmul(vecmul(pie, inv), m.Dc[c]);
129 for (const T& v : t) pc[c] += v;
130 }
131 return pc;
132}
133
134/** True when the per-class matrices partition D1 exactly and D0 is a generator. */
135template <class T>
136bool mmap_isfeasible(const Mmap<T>& m) {
137 const T zero = num_traits<T>::from_int(0);
138 for (std::size_t i = 0; i < m.order(); ++i) {
139 for (std::size_t j = 0; j < m.order(); ++j) {
140 T s = zero;
141 for (std::size_t c = 0; c < m.classes(); ++c) {
142 if (m.Dc[c](i, j) < zero) return false;
143 s += m.Dc[c](i, j);
144 }
145 if (s != m.D1(i, j)) return false;
146 if (i != j && m.D0(i, j) < zero) return false;
147 }
148 T row = zero;
149 for (std::size_t j = 0; j < m.order(); ++j) row += m.D0(i, j) + m.D1(i, j);
150 if (row != zero) return false;
151 }
152 return true;
153}
154
155/**
156 * Feasibility of a marked MAP WITHIN A TOLERANCE, the semantics of
157 * matlab/lib/m3a/m3a/mmap/mmap_isfeasible.m, whose second argument defaults to
158 * 10^-map_feastol = 1e-8: every per-class matrix non-negative up to -tol, the
159 * per-class matrices summing to D1 up to tol, and the underlying MAP feasible.
160 *
161 * The mmap_isfeasible above is the EXACT predicate: it
162 * compares sums with == and rejects any negative entry however small. That is
163 * the right check for an algebraically assembled MMAP, but no optimizer can
164 * pass it -- the marking probabilities sum to one only to the tolerance of the
165 * solve -- so the optimization-based fits report feasibility through this tolerant form,
166 * which is what the reference actually applies to their output.
167 */
168template <class T>
169bool mmap_isfeasible_tol(const Mmap<T>& m, const T& tol) {
170 const T zero = num_traits<T>::from_int(0);
171 if (!map_isfeasible(m.map(), tol)) return false;
172 for (std::size_t c = 0; c < m.classes(); ++c)
173 for (std::size_t i = 0; i < m.order(); ++i)
174 for (std::size_t j = 0; j < m.order(); ++j)
175 if (m.Dc[c](i, j) < -tol) return false;
176 for (std::size_t i = 0; i < m.order(); ++i)
177 for (std::size_t j = 0; j < m.order(); ++j) {
178 T s = m.D1(i, j);
179 for (std::size_t c = 0; c < m.classes(); ++c) s -= m.Dc[c](i, j);
180 if (num_abs(T(s)) > tol) return false;
181 }
182 return true;
183}
184
185/**
186 * Clamp negative off-diagonal and per-class entries to zero and rebuild D1 and
187 * the diagonal of D0 from them (mmap_normalize.m).
188 */
189template <class T>
191 const T zero = num_traits<T>::from_int(0);
192 Mmap<T> m = in;
193 const std::size_t n = m.order();
194 for (std::size_t i = 0; i < n; ++i)
195 for (std::size_t j = 0; j < n; ++j)
196 if (i != j && m.D0(i, j) < zero) m.D0(i, j) = zero;
197 for (std::size_t c = 0; c < m.classes(); ++c)
198 for (std::size_t i = 0; i < n; ++i)
199 for (std::size_t j = 0; j < n; ++j)
200 if (m.Dc[c](i, j) < zero) m.Dc[c](i, j) = zero;
201 m.D1 = Matrix<T>(n, n, zero);
202 for (std::size_t c = 0; c < m.classes(); ++c)
203 for (std::size_t i = 0; i < n; ++i)
204 for (std::size_t j = 0; j < n; ++j) m.D1(i, j) += m.Dc[c](i, j);
205 for (std::size_t i = 0; i < n; ++i) {
206 m.D0(i, i) = zero;
207 T s = zero;
208 for (std::size_t j = 0; j < n; ++j) s += m.D0(i, j) + m.D1(i, j);
209 m.D0(i, i) = -s;
210 }
211 return m;
212}
213
214/** Rescale time so that the mean inter-arrival time becomes M. */
215template <class T>
216Mmap<T> mmap_scale(const Mmap<T>& in, const T& M) {
217 if (M == num_traits<T>::from_int(0)) throw InputError("mmap_scale: zero target mean");
218 const T ratio = map_mean(in.map()) / M;
219 Mmap<T> m = in;
220 for (std::size_t i = 0; i < m.order(); ++i)
221 for (std::size_t j = 0; j < m.order(); ++j) {
222 m.D0(i, j) *= ratio;
223 m.D1(i, j) *= ratio;
224 }
225 for (std::size_t c = 0; c < m.classes(); ++c)
226 for (std::size_t i = 0; i < m.order(); ++i)
227 for (std::size_t j = 0; j < m.order(); ++j) m.Dc[c](i, j) *= ratio;
228 return m;
229}
230
231/**
232 * Hide a subset of the marks (mmap_hide.m).
233 *
234 * The process is UNCHANGED -- D0 and the total D1 still describe the same point
235 * process -- and only the observation of the hidden classes is removed, which is
236 * why the reference renormalizes afterwards: with Dc zeroed for the hidden
237 * classes, mmap_normalize rebuilds D1 as the sum of the SURVIVING marks and
238 * moves the hidden arrivals into D0 as phase changes. The result is the MAP of
239 * the visible class alone, embedded in the joint phase process.
240 *
241 * @param hide 0-based class indices to hide
242 */
243template <class T>
244Mmap<T> mmap_hide(const Mmap<T>& in, const std::vector<std::size_t>& hide) {
245 const T zero = num_traits<T>::from_int(0);
246 Mmap<T> m = in;
247 for (std::size_t k : hide) {
248 if (k >= m.classes()) throw InputError("mmap_hide: class index out of range");
249 m.Dc[k] = Matrix<T>(m.order(), m.order(), zero);
250 }
251 return mmap_normalize(m);
252}
253
254/** `mmap_hide(m, setdiff(1:K, keep))`: keep ONE mark, hide every other. */
255template <class T>
256Mmap<T> mmap_hide_but(const Mmap<T>& in, std::size_t keep) {
257 std::vector<std::size_t> hide;
258 for (std::size_t k = 0; k < in.classes(); ++k)
259 if (k != keep) hide.push_back(k);
260 return mmap_hide(in, hide);
261}
262
263/** Turn a MAP into a single-class MMAP (mmap_mark with one class). */
264template <class T>
265Mmap<T> mmap_mark(const Map<T>& base, const Matrix<T>& weights) {
266 Mmap<T> m;
267 m.D0 = base.D0;
268 m.D1 = base.D1;
269 const std::size_t C = weights.cols();
270 if (C == 0) throw InputError("mmap_mark: no classes");
271 for (std::size_t c = 0; c < C; ++c) {
272 Matrix<T> Dc(base.D1.rows(), base.D1.cols());
273 for (std::size_t i = 0; i < Dc.rows(); ++i)
274 for (std::size_t j = 0; j < Dc.cols(); ++j) Dc(i, j) = base.D1(i, j) * weights(i, c);
275 m.Dc.push_back(Dc);
276 }
277 return m;
278}
279
280} // namespace mam
281} // namespace line
282
283#endif // LINE_API_MAM_MMAP_LAMBDA_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
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.
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_scale(const Mmap< T > &in, const T &M)
Rescale time so that the mean inter-arrival time becomes M.
std::vector< T > mmap_lambda(const Mmap< T > &m)
Alias kept for parity with the MATLAB name.
bool mmap_isfeasible_tol(const Mmap< T > &m, const T &tol)
Feasibility of a marked MAP WITHIN A TOLERANCE, the semantics of matlab/lib/m3a/m3a/mmap/mmap_isfeasi...
Matrix< T > krons(const Matrix< T > &A, const Matrix< T > &B)
Kronecker sum, MATLAB's krons: kron(A, I_nb) + kron(I_na, B).
Definition mmap_lambda.h:71
Mmap< T > mmap_hide_but(const Mmap< T > &in, std::size_t keep)
mmap_hide(m, setdiff(1:K, keep)): keep ONE mark, hide every other.
Mmap< T > mmap_hide(const Mmap< T > &in, const std::vector< std::size_t > &hide)
Hide a subset of the marks (mmap_hide.m).
T map_mean(const Map< T > &m)
Mean inter-arrival time, 1/lambda.
Definition map_moment.h:101
Matrix< T > kron(const Matrix< T > &A, const Matrix< T > &B)
Kronecker product.
Definition mmap_lambda.h:57
std::vector< T > map_prob(const Map< T > &m)
Stationary distribution of the phase process, pi (D0 + D1) = 0.
Definition map_moment.h:73
bool map_isfeasible(const Map< T > &m, const T &tol)
Structural feasibility of a MAP within a tolerance (map_isfeasible.m): off-diagonal D0 and all of D1 ...
Mmap< T > mmap_mark(const Map< T > &base, const Matrix< T > &weights)
Turn a MAP into a single-class MMAP (mmap_mark with one class).
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
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
bool mmap_isfeasible(const Mmap< T > &m)
True when the per-class matrices partition D1 exactly and D0 is a generator.
std::vector< T > mmap_pc(const Mmap< T > &m)
Class probabilities seen by an arriving job, pc = pie (-D0)^-1 D1^(c) e.
std::vector< T > mmap_count_lambda(const Mmap< T > &m)
Per-class arrival rates, lambda_c = theta D1^(c) e.
T num_abs(const T &v)
Definition number.h:172
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 > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
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
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