LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ctmc_gth.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_CTMC_GTH_H
6#define LINE_API_MC_CTMC_GTH_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Grassmann-Taksar-Heyman (GTH) elimination for the stationary vector, and the
12 * limit matrix J = lim expm(Q t) of any finite chain built on it.
13 *
14 * Templated port of matlab/src/api/mc/ctmc_gth.m, dtmc_gth.m, ctmc_cesaro.m,
15 * dtmc_cesaro.m and private/gth_irreducible.m; JAR twins jline.api.mc.Ctmc_gth
16 * and Ctmc_cesaro, Python twin line_solver.api.mc.gth.
17 *
18 * GTH censors the states from last to first, a_ij += a_ik a_kj / s_k with the
19 * pivot s_k = sum_{j<k} a_kj taken from the off-diagonal rates, never from the
20 * diagonal, and back-substitutes x_k = sum_{i<k} x_i a_ik / s_k. There is no
21 * subtraction, so every probability keeps small RELATIVE error, also on stiff
22 * and nearly completely decomposable chains. Only the off-diagonal entries are
23 * read, so dtmc_gth(P) = ctmc_gth(P) and periodic chains are fine. GTH uses only
24 * +, * and /, so the exact instantiation returns the exact rational vector.
25 * (Grassmann, Taksar, Heyman, Oper. Res. 33(5):1107-1116, 1985.)
26 *
27 * ctmc_cesaro gives every closed class C_k its GTH vector pi_k and every
28 * transient state i the row J(i, C_k) = H(i,k) pi_k, with H = (-Q_TT)^{-1}
29 * Q_{T,C} 1 the absorption probabilities. J is the matrix of maximum out-forests
30 * of the transition digraph (Agaev and Chebotarev, Autom. Remote Control
31 * 61(9):1424-1450, 2000): stochastic, idempotent, Q J = J Q = 0, and for a DTMC
32 * the Cesaro limit of P^k whatever the periodicity.
33 */
34
35#include <algorithm>
36#include <cmath>
37#include <cstddef>
38#include <string>
39#include <vector>
40
42#include "line/num/number.h"
43#include "line/util/error.h"
44#include "line/util/lu.h"
45#include "line/util/matrix.h"
46
47namespace line {
48namespace mc {
49
50template <class T>
51struct GthResult {
52 std::vector<T> p; ///< stationary vector (empty when flag == 1)
53 int flag; ///< 0 success, 1 several closed classes (not unique)
54};
55
56namespace detail {
57
58/** Off-diagonal rates of a square matrix, validated nonnegative (and finite). */
59template <class T>
60Matrix<T> gth_offdiag(const Matrix<T>& Q, const char* fn) {
61 const std::size_t n = Q.rows();
62 if (Q.cols() != n) throw InputError(std::string(fn) + ": the generator must be a square matrix");
63 const T zero = num_traits<T>::from_int(0);
64 Matrix<T> A(n, n, zero);
65 for (std::size_t i = 0; i < n; ++i)
66 for (std::size_t j = 0; j < n; ++j) {
67 if (i == j) continue;
68 const T v = Q(i, j);
69 bool bad = v < zero;
70 if constexpr (!num_traits<T>::is_exact) bad = bad || !std::isfinite(num_traits<T>::to_double(v));
71 if (bad) throw InputError(std::string(fn) + ": the off-diagonal rates must be finite and nonnegative");
72 A(i, j) = v;
73 }
74 return A;
75}
76
77/**
78 * GTH on the off-diagonal rates of an irreducible chain (the diagonal is never
79 * read). ok is false when a pivot is not positive (or not finite); A is a copy.
80 */
81template <class T>
82std::vector<T> gth_irreducible(Matrix<T> A, bool& ok) {
83 const std::size_t n = A.rows();
84 const T zero = num_traits<T>::from_int(0);
85 const T one = num_traits<T>::from_int(1);
86 ok = true;
87 if (n == 1) return std::vector<T>{one};
88 for (std::size_t i = 0; i < n; ++i) A(i, i) = zero;
89 for (std::size_t k = n - 1; k >= 1; --k) {
90 T s = zero;
91 for (std::size_t j = 0; j < k; ++j) s += A(k, j);
92 bool bad = !(s > zero);
93 if constexpr (!num_traits<T>::is_exact) bad = bad || !std::isfinite(num_traits<T>::to_double(s));
94 if (bad) {
95 ok = false;
96 return std::vector<T>();
97 }
98 for (std::size_t i = 0; i < k; ++i) A(i, k) /= s;
99 for (std::size_t i = 0; i < k; ++i) {
100 const T aik = A(i, k);
101 if (aik == zero) continue;
102 for (std::size_t j = 0; j < k; ++j) A(i, j) += aik * A(k, j);
103 }
104 }
105 std::vector<T> x(n, zero);
106 x[0] = one;
107 for (std::size_t k = 1; k < n; ++k) {
108 T v = zero;
109 for (std::size_t i = 0; i < k; ++i) v += x[i] * A(i, k);
110 x[k] = v;
111 }
112 T tot = zero;
113 for (const T& v : x) tot += v;
114 for (T& v : x) v /= tot;
115 return x;
116}
117
118template <class T>
119Matrix<T> gth_sub(const Matrix<T>& A, const std::vector<std::size_t>& r, const std::vector<std::size_t>& c) {
120 Matrix<T> B(r.size(), c.size());
121 for (std::size_t a = 0; a < r.size(); ++a)
122 for (std::size_t b = 0; b < c.size(); ++b) B(a, b) = A(r[a], c[b]);
123 return B;
124}
125
126} // namespace detail
127
128/**
129 * @brief Stationary vector of a CTMC by GTH elimination, with a flag.
130 *
131 * Unichain generators are accepted (transient states get zero mass). With
132 * several closed classes the vector is not unique: flag 1 and an empty p.
133 */
134template <class T>
136 const Matrix<T> A = detail::gth_offdiag(Q, "ctmc_gth");
137 const std::size_t n = A.rows();
138 const T zero = num_traits<T>::from_int(0);
139 if (n == 0) throw InputError("ctmc_gth: empty generator");
140 if (n == 1) return GthResult<T>{std::vector<T>{num_traits<T>::from_int(1)}, 0};
141 const SccResult s = stronglyconncomp(A);
142 std::size_t nclosed = 0, closed = 0;
143 for (std::size_t c = 0; c < s.recurrent.size(); ++c)
144 if (s.recurrent[c]) {
145 ++nclosed;
146 closed = c;
147 }
148 if (nclosed != 1) return GthResult<T>{std::vector<T>(), 1};
149 std::vector<std::size_t> S = s.members[closed];
150 std::sort(S.begin(), S.end());
151 bool ok = true;
152 const std::vector<T> x = detail::gth_irreducible(detail::gth_sub(A, S, S), ok);
153 if (!ok)
154 throw NumericError(ErrorKind{"Degenerate"},
155 "ctmc_gth: GTH elimination met a zero pivot on a closed class: the rates underflowed");
156 std::vector<T> p(n, zero);
157 for (std::size_t k = 0; k < S.size(); ++k) p[S[k]] = x[k];
158 return GthResult<T>{p, 0};
159}
160
161/** @brief Stationary vector by GTH; throws InputError on several closed classes. */
162template <class T>
163std::vector<T> ctmc_gth(const Matrix<T>& Q) {
165 if (r.flag != 0)
166 throw InputError("ctmc_gth: the chain has several closed classes, so its stationary distribution is "
167 "not unique. Use ctmc_cesaro(Q) for the limit from every initial state, or "
168 "ctmc_solve_reducible(Q, pi0).");
169 return r.p;
170}
171
172/** @brief Stationary vector of a DTMC by GTH; self-loops are not read. */
173template <class T>
174std::vector<T> dtmc_gth(const Matrix<T>& P) {
175 return ctmc_gth(P);
176}
177
178/** @brief Limit matrix J = lim expm(Q t) of any finite CTMC (Agaev-Chebotarev 2000). */
179template <class T>
181 const Matrix<T> A = detail::gth_offdiag(Q, "ctmc_cesaro");
182 const std::size_t n = A.rows();
183 const T zero = num_traits<T>::from_int(0);
184 if (n == 0) throw InputError("ctmc_cesaro: empty generator");
185 Matrix<T> J(n, n, zero);
186 if (n == 1) {
187 J(0, 0) = num_traits<T>::from_int(1);
188 return J;
189 }
190 const SccResult s = stronglyconncomp(A);
191 std::vector<std::size_t> closed, T_;
192 for (std::size_t c = 0; c < s.recurrent.size(); ++c)
193 if (s.recurrent[c]) closed.push_back(c);
194 for (std::size_t v = 0; v < n; ++v)
195 if (!s.recurrent[s.scc[v] - 1]) T_.push_back(v);
196 const std::size_t K = closed.size(), nT = T_.size();
197 std::vector<std::vector<T>> H(K, std::vector<T>(nT, zero)); // H[k][a]
198 if (nT > 0) {
199 Matrix<T> M(nT, nT, zero);
200 for (std::size_t a = 0; a < nT; ++a) {
201 T out = zero;
202 for (std::size_t j = 0; j < n; ++j) out += A(T_[a], j);
203 for (std::size_t b = 0; b < nT; ++b) M(a, b) = zero - A(T_[a], T_[b]);
204 M(a, a) += out;
205 }
206 for (std::size_t k = 0; k < K; ++k) {
207 std::vector<T> rhs(nT, zero);
208 for (std::size_t a = 0; a < nT; ++a)
209 for (std::size_t j : s.members[closed[k]]) rhs[a] += A(T_[a], j);
210 H[k] = solve(M, rhs);
211 }
212 for (std::size_t a = 0; a < nT; ++a) {
213 T tot = zero;
214 for (std::size_t k = 0; k < K; ++k) {
215 if (H[k][a] < zero) H[k][a] = zero;
216 tot += H[k][a];
217 }
218 for (std::size_t k = 0; k < K; ++k) H[k][a] /= tot;
219 }
220 }
221 for (std::size_t k = 0; k < K; ++k) {
222 std::vector<std::size_t> Ck = s.members[closed[k]];
223 std::sort(Ck.begin(), Ck.end());
224 bool ok = true;
225 const std::vector<T> pik = detail::gth_irreducible(detail::gth_sub(A, Ck, Ck), ok);
226 if (!ok)
227 throw NumericError(ErrorKind{"Degenerate"},
228 "ctmc_cesaro: GTH elimination met a zero pivot on a closed class: the rates underflowed");
229 for (std::size_t i : Ck)
230 for (std::size_t j = 0; j < Ck.size(); ++j) J(i, Ck[j]) = pik[j];
231 for (std::size_t a = 0; a < nT; ++a)
232 for (std::size_t j = 0; j < Ck.size(); ++j) J(T_[a], Ck[j]) = H[k][a] * pik[j];
233 }
234 return J;
235}
236
237/** @brief Cesaro limit of P^k for any finite DTMC; equals ctmc_cesaro(P - I). */
238template <class T>
240 return ctmc_cesaro(P);
241}
242
243} // namespace mc
244} // namespace line
245
246#endif // LINE_API_MC_CTMC_GTH_H
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:112
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
double tot(const std::vector< double > &pops)
Matrix< T > ctmc_cesaro(const Matrix< T > &Q)
Limit matrix J = lim expm(Q t) of any finite CTMC (Agaev-Chebotarev 2000).
Definition ctmc_gth.h:180
GthResult< T > ctmc_gth_flag(const Matrix< T > &Q)
Stationary vector of a CTMC by GTH elimination, with a flag.
Definition ctmc_gth.h:135
SccResult stronglyconncomp(const Matrix< T > &A)
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
std::vector< T > dtmc_gth(const Matrix< T > &P)
Stationary vector of a DTMC by GTH; self-loops are not read.
Definition ctmc_gth.h:174
Matrix< T > dtmc_cesaro(const Matrix< T > &P)
Cesaro limit of P^k for any finite DTMC; equals ctmc_cesaro(P - I).
Definition ctmc_gth.h:239
std::vector< T > ctmc_gth(const Matrix< T > &Q)
Stationary vector by GTH; throws InputError on several closed classes.
Definition ctmc_gth.h:163
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
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.
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
The <kind> part of a LINE:<category>:<kind> identifier (see _kb/17-error-vocabulary....
Definition error.h:37
int flag
0 success, 1 several closed classes (not unique)
Definition ctmc_gth.h:53
std::vector< T > p
stationary vector (empty when flag == 1)
Definition ctmc_gth.h:52
std::vector< bool > recurrent
recurrent[c-1] is true when component c has no edge leaving it.
std::vector< std::size_t > scc
Component index of each state, 1-based as in MATLAB (0 is never used).
std::vector< std::vector< std::size_t > > members
Member states of each component, ascending.