-
Notifications
You must be signed in to change notification settings - Fork 1
/
Buffon.hs
373 lines (311 loc) · 15.2 KB
/
Buffon.hs
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE TypeFamilies #-}
-- From "On Buffon Machines and Numbers" by Flajolet, Pelletier, and Soria
module Data.Distribution.Buffon (
Buffon, runBuffon, runBuffonWithSystemRandomT,
toss, third, toNumberWith, toNumberM, toNumber,
expectationWith, expectationM, expectation,
bernoulli, if_, mean, evenParity, geometric,
vonNeumann, polylogarithmic, polylogarithmic',
poisson, poisson', anotherPoisson, logarithmic, logarithmic',
alternating, evenAlternating, oddAlternating,
isAlternating, cosine, sine, cotangent, bump, erf,
ternary, binary, ternaryBistoch, Bistoch, Bistoch',
fromBistoch, fromBistoch', bistoch, integrate', arcsin,
squareRoot, ramanujan, arctan, integrate, createReal,
pi8, pi4, zeta3
) where
import Control.Monad.IO.Class ( MonadIO )
import Control.Monad.Primitive ( PrimMonad )
import Control.Monad.Primitive.Class ( MonadPrim(..) )
import Control.Monad.ST (ST, runST)
import Control.Monad.Trans.Class ( MonadTrans )
import Data.Bits ( testBit )
import Data.Function ( fix )
import Data.Word ( Word64 )
import System.Random.MWC ( Variate )
import System.Random.MWC.Monad ( uniform, Rand, runWithCreate, runWithSystemRandomT )
newtype Buffon m a = Buffon { unBuffon :: Rand m a }
deriving (Functor, Applicative, Monad, MonadIO, MonadTrans)
instance (PrimMonad m, MonadPrim m) => MonadPrim (Buffon m) where
type BasePrimMonad (Buffon m) = BasePrimMonad m
liftPrim = Buffon . liftPrim
runBuffon :: MonadPrim m => Buffon m a -> m a
runBuffon = runWithCreate . unBuffon
runBuffonWithSystemRandomT :: (MonadPrim m, BasePrimMonad m ~ IO) => Buffon m a -> m a
runBuffonWithSystemRandomT = runWithSystemRandomT . unBuffon
----
-- | Toss a coin. P(toss) = 1/2
toss :: (MonadPrim m) => Buffon m Bool
toss = Buffon uniform
-- | A biased coin. P(third) = 1/3
third :: (MonadPrim m) => Buffon m Bool
third = do
a <- toss
b <- toss
if a && b then third else return (not (a||b))
toNumberWith :: (Fractional n, MonadPrim m) => Int -> Buffon m Bool -> m n
toNumberWith n p = runBuffon (go n 0)
where go 0 !acc = return (acc/fromIntegral n)
go c acc = if_ p (go (c-1) (acc+1)) (go (c-1) acc)
toNumberWithSystemRandomT :: (Fractional n) => Int -> Buffon IO Bool -> IO n
toNumberWithSystemRandomT n p = runBuffonWithSystemRandomT (go n 0)
where go 0 !acc = return (acc/fromIntegral n)
go c acc = if_ p (go (c-1) (acc+1)) (go (c-1) acc)
toNumberM :: (Fractional n, MonadPrim m) => Buffon m Bool -> m n
toNumberM = toNumberWith 1000000
-- | Estimate the probability of getting True
toNumber :: (Fractional n) => (forall s. Buffon (ST s) Bool) -> n
toNumber p = runST (toNumberM p)
expectationWith :: (Integral a, Fractional n, MonadPrim m) => Int -> Buffon m a -> m n
expectationWith n p = runBuffon (go n 0)
where go 0 !acc = return (fromIntegral acc/fromIntegral n)
go c acc = do x <- p; go (c-1) (acc+x)
expectationM :: (Integral a, Fractional n, MonadPrim m) => Buffon m a -> m n
expectationM = expectationWith 1000000
-- | Estimate the expectation
expectation :: (Integral a, Fractional n) => (forall s. Buffon (ST s) a) -> n
expectation p = runST (expectationM p)
----
-- If P(p) = x then p = bernoulli x
-- bernoulli x = fmap (\n -> bits x !! (n+1) == 1) (geometric toss)
-- where bits x returns the bits of the binary expansion of x
-- Cheating bernoulli
-- | P(bernoulli x) = x
bernoulli :: (Ord n, Variate n, Fractional n, MonadPrim m) => n -> Buffon m Bool
bernoulli p = do
x <- Buffon uniform
if x <= p then 1 else 0
-- | P(if_ (bernoulli r) (bernoulli p) (bernoulli q)) = rp + (1-r)q
if_ :: (MonadPrim m) => Buffon m Bool -> Buffon m a -> Buffon m a -> Buffon m a
if_ r p q = do b <- r; if b then p else q
-- | P(mean (bernoulli p) (bernoulli q)) = (p+q)/2
mean :: (MonadPrim m) => Buffon m a -> Buffon m a -> Buffon m a
mean p q = if_ toss p q
instance Eq (Buffon m a) where (==) = error "(==) not defined for Buffon"
instance Show (Buffon m a) where show _ = "<buffon>"
-- Warning: These don't obey the laws you'd expect.
-- They are left-biased insofar as:
-- fix (\p -> p*q) == undefined regardless of q (same for +)
-- Practically, this means you want left associated uses of * and +.
instance (MonadPrim m) => Num (Buffon m Bool) where
p * q = if_ q p 0 -- | P(bernoulli p*bernoulli q) = pq
p + q = if_ q 1 p -- | P(bernoulli p+bernoulli q) = p + q - pq
negate p = not <$> p -- | P(-bernoulli p) = 1 - p
fromInteger 0 = return False -- | P(0) = 0
fromInteger 1 = return True -- | P(1) = 1
abs = error "abs not defined for Buffon"
signum = error "signum not defined for Buffon"
----
-- | P(evenParity (bernoulli p)) = 1/(1+p)
evenParity :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
evenParity p = fix (\q -> if_ p (if_ p q 0) 1)
-- | von Neumann schema
-- |
-- | Let Perms be a subset of the class of all permutations.
-- | Let F_n be the number of permutations of n items in Perms.
-- | Define F(z) = sum_(n>=0) F_n z^n/n!, i.e. the exponential generating function.
-- | Let U = (U_1, ..., U_n) be a vector of bit streams.
-- | Let type(U) be the permutation that sorts U. type(U) is the order type of U.
-- | The von Neumann schema is:
-- |
-- | vonNeumann[Perms] p = go 1
-- | where go k = do
-- | n <- geometric p
-- | let U be an n-vector of uniformly distributed bit streams
-- | if type(U) in Perms then return (n, k) else go (k+1)
-- |
-- | P(fst (vonNeumann[Perms] (bernoulli p)) == n) = F_n/F(p) p^n/n!
-- | s = (1-p)F(p)
-- | P(snd (vonNeumann[Perms] (bernoulli p)) == k) = s(1-s)^(k-1)
-- | sum_(k>=1) k P(snd (vonNeumann[Perms] (bernoulli p)) == k) = 1/s
-- | The actual function takes a function inClass such that P(inClass n) = F_n/n!
vonNeumann :: (MonadPrim m) => (Int -> Buffon m Bool) -> Buffon m Bool -> Buffon m (Int, Int)
vonNeumann inClass p = go 1
where go !k = do
n <- geometric p
if_ (inClass n) (return (n,k)) (go (k+1))
-- | P(geometric (bernoulli p) == r) = (1-p)p^r
-- | Fits the von Neumann schema with F(z) = 1/(1-z) (i.e. Perms = all permutations)
geometric :: (MonadPrim m) => Buffon m Bool -> Buffon m Int
geometric p = go 0
where go !acc = if_ p (go (acc+1)) (return acc)
-- | P(fst (poisson (bernoulli p)) == r) = exp(-p)p^r/r!
-- | Fits the von Neumann schema with F(z) = exp(z) (i.e. Perms = sorted permutations)
-- Note: P(liftM2 (+) (fst (poisson (bernoulli p))) (fst (poisson (bernoulli q))) == r) = exp(-p-q)(p+q)^r/r!
poisson :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
poisson = vonNeumann isSorted
where isSorted 0 = return True
isSorted 1 = 1
isSorted i = loopFalse 0
where loopFalse j | j < i = mean (loopTrue j (j+1)) (loopFalse (j+1))
| otherwise = isSorted i
loopTrue cut j | j < i = mean (loopTrue cut (j+1)) 0
| otherwise = isSorted cut * isSorted (i-cut)
anotherPoisson :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
anotherPoisson = vonNeumann isSorted
where bernoullis = map (bernoulli . recip) [2 :: Double ..]
isSorted n = product (take (n-1) bernoullis)
-- | P(poisson' (bernoulli p)) = exp(-p) = P(fst (poisson (bernoulli p)) == 0)
poisson' :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
poisson' = fmap ((0==) . fst) . poisson
-- | P(fst (logarithmic (bernoulli p)) == r) = -p^r/(rlog(1-p))
-- | Fits the von Neumann schema with F(z) = -log(1-z) (i.e. Perms = cyclic permutations)
logarithmic :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
logarithmic = polylogarithmic 1
-- | P(logarithmic' (bernoulli p)) = -p/log(1-p) = P(fst (logarithmic (bernoulli p)) == 1)
logarithmic' :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
logarithmic' = fmap ((1==) . fst) . logarithmic
-- | P(fst (polylogarthmic k (bernoulli p)) == r) = p^r/(r^k li_k(p))
polylogarithmic :: (MonadPrim m) => Int -> Buffon m Bool -> Buffon m (Int, Int)
polylogarithmic r = vonNeumann (\n -> (isCyclic n)^r)
where isCyclic 0 = return False
isCyclic 1 = 1
isCyclic i = do
b <- toss
let inner 0 !acc = isCyclic acc
inner j acc = do
b' <- toss
case compare b b' of
LT -> 0
EQ -> inner (j-1) (acc+1)
GT -> inner (j-1) acc
inner (i-1) 1
-- | P(polylogarithmic' k (bernoulli p)) = p/li_k(p) = P(fst (polylogarithmic k (bernoulli p)) == 1)
polylogarithmic' :: (MonadPrim m) => Int -> Buffon m Bool -> Buffon m Bool
polylogarithmic' r = fmap ((1==) . fst) . polylogarithmic r
-- | P(fst (alternating (bernoulli p)) == r) = A_r/r! p^r/(sec(p)+tan(p)) = A_r/r! p^r/tan(z/2+pi/4)
-- | Fits the von Neumann schema with F(z) = sec(z) + tan(z) = tan(z/2+pi/4) (i.e. Perms = alternating permutations)
-- | For A_n see OEIS A000111
alternating :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
alternating = vonNeumann (\n -> isAlternating False n [])
-- | P(fst (evenAlternating (bernoulli p)) == r) = A_r/r! p^r/sec(p) for even r, 0 otherwise
-- | Fits the von Neumann schema with F(z) = sec(z) (i.e. Perms = even length alternating permutations)
evenAlternating :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
evenAlternating = vonNeumann (\n -> if even n then isAlternating False n [] else 0)
-- | P(fst (oddAlternating (bernoulli p)) == r) = A_r/r! p^r/tan(p) for odd r, 0 otherwise
-- | Fits the von Neumann schema with F(z) = tan(z) (i.e. Perms = odd length alternating permutations)
oddAlternating :: (MonadPrim m) => Buffon m Bool -> Buffon m (Int, Int)
oddAlternating = vonNeumann (\n -> if odd n then isAlternating False n [] else 0)
isAlternating :: (MonadPrim m) => Bool -> Int -> [Bool] -> Buffon m Bool
isAlternating !_ 0 _ = 1
isAlternating !_ 1 _ = 1
isAlternating !p !i bs = walk bs [] -- is it better to reverse bs here or append in the calls?
where walk [] acc = walk' acc
walk (b:bs) acc = do
b' <- toss
case if p then compare b b' else compare b' b of
LT -> 0
EQ -> walk bs (b:acc)
GT -> isAlternating (not p) (i-1) (acc++[b'])
walk' acc = do
b <- toss
b' <- toss
case compare b b' of
LT -> 0
EQ -> walk' (b':acc)
GT -> isAlternating (not p) (i-1) (acc++[p==b'])
-- | P(cosine (bernoulli p)) = cos(p) = P(fst (evenAlternating (bernoulli p)) == 0)
cosine :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
cosine = fmap ((0==) . fst) . evenAlternating
-- | P(sine (bernoulli p)) = sin(p)
sine :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
sine p = squareRoot (-cosine p^2)
-- | P(cotangent (bernoulli p)) = pcot(p) = P(fst (oddAlternating (bernoulli p)) == 1)
cotangent :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
cotangent = fmap ((1==) . fst) . oddAlternating
-- | P(squareRoot (bernoulli p)) = sqrt(p)
squareRoot :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
squareRoot = bump 1 . negate
-- | P(bump t (bernoulli p)) = (1-p)S(p/2)
-- | where S(z) = sum_(n>=0) ((t+1)n choose n) z^(tn+n)
bump :: (MonadPrim m) => Int -> Buffon m Bool -> Buffon m Bool
bump t p = go 0
where go !c = if_ p (bump' (bump' go) c) (return (c==0))
bump' k !c = mean (k (c+t)) (k (c-1))
-- | Bi(nary )stoch(astic) (grammar)
-- | Let G be a Bistoch X. It represents the following grammar:
-- | X_i = T (G X_i False) | H (G X_i True)
-- | where X_i is in X, the set of non-terminals and we sequence the list of results from g.
-- | See ternaryBistoch for an example.
type Bistoch k = k -> Bool -> [k]
-- | Bistoch ~ Bistoch'
type Bistoch' k = k -> ([k],[k])
fromBistoch' :: Bistoch' k -> Bistoch k
fromBistoch' g k False = fst (g k)
fromBistoch' g k True = snd (g k)
fromBistoch :: Bistoch k -> Bistoch' k
fromBistoch g k = (g k False, g k True)
-- | ternaryBistoch = fromBistoch' $ \() -> ([], [(),(),()])
-- | Represents the grammar: X = T | H X X X
-- | This grammar corresponds to ternary trees.
ternaryBistoch :: Bistoch ()
ternaryBistoch = fromBistoch' $ \() -> ([], [(),(),()])
-- | P(ternary (bernoulli p)) = T(p/2)
-- | where 2T(p/2) = p(1+T(p/2)^3)
ternary :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
ternary = bistoch ternaryBistoch ()
-- | P(binary (bernoulli p)) = 1/p - sqrt(1/p^2 - 1)
binary :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
binary = bistoch (fromBistoch' $ \() -> ([], [(),()])) ()
-- | Sample from a binary stochastic grammar, g, with given starting non-terminal s.
-- | L(g;s) is the language generated by the grammar starting from non-terminal s.
-- | See the Chomsky-Schutzenberger theorem.
-- | (-p)*bistoch g s p = do
-- | n <- geometric p
-- | ws <- replicateM n toss
-- | return (L(g;s) matches ws)
-- | P(bistoch g s (bernoulli p)) = S(p/2)
-- | where S(z) = sum_(n>=0) S_n z^n
-- | and S_n are the number of words of length n matched by L(g;s).
bistoch :: (MonadPrim m) => Bistoch k -> k -> Buffon m Bool -> Buffon m Bool
bistoch g s p = matches [s] 1
where matches [] c = c
matches (k:ks) c = (do
ks' <- g k <$> toss
matches ks' (matches ks c)) * p
-- | P(ramanujan) = 1/pi
ramanujan :: (MonadPrim m) => Buffon m Bool
ramanujan = do
x1 <- geometric (toss*toss)
x2 <- geometric (toss*toss)
b <- bernoulli (5/9 :: Double)
let t = 2*(if b then x1 + x2 + 1 else x1 + x2)
let go 0 !c = return $ c == 0
go i c = mean (go (i-1) (c+1)) (go (i-1) (c-1))
(go t 0 * go t 0) * go t 0
-- Close enough to a real...
-- We could save random bits by flipping on demand and caching the results but it doesn't seem worth it...
createReal :: (MonadPrim m) => Buffon m (Buffon m Bool)
createReal = do
u <- Buffon uniform
return (do
n <- geometric toss
return (testBit (u :: Word64) n))
-- | P(integrate' f (bernoulli p)) = int_0^p f(w)dw/p
integrate' :: (MonadPrim m) => (Buffon m Bool -> Buffon m Bool) -> Buffon m Bool -> Buffon m Bool
integrate' f p = createReal >>= f . (p*)
-- | P(integrate f (bernoulli p)) = int_0^p f(w)dw
integrate :: (MonadPrim m) => (Buffon m Bool -> Buffon m Bool) -> Buffon m Bool -> Buffon m Bool
integrate f p = integrate' f p * p
-- | P(arctan (bernoulli p)) = atan(p)
arctan :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
arctan p = (do u <- createReal; evenParity (p*(p*(u*u)))) * p
-- | P(arcsin (bernoulli p)) = asin(p)/2
arcsin :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
arcsin p = mean (integrate (\w -> evenParity w * squareRoot (-w*w)) p) (-squareRoot (-p*p))
-- | P(erf (bernoulli p)) = sqrt(pi)erf(p)/2
erf :: (MonadPrim m) => Buffon m Bool -> Buffon m Bool
erf p = integrate (\w -> poisson' (w*w)) p
-- | P(pi8) = pi/8 (via (atan(1/2) + atan(1/3))/2)
pi8 :: (MonadPrim m) => Buffon m Bool
pi8 = mean (arctan toss) (arctan third)
-- | P(pi4) = pi/4 (via atan(1))
pi4 :: (MonadPrim m) => Buffon m Bool
pi4 = do u <- createReal; evenParity (u*u)
-- | P(zeta3) = 3zeta(3)/4
zeta3 :: (MonadPrim m) => Buffon m Bool
zeta3 = integrate' (\x -> integrate' (\y -> integrate' (\z -> evenParity (x*y*z)) 1) 1) 1