5#ifndef LINE_LANG_PROCESSES_MARKOV_CHAIN_H
6#define LINE_LANG_PROCESSES_MARKOV_CHAIN_H
124void require_square(
const MarkovChainModel<T>& m,
const char* who) {
125 if (m.mat.rows() == 0 || m.mat.cols() != m.mat.rows())
126 throw InputError(std::string(who) +
": the chain matrix is empty or not square");
130void require_kind(
const MarkovChainModel<T>& m,
bool discrete,
const char* who) {
131 if (m.discrete != discrete)
132 throw InputError(std::string(who) +
": this is a method of " +
133 (discrete ?
"MarkovChain (a DTMC); the object is a MarkovProcess"
134 :
"MarkovProcess (a CTMC); the object is a MarkovChain"));
139std::vector<T> uniform_law(std::size_t n) {
140 return std::vector<T>(n, num_traits<T>::from_int(1) / num_traits<T>::from_int(
141 static_cast<int>(n)));
145std::vector<T> law_or_uniform(
const std::vector<T>& pi0, std::size_t n,
const char* who) {
146 if (pi0.empty())
return uniform_law<T>(n);
148 throw InputError(std::string(who) +
": the initial distribution has the wrong length");
165 detail::require_square(m,
"to_markov_process");
166 detail::require_kind(m,
true,
"to_markov_process");
168 const std::size_t n = Q.
rows();
177 const std::size_t n = Q.
rows();
179 for (std::size_t i = 0; i < n; ++i)
180 for (std::size_t j = 0; j < n; ++j) {
182 if (a > qmax) qmax = a;
197 detail::require_square(m,
"to_markov_chain");
198 detail::require_kind(m,
false,
"to_markov_chain");
200 throw InputError(
"to_markov_chain: the uniformization rate must be positive");
201 const std::size_t n = m.
mat.rows();
203 for (std::size_t i = 0; i < n; ++i)
204 for (std::size_t j = 0; j < n; ++j) P(i, j) = T(P(i, j) / q);
211 detail::require_square(m,
"to_markov_chain");
212 detail::require_kind(m,
false,
"to_markov_chain");
241 detail::require_square(m,
"to_embedded");
242 detail::require_kind(m,
false,
"to_embedded");
243 const std::size_t n = m.
mat.rows();
246 for (std::size_t i = 0; i < n; ++i) {
247 const T exit_rate = T(zero - m.
mat(i, i));
249 if (exit_rate > zero) {
250 for (std::size_t j = 0; j < n; ++j) P(i, j) = T(P(i, j) / exit_rate);
261 detail::require_square(m,
"to_time_reversed");
284 detail::require_square(m,
"chain_solve");
296 detail::require_square(m,
"solve_relative");
297 detail::require_kind(m,
false,
"solve_relative");
326 detail::require_square(m,
"get_prob_state");
327 detail::require_kind(m,
false,
"get_prob_state");
328 const std::size_t n = m.
mat.rows();
329 if (i >= n)
throw InputError(
"get_prob_state: state index is out of range");
333 for (std::size_t r = 0; r < n; ++r) Q(r, 0) = one;
335 for (std::size_t j = 0; j < n; ++j) Qi(i, j) = zero;
343 "get_prob_state: the normalized generator is singular, so Cramer's rule has no "
344 "quotient; the chain is reducible -- use chain_solve, which decomposes it");
354 throw InputError(
"match_state: the chain carries no state space to match against");
355 if (state.size() != cols)
356 throw InputError(
"match_state: the state has the wrong number of columns");
357 for (std::size_t r = 0; r < rows; ++r) {
359 for (std::size_t c = 0; c < cols && hit; ++c) hit = (m.
state_space(r, c) == state[c]);
370 throw InputError(
"get_prob_state: the state is not in the chain's state space");
377 detail::require_square(m,
"is_feasible");
404 const T& t,
const std::string& method =
"unif") {
405 detail::require_square(m,
"chain_transient_at");
406 detail::require_kind(m,
false,
"chain_transient_at");
407 const std::size_t n = m.
mat.rows();
408 const std::vector<T> pi0 = detail::law_or_uniform(pi0in, n,
"chain_transient_at");
411 if (method ==
"foxglynn") {
415 }
else if (method ==
"unif" || method.empty()) {
420 throw InputError(
"chain_transient_at: unknown transient method '" + method +
421 "'; the reference offers 'unif' and 'foxglynn'");
434 std::size_t steps = 1) {
435 detail::require_square(m,
"chain_transient_steps");
436 detail::require_kind(m,
true,
"chain_transient_steps");
437 const std::size_t n = m.
mat.rows();
438 const std::vector<T> pi0 = detail::law_or_uniform(pi0in, n,
"chain_transient_steps");
449 const std::vector<T>& pi0in,
const T& t) {
450 detail::require_square(m,
"chain_transient_unif");
451 detail::require_kind(m,
true,
"chain_transient_unif");
452 const std::size_t n = m.
mat.rows();
453 const std::vector<T> pi0 = detail::law_or_uniform(pi0in, n,
"chain_transient_unif");
473 detail::require_square(m,
"time_average");
474 detail::require_kind(m,
false,
"time_average");
475 const std::size_t n = m.
mat.rows();
476 const std::vector<T> pi0 = detail::law_or_uniform(pi0in, n,
"time_average");
492 detail::require_square(m,
"chain_sens");
493 detail::require_kind(m,
false,
"chain_sens");
528 const std::vector<std::vector<std::size_t>>& MS,
529 const std::string& method =
"courtois",
530 const T* param =
nullptr) {
531 detail::require_square(m,
"aggregate");
532 detail::require_kind(m,
false,
"aggregate");
533 if (MS.empty())
throw InputError(
"aggregate: the macrostate partition is empty");
536 if (method ==
"courtois" || method.empty()) {
542 }
else if (method ==
"kms") {
543 const std::size_t steps =
549 }
else if (method ==
"takahashi") {
550 const std::size_t steps =
556 }
else if (method ==
"multi") {
558 "aggregate: the 'multi' method requires the second-level partition MSS; call the "
559 "aggregate_multi overload, which takes it");
561 throw InputError(
"aggregate: unknown aggregation method '" + method +
"'");
573 const std::vector<std::vector<std::size_t>>& MS,
574 const std::vector<std::vector<std::size_t>>& MSS) {
575 detail::require_square(m,
"aggregate_multi");
576 detail::require_kind(m,
false,
"aggregate_multi");
577 if (MS.empty() || MSS.empty())
578 throw InputError(
"aggregate_multi: both partitions must be non-empty");
615 const std::vector<std::size_t>& I = std::vector<std::size_t>()) {
616 detail::require_square(m,
"stoch_comp_full");
619 std::vector<std::size_t> keep = I;
621 const std::size_t half = m.
mat.rows() / 2;
624 "stoch_comp_full: the default partition takes the first half of the state "
625 "space, which is empty on a chain of order one; pass I explicitly");
626 for (std::size_t i = 0; i < half; ++i) keep.push_back(i);
645 const std::vector<std::size_t>& I = std::vector<std::size_t>()) {
660 const std::vector<std::size_t>& target) {
661 detail::require_square(m,
"hitting_time");
662 if (target.empty())
throw InputError(
"hitting_time: the target set is empty");
682template <
class T,
class Gen>
684 std::size_t n, Gen& gen) {
685 detail::require_square(m,
"chain_sample");
687 const std::size_t order = m.
mat.rows();
689 std::vector<T> pi0 = pi0in;
691 std::uniform_real_distribution<double> unif(0.0, 1.0);
694 for (std::size_t i = 0; i < order; ++i) {
695 const double u = unif(gen);
699 for (std::size_t i = 0; i < order; ++i)
701 }
else if (pi0.size() != order) {
702 throw InputError(
"chain_sample: the initial distribution has the wrong length");
707 if (!pi0in.empty() && pi0in.size() != order)
708 throw InputError(
"chain_sample: the initial distribution has the wrong length");
721template <
class T,
class Gen>
723 std::uniform_real_distribution<double> unif(0.0, 1.0);
724 auto draw = [&]() {
return unif(gen); };
729template <
class T,
class Gen>
731 std::uniform_real_distribution<double> unif(0.0, 1.0);
732 auto draw = [&]() {
return unif(gen); };
755 const std::size_t obs = sample_state.
rows(), cols = sample_state.
cols();
758 "from_sample_sys_aggr: at least two observations are needed, since the estimate is "
759 "built from the transitions between consecutive ones");
761 std::vector<std::size_t> hash(obs);
762 std::vector<std::vector<T>> space;
763 for (std::size_t r = 0; r < obs; ++r) {
764 std::vector<T> row(cols);
765 for (std::size_t c = 0; c < cols; ++c) row[c] = sample_state(r, c);
766 std::size_t found = space.size();
767 for (std::size_t s = 0; s < space.size(); ++s) {
769 for (std::size_t c = 0; c < cols && hit; ++c) hit = (space[s][c] == row[c]);
775 if (found == space.size()) space.push_back(row);
779 const std::size_t n = space.size();
781 for (std::size_t r = 1; r < obs; ++r)
785 for (std::size_t s = 0; s < n; ++s)
786 for (std::size_t c = 0; c < cols; ++c) ss(s, c) = space[s][c];
NumericError(const std::string &what)
Courtois decomposition of a nearly completely decomposable (NCD) CTMC.
Transient distribution of a CTMC by uniformization with Fox-Glynn Poisson weights.
Feasibility predicates for generators and stochastic matrices.
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
First passage times into a target STATE SET, for Markov and semi-Markov chains.
Random infinitesimal generator of a CTMC.
Equilibrium distribution relative to a reference state.
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
Sample path of a continuous-time Markov chain given its generator.
Steady-state distribution of a continuous-time Markov chain.
Limiting distribution of a CTMC whose generator may be reducible.
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
Time-reversed generator and transition matrix.
Normalize a non-negative matrix into a stochastic transition matrix.
Random DTMC kernels, trajectory simulation and the weak-component split.
Equilibrium distribution of a discrete-time Markov chain, and stochastic complementation.
Limiting distribution of a discrete-time Markov chain whose transition matrix may be reducible.
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
Discrete-time transient distributions, hitting times and uniformization.
The exception types the port throws.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
MarkovChainModel< T > to_markov_chain(const MarkovChainModel< T > &m, const T &q)
MarkovProcess.toMarkovChain / toDTMC: the UNIFORMIZED chain, P = Q/q + I.
std::vector< T > hitting_time(const MarkovChainModel< T > &m, const std::vector< std::size_t > &target)
hittingTime: the mean time (CTMC) or step count (DTMC) to reach any state in target,...
TransientAtResult< T > chain_transient_unif(const MarkovChainModel< T > &m, const std::vector< T > &pi0in, const T &t)
MarkovChain.transientUnif: the DTMC read as the randomized image of a CTMC, so t is CONTINUOUS here w...
MarkovChainModel< T > rand_chain(std::size_t n, Gen &gen)
MarkovChain.rand: a random transition matrix of the given order.
MarkovChainModel< T > to_markov_process(const MarkovChainModel< T > &m)
MarkovChain.toMarkovProcess / toCTMC: read the DTMC as a CTMC with unit exit rates,...
std::vector< T > solve_relative(const MarkovChainModel< T > &m, std::size_t refstate=0)
MarkovProcess.solveRelative: the equilibrium vector normalized so that refstate carries one,...
MarkovChainModel< T > to_embedded(const MarkovChainModel< T > &m)
MarkovProcess.toEmbedded: the JUMP CHAIN, the DTMC of the states visited at transition epochs.
MarkovChainModel< T > to_dtmc(const MarkovChainModel< T > &m)
toDTMC, the backwards-compatible alias of toMarkovChain.
Matrix< T > stoch_comp(const MarkovChainModel< T > &m, const std::vector< std::size_t > &I=std::vector< std::size_t >())
stochComp: the complement alone.
std::vector< T > chain_solve(const MarkovChainModel< T > &m)
MarkovProcess.solve / MarkovChain.solve.
AggregateResult< T > aggregate_multi(const MarkovChainModel< T > &m, const std::vector< std::vector< std::size_t > > &MS, const std::vector< std::vector< std::size_t > > &MSS)
The "multi" arm of aggregate, separated because its parameter is a PARTITION OF THE PARTITION and not...
Matrix< T > chain_transient_steps(const MarkovChainModel< T > &m, const std::vector< T > &pi0in, std::size_t steps=1)
MarkovChain.transient: the law at every step 0..steps, one row per step.
T default_uniformization_rate(const Matrix< T > &Q)
The uniformization rate this port uses when the caller names none.
ChainPath< T > chain_sample(const MarkovChainModel< T > &m, const std::vector< T > &pi0in, std::size_t n, Gen &gen)
sample: simulate n steps.
TransientAtResult< T > chain_transient_at(const MarkovChainModel< T > &m, const std::vector< T > &pi0in, const T &t, const std::string &method="unif")
MarkovProcess.transient: the law at ONE time t, by uniformization.
TimeAverageOut< T > time_average(const MarkovChainModel< T > &m, const std::vector< T > &pi0in, const T &t)
MarkovProcess.timeAverage: the law averaged over [0,t], and its endpoint.
ProbStateResult< T > get_prob_state(const MarkovChainModel< T > &m, std::size_t i)
MarkovProcess.getProbState: the probability of ONE state by Cramer's rule.
StochCompOut< T > stoch_comp_full(const MarkovChainModel< T > &m, const std::vector< std::size_t > &I=std::vector< std::size_t >())
stochComp / stochCompFull for either kind.
AggregateResult< T > aggregate(const MarkovChainModel< T > &m, const std::vector< std::vector< std::size_t > > &MS, const std::string &method="courtois", const T *param=nullptr)
MarkovProcess.aggregate: aggregation-disaggregation over a macrostate partition.
MarkovChainModel< T > rand_process(std::size_t n, Gen &gen)
MarkovProcess.rand: a random generator of the given order.
std::size_t match_state(const MarkovChainModel< T > &m, const std::vector< T > &state)
Row index of state in the chain's state space, or n when it carries none.
MarkovChainModel< T > to_time_reversed(const MarkovChainModel< T > &m)
toTimeReversed for either kind: the chain run backwards in time.
std::vector< T > chain_sens(const MarkovChainModel< T > &m, const Matrix< T > &dQ)
MarkovProcess.sens: the derivative of the stationary law with respect to a scalar parameter,...
MarkovChainModel< T > from_sample_sys_aggr(const Matrix< T > &sample_state)
MarkovChain.fromSampleSysAggr: estimate a DTMC from an observed trajectory.
bool is_feasible(const MarkovChainModel< T > &m)
isFeasible: a valid generator, or a stochastic transition matrix.
ReducibleResult< T > dtmc_solve_reducible(const Matrix< T > &P, const std::vector< T > &pin, double zeroColTol=1e-12)
Limiting distribution of a discrete-time Markov chain whose transition matrix may be reducible.
Matrix< T > dtmc_makestochastic(const Matrix< T > &Pin)
Normalize a non-negative matrix into a stochastic transition matrix.
Matrix< T > ctmc_makeinfgen(const Matrix< T > &Q)
Set the diagonal so that every row sums to zero (ctmc_makeinfgen).
KmsResult< T > ctmc_kms(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps)
Koury-McAllister-Stewart aggregation-disaggregation for a nearly completely decomposable CTMC.
Matrix< T > dtmc_stochcomp(const Matrix< T > &P, const std::vector< std::size_t > &keep)
Stochastic complement of a DTMC partition, a port of matlab/lib/kpctoolbox/mc/dtmc_stochcomp....
StochCompResult< T > ctmc_stochcomp(const Matrix< T > &Q, const std::vector< std::size_t > &I)
UniformizationResult< T > ctmc_uniformization(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double tol=1e-12, long maxiter=-1)
Transient distribution of a CTMC by uniformization (Jensen's method), and the time-averaged distribut...
Matrix< T > dtmc_timereverse(const Matrix< T > &P)
Transition matrix of the time-reversed DTMC.
TakahashiResult< T > ctmc_takahashi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, std::size_t numSteps, double massTol=1e-14)
Takahashi's aggregation-disaggregation for a nearly completely decomposable CTMC.
Matrix< T > ctmc_rand(std::size_t n, Gen &gen)
Random infinitesimal generator of a CTMC.
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_hitting_time(const Matrix< T > &P, const std::vector< std::size_t > &target)
Expected number of steps to reach the target set, zero on the target set itself.
MultiResult< T > ctmc_multi(const Matrix< T > &Q, const std::vector< std::vector< std::size_t > > &MS, const std::vector< std::vector< std::size_t > > &MSS, const T &q)
Two-level multigrid aggregation-disaggregation for a nearly completely decomposable CTMC.
TimeAverageResult< T > ctmc_timeaverage(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double tol=1e-12, long maxiter=-1)
Time-averaged distribution (1/t) int_0^t pi(u) du, plus pi(t) itself.
int dtmc_isfeasible(const Matrix< T > &P)
Largest precision level 1..15 at which P is stochastic, or 0 when none holds.
UniformizationResult< T > dtmc_uniformization(const std::vector< T > &pi0, const Matrix< T > &P, const T &t, double tol=1e-12, long maxiter=-1)
Transient law of a DTMC through the uniformized generator of P.
bool ctmc_isfeasible(const Matrix< T > &Q, const T &tol)
True when Q is square, has nonnegative off-diagonals, nonpositive diagonal and zero row sums.
Matrix< T > dtmc_rand(std::size_t n, Gen &gen)
Random stochastic matrix, the uniformization of a random generator.
Matrix< T > ctmc_timereverse(const Matrix< T > &Q)
Generator of the time-reversed CTMC.
std::vector< T > ctmc_relsolve(const Matrix< T > &Qin, std::size_t refstate)
Stationary measure scaled so that entry refstate equals one.
CtmcPath< T > ctmc_simulate(const Matrix< T > &Q, const std::vector< T > &pi0, std::size_t n, pfqn::McRng &rng)
Simulate n steps of the CTMC with generator Q.
Matrix< T > dtmc_transient(const Matrix< T > &P, const std::vector< T > &pi0, std::size_t steps)
Trajectory of the law over steps transitions, row k holding pi0 P^k.
FoxGlynnResult< T > ctmc_foxglynn(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double tol=1e-12, long maxiter=-1)
Transient distribution of a CTMC by uniformization with Fox-Glynn Poisson weights.
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.
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.
std::vector< T > ctmc_sens(const Matrix< T > &Q, const Matrix< T > &dQ, const std::vector< T > &pi)
Sensitivity of the steady-state distribution of a CTMC to a scalar parameter.
std::vector< std::size_t > dtmc_simulate(const Matrix< T > &P, const std::vector< T > &pi0, std::size_t n, Gen &gen)
Sample path of a DTMC, n states starting from pi0.
std::mt19937_64 McRng
The generator type every Monte Carlo entry point in this tree accepts.
T lu_det(const Matrix< T > &A)
Determinant of a square matrix, by the same partial-pivoting elimination.
Number-type abstraction for the templated API port.
What aggregate returns: the approximate law and the two NCD indices.
T eps
nearly-complete-decomposability index of the partition
T epsMAX
the largest index for which the approximation is meant to hold
std::vector< T > p
approximate stationary vector, ORIGINAL state ordering
A sampled path: the states visited, with their holding times for a CTMC.
std::vector< T > sojourn
holding times; empty for a DTMC, which has none
std::vector< std::size_t > states
0-based state index at each step
A user-supplied chain: a MarkovProcess when discrete is false, else a MarkovChain.
Matrix< T > mat
generator Q (CTMC) or transition matrix P (DTMC)
Matrix< T > state_space
optional; empty means the chain carries none
bool discrete
true for a MarkovChain
static MarkovChainModel< T > chain(const Matrix< T > &transmat, const Matrix< T > &space=Matrix< T >())
std::size_t order() const
static MarkovChainModel< T > process(const Matrix< T > &infgen, const Matrix< T > &space=Matrix< T >())
Mirrors the constructors: a generator is closed, a transition matrix normalized.
What getProbState returns: the probability and the two determinants behind it.
T num
determinant of the numerator matrix
T pi_i
probability of the state
T den
determinant of the denominator matrix
What stochCompFull returns for a CTMC; a DTMC fills the same blocks from P.
Matrix< T > A11
the four blocks of the partitioned matrix
Matrix< T > S
the complement on the selected states
Matrix< T > T12
the return-path term, so that S = A11 + T12
What timeAverage returns.
std::vector< T > pi_exit
law at t
std::vector< T > pi_time_avg
time-averaged law over [0, t]
What the CTMC transient returns: the law at t and the truncation it used.
std::vector< T > pi
distribution at time t
std::size_t kmax
Poisson terms used (the right truncation point for 'foxglynn').
T eps
NCD index: largest ROW sum of B, ||B||_inf (MATLAB and the JAR).
std::vector< T > p
approximate stationary vector, ORIGINAL state ordering
T epsMAX
(1 - max subdominant block eigenvalue modulus) / 2
One simulated sample path: the state visited at each step and its holding time.
std::vector< std::size_t > states
0-based state index at each step
std::vector< T > sojourn
holding time spent in that state
long right
right truncation point
std::vector< T > pi
distribution at time t
T epsMAX
maximum admissible NCD index
T eps
NCD index, as ctmc_courtois defines it.
std::vector< T > p
estimate after numSteps sweeps, ORIGINAL ordering
std::vector< T > p
approximate stationary vector, ORIGINAL ordering
T eps
NCD index of the fine level.
T epsMAX
maximum admissible NCD index of the fine level
Matrix< T > S
stochastic complement on the selected states
Matrix< T > Q11
the four blocks, as MATLAB returns them
Matrix< T > T12
Q12 (-Q22)^-1 Q21, the correction term.
T eps
NCD index, as ctmc_courtois defines it.
T epsMAX
maximum admissible NCD index
std::vector< T > p
estimate after numSteps sweeps
std::vector< T > piTimeAvg
time-averaged distribution over [0, t]
std::vector< T > piExit
distribution at time t