5#ifndef LINE_API_MC_CTMC_SSD_H
6#define LINE_API_MC_CTMC_SSD_H
50using SsdMat = Matrix<double>;
52inline void ssd_mult(
const SsdMat& C1,
const SsdMat& R1,
const SsdMat& C2,
const SsdMat& R2,
double tol,
53 SsdMat& Cc, SsdMat& Rc) {
54 const double inf = std::numeric_limits<double>::infinity();
55 const std::size_t a = C1.rows(), b = C1.cols(), c = C2.cols();
56 Cc = SsdMat(a, c, 0.0);
57 Rc = SsdMat(a, c, inf);
58 for (std::size_t t = 0; t < b; ++t)
59 for (std::size_t i = 0; i < a; ++i)
60 for (std::size_t j = 0; j < c; ++j) {
61 const double r = R1(i, t) + R2(t, j);
62 const double cc = C1(i, t) * C2(t, j);
63 if (!std::isfinite(r) || !(cc > 0.0))
continue;
64 if (r < Rc(i, j) - tol) {
67 }
else if (std::fabs(r - Rc(i, j)) <= tol) {
73inline void ssd_add_inplace(SsdMat& C1, SsdMat& R1,
const SsdMat& C2,
const SsdMat& R2,
double tol) {
74 for (std::size_t i = 0; i < C1.rows(); ++i)
75 for (std::size_t j = 0; j < C1.cols(); ++j) {
76 if (!std::isfinite(R2(i, j)))
continue;
77 if (R2(i, j) < R1(i, j) - tol) {
80 }
else if (std::fabs(R2(i, j) - R1(i, j)) <= tol) {
86inline std::vector<double> ssd_recurse(SsdMat C, SsdMat R,
double tol,
int depth) {
87 const double inf = std::numeric_limits<double>::infinity();
88 const std::size_t n = C.rows();
89 if (n == 1)
return std::vector<double>{1.0};
92 "ctmc_ssd: the SSD recursion did not terminate; please report the input matrices");
94 for (std::size_t i = 0; i < n; ++i)
95 for (std::size_t j = 0; j < n; ++j)
96 if (std::isfinite(R(i, j)) && R(i, j) < rmin) rmin = R(i, j);
98 for (std::size_t i = 0; i < n; ++i)
99 for (std::size_t j = 0; j < n; ++j)
100 if (std::isfinite(R(i, j))) {
102 if (R(i, j) <= tol) {
108 std::vector<std::size_t> closed;
109 for (std::size_t c = 0; c < s.recurrent.size(); ++c)
110 if (s.recurrent[c]) closed.push_back(c);
111 if (closed.size() == 1) {
112 std::vector<std::size_t> S = s.members[closed[0]];
113 std::sort(S.begin(), S.end());
114 SsdMat A(S.size(), S.size(), 0.0);
115 for (std::size_t a = 0; a < S.size(); ++a)
116 for (std::size_t b = 0; b < S.size(); ++b) A(a, b) = C(S[a], S[b]) * Z(S[a], S[b]);
118 const std::vector<double> x = gth_irreducible(A, ok);
119 std::vector<double> p(n, 0.0);
120 for (std::size_t k = 0; k < S.size(); ++k) p[S[k]] = x[k];
124 if (s.members[0].size() > 1) {
125 std::vector<std::size_t> S = s.members[0];
126 std::sort(S.begin(), S.end());
127 std::vector<bool> inS(n,
false);
128 for (std::size_t v : S) inS[v] =
true;
129 for (std::size_t i : S)
130 for (std::size_t j : S)
131 if (i != j && Z(i, j) == 0.0) {
135 const std::vector<std::size_t> sv(S.begin() + 1, S.end());
136 std::vector<std::size_t> keep;
137 for (std::size_t v = 0; v < n; ++v)
138 if (!inS[v] || v == S[0]) keep.push_back(v);
139 const std::size_t ns = sv.size(), m = keep.size();
140 SsdMat C0(n, n, 0.0);
141 for (std::size_t i = 0; i < n; ++i)
142 for (std::size_t j = 0; j < n; ++j) C0(i, j) = C(i, j) * Z(i, j);
143 SsdMat M(ns, ns, 0.0);
144 for (std::size_t a = 0; a < ns; ++a) {
146 for (std::size_t j = 0; j < n; ++j) out += C0(sv[a], j);
147 for (std::size_t b = 0; b < ns; ++b) M(a, b) = -C0(sv[a], sv[b]);
150 SsdMat N0(ns, ns, 0.0), RN(ns, ns, inf);
151 for (std::size_t b = 0; b < ns; ++b) {
152 std::vector<double> e(ns, 0.0);
154 const std::vector<double> col =
solve(M, e);
155 for (std::size_t a = 0; a < ns; ++a) {
156 N0(a, b) = col[a] > 0.0 ? col[a] : 0.0;
157 if (N0(a, b) > 0.0) RN(a, b) = 0.0;
160 auto sub = [](
const SsdMat& X,
const std::vector<std::size_t>& r,
const std::vector<std::size_t>& c) {
161 return gth_sub(X, r, c);
163 SsdMat Cks, Rks, Cv, Rv;
164 ssd_mult(sub(C, keep, sv), sub(R, keep, sv), N0, RN, tol, Cks, Rks);
165 ssd_mult(Cks, Rks, sub(C, sv, keep), sub(R, sv, keep), tol, Cv, Rv);
166 SsdMat Cr = sub(C, keep, keep), Rr = sub(R, keep, keep);
167 ssd_add_inplace(Cr, Rr, Cv, Rv, tol);
168 for (std::size_t i = 0; i < m; ++i) {
172 const std::vector<double> x = ssd_recurse(Cr, Rr, tol, depth - 1);
173 std::vector<double> p(n, 0.0);
174 for (std::size_t k = 0; k < m; ++k) p[keep[k]] = x[k];
175 std::vector<double> y(ns, 0.0);
176 for (std::size_t a = 0; a < ns; ++a)
177 for (std::size_t k = 0; k < m; ++k) y[a] += x[k] * C0(keep[k], sv[a]);
178 for (std::size_t b = 0; b < ns; ++b) {
180 for (std::size_t a = 0; a < ns; ++a) v += y[a] * N0(a, b);
184 for (
double v : p)
tot += v;
185 for (
double& v : p) v /=
tot;
189 std::vector<bool> isClosed(n,
false);
190 for (std::size_t v = 0; v < n; ++v) isClosed[v] = s.recurrent[s.scc[v] - 1];
192 for (std::size_t i = 0; i < n; ++i)
194 for (std::size_t j = 0; j < n; ++j)
195 if (std::isfinite(R(i, j)) && R(i, j) < smin) smin = R(i, j);
196 for (std::size_t i = 0; i < n; ++i)
198 for (std::size_t j = 0; j < n; ++j)
199 if (std::isfinite(R(i, j))) R(i, j) -= smin;
200 std::vector<double> p = ssd_recurse(C, R, tol, depth - 1);
202 for (std::size_t v = 0; v < n; ++v) {
203 if (!isClosed[v]) p[v] = 0.0;
208 "ctmc_ssd: internal error: the scaled chain put no mass on the closed states");
209 for (
double& v : p) v /=
tot;
217 const double inf = std::numeric_limits<double>::infinity();
218 const std::size_t n = Cin.
rows();
219 if (Cin.
cols() != n || Rin.
rows() != n || Rin.
cols() != n)
220 throw InputError(
"ctmc_ssd: C and R must be square matrices of the same size");
221 if (n == 0)
throw InputError(
"ctmc_ssd: empty input");
224 for (std::size_t i = 0; i < n; ++i)
225 for (std::size_t j = 0; j < n; ++j) {
231 const double c = C(i, j), r = R(i, j);
232 if (std::isnan(c) || std::isnan(r) || c < 0.0 || std::isinf(c))
233 throw InputError(
"ctmc_ssd: costs must be finite and nonnegative, and neither C nor R may contain NaN");
234 if (r == -inf)
throw InputError(
"ctmc_ssd: a resistance of -Inf is not a valid perturbation order");
235 if (c == 0.0 || std::isinf(r)) {
240 rmax = std::max(rmax, std::fabs(r));
243 if (n == 1)
return std::vector<double>{1.0};
245 std::size_t nclosed = 0;
246 for (
bool b : s.
recurrent) nclosed += b ? 1 : 0;
248 throw InputError(
"ctmc_ssd: the perturbed chain has " + std::to_string(nclosed) +
249 " closed classes for eps > 0, so it has no unique stationary distribution and no "
250 "stochastically stable distribution");
251 return detail::ssd_recurse(C, R, 1e-10 * rmax,
static_cast<int>(4 * n + 8));
NumericError(const std::string &what)
Grassmann-Taksar-Heyman (GTH) elimination for the stationary vector, and the limit matrix J = lim exp...
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)
std::vector< double > dtmc_ssd(const Matrix< double > &C, const Matrix< double > &R)
SSD of a perturbed transition matrix P_eps(i,j) ~ C(i,j) eps^R(i,j); equals ctmc_ssd.
std::vector< double > ctmc_ssd(const Matrix< double > &Cin, const Matrix< double > &Rin)
Stochastically stable distribution, rates C(i,j) eps^R(i,j) (Wicks 2009).
SccResult stronglyconncomp(const Matrix< T > &A)
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
Conservation laws of a layered queueing network, enumerated from its structure.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Strongly connected components of a directed graph, and which of them are recurrent (closed under the ...
std::vector< bool > recurrent
recurrent[c-1] is true when component c has no edge leaving it.