LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ctmc_takahashi.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_TAKAHASHI_H
6#define LINE_API_MC_CTMC_TAKAHASHI_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Takahashi's aggregation-disaggregation for a nearly completely decomposable
12 * CTMC.
13 *
14 * Templated port of matlab/src/api/mc/ctmc_takahashi.m and
15 * jar/src/main/java/jline/api/mc/Ctmc_takahashi.java. Like KMS it starts from
16 * the Courtois approximation and alternates aggregation with disaggregation,
17 * but the disaggregation step is a per-macro-state fixed point rather than a
18 * Gauss-Seidel sweep: for macro-state I,
19 *
20 * (I - P_II') x_I = b_I, b_I(i) = sum_{K != I} gamma_K GI(K, i),
21 *
22 * with gamma the aggregated macro-state distribution and GI(K, .) the
23 * conditional one-step flow out of macro-state K.
24 *
25 * Everything here is in the ORIGINAL state ordering: P is the uniformization
26 * of the UNPERMUTED Q and each macro-state is addressed by the indices MS[I]
27 * carries. Contiguous block offsets would silently solve for a different
28 * partition of the same block sizes whenever a macro-state is not a contiguous
29 * range, which is the defect the MATLAB version records in its own comments.
30 *
31 * The uniformization rate is passed explicitly as (21/20) max|Q|, matching the
32 * rate ctmc_courtois derives. The reference used to leave it to the
33 * ctmc_randomization default, max|Q| + rand, which made the whole iteration
34 * irreproducible for no benefit: the aggregation and disaggregation equations
35 * are homogeneous in P - I = Q/q, so the fixed point does not depend on q.
36 *
37 * REFERENCE DEFECT, repaired here. MATLAB guards the aggregation against an
38 * empty macro-state (S > 1e-14) but then divides by that same S unguarded when
39 * building GI, so a macro-state carrying no mass poisons the whole iterate with
40 * NaN. Here a macro-state whose current mass is below the threshold contributes
41 * nothing to either G or GI, which is the limit of the expression as S -> 0.
42 *
43 * GATED ON TRANSCENDENTAL ARITHMETIC: seeded by ctmc_courtois, whose epsMAX is
44 * an eigenvalue modulus, and its large-system block solve is GMRES.
45 */
46
47#include <cstddef>
48#include <vector>
49
55#include "line/num/number.h"
56#include "line/util/error.h"
57#include "line/util/lu.h"
58#include "line/util/matrix.h"
59
60namespace line {
61namespace mc {
62
63template <class T>
65 std::vector<T> p; ///< estimate after numSteps sweeps
66 std::vector<T> p_1; ///< the previous iterate
67 std::vector<T> pcourt; ///< the Courtois starting point
68 Matrix<T> Qperm; ///< Q reordered by macro-state
69 T eps; ///< NCD index, as ctmc_courtois defines it
70 T epsMAX; ///< maximum admissible NCD index
71};
72
73/**
74 * @brief Takahashi's aggregation-disaggregation for a nearly completely
75 * decomposable CTMC.
76 *
77 * @param Q generator
78 * @param MS macro-states partitioning 0..n-1
79 * @param numSteps number of aggregation-disaggregation sweeps
80 * @param massTol macro-state mass below which its contribution is dropped
81 */
82template <class T>
83TakahashiResult<T> ctmc_takahashi(const Matrix<T>& Q, const std::vector<std::vector<std::size_t>>& MS,
84 std::size_t numSteps, double massTol = 1e-14) {
86 "ctmc_takahashi requires transcendental arithmetic: it is seeded by "
87 "ctmc_courtois, whose epsMAX is an eigenvalue modulus, and its large-system "
88 "block solve is GMRES, which stops on a residual tolerance");
89 const std::size_t n = Q.rows();
90 if (Q.cols() != n) throw InputError("ctmc_takahashi: generator is not square");
91 const T zero = num_traits<T>::from_int(0);
92 const T one = num_traits<T>::from_int(1);
93 const std::size_t nMacro = MS.size();
94 const T tol = num_traits<T>::from_double(massTol);
95
96 const detail::CourtoisCore<T> c = detail::courtois_core(Q, MS, detail::courtois_default_rate(Q, MS));
97 const std::vector<T> pMacro = dtmc_solve(c.G);
98
99 std::vector<std::size_t> off(nMacro + 1, 0);
100 for (std::size_t i = 0; i < nMacro; ++i) off[i + 1] = off[i] + MS[i].size();
101 std::vector<T> pperm(n, zero);
102 for (std::size_t i = 0; i < nMacro; ++i)
103 for (std::size_t a = off[i]; a < off[i + 1]; ++a) pperm[a] = pMacro[i] * c.pmicro[a];
104
106 r.pcourt = detail::unpermute_states(pperm, c.v);
107 r.Qperm = c.Qperm;
108 r.eps = c.eps;
109 r.epsMAX = c.epsMAX;
110
111 // Uniformization of the UNPERMUTED generator, at the rate Courtois derives.
112 const T qmax = ctmc_maxabs(Q);
113 if (qmax == zero) throw InputError("ctmc_takahashi: the generator has no transitions");
114 const Matrix<T> P = ctmc_randomization(Q, T(qmax * num_traits<T>::from_rational(21, 20))).P;
115
116 std::vector<T> pn = r.pcourt, pn_1 = r.pcourt;
117 for (std::size_t step = 0; step < numSteps; ++step) {
118 pn_1 = pn;
119
120 // Aggregation.
121 Matrix<T> G(nMacro, nMacro, zero);
122 std::vector<T> S(nMacro, zero);
123 for (std::size_t I = 0; I < nMacro; ++I)
124 for (std::size_t i : MS[I]) S[I] += pn_1[i];
125 for (std::size_t I = 0; I < nMacro; ++I) {
126 if (!(S[I] > tol)) continue;
127 for (std::size_t J = 0; J < nMacro; ++J) {
128 if (I == J) continue;
129 T acc = zero;
130 for (std::size_t i : MS[I])
131 for (std::size_t j : MS[J]) acc += P(i, j) * pn_1[i] / S[I];
132 G(I, J) = acc;
133 }
134 }
135 for (std::size_t I = 0; I < nMacro; ++I) {
136 T rs = zero;
137 for (std::size_t J = 0; J < nMacro; ++J)
138 if (J != I) rs += G(I, J);
139 G(I, I) = one - rs;
140 }
141 const std::vector<T> gamma = dtmc_solve(G);
142
143 // Conditional one-step flow out of each macro-state.
144 Matrix<T> GI(nMacro, n, zero);
145 for (std::size_t I = 0; I < nMacro; ++I) {
146 if (!(S[I] > tol)) continue;
147 for (std::size_t j = 0; j < n; ++j) {
148 T acc = zero;
149 for (std::size_t i : MS[I]) acc += P(i, j) * pn_1[i];
150 GI(I, j) = acc / S[I];
151 }
152 }
153
154 // Disaggregation, one macro-state at a time.
155 for (std::size_t I = 0; I < nMacro; ++I) {
156 const std::size_t sz = MS[I].size();
157 Matrix<T> A(sz, sz, zero);
158 std::vector<T> b(sz, zero);
159 for (std::size_t i = 0; i < sz; ++i) {
160 for (std::size_t j = 0; j < sz; ++j)
161 A(i, j) = (i == j ? one : zero) - P(MS[I][j], MS[I][i]);
162 for (std::size_t K = 0; K < nMacro; ++K)
163 if (K != I) b[i] += gamma[K] * GI(K, MS[I][i]);
164 }
165 std::vector<T> xI;
166 if (sz > GMRES_MIN_STATES) {
167 const GmresResult<T> g = ctmc_gmres(A, b);
168 if (g.flag == 0) {
169 xI = g.x;
170 } else {
171 // Short-recurrence retry before the cubic factorization, as in ctmc_solve.
172 const BicgstabResult<T> bs = ctmc_bicgstab(A, b);
173 if (bs.flag == 0) xI = bs.x;
174 }
175 }
176 if (xI.empty()) xI = solve(A, b);
177 for (std::size_t i = 0; i < sz; ++i) pn[MS[I][i]] = xI[i];
178 }
179
180 T tot = zero;
181 for (const T& x : pn) tot += x;
182 if (tot == zero) throw NumericError("ctmc_takahashi: the disaggregation step returned a null vector");
183 for (T& x : pn) x /= tot;
184 }
185
186 r.p = pn;
187 r.p_1 = pn_1;
188 return r;
189}
190
191} // namespace mc
192} // namespace line
193
194#endif // LINE_API_MC_CTMC_TAKAHASHI_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
NumericError(const std::string &what)
Definition error.h:45
Preconditioned stabilized biconjugate gradients, for the linear systems a generator produces.
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
BicgstabResult< T > ctmc_bicgstab(const Matrix< T > &A, const std::vector< T > &b, double tol=1e-12, long maxit=0, const std::vector< T > &x0=std::vector< T >())
Preconditioned stabilized biconjugate gradients, for the linear systems a generator produces.
constexpr std::size_t GMRES_MIN_STATES
Order above which the direct sparse factorization is abandoned in favour of the Krylov path.
Definition ctmc_gmres.h:75
T ctmc_maxabs(const Matrix< T > &Q)
Largest magnitude of any entry of Q; equals max_i |q_ii| for a generator.
TakahashiResult< T > ctmc_takahashi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps, double massTol=1e-14)
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
RandomizationResult< T > ctmc_randomization(const Matrix< T > &Q, const T &q)
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
GmresResult< T > ctmc_gmres(const Matrix< T > &A, const std::vector< T > &b, double tol=1e-12, long restart=0, long maxit=0, const std::vector< T > &x0=std::vector< T >())
Restarted GMRES with an ILUT preconditioner, for the linear systems a generator produces.
Definition ctmc_gmres.h:653
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.
std::vector< T > x
solution
int flag
0 converged, 1 iteration limit, 3 stagnation, 4 breakdown
int flag
0 converged, 1 iteration limit, 3 stagnation/divergence
Definition ctmc_gmres.h:80
std::vector< T > x
solution
Definition ctmc_gmres.h:79
T eps
NCD index, as ctmc_courtois defines it.
T epsMAX
maximum admissible NCD index
Matrix< T > Qperm
Q reordered by macro-state.
std::vector< T > p
estimate after numSteps sweeps
std::vector< T > pcourt
the Courtois starting point
std::vector< T > p_1
the previous iterate