5#ifndef LINE_API_MC_CTMC_PASSAGE_H
6#define LINE_API_MC_CTMC_PASSAGE_H
68 std::vector<std::size_t>
keep;
72namespace passage_detail {
74inline std::vector<std::size_t> unique_target(
const std::vector<std::size_t>& target,
75 std::size_t n,
const char* fn) {
76 std::vector<std::size_t> t = target;
77 std::sort(t.begin(), t.end());
78 t.erase(std::unique(t.begin(), t.end()), t.end());
81 ": the target state set is empty: a first passage time into no state "
84 throw InputError(std::string(fn) +
": a target state index is outside the state space");
96inline std::vector<std::complex<double>> solve_cplx(
Matrix<std::complex<double>> A,
97 std::vector<std::complex<double>> b,
99 using C = std::complex<double>;
100 const std::size_t n = A.rows();
101 if (A.cols() != n || b.size() != n)
102 throw InputError(std::string(fn) +
": complex system is not square");
103 for (std::size_t k = 0; k < n; ++k) {
105 double best = std::abs(A(k, k));
106 for (std::size_t i = k + 1; i < n; ++i) {
107 const double m = std::abs(A(i, k));
114 throw NumericError(std::string(fn) +
": singular transform matrix");
116 for (std::size_t j = 0; j < n; ++j) std::swap(A(k, j), A(piv, j));
117 std::swap(b[k], b[piv]);
119 for (std::size_t i = k + 1; i < n; ++i) {
120 const C f = A(i, k) / A(k, k);
121 if (f == C(0.0, 0.0))
continue;
122 for (std::size_t j = k; j < n; ++j) A(i, j) -= f * A(k, j);
126 std::vector<C> x(n, C(0.0, 0.0));
127 for (std::size_t i = n; i-- > 0;) {
129 for (std::size_t j = i + 1; j < n; ++j) s -= A(i, j) * x[j];
137std::vector<bool> reaches_target(
const Matrix<T>& Q,
const std::vector<std::size_t>& keep,
138 const std::vector<std::size_t>& target) {
139 const std::size_t n = Q.rows();
140 std::vector<bool> seen(n,
false);
141 std::vector<std::size_t> frontier;
142 for (std::size_t k : target) {
144 frontier.push_back(k);
146 while (!frontier.empty()) {
147 std::vector<std::size_t> next;
148 for (std::size_t j : frontier)
149 for (std::size_t i = 0; i < n; ++i)
150 if (i != j && !seen[i] && Q(i, j) != T(0)) {
156 std::vector<bool> out(keep.size(),
false);
157 for (std::size_t a = 0; a < keep.size(); ++a) out[a] = seen[keep[a]];
171 const std::vector<std::size_t>& target) {
172 const std::size_t n = Q.
rows();
173 if (Q.
cols() != n)
throw InputError(
"ctmc_passage_ph: the generator must be square");
174 const std::vector<std::size_t> tgt =
175 passage_detail::unique_target(target, n,
"ctmc_passage_ph");
178 for (std::size_t i = 0; i < n; ++i)
179 for (std::size_t j = 0; j < n; ++j)
181 for (std::size_t i = 0; i < n; ++i) {
183 for (std::size_t j = 0; j < n; ++j) rs += Q(i, j);
186 "ctmc_passage_ph: Q is not an infinitesimal generator: its rows do not sum to "
187 "zero. Pass it through ctmc_makeinfgen first");
190 std::vector<bool> is_target(n,
false);
191 for (std::size_t k : tgt) is_target[k] =
true;
193 for (std::size_t i = 0; i < n; ++i)
194 if (!is_target[i]) out.
keep.push_back(i);
196 const std::size_t nA = out.
keep.size();
198 for (std::size_t a = 0; a < nA; ++a)
199 for (std::size_t c = 0; c < nA; ++c) out.
S(a, c) = Q(out.
keep[a], out.
keep[c]);
200 out.
s0.assign(nA, T(0));
201 for (std::size_t a = 0; a < nA; ++a) {
203 for (std::size_t c = 0; c < nA; ++c) r += out.
S(a, c);
207 out.
alpha.assign(nA, T(0));
215 for (std::size_t a = 0; a < nA; ++a) mass += p[out.
keep[a]];
218 "ctmc_passage_ph: the stationary law puts no mass outside the target set, so "
219 "there is no passage to time");
220 for (std::size_t a = 0; a < nA; ++a) out.
alpha[a] = p[out.
keep[a]] / mass;
225 "ctmc_passage_ph: pi0 must be a distribution over the state space, one entry per "
227 for (std::size_t a = 0; a < nA; ++a) out.
alpha[a] = pi0[out.
keep[a]];
228 for (std::size_t k : tgt) out.
atom += pi0[k];
244 const std::vector<std::size_t>& target,
245 const std::vector<std::complex<double>>& s) {
247 const std::size_t nA = ph.
S.rows();
248 using C = std::complex<double>;
249 std::vector<C> out(s.size(), C(0.0, 0.0));
250 for (std::size_t is = 0; is < s.size(); ++is) {
252 for (std::size_t i = 0; i < nA; ++i)
253 for (std::size_t j = 0; j < nA; ++j)
254 A(i, j) = (i == j ? s[is] : C(0.0, 0.0)) -
256 std::vector<C> b(nA);
258 const std::vector<C> x = passage_detail::solve_cplx(A, b,
"ctmc_passage_lst");
260 for (std::size_t i = 0; i < nA; ++i)
285 const std::vector<std::size_t>& target,
286 std::size_t nmax = 1) {
288 "ctmc_passage_moments marks unreachable states with an infinity and therefore "
289 "requires an arithmetic that has one");
290 if (nmax == 0)
throw InputError(
"ctmc_passage_moments: nmax must be positive");
292 const std::size_t n = Q.
rows();
293 const std::vector<std::size_t> tgt =
294 passage_detail::unique_target(target, n,
"ctmc_passage_moments");
295 const std::size_t nA = ph.
keep.size();
299 out.
m.assign(nmax, T(0));
300 if (nA == 0)
return out;
305 const std::vector<bool> reach = passage_detail::reaches_target(Q, ph.
keep, tgt);
306 bool all_reach =
true;
307 for (
bool r : reach) all_reach = all_reach && r;
310 for (std::size_t i = 0; i < nA; ++i)
311 for (std::size_t j = 0; j < nA; ++j) A(i, j) = -ph.
S(i, j);
314 std::vector<T> x(nA, T(1));
315 for (std::size_t k = 1; k <= nmax; ++k) {
316 std::vector<T> rhs(nA);
317 for (std::size_t i = 0; i < nA; ++i) rhs[i] = num_traits<T>::from_double(double(k)) * x[i];
324 std::vector<std::size_t> idx;
325 for (std::size_t i = 0; i < nA; ++i)
326 if (reach[i]) idx.push_back(i);
328 std::vector<T> br(idx.size());
329 for (std::size_t a = 0; a < idx.size(); ++a) {
330 for (std::size_t c = 0; c < idx.size(); ++c) Ar(a, c) = A(idx[a], idx[c]);
333 const std::vector<T> xr =
solve(Ar, br);
335 for (std::size_t a = 0; a < idx.size(); ++a) x[idx[a]] = xr[a];
337 for (std::size_t i = 0; i < nA; ++i)
338 out.
mall(ph.
keep[i], k - 1) = reach[i] ? x[i] : inf;
340 for (std::size_t i = 0; i < nA; ++i)
341 if (!reach[i]) x[i] = T(1);
344 bool unreachable_start =
false;
345 for (std::size_t i = 0; i < nA; ++i)
347 for (std::size_t k = 0; k < nmax; ++k) {
348 if (unreachable_start) {
353 for (std::size_t i = 0; i < nA; ++i)
369 const std::size_t n = Q.
rows();
375 for (std::size_t i = 0; i < n; ++i) h[i] = pm.
mall(i, 0);
382 std::vector<double>
t;
383 std::vector<double>
F;
384 std::vector<double>
f;
399 const std::vector<std::size_t>& target,
400 const std::vector<double>& tset,
401 const std::string& method =
"expm",
402 const std::string& lti_method =
"euler") {
404 "ctmc_passage_time takes a matrix exponential and therefore requires an "
405 "arithmetic with transcendental functions");
407 const std::size_t nA = ph.
S.rows();
410 out.
F.assign(tset.size(), 0.0);
411 out.
f.assign(tset.size(), 0.0);
414 if (method ==
"expm") {
415 bool uniform = tset.size() > 2;
416 const double dt = tset.size() > 1 ? tset[1] - tset[0] : 0.0;
417 if (uniform && !(dt > 0.0)) uniform =
false;
418 for (std::size_t i = 1; uniform && i + 1 < tset.size(); ++i)
419 if (std::abs((tset[i + 1] - tset[i]) - dt) > 1e-12 * std::max(1.0, std::abs(dt)))
422 std::vector<double> v(nA, 0.0);
424 for (std::size_t i = 0; i < nA; ++i)
426 std::vector<double> s0d(nA), ad(nA);
427 for (std::size_t i = 0; i < nA; ++i) {
432 auto step = [&](
const Matrix<double>& E, std::vector<double>& w) {
433 std::vector<double> z(nA, 0.0);
434 for (std::size_t j = 0; j < nA; ++j) {
436 for (std::size_t i = 0; i < nA; ++i) acc += w[i] * E(i, j);
446 for (std::size_t i = 0; i < nA; ++i)
447 for (std::size_t j = 0; j < nA; ++j) Sdt(i, j) = Sd(i, j) * dt;
450 for (std::size_t i = 0; i < nA; ++i)
451 for (std::size_t j = 0; j < nA; ++j) S0(i, j) = Sd(i, j) * tset[0];
454 for (std::size_t j = 0; j < nA; ++j) {
456 for (std::size_t i = 0; i < nA; ++i) acc += ad[i] * E0(i, j);
459 for (std::size_t i = 0; i < tset.size(); ++i) {
460 if (i > 0) step(E, v);
461 double sF = 0.0, sf = 0.0;
462 for (std::size_t j = 0; j < nA; ++j) {
470 for (std::size_t i = 0; i < tset.size(); ++i) {
471 if (tset[i] < 0.0)
continue;
473 for (std::size_t a = 0; a < nA; ++a)
474 for (std::size_t b = 0; b < nA; ++b) St(a, b) = Sd(a, b) * tset[i];
476 double sF = 0.0, sf = 0.0;
477 for (std::size_t j = 0; j < nA; ++j) {
479 for (std::size_t a = 0; a < nA; ++a) acc += ad[a] * E(a, j);
487 }
else if (method ==
"lt") {
488 const double atom = out.
atom;
490 std::vector<std::complex<double>> sv(1, s);
495 const lti::LaplaceFn Ld = [&](std::complex<double> s) {
return L(s) - atom; };
498 throw InputError(
"ctmc_passage_time: unknown method '" + method +
499 "', expected expm or lt");
502 for (std::size_t i = 0; i < out.
F.size(); ++i) {
503 out.
F[i] = std::min(1.0, std::max(0.0, out.
F[i]));
504 out.
f[i] = std::max(0.0, out.
f[i]);
527 const std::vector<T>& pi0,
528 const std::vector<std::size_t>& target,
529 std::size_t nmax = 1) {
530 const std::size_t n = P.
rows();
532 throw InputError(
"smp_passage_moments: the embedded transition matrix must be square");
533 if (nmax == 0)
throw InputError(
"smp_passage_moments: nmax must be positive");
534 for (std::size_t i = 0; i < n; ++i) {
536 for (std::size_t j = 0; j < n; ++j) rs += P(i, j);
539 "smp_passage_moments: the embedded transition matrix rows must sum to one");
541 if (hmom.
rows() != n)
542 throw InputError(
"smp_passage_moments: hmom must carry one row per state");
543 if (hmom.
cols() < nmax)
545 "smp_passage_moments: hmom must carry at least nmax holding-time moments per state");
546 const std::vector<std::size_t> tgt =
547 passage_detail::unique_target(target, n,
"smp_passage_moments");
549 std::vector<bool> is_target(n,
false);
550 for (std::size_t k : tgt) is_target[k] =
true;
551 std::vector<std::size_t> A;
552 for (std::size_t i = 0; i < n; ++i)
553 if (!is_target[i]) A.push_back(i);
554 const std::size_t nA = A.size();
558 out.
m.assign(nmax, T(0));
559 if (nA == 0)
return out;
563 for (std::size_t r = 1; r <= nmax; ++r) {
564 for (std::size_t a = 0; a < nA; ++a) {
566 for (std::size_t j = 1; j <= r; ++j) {
567 const T base = (r - j == 0) ? T(1) : u(a, r - j - 1);
569 for (std::size_t q = 0; q < j; ++q)
570 c = c *
double(r - q) / double(q + 1);
578 for (std::size_t a = 0; a < nA; ++a)
579 for (std::size_t c = 0; c < nA; ++c)
580 IPAA(a, c) = (a == c ? T(1) : T(0)) - P(A[a], A[c]);
583 for (std::size_t q = 1; q <= nmax; ++q) {
584 std::vector<T> b(nA, T(0));
585 for (std::size_t r = 1; r <= q; ++r) {
587 for (std::size_t k = 0; k < r; ++k) c = c *
double(q - k) / double(k + 1);
588 for (std::size_t a = 0; a < nA; ++a) {
589 const T base = (r < q) ? M(a, q - r - 1) : T(1);
593 const std::vector<T> x =
solve(IPAA, b);
594 for (std::size_t a = 0; a < nA; ++a) M(a, q - 1) = x[a];
597 for (std::size_t a = 0; a < nA; ++a)
598 for (std::size_t q = 0; q < nmax; ++q) out.
mall(A[a], q) = M(a, q);
600 for (std::size_t q = 0; q < nmax; ++q) {
602 for (std::size_t i = 0; i < n; ++i) acc += pi0[i] * out.
mall(i, q);
620 const Matrix<T>& P,
const std::vector<std::function<std::complex<double>(std::complex<double>)>>& hlst,
621 const std::vector<T>& pi0,
const std::vector<std::size_t>& target,
622 const std::vector<std::complex<double>>& s) {
623 const std::size_t n = P.
rows();
624 const std::vector<std::size_t> tgt = passage_detail::unique_target(target, n,
"smp_passage_lst");
625 if (hlst.size() != n)
626 throw InputError(
"smp_passage_lst: hlst must carry one transform per state");
627 std::vector<bool> is_target(n,
false);
628 for (std::size_t k : tgt) is_target[k] =
true;
629 std::vector<std::size_t> A;
630 for (std::size_t i = 0; i < n; ++i)
631 if (!is_target[i]) A.push_back(i);
632 const std::size_t nA = A.size();
634 using C = std::complex<double>;
639 std::vector<C> out(s.size(), C(0.0, 0.0));
640 for (std::size_t is = 0; is < s.size(); ++is) {
641 std::vector<C> h(nA);
642 for (std::size_t a = 0; a < nA; ++a) h[a] = hlst[A[a]](s[is]);
644 std::vector<C> b(nA, C(0.0, 0.0));
645 for (std::size_t a = 0; a < nA; ++a) {
646 for (std::size_t c = 0; c < nA; ++c)
647 M(a, c) = (a == c ? C(1.0, 0.0) : C(0.0, 0.0)) -
651 b[a] = h[a] * C(pb, 0.0);
653 const std::vector<C> x = passage_detail::solve_cplx(M, b,
"smp_passage_lst");
655 for (std::size_t a = 0; a < nA; ++a)
678 const Matrix<T>& P,
const std::vector<std::function<std::complex<double>(std::complex<double>)>>& hlst,
679 const std::vector<T>& pi0,
const std::vector<std::size_t>& target,
680 const std::vector<double>& tset,
const std::string& lti_method =
"euler") {
681 const std::size_t n = P.
rows();
682 const std::vector<std::size_t> tgt =
683 passage_detail::unique_target(target, n,
"smp_passage_time");
689 std::vector<std::complex<double>> sv(1, s);
697 const lti::LaplaceFn Ld = [&](std::complex<double> s) {
return L(s) - atom; };
NumericError(const std::string &what)
Steady-state distribution of a continuous-time Markov chain.
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
Numerical inversion of a Laplace transform: Euler, Talbot, Gaver-Stehfest.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
std::vector< double > laplace_invert_cdf(const LaplaceFn &F, const std::vector< double > &t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
The DISTRIBUTION on a grid, from the transform of the DENSITY.
std::vector< double > laplace_invert_pdf(const LaplaceFn &F, const std::vector< double > &t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
The DENSITY on a grid: the inversion clamped at zero.
LaplaceMethod
The methods laplace_invert accepts.
LaplaceMethod laplace_method(const std::string &s)
Parse the reference's method names, including its two Gaver spellings.
std::function< Cplx(Cplx)> LaplaceFn
The transform, evaluated at complex argument.
std::vector< std::complex< double > > ctmc_passage_lst(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< std::complex< double > > &s)
L(s) = alpha (sI-S)^{-1} s0 + atom at the (complex) points s.
PassageMoments< T > ctmc_passage_moments(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target, std::size_t nmax=1)
Moments of order 1..nmax of the first passage time into target.
std::vector< std::complex< double > > smp_passage_lst(const Matrix< T > &P, const std::vector< std::function< std::complex< double >(std::complex< double >)> > &hlst, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< std::complex< double > > &s)
L(s) of the semi-Markov first passage time, Eqs.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
PassageMoments< T > smp_passage_moments(const Matrix< T > &P, const Matrix< T > &hmom, const std::vector< T > &pi0, const std::vector< std::size_t > &target, std::size_t nmax=1)
Moments of the semi-Markov first passage time from the per-state holding moments m_i(r),...
PassageCurve< T > ctmc_passage_time(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< double > &tset, const std::string &method="expm", const std::string <i_method="euler")
CDF and density of the first passage time on the grid tset: F(t) = 1 - alpha exp(St) 1 and f(t) = alp...
PassageCurve< T > smp_passage_time(const Matrix< T > &P, const std::vector< std::function< std::complex< double >(std::complex< double >)> > &hlst, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< double > &tset, const std::string <i_method="euler")
CDF and density of the semi-Markov first passage time, by inverting smp_passage_lst through api/lti.
PassagePh< T > ctmc_passage_ph(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target)
Phase-type representation of the first passage time from pi0 into target.
std::vector< T > ctmc_hitting_time(const Matrix< T > &Q, const std::vector< std::size_t > &target)
Mean time to reach any state in target from each state of a CTMC.
std::vector< T > solve(const Matrix< T > &A, const std::vector< T > &b)
Convenience: solve Ax = b, leaving A and b untouched.
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Number-type abstraction for the templated API port.
A passage-time law on a grid.
Per-source and pi0-weighted passage moments.
Matrix< T > mall
(nstates x nmax), zero on the target, inf where unreachable
std::vector< T > m
the pi0-weighted moment vector
The phase-type form of a first passage time.
std::vector< T > alpha
pi0 restricted to the non-target block, UNNORMALIZED
std::vector< std::size_t > keep
row of S -> state index of Q
T atom
mass of pi0 already inside the target: F(0)
std::vector< T > s0
exit vector -S*1
Matrix< T > S
sub-generator Q(A,A)