5#ifndef LINE_API_MC_CTMC_FAU_H
6#define LINE_API_MC_CTMC_FAU_H
96inline double fau_tailbound(
double lstar,
double t,
long k) {
97 const double lambda = lstar * t;
98 const double kd =
static_cast<double>(k);
99 if (lambda <= 0.0 || kd <= lambda)
return 1.0;
100 return std::exp(-(lambda - kd + kd * std::log(kd / lambda)));
105std::vector<std::size_t> fau_support(
const std::vector<T>& u) {
106 std::vector<std::size_t> act;
107 for (std::size_t i = 0; i < u.size(); ++i)
113inline double fau_max_exit(
const std::vector<double>& d,
const std::vector<std::size_t>& act) {
115 for (std::size_t k = 0; k < act.size(); ++k)
116 if (d[act[k]] > L) L = d[act[k]];
128std::vector<std::size_t> fau_step(std::vector<T>& u,
const std::vector<std::size_t>& act,
129 const Matrix<T>& Q,
const T& L,
const T& delta,
130 std::vector<T>& scratch, std::vector<char>& touchedFlag,
132 const T zero = num_traits<T>::from_int(0);
133 const std::size_t n = u.size();
134 std::vector<std::size_t> touched;
135 for (std::size_t k = 0; k < act.size(); ++k) {
136 const std::size_t i = act[k];
138 for (std::size_t j = 0; j < n; ++j) {
139 const T& qij = Q(i, j);
140 if (qij == zero)
continue;
141 if (!touchedFlag[j]) {
143 touched.push_back(j);
145 scratch[j] += ui * qij;
148 if (touched.empty())
return act;
149 std::sort(touched.begin(), touched.end());
151 std::vector<std::size_t> written;
152 written.reserve(touched.size());
153 for (std::size_t k = 0; k < touched.size(); ++k) {
154 const std::size_t j = touched[k];
155 const T contrib = scratch[j];
160 if (contrib == zero)
continue;
161 T v = u[j] + contrib / L;
163 if (dropped !=
nullptr && v > zero) *dropped += v;
167 if (v > zero) written.push_back(j);
169 std::vector<std::size_t> survivors;
170 survivors.reserve(act.size());
171 for (std::size_t k = 0; k < act.size(); ++k)
172 if (u[act[k]] > zero) survivors.push_back(act[k]);
174 std::vector<std::size_t> out;
175 out.reserve(survivors.size() + written.size());
176 std::set_union(survivors.begin(), survivors.end(), written.begin(), written.end(),
177 std::back_inserter(out));
191void fau_weights(
const std::vector<double>& lambda,
const T& t,
double tDouble,
double tol,
192 std::vector<T>& b, T& tail, T& window) {
193 const T zero = num_traits<T>::from_int(0);
194 const std::size_t k1 = lambda.size();
200 for (std::size_t m = 0; m < k1; ++m) lstar = std::max(lstar, lambda[m]);
201 if (lstar <= 0.0 || tDouble <= 0.0) {
202 b[0] = num_traits<T>::from_int(1);
205 const double lambdaDouble = lstar * tDouble;
206 const long left = detail::foxglynn_left(lambdaDouble, tol);
207 const long right = detail::foxglynn_right(lambdaDouble, tol);
208 const T lstarT = num_traits<T>::from_double(lstar);
209 const std::vector<T> w =
210 detail::foxglynn_poisson(lstarT * t, left, right, lambdaDouble,
false);
213 for (std::size_t i = 0; i < w.size(); ++i) wsum += w[i];
214 window = (num_traits<T>::from_int(1) > wsum) ? num_traits<T>::from_int(1) - wsum : zero;
216 std::vector<T> v(k1 + 1, zero);
217 std::vector<T> acc(k1 + 1, zero);
218 v[0] = num_traits<T>::from_int(1);
219 std::vector<T> c(k1, zero);
220 std::vector<T> a(k1, zero);
221 for (std::size_t m = 0; m < k1; ++m) {
222 c[m] = num_traits<T>::from_double(lambda[m]) / lstarT;
223 a[m] = num_traits<T>::from_int(1) - c[m];
225 for (
long k = 0; k <= right; ++k) {
227 const T& wk = w[
static_cast<std::size_t
>(k - left)];
228 for (std::size_t m = 0; m <= k1; ++m) acc[m] += wk * v[m];
231 for (std::size_t m = k1; m >= 1; --m) {
232 const T forward = v[m - 1] * c[m - 1];
233 const T stay = (m < k1) ? v[m] * a[m] : v[m];
234 v[m] = stay + forward;
239 for (std::size_t m = 0; m < k1; ++m) b[m] = acc[m];
257 double epsilon = 1e-6,
double delta = 1e-12,
long maxsteps = -1) {
259 const std::size_t n = Q.
rows();
260 if (Q.
cols() != n)
throw InputError(
"ctmc_fau: Q must be square");
261 if (pi0.size() != n)
throw InputError(
"ctmc_fau: pi0 and Q have inconsistent sizes");
263 if (tDouble < 0.0)
throw InputError(
"ctmc_fau: t must be nonnegative");
264 const double eps = (epsilon > 0.0) ? epsilon : 1e-6;
274 std::vector<double> d(n, 0.0);
275 std::vector<T> exitRate(n, zero);
276 for (std::size_t i = 0; i < n; ++i) {
277 exitRate[i] = zero - Q(i, i);
281 if (tDouble == 0.0 || n == 0) {
284 r.
supportMax = detail::fau_support(pi0).size();
290 std::vector<double> lambda;
292 std::vector<T> u = pi0;
293 std::vector<T> scratch(n, zero);
294 std::vector<char> touchedFlag(n, 0);
295 std::vector<std::size_t> act = detail::fau_support(u);
302 const double L = detail::fau_max_exit(d, act);
310 lstar = std::max(lstar, L);
311 if (detail::fau_tailbound(lstar, tDouble,
static_cast<long>(lambda.size())) <= eps)
break;
312 if (
static_cast<long>(lambda.size()) >= cap) {
317 touchedFlag,
static_cast<T*
>(
nullptr));
327 std::vector<T> u = pi0;
328 std::vector<T> scratch(n, zero);
329 std::vector<char> touchedFlag(n, 0);
330 std::vector<std::size_t> act = detail::fau_support(u);
331 r.
pit.assign(n, zero);
334 for (std::size_t m = 0; m < lambda.size(); ++m) {
335 if (act.empty())
break;
338 for (std::size_t k = 0; k < act.size(); ++k) r.
pit[act[k]] += b[m] * u[act[k]];
339 if (m + 1 < lambda.size()) {
340 const double L = detail::fau_max_exit(d, act);
348 r.
steps =
static_cast<long>(lambda.size());
349 if (!lambda.empty()) {
350 r.
lambdaMin = *std::min_element(lambda.begin(), lambda.end());
351 r.
lambdaMax = *std::max_element(lambda.begin(), lambda.end());
355 for (std::size_t i = 0; i < n; ++i) {
Transient distribution of a CTMC by uniformization with Fox-Glynn Poisson weights.
The exception types the port throws.
Dense matrix and non-owning view.
constexpr long FAU_MAX_STEPS
Default cap on birth steps, so a pathological horizon reports truncation.
FauResult< T > ctmc_fau(const std::vector< T > &pi0, const Matrix< T > &Q, const T &t, double epsilon=1e-6, double delta=1e-12, long maxsteps=-1)
Transient distribution of a CTMC by fast adaptive uniformization.
Number-type abstraction for the templated API port.
T weightTail
mass reaching the overflow index, i.e. P{N(t) > K}
T droppedMass
probability removed by the occupancy threshold
T weightWindow
Poisson mass outside the Fox-Glynn window.
bool absorbed
the support emptied or became absorbing
double lambdaMin
smallest adaptive rate used
bool truncated
maxsteps stopped the sweep
std::size_t supportFinal
support at the last step
T errorBound
sum(pi0) - sum(pit), which IS the L1 error
double uniformRate
max_i |q_ii|, the rate ordinary uniformization would use
std::size_t supportMax
largest occupied support over the sweep
long steps
number of birth steps K+1 actually taken
std::vector< T > pit
defective distribution at t, a lower bound on pi(t)
double lambdaMax
largest adaptive rate used, the Lstar of the weights