1function sample = rap_sample(RAP, n)
2% SAMPLE = RAP_SAMPLE(RAP, N) - Generate a correlated sample path from a RAP
4% Generate N successive inter-
event times along one sample path of a
5% Rational Arrival Process. A RAP has no underlying Markov chain over
the
6% phases to walk, so
the sampler carries
the conditional phase vector V
7% across events instead of a phase index.
10% RAP: RAP distribution as either:
12% - Process cell array {H0, H1}
13% N: Number of samples to generate (
default: 1)
16% SAMPLE: Column vector of N inter-event times along a single sample path
19% Let V be a row vector with V*e = 1, initialized to
the arrival-embedded
20% equilibrium vector map_pie. For each draw:
21% 1. Draw U ~ Uniform(0,1) and solve
the conditional survival equation
22% S(X) = V*expm(H0*X)*e = 1-U for X, by bracket expansion followed by
23% safeguarded Newton iteration. S
is monotone decreasing from 1 to 0,
24% and its derivative
is S
'(X) = V*expm(H0*X)*H0*e, so the conditional
25% density is -S'(X) = V*expm(H0*X)*H1*e.
26% 2. Update V <- V*expm(H0*X)*H1 / (V*expm(H0*X)*H1*e).
28% Step 2
is what reproduces
the autocorrelation of
the process. Sampling
29% from
the ME marginal alone (me_sample) yields independent inter-event
30% times with
the correct marginal but zero autocorrelation, and
the MAP
31% CTMC walk (map_sample)
is invalid here because a general RAP has
32% negative off-diagonal entries, for which
the walk has no probabilistic
33% interpretation. A MAP
is the special case in which H0 and H1 are
34% nonnegative, and on that input this sampler agrees with map_sample.
37% rap = RAP([-2, 1; 0.5, -1.5], [0.5, 0.5; 0.5, 0.5]);
38% samples = rap_sample(rap, 10000);
40% % Or use process representation directly
41% RAP_proc = {[-2, 1; 0.5, -1.5], [0.5, 0.5; 0.5, 0.5]};
42% samples = rap_sample(RAP_proc, 10000);
44% Copyright (c) 2012-2026, Imperial College London
51% Extract process representation
53 RAP_proc = RAP.getProcess();
57 error('RAP must be either a RAP
object or a cell array {H0, H1}
');
65% Conditional phase vector, normalized so that v*e = 1. The arrival-embedded
66% equilibrium vector is the stationary choice for the first inter-event time.
70% Scale for the initial bracket and for the Newton safeguard.
71meanval = map_mean(RAP_proc);
72if ~isfinite(meanval) || meanval <= 0
78 target = 1 - rand(); % target survival level in (0,1]
80 x = invert_survival(v, H0, e, target, meanval);
83 % Advance the conditional vector across the event.
84 vnext = v * expm(H0 * x) * H1;
86 if mass <= 0 || ~isfinite(mass)
87 % The conditional vector has lost its normalization to roundoff,
88 % which can only happen at survival levels far into the tail. Restart
89 % from the embedded equilibrium rather than propagate a meaningless
91 v = map_pie(RAP_proc);
100function x = invert_survival(v, H0, e, target, scale)
101% Solve v*expm(H0*x)*e = target for x, with target in (0,1].
103surv = @(t) v * expm(H0 * t) * e;
105% Bracket the root: survival is 1 at t = 0 and decreases to 0.
110while surv(hi) > target && k < smax
120% Bisection to a tight bracket, then Newton polish. Bisection alone is used
121% first because the conditional density of a RAP need not be monotone, so an
122% unguarded Newton step from an arbitrary start can leave the bracket.
124 mid = 0.5 * (lo + hi);
125 if surv(mid) > target
130 if (hi - lo) <= 1e-12 * max(1, hi)
139 d = v * ex * H0 * e; % derivative of the survival function, negative
143 xn = x - (s - target) / d;
144 if xn <= lo || xn >= hi
147 if abs(xn - x) <= 1e-14 * max(1, abs(x))