packages feed

buffon-0.1.0.0: Data/Distribution/Buffon.hs

{-# 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