1function sample = me_sample(ME, n, xs)
2% SAMPLE = ME_SAMPLE(ME, N, XS) - Generate random samples from ME distribution
4% Generate N independent samples from a Matrix Exponential (ME) distribution
5% by numerical inversion of its exact CDF.
8% ME: ME distribution as either:
10% - Process cell array {D0, D1}
11% N: Number of samples to generate (
default: 1)
12% XS: Optional pre-computed grid for CDF evaluation. When supplied,
the
13% grid
is used as given and no horizon extension
is performed, so
the
14% caller
is responsible for covering
the tail.
17% SAMPLE: Column vector of N samples from
the ME distribution
20% Inversion of F(t) = 1 - alpha*expm(A*t)*e, which
is valid for every ME
21% representation. The CTMC walk of map_sample
is not applicable here: it
22% presumes a phase-type reading of
the representation, which fails when
23% alpha has negative entries or A has negative off-diagonal entries.
24% 1. The horizon
is doubled until
the survival function falls below
25% TAILTOL, so
the tabulated range covers all but a negligible mass.
26% A fixed horizon of mean + 10*sigma
is not enough for a heavy tail:
27% on an MMPP with SCV 4.21 it leaves 5e-04 of
the mass untabulated.
28% 2. The CDF
is tabulated on that horizon and forced nondecreasing to
30% 3. Each variate
is located by binary search, then polished by Newton
31% steps on
the exact CDF and density, so
the result
is not limited by
32%
the linear interpolation of
the table.
33% 4. Variates beyond
the last tabulated CDF value are placed by
34% exponential extrapolation using
the dominant (least negative)
35% eigenvalue of D0, which governs
the decay of
the tail. Extrapolating
36% with a unit rate instead, as this function previously did, inflates
37%
the tail:
the same MMPP then samples an SCV of 5.27 against an exact
38% 4.21. Clamping to
the grid endpoint truncates it and biases
the mean
42% Passing a MAP or a RAP samples its stationary marginal independently,
43% which
is a deliberate renewal approximation:
the autocorrelation
is
44% discarded. Use rap_sample to retain it.
47% me = ME([0.3, 0.7], [-2, 1; 0.5, -1.5]);
48% samples = me_sample(me, 10000);
50% % Or use process representation directly
51% ME_proc = {[-2, 1; 0.5, -1.5], [0.4, 0.6; 0.3, 0.7]};
52% samples = me_sample(ME_proc, 10000);
54% Copyright (c) 2012-2026, Imperial College London
57% Handle input arguments
62% Extract process representation
64 ME_proc = ME.getProcess();
68 error('ME must be either an ME
object or a cell array {D0, D1}
');
71TAILTOL = 1e-12; % survival mass left beyond the horizon
72MAXDBL = 40; % cap on horizon doublings
76% Auto-generate grid if not provided
77if nargin < 3 || isempty(xs)
78 mean_val = map_mean(ME_proc);
79 var_val = map_var(ME_proc);
80 std_val = sqrt(max(var_val, 0));
82 horizon = mean_val + 10*std_val;
83 if ~isfinite(horizon) || horizon <= 0
86 % Extend until the tabulated range covers all but TAILTOL of the mass.
88 if (1 - map_cdf(ME_proc, horizon)) < TAILTOL
94 xs = linspace(0, horizon, GRIDPTS);
97% Compute CDF at grid points
98Fxs = map_cdf(ME_proc, xs);
102% Force the tabulated CDF to be nondecreasing (roundoff can break monotonicity
103% near the tail, where consecutive values differ by less than eps).
110% Dominant eigenvalue of D0 sets the decay rate of the tail.
111eta = max(real(eig(full(ME_proc{1}))));
112if ~(eta < 0) || ~isfinite(eta)
113 if isfinite(mean_val) && mean_val > 0
121sEnd = max(1 - Fxs(end), 0);
132 % Exponential tail: S(x) ~ S(xEnd)*exp(eta*(x-xEnd)).
134 if sEnd <= 0 || tailProb <= 0
137 sample(i) = xEnd + log(sEnd/tailProb)/(-eta);
142 % Binary search for the bracketing interval.
146 mid = floor((lo + hi)/2);
154 den = Fxs(lo+1) - Fxs(lo);
156 x = xs(lo) + (u - Fxs(lo))/den*(xs(lo+1) - xs(lo));
161 % Newton polish on the exact CDF and density, kept inside the bracket.
163 fx = map_pdf(ME_proc, x);
168 err = map_cdf(ME_proc, x) - u;
173 if xn <= xs(lo) || xn >= xs(lo+1)
176 if abs(xn - x) <= 1e-14*max(1, abs(x))