5#ifndef LINE_API_MC_CTMC_FOXGLYNN_H
6#define LINE_API_MC_CTMC_FOXGLYNN_H
64constexpr double FOXGLYNN_PI = 3.14159265358979323846;
67inline double foxglynn_chernoff(
double lambda,
double k) {
68 if (k <= 0.0)
return lambda;
69 return lambda - k + k * std::log(k / lambda);
73inline long foxglynn_right(
double lambda,
double tol) {
74 const double target = std::log(2.0 / tol);
75 const double m = std::floor(lambda);
78 const double a = (1.0 + 1.0 / lambda) * std::exp(1.0 / 16.0) * std::sqrt(2.0);
79 const double spread = std::sqrt(2.0 * lambda);
80 for (
int k = 1; k <= 64; ++k) {
81 const double shift = k * spread + 1.5;
82 const double d = 1.0 / (1.0 - std::exp(-(2.0 / 9.0) * shift));
83 const double bound = a * d * std::exp(-0.5 * k * k) / (k * std::sqrt(2.0 * FOXGLYNN_PI));
84 if (bound <= 0.5 * tol) {
85 r = m + std::ceil(shift);
90 while (r > m && foxglynn_chernoff(lambda, r) >= target) r -= 1.0;
91 while (foxglynn_chernoff(lambda, r + 1.0) < target) r += 1.0;
92 return static_cast<long>(r);
96inline long foxglynn_left(
double lambda,
double tol) {
97 const double target = std::log(2.0 / tol);
98 const double m = std::floor(lambda);
99 if (foxglynn_chernoff(lambda, 0.0) < target)
return 0;
101 if (lambda >= 25.0) {
102 const double b = (1.0 + 1.0 / lambda) * std::exp(1.0 / (8.0 * lambda));
103 const double spread = std::sqrt(lambda);
104 for (
int k = 1; k <= 64; ++k) {
105 const double bound = b * std::exp(-0.5 * k * k) / (k * std::sqrt(2.0 * FOXGLYNN_PI));
106 if (bound <= 0.5 * tol) {
107 l = m - std::floor(k * spread + 1.5);
111 if (l < 0.0) l = 0.0;
113 while (l > 0.0 && foxglynn_chernoff(lambda, l - 1.0) < target) l -= 1.0;
114 while (l < m && foxglynn_chernoff(lambda, l) >= target) l += 1.0;
115 return static_cast<long>(l);
129std::vector<T> foxglynn_poisson(
const T& lambda,
long left,
long right,
double lambdaDouble,
130 bool normalize =
true) {
131 const std::size_t len =
static_cast<std::size_t
>(right - left + 1);
132 std::vector<T> w(len, num_traits<T>::from_int(0));
133 long m =
static_cast<long>(std::floor(lambdaDouble));
134 if (m < left) m = left;
135 if (m > right) m = right;
136 w[
static_cast<std::size_t
>(m - left)] = num_traits<T>::from_int(1);
137 for (
long k = m; k >= left + 1; --k)
138 w[
static_cast<std::size_t
>(k - 1 - left)] =
139 w[
static_cast<std::size_t
>(k - left)] * num_traits<T>::from_int(k) / lambda;
140 for (
long k = m; k <= right - 1; ++k)
141 w[
static_cast<std::size_t
>(k + 1 - left)] =
142 w[
static_cast<std::size_t
>(k - left)] * lambda / num_traits<T>::from_int(k + 1);
144 const double logMode = -lambdaDouble +
static_cast<double>(m) * std::log(lambdaDouble) -
145 std::lgamma(
static_cast<double>(m) + 1.0);
146 const T scale = num_traits<T>::from_double(std::exp(logMode));
147 for (T& v : w) v *= scale;
150 std::vector<T> sorted = w;
151 std::sort(sorted.begin(), sorted.end());
152 T s = num_traits<T>::from_int(0);
153 for (
const T& v : sorted) s += v;
154 if (s == num_traits<T>::from_int(0))
throw NumericError(
"ctmc_foxglynn: Poisson weights vanish");
155 for (T& v : w) v /= s;
173 double tol = 1e-12,
long maxiter = -1) {
175 "ctmc_foxglynn requires transcendental arithmetic: the truncation window is "
176 "defined by a logarithmic Poisson tail bound, and the result is an "
177 "approximation of exp(Qt) controlled by tol rather than an exact quantity");
178 const std::size_t n = Q.
rows();
179 if (Q.
cols() != n)
throw InputError(
"ctmc_foxglynn: generator is not square");
180 if (pi0.size() != n)
throw InputError(
"ctmc_foxglynn: pi0 has the wrong length");
181 if (tol <= 0.0) tol = 1e-12;
185 for (std::size_t i = 0; i < n; ++i) {
186 const T a =
num_abs(T(Q(i, i)));
187 if (a > qmax) qmax = a;
190 const T lambda = q * t;
201 long left = detail::foxglynn_left(lambdaDouble, tol);
202 long right = detail::foxglynn_right(lambdaDouble, tol);
203 if (maxiter > 0 && right > maxiter) {
205 left = std::min(left, right);
211 r.
w = detail::foxglynn_poisson(lambda, left, right, lambdaDouble);
213 const Matrix<T> Qs = detail::uniformized_matrix(Q, q);
215 std::vector<T> P = pi0;
216 for (
long k = 0; k <= right; ++k) {
218 const T& wk = r.
w[
static_cast<std::size_t
>(k - left)];
219 for (std::size_t i = 0; i < n; ++i) r.
pi[i] += wk * P[i];
221 if (k < right) P = detail::vecmat(P, Qs);
NumericError(const std::string &what)
The exception types the port throws.
Dense matrix and non-owning view.
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.
Number-type abstraction for the templated API port.
long right
right truncation point
long left
left truncation point
std::vector< T > pi
distribution at time t
std::vector< T > w
normalized Poisson weights on [left, right]