Minimal examples with LazyPPL

Here are two minimal demonstrations of how to use LazyPPL, with no other libraries, to help with getting started.

Extensions and imports for this Literate Haskell file
Haskell
module MinimalDemo where
import LazyPPL
import LazyPPL.Distributions
import Data.List

Vote prediction example

Suppose I am standing for election in a large population, and that there are two candidates. I run a poll over 100 people, and 51 say they will vote for me and 49 say they won’t. What is the chance that I will win the election?

Here are the poll results:
Haskell
poll :: [Bool]
poll = replicate 51 True ++ replicate 49 False
Here is my model:
Haskell
-- Likelihood function of Bernoulli distribution.
bernoulliPdf r x = if x then r else (1-r)

voteModel :: Meas (Bool)
voteModel = do
  -- prior belief: vote share is unknown, so uniform
  voteShare <- sample uniform
  -- incorporate each poll result as an observation, using bernoulli likelihood
  mapM_ (\actualVote-> score (bernoulliPdf voteShare actualVote)) poll
  -- I win if my vote share is more than 50%.
  return $ voteShare > 0.5
(Simulation code)
Haskell
runVoteModel =
  do
    xws <- mh 1 voteModel
    let n=10000 
    let m = length $ filter id $ map fst $ take n $ xws
    putStrLn $ "Chance of winning = " ++ (show (100 * (fromIntegral m) / (fromIntegral n) )) ++ "%"

When run, this should print the following (roughly).

Text
  Chance of winning = 57.9%

(It may be tempting to think that the chance of winning should be 51%, but that is the expected vote share, not the chance that the vote share is greater than 50%!)

This model can be solved analytically, as ∫0.51v51(1−v)49 dv∫01v51(1−v)49 dv\frac {\int_{0.5}^1 v^{51}(1-v)^{49}\,\mathrm{d}v}{\int_{0}^1 v^{51}(1-v)^{49}\,\mathrm{d}v}.

A clever program analysis could derive this automatically using the Beta/Bernoulli conjugacy, but that is not implemented in LazyPPL.



Simulating a Poisson distribution

Here is a simple example using laziness, but not using inference.

A one-dimensional Poisson point process has steps that are exponentially distributed. The following function produces this infinite stream, lazily.
Haskell
poissonPP :: Double -> Double -> Prob [Double]
poissonPP start rate = do
  step <- exponential rate
  let next = start + step
  rest <- poissonPP next rate 
  return $ next : rest
We can simulate the Poisson distribution by counting how many points in the unit interval of a Poisson point process.
Haskell
poissonSim :: Double -> Prob Int
poissonSim rate = do
  xs <- poissonPP 0 rate
  return $ length $ takeWhile (<1) xs
(Simulation code)
Haskell
runPoissonSim =
  do
    xws <- mh 1 $ sample $ poissonSim 1
    let n=10000
    let xs = sort $ map fst $ take n $ xws
    -- histogram generator
    let categories = nub xs
    let counts = map (\c -> length $ filter (==c) xs) categories
    putStrLn $ "k\tpoisson(k)"
    putStrLn $ "--\t----------"
    mapM_ (\(c,k) -> putStrLn $ (show c) ++ "\t" ++ (show $ (fromIntegral k)/(fromIntegral n))) (zip categories counts)

When run, this should print a Poisson distribution table (approximately).

Poisson point process

Ready.
Poisson simulation frequenciesRun the simulation to count points in the unit interval.
Numerical results
Text
  k       poisson(k)
  --      ----------
  0       0.3679
  1       0.3679
  2       0.1839
  3       6.13e-2
  4       1.53e-2
  5       3.1e-3
  6       0.5e-3
  7       1.0e-4
Expected values: the Poisson(1) probabilities to four decimal places. A run of 10,000 draws prints frequencies close to these.

Note that it might often be better to use the LazyPPL poisson distribution than to use this simulation method. This simple example is just for illustration.



(Main function)
Haskell
main :: IO ()
main = do {runVoteModel ; putStrLn "" ; runPoissonSim }