LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
dtime.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_DTIME_H
6#define LINE_API_MAM_DTIME_H
7
8/**
9 * @file
10 * @ingroup api_mam
11 * Discrete-time (slotted) matrix-analytic primitives and queues.
12 *
13 * Port of matlab/src/api/mam/dph_from_dist.m, dph_to_dmap.m, dmap_to_dph.m,
14 * dmap_is_renewal.m, dmap_lambda.m, dmap_super.m, dmap_thin.m and
15 * mg1_dt_queue.m, plus the queue-length half of the Q-MAM discrete-time queues
16 * Q_DT_MAP_MAP_1.m and Q_DT_PH_PH_1.m.
17 *
18 * Everything measures time in SLOTS and follows the late arrival system with
19 * delayed access (LAS-DA): within a slot the service completion resolves first,
20 * arrivals are appended at the end of the slot and cannot enter service before
21 * the next one, and the level is read after both. The QBD blocks state it
22 * directly, A1 = kron(C1,D0) being the claim that a job arriving at the end of a
23 * slot is not served within it. The LDES slotted engine orders its intra-slot
24 * events the same way, so the two are directly comparable.
25 *
26 * A discrete phase-type law is (alpha, A) with P[X=k] = alpha A^(k-1) a,
27 * a = e - A e, k = 1,2,... A batch stream is a vector [A_0, A_1, ...] whose
28 * A_k carries the slots delivering k events; a plain D-MAP is the two-entry case.
29 *
30 * Two solver differences from the MATLAB twin, both deliberate and measured
31 * against it: the QBD fundamental matrix uses logarithmic reduction rather than
32 * SMCSolver's cyclic reduction (same minimal solution, both iterate to 1e-14),
33 * and the batch M/G/1-type chain is solved by level truncation rather than by
34 * MG1_CR, because C++ carries neither SMCSolver nor BuTools. The truncation
35 * level is chosen from the tail mass, so it is an accuracy knob, not a model
36 * change.
37 */
38
39#include <cstddef>
40#include <vector>
41
42#include "line/api/mam/dmap.h"
45#include "line/num/number.h"
46#include "line/util/error.h"
47#include "line/util/lu.h"
48#include "line/util/matrix.h"
49
50namespace line {
51namespace mam {
52
53/** A discrete phase-type law: initial row vector alpha and transient A. */
54template <class T>
55struct Dph {
56 std::vector<T> alpha;
58
59 std::size_t order() const { return A.rows(); }
60};
61
62/** A discrete batch arrival stream, entry k carrying the slots with k events. */
63template <class T>
64using DBatch = std::vector<Matrix<T>>;
65
66namespace detail {
67
68/** Kronecker product; C++ has no shared templated kron. */
69template <class T>
70Matrix<T> dt_kron(const Matrix<T>& A, const Matrix<T>& B) {
71 Matrix<T> C(A.rows() * B.rows(), A.cols() * B.cols(), num_traits<T>::from_int(0));
72 for (std::size_t i = 0; i < A.rows(); ++i)
73 for (std::size_t j = 0; j < A.cols(); ++j) {
74 const T& a = A(i, j);
75 for (std::size_t k = 0; k < B.rows(); ++k)
76 for (std::size_t l = 0; l < B.cols(); ++l)
77 C(i * B.rows() + k, j * B.cols() + l) = a * B(k, l);
78 }
79 return C;
80}
81
82template <class T>
83Matrix<T> dt_eye(std::size_t n) {
85 for (std::size_t i = 0; i < n; ++i) I(i, i) = num_traits<T>::from_int(1);
86 return I;
87}
88
89template <class T>
90Matrix<T> dt_add(const Matrix<T>& A, const Matrix<T>& B) {
91 Matrix<T> C = A;
92 for (std::size_t i = 0; i < A.rows(); ++i)
93 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = C(i, j) + B(i, j);
94 return C;
95}
96
97template <class T>
98Matrix<T> dt_sub(const Matrix<T>& A, const Matrix<T>& B) {
99 Matrix<T> C = A;
100 for (std::size_t i = 0; i < A.rows(); ++i)
101 for (std::size_t j = 0; j < A.cols(); ++j) C(i, j) = C(i, j) - B(i, j);
102 return C;
103}
104
105template <class T>
106Matrix<T> dt_mul(const Matrix<T>& A, const Matrix<T>& B) {
108 for (std::size_t i = 0; i < A.rows(); ++i)
109 for (std::size_t k = 0; k < A.cols(); ++k) {
110 const T& a = A(i, k);
111 for (std::size_t j = 0; j < B.cols(); ++j) C(i, j) = C(i, j) + a * B(k, j);
112 }
113 return C;
114}
115
116/** Solves X * M = N for X, i.e. M^T X^T = N^T column by column. */
117template <class T>
118Matrix<T> dt_right_divide(const Matrix<T>& N, const Matrix<T>& M) {
119 std::size_t n = M.rows();
120 Matrix<T> Mt(n, n);
121 for (std::size_t i = 0; i < n; ++i)
122 for (std::size_t j = 0; j < n; ++j) Mt(i, j) = M(j, i);
123 Matrix<T> X(N.rows(), n);
124 for (std::size_t r = 0; r < N.rows(); ++r) {
125 std::vector<T> rhs(n);
126 for (std::size_t j = 0; j < n; ++j) rhs[j] = N(r, j);
127 std::vector<T> sol = solve(Mt, rhs);
128 for (std::size_t j = 0; j < n; ++j) X(r, j) = sol[j];
129 }
130 return X;
131}
132
133/** Left-multiplies the inverse: returns M^-1 * N. */
134template <class T>
135Matrix<T> dt_left_solve(const Matrix<T>& M, const Matrix<T>& N) {
136 std::size_t n = M.rows();
137 Matrix<T> X(n, N.cols());
138 for (std::size_t c = 0; c < N.cols(); ++c) {
139 std::vector<T> rhs(n);
140 for (std::size_t i = 0; i < n; ++i) rhs[i] = N(i, c);
141 std::vector<T> sol = solve(M, rhs);
142 for (std::size_t i = 0; i < n; ++i) X(i, c) = sol[i];
143 }
144 return X;
145}
146
147} // namespace detail
148
149/**
150 * Exact discrete phase-type representation of a lattice-valued law.
151 *
152 * Geometric, Det and DiscreteUniform are represented EXACTLY, not
153 * moment-matched: a fitted surrogate would leave the lattice the caller relies
154 * on, so any other family is an error here.
155 */
156template <class T>
157Dph<T> dph_from_dist(lang::ProcessType type, const T& mean_slots, const T& scv) {
158 const T one = num_traits<T>::from_int(1);
159 const T zero = num_traits<T>::from_int(0);
160 const double tol = 1e-8;
161
162 if (type == lang::ProcessType::GEOMETRIC) {
163 T p = one / mean_slots;
164 if (num_traits<T>::to_double(p) > 1 + tol || num_traits<T>::to_double(p) <= 0)
165 throw InputError("dph_from_dist: Geometric mean is outside the support {1,2,...}");
166 if (num_traits<T>::to_double(p) > 1) p = one;
167 Dph<T> d;
168 d.alpha.assign(1, one);
169 d.A = Matrix<T>(1, 1, one - p);
170 return d;
171 }
172
173 if (type == lang::ProcessType::DET) {
174 double m = num_traits<T>::to_double(mean_slots);
175 long k = static_cast<long>(m + 0.5);
176 if (std::abs(m - static_cast<double>(k)) > tol * std::max(1.0, m) || k < 1)
177 throw InputError("dph_from_dist: Det is not a positive integral number of slots");
178 std::size_t n = static_cast<std::size_t>(k);
179 Dph<T> d;
180 d.alpha.assign(n, zero);
181 d.alpha[0] = one;
182 d.A = Matrix<T>(n, n, zero);
183 for (std::size_t i = 0; i + 1 < n; ++i) d.A(i, i + 1) = one;
184 return d;
185 }
186
187 if (type == lang::ProcessType::DUNIFORM) {
188 double m = num_traits<T>::to_double(mean_slots);
189 double v = num_traits<T>::to_double(scv) * m * m;
190 double width = std::sqrt(std::max(0.0, 12 * v + 1)) - 1;
191 long lo = static_cast<long>(m - width / 2 + 0.5);
192 long hi = static_cast<long>(m + width / 2 + 0.5);
193 if (lo < 1 || hi < lo)
194 throw InputError("dph_from_dist: DiscreteUniform is outside the support {1,2,...}");
195 std::size_t n = static_cast<std::size_t>(hi);
196 Dph<T> d;
197 d.alpha.assign(n, zero);
198 d.alpha[0] = one;
199 d.A = Matrix<T>(n, n, zero);
200 for (long j = 1; j < hi; ++j) {
201 // hazard of absorbing at step j, zero below the lower bound
202 T h = j < lo ? zero : one / num_traits<T>::from_int(static_cast<int>(hi - j + 1));
203 d.A(static_cast<std::size_t>(j - 1), static_cast<std::size_t>(j)) = one - h;
204 }
205 return d;
206 }
207
208 throw InputError(
209 "dph_from_dist: the process type has no exact discrete phase-type representation. "
210 "The discrete-time path accepts Geometric, Det on the slot lattice, DiscreteUniform "
211 "and DMAP.");
212}
213
214/** Renewal D-MAP (A, a alpha) of a discrete phase-type law. */
215template <class T>
217 const T one = num_traits<T>::from_int(1);
218 std::size_t m = d.A.rows();
219 Dmap<T> out;
220 out.D0 = d.A;
221 out.D1 = Matrix<T>(m, m, num_traits<T>::from_int(0));
222 for (std::size_t i = 0; i < m; ++i) {
223 T rowsum = num_traits<T>::from_int(0);
224 for (std::size_t j = 0; j < m; ++j) rowsum = rowsum + d.A(i, j);
225 T a = one - rowsum;
226 for (std::size_t j = 0; j < m; ++j) out.D1(i, j) = a * d.alpha[j];
227 }
228 return out;
229}
230
231/** True when D1 has rank one, i.e. the process renews at every event. */
232template <class T>
233bool dmap_is_renewal(const Dmap<T>& d) {
234 std::size_t m = d.D0.rows();
235 if (m == 1) return true;
236 std::vector<T> row_mass(m, num_traits<T>::from_int(0));
237 std::size_t pivot = 0;
238 for (std::size_t i = 0; i < m; ++i) {
239 for (std::size_t j = 0; j < m; ++j) row_mass[i] = row_mass[i] + d.D1(i, j);
240 if (num_traits<T>::to_double(row_mass[i]) > num_traits<T>::to_double(row_mass[pivot]))
241 pivot = i;
242 }
243 if (num_traits<T>::to_double(row_mass[pivot]) <= 1e-14) return false;
244 for (std::size_t i = 0; i < m; ++i)
245 for (std::size_t j = 0; j < m; ++j) {
246 T expected = row_mass[i] * d.D1(pivot, j) / row_mass[pivot];
247 if (std::abs(num_traits<T>::to_double(d.D1(i, j) - expected)) > 1e-8) return false;
248 }
249 return true;
250}
251
252/** Discrete phase-type law underlying a renewal D-MAP. */
253template <class T>
255 if (!dmap_is_renewal(d))
256 throw InputError("dmap_to_dph: the D-MAP does not renew at events, so it has no DPH form");
257 std::size_t m = d.D0.rows();
258 std::vector<T> row_mass(m, num_traits<T>::from_int(0));
259 std::size_t pivot = 0;
260 for (std::size_t i = 0; i < m; ++i) {
261 for (std::size_t j = 0; j < m; ++j) row_mass[i] = row_mass[i] + d.D1(i, j);
262 if (num_traits<T>::to_double(row_mass[i]) > num_traits<T>::to_double(row_mass[pivot]))
263 pivot = i;
264 }
265 Dph<T> out;
266 out.A = d.D0;
267 out.alpha.resize(m);
268 for (std::size_t j = 0; j < m; ++j) out.alpha[j] = d.D1(pivot, j) / row_mass[pivot];
269 return out;
270}
271
272/**
273 * Mean number of EVENTS per slot, pi sum_k k A_k e. A slot carrying a batch of
274 * two counts twice, which is what Little's law consumes downstream.
275 */
276template <class T>
278 std::size_t m = A[0].rows();
280 for (std::size_t k = 0; k < A.size(); ++k) P = detail::dt_add(P, A[k]);
281 std::vector<T> pi = mc::dtmc_solve(P);
282 T lambda = num_traits<T>::from_int(0);
283 for (std::size_t k = 1; k < A.size(); ++k)
284 for (std::size_t i = 0; i < m; ++i)
285 for (std::size_t j = 0; j < m; ++j)
286 lambda = lambda + pi[i] * num_traits<T>::from_int(static_cast<int>(k)) * A[k](i, j);
287 return lambda;
288}
289
290/**
291 * Superposition, E_k = sum_{i+j=k} kron(A_i, B_j).
292 *
293 * NOT closed on D-MAPs: two slotted streams fire in the same slot with positive
294 * probability, so the merged stream carries batches. Folding E_2 into E_1 would
295 * conserve neither the arrival rate nor the slot in which the work appears, so
296 * the batch dimension is kept and the station is solved as an M/G/1-type chain
297 * instead of a QBD.
298 */
299template <class T>
301 std::size_t p = A.size() - 1, q = B.size() - 1;
302 DBatch<T> E;
303 E.reserve(p + q + 1);
304 for (std::size_t k = 0; k <= p + q; ++k) {
305 Matrix<T> Ek(A[0].rows() * B[0].rows(), A[0].cols() * B[0].cols(),
307 std::size_t lo = k > q ? k - q : 0;
308 for (std::size_t i = lo; i <= (k < p ? k : p); ++i)
309 Ek = detail::dt_add(Ek, detail::dt_kron(A[i], B[k - i]));
310 E.push_back(Ek);
311 }
312 return E;
313}
314
315/**
316 * Bernoulli thinning, B_k = sum_{n>=k} C(n,k) p^k (1-p)^(n-k) A_n. The phase
317 * process is untouched, so this is exact for PROB/RAND routing.
318 */
319template <class T>
320DBatch<T> dmap_thin(const DBatch<T>& A, const T& p) {
321 double pd = num_traits<T>::to_double(p);
322 if (pd < 0 || pd > 1) throw InputError("dmap_thin: the routing probability must lie in [0,1]");
323 const T one = num_traits<T>::from_int(1);
324 std::size_t n = A.size() - 1;
325 DBatch<T> B;
326 B.reserve(n + 1);
327 for (std::size_t k = 0; k <= n; ++k) {
328 Matrix<T> Bk(A[0].rows(), A[0].cols(), num_traits<T>::from_int(0));
329 for (std::size_t j = k; j <= n; ++j) {
331 for (std::size_t i = 0; i < k; ++i)
332 w = w * num_traits<T>::from_int(static_cast<int>(j - i)) /
333 num_traits<T>::from_int(static_cast<int>(i + 1));
334 for (std::size_t i = 0; i < k; ++i) w = w * p;
335 for (std::size_t i = 0; i < j - k; ++i) w = w * (one - p);
336 if (num_traits<T>::to_double(w) > 0)
337 for (std::size_t r = 0; r < Bk.rows(); ++r)
338 for (std::size_t c = 0; c < Bk.cols(); ++c)
339 Bk(r, c) = Bk(r, c) + w * A[j](r, c);
340 }
341 B.push_back(Bk);
342 }
343 // trailing zero batch levels carry no mass and only inflate the blocks
344 while (B.size() > 2) {
345 double worst = 0;
346 for (std::size_t r = 0; r < B.back().rows(); ++r)
347 for (std::size_t c = 0; c < B.back().cols(); ++c)
348 worst = std::max(worst, std::abs(num_traits<T>::to_double(B.back()(r, c))));
349 if (worst >= 1e-14) break;
350 B.pop_back();
351 }
352 return B;
353}
354
355namespace detail {
356
357/**
358 * Raw moments over {1,2,...} of a two-phase acyclic discrete phase-type law
359 * written as a mixture: with probability b the sum of two geometrics with
360 * success probabilities p1 and p2, otherwise the second geometric alone.
361 */
362inline void dph2_moments(double b, double p1, double p2, double* m1, double* m2, double* m3) {
363 const double A1 = 1 / p1, A2 = 1 / p2;
364 const double S1 = (2 - p1) / (p1 * p1), S2 = (2 - p2) / (p2 * p2);
365 const double C1 = (p1 * p1 - 6 * p1 + 6) / (p1 * p1 * p1);
366 const double C2 = (p2 * p2 - 6 * p2 + 6) / (p2 * p2 * p2);
367 *m1 = b * A1 + A2;
368 *m2 = b * (S1 + 2 * A1 * A2) + S2;
369 *m3 = b * (C1 + 3 * S1 * A2 + 3 * A1 * S2) + C2;
370}
371
372/**
373 * Fits the mixture above to three raw moments by a damped Newton iteration on
374 * (p1,p2), the mixing weight following from the first moment. Returns false
375 * when no feasible triple is reached from any start.
376 */
377inline bool dph2_from_3moments(double m1, double m2, double m3, double* b_out, double* p1_out,
378 double* p2_out) {
379 const double starts[4][2] = {{0.5, 0.5}, {0.9, 1 / std::max(1.0, m1)}, {0.2, 0.8}, {0.99, 0.3}};
380 for (int s = 0; s < 4; ++s) {
381 double p1 = starts[s][0], p2 = starts[s][1];
382 for (int it = 0; it < 200; ++it) {
383 const double b = (m1 - 1 / p2) * p1;
384 double f1, f2, f3;
385 dph2_moments(b, p1, p2, &f1, &f2, &f3);
386 const double r2 = f2 - m2, r3 = f3 - m3;
387 if (std::abs(r2) <= 1e-11 * std::max(1.0, std::abs(m2)) &&
388 std::abs(r3) <= 1e-11 * std::max(1.0, std::abs(m3))) {
389 if (b < -1e-9 || b > 1 + 1e-9 || p1 <= 0 || p1 > 1 || p2 <= 0 || p2 > 1) break;
390 *b_out = std::min(1.0, std::max(0.0, b));
391 *p1_out = p1;
392 *p2_out = p2;
393 return true;
394 }
395 const double h = 1e-7;
396 double a2, a3, c2, c3, d2, d3, dummy;
397 dph2_moments((m1 - 1 / p2) * (p1 + h), p1 + h, p2, &dummy, &a2, &a3);
398 dph2_moments((m1 - 1 / (p2 + h)) * p1, p1, p2 + h, &dummy, &c2, &c3);
399 const double j11 = (a2 - f2) / h, j12 = (c2 - f2) / h;
400 const double j21 = (a3 - f3) / h, j22 = (c3 - f3) / h;
401 const double det = j11 * j22 - j12 * j21;
402 if (std::abs(det) < 1e-18) break;
403 double dp1 = -(j22 * r2 - j12 * r3) / det;
404 double dp2 = -(-j21 * r2 + j11 * r3) / det;
405 // damping keeps the step inside (0,1], where the parameters live
406 double step = 1.0;
407 while (step > 1e-6 && (p1 + step * dp1 <= 1e-9 || p1 + step * dp1 > 1 ||
408 p2 + step * dp2 <= 1e-9 || p2 + step * dp2 > 1))
409 step /= 2;
410 if (step <= 1e-6) break;
411 p1 += step * dp1;
412 p2 += step * dp2;
413 }
414 }
415 return false;
416}
417
418} // namespace detail
419
420/**
421 * Reduces the order of a D-MAP by matching interevent moments.
422 *
423 * Leaves the process untouched while its order stays within `max_order`, and
424 * otherwise replaces it by the two-phase discrete phase-type law with the same
425 * first three interevent moments, read back as a renewal D-MAP. Correlation is
426 * NOT preserved, which is the reason the multi-station discrete-time path is an
427 * approximation. When the moment triple is outside the DPH(2) region the
428 * fallback keeps the exact mean with a Geometric, so the event rate of the
429 * decomposition is conserved in every branch.
430 *
431 * The MATLAB twin reaches the same three moments through BuTools
432 * (MGFromMoments then CanonicalFromDPH2), which admits the wider
433 * matrix-geometric class; this port solves the acyclic DPH(2) directly, so a
434 * triple that is MG(2)-feasible but not DPH(2)-feasible takes the Geometric
435 * fallback here and the two-phase fit there.
436 */
437template <class T>
438Dmap<T> dmap_compress(const Dmap<T>& d, std::size_t max_order) {
439 if (d.D0.rows() <= max_order) return d;
440
441 std::vector<T> moms = dmap_moment(d, std::vector<unsigned>{1u, 2u, 3u});
442 const double m1 = num_traits<T>::to_double(moms[0]);
443 const double m2 = num_traits<T>::to_double(moms[1]);
444 const double m3 = num_traits<T>::to_double(moms[2]);
445
446 double b = 0, p1 = 0, p2 = 0;
447 if (detail::dph2_from_3moments(m1, m2, m3, &b, &p1, &p2)) {
448 Dph<T> fit;
449 fit.alpha.assign(2, num_traits<T>::from_int(0));
451 fit.alpha[1] = num_traits<T>::from_double(1 - b);
452 fit.A = Matrix<T>(2, 2, num_traits<T>::from_int(0));
453 fit.A(0, 0) = num_traits<T>::from_double(1 - p1);
454 fit.A(0, 1) = num_traits<T>::from_double(p1);
455 fit.A(1, 1) = num_traits<T>::from_double(1 - p2);
456 Dmap<T> cand = dph_to_dmap(fit);
457 if (dmap_isfeasible(cand)) return cand;
458 }
459
460 // the moment triple is not DPH(2)-feasible: keep the rate, drop the shape
461 double p = 1 / m1;
462 p = std::min(1.0, std::max(1e-12, p));
463 Dph<T> geo;
464 geo.alpha.assign(1, num_traits<T>::from_int(1));
465 geo.A = Matrix<T>(1, 1, num_traits<T>::from_double(1 - p));
466 return dph_to_dmap(geo);
467}
468
469/**
470 * Order reduction of a BATCH stream.
471 *
472 * The process of NONEMPTY SLOTS is compressed as a plain D-MAP and the
473 * stationary batch-size distribution, conditional on the slot being nonempty,
474 * is reattached to it. Compressing the phase alone would keep the slot process
475 * and lose the batch sizes, which would silently rescale the event rate.
476 *
477 * MATLAB twin: dmap_compress_batch.m
478 */
479template <class T>
480DBatch<T> dmap_compress_batch(const DBatch<T>& A, std::size_t max_order) {
481 if (A[0].rows() <= max_order) return A;
482 const std::size_t nb = A.size() - 1, m = A[0].rows();
483
484 Matrix<T> Ptot = A[0];
485 for (std::size_t k = 1; k < A.size(); ++k) Ptot = detail::dt_add(Ptot, A[k]);
486 std::vector<T> pi_phase = mc::dtmc_solve(Ptot);
487
488 std::vector<T> qraw(nb, num_traits<T>::from_int(0));
489 T mass = num_traits<T>::from_int(0);
490 for (std::size_t k = 0; k < nb; ++k) {
491 for (std::size_t i = 0; i < m; ++i)
492 for (std::size_t j = 0; j < m; ++j) qraw[k] = qraw[k] + pi_phase[i] * A[k + 1](i, j);
493 mass = mass + qraw[k];
494 }
495 if (num_traits<T>::to_double(mass) <= 0)
496 throw InputError("dmap_compress_batch: the batch stream carries no events");
497
498 Dmap<T> marked;
499 marked.D0 = A[0];
500 marked.D1 = detail::dt_sub(Ptot, A[0]);
501 Dmap<T> markedC = dmap_compress(marked, max_order);
502
503 DBatch<T> out;
504 out.push_back(markedC.D0);
505 for (std::size_t k = 0; k < nb; ++k) {
506 Matrix<T> blk = markedC.D1;
507 const T w = qraw[k] / mass;
508 for (std::size_t i = 0; i < blk.rows(); ++i)
509 for (std::size_t j = 0; j < blk.cols(); ++j) blk(i, j) = blk(i, j) * w;
510 out.push_back(blk);
511 }
512 return out;
513}
514
515/**
516 * G matrix of a discrete-time QBD by logarithmic reduction (Latouche and
517 * Ramaswami). The blocks are stochastic, so no uniformization is needed; the
518 * MATLAB twin reaches the same minimal solution through SMCSolver's cyclic
519 * reduction.
520 */
521template <class T>
522Matrix<T> qbd_dt_g(const Matrix<T>& A0, const Matrix<T>& A1, const Matrix<T>& A2,
523 int max_iter = 200) {
524 std::size_t m = A1.rows();
525 Matrix<T> I = detail::dt_eye<T>(m);
526 Matrix<T> inv_local = detail::dt_left_solve(detail::dt_sub(I, A1), I);
527 Matrix<T> B0 = detail::dt_mul(inv_local, A0);
528 Matrix<T> B2 = detail::dt_mul(inv_local, A2);
529 Matrix<T> G = B0;
530 Matrix<T> PI = B2;
531
532 for (int it = 0; it < max_iter; ++it) {
533 Matrix<T> A1n = detail::dt_add(detail::dt_mul(B0, B2), detail::dt_mul(B2, B0));
534 Matrix<T> A0n = detail::dt_mul(B0, B0);
535 Matrix<T> A2n = detail::dt_mul(B2, B2);
536 Matrix<T> inv_n = detail::dt_left_solve(detail::dt_sub(I, A1n), I);
537 B0 = detail::dt_mul(inv_n, A0n);
538 B2 = detail::dt_mul(inv_n, A2n);
539 G = detail::dt_add(G, detail::dt_mul(PI, B0));
540 PI = detail::dt_mul(PI, B2);
541
542 double residual = 0;
543 for (std::size_t i = 0; i < m; ++i) {
544 T rowsum = num_traits<T>::from_int(0);
545 for (std::size_t j = 0; j < m; ++j) rowsum = rowsum + G(i, j);
546 residual = std::max(residual,
547 std::abs(1.0 - num_traits<T>::to_double(rowsum)));
548 }
549 if (residual < 1e-14) break;
550 }
551 return G;
552}
553
554/**
555 * G matrix of an M/G/1-type chain by functional iteration on
556 * G = sum_k A_k G^k, the blocks being stochastic.
557 *
558 * `A[0]` is the down-one block and `A[k]` the block raising the level by k-1.
559 * SMCSolver reaches the same minimal solution by cyclic reduction; the JAR port
560 * of that routine costs 19.8 s at order 17 where functional iteration costs
561 * 25 ms and agrees to 1.6e-15, which is why neither this port nor the JAR one
562 * takes it.
563 */
564template <class T>
565Matrix<T> mg1_dt_g(const std::vector<Matrix<T>>& A, int max_iter = 5000,
566 double tol = 1e-14) {
567 std::size_t m = A[0].rows();
568 Matrix<T> G = A[0];
569 for (int it = 0; it < max_iter; ++it) {
570 Matrix<T> Gnew = A[0];
571 Matrix<T> Gpow = G;
572 for (std::size_t k = 1; k < A.size(); ++k) {
573 Gnew = detail::dt_add(Gnew, detail::dt_mul(A[k], Gpow));
574 if (k + 1 < A.size()) Gpow = detail::dt_mul(Gpow, G);
575 }
576 double delta = 0;
577 for (std::size_t i = 0; i < m; ++i)
578 for (std::size_t j = 0; j < m; ++j)
579 delta = std::max(delta, std::abs(num_traits<T>::to_double(Gnew(i, j) - G(i, j))));
580 G = Gnew;
581 if (delta < tol) break;
582 }
583 return G;
584}
585
586/**
587 * Stationary vector of an M/G/1-type chain by the stable Ramaswami formula.
588 *
589 * `A` repeats from level one and `B` is the boundary row, both as block lists
590 * with `A[0]` the down-one block. The recursion is level-by-level, so it costs
591 * O(levels * m^3) and not O((levels*m)^3): a dense solve of the truncated chain
592 * is cubic in the WHOLE state space and stops being affordable at the second
593 * station of a decomposition, where the arrival process already carries the
594 * level space of the first.
595 *
596 * MATLAB twin: MG1_pi.m (SMCSolver, Van Houdt), default-boundary branch.
597 */
598template <class T>
599std::vector<T> mg1_dt_pi(const std::vector<Matrix<T>>& B, const std::vector<Matrix<T>>& A,
600 std::size_t max_num_comp = 1000) {
601 const T one = num_traits<T>::from_int(1);
602 const T zero = num_traits<T>::from_int(0);
603 std::size_t m = A[0].rows();
604 std::size_t dega = A.size() - 1;
605 std::size_t degb = B.size() - 1;
606 Matrix<T> I = detail::dt_eye<T>(m);
607 Matrix<T> G = mg1_dt_g(A);
608
609 // hatA_i = sum_{v>=i} A_v G^(v-i), while sumA accumulates the originals
610 std::vector<Matrix<T>> hatA = A;
611 Matrix<T> sumA = A[dega];
612 std::vector<T> beta(m, zero);
613 for (std::size_t i = 0; i < m; ++i)
614 for (std::size_t j = 0; j < m; ++j) beta[i] = beta[i] + sumA(i, j);
615 for (std::size_t i = dega; i-- > 1;) {
616 sumA = detail::dt_add(sumA, A[i]);
617 hatA[i] = detail::dt_add(A[i], detail::dt_mul(hatA[i + 1], G));
618 for (std::size_t r = 0; r < m; ++r)
619 for (std::size_t c = 0; c < m; ++c) beta[r] = beta[r] + sumA(r, c);
620 }
621 sumA = detail::dt_add(sumA, A[0]);
622
623 std::vector<T> theta = mc::dtmc_solve(sumA);
624 T drift = zero;
625 for (std::size_t i = 0; i < m; ++i) drift = drift + theta[i] * beta[i];
626 if (num_traits<T>::to_double(drift) >= 1)
627 throw InputError("mg1_dt_pi: the chain characterized by A is not positive recurrent");
628
629 Matrix<T> invBarA1 = detail::dt_left_solve(detail::dt_sub(I, hatA[1]), I);
630
631 // hatB_i = sum_{v>=i} B_v G^(v-i), sumBB0 = sum_{v>=1} B_v,
632 // Bbeta = sum_{v>=1} (v-1) B_v e
633 std::vector<Matrix<T>> hatB = B;
634 Matrix<T> sumBB0 = B[degb];
635 std::vector<T> Bbeta(m, zero);
636 for (std::size_t i = degb; i-- > 1;) {
637 for (std::size_t r = 0; r < m; ++r)
638 for (std::size_t c = 0; c < m; ++c) Bbeta[r] = Bbeta[r] + sumBB0(r, c);
639 sumBB0 = detail::dt_add(sumBB0, B[i]);
640 hatB[i] = detail::dt_add(B[i], detail::dt_mul(hatB[i + 1], G));
641 }
642
643 Matrix<T> Kmat = detail::dt_add(B[0], detail::dt_mul(hatB[1], G));
644 std::vector<T> kappa = mc::dtmc_solve(Kmat);
645 std::vector<T> g = mc::dtmc_solve(G);
646
647 // temp = rowsum(inv(I - sumA - (e - beta) g))
648 Matrix<T> W = detail::dt_sub(I, sumA);
649 for (std::size_t r = 0; r < m; ++r)
650 for (std::size_t c = 0; c < m; ++c) W(r, c) = W(r, c) - (one - beta[r]) * g[c];
651 Matrix<T> invW = detail::dt_left_solve(W, I);
652 std::vector<T> temp(m, zero);
653 for (std::size_t r = 0; r < m; ++r)
654 for (std::size_t c = 0; c < m; ++c) temp[r] = temp[r] + invW(r, c);
655
656 const T inv_slack = one / (one - drift);
657 std::vector<T> psi1(m, zero), psi2(m, one);
658 Matrix<T> M1 = detail::dt_sub(detail::dt_sub(I, A[0]), hatA[1]);
659 Matrix<T> M2 = detail::dt_sub(sumBB0, hatB[1]);
660 for (std::size_t r = 0; r < m; ++r) {
661 T acc1 = zero, acc2 = zero, a0row = zero;
662 for (std::size_t c = 0; c < m; ++c) {
663 acc1 = acc1 + M1(r, c) * temp[c];
664 acc2 = acc2 + M2(r, c) * temp[c];
665 a0row = a0row + A[0](r, c);
666 }
667 psi1[r] = acc1 + inv_slack * a0row;
668 psi2[r] = one + acc2 + inv_slack * Bbeta[r];
669 }
670 // tildekappa1 = psi2 + hatB_1 inv(I - hatA_1) psi1
671 std::vector<T> tmp(m, zero), tilde(m, zero);
672 for (std::size_t r = 0; r < m; ++r)
673 for (std::size_t c = 0; c < m; ++c) tmp[r] = tmp[r] + invBarA1(r, c) * psi1[c];
674 for (std::size_t r = 0; r < m; ++r) {
675 T acc = zero;
676 for (std::size_t c = 0; c < m; ++c) acc = acc + hatB[1](r, c) * tmp[c];
677 tilde[r] = psi2[r] + acc;
678 }
679 T denom = zero;
680 for (std::size_t r = 0; r < m; ++r) denom = denom + kappa[r] * tilde[r];
681
682 std::vector<std::vector<T>> pi;
683 std::vector<T> pi0(m, zero);
684 for (std::size_t r = 0; r < m; ++r) pi0[r] = kappa[r] / denom;
685 pi.push_back(pi0);
686
687 double sumpi = 0;
688 for (std::size_t r = 0; r < m; ++r) sumpi += num_traits<T>::to_double(pi0[r]);
689 std::size_t numit = 1;
690 while (sumpi < 1 - 1e-10 && numit < max_num_comp) {
691 std::vector<T> pin(m, zero);
692 if (numit <= degb)
693 for (std::size_t r = 0; r < m; ++r)
694 for (std::size_t c = 0; c < m; ++c)
695 pin[c] = pin[c] + pi0[r] * hatB[numit](r, c);
696 for (std::size_t j = 1; j <= std::min(numit - 1, dega - 1); ++j)
697 for (std::size_t r = 0; r < m; ++r)
698 for (std::size_t c = 0; c < m; ++c)
699 pin[c] = pin[c] + pi[numit - j][r] * hatA[j + 1](r, c);
700 std::vector<T> row(m, zero);
701 for (std::size_t r = 0; r < m; ++r)
702 for (std::size_t c = 0; c < m; ++c) row[c] = row[c] + pin[r] * invBarA1(r, c);
703 pi.push_back(row);
704 for (std::size_t r = 0; r < m; ++r) sumpi += num_traits<T>::to_double(row[r]);
705 ++numit;
706 }
707
708 std::vector<T> out;
709 out.reserve(pi.size() * m);
710 for (std::size_t i = 0; i < pi.size(); ++i)
711 for (std::size_t r = 0; r < m; ++r) out.push_back(pi[i][r]);
712 return out;
713}
714
715/**
716 * Queue length distribution of a discrete-time D-MAP/D-MAP/1/FCFS queue, the
717 * queue-length half of Q_DT_MAP_MAP_1.
718 *
719 * Entry i is Prob[i customers in system] under LAS-DA. The waiting and sojourn
720 * pmfs of the Q-MAM routine are deliberately not ported: no LINE caller consumes
721 * them, and the discrete-time solver path reads the queue length alone.
722 */
723template <class T>
724std::vector<T> q_dt_map_map_1(const Dmap<T>& arv, const Dmap<T>& svc,
725 std::size_t max_num_comp = 1000) {
726 const T one = num_traits<T>::from_int(1);
727 std::size_t ma = arv.D0.rows(), ms = svc.D0.rows(), mtot = ma * ms;
728
729 std::vector<T> pi_a = mc::dtmc_solve(detail::dt_add(arv.D0, arv.D1));
730 T avga = num_traits<T>::from_int(0);
731 for (std::size_t i = 0; i < ma; ++i)
732 for (std::size_t j = 0; j < ma; ++j) avga = avga + pi_a[i] * arv.D1(i, j);
733 std::vector<T> pi_s = mc::dtmc_solve(detail::dt_add(svc.D0, svc.D1));
734 T avgs = num_traits<T>::from_int(0);
735 for (std::size_t i = 0; i < ms; ++i)
736 for (std::size_t j = 0; j < ms; ++j) avgs = avgs + pi_s[i] * svc.D1(i, j);
737 if (num_traits<T>::to_double(avga / avgs) >= 1)
738 throw InputError("q_dt_map_map_1: the load of the system exceeds one");
739
740 Matrix<T> Ims = detail::dt_eye<T>(ms);
741 Matrix<T> Am1 = detail::dt_kron(arv.D0, svc.D1);
742 Matrix<T> A0 = detail::dt_add(detail::dt_kron(arv.D0, svc.D0),
743 detail::dt_kron(arv.D1, svc.D1));
744 Matrix<T> A1 = detail::dt_kron(arv.D1, svc.D0);
745 Matrix<T> B0 = detail::dt_kron(arv.D0, Ims);
746 Matrix<T> B1 = detail::dt_kron(arv.D1, Ims);
747
748 Matrix<T> G = qbd_dt_g(Am1, A0, A1);
749 // R = A1 (I - A0 - A1 G)^-1
750 Matrix<T> I = detail::dt_eye<T>(mtot);
751 Matrix<T> denom = detail::dt_sub(detail::dt_sub(I, A0), detail::dt_mul(A1, G));
752 Matrix<T> R = detail::dt_right_divide(A1, denom);
753
754 // General boundary [B1; A0 + R Am1]: the empty system has its own local
755 // block, so levels 0 and 1 are solved together (QBD_pi.m else-branch)
756 Matrix<T> lower = detail::dt_add(A0, detail::dt_mul(R, Am1));
757 Matrix<T> joint(2 * mtot, 2 * mtot, num_traits<T>::from_int(0));
758 for (std::size_t i = 0; i < mtot; ++i)
759 for (std::size_t j = 0; j < mtot; ++j) {
760 joint(i, j) = B0(i, j);
761 joint(mtot + i, j) = Am1(i, j);
762 joint(i, mtot + j) = B1(i, j);
763 joint(mtot + i, mtot + j) = lower(i, j);
764 }
765 std::vector<T> pi01 = mc::dtmc_solve(joint);
766
767 Matrix<T> temp = detail::dt_left_solve(detail::dt_sub(I, R), I);
768 std::vector<T> pi0(pi01.begin(), pi01.begin() + mtot);
769 std::vector<T> pi1(pi01.begin() + mtot, pi01.end());
770 T norm = num_traits<T>::from_int(0);
771 for (std::size_t i = 0; i < mtot; ++i) norm = norm + pi0[i];
772 for (std::size_t i = 0; i < mtot; ++i)
773 for (std::size_t j = 0; j < mtot; ++j) norm = norm + pi1[i] * temp(i, j);
774 for (std::size_t i = 0; i < mtot; ++i) {
775 pi0[i] = pi0[i] / norm;
776 pi1[i] = pi1[i] / norm;
777 }
778
779 std::vector<T> ql;
780 T mass0 = num_traits<T>::from_int(0);
781 for (std::size_t i = 0; i < mtot; ++i) mass0 = mass0 + pi0[i];
782 ql.push_back(mass0);
783 std::vector<T> cur = pi1;
784 double acc = num_traits<T>::to_double(mass0);
785 for (std::size_t it = 0; it < max_num_comp; ++it) {
786 T mass = num_traits<T>::from_int(0);
787 for (std::size_t i = 0; i < mtot; ++i) mass = mass + cur[i];
788 ql.push_back(mass);
789 acc += num_traits<T>::to_double(mass);
790 if (acc > 1 - 1e-10) break;
791 std::vector<T> nxt(mtot, num_traits<T>::from_int(0));
792 for (std::size_t j = 0; j < mtot; ++j)
793 for (std::size_t i = 0; i < mtot; ++i) nxt[j] = nxt[j] + cur[i] * R(i, j);
794 cur = nxt;
795 }
796
797 T total = num_traits<T>::from_int(0);
798 for (std::size_t i = 0; i < ql.size(); ++i) total = total + ql[i];
799 for (std::size_t i = 0; i < ql.size(); ++i) ql[i] = ql[i] / total;
800 return ql;
801}
802
803/** Queue length of a discrete-time DPH/DPH/1/FCFS queue, via the D-MAP route. */
804template <class T>
805std::vector<T> q_dt_ph_ph_1(const Dph<T>& arv, const Dph<T>& svc,
806 std::size_t max_num_comp = 1000) {
807 return q_dt_map_map_1(dph_to_dmap(arv), dph_to_dmap(svc), max_num_comp);
808}
809
810/** Outcome of a slotted station solve. */
811template <class T>
813 T QN;
814 T UN;
815 T TN;
816 std::vector<T> ql;
818};
819
820/**
821 * Discrete-time single-server queue with batch D-MAP arrivals, DBMAP/D-MAP/1.
822 *
823 * The chain is M/G/1-type because a slot may deliver a batch: with arrival
824 * matrices A_k and service pair (S0,S1),
825 * A^(-1) = kron(A_0,S1), A^(k) = kron(A_k,S0) + kron(A_{k+1},S1),
826 * B^(k) = kron(A_k,I), the boundary row holding the empty system where no
827 * service runs.
828 *
829 * Solved by the Ramaswami level recursion of `mg1_dt_pi`. The DEPARTURE process
830 * is a different object: it is read off the chain truncated at the level where
831 * the tail carries less than 1e-10, with arrivals that would cross the top held
832 * there, which is a level cut and not a rate change.
833 */
834template <class T>
836 std::size_t max_num_comp = 1000, bool want_departure = false) {
837 const T one = num_traits<T>::from_int(1);
838 std::size_t ms = svc.D0.rows(), ma = arv[0].rows(), K = arv.size() - 1;
839 std::size_t m = ma * ms;
840 Matrix<T> Ims = detail::dt_eye<T>(ms);
841
842 T lambda = dmap_lambda_batch(arv);
843 DBatch<T> svc_batch;
844 svc_batch.push_back(svc.D0);
845 svc_batch.push_back(svc.D1);
846 T mu = dmap_lambda_batch(svc_batch);
847 if (num_traits<T>::to_double(lambda / mu) >= 1)
848 throw InputError("mg1_dt_queue: the discrete-time load of the station is not below one");
849
850 // A^(-1) = kron(A_0,S1), A^(k) = kron(A_k,S0) + kron(A_{k+1},S1),
851 // B^(k) = kron(A_k,I), the boundary row where no service runs
852 std::vector<Matrix<T>> Ablocks, Bblocks;
853 Ablocks.push_back(detail::dt_kron(arv[0], svc.D1));
854 for (std::size_t k = 0; k <= K; ++k) {
855 Matrix<T> blk = detail::dt_kron(arv[k], svc.D0);
856 if (k + 1 <= K) blk = detail::dt_add(blk, detail::dt_kron(arv[k + 1], svc.D1));
857 Ablocks.push_back(blk);
858 }
859 for (std::size_t k = 0; k <= K; ++k) Bblocks.push_back(detail::dt_kron(arv[k], Ims));
860
861 std::vector<T> pi = mg1_dt_pi(Bblocks, Ablocks, max_num_comp);
862
864 std::size_t nlev = pi.size() / m;
865 out.ql.assign(nlev, num_traits<T>::from_int(0));
866 for (std::size_t i = 0; i < nlev; ++i)
867 for (std::size_t j = 0; j < m; ++j) out.ql[i] = out.ql[i] + pi[i * m + j];
868 T total = num_traits<T>::from_int(0);
869 for (std::size_t i = 0; i < nlev; ++i) total = total + out.ql[i];
870 for (std::size_t i = 0; i < nlev; ++i) out.ql[i] = out.ql[i] / total;
871
873 for (std::size_t i = 0; i < nlev; ++i)
874 out.QN = out.QN + num_traits<T>::from_int(static_cast<int>(i)) * out.ql[i];
875 out.UN = one - out.ql[0];
876 out.TN = lambda;
877
878 if (want_departure) {
879 double cum = 0;
880 std::size_t L = nlev - 1;
881 for (std::size_t i = 0; i < nlev; ++i) {
882 cum += num_traits<T>::to_double(out.ql[i]);
883 if (cum > 1 - 1e-10) {
884 L = i;
885 break;
886 }
887 }
888 if (L < 1) L = 1;
889 std::size_t nstates = (L + 1) * m;
890 // departure process of the same truncated chain: D1 collects the
891 // transitions carrying a completion, D0 the rest
892 Matrix<T> D0(nstates, nstates, num_traits<T>::from_int(0));
893 Matrix<T> D1(nstates, nstates, num_traits<T>::from_int(0));
894 for (std::size_t i = 0; i <= L; ++i)
895 for (std::size_t k = 0; k <= K; ++k) {
896 if (i == 0) {
897 std::size_t tgt = std::min(L, k);
898 Matrix<T> blk = detail::dt_kron(arv[k], Ims);
899 for (std::size_t r = 0; r < m; ++r)
900 for (std::size_t c = 0; c < m; ++c)
901 D0(i * m + r, tgt * m + c) = D0(i * m + r, tgt * m + c) + blk(r, c);
902 } else {
903 std::size_t tno = std::min(L, i + k);
904 Matrix<T> no = detail::dt_kron(arv[k], svc.D0);
905 for (std::size_t r = 0; r < m; ++r)
906 for (std::size_t c = 0; c < m; ++c)
907 D0(i * m + r, tno * m + c) = D0(i * m + r, tno * m + c) + no(r, c);
908 std::size_t tdep = std::min(L, i - 1 + k);
909 Matrix<T> dp = detail::dt_kron(arv[k], svc.D1);
910 for (std::size_t r = 0; r < m; ++r)
911 for (std::size_t c = 0; c < m; ++c)
912 D1(i * m + r, tdep * m + c) = D1(i * m + r, tdep * m + c) + dp(r, c);
913 }
914 }
915 out.dep.D0 = D0;
916 out.dep.D1 = D1;
917 }
918 return out;
919}
920
921} // namespace mam
922} // namespace line
923
924#endif // LINE_API_MAM_DTIME_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
Discrete-time Markovian arrival processes (D-MAPs).
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
ProcessType
Distribution kinds, with the values of MATLAB ProcessType.
Definition lang_types.h:483
DBatch< T > dmap_compress_batch(const DBatch< T > &A, std::size_t max_order)
Order reduction of a BATCH stream.
Definition dtime.h:480
Matrix< T > mg1_dt_g(const std::vector< Matrix< T > > &A, int max_iter=5000, double tol=1e-14)
G matrix of an M/G/1-type chain by functional iteration on G = sum_k A_k G^k, the blocks being stocha...
Definition dtime.h:565
Dmap< T > dmap_compress(const Dmap< T > &d, std::size_t max_order)
Reduces the order of a D-MAP by matching interevent moments.
Definition dtime.h:438
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< T > q_dt_map_map_1(const Dmap< T > &arv, const Dmap< T > &svc, std::size_t max_num_comp=1000)
Queue length distribution of a discrete-time D-MAP/D-MAP/1/FCFS queue, the queue-length half of Q_DT_...
Definition dtime.h:724
std::vector< Matrix< T > > DBatch
A discrete batch arrival stream, entry k carrying the slots with k events.
Definition dtime.h:64
T dmap_lambda_batch(const DBatch< T > &A)
Mean number of EVENTS per slot, pi sum_k k A_k e.
Definition dtime.h:277
std::vector< T > q_dt_ph_ph_1(const Dph< T > &arv, const Dph< T > &svc, std::size_t max_num_comp=1000)
Queue length of a discrete-time DPH/DPH/1/FCFS queue, via the D-MAP route.
Definition dtime.h:805
Matrix< T > qbd_dt_g(const Matrix< T > &A0, const Matrix< T > &A1, const Matrix< T > &A2, int max_iter=200)
G matrix of a discrete-time QBD by logarithmic reduction (Latouche and Ramaswami).
Definition dtime.h:522
DtQueueResult< T > mg1_dt_queue(const DBatch< T > &arv, const Dmap< T > &svc, std::size_t max_num_comp=1000, bool want_departure=false)
Discrete-time single-server queue with batch D-MAP arrivals, DBMAP/D-MAP/1.
Definition dtime.h:835
Dph< T > dph_from_dist(lang::ProcessType type, const T &mean_slots, const T &scv)
Exact discrete phase-type representation of a lattice-valued law.
Definition dtime.h:157
Dmap< T > dph_to_dmap(const Dph< T > &d)
Renewal D-MAP (A, a alpha) of a discrete phase-type law.
Definition dtime.h:216
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
Dph< T > dmap_to_dph(const Dmap< T > &d)
Discrete phase-type law underlying a renewal D-MAP.
Definition dtime.h:254
DBatch< T > dmap_super(const DBatch< T > &A, const DBatch< T > &B)
Superposition, E_k = sum_{i+j=k} kron(A_i, B_j).
Definition dtime.h:300
bool dmap_is_renewal(const Dmap< T > &d)
True when D1 has rank one, i.e.
Definition dtime.h:233
std::vector< T > mg1_dt_pi(const std::vector< Matrix< T > > &B, const std::vector< Matrix< T > > &A, std::size_t max_num_comp=1000)
Stationary vector of an M/G/1-type chain by the stable Ramaswami formula.
Definition dtime.h:599
DBatch< T > dmap_thin(const DBatch< T > &A, const T &p)
Bernoulli thinning, B_k = sum_{n>=k} C(n,k) p^k (1-p)^(n-k) A_n.
Definition dtime.h:320
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
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 discrete-time MAP: substochastic D0 (no arrival) and D1 (one arrival).
Definition dmap.h:48
Matrix< T > D0
Definition dmap.h:49
Matrix< T > D1
Definition dmap.h:50
A discrete phase-type law: initial row vector alpha and transient A.
Definition dtime.h:55
std::size_t order() const
Definition dtime.h:59
std::vector< T > alpha
Definition dtime.h:56
Matrix< T > A
Definition dtime.h:57
Outcome of a slotted station solve.
Definition dtime.h:812
std::vector< T > ql
Definition dtime.h:816