5#ifndef LINE_API_MC_CTMC_CLASSICAL_H
6#define LINE_API_MC_CTMC_CLASSICAL_H
79enum class SweepKind { Power, Jacobi, GaussSeidel, Sor };
82inline bool classical_isfinite(
const T& v) {
88T classical_residual_l1(
const std::vector<T>& x,
const Matrix<T>& Q) {
89 const std::size_t n = Q.rows();
90 T s = num_traits<T>::from_int(0);
91 for (std::size_t j = 0; j < n; ++j) {
92 T r = num_traits<T>::from_int(0);
93 for (std::size_t i = 0; i < n; ++i) r += x[i] * Q(i, j);
100ClassicalResult<T> stationary_sweep_iterate(SweepKind kind,
const Matrix<T>& Q,
double tol,
long maxiter,
101 double omega,
const std::vector<T>& p0) {
102 static_assert(num_traits<T>::has_transcendental,
103 "the classical iterative solvers stop on a residual tolerance and have no exact "
104 "instantiation; use ctmc_solve for exact arithmetic");
105 const std::size_t n = Q.rows();
106 if (Q.cols() != n)
throw InputError(
"stationary_sweep_iterate: generator is not square");
107 if (n == 0)
throw InputError(
"stationary_sweep_iterate: empty generator");
110 const T
zero = num_traits<T>::from_int(0);
111 const T one = num_traits<T>::from_int(1);
115 for (std::size_t i = 0; i < n; ++i) x[i] = one / num_traits<T>::from_int(
static_cast<long>(n));
117 if (p0.size() != n)
throw InputError(
"stationary_sweep_iterate: p0 has the wrong length");
119 for (
const T& v : p0) s += v;
120 for (std::size_t i = 0; i < n; ++i) x[i] = p0[i] / s;
123 const bool adaptive = (kind == SweepKind::Sor) && !(omega > 0);
124 if (kind == SweepKind::Sor) {
127 }
else if (!(omega > 0 && omega < 2)) {
128 throw InputError(
"ctmc_sor: relaxation factor omega must lie in (0,2)");
130 }
else if (kind == SweepKind::GaussSeidel) {
136 const double ADAPT_EVERY = 10, OMEGA_CAP = 1.95, DIVERGE_FACTOR = 100;
138 bool zeroDiag =
false;
139 for (std::size_t i = 0; i < n; ++i) {
141 if (a > qmax) qmax = a;
142 if (Q(i, i) == zero) zeroDiag =
true;
144 ClassicalResult<T> res{x, 0, 0.0, 0, omega};
145 if (qmax == zero)
return res;
146 if (kind != SweepKind::Power && zeroDiag) {
148 res.relres = num_traits<T>::to_double(classical_residual_l1(x, Q) / qmax);
151 const T q = qmax * num_traits<T>::from_rational(11, 10);
153 bool frozen =
false, haveRef =
false;
154 double dPrev = std::nan(
""), dCur = std::nan(
""), relresRef = 0.0;
156 std::vector<T> r(n), xn(n);
157 for (
long k = 0; k <= maxiter; ++k) {
158 for (std::size_t j = 0; j < n; ++j) {
160 for (std::size_t i = 0; i < n; ++i) s += x[i] * Q(i, j);
164 for (
const T& v : r) rn +=
num_abs(v);
165 const double relres = num_traits<T>::to_double(rn / qmax);
178 if (k == maxiter)
break;
179 if (adaptive && !frozen && relres > DIVERGE_FACTOR * relresRef) {
184 case SweepKind::Power:
185 for (std::size_t i = 0; i < n; ++i) xn[i] = x[i] + r[i] / q;
187 case SweepKind::Jacobi:
188 for (std::size_t i = 0; i < n; ++i) {
190 for (std::size_t j = 0; j < n; ++j)
191 if (j != i) s += Q(j, i) * x[j];
192 xn[i] = -s / Q(i, i);
197 const T w = num_traits<T>::from_double(omega);
198 for (std::size_t i = 0; i < n; ++i) {
200 for (std::size_t j = 0; j < i; ++j) s += Q(j, i) * xn[j];
201 for (std::size_t j = i + 1; j < n; ++j) s += Q(j, i) * x[j];
202 xn[i] = ((one - w) * Q(i, i) * x[i] - w * s) / Q(i, i);
209 for (
const T& v : xn) {
211 if (!classical_isfinite(v)) finite =
false;
213 if (!finite || !classical_isfinite(s) || s == zero) {
221 for (std::size_t i = 0; i < n; ++i) {
226 dCur = num_traits<T>::to_double(d);
229 if (adaptive && !frozen && (iter %
static_cast<long>(ADAPT_EVERY)) == 0 && dPrev > 0) {
230 const double lambda = dCur / dPrev;
231 if (lambda > 0 && lambda < 1) {
232 const double mu2 = (lambda + omega - 1) * (lambda + omega - 1) / (lambda * omega * omega);
233 if (mu2 >= 0 && mu2 < 1) {
234 const double omegaNew = std::min(2.0 / (1.0 + std::sqrt(1.0 - mu2)), OMEGA_CAP);
235 if (omegaNew > omega) {
237 relresRef = num_traits<T>::to_double(classical_residual_l1(x, Q) / qmax);
262 return detail::stationary_sweep_iterate(detail::SweepKind::Power, Q, tol, maxiter, 0.0, p0);
269 return detail::stationary_sweep_iterate(detail::SweepKind::Jacobi, Q, tol, maxiter, 0.0, p0);
276 return detail::stationary_sweep_iterate(detail::SweepKind::GaussSeidel, Q, tol, maxiter, 1.0, p0);
286 const std::vector<T>& p0 = {}) {
287 return detail::stationary_sweep_iterate(detail::SweepKind::Sor, Q, tol, maxiter, omega, p0);
295 return name ==
"power" || name ==
"jacobi" || name ==
"gaussseidel" || name ==
"sor";
304 if (name ==
"sor")
return ctmc_sor(Q);
305 throw InputError(
"ctmc_classical_by_name: unknown classical solver '" + name +
"'");
The exception types the port throws.
Dense matrix and non-owning view.
bool is_classical_linsolver(const std::string &name)
Whether a linsolver name selects one of the classical solvers, as accepted by ctmc_solve in all four ...
ClassicalResult< T > ctmc_power(const Matrix< T > &Q, double tol=CLASSICAL_DEFAULT_TOL, long maxiter=CLASSICAL_DEFAULT_MAXITER, const std::vector< T > &p0={})
Power method on the uniformised DTMC P = I + Q/q, q = 1.1 max|q_ii|.
ClassicalResult< T > ctmc_jacobi(const Matrix< T > &Q, double tol=CLASSICAL_DEFAULT_TOL, long maxiter=CLASSICAL_DEFAULT_MAXITER, const std::vector< T > &p0={})
Jacobi iteration on Q' x = 0 with renormalisation (see file comment).
ClassicalResult< T > ctmc_sor(const Matrix< T > &Q, double tol=CLASSICAL_DEFAULT_TOL, long maxiter=CLASSICAL_DEFAULT_MAXITER, double omega=0.0, const std::vector< T > &p0={})
Successive over-relaxation on Q' x = 0 with renormalisation.
constexpr long CLASSICAL_DEFAULT_MAXITER
ClassicalResult< T > ctmc_classical_by_name(const std::string &name, const Matrix< T > &Q)
Run the classical solver named by is_classical_linsolver with defaults.
ClassicalResult< T > ctmc_gaussseidel(const Matrix< T > &Q, double tol=CLASSICAL_DEFAULT_TOL, long maxiter=CLASSICAL_DEFAULT_MAXITER, const std::vector< T > &p0={})
Forward Gauss-Seidel iteration on Q' x = 0 with renormalisation.
constexpr double CLASSICAL_DEFAULT_TOL
Conservation laws of a layered queueing network, enumerated from its structure.
Number-type abstraction for the templated API port.
long iter
sweeps performed
double omega
relaxation factor at return (1 for Gauss-Seidel, 0 otherwise)
double relres
||p Q||_1 / max|q_ii| at return
int flag
0 converged, 1 iteration cap, 2 zero diagonal, 4 breakdown
std::vector< T > p
stationary distribution estimate, sums to one