LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
dtmc_rand.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_MC_DTMC_RAND_H
6#define LINE_API_MC_DTMC_RAND_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Random DTMC kernels, trajectory simulation and the weak-component split.
12 *
13 * Templated port of matlab/lib/kpctoolbox/mc: dtmc_rand.m, dtmc_simulate.m and
14 * weaklyconncomp.m.
15 *
16 * dtmc_rand is defined as the uniformization of a random generator, so its
17 * kernel always has a nonzero diagonal and never mixes at rate one. Building
18 * the rows directly from normalised uniforms would look equivalent and is not:
19 * the self-loop probability of the uniformized chain is 1 + q_ii/q, which
20 * concentrates near one on the fast states.
21 *
22 * weaklyconncomp goes through dmperm in MATLAB and through a graph traversal
23 * here. Component LABELS are therefore not comparable across the two, only the
24 * partition is; the labels here are assigned in order of the smallest member,
25 * which is the one canonical choice that is stable under recompilation.
26 */
27
28#include <algorithm>
29#include <cstddef>
30#include <random>
31#include <vector>
32
36#include "line/num/number.h"
37#include "line/util/error.h"
38#include "line/util/matrix.h"
39
40namespace line {
41namespace mc {
42
43/** Random stochastic matrix, the uniformization of a random generator. */
44template <class T, class Gen>
45Matrix<T> dtmc_rand(std::size_t n, Gen& gen) {
46 return ctmc_randomization(ctmc_rand<T>(n, gen)).P;
47}
48
49/**
50 * Sample path of a DTMC, n states starting from pi0.
51 *
52 * An absorbing state ends the path early, exactly as the reference does: it
53 * returns the prefix rather than padding, so the returned length is at most n.
54 */
55template <class T, class Gen>
56std::vector<std::size_t> dtmc_simulate(const Matrix<T>& P, const std::vector<T>& pi0,
57 std::size_t n, Gen& gen) {
58 const std::size_t m = P.rows();
59 if (P.cols() != m) throw InputError("dtmc_simulate: P is not square");
60 if (pi0.size() != m) throw InputError("dtmc_simulate: pi0 does not match the state space");
61 const T zero = num_traits<T>::from_int(0);
62 std::uniform_real_distribution<double> unif(0.0, 1.0);
63
64 std::size_t st = m;
65 {
66 const T r = num_traits<T>::from_double(unif(gen));
67 T acc = zero;
68 for (std::size_t i = 0; i < m; ++i) {
69 acc += pi0[i];
70 if (pi0[i] > zero && r < acc) {
71 st = i;
72 break;
73 }
74 }
75 if (st == m)
76 for (std::size_t i = 0; i < m; ++i)
77 if (pi0[i] > zero) {
78 st = i;
79 break;
80 }
81 if (st == m) throw InputError("dtmc_simulate: pi0 puts no mass on any state");
82 }
83
84 std::vector<std::size_t> sts;
85 sts.reserve(n);
86 for (std::size_t k = 0; k < n; ++k) {
87 sts.push_back(st);
88 T rowsum = zero;
89 for (std::size_t j = 0; j < m; ++j) rowsum += P(st, j);
90 if (rowsum == zero || P(st, st) == num_traits<T>::from_int(1)) return sts;
91 const T r = num_traits<T>::from_double(unif(gen));
92 T acc = zero;
93 std::size_t nxt = m;
94 for (std::size_t j = 0; j < m; ++j) {
95 acc += P(st, j);
96 if (P(st, j) > zero && r < acc) {
97 nxt = j;
98 break;
99 }
100 }
101 if (nxt == m)
102 for (std::size_t j = 0; j < m; ++j)
103 if (P(st, j) > zero) {
104 nxt = j;
105 break;
106 }
107 st = nxt;
108 }
109 return sts;
110}
111
112/** Number of weakly connected components and the per-node label (weaklyconncomp.m). */
113template <class T>
115 std::size_t count; ///< S, the number of components
116 std::vector<std::size_t> comp; ///< C, zero-based component label per node
117};
118
119/** Weakly connected components of the graph whose adjacency is the support of G. */
120template <class T>
122 const std::vector<std::vector<std::size_t>> parts = detail::weak_components(G);
124 out.count = parts.size();
125 out.comp.assign(G.rows(), 0);
126 for (std::size_t c = 0; c < parts.size(); ++c)
127 for (std::size_t k = 0; k < parts[c].size(); ++k) out.comp[parts[c][k]] = c;
128 return out;
129}
130
131} // namespace mc
132} // namespace line
133
134#endif // LINE_API_MC_DTMC_RAND_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
Random infinitesimal generator of a CTMC.
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Dense matrix and non-owning view.
WeakCompResult< T > weaklyconncomp(const Matrix< T > &G)
Weakly connected components of the graph whose adjacency is the support of G.
Definition dtmc_rand.h:121
Matrix< T > ctmc_rand(std::size_t n, Gen &gen)
Random infinitesimal generator of a CTMC.
Definition ctmc_rand.h:71
Matrix< T > dtmc_rand(std::size_t n, Gen &gen)
Random stochastic matrix, the uniformization of a random generator.
Definition dtmc_rand.h:45
RandomizationResult< T > ctmc_randomization(const Matrix< T > &Q, const T &q)
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
std::vector< std::size_t > dtmc_simulate(const Matrix< T > &P, const std::vector< T > &pi0, std::size_t n, Gen &gen)
Sample path of a DTMC, n states starting from pi0.
Definition dtmc_rand.h:56
Number-type abstraction for the templated API port.
Number of weakly connected components and the per-node label (weaklyconncomp.m).
Definition dtmc_rand.h:114
std::size_t count
S, the number of components.
Definition dtmc_rand.h:115
std::vector< std::size_t > comp
C, zero-based component label per node.
Definition dtmc_rand.h:116