LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
dmap.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_DMAP_H
6#define LINE_API_MAM_DMAP_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Discrete-time Markovian arrival processes (D-MAPs).
12 *
13 * Templated port of matlab/lib/kpctoolbox/dmap: dmap_pie.m, dmap_moment.m,
14 * dmap_isfeasible.m, dmap_exp_mul_int.m, dmap_dist.m, dmap_geo_mul_sum.m,
15 * dmap_dist_acf.m, dmap_dist_lag1.m and dmap_sample.m.
16 *
17 * A D-MAP is the pair (D0, D1) of SUBSTOCHASTIC matrices with D0 + D1
18 * stochastic: D0 carries a slot with no arrival and D1 a slot with one. The
19 * continuous-time analogue has D0 with a negative diagonal and rows of D0 + D1
20 * summing to ZERO, so a routine written for one representation silently
21 * produces nonsense on the other. Interarrival times are counted in SLOTS and
22 * are at least one, which is why the first moment is alpha (I - D0)^-1 1 and
23 * not the continuous alpha (-D0)^-1 1.
24 *
25 * The distance functionals all reduce to Stein equations A X B - X + C = 0,
26 * MATLAB's three-argument dlyap. They are solved here through the Kronecker
27 * form, which is exact in the rational backend where a Schur-based solver could
28 * not be.
29 */
30
31#include <cstddef>
32#include <random>
33#include <vector>
34
37#include "line/num/number.h"
38#include "line/util/error.h"
39#include "line/util/linalg.h"
40#include "line/util/lu.h"
41#include "line/util/matrix.h"
42
43namespace line {
44namespace mam {
45
46/** A discrete-time MAP: substochastic D0 (no arrival) and D1 (one arrival). */
47template <class T>
48struct Dmap {
51
52 std::size_t order() const { return D0.rows(); }
53};
54
55namespace detail {
56
57/** Column-major vectorization, matching the MATLAB reshape convention. */
58template <class T>
59std::vector<T> vec_colmajor(const Matrix<T>& A) {
60 std::vector<T> v(A.rows() * A.cols());
61 for (std::size_t j = 0; j < A.cols(); ++j)
62 for (std::size_t i = 0; i < A.rows(); ++i) v[j * A.rows() + i] = A(i, j);
63 return v;
64}
65
66/** MATLAB dlyap(A, B, C): solves A X B - X + C = 0 through the Kronecker form. */
67template <class T>
68Matrix<T> stein_solve(const Matrix<T>& A, const Matrix<T>& B, const Matrix<T>& C) {
69 const std::size_t m = A.rows(), n = B.cols();
70 if (A.cols() != m || B.rows() != n)
71 throw InputError("stein_solve: A and B must be square");
72 if (C.rows() != m || C.cols() != n)
73 throw InputError("stein_solve: the right-hand side is not conformable");
74 const std::size_t N = m * n;
75 Matrix<T> M(N, N, num_traits<T>::from_int(0));
76 // vec(A X B) = kron(B', A) vec(X) in the column-major layout
77 for (std::size_t jb = 0; jb < n; ++jb)
78 for (std::size_t ib = 0; ib < n; ++ib)
79 for (std::size_t ia = 0; ia < m; ++ia)
80 for (std::size_t ja = 0; ja < m; ++ja)
81 M(jb * m + ia, ib * m + ja) = -B(ib, jb) * A(ia, ja);
82 for (std::size_t k = 0; k < N; ++k) M(k, k) += num_traits<T>::from_int(1);
83 const std::vector<T> x = solve(M, vec_colmajor(C));
84 Matrix<T> X(m, n, num_traits<T>::from_int(0));
85 for (std::size_t j = 0; j < n; ++j)
86 for (std::size_t i = 0; i < m; ++i) X(i, j) = x[j * m + i];
87 return X;
88}
89
90/** (I - D0)^-1 D1, the embedded chain at arrival epochs. */
91template <class T>
92Matrix<T> dmap_embedded_chain(const Matrix<T>& D0, const Matrix<T>& D1) {
93 const std::size_t n = D0.rows();
94 Matrix<T> A = eye<T>(n);
95 for (std::size_t i = 0; i < n; ++i)
96 for (std::size_t j = 0; j < n; ++j) A(i, j) -= D0(i, j);
97 return matmul(inverse(A), D1);
98}
99
100} // namespace detail
101
102/** Stationary phase distribution at arrival epochs. */
103template <class T>
104std::vector<T> dmap_pie(const Dmap<T>& d) {
105 return mc::dtmc_solve(detail::dmap_embedded_chain(d.D0, d.D1));
106}
107
108/** Raw moments of the interarrival time in slots, for orders 1, 2 and 3 only. */
109template <class T>
110std::vector<T> dmap_moment(const Dmap<T>& d, const std::vector<unsigned>& orders) {
111 const std::size_t n = d.order();
112 const std::vector<T> al = dmap_pie(d);
113 Matrix<T> IA = eye<T>(n);
114 for (std::size_t i = 0; i < n; ++i)
115 for (std::size_t j = 0; j < n; ++j) IA(i, j) -= d.D0(i, j);
116 const Matrix<T> A = inverse(IA);
117 const std::vector<T> e = ones<T>(n);
118 const std::vector<T> Ae = mulvec(A, e);
119 T m1 = num_traits<T>::from_int(0);
120 for (std::size_t i = 0; i < n; ++i) m1 += al[i] * Ae[i];
121 const std::vector<T> alA = vecmul(al, A);
122 const std::vector<T> alAA = vecmul(alA, A);
123 T alAAe = num_traits<T>::from_int(0), alAe = num_traits<T>::from_int(0);
124 for (std::size_t i = 0; i < n; ++i) {
125 alAe += alA[i] * Ae[i];
126 alAAe += alAA[i] * Ae[i];
127 }
128 std::vector<T> out(orders.size(), num_traits<T>::from_int(0));
129 for (std::size_t t = 0; t < orders.size(); ++t) {
130 switch (orders[t]) {
131 case 1:
132 out[t] = m1;
133 break;
134 case 2:
135 out[t] = num_traits<T>::from_int(2) * alAe - m1;
136 break;
137 case 3:
138 out[t] = num_traits<T>::from_int(6) * alAAe -
139 num_traits<T>::from_int(6) * alAe + m1;
140 break;
141 default:
142 throw InputError("dmap_moment: raw moments of order > 3 are not implemented");
143 }
144 }
145 return out;
146}
147
148/** True when D0 and D1 are nonnegative and D0 + D1 is stochastic. */
149template <class T>
150bool dmap_isfeasible(const Dmap<T>& d) {
151 const std::size_t n = d.D0.rows();
152 if (d.D0.cols() != n || d.D1.rows() != n || d.D1.cols() != n) return false;
153 const T negtol = -num_traits<T>::from_double(1e-10);
154 const T sumtol = num_traits<T>::from_double(1e-6);
155 for (std::size_t i = 0; i < n; ++i) {
157 for (std::size_t j = 0; j < n; ++j) {
158 if (d.D0(i, j) < negtol || d.D1(i, j) < negtol) return false;
159 rs += d.D0(i, j) + d.D1(i, j);
160 d1rs += d.D1(i, j);
161 }
162 if (num_abs(rs - num_traits<T>::from_int(1)) > sumtol) return false;
163 if (d1rs < negtol) return false;
164 }
165 return true;
166}
167
168/** Inner product of the two interarrival densities truncated at lag L. */
169template <class T>
170T dmap_exp_mul_int(const Dmap<T>& a, const Dmap<T>& b, unsigned L, const std::vector<T>& alA,
171 const std::vector<T>& alB) {
172 const std::size_t NA = a.order(), NB = b.order();
174 for (std::size_t i = 0; i < NB; ++i)
175 for (std::size_t j = 0; j < NA; ++j) C(i, j) = alB[i] * alA[j];
176 if (L == 0) throw InputError("dmap_exp_mul_int: the truncation lag L must be positive");
177 Matrix<T> Z = detail::stein_solve(b.D0.transpose(), a.D0, C);
178 for (unsigned i = 1; i <= L - 1; ++i) {
179 Matrix<T> R = matmul(matmul(b.D1.transpose(), Z), a.D1);
180 Z = detail::stein_solve(b.D0.transpose(), a.D0, R);
181 }
182 std::vector<T> dA(NA, num_traits<T>::from_int(0)), dB(NB, num_traits<T>::from_int(0));
183 for (std::size_t i = 0; i < NA; ++i) {
184 dA[i] = num_traits<T>::from_int(1);
185 for (std::size_t j = 0; j < NA; ++j) dA[i] -= a.D0(i, j);
186 }
187 for (std::size_t i = 0; i < NB; ++i) {
188 dB[i] = num_traits<T>::from_int(1);
189 for (std::size_t j = 0; j < NB; ++j) dB[i] -= b.D0(i, j);
190 }
191 const std::vector<T> t = vecmul(dB, Z);
192 T out = num_traits<T>::from_int(0);
193 for (std::size_t i = 0; i < NA; ++i) out += t[i] * dA[i];
194 return out;
195}
196
197/** Default stationary vectors, matching the three-argument MATLAB call. */
198template <class T>
199T dmap_exp_mul_int(const Dmap<T>& a, const Dmap<T>& b, unsigned L) {
200 return dmap_exp_mul_int(a, b, L, dmap_pie(a), dmap_pie(b));
201}
202
203/** Squared L2 distance between the interarrival densities truncated at lag L. */
204template <class T>
205T dmap_dist(const Dmap<T>& a, const Dmap<T>& b, unsigned L, const std::vector<T>& alA,
206 const std::vector<T>& alB) {
207 return dmap_exp_mul_int(a, a, L + 1, alA, alA) -
208 num_traits<T>::from_int(2) * dmap_exp_mul_int(a, b, L + 1, alA, alB) +
209 dmap_exp_mul_int(b, b, L + 1, alB, alB);
210}
211
212/** Default stationary vectors, matching the three-argument MATLAB call. */
213template <class T>
214T dmap_dist(const Dmap<T>& a, const Dmap<T>& b, unsigned L) {
215 return dmap_dist(a, b, L, dmap_pie(a), dmap_pie(b));
216}
217
218/**
219 * Geometrically weighted sum of the lagged joint moments, the building block of
220 * the autocorrelation distance. MATLAB returns 1/rcond as a large sentinel when
221 * the Stein operator is near singular; here the linear solve is exact in the
222 * rational backend and raises otherwise, so no sentinel is produced.
223 */
224template <class T>
225T dmap_geo_mul_sum(const Dmap<T>& a, const Dmap<T>& b, const std::vector<T>& alA,
226 const std::vector<T>& alB) {
227 const std::size_t NA = a.order(), NB = b.order();
228 Matrix<T> IA = eye<T>(NA), IB = eye<T>(NB);
229 for (std::size_t i = 0; i < NA; ++i)
230 for (std::size_t j = 0; j < NA; ++j) IA(i, j) -= a.D0(i, j);
231 for (std::size_t i = 0; i < NB; ++i)
232 for (std::size_t j = 0; j < NB; ++j) IB(i, j) -= b.D0(i, j);
233 const Matrix<T> D0Ai = inverse(IA), D0Bi = inverse(IB);
234 Matrix<T> PAh = matmul(D0Ai, a.D1), PBh = matmul(D0Bi, b.D1);
235 for (std::size_t i = 0; i < NA; ++i)
236 for (std::size_t j = 0; j < NA; ++j) PAh(i, j) -= alA[j];
237 for (std::size_t i = 0; i < NB; ++i)
238 for (std::size_t j = 0; j < NB; ++j) PBh(i, j) -= alB[j];
239 std::vector<T> rowA(NA, num_traits<T>::from_int(0));
240 for (std::size_t i = 0; i < NA; ++i)
241 for (std::size_t j = 0; j < NA; ++j) rowA[i] += D0Ai(i, j);
242 const std::vector<T> alBD0Bi = vecmul(alB, D0Bi);
244 for (std::size_t i = 0; i < NA; ++i)
245 for (std::size_t j = 0; j < NB; ++j) C(i, j) = rowA[i] * alBD0Bi[j];
246 const Matrix<T> X = detail::stein_solve(PAh, PBh, C);
247 const std::vector<T> v = vecmul(vecmul(vecmul(alA, D0Ai), X), D0Bi);
248 T out = num_traits<T>::from_int(0);
249 for (std::size_t i = 0; i < NB; ++i) out += v[i];
250 return out;
251}
252
253/** Default stationary vectors, matching the two-argument MATLAB call. */
254template <class T>
255T dmap_geo_mul_sum(const Dmap<T>& a, const Dmap<T>& b) {
256 return dmap_geo_mul_sum(a, b, dmap_pie(a), dmap_pie(b));
257}
258
259/** Squared distance between the autocorrelation structures of two D-MAPs. */
260template <class T>
261T dmap_dist_acf(const Dmap<T>& a, const Dmap<T>& b, const std::vector<T>& alA,
262 const std::vector<T>& alB) {
263 std::vector<unsigned> ord;
264 ord.push_back(1);
265 ord.push_back(2);
266 const std::vector<T> momA = dmap_moment(a, ord);
267 const std::vector<T> momB = dmap_moment(b, ord);
268 const T varA = momA[1] - momA[0] * momA[0];
269 const T varB = momB[1] - momB[0] * momB[0];
270 const T four = num_traits<T>::from_int(4);
271 return (dmap_geo_mul_sum(a, a, alA, alA) - momA[1] * momA[1] / four) / (varA * varA) -
273 (dmap_geo_mul_sum(a, b, alA, alB) - momA[1] * momB[1] / four) / (varA * varB) +
274 (dmap_geo_mul_sum(b, b, alB, alB) - momB[1] * momB[1] / four) / (varB * varB);
275}
276
277/** Default stationary vectors, matching the two-argument MATLAB call. */
278template <class T>
279T dmap_dist_acf(const Dmap<T>& a, const Dmap<T>& b) {
280 return dmap_dist_acf(a, b, dmap_pie(a), dmap_pie(b));
281}
282
283/** Squared distance between the lag-1 joint densities of two D-MAPs. */
284template <class T>
285T dmap_dist_lag1(const Dmap<T>& a, const Dmap<T>& b, const std::vector<T>& alA,
286 const std::vector<T>& alB) {
287 const std::size_t NA = a.order(), NB = b.order();
288 std::vector<T> ea(NA, num_traits<T>::from_int(0)), eb(NB, num_traits<T>::from_int(0));
289 for (std::size_t i = 0; i < NA; ++i) {
290 ea[i] = num_traits<T>::from_int(1);
291 for (std::size_t j = 0; j < NA; ++j) ea[i] -= a.D0(i, j);
292 }
293 for (std::size_t i = 0; i < NB; ++i) {
294 eb[i] = num_traits<T>::from_int(1);
295 for (std::size_t j = 0; j < NB; ++j) eb[i] -= b.D0(i, j);
296 }
297 Matrix<T> Cab(NA, NB, num_traits<T>::from_int(0)), Caa(NA, NA, num_traits<T>::from_int(0)),
298 Cbb(NB, NB, num_traits<T>::from_int(0));
299 for (std::size_t i = 0; i < NA; ++i)
300 for (std::size_t j = 0; j < NB; ++j) Cab(i, j) = alA[i] * alB[j];
301 for (std::size_t i = 0; i < NA; ++i)
302 for (std::size_t j = 0; j < NA; ++j) Caa(i, j) = alA[i] * alA[j];
303 for (std::size_t i = 0; i < NB; ++i)
304 for (std::size_t j = 0; j < NB; ++j) Cbb(i, j) = alB[i] * alB[j];
305 const Matrix<T> Z_AB = detail::stein_solve(a.D0.transpose(), b.D0, Cab);
306 const Matrix<T> Z_AA = detail::stein_solve(a.D0.transpose(), a.D0, Caa);
307 const Matrix<T> Z_BB = detail::stein_solve(b.D0.transpose(), b.D0, Cbb);
308 Matrix<T> Rab(NA, NB, num_traits<T>::from_int(0)), Raa(NA, NA, num_traits<T>::from_int(0)),
309 Rbb(NB, NB, num_traits<T>::from_int(0));
310 for (std::size_t i = 0; i < NA; ++i)
311 for (std::size_t j = 0; j < NB; ++j) Rab(i, j) = ea[i] * eb[j];
312 for (std::size_t i = 0; i < NA; ++i)
313 for (std::size_t j = 0; j < NA; ++j) Raa(i, j) = ea[i] * ea[j];
314 for (std::size_t i = 0; i < NB; ++i)
315 for (std::size_t j = 0; j < NB; ++j) Rbb(i, j) = eb[i] * eb[j];
316 const Matrix<T> X_AB = detail::stein_solve(a.D0, b.D0.transpose(), Rab);
317 const Matrix<T> X_AA = detail::stein_solve(a.D0, a.D0.transpose(), Raa);
318 const Matrix<T> X_BB = detail::stein_solve(b.D0, b.D0.transpose(), Rbb);
319 const std::vector<T> vA = detail::vec_colmajor(a.D1);
320 const std::vector<T> vB = detail::vec_colmajor(b.D1);
321 // v' kron(X, Z) w with the column-major vec, i.e. sum over the four indices
324 for (std::size_t p = 0; p < NB; ++p)
325 for (std::size_t q = 0; q < NB; ++q)
326 for (std::size_t r = 0; r < NB; ++r)
327 for (std::size_t s = 0; s < NB; ++s)
328 sBB += vB[p * NB + q] * X_BB(p, r) * Z_BB(q, s) * vB[r * NB + s];
329 for (std::size_t p = 0; p < NA; ++p)
330 for (std::size_t q = 0; q < NA; ++q)
331 for (std::size_t r = 0; r < NA; ++r)
332 for (std::size_t s = 0; s < NA; ++s)
333 sAA += vA[p * NA + q] * X_AA(p, r) * Z_AA(q, s) * vA[r * NA + s];
334 for (std::size_t p = 0; p < NA; ++p)
335 for (std::size_t q = 0; q < NA; ++q)
336 for (std::size_t r = 0; r < NB; ++r)
337 for (std::size_t s = 0; s < NB; ++s)
338 sAB += vA[p * NA + q] * X_AB(p, r) * Z_AB(q, s) * vB[r * NB + s];
339 return sBB + sAA - num_traits<T>::from_int(2) * sAB;
340}
341
342/** Default stationary vectors, matching the two-argument MATLAB call. */
343template <class T>
344T dmap_dist_lag1(const Dmap<T>& a, const Dmap<T>& b) {
345 return dmap_dist_lag1(a, b, dmap_pie(a), dmap_pie(b));
346}
347
348/** n interarrival times in slots, drawn by walking the phase process. */
349template <class T, class Gen>
350std::vector<unsigned> dmap_sample(const Dmap<T>& d, std::size_t n, Gen& gen) {
351 const std::size_t N = d.order();
352 const std::vector<T> al = dmap_pie(d);
353 std::uniform_real_distribution<double> unif(0.0, 1.0);
354 std::size_t phase = 0;
355 double u = unif(gen), acc = 0.0;
356 for (std::size_t i = 0; i < N; ++i) {
357 acc += num_traits<T>::to_double(al[i]);
358 if (u <= acc) {
359 phase = i;
360 break;
361 }
362 }
363 std::vector<unsigned> X(n, 0);
364 for (std::size_t i = 0; i < n; ++i) {
365 unsigned t = 0;
366 while (true) {
367 ++t;
368 const double v = unif(gen);
369 double c = 0.0;
370 std::size_t next = 2 * N - 1;
371 for (std::size_t j = 0; j < 2 * N; ++j) {
372 c += num_traits<T>::to_double(j < N ? d.D0(phase, j) : d.D1(phase, j - N));
373 if (v <= c) {
374 next = j;
375 break;
376 }
377 }
378 if (next >= N) {
379 phase = next - N;
380 X[i] = t;
381 break;
382 }
383 phase = next;
384 }
385 }
386 return X;
387}
388
389} // namespace mam
390} // namespace line
391
392#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
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
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.
Dense matrix and non-owning view.
Marked MAP (MMAP) algebra: per-class rates, class probabilities, superposition, normalization and sca...
T dmap_dist_acf(const Dmap< T > &a, const Dmap< T > &b, const std::vector< T > &alA, const std::vector< T > &alB)
Squared distance between the autocorrelation structures of two D-MAPs.
Definition dmap.h:261
T dmap_dist(const Dmap< T > &a, const Dmap< T > &b, unsigned L, const std::vector< T > &alA, const std::vector< T > &alB)
Squared L2 distance between the interarrival densities truncated at lag L.
Definition dmap.h:205
bool dmap_isfeasible(const Dmap< T > &d)
True when D0 and D1 are nonnegative and D0 + D1 is stochastic.
Definition dmap.h:150
std::vector< unsigned > dmap_sample(const Dmap< T > &d, std::size_t n, Gen &gen)
n interarrival times in slots, drawn by walking the phase process.
Definition dmap.h:350
T dmap_dist_lag1(const Dmap< T > &a, const Dmap< T > &b, const std::vector< T > &alA, const std::vector< T > &alB)
Squared distance between the lag-1 joint densities of two D-MAPs.
Definition dmap.h:285
std::vector< T > dmap_pie(const Dmap< T > &d)
Stationary phase distribution at arrival epochs.
Definition dmap.h:104
T dmap_geo_mul_sum(const Dmap< T > &a, const Dmap< T > &b, const std::vector< T > &alA, const std::vector< T > &alB)
Geometrically weighted sum of the lagged joint moments, the building block of the autocorrelation dis...
Definition dmap.h:225
T dmap_exp_mul_int(const Dmap< T > &a, const Dmap< T > &b, unsigned L, const std::vector< T > &alA, const std::vector< T > &alB)
Inner product of the two interarrival densities truncated at lag L.
Definition dmap.h:170
std::vector< T > dmap_moment(const Dmap< T > &d, const std::vector< unsigned > &orders)
Raw moments of the interarrival time in slots, for orders 1, 2 and 3 only.
Definition dmap.h:110
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
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 > matmul(const Matrix< T > &A, const Matrix< T > &B)
Matrix product A B.
Definition linalg.h:36
std::vector< T > mulvec(const Matrix< T > &A, const std::vector< T > &v)
Matrix times column vector, A v.
Definition linalg.h:62
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
std::vector< T > ones(std::size_t n)
Column vector of ones, the ubiquitous e in MAP algebra.
Definition linalg.h:104
Matrix< T > eye(std::size_t n)
Identity of order n.
Definition linalg.h:28
Number-type abstraction for the templated API port.
A discrete-time MAP: substochastic D0 (no arrival) and D1 (one arrival).
Definition dmap.h:48
Matrix< T > D0
Definition dmap.h:49
std::size_t order() const
Definition dmap.h:52
Matrix< T > D1
Definition dmap.h:50