-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathbinomial.py
executable file
·107 lines (100 loc) · 3.36 KB
/
binomial.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import random
import randomgen
import math
from fractions import Fraction
from betadist import (
logbinco,
psrnexpo,
RealLn,
RealExp,
RandPSRN,
RealNegate,
RandUniform,
realIsLess,
)
class BinomialSampler:
def __init__(self, rg=None):
self.rg = randomgen.RandomGen() if rg == None else rg
self.logcache = {}
self.binomialinfo = {}
self.bits = 0
self.curbit = -1
def _logint(self, n):
if not n in self.logcache:
self.logcache[n] = RealLn(n)
return self.logcache[n]
def _randbit(self):
if self.rg != None:
return self.rg.randbit()
if self.curbit == -1 or self.curbit >= 64:
self.bits = random.randint(0, (1 << 64) - 1)
self.curbit = 0
r = self.bits & 1
self.bits >>= 1
self.curbit += 1
return r
def _roughSqrt(x):
"""Returns a number m such that m is in the
interval [sqrt(x), sqrt(x)+3]. This rough approximation
suffices for the binomial sampler."""
u = 1 << ((x.bit_length() + 1) // 2)
i = 0
while True:
v = (u + x // u) // 2
if v >= u:
return u + 1
u = v
def sample(self, n):
"""Draws a binomial(n, 1/2) random variate.
Uses the rejection sampling approach from Bringmann et al.
2014, but uses log binomial coefficients and in general, upper
and lower bounds of logarithmic probabilities (to support very
large values of n) together with the alternating series method
and rational interval arithmetic (rather than floating-point arithmetic).
Reference:
K. Bringmann, F. Kuhn, et al., “Internal DLA: Efficient Simulation of
a Physical Growth Model.” In: _Proc. 41st International
Colloquium on Automata, Languages, and Programming (ICALP'14)_, 2014.
"""
if n == 0:
return 0
if n % 2 == 1:
return self._randbit() + self.sample(n - 1)
n2 = n
ret = 0
if n2 % 2 == 1:
ret += self._randbit()
n2 -= 1
if n2 == 0:
return ret
if not n2 in self.binomialinfo:
bm = BinomialSampler._roughSqrt(n2)
bi = [bm, {}]
self.binomialinfo[n2] = bi
halfn = n2 // 2
m = self.binomialinfo[n2][0]
bincos = self.binomialinfo[n2][1]
while True:
pos = self._randbit() == 0
k = 0
while self._randbit() == 0:
k += 1
r = k * m + (
self.rg.rndint(m - 1) if self.rg != None else random.randint(0, m - 1)
)
rv = halfn + r if pos else halfn - r - 1
if rv >= 0 and rv <= n2:
# psrn = psrnexpo(self.rg)
# psrn[0] = -1 # Negate
if not (rv in bincos):
bincos[rv] = None
if bincos[rv] == None:
# Calculate log of acceptance probability on demand
bincos[rv] = (
logbinco(n2, rv)
+ self._logint(m)
+ self._logint(2) * (k - n2 - 2)
)
h = RealLn(RandUniform())
if realIsLess(h, bincos[rv]):
return rv