LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
ctmc_saddlepoint.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_SADDLEPOINT_H
6#define LINE_API_MC_CTMC_SADDLEPOINT_H
7
8/**
9 * @file
10 * @ingroup api_mc
11 * Saddlepoint approximation of Pr{N(t)=k} for the counting process of a MAP.
12 *
13 * Templated port of matlab/src/api/mc/ctmc_saddlepoint.m. The probability that
14 * the Markovian arrival process (D0,D1) records exactly k events in (0,t],
15 * obtained by steepest-descent inversion of the counting generating function
16 * instead of by forming the k-th superdiagonal block of expm(t*X).
17 *
18 * The counting generating function is the matrix exponential
19 *
20 * sum_k P(k,t) z^k = expm(t*(D0 + z*D1)),
21 *
22 * so the cumulant generating function of N(t) is eta(theta) = spectral abscissa
23 * of A(theta) = D0 + exp(theta)*D1, the Perron root of an irreducible Metzler
24 * matrix: real, simple, strictly convex in theta, with eta(0)=0 and
25 * eta'(0)=lambda. Inverting by steepest descent gives Daniels (1954),
26 *
27 * Pr{N(t)=k} ~ g(theta*) exp(t eta(theta*) - k theta*)
28 * / sqrt(2 pi t eta''(theta*)),
29 *
30 * with the saddle theta* solving eta'(theta*) = k/t and g the amplitude of the
31 * Perron projection, g(theta) = (pi0 v)(u 1), u and v the left and right Perron
32 * vectors normalised by u v = 1.
33 *
34 * THE EXPANSION PARAMETER IS K2 = t*eta''(theta*), THE VARIANCE OF THE COUNT,
35 * not its mean and not t. Measured error laws, with the constants flat to two
36 * digits over Erlang orders 1..8 and horizons 10..160:
37 *
38 * err(daniels) = 0.083 / K2 err(daniels2) = 0.017 / K2^2
39 *
40 * For a renewal Erlang(r) the count variance rate is lambda/r, so
41 * K2 = lambda*t/r and an Erlang-4 at t=50 is as accurate as a Poisson at
42 * t=12.5: low variability shrinks the parameter, it does not break the method.
43 * Below K2 = 5 the expansion is out of its regime and the result carries a
44 * flag saying so.
45 *
46 * ATTRIBUTION. The first-order form is Daniels (1954). The amplitude g and the
47 * whole 'daniels2' bracket are NOT a rederivation: they are Jensen, "Saddlepoint
48 * Expansions for Sums of Markov Dependent Variables on a Continuous State Space",
49 * Probab. Th. Rel. Fields 89, 1991, Eq. (4.4) with the coefficients on p.191. His
50 * gamma_0(s) = (sum_i c_i)(sum_i r_i P(Y_0=i)) is exactly g under his own
51 * normalisation sum_i r_i c_i = 1, and expanding his
52 * alpha_0 + (1/n){-alpha_3/2 + alpha_4/8 - 5*alpha_5/24} reproduces
53 * g*(1 + lam4/8 - 5*lam3^2/24) - g''/(2*K2) + g'*K3/(2*K2^2) term for term; his
54 * Theorem 4.1 gives the O(n^-2) error measured here as 0.017/K2^2. Jensen works
55 * with discrete-n sums over a Markov chain, so the continuous-time MAP counting
56 * process is that result transcribed, n -> t and the kernel eigenvalue -> the
57 * Perron root of D0+exp(theta)*D1.
58 *
59 * GATED ON TRANSCENDENTAL ARITHMETIC (exp, log, sqrt) and, for the Perron root,
60 * on LAPACK through eig_values, as several other api/mam headers already are.
61 *
62 * This is an asymptotic method, not a quadrature: use it for rare-event and
63 * large-deviation coefficients, where k/t is away from lambda or where the
64 * probability underflows. For the bulk of the transient distribution, i.e.
65 * every block k=0..N-1 at once at moderate t, uniformization
66 * (ctmc_uniformization, ctmc_foxglynn) is both exact and faster.
67 */
68
69#include <algorithm>
70#include <cmath>
71#include <complex>
72#include <limits>
73#include <cstddef>
74#include <numeric>
75#include <string>
76#include <vector>
77
78#include "line/num/number.h"
79#include "line/util/eig.h"
80#include "line/util/error.h"
81#include "line/util/expm.h"
83#include "line/util/lu.h"
84#include "line/util/matrix.h"
85
86namespace line {
87namespace mc {
88
89/**
90 * Below this value of K2 = t*eta''(theta*) the expansion is out of its regime.
91 * Do NOT threshold on lambda*t: for Erlang(r) the count variance rate is
92 * lambda/r, so K2 = lambda*t/r, and lambda*t over-warns on Poisson-like
93 * processes while under-warning on low-variability ones.
94 */
95static const double CTMC_SADDLEPOINT_K2_MIN = 5.0;
96
97/** Which term of the steepest-descent expansion to stop at. */
99 SADDLEPOINT_DANIELS2 = 0, ///< second order, error O(1/K2^2) -- the DEFAULT
100 SADDLEPOINT_DANIELS = 1, ///< first order with the Perron amplitude, O(1/K2)
101 SADDLEPOINT_PLAIN = 2 ///< bare first order, amplitude set to 1
102};
103
104namespace detail {
105
106/**
107 * exp/log/isfinite through ADL, the idiom the rest of api/mc uses: `using
108 * std::exp` then an unqualified call, so a Real<D> or Rational picks up its own
109 * overload. num_traits carries no transcendental entry points.
110 */
111template <class T>
112inline T tx_exp(const T& x) {
113 using std::exp;
114 return exp(x);
115}
116
117template <class T>
118inline T tx_log(const T& x) {
119 using std::log;
120 return log(x);
121}
122
123template <class T>
124inline bool tx_finite(const T& x) {
125 const double d = num_traits<T>::to_double(x);
126 return d == d && d != std::numeric_limits<double>::infinity() &&
127 d != -std::numeric_limits<double>::infinity();
128}
129
130} // namespace detail
131
132using detail::tx_exp;
133using detail::tx_log;
134using detail::tx_finite;
135
136/** Perron root of A(theta) with its first two derivatives and the amplitude. */
137template <class T>
143};
144
145/** One entry per (t,k) pair. */
146template <class T>
148 std::vector<T> p; ///< approximation of Pr{N(t)=k}
149 std::vector<T> logp; ///< its natural logarithm, accurate below the floor
150 std::vector<T> theta; ///< the saddle theta*, -inf where k=0
151 std::vector<T> eta; ///< eta(theta*)
152 std::vector<T> deta; ///< eta'(theta*), equal to k/t at convergence
153 std::vector<T> d2eta; ///< eta''(theta*)
154 std::vector<T> ampl; ///< the Perron amplitude g(theta*)
155 std::vector<T> corr; ///< the bracket multiplying the leading term
156 std::vector<T> k2; ///< K2 = t*eta''(theta*), the expansion parameter
157 std::vector<int> iter; ///< Newton steps taken
158 std::vector<bool> exact; ///< true where the value is exact, not approximated
159 T lambda; ///< the stationary event rate eta'(0)
160 /// true when some point fell below CTMC_SADDLEPOINT_K2_MIN
162 T worst_k2; ///< the smallest K2 met
163 double worst_t; ///< horizon at which it was met
164 long worst_k; ///< count at which it was met
165};
166
167namespace detail {
168
169/** Dense solve with partial pivoting, orders K and K+1 only. */
170template <class T>
171inline std::vector<T> saddle_solve(Matrix<T> A, std::vector<T> b) {
172 std::vector<std::size_t> piv = lu_factor(A);
173 lu_solve(A, piv, b);
174 return b;
175}
176
177} // namespace detail
178
179/**
180 * Perron root of A(th) = D0 + exp(th)*D1 with deta, d2eta and the amplitude.
181 *
182 * Only EIGENVALUES are taken from the eigensolver; the Perron vectors come from
183 * bordered solves, the idiom ctmc_solve already uses. That keeps all four
184 * codebases on ONE algorithm: eig.h exposes values only, and the JAR's
185 * commons-math hands back Schur blocks rather than eigenvectors as soon as a
186 * complex pair appears, so neither can supply a left eigenvector.
187 */
188template <class T>
190 const std::vector<T>& pi0, const T& th) {
191 const std::size_t n = D0.rows();
192 const T ex = tx_exp(th);
193 Matrix<T> W(n, n), A(n, n);
194 for (std::size_t i = 0; i < n; ++i)
195 for (std::size_t j = 0; j < n; ++j) {
196 W(i, j) = ex * D1(i, j); // A'(th) = A''(th) = exp(th)*D1
197 A(i, j) = D0(i, j) + W(i, j);
198 }
199
200 Matrix<double> Ad(n, n);
201 for (std::size_t i = 0; i < n; ++i)
202 for (std::size_t j = 0; j < n; ++j) Ad(i, j) = num_traits<T>::to_double(A(i, j));
203 const std::vector<std::complex<double> > ev = eig_values(Ad);
204 double etad = -std::numeric_limits<double>::infinity();
205 for (std::size_t i = 0; i < ev.size(); ++i)
206 if (ev[i].real() > etad) etad = ev[i].real();
207 const T eta = num_traits<T>::from_double(etad);
208
209 Matrix<T> Ashift(n, n);
210 for (std::size_t i = 0; i < n; ++i)
211 for (std::size_t j = 0; j < n; ++j)
212 Ashift(i, j) = A(i, j) - (i == j ? eta : num_traits<T>::from_int(0));
213
214 std::vector<T> rhs(n, num_traits<T>::from_int(0));
215 rhs[n - 1] = num_traits<T>::from_int(1);
216 // (A-eta*I)v = 0 with the last row replaced by sum(v)=1. A row may be
217 // dropped because A-eta*I is a singular irreducible M-matrix, every proper
218 // principal submatrix of which is nonsingular.
219 Matrix<T> M = Ashift;
220 for (std::size_t j = 0; j < n; ++j) M(n - 1, j) = num_traits<T>::from_int(1);
221 const std::vector<T> v = detail::saddle_solve(M, rhs);
222 // u(A-eta*I) = 0 by the same construction on the transpose
223 Matrix<T> Mt(n, n);
224 for (std::size_t i = 0; i < n; ++i)
225 for (std::size_t j = 0; j < n; ++j)
226 Mt(i, j) = (i == n - 1) ? num_traits<T>::from_int(1) : Ashift(j, i);
227 std::vector<T> u = detail::saddle_solve(Mt, rhs);
228 T uv = num_traits<T>::from_int(0);
229 for (std::size_t i = 0; i < n; ++i) uv += u[i] * v[i];
230 for (std::size_t i = 0; i < n; ++i) u[i] /= uv; // u v = 1 fixes the scale
231
233 st.eta = eta;
235 for (std::size_t i = 0; i < n; ++i) {
236 T inner = num_traits<T>::from_int(0);
237 for (std::size_t j = 0; j < n; ++j) inner += W(i, j) * v[j];
238 st.deta += u[i] * inner;
239 }
240
241 // First-order eigenvector perturbation (A-eta*I)v' = (eta'*I-W)v taken with
242 // u v' = 0; the bordered system is nonsingular because the Perron root of an
243 // irreducible Metzler matrix is simple.
244 Matrix<T> B(n + 1, n + 1, num_traits<T>::from_int(0));
245 std::vector<T> r(n + 1, num_traits<T>::from_int(0));
246 for (std::size_t i = 0; i < n; ++i) {
247 for (std::size_t j = 0; j < n; ++j) B(i, j) = Ashift(i, j);
248 B(i, n) = v[i];
249 B(n, i) = u[i];
250 T acc = st.deta * v[i];
251 for (std::size_t j = 0; j < n; ++j) acc -= W(i, j) * v[j];
252 r[i] = acc;
253 }
254 const std::vector<T> sol = detail::saddle_solve(B, r);
255 T corr2 = num_traits<T>::from_int(0);
256 for (std::size_t i = 0; i < n; ++i) {
257 T inner = num_traits<T>::from_int(0);
258 for (std::size_t j = 0; j < n; ++j) inner += W(i, j) * sol[j];
259 corr2 += u[i] * inner;
260 }
261 st.d2eta = st.deta + num_traits<T>::from_int(2) * corr2;
262
264 for (std::size_t i = 0; i < n; ++i) {
265 pv += pi0[i] * v[i];
266 u1 += u[i];
267 }
268 st.ampl = pv * u1;
269 return st;
270}
271
272namespace detail {
273
274/**
275 * Saddle of the counting cumulant generating function at rate r, the root of
276 * eta'(th) = r. eta' is continuous and strictly increasing from 0 to +inf, so
277 * the root exists and is unique for every r>0; it is bracketed by geometric
278 * expansion from th0 and refined by Newton on log(eta'), safeguarded by
279 * bisection.
280 */
281template <class T>
282inline T saddle_root(const Matrix<T>& D0, const Matrix<T>& D1, const std::vector<T>& pi0,
283 const T& r, const T& th0, const T& thmin, const T& thmax, int& iters) {
284 const T TOL = num_traits<T>::from_double(1e-13);
285 const int MAXIT = 200;
286 T th = std::min(std::max(th0, thmin), thmax);
287 T d1 = ctmc_saddlepoint_perron(D0, D1, pi0, th).deta;
288 T lo = th, hi = th, dlo = d1, dhi = d1;
289 T step = num_traits<T>::from_int(1);
290 while (dlo > r) {
291 hi = lo;
292 dhi = dlo;
293 lo = lo - step;
294 if (lo <= thmin) {
295 lo = thmin;
296 dlo = ctmc_saddlepoint_perron(D0, D1, pi0, lo).deta;
297 if (dlo > r)
298 throw InputError("ctmc_saddlepoint: the rate k/t is below the representable "
299 "range of eta'");
300 break;
301 }
302 dlo = ctmc_saddlepoint_perron(D0, D1, pi0, lo).deta;
303 step += step;
304 }
305 step = num_traits<T>::from_int(1);
306 while (dhi < r) {
307 lo = hi;
308 dlo = dhi;
309 hi = hi + step;
310 if (hi >= thmax) {
311 hi = thmax;
312 dhi = ctmc_saddlepoint_perron(D0, D1, pi0, hi).deta;
313 if (dhi < r)
314 throw InputError("ctmc_saddlepoint: the rate k/t is above the representable "
315 "range of eta'");
316 break;
317 }
318 dhi = ctmc_saddlepoint_perron(D0, D1, pi0, hi).deta;
319 step += step;
320 }
321 th = std::min(std::max(th, lo), hi);
322 const T logr = tx_log(r);
323 iters = 0;
324 for (int it = 1; it <= MAXIT; ++it) {
325 iters = it;
326 const PerronState<T> si = ctmc_saddlepoint_perron(D0, D1, pi0, th);
327 const T f = tx_log(si.deta) - logr;
328 if (num_abs(f) <= TOL) break;
329 if (f > num_traits<T>::from_int(0))
330 hi = th;
331 else
332 lo = th;
333 T thn = th - f * si.deta / si.d2eta;
334 if (!tx_finite(thn) || thn <= lo || thn >= hi)
335 thn = (lo + hi) / num_traits<T>::from_int(2);
336 if (num_abs(thn - th) <=
337 TOL * std::max(num_traits<T>::from_int(1), num_abs(th))) {
338 th = thn;
339 break;
340 }
341 th = thn;
342 }
343 return th;
344}
345
346} // namespace detail
347
348/**
349 * Pr{N(t)=k} over arrays of horizons and counts.
350 *
351 * @param D0 generator of the phase process with the counted transitions removed
352 * @param D1 rates of the counted transitions; D0+D1 must be an irreducible generator
353 * @param t time horizons; length 1 broadcasts against k
354 * @param k event counts; length 1 broadcasts against t
355 * @param method SADDLEPOINT_DANIELS2 (the default), SADDLEPOINT_DANIELS or SADDLEPOINT_PLAIN
356 * @param pi0 initial phase distribution; empty selects the stationary distribution of D0+D1
357 */
358template <class T>
360 const std::vector<T>& t, const std::vector<long>& k,
362 const std::vector<T>& pi0 = std::vector<T>()) {
364 "ctmc_saddlepoint is unavailable over the rational field: the saddlepoint "
365 "involves exp, log and sqrt of the Perron root, none a rational function "
366 "of the rates");
367 const std::size_t nph = D0.rows();
368 if (D0.cols() != nph || D1.rows() != nph || D1.cols() != nph)
369 throw InputError("ctmc_saddlepoint: D0 and D1 must be square matrices of the same order");
370 const T zero = num_traits<T>::from_int(0);
371 T maxrate = zero, maxq = zero;
372 for (std::size_t i = 0; i < nph; ++i)
373 for (std::size_t j = 0; j < nph; ++j) {
374 if (D1(i, j) < zero) throw InputError("ctmc_saddlepoint: D1 must be nonnegative");
375 maxrate = std::max(maxrate, D1(i, j));
376 maxq = std::max(maxq, num_abs(D0(i, j) + D1(i, j)));
377 }
378 if (!(maxrate > zero))
379 throw InputError("ctmc_saddlepoint: D1 has no counted transitions, the counting process "
380 "is identically zero");
381 Matrix<T> Q(nph, nph);
382 for (std::size_t i = 0; i < nph; ++i) {
383 T rowsum = zero;
384 for (std::size_t j = 0; j < nph; ++j) {
385 Q(i, j) = D0(i, j) + D1(i, j);
386 rowsum += Q(i, j);
387 }
388 if (num_abs(rowsum) >
389 num_traits<T>::from_double(1e-8) * std::max(num_traits<T>::from_int(1), maxq))
390 throw InputError("ctmc_saddlepoint: D0+D1 must be an infinitesimal generator "
391 "(zero row sums)");
392 }
393
394 std::vector<T> pi = pi0;
395 if (pi.empty()) pi = ctmc_solve(Q);
396 if (pi.size() != nph)
397 throw InputError("ctmc_saddlepoint: pi0 must have one entry per phase");
398 T pisum = zero;
399 for (std::size_t i = 0; i < nph; ++i) pisum += pi[i];
401 throw InputError("ctmc_saddlepoint: pi0 must sum to one");
402
403 // Broadcast the horizons against the counts
404 const std::size_t n = std::max(t.size(), k.size());
405 if ((t.size() != n && t.size() != 1) || (k.size() != n && k.size() != 1))
406 throw InputError("ctmc_saddlepoint: t and k must be scalars or arrays of the same size");
407 std::vector<T> tv(n);
408 std::vector<long> kv(n);
409 for (std::size_t i = 0; i < n; ++i) {
410 tv[i] = t.size() == 1 ? t[0] : t[i];
411 kv[i] = k.size() == 1 ? k[0] : k[i];
412 if (tv[i] < zero) throw InputError("ctmc_saddlepoint: the horizon t must be nonnegative");
413 if (kv[i] < 0) throw InputError("ctmc_saddlepoint: the count k must be nonnegative");
414 }
415
417 const T ninf = num_traits<T>::from_double(-std::numeric_limits<double>::infinity());
418 const T nan = num_traits<T>::from_double(std::numeric_limits<double>::quiet_NaN());
419 res.p.assign(n, zero);
420 res.logp.assign(n, ninf);
421 res.theta.assign(n, ninf);
422 res.eta.assign(n, nan);
423 res.deta.assign(n, nan);
424 res.d2eta.assign(n, nan);
425 res.ampl.assign(n, nan);
426 res.corr.assign(n, nan);
427 res.k2.assign(n, nan);
428 res.iter.assign(n, 0);
429 res.exact.assign(n, false);
430 res.lambda = ctmc_saddlepoint_perron(D0, D1, pi, zero).deta;
431 res.out_of_regime = false;
432 res.worst_k2 = num_traits<T>::from_double(std::numeric_limits<double>::infinity());
433 res.worst_t = 0.0;
434 res.worst_k = 0;
435
436 // exp(theta) multiplies D1, so the saddle is confined to the range over
437 // which A(theta) is representable; never active for a feasible k/t
438 const T thmax = num_traits<T>::from_double(std::log(std::numeric_limits<double>::max() / 1e6)) -
439 tx_log(maxrate);
440 const T thmin = num_traits<T>::from_double(std::log(std::numeric_limits<double>::min() * 1e6)) -
441 tx_log(maxrate);
442
443 // Sorting by the rate k/t lets each Newton solve warm-start from the
444 // previous saddle, the saddle being a monotone function of that rate alone
445 std::vector<std::size_t> ord(n);
446 for (std::size_t i = 0; i < n; ++i) ord[i] = i;
447 std::vector<double> rate(n, 0.0);
448 for (std::size_t i = 0; i < n; ++i)
449 if (tv[i] > zero) rate[i] = double(kv[i]) / num_traits<T>::to_double(tv[i]);
450 std::stable_sort(ord.begin(), ord.end(),
451 [&rate](std::size_t a, std::size_t b) { return rate[a] < rate[b]; });
452
453 T thprev = zero;
454 for (std::size_t idx = 0; idx < n; ++idx) {
455 const std::size_t i = ord[idx];
456 const T ti = tv[i];
457 const long ki = kv[i];
458 if (ti == zero) {
459 // No time has elapsed, so the count is zero with probability one
460 res.exact[i] = true;
461 if (ki == 0) {
462 res.p[i] = num_traits<T>::from_int(1);
463 res.logp[i] = zero;
464 }
465 continue;
466 }
467 if (ki == 0) {
468 // The saddle runs off to -inf; the exact value is one matrix
469 // exponential of the taboo generator and costs no more than a step
470 // of the approximation itself
471 res.exact[i] = true;
472 const Matrix<T> E = expm(D0, ti);
473 T acc = zero;
474 for (std::size_t a = 0; a < nph; ++a)
475 for (std::size_t b = 0; b < nph; ++b) acc += pi[a] * E(a, b);
476 res.p[i] = acc;
477 res.logp[i] = acc > zero ? tx_log(acc) : ninf;
478 continue;
479 }
480
481 int iters = 0;
482 const T th = detail::saddle_root(D0, D1, pi, num_traits<T>::from_double(rate[i]), thprev,
483 thmin, thmax, iters);
484 thprev = th;
485 res.theta[i] = th;
486 res.iter[i] = iters;
487
488 const PerronState<T> s = ctmc_saddlepoint_perron(D0, D1, pi, th);
489 res.eta[i] = s.eta;
490 res.deta[i] = s.deta;
491 res.d2eta[i] = s.d2eta;
492
493 const T K2 = ti * s.d2eta;
494 res.k2[i] = K2;
495 if (K2 < res.worst_k2) {
496 res.worst_k2 = K2;
498 res.worst_k = ki;
499 }
500 if (!(K2 > zero))
501 throw NumericError("ctmc_saddlepoint: the cumulant generating function is not "
502 "strictly convex at the saddle; D0+D1 is probably reducible");
503 const T kiT = num_traits<T>::from_double(double(ki));
504 const T base = ti * s.eta - kiT * th -
506 tx_log(num_traits<T>::from_double(2.0 * 3.14159265358979323846) * K2);
507 const T ampl = (method == SADDLEPOINT_PLAIN) ? num_traits<T>::from_int(1) : s.ampl;
508 res.ampl[i] = s.ampl;
509
510 T corr;
511 if (method != SADDLEPOINT_DANIELS2) {
512 corr = ampl;
513 } else {
514 // The higher cumulants and the derivatives of the amplitude come
515 // from central differences of the analytic eta'' and g, both of
516 // which carry full precision at each evaluation point
517 const T h = num_traits<T>::from_double(1e-3) *
518 std::max(num_traits<T>::from_int(1), num_abs(th));
519 const PerronState<T> sp = ctmc_saddlepoint_perron(D0, D1, pi, th + h);
520 const PerronState<T> sm = ctmc_saddlepoint_perron(D0, D1, pi, th - h);
521 const T two = num_traits<T>::from_int(2);
522 const T d3 = (sp.d2eta - sm.d2eta) / (two * h);
523 const T d4 = (sp.d2eta - two * s.d2eta + sm.d2eta) / (h * h);
524 const T K3 = ti * d3, K4 = ti * d4;
525 const T lam3sq = K3 * K3 / (K2 * K2 * K2);
526 const T lam4 = K4 / (K2 * K2);
527 const T gp = (sp.ampl - sm.ampl) / (two * h);
528 const T gpp = (sp.ampl - two * s.ampl + sm.ampl) / (h * h);
529 // Steepest descent to O(1/K2), Jensen (1991) Eq. (4.4): the Daniels
530 // bracket on the amplitude, plus the two terms the amplitude
531 // contributes through its own curvature along the contour
532 corr = ampl * (num_traits<T>::from_int(1) + lam4 / num_traits<T>::from_int(8) -
534 gpp / (two * K2) + gp * K3 / (two * K2 * K2);
535 if (corr <= zero) corr = ampl; // expansion broken down, fall back
536 }
537 res.corr[i] = corr;
538 res.logp[i] = base + tx_log(corr);
539 res.p[i] = tx_exp(res.logp[i]);
540 }
541
542 res.out_of_regime =
544 return res;
545}
546
547/** Pr{N(t)=k} at a single (t,k), with the default method. */
548template <class T>
549inline T ctmc_saddlepoint(const Matrix<T>& D0, const Matrix<T>& D1, const T& t, long k,
551 return ctmc_saddlepoint(D0, D1, std::vector<T>(1, t), std::vector<long>(1, k), method).p[0];
552}
553
554} // namespace mc
555} // namespace line
556
557#endif // LINE_API_MC_CTMC_SADDLEPOINT_H
InputError(const std::string &what)
Definition error.h:39
std::size_t cols() const
Definition matrix.h:90
std::size_t rows() const
Definition matrix.h:89
NumericError(const std::string &what)
Definition error.h:45
Steady-state distribution of a continuous-time Markov chain.
Eigenvalues and singular values, backed by LAPACK.
The exception types the port throws.
Matrix exponential by scaling and squaring with a diagonal Pade approximant.
LU factorization with partial pivoting, templated on the number type.
Dense matrix and non-owning view.
T tx_log(const T &x)
bool tx_finite(const T &x)
SaddlepointMethod
Which term of the steepest-descent expansion to stop at.
@ SADDLEPOINT_DANIELS2
second order, error O(1/K2^2) – the DEFAULT
@ SADDLEPOINT_PLAIN
bare first order, amplitude set to 1
@ SADDLEPOINT_DANIELS
first order with the Perron amplitude, O(1/K2)
std::vector< T > ctmc_solve(const Matrix< T > &Qin)
Steady-state distribution of a continuous-time Markov chain.
Definition ctmc_solve.h:122
T tx_exp(const T &x)
exp/log/isfinite through ADL, the idiom the rest of api/mc uses: using std::exp then an unqualified c...
SaddlepointResult< T > ctmc_saddlepoint(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &t, const std::vector< long > &k, SaddlepointMethod method=SADDLEPOINT_DANIELS2, const std::vector< T > &pi0=std::vector< T >())
Pr{N(t)=k} over arrays of horizons and counts.
static const double CTMC_SADDLEPOINT_K2_MIN
Below this value of K2 = t*eta''(theta*) the expansion is out of its regime.
PerronState< T > ctmc_saddlepoint_perron(const Matrix< T > &D0, const Matrix< T > &D1, const std::vector< T > &pi0, const T &th)
Perron root of A(th) = D0 + exp(th)*D1 with deta, d2eta and the amplitude.
void lu_solve(const Matrix< T > &LU, const std::vector< std::size_t > &piv, std::vector< T > &b)
Solve LUx = Pb in place on b, using the factors from lu_factor.
Definition lu.h:94
T num_abs(const T &v)
Definition number.h:172
std::vector< std::size_t > lu_factor(Matrix< T > &A)
In-place LU of A (n x n).
Definition lu.h:48
std::vector< std::complex< double > > eig_values(const Matrix< double > &A)
Eigenvalues of a general real square matrix, in LAPACK's order.
Definition eig.h:59
Matrix< T > expm(const Matrix< T > &A)
Matrix exponential exp(A).
Definition expm.h:141
Number-type abstraction for the templated API port.
Perron root of A(theta) with its first two derivatives and the amplitude.
One entry per (t,k) pair.
double worst_t
horizon at which it was met
std::vector< T > eta
eta(theta*)
std::vector< T > p
approximation of Pr{N(t)=k}
std::vector< bool > exact
true where the value is exact, not approximated
std::vector< T > k2
K2 = t*eta''(theta*), the expansion parameter.
std::vector< int > iter
Newton steps taken.
std::vector< T > theta
the saddle theta*, -inf where k=0
bool out_of_regime
true when some point fell below CTMC_SADDLEPOINT_K2_MIN
T lambda
the stationary event rate eta'(0)
std::vector< T > ampl
the Perron amplitude g(theta*)
std::vector< T > deta
eta'(theta*), equal to k/t at convergence
T worst_k2
the smallest K2 met
std::vector< T > d2eta
eta''(theta*)
std::vector< T > logp
its natural logarithm, accurate below the floor
std::vector< T > corr
the bracket multiplying the leading term
long worst_k
count at which it was met