optimising accept-reject

I spotted on R-bloggers a post discussing optimising the efficiency of programming accept-reject algorithms. While it is about SAS programming, and apparently supported by the SAS company, there are two interesting features with this discussion. The first one is about avoiding the dreaded loop in accept-reject algorithms. For instance, taking the case of the truncated-at-one Poisson distribution, the code

[sourcecode language=”r” gutter=”false”]
rtpois=function(n,lambda){
sampl=c()
while (length(sampl)<n){
x=rpois(1,lambda)
if (x!=0) sampl=c(sampl,x)}
return(sampl)
}
[/sourcecode]

is favoured by my R course students but highly inefficient:

[sourcecode language=”r” gutter=”false”]
> system.time(rtpois(10^5,.5))
user system elapsed
61.600  27.781  98.727
[/sourcecode]

both for the stepwise increase in the size of the vector and for the loop. For instance, defining the vector sampl first requires a tenth of the above time (note the switch from 10⁵ to 10⁶):

[sourcecode language=”r” gutter=”false”]
> system.time(rtpois(10^6,.5))
user system elapsed
54.155 0.200 62.635
[/sourcecode]

As discussed by the author of the post, a more efficient programming should aim at avoiding the loop by predicting the number of proposals necessary to accept a given number of values. Since the bound M used in accept-reject algorithms is also the expected number of attempts for one acceptance, one should start with something around Mn proposed values. (Assuming of course all densities are properly normalised.) For instance, in the case of the truncated-at-one Poisson distribution based on proposals from the regular Poisson, the bound is 1/1-e-λ. A first implementation of this principle is to build the sample via a few loops:

[sourcecode language=”r” gutter=”false”]
rtpois=function(n,lambda){
propal=rpois(ceiling(n/(1-exp(-lambda))),lambda)
propal=propal[propal>0]
n0=length(propal)
if (n0>=n)
return(propal[1:n])
else return(c(propal,rtpois(n-n0,lambda)))
}
[/sourcecode]

with a higher efficiency:

[sourcecode language=”r” gutter=”false”]
> system.time(rtpois(10^6,.5))
user system elapsed
0.816 0.092 0.928
[/sourcecode]

Replacing the expectation with an upper bound using the variance of the negative binomial distribution does not make a significant dent in the computing time

[sourcecode language=”r” gutter=”false”]
rtpois=function(n,lambda){
M=1/(1-exp(-lambda))
propal=rpois(ceiling(M*(n+2*sqrt(n/M)/(M-1))),lambda)
propal=propal[propal>0]
n0=length(propal)
if (n0>=n)
return(propal[1:n])
else return(c(propal,rtpois(n-n0,lambda)))}
[/sourcecode]

since we get

[sourcecode language=”r” gutter=”false”]
> system.time(rtpois(10^6,.5))
user system elapsed
0.824 0.048 0.877
[/sourcecode]

The second point about this Poisson example is that simulating a distribution with a restricted support using another distribution with a larger support is quite inefficient. Especially when λ goes to zero By comparison, using a Poisson proposal with parameter μ and translating it by 1 may bring a considerable improvement: without getting into the gory details, it can be shown that the optimal value of μ (in terms of maximal acceptance probability) is λ and that the corresponding probability of acceptance is

\dfrac{1-\exp\{-\lambda\}}{\lambda}

which is larger than the probability of the original approach when λ is less than one. As shown by the graph below, this allows for a lower bound in the probability of acceptance that remains tolerable.

2 Responses to “optimising accept-reject”

  1. It’s interesting that you bring up the negative binomial distribution, since I just posted a follow-up article about that topic: http://blogs.sas.com/content/iml/2012/11/21/efficient-acceptance-rejection-simulation-part-ii/

    I think the NegBinom timings indicates that if you get “in the ballpark” by properly inflating the number of requested variates, then it doesn’t matter much whether you generate all random variates in one call or whether you need to use 2 calls. Still, I like the negative binomial approach for aesthetic reasons.

    And, yes, there are better algorithms to simulate from a truncated distribution, including bounding and squeezing. Since you didn’t plug your own work, I’ll mention Robert (1995), “Simulation of Truncated Normal Variables,” as an excellent introduction, as well as Robert and Casella (2009).

    • Thanks. I try to teach my students the negative binomial trick that I like very much too. But they do stick to the terrible incremental construction of the output vector, sad isn’t it?!

Leave a Reply

Discover more from Xi'an's Og

Subscribe now to keep reading and get access to the full archive.

Continue reading