1function [pi,left,right,w]=ctmc_foxglynn(pi0,Q,t,tol,maxiter)
2% [PI,LEFT,RIGHT,W]=CTMC_FOXGLYNN(PI0,Q,T,TOL,MAXITER)
4% Transient distribution of
the CTMC by uniformization, with Poisson weights
5% and truncation points obtained by
the Fox-Glynn algorithm, with Jansen
's
6% correction to the right tail estimate. Neither exp(-q*t) nor (q*t)^k/k! is
7% ever formed, so the method is free of overflow and underflow and needs no
8% horizon splitting. MAXITER caps the right truncation point; pass a
9% nonpositive value (or omit it) to leave it uncapped.
11% Copyright (c) 2012-2026, Imperial College London
13if nargin<4%~exist('tol
','var
')
16if nargin<5%~exist('maxiter
','var
')
22q=1.1*max(abs(diag(Q)));
31[left,right,w] = ctmc_foxglynn_weights(lambda,tol,maxiter);
32Qs=speye(size(Q))+sparse(Q)/q;
37 pi = pi + w(k-left+1)*P;
45function [left,right,w] = ctmc_foxglynn_weights(lambda,tol,maxiter)
46% Fox-Glynn truncation window and normalized Poisson weights for a
47% Poisson(lambda) mixing distribution at tail-mass tolerance TOL.
48left = ctmc_foxglynn_left(lambda,tol);
49right = ctmc_foxglynn_right(lambda,tol);
50if maxiter>0 && right>maxiter
52 left = min(left,right);
54w = ctmc_foxglynn_poisson(lambda,left,right);
57function e = ctmc_foxglynn_chernoff(lambda,k)
58% Chernoff exponent lambda*h(k/lambda) with h(u)=u*log(u)-u+1, so that exp(-e)
59% dominates P{X>=k} for k>lambda and P{X<=k} for k<lambda under
60% X~Poisson(lambda). It certifies the Fox-Glynn estimates below, which are
61% asymptotic and valid only for lambda>=25.
65 e = lambda - k + k*log(k/lambda);
69function r = ctmc_foxglynn_right(lambda,tol)
70% Right truncation point R with P{X>R}<=tol/2. The starting guess is the
71% Fox-Glynn (1988) right tail estimate: with m=floor(lambda) and shift
72% s(k)=k*sqrt(2*lambda)+3/2, the tail P{X>=m+ceil(s(k))} is bounded by
73% a*d*exp(-k^2/2)/(k*sqrt(2*pi)) with a=(1+1/lambda)*exp(1/16)*sqrt(2).
74% Jansen's correction (2011), applied here as
the factor
75% d=1/(1-exp(-(2/9)*s(k))), repairs
the original statement, which drops
this
76%
factor for the k-dependent shift and
is therefore optimistic at moderate
77% lambda; d tends to one as lambda grows, so
the corrected bound agrees with
78% Fox and Glynn
's asymptotically. The guess is then tightened and, if needed,
79% grown until the Chernoff bound holds, so R is certified in any regime.
84 a = (1+1/lambda)*exp(1/16)*sqrt(2);
85 spread = sqrt(2*lambda);
87 shift = k*spread + 1.5;
88 d = 1/(1-exp(-(2/9)*shift));
89 bound = a*d*exp(-0.5*k^2)/(k*sqrt(2*pi));
96while r>m && ctmc_foxglynn_chernoff(lambda,r)>=target
99while ctmc_foxglynn_chernoff(lambda,r+1)<target
104function l = ctmc_foxglynn_left(lambda,tol)
105% Left truncation point L with P{X<L}<=tol/2, zero when no truncation is
106% admissible. The starting guess is the Fox-Glynn (1988) left tail estimate
107% with b=(1+1/lambda)*exp(1/(8*lambda)) and shift k*sqrt(lambda)+3/2 below the
108% mode. Unlike the right tail this one needs no Jansen factor, the left tail of
109% a Poisson being lighter than its normal approximation.
112if ctmc_foxglynn_chernoff(lambda,0) < target
118 b = (1+1/lambda)*exp(1/(8*lambda));
119 spread = sqrt(lambda);
121 bound = b*exp(-0.5*k^2)/(k*sqrt(2*pi));
123 l = m - floor(k*spread + 1.5);
129while l>0 && ctmc_foxglynn_chernoff(lambda,l-1) < target
132while l<m && ctmc_foxglynn_chernoff(lambda,l) >= target
137function w = ctmc_foxglynn_poisson(lambda,left,right)
138% Poisson(lambda) weights on [left,right], normalized to sum to one. Following
139% Fox-Glynn they are built by the two-sided recursion w(k-1)=w(k)*k/lambda and
140% w(k+1)=w(k)*lambda/(k+1) anchored at the mode, so neither exp(-lambda) nor
141% lambda^k/k! is ever evaluated and the overflow and underflow that limit the
142% direct series cannot occur. Anchoring at w(mode)=1 keeps the extreme weights
143% near tol, far above the denormal threshold, making Fox and Glynn's rescaling
144% of
the mode weight unnecessary. The normalizing sum
is accumulated in
145% increasing order of magnitude.
148m = min(max(floor(lambda),left),right);
151 w(k-1-left+1) = w(k-left+1)*k/lambda;
154 w(k+1-left+1) = w(k-left+1)*lambda/(k+1);