LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Toggle main menu visibility
Loading...
Searching...
No Matches
ctmc_testpf_kolmogorov.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_TESTPF_KOLMOGOROV_H
6
#define LINE_API_MC_CTMC_TESTPF_KOLMOGOROV_H
7
8
/**
9
* @file
10
* @ingroup api_mc
11
* Kolmogorov reversibility criterion, used as a product-form test.
12
*
13
* Templated port of jar/src/main/java/jline/api/mc/Ctmc_testpf_kolmogorov.java,
14
* which has no MATLAB twin. Kolmogorov's criterion states that an irreducible
15
* chain is reversible iff for every cycle c0 -> c1 -> ... -> c0 the product of
16
* the rates around it equals the product around the reverse cycle,
17
* prod_i q(c_i, c_i+1) = prod_i q(c_i+1, c_i),
18
* both products taken on the SAME generator. A cycle whose reverse edges are
19
* not all present fails outright.
20
*
21
* REFERENCE DEFECT (fixed here and in the JAR, 2026-08-01). The reference took
22
* the reverse product on the time-reversed generator Qr instead of on Q. Since
23
* Qr(a,b) = Q(b,a) pi_b / pi_a, that product is
24
* prod_i Q(c_i, c_i+1) pi_c_i / pi_c_i+1,
25
* whose pi factors telescope to 1 around any cycle, so it equals the forward
26
* product identically and the test returned true for every chain, reversible or
27
* not. The two formulations agree exactly when the chain IS reversible, which is
28
* why the defect never showed up as a wrong "false".
29
*
30
* COST. Every simple cycle through every edge is enumerated, so the work is
31
* exponential in the number of states. That is the reference algorithm and is
32
* kept: the callers apply it to small chains only.
33
*
34
* ARITHMETIC: the tolerance 1e-6 on the relative gap is a double comparison, so
35
* the verdict is a floating-point one even at Rational.
36
*/
37
38
#include <cmath>
39
#include <cstddef>
40
#include <vector>
41
42
#include "
line/api/mc/ctmc_solve.h
"
43
#include "
line/num/number.h
"
44
#include "
line/util/error.h
"
45
#include "
line/util/matrix.h
"
46
47
namespace
line
{
48
namespace
mc
{
49
50
namespace
detail {
51
52
/**
53
* Every simple path from `start` to `target` in the adjacency matrix `adj`,
54
* avoiding the states already marked used. A path is returned as the list of
55
* its states, `start` first and `target` last.
56
*/
57
template
<
class
T>
58
void
kolmogorov_paths(
const
Matrix<T>& adj, std::vector<bool>& used, std::vector<std::size_t>& path,
59
std::size_t start, std::size_t target,
60
std::vector<std::vector<std::size_t>>& out) {
61
const
T zero = num_traits<T>::from_int(0);
62
const
bool
wasUsed = used[start];
63
used[start] =
true
;
64
path.push_back(start);
65
if
(start == target) {
66
out.push_back(path);
67
}
else
{
68
for
(std::size_t j = 0; j < adj.cols(); ++j)
69
if
(adj(start, j) > zero && !used[j]) kolmogorov_paths(adj, used, path, j, target, out);
70
}
71
path.pop_back();
72
used[start] = wasUsed;
73
}
74
75
}
// namespace detail
76
77
/**
78
* @brief Kolmogorov reversibility criterion, used as a product-form test.
79
*
80
* @param Qin generator of an irreducible CTMC
81
* @return true when Kolmogorov's criterion holds on every simple cycle
82
*/
83
template
<
class
T>
84
bool
ctmc_testpf_kolmogorov
(
const
Matrix<T>
& Qin) {
85
const
std::size_t n = Qin.
rows
();
86
if
(Qin.
cols
() != n)
throw
InputError
(
"ctmc_testpf_kolmogorov: generator is not square"
);
87
const
T zero =
num_traits<T>::from_int
(0);
88
89
// Clip the negative off-diagonals, then reset the diagonal to close the rows.
90
Matrix<T>
Q = Qin;
91
for
(std::size_t i = 0; i < n; ++i)
92
for
(std::size_t j = 0; j < n; ++j)
93
if
(i != j && Q(i, j) < zero) Q(i, j) = zero;
94
Q =
ctmc_makeinfgen
(Q);
95
96
Matrix<T>
A(n, n, zero);
97
const
T one =
num_traits<T>::from_int
(1);
98
for
(std::size_t i = 0; i < n; ++i)
99
for
(std::size_t j = 0; j < n; ++j)
100
if
(i != j && Q(i, j) > zero) A(i, j) = one;
101
102
for
(std::size_t start = 0; start < n; ++start) {
103
for
(std::size_t target = 0; target < n; ++target) {
104
if
(target == start || !(A(target, start) > zero))
continue
;
105
std::vector<bool> used(n,
false
);
106
std::vector<std::size_t> path;
107
std::vector<std::vector<std::size_t>> cycles;
108
detail::kolmogorov_paths(A, used, path, start, target, cycles);
109
110
for
(std::size_t c = 0; c < cycles.size(); ++c) {
111
std::vector<std::size_t> cyc = cycles[c];
112
cyc.push_back(start);
// close the cycle back onto its origin
113
T q = one, qr = one;
114
for
(std::size_t i = 0; i + 1 < cyc.size(); ++i) {
115
q = T(q * Q(cyc[i], cyc[i + 1]));
116
qr = T(qr * Q(cyc[i + 1], cyc[i]));
117
}
118
const
double
qd =
num_traits<T>::to_double
(q);
119
const
double
qrd =
num_traits<T>::to_double
(qr);
120
if
(qd == 0.0)
continue
;
121
if
(std::fabs(qd - qrd) / std::fabs(qd) > 1e-6)
return
false
;
122
}
123
}
124
}
125
return
true
;
126
}
127
128
}
// namespace mc
129
}
// namespace line
130
131
#endif
// LINE_API_MC_CTMC_TESTPF_KOLMOGOROV_H
line::InputError::InputError
InputError(const std::string &what)
Definition
error.h:39
line::Matrix
Definition
matrix.h:56
line::Matrix::cols
std::size_t cols() const
Definition
matrix.h:90
line::Matrix::rows
std::size_t rows() const
Definition
matrix.h:89
ctmc_solve.h
Steady-state distribution of a continuous-time Markov chain.
error.h
The exception types the port throws.
matrix.h
Dense matrix and non-owning view.
line::mc
Definition
ctmc_bicgstab.h:59
line::mc::ctmc_makeinfgen
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
Definition
ctmc_solve.h:58
line::mc::ctmc_testpf_kolmogorov
bool ctmc_testpf_kolmogorov(const Matrix< T > &Qin)
Kolmogorov reversibility criterion, used as a product-form test.
Definition
ctmc_testpf_kolmogorov.h:84
line
Definition
aoi_dist2ph.h:52
number.h
Number-type abstraction for the templated API port.
line::num_traits
Definition
number.h:111
include
line
api
mc
ctmc_testpf_kolmogorov.h
Generated by
1.18.0