5#ifndef LINE_API_MC_CTMC_COURTOIS_H
6#define LINE_API_MC_CTMC_COURTOIS_H
90std::vector<T> eig_moduli(
const Matrix<T>& Ain) {
91 const std::size_t n = Ain.rows();
92 if (Ain.cols() != n)
throw InputError(
"eig_moduli: matrix is not square");
93 const T zero = num_traits<T>::from_int(0);
94 if (n == 0)
return std::vector<T>();
95 if (n == 1)
return std::vector<T>(1,
num_abs(T(Ain(0, 0))));
97 Matrix<T> a(n + 1, n + 1, zero);
98 for (std::size_t i = 1; i <= n; ++i)
99 for (std::size_t j = 1; j <= n; ++j) a(i, j) = Ain(i - 1, j - 1);
102 for (std::size_t m = 2; m < n; ++m) {
105 for (std::size_t j = m; j <= n; ++j)
111 for (std::size_t j = m - 1; j <= n; ++j) std::swap(a(piv, j), a(m, j));
112 for (std::size_t j = 1; j <= n; ++j) std::swap(a(j, piv), a(j, m));
115 for (std::size_t i = m + 1; i <= n; ++i) {
117 if (y == zero)
continue;
120 for (std::size_t j = m; j <= n; ++j) a(i, j) -= y * a(m, j);
121 for (std::size_t j = 1; j <= n; ++j) a(j, m) += y * a(j, i);
127 for (std::size_t i = 3; i <= n; ++i)
128 for (std::size_t j = 1; j + 1 < i; ++j) a(i, j) = zero;
130 const T epsT = std::numeric_limits<T>::epsilon();
132 for (std::size_t i = 1; i <= n; ++i)
133 for (std::size_t j = (i > 1 ? i - 1 : 1); j <= n; ++j) anorm +=
num_abs(T(a(i, j)));
135 std::vector<T> wr(n + 1, zero), wi(n + 1, zero);
142 for (l = nn; l >= 2; --l) {
144 if (s == zero) s = anorm;
145 if (
num_abs(T(a(l, l - 1))) <= epsT * s) {
157 T y = a(nn - 1, nn - 1);
158 T w = a(nn, nn - 1) * a(nn - 1, nn);
160 const T p = (y - x) / num_traits<T>::from_int(2);
161 const T q = p * p + w;
166 z = p + (p < zero ? T(-z) : z);
168 wr[nn] = (z != zero) ? T(x - w / z) : T(x + z);
179 if (its == 60)
throw NumericError(
"eig_moduli: QR iteration did not converge");
180 if (its == 10 || its == 20 || its == 30 || its == 40 || its == 50) {
184 for (std::size_t i = 1; i <= nn; ++i) a(i, i) -= x;
185 const T s =
num_abs(T(a(nn, nn - 1))) +
num_abs(T(a(nn - 1, nn - 2)));
186 x = num_traits<T>::from_rational(3, 4) * s;
188 w = -num_traits<T>::from_rational(7, 16) * s * s;
191 std::size_t m = nn - 2;
192 T p = zero, q = zero, r = zero;
193 for (; m >= l; --m) {
197 p = (rr * ss - w) / a(m + 1, m) + a(m, m + 1);
198 q = a(m + 1, m + 1) - z - rr - ss;
208 if (u <= epsT * v)
break;
210 for (std::size_t i = m + 2; i <= nn; ++i) {
212 if (i != m + 2) a(i, i - 3) = zero;
214 for (std::size_t k = m; k <= nn - 1; ++k) {
219 if (k != nn - 1) r = a(k + 2, k - 1);
228 T s = sqrt(T(p * p + q * q + r * r));
229 if (p < zero) s = -s;
230 if (s == zero)
continue;
232 if (l != m) a(k, k - 1) = -a(k, k - 1);
234 a(k, k - 1) = -s * x;
242 for (std::size_t j = k; j <= nn; ++j) {
243 T pp = a(k, j) + q * a(k + 1, j);
245 pp += r * a(k + 2, j);
246 a(k + 2, j) -= pp * z;
248 a(k + 1, j) -= pp * y;
251 const std::size_t mmin = nn < k + 3 ? nn : k + 3;
252 for (std::size_t i = l; i <= mmin; ++i) {
253 T pp = x * a(i, k) + y * a(i, k + 1);
255 pp += z * a(i, k + 2);
256 a(i, k + 2) -= pp * r;
258 a(i, k + 1) -= pp * q;
264 }
while (nn >= 2 && l < nn - 1);
267 std::vector<T> mod(n);
269 for (std::size_t i = 1; i <= n; ++i) mod[i - 1] = sqrt(T(wr[i] * wr[i] + wi[i] * wi[i]));
270 std::sort(mod.begin(), mod.end());
275inline std::vector<std::size_t> macrostate_permutation(
const std::vector<std::vector<std::size_t>>& MS,
277 std::vector<std::size_t> v;
278 std::vector<char> seen(n, 0);
279 for (
const std::vector<std::size_t>& b : MS)
280 for (std::size_t k : b) {
281 if (k >= n)
throw InputError(
"ctmc_courtois: macro-state index out of range");
282 if (seen[k])
throw InputError(
"ctmc_courtois: state listed in more than one macro-state");
286 if (v.size() != n)
throw InputError(
"ctmc_courtois: the macro-states do not cover every state");
292void zero_offblock(Matrix<T>& A,
const std::vector<std::vector<std::size_t>>& MS) {
293 const std::size_t n = A.rows();
294 const T zero = num_traits<T>::from_int(0);
295 std::size_t proc = 0;
296 for (
const std::vector<std::size_t>& b : MS) {
297 const std::size_t sz = b.size();
298 for (std::size_t row = proc; row < proc + sz; ++row) {
299 for (std::size_t col = 0; col < proc; ++col) A(row, col) = zero;
300 for (std::size_t col = proc + sz; col < n; ++col) A(row, col) = zero;
314 std::vector<std::size_t> v;
315 Matrix<T> Qperm, Qdec, P, B;
316 std::vector<T> pmicro;
318 T eps, epsRowMax, epsColMax, epsMAX, q;
322CourtoisCore<T> courtois_core(
const Matrix<T>& Q,
const std::vector<std::vector<std::size_t>>& MS,
324 const std::size_t n = Q.rows();
325 if (Q.cols() != n)
throw InputError(
"ctmc_courtois: generator is not square");
326 if (MS.empty())
throw InputError(
"ctmc_courtois: no macro-states given");
327 const T zero = num_traits<T>::from_int(0);
328 const T one = num_traits<T>::from_int(1);
329 const std::size_t nMacro = MS.size();
333 r.v = macrostate_permutation(MS, n);
334 r.Qperm = submatrix(Q, r.v);
337 zero_offblock(r.Qdec, MS);
343 zero_offblock(A, MS);
345 for (std::size_t i = 0; i < n; ++i)
346 for (std::size_t j = 0; j < n; ++j) r.B(i, j) = r.P(i, j) - A(i, j);
349 for (std::size_t j = 0; j < n; ++j) {
351 for (std::size_t i = 0; i < n; ++i) cs += r.B(i, j);
352 if (j == 0 || cs > r.epsColMax) r.epsColMax = cs;
355 for (std::size_t i = 0; i < n; ++i) {
357 for (std::size_t j = 0; j < n; ++j) rs += r.B(i, j);
358 if (i == 0 || rs > r.eps) r.eps = rs;
366 std::size_t proc = 0;
367 for (
const std::vector<std::size_t>& b : MS) {
368 const std::size_t sz = b.size();
369 for (std::size_t i = 0; i < sz; ++i) {
371 for (std::size_t j = 0; j < sz; ++j)
372 if (j != i) off += As(proc + i, proc + j);
373 As(proc + i, proc + i) = one - off;
379 for (
const std::vector<std::size_t>& b : MS) {
380 const std::size_t sz = b.size();
382 Matrix<T> blk(sz, sz);
383 for (std::size_t i = 0; i < sz; ++i)
384 for (std::size_t j = 0; j < sz; ++j) blk(i, j) = As(proc + i, proc + j);
385 const std::vector<T> mod = eig_moduli(blk);
386 const T sub = mod[mod.size() - 2];
387 if (sub > maxSub) maxSub = sub;
391 r.epsMAX = (one - maxSub) / num_traits<T>::from_int(2);
395 r.pmicro.assign(n, zero);
397 std::size_t proc = 0;
398 for (
const std::vector<std::size_t>& b : MS) {
399 const std::size_t sz = b.size();
400 Matrix<T> blk(sz, sz);
401 for (std::size_t i = 0; i < sz; ++i)
402 for (std::size_t j = 0; j < sz; ++j) blk(i, j) = r.Qdec(proc + i, proc + j);
404 for (std::size_t i = 0; i < sz; ++i) r.pmicro[proc + i] = pb[i];
411 std::size_t procRows = 0;
412 for (std::size_t i = 0; i < nMacro; ++i) {
413 std::size_t procCols = 0;
414 for (std::size_t j = 0; j < nMacro; ++j) {
417 for (std::size_t a = 0; a < MS[i].size(); ++a) {
419 for (std::size_t b = 0; b < MS[j].size(); ++b) s += r.P(procRows + a, procCols + b);
420 acc += r.pmicro[procRows + a] * s;
424 procCols += MS[j].size();
426 procRows += MS[i].size();
428 for (std::size_t i = 0; i < nMacro; ++i) {
430 for (std::size_t j = 0; j < nMacro; ++j)
431 if (j != i) rs += r.G(i, j);
432 r.G(i, i) = one - rs;
439T courtois_default_rate(
const Matrix<T>& Q,
const std::vector<std::vector<std::size_t>>& MS) {
440 const std::vector<std::size_t> v = macrostate_permutation(MS, Q.rows());
442 if (m == num_traits<T>::from_int(0))
throw InputError(
"ctmc_courtois: the generator has no transitions");
443 return T(m * num_traits<T>::from_rational(21, 20));
448std::vector<T> unpermute_states(
const std::vector<T>& pperm,
const std::vector<std::size_t>& v) {
449 std::vector<T> p(pperm.size(), num_traits<T>::from_int(0));
450 for (std::size_t i = 0; i < v.size(); ++i) p[v[i]] = pperm[i];
459 std::vector<std::size_t>
v;
485 "ctmc_courtois requires transcendental arithmetic: epsMAX is a subdominant "
486 "eigenvalue modulus, computed by an iterative QR that stops on a tolerance and "
487 "has no rational closed form");
488 const detail::CourtoisCore<T> c = detail::courtois_core(Q, MS, q);
489 const std::vector<T> pMacro =
dtmc_solve(c.G);
492 std::size_t proc = 0;
493 for (std::size_t i = 0; i < MS.size(); ++i) {
494 for (std::size_t a = 0; a < MS[i].size(); ++a) pperm[proc + a] = pMacro[i] * c.pmicro[proc + a];
495 proc += MS[i].size();
499 r.
p = detail::unpermute_states(pperm, c.v);
517 return ctmc_courtois(Q, MS, detail::courtois_default_rate(Q, MS));
NumericError(const std::string &what)
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
Steady-state distribution of a continuous-time Markov chain.
Limiting distribution of a CTMC whose generator may be reducible.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
The exception types the port throws.
Dense matrix and non-owning view.
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
T ctmc_maxabs(const Matrix< T > &Q)
Largest magnitude of any entry of Q; equals max_i |q_ii| for a generator.
ReducibleResult< T > ctmc_solve_reducible(const Matrix< T > &Q, const std::vector< T > &pi0, double zeroColTol=1e-12)
Limiting distribution of a CTMC whose generator may be reducible.
std::vector< T > dtmc_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
RandomizationResult< T > ctmc_randomization(const Matrix< T > &Q, const T &q)
Uniformization (randomization) of a CTMC: the embedded DTMC P = I + Q/q.
CourtoisResult< T > ctmc_courtois(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const T &q)
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
Number-type abstraction for the templated API port.
Matrix< T > Qdec
block-diagonal generator of the decoupled chain
T C
the reference's degenerate output, identically zero
T q
uniformization rate used
Matrix< T > P
uniformized Qperm
std::vector< std::size_t > v
macro-state-major permutation used
Matrix< T > B
the coupling part of P, P minus its block diagonal
T eps
NCD index: largest ROW sum of B, ||B||_inf (MATLAB and the JAR).
T epsRowMax
the same quantity under the name it had when only the JAR computed it
std::vector< T > p
approximate stationary vector, ORIGINAL state ordering
T epsMAX
(1 - max subdominant block eigenvalue modulus) / 2
T epsColMax
largest COLUMN sum of B: what MATLAB reported before 2026-08-15
Matrix< T > Qperm
Q reordered by macro-state.