LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
solver_ctmc_chain.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_SOLVERS_CTMC_SOLVER_CTMC_CHAIN_H
6#define LINE_SOLVERS_CTMC_SOLVER_CTMC_CHAIN_H
7
8/**
9 * @file
10 * @ingroup line_solvers
11 * Port of `matlab/src/solvers/CTMC/solver_ctmc_chain.m` and
12 * `solver_ctmc_chain_transient.m`: SolverCTMC applied to a USER-SUPPLIED Markov
13 * chain rather than to a queueing network. The reference dispatches on the
14 * class of its argument, a MarkovProcess (CTMC, generator Q) or a MarkovChain
15 * (DTMC, transition matrix P).
16 *
17 * These two functions read only `getGenerator` / `getTransMat` and
18 * `stateSpace`, so `MarkovChainModel` carries exactly that. The rest of the
19 * MarkovProcess / MarkovChain class surface -- `toEmbedded`, `toDTMC`,
20 * `aggregate`, `stochComp`, `timeAverage`, `sens`, `hittingTime`, `sample` and
21 * the two constructors from a random draw or an observed trajectory -- lives in
22 * `lang/processes/markov_chain.h`, which is where the model type is declared
23 * now; the alias below keeps the name this header introduced.
24 *
25 * NOTE that `solver_ctmc_chain` and `processes::chain_solve` DELIBERATELY
26 * disagree on how to solve. This one ports `solver_ctmc_chain.m`, which tries
27 * the primary solver and falls back to the reducible one on a failed validity
28 * test; the class method ports `MarkovProcess.solve`, which takes the reducible
29 * one outright for a numeric matrix. Do not "align" them.
30 */
31
32#include <chrono>
33#include <cmath>
34#include <cstddef>
35#include <string>
36#include <vector>
37
46#include "line/util/error.h"
47#include "line/util/matrix.h"
48#include "line/num/number.h"
49
50namespace line {
51namespace ctmc {
52
53using lang::GlobalConstants;
54
55/** A user-supplied chain: a MarkovProcess when `discrete` is false, else a MarkovChain. */
56template <class T>
58
59template <class T>
61 std::vector<T> pi; ///< stationary distribution, length n
62 Matrix<T> infgen; ///< Q for a CTMC, the uniformized P-I for a DTMC
63 Matrix<T> state_space; ///< the chain's space, or the state indices when it carries none
64 double runtime = 0.0; ///< seconds
65};
66
67namespace chain_detail {
68
69/**
70 * Port of the reference's `solver_ctmc_chain_isvalid`.
71 *
72 * Reject a solution the primary solver could not produce on a reducible chain,
73 * so that the reducible fallback is used instead. A NaN or a negative mass is
74 * not a near miss to be patched: it says the primary solve did not apply.
75 */
76template <class T>
77bool chain_isvalid(const std::vector<T>& pi, std::size_t n) {
78 if (pi.size() != n) return false;
79 double total = 0.0;
80 for (std::size_t i = 0; i < n; ++i) {
81 const double v = num_traits<T>::to_double(pi[i]);
82 if (!std::isfinite(v)) return false;
83 if (v < -GlobalConstants::FineTol) return false;
84 total += v;
85 }
86 return std::abs(total - 1.0) <= std::sqrt(GlobalConstants::FineTol);
87}
88
89/** The chain's own space, or the 1-based state indices as a column. */
90template <class T>
91Matrix<T> chain_space(const MarkovChainModel<T>& chain, std::size_t n) {
92 if (chain.state_space.rows() > 0) return chain.state_space;
93 Matrix<T> idx(n, 1);
94 for (std::size_t i = 0; i < n; ++i) idx(i, 0) = num_traits<T>::from_int(static_cast<long long>(i + 1));
95 return idx;
96}
97
98} // namespace chain_detail
99
100/**
101 * Steady-state analysis of a user-supplied Markov chain.
102 *
103 * `infgen` is Q for a CTMC and the uniformized generator P-I for a DTMC, which
104 * carries the same stationary vector; returning it under one name is what lets
105 * a caller treat the two chain kinds alike.
106 */
107template <class T>
109 const std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();
110
112 const std::size_t n = chain.mat.rows();
113 if (n == 0 || chain.mat.cols() != n)
114 throw InputError("solver_ctmc_chain: the chain matrix is empty or not square");
115
116 if (chain.discrete) {
117 const Matrix<T>& P = chain.mat;
118 out.pi = mc::dtmc_solve(P);
119 if (!chain_detail::chain_isvalid(out.pi, n)) out.pi = mc::dtmc_solve_reducible(P).pi;
120 out.infgen = P;
121 for (std::size_t i = 0; i < n; ++i) out.infgen(i, i) -= num_traits<T>::from_int(1);
122 } else {
123 const Matrix<T>& Q = chain.mat;
124 out.pi = mc::ctmc_solve(Q);
125 if (!chain_detail::chain_isvalid(out.pi, n)) out.pi = mc::ctmc_solve_reducible(Q).pi;
126 out.infgen = Q;
127 }
128
129 out.state_space = chain_detail::chain_space(chain, n);
130 out.runtime = std::chrono::duration<double>(std::chrono::steady_clock::now() - t0).count();
131 return out;
132}
133
134template <class T>
136 Matrix<T> pi_t; ///< one row per time point
137 std::vector<T> t; ///< the time points; integer STEPS for a DTMC
138};
139
140/**
141 * Transient distribution of a user-supplied Markov chain over [t0,t1].
142 *
143 * For a MarkovProcess the Kolmogorov forward equations are integrated from
144 * `pi0`; for a MarkovChain the distribution is advanced one step per unit of
145 * time, so the returned `t` holds the integer steps within the timespan.
146 *
147 * @param pi0in initial distribution; empty for the uniform one
148 */
149template <class T>
151 const std::vector<T>& pi0in, const T& t0in,
152 const T& t1) {
153 const std::size_t n = chain.mat.rows();
154 if (n == 0 || chain.mat.cols() != n)
155 throw InputError("solver_ctmc_chain_transient: the chain matrix is empty or not square");
156
157 std::vector<T> pi0 = pi0in;
158 if (pi0.empty()) {
159 pi0.assign(n, num_traits<T>::from_double(1.0 / static_cast<double>(n)));
160 } else if (pi0.size() != n) {
161 throw InputError("solver_ctmc_chain_transient: the initial distribution has the wrong length");
162 } else {
163 double total = 0.0;
164 for (std::size_t i = 0; i < n; ++i) total += num_traits<T>::to_double(pi0[i]);
165 if (std::abs(total - 1.0) > GlobalConstants::FineTol)
166 throw InputError("solver_ctmc_chain_transient: the initial distribution must sum to one");
167 }
168
169 const double d1 = num_traits<T>::to_double(t1);
170 if (!std::isfinite(d1))
171 throw InputError(
172 "solver_ctmc_chain_transient: a finite timespan is required, e.g. --timespan 0,T");
173 double d0 = num_traits<T>::to_double(t0in);
174 // An infinite LOWER end is the "no start declared" spelling, not an error.
175 if (!std::isfinite(d0)) d0 = 0.0;
176 const T t0 = num_traits<T>::from_double(d0);
177
179 if (chain.discrete) {
180 const Matrix<T>& P = chain.mat;
181 const long long k0 = static_cast<long long>(std::ceil(d0));
182 const long long k1 = static_cast<long long>(std::floor(d1));
183 if (k1 < k0)
184 throw InputError("solver_ctmc_chain_transient: the timespan contains no integer step of "
185 "the DTMC");
186 const std::size_t steps = static_cast<std::size_t>(k1 - k0 + 1);
187
188 // pi0 * P^k0, accumulated one step at a time: the reference forms the
189 // matrix power, but the row vector needs only the vector-matrix product
190 // and that is what keeps the cost linear in k0 rather than cubic.
191 std::vector<T> pik = pi0;
192 const T zero = num_traits<T>::from_int(0);
193 std::vector<T> next(n);
194 for (long long k = 0; k < k0; ++k) {
195 for (std::size_t j = 0; j < n; ++j) {
196 T acc = zero;
197 for (std::size_t i = 0; i < n; ++i) acc += pik[i] * P(i, j);
198 next[j] = acc;
199 }
200 pik = next;
201 }
202
203 out.pi_t = Matrix<T>(steps, n);
204 out.t.resize(steps);
205 for (std::size_t s = 0; s < steps; ++s) {
206 out.t[s] = num_traits<T>::from_int(k0 + static_cast<long long>(s));
207 for (std::size_t j = 0; j < n; ++j) out.pi_t(s, j) = pik[j];
208 for (std::size_t j = 0; j < n; ++j) {
209 T acc = zero;
210 for (std::size_t i = 0; i < n; ++i) acc += pik[i] * P(i, j);
211 next[j] = acc;
212 }
213 pik = next;
214 }
215 } else {
216 const mc::TransientResult<T> r = mc::ctmc_transient(chain.mat, pi0, t0, t1);
217 out.pi_t = r.pi;
218 out.t = r.t;
219 }
220 return out;
221}
222
223} // namespace ctmc
224} // namespace line
225
226#endif // LINE_SOLVERS_CTMC_SOLVER_CTMC_CHAIN_H
InputError(const std::string &what)
Definition error.h:39
Steady-state distribution of a continuous-time Markov chain.
Limiting distribution of a CTMC whose generator may be reducible.
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
Normalize a non-negative matrix into a stochastic transition matrix.
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.
The exception types the port throws.
Enumerations and the minimal distribution descriptor shared by the model layer of the C++ port.
The MarkovProcess / MarkovChain object surface.
Dense matrix and non-owning view.
CtmcChainTransientSolution< T > solver_ctmc_chain_transient(const MarkovChainModel< T > &chain, const std::vector< T > &pi0in, const T &t0in, const T &t1)
Transient distribution of a user-supplied Markov chain over [t0,t1].
CtmcChainSolution< T > solver_ctmc_chain(const MarkovChainModel< T > &chain)
Steady-state analysis of a user-supplied Markov chain.
lang::processes::MarkovChainModel< T > MarkovChainModel
A user-supplied chain: a MarkovProcess when discrete is false, else a MarkovChain.
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.
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
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_solve(const Matrix< T > &P)
Stationary distribution of a stochastic matrix P.
Definition dtmc_solve.h:106
TransientResult< T > ctmc_transient(const Matrix< T > &Q, const std::vector< T > &pi0, const T &t0, const T &t1, double rtol=1e-3, double atol=1e-6)
Transient distribution of a CTMC over a time interval, by integrating the forward equations d pi/dt =...
Number-type abstraction for the templated API port.
Matrix< T > state_space
the chain's space, or the state indices when it carries none
std::vector< T > pi
stationary distribution, length n
Matrix< T > infgen
Q for a CTMC, the uniformized P-I for a DTMC.
Matrix< T > pi_t
one row per time point
std::vector< T > t
the time points; integer STEPS for a DTMC
static constexpr double FineTol
Definition lang_types.h:668
A user-supplied chain: a MarkovProcess when discrete is false, else a MarkovChain.
Matrix< T > mat
generator Q (CTMC) or transition matrix P (DTMC)
bool discrete
true for a MarkovChain
std::vector< T > t
accepted time points, the first being t0
Matrix< T > pi
one row per time point