LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ctmc_accumulated.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_API_MC_CTMC_ACCUMULATED_H
6#define LINE_API_MC_CTMC_ACCUMULATED_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Accumulated state probabilities L(t) = int_0^t pi(s) ds of a CTMC by
12 * uniformisation (Reibman and Trivedi 1989; Haverkort 1998, section 15.2.4).
13 *
14 * Templated port of matlab/src/api/mc/ctmc_accumulated.m, twin of
15 * jline.api.mc.Ctmc_accumulated and line_solver.api.mc.ctmc.ctmc_accumulated.
16 * With P = I + Q/q, q = 1.1 max|q_ii| and Poisson weights w_j at q h,
17 *
18 * int_0^h pi0 exp(Q s) ds = (1/q) sum_j (1 - W_j) pi0 P^j, W_j = sum_{i<=j} w_i
19 *
20 * so L(t) r is the expected reward accumulated over [0, t] for a rate reward
21 * r, and L(t) r / t its time average. Successive time points are marched
22 * interval by interval, so a grid costs one series per gap. The series is
23 * truncated at the first K whose Poisson tail 1 - W_K is below tol and whose
24 * neglected integral mass q h - sum_{j<=K}(1 - W_j) is below tol q h. An
25 * interval with q h > 500 is split into equal segments, as in
26 * ctmc_uniformization.h, and each segment end is rescaled to the mass of its
27 * start so the truncation loss does not compound across segments.
28 *
29 * The weights involve exp(-q h): no exact instantiation.
30 */
31
32#include <algorithm>
33#include <cmath>
34#include <cstddef>
35#include <vector>
36
38#include "line/num/number.h"
39#include "line/util/error.h"
40#include "line/util/matrix.h"
41
42namespace line {
43namespace mc {
44
45template <class T>
47 Matrix<T> L; ///< row k is int_0^{t_k} pi(s) ds
48 Matrix<T> piT; ///< row k is pi(t_k)
49 std::size_t kmax; ///< largest number of Poisson terms used by any interval
50};
51
52namespace detail {
53
54template <class T>
55void accumulate_interval(std::vector<T>& v, const Matrix<T>& P, const T& q, const T& h, double tol,
56 long maxiter, std::vector<T>& Lh, std::size_t& kmax) {
57 using std::exp;
58 using std::expm1;
59 const std::size_t n = v.size();
60 const T zero = num_traits<T>::from_int(0);
61 Lh.assign(n, zero);
62 kmax = 0;
63 if (h == zero) return;
64 if (q == zero) {
65 for (std::size_t i = 0; i < n; ++i) Lh[i] = h * v[i];
66 return;
67 }
68 const T qt = q * h;
69 const double qtd = num_traits<T>::to_double(qt);
70 const double MAXQT = 500.0;
71 if (qtd > MAXQT) {
72 const long nSeg = static_cast<long>(std::ceil(qtd / MAXQT));
73 const T hs = h / num_traits<T>::from_int(nSeg);
74 std::vector<T> dL;
75 for (long s = 0; s < nSeg; ++s) {
76 std::size_t K = 0;
77 accumulate_interval(v, P, q, hs, tol, maxiter, dL, K);
78 for (std::size_t i = 0; i < n; ++i) Lh[i] += dL[i];
79 kmax = std::max(kmax, K);
80 }
81 return;
82 }
83 if (maxiter <= 0)
84 maxiter = static_cast<long>(std::max(100.0, std::ceil(qtd + 10.0 * std::sqrt(qtd) + 20.0)));
85 const T tolT = num_traits<T>::from_double(tol);
86
87 // Truncation point.
88 T w = exp(-qt);
89 T tail = -expm1(-qt); // 1 - W_0
90 T S = tail; // sum_{j<=K} (1 - W_j)
91 long K = 0;
92 while (K < maxiter && !(tail <= tolT && T(qt - S) <= T(tolT * qt))) {
93 ++K;
94 w = w * qt / num_traits<T>::from_int(K);
95 tail = tail - w;
96 if (tail < zero) tail = zero;
97 S += tail;
98 }
99 kmax = static_cast<std::size_t>(K);
100
101 // Series.
102 w = exp(-qt);
103 tail = -expm1(-qt);
104 std::vector<T> Pk = v, vEnd(n), Lsum(n);
105 for (std::size_t i = 0; i < n; ++i) {
106 vEnd[i] = w * Pk[i];
107 Lsum[i] = tail * Pk[i];
108 }
109 for (long j = 1; j <= K; ++j) {
110 Pk = vecmat(Pk, P);
111 w = w * qt / num_traits<T>::from_int(j);
112 tail = tail - w;
113 if (tail < zero) tail = zero;
114 for (std::size_t i = 0; i < n; ++i) {
115 vEnd[i] += w * Pk[i];
116 Lsum[i] += tail * Pk[i];
117 }
118 }
119 T sv = zero, se = zero;
120 for (std::size_t i = 0; i < n; ++i) {
121 sv += v[i];
122 se += vEnd[i];
123 }
124 if (se != zero && sv != zero)
125 for (T& e : vEnd) e = e * (sv / se);
126 for (std::size_t i = 0; i < n; ++i) Lh[i] = Lsum[i] / q;
127 v = vEnd;
128}
129
130} // namespace detail
131
132/**
133 * @brief Accumulated state probabilities int_0^t pi(s) ds at each time point.
134 *
135 * @param pi0 initial distribution (row vector of length n)
136 * @param Q generator
137 * @param t nonempty, finite, nonnegative, nondecreasing time points
138 * @param tol truncation tolerance (MATLAB default 1e-12)
139 * @param maxiter Poisson term cap per interval, <= 0 for the MATLAB heuristic
140 */
141template <class T>
142AccumulatedResult<T> ctmc_accumulated(const std::vector<T>& pi0, const Matrix<T>& Q, const std::vector<T>& t,
143 double tol = 1e-12, long maxiter = -1) {
145 "ctmc_accumulated requires transcendental arithmetic: the Poisson weights involve "
146 "exp(-q t)");
147 const std::size_t n = Q.rows();
148 if (Q.cols() != n) throw InputError("ctmc_accumulated: generator is not square");
149 if (pi0.size() != n) throw InputError("ctmc_accumulated: pi0 has the wrong length");
150 const T zero = num_traits<T>::from_int(0);
151 if (t.empty()) throw InputError("ctmc_accumulated: t must be nonempty");
152 for (std::size_t k = 0; k < t.size(); ++k) {
153 const double tk = num_traits<T>::to_double(t[k]);
154 if (!std::isfinite(tk) || t[k] < zero || (k > 0 && t[k] < t[k - 1]))
155 throw InputError("ctmc_accumulated: t must hold finite, nonnegative, nondecreasing time points");
156 }
157 const T q = detail::uniformization_rate(Q);
158 const Matrix<T> P = (q == zero) ? Matrix<T>(n, n) : detail::uniformized_matrix(Q, q);
159
160 AccumulatedResult<T> res{Matrix<T>(t.size(), n), Matrix<T>(t.size(), n), 0};
161 std::vector<T> v = pi0, acc(n, zero), dL;
162 T tprev = zero;
163 for (std::size_t k = 0; k < t.size(); ++k) {
164 std::size_t K = 0;
165 detail::accumulate_interval(v, P, q, T(t[k] - tprev), tol, maxiter, dL, K);
166 res.kmax = std::max(res.kmax, K);
167 for (std::size_t i = 0; i < n; ++i) {
168 acc[i] += dL[i];
169 res.L(k, i) = acc[i];
170 res.piT(k, i) = v[i];
171 }
172 tprev = t[k];
173 }
174 return res;
175}
176
177/** Single time point overload. */
178template <class T>
179AccumulatedResult<T> ctmc_accumulated(const std::vector<T>& pi0, const Matrix<T>& Q, const T& t,
180 double tol = 1e-12, long maxiter = -1) {
181 return ctmc_accumulated(pi0, Q, std::vector<T>{t}, tol, maxiter);
182}
183
184} // namespace mc
185} // namespace line
186
187#endif // LINE_API_MC_CTMC_ACCUMULATED_H
InputError(const std::string &what)
Definition error.h:105
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
Transient distribution of a CTMC by uniformization (Jensen's method), and the time-averaged distribut...
The exception types the port throws.
Dense matrix and non-owning view.
AccumulatedResult< T > ctmc_accumulated(const std::vector< T > &pi0, const Matrix< T > &Q, const std::vector< T > &t, double tol=1e-12, long maxiter=-1)
Accumulated state probabilities int_0^t pi(s) ds at each time point.
Conservation laws of a layered queueing network, enumerated from its structure.
Definition aoi_dist2ph.h:52
Number-type abstraction for the templated API port.
Matrix< T > L
row k is int_0^{t_k} pi(s) ds
Matrix< T > piT
row k is pi(t_k)
std::size_t kmax
largest number of Poisson terms used by any interval