5#ifndef LINE_API_MC_CTMC_PASSAGE_H
6#define LINE_API_MC_CTMC_PASSAGE_H
70 std::vector<std::size_t>
keep;
81namespace passage_detail {
83inline std::vector<std::size_t> unique_target(
const std::vector<std::size_t>& target,
84 std::size_t n,
const char* fn) {
85 std::vector<std::size_t> t = target;
86 std::sort(t.begin(), t.end());
87 t.erase(std::unique(t.begin(), t.end()), t.end());
90 ": the target state set is empty: a first passage time into no state "
93 throw InputError(std::string(fn) +
": a target state index is outside the state space");
105inline std::vector<std::complex<double>> solve_cplx(
Matrix<std::complex<double>> A,
106 std::vector<std::complex<double>> b,
108 using C = std::complex<double>;
109 const std::size_t n = A.rows();
110 if (A.cols() != n || b.size() != n)
111 throw InputError(std::string(fn) +
": complex system is not square");
112 for (std::size_t k = 0; k < n; ++k) {
114 double best = std::abs(A(k, k));
115 for (std::size_t i = k + 1; i < n; ++i) {
116 const double m = std::abs(A(i, k));
123 throw NumericError(ErrorKind{
"Singular"}, std::string(fn) +
": singular transform matrix");
125 for (std::size_t j = 0; j < n; ++j) std::swap(A(k, j), A(piv, j));
126 std::swap(b[k], b[piv]);
128 for (std::size_t i = k + 1; i < n; ++i) {
129 const C f = A(i, k) / A(k, k);
130 if (f == C(0.0, 0.0))
continue;
131 for (std::size_t j = k; j < n; ++j) A(i, j) -= f * A(k, j);
135 std::vector<C> x(n, C(0.0, 0.0));
136 for (std::size_t i = n; i-- > 0;) {
138 for (std::size_t j = i + 1; j < n; ++j) s -= A(i, j) * x[j];
149void smp_weighted_moments(PassageMoments<T>& out,
const std::vector<T>& pi0, std::size_t n,
150 std::size_t nmax,
const char* fn) {
152 if (pi0.empty())
return;
155 ": PI0 must be a distribution over the state space, one entry per state");
156 out.m.assign(nmax, T(0));
157 for (std::size_t q = 0; q < nmax; ++q) {
159 for (std::size_t i = 0; i < n; ++i) acc += pi0[i] * out.mall(i, q);
166std::vector<bool> reaches_target(
const Matrix<T>& Q,
const std::vector<std::size_t>& keep,
167 const std::vector<std::size_t>& target) {
168 const std::size_t n = Q.rows();
169 std::vector<bool> seen(n,
false);
170 std::vector<std::size_t> frontier;
171 for (std::size_t k : target) {
173 frontier.push_back(k);
175 while (!frontier.empty()) {
176 std::vector<std::size_t> next;
177 for (std::size_t j : frontier)
178 for (std::size_t i = 0; i < n; ++i)
179 if (i != j && !seen[i] && Q(i, j) != T(0)) {
185 std::vector<bool> out(keep.size(),
false);
186 for (std::size_t a = 0; a < keep.size(); ++a) out[a] = seen[keep[a]];
200 const std::vector<std::size_t>& target) {
201 const std::size_t n = Q.
rows();
202 if (Q.
cols() != n)
throw InputError(
"ctmc_passage_ph: the generator must be square");
203 const std::vector<std::size_t> tgt =
204 passage_detail::unique_target(target, n,
"ctmc_passage_ph");
207 for (std::size_t i = 0; i < n; ++i)
208 for (std::size_t j = 0; j < n; ++j)
210 for (std::size_t i = 0; i < n; ++i) {
212 for (std::size_t j = 0; j < n; ++j) rs += Q(i, j);
215 "ctmc_passage_ph: Q is not an infinitesimal generator: its rows do not sum to "
216 "zero. Pass it through ctmc_makeinfgen first");
219 std::vector<bool> is_target(n,
false);
220 for (std::size_t k : tgt) is_target[k] =
true;
222 for (std::size_t i = 0; i < n; ++i)
223 if (!is_target[i]) out.
keep.push_back(i);
225 const std::size_t nA = out.
keep.size();
227 for (std::size_t a = 0; a < nA; ++a)
228 for (std::size_t c = 0; c < nA; ++c) out.
S(a, c) = Q(out.
keep[a], out.
keep[c]);
229 out.
s0.assign(nA, T(0));
230 for (std::size_t a = 0; a < nA; ++a) {
232 for (std::size_t c = 0; c < nA; ++c) r += out.
S(a, c);
236 out.
alpha.assign(nA, T(0));
244 for (std::size_t a = 0; a < nA; ++a) mass += p[out.
keep[a]];
247 "ctmc_passage_ph: the stationary law puts no mass outside the target set, so "
248 "there is no passage to time");
249 for (std::size_t a = 0; a < nA; ++a) out.
alpha[a] = p[out.
keep[a]] / mass;
254 "ctmc_passage_ph: pi0 must be a distribution over the state space, one entry per "
256 for (std::size_t a = 0; a < nA; ++a) out.
alpha[a] = pi0[out.
keep[a]];
257 for (std::size_t k : tgt) out.
atom += pi0[k];
273 const std::vector<std::size_t>& target,
274 const std::vector<std::complex<double>>& s) {
276 const std::size_t nA = ph.
S.rows();
277 using C = std::complex<double>;
278 std::vector<C> out(s.size(), C(0.0, 0.0));
279 for (std::size_t is = 0; is < s.size(); ++is) {
281 for (std::size_t i = 0; i < nA; ++i)
282 for (std::size_t j = 0; j < nA; ++j)
283 A(i, j) = (i == j ? s[is] : C(0.0, 0.0)) -
285 std::vector<C> b(nA);
287 const std::vector<C> x = passage_detail::solve_cplx(A, b,
"ctmc_passage_lst");
289 for (std::size_t i = 0; i < nA; ++i)
307 const std::vector<std::size_t>& target,
308 std::size_t nmax = 1) {
310 "ctmc_passage_moments marks unreachable states with an infinity and therefore "
311 "requires an arithmetic that has one");
312 if (nmax == 0)
throw InputError(
"ctmc_passage_moments: nmax must be positive");
314 const std::size_t n = Q.
rows();
315 const std::vector<std::size_t> tgt =
316 passage_detail::unique_target(target, n,
"ctmc_passage_moments");
317 const std::size_t nA = ph.
keep.size();
321 out.
m.assign(nmax, T(0));
322 if (nA == 0)
return out;
327 const std::vector<bool> reach = passage_detail::reaches_target(Q, ph.
keep, tgt);
328 bool all_reach =
true;
329 for (
bool r : reach) all_reach = all_reach && r;
332 for (std::size_t i = 0; i < nA; ++i)
333 for (std::size_t j = 0; j < nA; ++j) A(i, j) = -ph.
S(i, j);
336 std::vector<T> x(nA, T(1));
337 for (std::size_t k = 1; k <= nmax; ++k) {
338 std::vector<T> rhs(nA);
339 for (std::size_t i = 0; i < nA; ++i) rhs[i] = num_traits<T>::from_double(double(k)) * x[i];
346 std::vector<std::size_t> idx;
347 for (std::size_t i = 0; i < nA; ++i)
348 if (reach[i]) idx.push_back(i);
350 std::vector<T> br(idx.size());
351 for (std::size_t a = 0; a < idx.size(); ++a) {
352 for (std::size_t c = 0; c < idx.size(); ++c) Ar(a, c) = A(idx[a], idx[c]);
355 const std::vector<T> xr =
solve(Ar, br);
357 for (std::size_t a = 0; a < idx.size(); ++a) x[idx[a]] = xr[a];
359 for (std::size_t i = 0; i < nA; ++i)
360 out.
mall(ph.
keep[i], k - 1) = reach[i] ? x[i] : inf;
362 for (std::size_t i = 0; i < nA; ++i)
363 if (!reach[i]) x[i] = T(1);
366 bool unreachable_start =
false;
367 for (std::size_t i = 0; i < nA; ++i)
369 for (std::size_t k = 0; k < nmax; ++k) {
370 if (unreachable_start) {
375 for (std::size_t i = 0; i < nA; ++i)
391 const std::size_t n = Q.
rows();
397 for (std::size_t i = 0; i < n; ++i) h[i] = pm.
mall(i, 0);
404 std::vector<double>
t;
405 std::vector<double>
F;
406 std::vector<double>
f;
421 const std::vector<std::size_t>& target,
422 const std::vector<double>& tset,
423 const std::string& method =
"expm",
424 const std::string& lti_method =
"euler") {
426 "ctmc_passage_time takes a matrix exponential and therefore requires an "
427 "arithmetic with transcendental functions");
429 const std::size_t nA = ph.
S.rows();
432 out.
F.assign(tset.size(), 0.0);
433 out.
f.assign(tset.size(), 0.0);
436 if (method ==
"expm") {
437 bool uniform = tset.size() > 2;
438 const double dt = tset.size() > 1 ? tset[1] - tset[0] : 0.0;
439 if (uniform && !(dt > 0.0)) uniform =
false;
440 for (std::size_t i = 1; uniform && i + 1 < tset.size(); ++i)
441 if (std::abs((tset[i + 1] - tset[i]) - dt) > 1e-12 * std::max(1.0, std::abs(dt)))
444 std::vector<double> v(nA, 0.0);
446 for (std::size_t i = 0; i < nA; ++i)
448 std::vector<double> s0d(nA), ad(nA);
449 for (std::size_t i = 0; i < nA; ++i) {
454 auto step = [&](
const Matrix<double>& E, std::vector<double>& w) {
455 std::vector<double> z(nA, 0.0);
456 for (std::size_t j = 0; j < nA; ++j) {
458 for (std::size_t i = 0; i < nA; ++i) acc += w[i] * E(i, j);
468 for (std::size_t i = 0; i < nA; ++i)
469 for (std::size_t j = 0; j < nA; ++j) Sdt(i, j) = Sd(i, j) * dt;
472 for (std::size_t i = 0; i < nA; ++i)
473 for (std::size_t j = 0; j < nA; ++j) S0(i, j) = Sd(i, j) * tset[0];
476 for (std::size_t j = 0; j < nA; ++j) {
478 for (std::size_t i = 0; i < nA; ++i) acc += ad[i] * E0(i, j);
481 for (std::size_t i = 0; i < tset.size(); ++i) {
482 if (i > 0) step(E, v);
483 double sF = 0.0, sf = 0.0;
484 for (std::size_t j = 0; j < nA; ++j) {
492 for (std::size_t i = 0; i < tset.size(); ++i) {
493 if (tset[i] < 0.0)
continue;
495 for (std::size_t a = 0; a < nA; ++a)
496 for (std::size_t b = 0; b < nA; ++b) St(a, b) = Sd(a, b) * tset[i];
498 double sF = 0.0, sf = 0.0;
499 for (std::size_t j = 0; j < nA; ++j) {
501 for (std::size_t a = 0; a < nA; ++a) acc += ad[a] * E(a, j);
509 }
else if (method ==
"lt") {
510 const double atom = out.
atom;
512 std::vector<std::complex<double>> sv(1, s);
517 const lti::LaplaceFn Ld = [&](std::complex<double> s) {
return L(s) - atom; };
520 throw InputError(
"ctmc_passage_time: unknown method '" + method +
521 "', expected expm or lt");
524 for (std::size_t i = 0; i < out.
F.size(); ++i) {
525 out.
F[i] = std::min(1.0, std::max(0.0, out.
F[i]));
526 out.
f[i] = std::max(0.0, out.
f[i]);
547 std::vector<double>
t;
548 std::vector<double>
F;
549 std::vector<double>
f;
573namespace passage_detail {
580std::vector<bool> backward_closure(
const Matrix<T>& Q,
const std::vector<bool>& from,
581 const std::vector<bool>& stop) {
582 const std::size_t n = Q.
rows();
583 std::vector<bool> seen = from;
584 std::vector<std::size_t> frontier;
585 for (std::size_t i = 0; i < n; ++i)
586 if (from[i]) frontier.push_back(i);
587 while (!frontier.empty()) {
588 std::vector<std::size_t> next;
589 for (std::size_t j : frontier)
590 for (std::size_t i = 0; i < n; ++i)
591 if (i != j && !seen[i] && !stop[i] && Q(i, j) != T(0)) {
602PassageTaboo<T> taboo_partition(
const Matrix<T>& Q,
const std::vector<T>& pi0,
603 const std::vector<std::size_t>& target,
604 const std::vector<std::size_t>& taboo,
const char* fn) {
605 const std::size_t n = Q.
rows();
606 if (Q.
cols() != n)
throw InputError(std::string(fn) +
": the generator must be square");
608 out.target = unique_target(target, n, fn);
609 std::vector<std::size_t> tb = taboo;
610 std::sort(tb.begin(), tb.end());
611 tb.erase(std::unique(tb.begin(), tb.end()), tb.end());
612 if (!tb.empty() && tb.back() >= n)
613 throw InputError(std::string(fn) +
": a target or taboo state index is outside the state space");
616 for (std::size_t i = 0; i < n; ++i)
617 for (std::size_t j = 0; j < n; ++j)
619 for (std::size_t i = 0; i < n; ++i) {
621 for (std::size_t j = 0; j < n; ++j) rs += Q(i, j);
624 ": Q is not an infinitesimal generator: its rows do not sum to zero. "
625 "Pass it through ctmc_makeinfgen first");
628 std::vector<bool> isB(n,
false), isC(n,
false);
629 for (std::size_t k : out.target) isB[k] =
true;
630 for (std::size_t k : tb)
633 out.taboo.push_back(k);
641 for (std::size_t i = 0; i < n; ++i) {
642 if (isB[i]) p0[i] = T(0);
645 if (!(num_traits<T>::to_double(mass) > 0.0))
647 ": the stationary law puts no mass outside the target set, so there "
648 "is no passage to time");
649 for (std::size_t i = 0; i < n; ++i) p0[i] = T(p0[i] / mass);
653 ": PI0 must be a distribution over the state space, one entry per "
658 std::vector<bool> isBC(n,
false);
659 for (std::size_t i = 0; i < n; ++i) isBC[i] = isB[i] || isC[i];
660 const std::vector<bool> toB = backward_closure(Q, isB, isBC);
661 const std::vector<bool> toBC = backward_closure(Q, isBC, isBC);
663 std::vector<std::size_t> absorb, Nset;
664 for (std::size_t i = 0; i < n; ++i)
665 if (isB[i]) absorb.push_back(i);
666 for (std::size_t i = 0; i < n; ++i)
667 if (isC[i]) absorb.push_back(i);
668 for (std::size_t i = 0; i < n; ++i) {
669 if (!toBC[i]) Nset.push_back(i);
670 else if (!isBC[i]) out.keep.push_back(i);
672 absorb.insert(absorb.end(), Nset.begin(), Nset.end());
673 const std::size_t nB = out.target.size(), nC = out.taboo.size(), nK = out.keep.size();
675 out.pB.assign(n, T(0));
676 out.pC.assign(n, T(0));
677 for (std::size_t i = 0; i < n; ++i) {
678 if (isB[i]) out.pB[i] = T(1);
679 if (isC[i]) out.pC[i] = T(1);
687 std::vector<bool> isK(n,
false);
688 for (std::size_t k : out.keep) isK[k] =
true;
689 for (std::size_t i = 0; i < n; ++i)
691 for (std::size_t j = 0; j < n; ++j) Qabs(i, j) = T(0);
694 for (std::size_t a = 0; a < nK; ++a) {
695 T b = T(0), c = T(0);
696 for (std::size_t j = 0; j < nB; ++j) b += sc.X(a, j);
697 for (std::size_t j = 0; j < nC; ++j) c += sc.X(a, nB + j);
698 if (toB[out.keep[a]]) out.pB[out.keep[a]] = b;
699 out.pC[out.keep[a]] = c;
703 out.alpha.assign(nK, T(0));
704 out.sB.assign(nK, T(0));
705 out.sC.assign(nK, T(0));
706 for (std::size_t a = 0; a < nK; ++a) {
707 const std::size_t i = out.keep[a];
708 out.alpha[a] = p0[i];
709 for (std::size_t j : out.target) out.sB[a] += Q(i, j);
710 for (std::size_t j : out.taboo) out.sC[a] += Q(i, j);
713 for (std::size_t j : out.target) out.atom += p0[j];
714 out.reachprob = T(0);
715 out.tabooprob = T(0);
716 for (std::size_t i = 0; i < n; ++i) {
717 out.reachprob += T(p0[i] * out.pB[i]);
718 out.tabooprob += T(p0[i] * out.pC[i]);
752 const std::vector<std::size_t>& target,
753 const std::vector<std::size_t>& taboo,
754 const std::vector<double>& tset = std::vector<double>(),
755 const std::string& method =
"expm",
756 const std::string& lti_method =
"euler") {
758 "ctmc_passage_taboo takes a matrix exponential and therefore requires an "
759 "arithmetic with transcendental functions");
761 passage_detail::taboo_partition(Q, pi0, target, taboo,
"ctmc_passage_taboo");
764 out.
F.assign(tset.size(), 0.0);
765 out.
f.assign(tset.size(), 0.0);
766 if (tset.empty())
return out;
768 const double inf = std::numeric_limits<double>::infinity();
770 std::vector<std::size_t> pos;
771 for (std::size_t i = 0; i < tset.size(); ++i) {
773 else if (std::isfinite(tset[i]) && tset[i] >= 0.0) pos.push_back(i);
775 std::vector<double> tf(pos.size());
776 for (std::size_t i = 0; i < pos.size(); ++i) tf[i] = tset[pos[i]];
777 std::vector<double> Ff(tf.size(), atom), ff(tf.size(), 0.0);
779 const std::size_t nK = out.
keep.size();
780 if (nK > 0 && !tf.empty()) {
781 std::vector<double> ad(nK), sBd(nK);
782 for (std::size_t a = 0; a < nK; ++a) {
786 if (method ==
"expm") {
789 const std::size_t m = nK + 1;
791 for (std::size_t a = 0; a < nK; ++a) {
795 auto scaled = [&](
double s) {
797 for (std::size_t a = 0; a < m; ++a)
798 for (std::size_t c = 0; c < m; ++c) Gs(a, c) = G(a, c) * s;
801 auto times = [&](
const std::vector<double>& w,
const Matrix<double>& E) {
802 std::vector<double> z(m, 0.0);
803 for (std::size_t c = 0; c < m; ++c) {
805 for (std::size_t a = 0; a < m; ++a) acc += w[a] * E(a, c);
810 auto record = [&](std::size_t i,
const std::vector<double>& v) {
811 Ff[i] = atom + v[nK];
813 for (std::size_t a = 0; a < nK; ++a) acc += v[a] * sBd[a];
816 std::vector<double> v0(m, 0.0);
817 for (std::size_t a = 0; a < nK; ++a) v0[a] = ad[a];
819 bool uniform = tf.size() > 2;
820 const double dt = tf.size() > 1 ? tf[1] - tf[0] : 0.0;
821 if (uniform && !(dt > 0.0)) uniform =
false;
822 for (std::size_t i = 1; uniform && i + 1 < tf.size(); ++i)
823 if (!(std::abs((tf[i + 1] - tf[i]) - dt) < 1e-12 * std::max(1.0, std::abs(dt))))
827 std::vector<double> v = times(v0, scaled(tf[0]));
828 for (std::size_t i = 0; i < tf.size(); ++i) {
829 if (i > 0) v = times(v, E);
833 for (std::size_t i = 0; i < tf.size(); ++i) record(i, times(v0, scaled(tf[i])));
835 }
else if (method ==
"lt") {
836 using C = std::complex<double>;
840 for (std::size_t a = 0; a < nK; ++a)
841 for (std::size_t c = 0; c < nK; ++c)
842 A(a, c) = (a == c ? s : C(0.0, 0.0)) -
844 std::vector<C> b(nK);
845 for (std::size_t a = 0; a < nK; ++a) b[a] = C(sBd[a], 0.0);
846 const std::vector<C> x = passage_detail::solve_cplx(A, b,
"ctmc_passage_taboo");
848 for (std::size_t a = 0; a < nK; ++a) acc += C(ad[a], 0.0) * x[a];
856 throw InputError(
"ctmc_passage_taboo: unknown passage-time method '" + method +
857 "', expected expm or lt");
860 for (std::size_t i = 0; i < pos.size(); ++i) {
861 out.
F[pos[i]] = Ff[i];
862 out.
f[pos[i]] = ff[i];
864 for (std::size_t i = 0; i < out.
F.size(); ++i) {
865 out.
F[i] = std::min(1.0, std::max(0.0, out.
F[i]));
866 out.
f[i] = std::max(0.0, out.
f[i]);
887 const std::vector<std::size_t>& target,
888 const std::vector<std::size_t>& taboo,
889 std::size_t nmax = 1) {
891 "ctmc_passage_taboo_moments marks undefined conditional moments with a NaN "
892 "and therefore requires an arithmetic that has one");
893 if (nmax == 0)
throw InputError(
"ctmc_passage_taboo_moments: nmax must be positive");
895 passage_detail::taboo_partition(Q, pi0, target, taboo,
"ctmc_passage_taboo_moments");
896 const std::size_t n = Q.
rows(), nK = pt.
keep.size();
902 for (std::size_t i : pt.
target)
903 for (std::size_t k = 0; k < nmax; ++k) out.
mall(i, k) = T(0);
908 for (std::size_t a = 0; a < nK; ++a)
909 for (std::size_t c = 0; c < nK; ++c) LU(a, c) = T(-pt.
S(a, c));
910 const std::vector<std::size_t> piv =
lu_factor(LU);
911 std::vector<T> y(nK);
912 for (std::size_t a = 0; a < nK; ++a) y[a] = pt.
pB[pt.
keep[a]];
913 for (std::size_t k = 1; k <= nmax; ++k) {
915 for (std::size_t a = 0; a < nK; ++a) y[a] = T(kk * y[a]);
917 for (std::size_t a = 0; a < nK; ++a) Y(a, k - 1) = y[a];
919 for (std::size_t a = 0; a < nK; ++a) {
920 const T p = pt.
pB[pt.
keep[a]];
922 for (std::size_t k = 0; k < nmax; ++k) out.
mall(pt.
keep[a], k) = T(Y(a, k) / p);
927 for (std::size_t a = 0; a < nK; ++a) PB += T(pt.
alpha[a] * pt.
pB[pt.
keep[a]]);
928 out.
m.assign(nmax, nan);
930 for (std::size_t k = 0; k < nmax; ++k) {
932 for (std::size_t a = 0; a < nK; ++a) acc += T(pt.
alpha[a] * Y(a, k));
933 out.
m[k] = T(acc / PB);
956 const std::vector<T>& pi0,
957 const std::vector<std::size_t>& target,
958 std::size_t nmax = 1) {
959 const std::size_t n = P.rows();
961 throw InputError(
"smp_passage_moments: the embedded transition matrix must be square");
962 if (nmax == 0)
throw InputError(
"smp_passage_moments: nmax must be positive");
963 for (std::size_t i = 0; i < n; ++i) {
965 for (std::size_t j = 0; j < n; ++j) rs += P(i, j);
968 "smp_passage_moments: the embedded transition matrix rows must sum to one");
970 if (hmom.
rows() != n)
971 throw InputError(
"smp_passage_moments: hmom must carry one row per state");
972 if (hmom.
cols() < nmax)
974 "smp_passage_moments: hmom must carry at least nmax holding-time moments per state");
975 const std::vector<std::size_t> tgt =
976 passage_detail::unique_target(target, n,
"smp_passage_moments");
978 std::vector<bool> is_target(n,
false);
979 for (std::size_t k : tgt) is_target[k] =
true;
980 std::vector<std::size_t> A;
981 for (std::size_t i = 0; i < n; ++i)
982 if (!is_target[i]) A.push_back(i);
983 const std::size_t nA = A.size();
989 out.
m.assign(nmax, T(0));
995 for (std::size_t r = 1; r <= nmax; ++r) {
996 for (std::size_t a = 0; a < nA; ++a) {
998 for (std::size_t j = 1; j <= r; ++j) {
999 const T base = (r - j == 0) ? T(1) : u(a, r - j - 1);
1001 for (std::size_t q = 0; q < j; ++q)
1002 c = c *
double(r - q) / double(q + 1);
1010 for (std::size_t a = 0; a < nA; ++a)
1011 for (std::size_t c = 0; c < nA; ++c)
1012 IPAA(a, c) = (a == c ? T(1) : T(0)) - P(A[a], A[c]);
1015 for (std::size_t q = 1; q <= nmax; ++q) {
1016 std::vector<T> b(nA, T(0));
1017 for (std::size_t r = 1; r <= q; ++r) {
1019 for (std::size_t k = 0; k < r; ++k) c = c *
double(q - k) / double(k + 1);
1020 for (std::size_t a = 0; a < nA; ++a) {
1021 const T base = (r < q) ? M(a, q - r - 1) : T(1);
1025 const std::vector<T> x =
solve(IPAA, b);
1026 for (std::size_t a = 0; a < nA; ++a) M(a, q - 1) = x[a];
1029 for (std::size_t a = 0; a < nA; ++a)
1030 for (std::size_t q = 0; q < nmax; ++q) out.
mall(A[a], q) = M(a, q);
1031 passage_detail::smp_weighted_moments(out, pi0, n, nmax,
"smp_passage_moments");
1054 const std::vector<std::vector<std::vector<T>>>& hmom,
1055 const std::vector<T>& pi0,
1056 const std::vector<std::size_t>& target,
1057 std::size_t nmax = 1) {
1058 const std::size_t n = P.rows();
1060 throw InputError(
"smp_passage_moments: the embedded transition matrix must be square");
1061 if (nmax == 0)
throw InputError(
"smp_passage_moments: nmax must be positive");
1062 for (std::size_t i = 0; i < n; ++i) {
1064 for (std::size_t j = 0; j < n; ++j) rs += P(i, j);
1067 "smp_passage_moments: the embedded transition matrix rows must sum to one");
1069 if (hmom.size() != n)
1071 "smp_passage_moments: a cell HMOM must be (nstates x nstates), entry (i,k) holding "
1072 "the moments of the sojourn in i when the next state is k");
1073 for (std::size_t i = 0; i < n; ++i)
1074 if (hmom[i].size() != n)
1076 "smp_passage_moments: a cell HMOM must be (nstates x nstates), entry (i,k) "
1077 "holding the moments of the sojourn in i when the next state is k");
1078 const std::vector<std::size_t> tgt =
1079 passage_detail::unique_target(target, n,
"smp_passage_moments");
1081 std::vector<bool> is_target(n,
false);
1082 for (std::size_t k : tgt) is_target[k] =
true;
1083 std::vector<std::size_t> A;
1084 for (std::size_t i = 0; i < n; ++i)
1085 if (!is_target[i]) A.push_back(i);
1086 const std::size_t nA = A.size();
1092 out.
m.assign(nmax, T(0));
1097 const auto mik = [&](std::size_t i, std::size_t k, std::size_t r) {
1098 const std::vector<T>& v = hmom[i][k];
1099 return r - 1 < v.size() ? v[r - 1] : T(0);
1103 for (std::size_t a = 0; a < nA; ++a)
1104 for (std::size_t c = 0; c < nA; ++c)
1105 IPAA(a, c) = (a == c ? T(1) : T(0)) - P(A[a], A[c]);
1108 for (std::size_t q = 1; q <= nmax; ++q) {
1109 std::vector<T> b(nA, T(0));
1110 for (std::size_t a = 0; a < nA; ++a) {
1111 const std::size_t i = A[a];
1113 for (std::size_t c = 0; c < nA; ++c) {
1114 const std::size_t k = A[c];
1115 for (std::size_t r = 1; r <= q; ++r) {
1117 for (std::size_t t = 0; t < r; ++t) bin = bin *
double(q - t) / double(t + 1);
1118 const T base = (r < q) ? M(c, q - r - 1) : T(1);
1122 for (std::size_t k : tgt) acc += mik(i, k, q);
1125 const std::vector<T> x =
solve(IPAA, b);
1126 for (std::size_t a = 0; a < nA; ++a) M(a, q - 1) = x[a];
1129 for (std::size_t a = 0; a < nA; ++a)
1130 for (std::size_t q = 0; q < nmax; ++q) out.
mall(A[a], q) = M(a, q);
1131 passage_detail::smp_weighted_moments(out, pi0, n, nmax,
"smp_passage_moments");
1147 const Matrix<T>& P,
const std::vector<std::function<std::complex<double>(std::complex<double>)>>& hlst,
1148 const std::vector<T>& pi0,
const std::vector<std::size_t>& target,
1149 const std::vector<std::complex<double>>& s) {
1150 const std::size_t n = P.rows();
1151 const std::vector<std::size_t> tgt = passage_detail::unique_target(target, n,
"smp_passage_lst");
1152 if (hlst.size() != n)
1153 throw InputError(
"smp_passage_lst: hlst must carry one transform per state");
1154 std::vector<bool> is_target(n,
false);
1155 for (std::size_t k : tgt) is_target[k] =
true;
1156 std::vector<std::size_t> A;
1157 for (std::size_t i = 0; i < n; ++i)
1158 if (!is_target[i]) A.push_back(i);
1159 const std::size_t nA = A.size();
1161 using C = std::complex<double>;
1163 if (pi0.size() == n)
1166 std::vector<C> out(s.size(), C(0.0, 0.0));
1167 for (std::size_t is = 0; is < s.size(); ++is) {
1168 std::vector<C> h(nA);
1169 for (std::size_t a = 0; a < nA; ++a) h[a] = hlst[A[a]](s[is]);
1171 std::vector<C> b(nA, C(0.0, 0.0));
1172 for (std::size_t a = 0; a < nA; ++a) {
1173 for (std::size_t c = 0; c < nA; ++c)
1174 M(a, c) = (a == c ? C(1.0, 0.0) : C(0.0, 0.0)) -
1178 b[a] = h[a] * C(pb, 0.0);
1180 const std::vector<C> x = passage_detail::solve_cplx(M, b,
"smp_passage_lst");
1182 for (std::size_t a = 0; a < nA; ++a)
1206 const std::vector<std::vector<std::function<std::complex<double>(std::complex<double>)>>>& hlst,
1207 const std::vector<T>& pi0,
const std::vector<std::size_t>& target,
1208 const std::vector<std::complex<double>>& s) {
1209 const std::size_t n = P.rows();
1210 const std::vector<std::size_t> tgt = passage_detail::unique_target(target, n,
"smp_passage_lst");
1211 if (hlst.size() != n)
1212 throw InputError(
"smp_passage_lst: a kernel hlst must be (nstates x nstates)");
1213 for (std::size_t i = 0; i < n; ++i)
1214 if (hlst[i].size() != n)
1215 throw InputError(
"smp_passage_lst: a kernel hlst must be (nstates x nstates)");
1216 if (!pi0.empty() && pi0.size() != n)
1218 "smp_passage_lst: PI0 must be a distribution over the state space, one entry per "
1220 std::vector<bool> is_target(n,
false);
1221 for (std::size_t k : tgt) is_target[k] =
true;
1222 std::vector<std::size_t> A;
1223 for (std::size_t i = 0; i < n; ++i)
1224 if (!is_target[i]) A.push_back(i);
1225 const std::size_t nA = A.size();
1227 using C = std::complex<double>;
1229 std::vector<double> p0(n, 1.0 /
double(n));
1230 if (pi0.size() == n)
1231 for (std::size_t i = 0; i < n; ++i) p0[i] = num_traits<T>::to_double(pi0[i]);
1233 for (std::size_t k : tgt) atom += p0[k];
1235 std::vector<C> out(s.size(), C(0.0, 0.0));
1236 for (std::size_t is = 0; is < s.size(); ++is) {
1238 std::vector<C> b(nA, C(0.0, 0.0));
1239 for (std::size_t a = 0; a < nA; ++a) {
1240 for (std::size_t c = 0; c < nA; ++c) {
1241 const C r = hlst[A[a]][A[c]] ? hlst[A[a]][A[c]](s[is]) : C(0.0, 0.0);
1242 M(a, c) = (a == c ? C(1.0, 0.0) : C(0.0, 0.0)) - r;
1244 for (std::size_t k : tgt)
1245 if (hlst[A[a]][k]) b[a] += hlst[A[a]][k](s[is]);
1247 const std::vector<C> x = passage_detail::solve_cplx(M, b,
"smp_passage_lst");
1249 for (std::size_t a = 0; a < nA; ++a) acc += C(p0[A[a]], 0.0) * x[a];
1270 const Matrix<T>& P,
const std::vector<std::function<std::complex<double>(std::complex<double>)>>& hlst,
1271 const std::vector<T>& pi0,
const std::vector<std::size_t>& target,
1272 const std::vector<double>& tset,
const std::string& lti_method =
"euler") {
1273 const std::size_t n = P.rows();
1274 const std::vector<std::size_t> tgt =
1275 passage_detail::unique_target(target, n,
"smp_passage_time");
1277 if (pi0.size() == n)
1281 std::vector<std::complex<double>> sv(1, s);
1289 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.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
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.
StochCompResult< T > ctmc_stochcomp(const Matrix< T > &Q, const std::vector< std::size_t > &I)
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.
PassageTabooMoments< T > ctmc_passage_taboo_moments(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< std::size_t > &taboo, std::size_t nmax=1)
Moments of order 1..nmax of the first passage time into target, CONDITIONAL on entering it before tab...
PassageTaboo< T > ctmc_passage_taboo(const Matrix< T > &Q, const std::vector< T > &pi0, const std::vector< std::size_t > &target, const std::vector< std::size_t > &taboo, const std::vector< double > &tset=std::vector< double >(), const std::string &method="expm", const std::string <i_method="euler")
First passage into target under the taboo set taboo, the reference's ctmc_passage_taboo.
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.
Conservation laws of a layered queueing network, enumerated from its structure.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
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.
Moments of a first passage time, [Mall, m] of the reference.
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)
Conditional moments under a taboo set, [mall, m, pB] of ctmc_passage_taboo_moments....
std::vector< T > m
the pi0-weighted conditional moments, NaN if B is never entered first
Matrix< T > mall
(nstates x nmax): zero on B, NaN where B cannot be entered first
std::vector< T > pB
(nstates) probability of entering B before C
First passage into a target set B under a TABOO set C, [F, f, out] of ctmc_passage_taboo....
std::vector< std::size_t > taboo
C minus B, sorted and unique.
std::vector< T > sC
Q(K,C)*1, the exit rates into the taboo set.
T tabooprob
Pr{C entered before B}.
std::vector< T > pB
(nstates) reachprob per starting state, one on B
std::vector< T > sB
Q(K,B)*1, the exit rates into the target.
std::vector< T > pC
(nstates) tabooprob per starting state, one on C
std::vector< std::size_t > target
B, sorted and unique.
std::vector< T > alpha
pi0 restricted to K, UNNORMALIZED
T atom
mass of pi0 already in B, i.e. F(0)
std::vector< double > F
DEFECTIVE CDF Pr{B entered by t, C not before}, clamped to [0, 1].
std::vector< double > t
the grid the curve was asked on
T reachprob
Pr{B entered before C}, the limit of F.
std::vector< double > f
its density alpha exp(St) sB
std::vector< std::size_t > keep
row of S -> state index of Q (the set K)