monad-bayes 0.1.1.0 → 1.0.0
raw patch · 52 files changed
+2938/−1179 lines, 52 filesdep +brickdep +foldldep +histogram-filldep ~basedep ~containersdep ~ieee754new-uploader
Dependencies added: brick, foldl, histogram-fill, integration, lens, linear, matrix, monad-extras, pipes, pretty-simple, primitive, profunctors, random, scientific, text, typed-process, vty
Dependency ranges changed: base, containers, ieee754, math-functions, monad-coroutine, mtl, mwc-random, safe, statistics, transformers, vector
Files
- CHANGELOG.md +45/−4
- benchmark/SSM.hs +26/−12
- benchmark/Single.hs +37/−21
- benchmark/Speed.hs +66/−177
- models/BetaBin.hs +64/−0
- models/ConjugatePriors.hs +66/−0
- models/Dice.hs +8/−3
- models/HMM.hs +44/−12
- models/LDA.hs +49/−19
- models/LogReg.hs +22/−16
- models/NonlinearSSM.hs +6/−1
- models/Sprinkler.hs +1/−1
- monad-bayes.cabal +220/−141
- src/Control/Monad/Bayes/Class.hs +111/−28
- src/Control/Monad/Bayes/Density/Free.hs +88/−0
- src/Control/Monad/Bayes/Density/State.hs +40/−0
- src/Control/Monad/Bayes/Enumerator.hs +53/−18
- src/Control/Monad/Bayes/Free.hs +0/−86
- src/Control/Monad/Bayes/Helpers.hs +0/−77
- src/Control/Monad/Bayes/Inference/Lazy/MH.hs +79/−0
- src/Control/Monad/Bayes/Inference/Lazy/WIS.hs +23/−0
- src/Control/Monad/Bayes/Inference/MCMC.hs +53/−0
- src/Control/Monad/Bayes/Inference/PMMH.hs +39/−18
- src/Control/Monad/Bayes/Inference/RMSMC.hs +42/−36
- src/Control/Monad/Bayes/Inference/SMC.hs +27/−67
- src/Control/Monad/Bayes/Inference/SMC2.hs +27/−14
- src/Control/Monad/Bayes/Inference/TUI.hs +176/−0
- src/Control/Monad/Bayes/Integrator.hs +167/−0
- src/Control/Monad/Bayes/Population.hs +114/−48
- src/Control/Monad/Bayes/Sampler.hs +0/−105
- src/Control/Monad/Bayes/Sampler/Lazy.hs +79/−0
- src/Control/Monad/Bayes/Sampler/Strict.hs +101/−0
- src/Control/Monad/Bayes/Sequential.hs +0/−98
- src/Control/Monad/Bayes/Sequential/Coroutine.hs +119/−0
- src/Control/Monad/Bayes/Traced/Basic.hs +30/−17
- src/Control/Monad/Bayes/Traced/Common.hs +69/−29
- src/Control/Monad/Bayes/Traced/Dynamic.hs +27/−15
- src/Control/Monad/Bayes/Traced/Static.hs +32/−18
- src/Control/Monad/Bayes/Weighted.hs +22/−17
- src/Math/Integrators/StormerVerlet.hs +77/−0
- test/Spec.hs +132/−33
- test/TestAdvanced.hs +65/−0
- test/TestDistribution.hs +69/−0
- test/TestEnumerator.hs +15/−7
- test/TestInference.hs +75/−11
- test/TestIntegrator.hs +151/−0
- test/TestPipes.hs +21/−0
- test/TestPopulation.hs +24/−15
- test/TestSampler.hs +15/−0
- test/TestSequential.hs +12/−8
- test/TestStormerVerlet.hs +98/−0
- test/TestWeighted.hs +12/−7
CHANGELOG.md view
@@ -1,11 +1,52 @@-# Unreleased+# 1.0.0 (2022-09-10) -- No unreleased changes so far.+- host website from repo+- host notebooks from repo+- use histogram-fill -# 0.1.1.0 (2020-04-08)+# 0.2.0 (2022-07-26) +- rename various functions to match the names of the corresponding types (e.g. `Enumerator` goes with `enumerator`)+- add configs as arguments to inference methods `smc` and `mcmc`+- add rudimentary tests for all inference methods+- put `mcmc` as inference method in new module `Control.Monad.Bayes.Inference.MCMC`+- update history of changelog in line with semantic versioning conventions+- bumped to GHC 9.2.3++# 0.1.5 (2022-07-26)++- Refactor of sampler to be parametric in the choice of a pair of IO monad and RNG++# 0.1.4 (2022-06-15)++Addition of new helper functions, plotting tools, tests, and Integrator monad.++- helpers include: `toEmpirical` (list of samples to empirical distribution) and `toBins` (simple histogramming)+- `Integrator` is an instance of `MonadSample` for numerical integration+- `notebooks` now contains working notebook-based tutorials and examples+- new tests, including with conjugate distributions to compare analytic solution against inferred posterior+- `models` directory is cleaned up. New sequential models using `pipes` package to represent monadic streams++# 0.1.3 (2022-06-08)++Clean up of unused functions and broken code++- remove unused functions in `Weighted` and `Population`+- remove broken models in `models`+- explicit imports+- added some global language extensions++# 0.1.2 (2022-06-08)++Add documentation++- docs written in markdown+- docs built by sphinx++# 0.1.1 (2020-04-08)+ - New exported function: `Control.Monad.Bayes.Class` now exports `discrete`. -# 0.1.0.0 (2020-02-17)+# 0.1.0 (2020-02-17) Initial release.
benchmark/SSM.hs view
@@ -1,29 +1,43 @@ module Main where -import Control.Monad.Bayes.Inference.PMMH as PMMH-import Control.Monad.Bayes.Inference.RMSMC+import Control.Monad.Bayes.Inference.MCMC+import Control.Monad.Bayes.Inference.PMMH as PMMH (pmmh)+import Control.Monad.Bayes.Inference.RMSMC (rmsmcDynamic) import Control.Monad.Bayes.Inference.SMC-import Control.Monad.Bayes.Inference.SMC2 as SMC2+import Control.Monad.Bayes.Inference.SMC2 as SMC2 (smc2) import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sampler-import Control.Monad.Bayes.Weighted-import Control.Monad.IO.Class-import NonlinearSSM+import Control.Monad.Bayes.Population (population, resampleMultinomial)+import Control.Monad.Bayes.Sampler.Strict (sampleIO, sampleIOfixed, sampleWith)+import Control.Monad.Bayes.Weighted (unweighted)+import Control.Monad.IO.Class (MonadIO (liftIO))+import NonlinearSSM (generateData, model, param)+import System.Random.Stateful (mkStdGen, newIOGenM) main :: IO ()-main = sampleIO $ do+main = sampleIOfixed $ do let t = 5 dat <- generateData t let ys = map snd dat liftIO $ print "SMC"- smcRes <- runPopulation $ smcMultinomial t 10 (param >>= model ys)+ smcRes <- population $ smc SMCConfig {numSteps = t, numParticles = 10, resampler = resampleMultinomial} (param >>= model ys) liftIO $ print $ show smcRes liftIO $ print "RM-SMC"- smcrmRes <- runPopulation $ rmsmcLocal t 10 10 (param >>= model ys)+ smcrmRes <-+ population $+ rmsmcDynamic+ MCMCConfig {numMCMCSteps = 10, numBurnIn = 0, proposal = SingleSiteMH}+ SMCConfig {numSteps = t, numParticles = 10, resampler = resampleSystematic}+ (param >>= model ys) liftIO $ print $ show smcrmRes liftIO $ print "PMMH"- pmmhRes <- prior $ pmmh 2 t 3 param (model ys)+ pmmhRes <-+ unweighted $+ pmmh+ MCMCConfig {numMCMCSteps = 2, numBurnIn = 0, proposal = SingleSiteMH}+ SMCConfig {numSteps = t, numParticles = 3, resampler = resampleSystematic}+ param+ (model ys) liftIO $ print $ show pmmhRes liftIO $ print "SMC2"- smc2Res <- runPopulation $ smc2 t 3 2 1 param (model ys)+ smc2Res <- population $ smc2 t 3 2 1 param (model ys) liftIO $ print $ show smc2Res
benchmark/Single.hs view
@@ -1,19 +1,38 @@-import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Inference.RMSMC+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImportQualifiedPost #-}++import Control.Monad.Bayes.Class (MonadInfer)+import Control.Monad.Bayes.Inference.MCMC (MCMCConfig (..), Proposal (SingleSiteMH))+import Control.Monad.Bayes.Inference.RMSMC (rmsmcBasic) import Control.Monad.Bayes.Inference.SMC+ ( SMCConfig (SMCConfig, numParticles, numSteps, resampler),+ smc,+ ) import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sampler-import Control.Monad.Bayes.Traced+import Control.Monad.Bayes.Sampler.Strict+import Control.Monad.Bayes.Traced hiding (model) import Control.Monad.Bayes.Weighted-import Data.Time-import qualified HMM-import qualified LDA-import qualified LogReg+import Control.Monad.ST (runST)+import Data.Time (diffUTCTime, getCurrentTime)+import HMM qualified+import LDA qualified+import LogReg qualified import Options.Applicative-import System.Random.MWC (createSystemRandom)+ ( Applicative (liftA2),+ ParserInfo,+ auto,+ execParser,+ fullDesc,+ help,+ info,+ long,+ maybeReader,+ option,+ short,+ ) data Model = LR Int | HMM Int | LDA (Int, Int)- deriving (Show, Read)+ deriving stock (Show, Read) parseModel :: String -> Maybe Model parseModel s =@@ -29,14 +48,12 @@ size (LR n) = n size (HMM n) = n size (LDA (d, w)) = d * w- synthesize :: SamplerST a -> (a -> b) -> b- synthesize dataGen prog = prog (sampleSTfixed dataGen)- program (LR n) = show <$> synthesize (LogReg.syntheticData n) LogReg.logisticRegression- program (HMM n) = show <$> synthesize (HMM.syntheticData n) HMM.hmm- program (LDA (d, w)) = show <$> synthesize (LDA.syntheticData d w) LDA.lda+ program (LR n) = show <$> (LogReg.logisticRegression (runST $ sampleSTfixed (LogReg.syntheticData n)))+ program (HMM n) = show <$> (HMM.hmm (runST $ sampleSTfixed (HMM.syntheticData n)))+ program (LDA (d, w)) = show <$> (LDA.lda (runST $ sampleSTfixed (LDA.syntheticData d w))) data Alg = SMC | MH | RMSMC- deriving (Read, Show)+ deriving stock (Read, Show) runAlg :: Model -> Alg -> SamplerIO String runAlg model alg =@@ -44,21 +61,20 @@ SMC -> let n = 100 (k, m) = getModel model- in show <$> runPopulation (smcSystematic k n m)+ in show <$> population (smc SMCConfig {numSteps = k, numParticles = n, resampler = resampleSystematic} m) MH -> let t = 100 (_, m) = getModel model- in show <$> prior (mh t m)+ in show <$> unweighted (mh t m) RMSMC -> let n = 10 t = 1 (k, m) = getModel model- in show <$> runPopulation (rmsmcBasic k n t m)+ in show <$> population (rmsmcBasic MCMCConfig {numMCMCSteps = t, numBurnIn = 0, proposal = SingleSiteMH} (SMCConfig {numSteps = k, numParticles = n, resampler = resampleSystematic}) m) infer :: Model -> Alg -> IO () infer model alg = do- g <- createSystemRandom- x <- sampleIOwith (runAlg model alg) g+ x <- sampleIOfixed (runAlg model alg) print x opts :: ParserInfo (Model, Alg)
benchmark/Speed.hs view
@@ -1,123 +1,38 @@-import Control.Monad (unless)-import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Inference.RMSMC-import Control.Monad.Bayes.Inference.SMC-import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sampler-import Control.Monad.Bayes.Traced-import Control.Monad.Bayes.Weighted-import Criterion.Main-import Criterion.Types-import GHC.IO.Handle--- import NonlinearSSM-import qualified HMM-import qualified LDA-import qualified LogReg-import System.Exit-import System.Process hiding (env)-import System.Random.MWC (GenIO, createSystemRandom)---- | Path to the Anglican project with benchmarks.-anglicanPath :: String-anglicanPath = "/scratch/ams240/repos/anglican-white-paper/experiments"---- | Running Leiningen repl process.--- Leiningen takes a lot of time to start so we keep a repl--- running to speed up benchmarking.--- Contains in order input handle, output handle, and process handle.-data LeinProc = LeinProc Handle Handle ProcessHandle--anglicanModelName :: Model -> String-anglicanModelName (LR _) = "logisticRegression"-anglicanModelName (HMM _) = "hmm"-anglicanModelName (LDA _) = "lda"--clojureBool :: Bool -> String-clojureBool False = "false"-clojureBool True = "true"---- | Format Haskell [Bool] as a Clojure Boolean vector.-clojureBoolVector :: [Bool] -> String-clojureBoolVector = clojureVector . map clojureBool---- | Format a Haskell list as a Clojure vector.-clojureVector :: [String] -> String-clojureVector xs = "[" ++ unwords xs ++ "]"---- | Format a Haskell list as a Clojure vector.-clojureShowVector :: Show a => [a] -> String-clojureShowVector xs = clojureVector $ map show xs---- | Insert data into an Anglican model.-anglicanData :: LeinProc -> Model -> IO ()-anglicanData leinProc model = do- anglican leinProc ["(use 'nstools.ns)\n"]- anglican leinProc ["(ns+ " ++ anglicanModelName model ++ ")\n"]- case model of- LR dataset -> do- let (xs, labels) = unzip dataset- anglican leinProc ["(ns-unmap *ns* 'xs)\n"]- anglican leinProc ["(def xs " ++ clojureShowVector xs ++ ")\n", "nil\n"]- anglican leinProc ["(ns-unmap *ns* 'labels)\n"]- anglican leinProc ["(def labels " ++ clojureBoolVector labels ++ ")\n", "nil\n"]- HMM observations -> do- anglican leinProc ["(ns-unmap *ns* 'observations)\n"]- anglican leinProc ["(def observations " ++ clojureShowVector observations ++ ")\n", "nil\n"]- LDA docs -> do- anglican leinProc ["(ns-unmap *ns* 'docs)\n"]- anglican leinProc ["(def docs " ++ clojureVector (map clojureShowVector docs) ++ ")\n", "nil\n"]- anglican leinProc ["(use 'nstools.ns)\n"]- anglican leinProc ["(ns+ anglican.core)\n"]--anglican :: LeinProc -> [String] -> IO ()-anglican (LeinProc input output _) cmds = do- -- execute command- mapM_ (hPutStr input) cmds- hFlush input- -- wait until all samples are produced- waitForAnglican output---- | Wait until an Anglican program finishes-waitForAnglican :: Handle -> IO ()-waitForAnglican handle = run- where- run = do- l <- hGetLine handle- -- traceIO l- -- We recognize that Anglican is finished when the repl emits a line "nil"- unless (l == "nil") run---- | Start a Leiningen process in a directory that contains benchmarks.-startLein :: IO LeinProc-startLein = do- let setup = (shell "lein repl") {cwd = Just anglicanPath, std_in = CreatePipe, std_out = CreatePipe}- (Just input, Just output, _, process) <- createProcess setup- -- wait until Leiningen starts producing output to make sure it's ready- _ <- hWaitForInput output (-1) -- wait for output indefinitely- let leinProc = LeinProc input output process- anglican leinProc ["(use 'nstools.ns)\n"]- return leinProc---- | Path to the WebPPL project with benchmarks.-webpplPath :: String-webpplPath = "/scratch/ams240/repos/anglican-white-paper/experiments/WebPPL"---- | Format Haskell list as a Javascript list.-javascriptList :: Show a => [a] -> String-javascriptList xs = unwords (map show xs)+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# OPTIONS_GHC -Wall #-} -webpplModelName :: Model -> String-webpplModelName (LR _) = "logisticRegression"-webpplModelName (HMM _) = "hmm"-webpplModelName (LDA _) = "lda"+module Main (main) where --- | Environment to execute benchmarks in.-data Env = Env {rng :: GenIO, lein :: LeinProc}+import Control.Monad.Bayes.Class (MonadInfer, MonadSample)+import Control.Monad.Bayes.Inference.MCMC (MCMCConfig (MCMCConfig, numBurnIn, numMCMCSteps, proposal), Proposal (SingleSiteMH))+import Control.Monad.Bayes.Inference.RMSMC (rmsmcDynamic)+import Control.Monad.Bayes.Inference.SMC (SMCConfig (SMCConfig, numParticles, numSteps, resampler), smc)+import Control.Monad.Bayes.Population (population, resampleSystematic)+import Control.Monad.Bayes.Sampler.Strict (SamplerIO, sampleIOfixed)+import Control.Monad.Bayes.Traced (mh)+import Control.Monad.Bayes.Weighted (unweighted)+import Criterion.Main+ ( Benchmark,+ Benchmarkable,+ bench,+ defaultConfig,+ defaultMainWith,+ nfIO,+ )+import Criterion.Types (Config (csvFile, rawDataFile))+import Data.Functor (void)+import Data.Text qualified as T+import HMM qualified+import LDA qualified+import LogReg qualified+import System.Process.Typed (runProcess)+import System.Random.Stateful (IOGenM, StatefulGen, StdGen, mkStdGen, newIOGenM) -data ProbProgSys = MonadBayes | Anglican | WebPPL- deriving (Show)+data ProbProgSys = MonadBayes+ deriving stock (Show) -data Model = LR [(Double, Bool)] | HMM [Double] | LDA [[String]]+data Model = LR [(Double, Bool)] | HMM [Double] | LDA [[T.Text]] instance Show Model where show (LR xs) = "LR" ++ show (length xs)@@ -142,67 +57,43 @@ show (RMSMC n t) = "RMSMC" ++ show n ++ "-" ++ show t runAlg :: Model -> Alg -> SamplerIO String-runAlg model (MH n) = show <$> prior (mh n (buildModel model))-runAlg model (SMC n) = show <$> runPopulation (smcSystematic (modelLength model) n (buildModel model))-runAlg model (RMSMC n t) = show <$> runPopulation (rmsmcLocal (modelLength model) n t (buildModel model))+runAlg model (MH n) = show <$> unweighted (mh n (buildModel model))+runAlg model (SMC n) = show <$> population (smc SMCConfig {numSteps = (modelLength model), numParticles = n, resampler = resampleSystematic} (buildModel model))+runAlg model (RMSMC n t) =+ show+ <$> population+ ( rmsmcDynamic+ MCMCConfig {numMCMCSteps = t, numBurnIn = 0, proposal = SingleSiteMH}+ SMCConfig {numSteps = modelLength model, numParticles = n, resampler = resampleSystematic}+ (buildModel model)+ ) -prepareBenchmarkable :: GenIO -> ProbProgSys -> Model -> Alg -> Benchmarkable-prepareBenchmarkable g MonadBayes model alg = nfIO $ sampleIOwith (runAlg model alg) g-prepareBenchmarkable _ Anglican _ _ = error "Anglican benchmarks not available"-prepareBenchmarkable _ WebPPL _ _ = error "WebPPL benchmarks not available"+prepareBenchmarkable :: ProbProgSys -> Model -> Alg -> Benchmarkable+prepareBenchmarkable MonadBayes model alg = nfIO $ sampleIOfixed (runAlg model alg) -prepareBenchmark :: Env -> ProbProgSys -> Model -> Alg -> Benchmark-prepareBenchmark e MonadBayes model alg =+prepareBenchmark :: ProbProgSys -> Model -> Alg -> Benchmark+prepareBenchmark MonadBayes model alg = bench (show MonadBayes ++ sep ++ show model ++ sep ++ show alg) $- prepareBenchmarkable (rng e) MonadBayes model alg- where- sep = "_"-prepareBenchmark e Anglican model alg = env prepareData (const $ bench name $ whnfIO collect)- where- name = show Anglican ++ sep ++ show model ++ sep ++ show alg- sep = "_"- algString (MH n) = "-a lmh -n " ++ show n- algString (SMC n) = "-a smc -n " ++ show n- algString (RMSMC _ _) = error "Anglican does not support resample-move SMC"- prepareData = anglicanData (lein e) model- collect =- anglican (lein e) ["(time (m! " ++ anglicanModelName model ++ " " ++ algString alg ++ "))\n"]-prepareBenchmark _ WebPPL model alg = bench name $ whnfIO run+ prepareBenchmarkable MonadBayes model alg where- name = show WebPPL ++ sep ++ show model ++ sep ++ show alg- sep = "_"- algString (MH n) = "--alg MCMC --samples " ++ show n ++ " --rejuv 0 "- algString (SMC n) = "--alg SMC --samples " ++ show n ++ " --rejuv 0 "- algString (RMSMC n t) = "--alg SMC --samples " ++ show n ++ " --rejuv " ++ show t ++ " "- dataString (LR dataset) = let (xs, labels) = unzip dataset in "--xs='" ++ javascriptList xs ++ "' --labels='" ++ javascriptList (map (\b -> if b then (1 :: Int) else 0) labels) ++ "'"- dataString (HMM obs) = "--obs='" ++ javascriptList obs ++ "'"- dataString (LDA docs) = unwords $ zipWith (\i doc -> "--doc" ++ show (i :: Int) ++ "='" ++ unwords doc ++ "'") [1 .. 5] docs- run = do- let command = "node " ++ webpplModelName model ++ ".js " ++ algString alg ++ dataString model- (_, _, _, process) <- createProcess $ (shell command) {cwd = Just webpplPath, std_out = NoStream, std_err = NoStream}- exitCode <- waitForProcess process- case exitCode of- ExitSuccess -> return ()- ExitFailure i -> error $ "WebPPL terminated with exit code " ++ show i+ sep = "_" :: String -- | Checks if the requested benchmark is implemented. supported :: (ProbProgSys, Model, Alg) -> Bool-supported (Anglican, _, RMSMC _ _) = False+supported (_, _, RMSMC _ _) = True supported _ = True systems :: [ProbProgSys] systems = [ MonadBayes- -- Anglican,- -- WebPPL ] -lengthBenchmarks :: Env -> [(Double, Bool)] -> [Double] -> [[String]] -> [Benchmark]-lengthBenchmarks e lrData hmmData ldaData = benchmarks+lengthBenchmarks :: [(Double, Bool)] -> [Double] -> [[T.Text]] -> [Benchmark]+lengthBenchmarks lrData hmmData ldaData = benchmarks where- lrLengths = 10 : map (* 100) [1 .. 10]- hmmLengths = 10 : map (* 100) [1 .. 10]- ldaLengths = 5 : map (* 50) [1 .. 10]+ lrLengths = 10 : map (* 100) [1 :: Int .. 10]+ hmmLengths = 10 : map (* 100) [1 :: Int .. 10]+ ldaLengths = 5 : map (* 50) [1 :: Int .. 10] models = map (LR . (`take` lrData)) lrLengths ++ map (HMM . (`take` hmmData)) hmmLengths@@ -212,7 +103,7 @@ SMC 100, RMSMC 10 1 ]- benchmarks = map (uncurry3 (prepareBenchmark e)) $ filter supported xs+ benchmarks = map (uncurry3 (prepareBenchmark)) $ filter supported xs where uncurry3 f (x, y, z) = f x y z xs = do@@ -221,12 +112,12 @@ a <- algs return (s, m, a) -samplesBenchmarks :: Env -> [(Double, Bool)] -> [Double] -> [[String]] -> [Benchmark]-samplesBenchmarks e lrData hmmData ldaData = benchmarks+samplesBenchmarks :: [(Double, Bool)] -> [Double] -> [[T.Text]] -> [Benchmark]+samplesBenchmarks lrData hmmData ldaData = benchmarks where- lrLengths = [50]- hmmLengths = [20]- ldaLengths = [10]+ lrLengths = [50 :: Int]+ hmmLengths = [20 :: Int]+ ldaLengths = [10 :: Int] models = map (LR . (`take` lrData)) lrLengths ++ map (HMM . (`take` hmmData)) hmmLengths@@ -234,7 +125,7 @@ algs = map (\x -> MH (100 * x)) [1 .. 10] ++ map (\x -> SMC (100 * x)) [1 .. 10] ++ map (\x -> RMSMC 10 (10 * x)) [1 .. 10]- benchmarks = map (uncurry3 (prepareBenchmark e)) $ filter supported xs+ benchmarks = map (uncurry3 (prepareBenchmark)) $ filter supported xs where uncurry3 f (x, y, z) = f x y z xs = do@@ -245,13 +136,11 @@ main :: IO () main = do- g <- createSystemRandom- l <- startLein- let e = Env g l- lrData <- sampleIOwith (LogReg.syntheticData 1000) g- hmmData <- sampleIOwith (HMM.syntheticData 1000) g- ldaData <- sampleIOwith (LDA.syntheticData 5 1000) g+ lrData <- sampleIOfixed (LogReg.syntheticData 1000)+ hmmData <- sampleIOfixed (HMM.syntheticData 1000)+ ldaData <- sampleIOfixed (LDA.syntheticData 5 1000) let configLength = defaultConfig {csvFile = Just "speed-length.csv", rawDataFile = Just "raw.dat"}- defaultMainWith configLength (lengthBenchmarks e lrData hmmData ldaData)+ defaultMainWith configLength (lengthBenchmarks lrData hmmData ldaData) let configSamples = defaultConfig {csvFile = Just "speed-samples.csv", rawDataFile = Just "raw.dat"}- defaultMainWith configSamples (samplesBenchmarks e lrData hmmData ldaData)+ defaultMainWith configSamples (samplesBenchmarks lrData hmmData ldaData)+ void $ runProcess "python plots.py"
+ models/BetaBin.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+{-# OPTIONS_GHC -Wno-missing-export-lists #-}++module BetaBin where++-- The beta-binomial model in latent variable and urn model representations.+-- The two formulations should be exactly equivalent, but only urn works with Dist.+import Control.Monad (replicateM)+import Control.Monad.Bayes.Class+ ( MonadInfer,+ MonadSample (bernoulli, uniform),+ condition,+ )+import Control.Monad.State.Lazy (evalStateT, get, put)+import Pipes ((<-<))+import Pipes.Prelude qualified as P hiding (show)++-- | Beta-binomial model as an i.i.d. sequence conditionally on weight.+latent :: MonadSample m => Int -> m [Bool]+latent n = do+ weight <- uniform 0 1+ replicateM n (bernoulli weight)++-- | Beta-binomial as a random process.+-- Equivalent to the above by De Finetti's theorem.+urn :: MonadSample m => Int -> m [Bool]+urn n = flip evalStateT (1, 1) $ do+ replicateM n do+ (a, b) <- get+ let weight = a / (a + b)+ outcome <- bernoulli weight+ let (a', b') = if outcome then (a + 1, b) else (a, b + 1)+ put (a', b')+ return outcome++-- | Beta-binomial as a random process.+-- This time using the Pipes library, for a more pure functional style+urnP :: MonadSample m => Int -> m [Bool]+urnP n = P.toListM $ P.take n <-< P.unfoldr toss (1, 1)+ where+ toss (a, b) = do+ let weight = a / (a + b)+ outcome <- bernoulli weight+ let (a', b') = if outcome then (a + 1, b) else (a, b + 1)+ return $ Right (outcome, (a', b'))++-- | A beta-binomial model where the first three states are True,True,False.+-- The resulting distribution is on the remaining outcomes.+cond :: MonadInfer m => m [Bool] -> m [Bool]+cond d = do+ ~(first : second : third : rest) <- d+ condition first+ condition second+ condition (not third)+ return rest++-- | The final conditional model, abstracting the representation.+model :: MonadInfer m => (Int -> m [Bool]) -> Int -> m Int+model repr n = fmap count $ cond $ repr (n + 3)+ where+ -- Post-processing by counting the number of True values.+ count :: [Bool] -> Int+ count = length . filter id
+ models/ConjugatePriors.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeFamilies #-}++module ConjugatePriors where++import Control.Applicative (Applicative (liftA2))+import Control.Foldl (fold)+import Control.Foldl qualified as F+import Control.Monad.Bayes.Class (Bayesian (..), MonadInfer, MonadSample (bernoulli, beta, gamma, normal), normalPdf)+import Numeric.Log (Log (Exp))+import Prelude++type GammaParams = (Double, Double)++type BetaParams = (Double, Double)++type NormalParams = (Double, Double)++-- | Posterior on the precision of the normal after the points are observed+gammaNormalAnalytic ::+ (MonadInfer m, Foldable t, Functor t) =>+ GammaParams ->+ t Double ->+ m Double++-- | Exact posterior for the model.+-- For derivation see Kevin Murphy's+-- "Conjugate Bayesian analysis of the Gaussian distribution"+-- section 4.+gammaNormalAnalytic (a, b) points = gamma a' (recip b')+ where+ a' = a + fromIntegral (length points) / 2+ b' = b + sum (fmap (** 2) points) / 2++-- | Posterior on beta after the bernoulli sample+betaBernoulliAnalytic :: (MonadInfer m, Foldable t) => BetaParams -> t Bool -> m Double+betaBernoulliAnalytic (a, b) points = beta a' b'+ where+ (n, s) = fold (liftA2 (,) F.length (F.premap (\case True -> 1; False -> 0) F.sum)) points+ a' = a + s+ b' = b + fromIntegral n - s++bernoulliPdf :: Floating a => a -> Bool -> Log a+bernoulliPdf p x = let numBool = if x then 1.0 else 0 in Exp $ log (p ** numBool * (1 - p) ** (1 - numBool))++betaBernoulli' :: MonadInfer m => (Double, Double) -> Bayesian m Double Bool+betaBernoulli' (a, b) = Bayesian (beta a b) bernoulli bernoulliPdf++normalNormal' :: MonadInfer m => Double -> (Double, Double) -> Bayesian m Double Double+normalNormal' var (mu0, var0) = Bayesian (normal mu0 (sqrt var0)) (`normal` (sqrt var)) (`normalPdf` (sqrt var))++gammaNormal' :: MonadInfer m => (Double, Double) -> Bayesian m Double Double+gammaNormal' (a, b) = Bayesian (gamma a (recip b)) (normal 0 . sqrt . recip) (normalPdf 0 . sqrt . recip)++normalNormalAnalytic ::+ (MonadInfer m, Foldable t) =>+ Double ->+ NormalParams ->+ t Double ->+ m Double+normalNormalAnalytic sigma_2 (mu0, sigma0_2) points = normal mu' (sqrt sigma_2')+ where+ (n, s) = fold (liftA2 (,) F.length F.sum) points+ mu' = sigma_2' * (mu0 / sigma0_2 + s / sigma_2)+ sigma_2' = recip (recip sigma0_2 + fromIntegral n / sigma_2)
models/Dice.hs view
@@ -1,10 +1,15 @@-module Dice where+module Dice (diceHard, diceSoft) where -- A toy model for dice rolling from http://dl.acm.org/citation.cfm?id=2804317 -- Exact results can be obtained using Dist monad -import Control.Monad (liftM2)+import Control.Applicative (liftA2) import Control.Monad.Bayes.Class+ ( MonadCond (score),+ MonadInfer,+ MonadSample (uniformD),+ condition,+ ) -- | A toss of a six-sided die. die :: MonadSample m => m Int@@ -13,7 +18,7 @@ -- | A sum of outcomes of n independent tosses of six-sided dice. dice :: MonadSample m => Int -> m Int dice 1 = die-dice n = liftM2 (+) die (dice (n -1))+dice n = liftA2 (+) die (dice (n - 1)) -- | Toss of two dice where the output is greater than 4. diceHard :: MonadInfer m => m Int
models/HMM.hs view
@@ -1,20 +1,21 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE TypeFamilies #-}- -- HMM from Anglican (https://bitbucket.org/probprog/anglican-white-paper) -module HMM- ( values,- hmm,- syntheticData,- )-where----Hidden Markov Models+module HMM where -import Control.Monad (replicateM)+import Control.Monad (replicateM, when) import Control.Monad.Bayes.Class+ ( MonadCond,+ MonadInfer,+ MonadSample (categorical, normal, uniformD),+ factor,+ normalPdf,+ )+import Control.Monad.Bayes.Enumerator (enumerateToDistribution)+import Data.Maybe (fromJust, isJust) import Data.Vector (fromList)+import Pipes (MFunctor (hoist), MonadTrans (lift), each, yield, (>->))+import Pipes.Core (Producer)+import qualified Pipes.Prelude as Pipes -- | Observed values values :: [Double]@@ -70,3 +71,34 @@ syntheticData n = replicateM n syntheticPoint where syntheticPoint = uniformD [0, 1, 2]++-- | Equivalent model, but using pipes for simplicity++-- | Prior expressed as a stream+hmmPrior :: MonadSample m => Producer Int m b+hmmPrior = do+ x <- lift start+ yield x+ Pipes.unfoldr (fmap (Right . (\k -> (k, k))) . trans) x++-- | Observations expressed as a stream+hmmObservations :: Functor m => [a] -> Producer (Maybe a) m ()+hmmObservations dataset = each (Nothing : (Just <$> reverse dataset))++-- | Posterior expressed as a stream+hmmPosterior :: (MonadInfer m) => [Double] -> Producer Int m ()+hmmPosterior dataset =+ zipWithM+ hmmLikelihood+ hmmPrior+ (hmmObservations dataset)+ where+ hmmLikelihood :: MonadCond f => (Int, Maybe Double) -> f ()+ hmmLikelihood (l, o) = when (isJust o) (factor $ normalPdf (emissionMean l) 1 (fromJust o))++ zipWithM f p1 p2 = Pipes.zip p1 p2 >-> Pipes.chain f >-> Pipes.map fst++hmmPosteriorPredictive :: MonadSample m => [Double] -> Producer Double m ()+hmmPosteriorPredictive dataset =+ Pipes.hoist enumerateToDistribution (hmmPosterior dataset)+ >-> Pipes.mapM (\x -> normal (emissionMean x) 1)
models/LDA.hs view
@@ -1,21 +1,40 @@+{-# LANGUAGE ImportQualifiedPost #-}+ -- LDA model from Anglican -- (https://bitbucket.org/probprog/anglican-white-paper) +-- This model is just a toy/reference implementation.+-- A more serious one would not store documents as lists of words.+-- The point is just to showcase the model+ module LDA where -import qualified Control.Monad as List (replicateM)+import Control.Monad qualified as List (replicateM) import Control.Monad.Bayes.Class-import qualified Data.Map as Map-import Data.Vector as V hiding (length, mapM, mapM_, zip)-import Numeric.Log+ ( MonadInfer,+ MonadSample (categorical, dirichlet, uniformD),+ factor,+ )+import Control.Monad.Bayes.Sampler.Strict (sampleIO, sampleIOfixed)+import Control.Monad.Bayes.Traced (mh)+import Control.Monad.Bayes.Weighted (unweighted)+import Data.Map qualified as Map+import Data.Text (Text, words)+import Data.Vector as V (Vector, replicate, (!))+import Data.Vector qualified as V hiding (length, mapM, mapM_)+import Numeric.Log (Log (Exp))+import Text.Pretty.Simple (pPrint)+import Prelude hiding (words) -vocabulary :: [String]+vocabulary :: [Text] vocabulary = ["bear", "wolf", "python", "prolog"] -topics :: [String]+topics :: [Text] topics = ["topic1", "topic2"] -documents :: [[String]]+type Documents = [[Text]]++documents :: Documents documents = [ words "bear wolf bear wolf bear wolf python wolf bear wolf", words "python prolog python prolog python prolog python prolog python prolog",@@ -24,31 +43,42 @@ words "bear wolf bear python bear wolf bear wolf bear wolf" ] -wordDistPrior :: MonadSample m => m (Vector Double)+wordDistPrior :: MonadSample m => m (V.Vector Double) wordDistPrior = dirichlet $ V.replicate (length vocabulary) 1 -topicDistPrior :: MonadSample m => m (Vector Double)+topicDistPrior :: MonadSample m => m (V.Vector Double) topicDistPrior = dirichlet $ V.replicate (length topics) 1 -wordIndex :: Map.Map String Int+wordIndex :: Map.Map Text Int wordIndex = Map.fromList $ zip vocabulary [0 ..] -lda :: MonadInfer m => [[String]] -> m [Int]+lda ::+ MonadInfer m =>+ Documents ->+ m (Map.Map Text (V.Vector (Text, Double)), [(Text, V.Vector (Text, Double))]) lda docs = do word_dist_for_topic <- do- ts <- mapM (const wordDistPrior) [0 .. length topics]- return $ Map.fromList $ zip [0 .. length topics] ts+ ts <- List.replicateM (length topics) wordDistPrior+ return $ Map.fromList $ zip topics ts let obs doc = do- topic_dist <- fmap categorical topicDistPrior+ topic_dist <- topicDistPrior let f word = do- topic <- topic_dist+ topic <- (fmap (topics !!) . categorical) topic_dist factor $ (Exp . log) $ (word_dist_for_topic Map.! topic) V.! (wordIndex Map.! word) mapM_ f doc- mapM_ obs docs- -- return samples since Discrete is not NFData- mapM (categorical . snd) $ Map.toList word_dist_for_topic+ return topic_dist+ td <- mapM obs docs+ return+ ( fmap (V.zip (V.fromList vocabulary)) word_dist_for_topic,+ zip (fmap (foldr1 (\x y -> x <> " " <> y)) docs) (fmap (V.zip $ V.fromList ["topic1", "topic2"]) td)+ ) -syntheticData :: MonadSample m => Int -> Int -> m [[String]]+syntheticData :: MonadSample m => Int -> Int -> m [[Text]] syntheticData d w = List.replicateM d (List.replicateM w syntheticWord) where syntheticWord = uniformD vocabulary++runLDA :: IO ()+runLDA = do+ s <- sampleIOfixed $ unweighted $ mh 1000 $ lda documents+ pPrint (head s)
models/LogReg.hs view
@@ -1,35 +1,41 @@+{-# LANGUAGE BlockArguments #-}+ -- Logistic regression model from Anglican -- (https://bitbucket.org/probprog/anglican-white-paper) -module LogReg where+module LogReg (logisticRegression, syntheticData, xs, labels) where import Control.Monad (replicateM) import Control.Monad.Bayes.Class-import Numeric.Log--xs :: [Double]-xs = [-10, -5, 2, 6, 10]--labels :: [Bool]-labels = [False, False, True, True, True]+ ( MonadInfer,+ MonadSample (bernoulli, gamma, normal, uniform),+ factor,+ )+import Numeric.Log (Log (Exp)) -logisticRegression :: (MonadInfer m) => [(Double, Bool)] -> m Double+logisticRegression :: MonadInfer m => [(Double, Bool)] -> m Double logisticRegression dat = do m <- normal 0 1 b <- normal 0 1 sigma <- gamma 1 1 let y x = normal (m * x + b) sigma- sigmoid x = y x >>= \t -> return $ 1 / (1 + exp (- t))+ sigmoid x = y x >>= \t -> return $ 1 / (1 + exp (-t)) obs x label = do p <- sigmoid x factor $ (Exp . log) $ if label then p else 1 - p mapM_ (uncurry obs) dat sigmoid 8 +-- make a synthetic dataset by randomly choosing input-label pairs syntheticData :: MonadSample m => Int -> m [(Double, Bool)]-syntheticData n = replicateM n syntheticPoint- where- syntheticPoint = do- x <- uniform (-1) 1- label <- bernoulli 0.5- return (x, label)+syntheticData n = replicateM n do+ x <- uniform (-1) 1+ label <- bernoulli 0.5+ return (x, label)++-- a tiny test dataset, for sanity-checking+xs :: [Double]+xs = [-10, -5, 2, 6, 10]++labels :: [Bool]+labels = [False, False, True, True, True]
models/NonlinearSSM.hs view
@@ -1,6 +1,11 @@ module NonlinearSSM where import Control.Monad.Bayes.Class+ ( MonadInfer,+ MonadSample (gamma, normal),+ factor,+ normalPdf,+ ) param :: MonadSample m => m (Double, Double) param = do@@ -51,7 +56,7 @@ let n = length acc x' <- normal (mean x n) sigmaX y' <- normal (sq x' / 20) sigmaY- simulate (k -1) x' ((x', y') : acc)+ simulate (k - 1) x' ((x', y') : acc) x0 <- normal 0 (sqrt 5) xys <- simulate t x0 [] return $ reverse xys
models/Sprinkler.hs view
@@ -1,4 +1,4 @@-module Sprinkler where+module Sprinkler (hard, soft) where import Control.Monad (when) import Control.Monad.Bayes.Class
monad-bayes.cabal view
@@ -1,183 +1,262 @@ cabal-version: 2.0 name: monad-bayes-version: 0.1.1.0+version: 1.0.0 license: MIT license-file: LICENSE.md copyright: 2015-2020 Adam Scibior-maintainer: leonhard.markert@tweag.io+maintainer: dominic.steinitz@tweag.io author: Adam Scibior <adscib@gmail.com> stability: experimental-tested-with: ghc ==8.4.4 ghc ==8.6.5 ghc ==8.8.1+tested-with: GHC ==9.2.2 homepage: http://github.com/tweag/monad-bayes#readme bug-reports: https://github.com/tweag/monad-bayes/issues synopsis: A library for probabilistic programming. description:- A library for probabilistic programming using probability monads. The- emphasis is on composition of inference algorithms implemented in- terms of monad transformers.+ A library for probabilistic programming using probability monads. The+ emphasis is on composition of inference algorithms implemented in+ terms of monad transformers. category: Statistics build-type: Simple extra-source-files: CHANGELOG.md source-repository head- type: git- location: https://github.com/tweag/monad-bayes.git+ type: git+ location: https://github.com/tweag/monad-bayes.git flag dev- description: Turn on development settings.- default: False- manual: True+ description: Turn on development settings.+ default: False+ manual: True library- exposed-modules:- Control.Monad.Bayes.Class- Control.Monad.Bayes.Enumerator- Control.Monad.Bayes.Free- Control.Monad.Bayes.Helpers- Control.Monad.Bayes.Inference.PMMH- Control.Monad.Bayes.Inference.RMSMC- Control.Monad.Bayes.Inference.SMC- Control.Monad.Bayes.Inference.SMC2- Control.Monad.Bayes.Population- Control.Monad.Bayes.Sampler- Control.Monad.Bayes.Sequential- Control.Monad.Bayes.Traced- Control.Monad.Bayes.Traced.Basic- Control.Monad.Bayes.Traced.Dynamic- Control.Monad.Bayes.Traced.Static- Control.Monad.Bayes.Weighted+ exposed-modules:+ Control.Monad.Bayes.Class+ Control.Monad.Bayes.Density.Free+ Control.Monad.Bayes.Density.State+ Control.Monad.Bayes.Enumerator+ Control.Monad.Bayes.Inference.Lazy.MH+ Control.Monad.Bayes.Inference.Lazy.WIS+ Control.Monad.Bayes.Inference.MCMC+ Control.Monad.Bayes.Inference.PMMH+ Control.Monad.Bayes.Inference.RMSMC+ Control.Monad.Bayes.Inference.SMC+ Control.Monad.Bayes.Inference.SMC2+ Control.Monad.Bayes.Inference.TUI+ Control.Monad.Bayes.Integrator+ Control.Monad.Bayes.Population+ Control.Monad.Bayes.Sampler.Lazy+ Control.Monad.Bayes.Sampler.Strict+ Control.Monad.Bayes.Sequential.Coroutine+ Control.Monad.Bayes.Traced+ Control.Monad.Bayes.Traced.Basic+ Control.Monad.Bayes.Traced.Dynamic+ Control.Monad.Bayes.Traced.Static+ Control.Monad.Bayes.Weighted+ Math.Integrators.StormerVerlet - hs-source-dirs: src- other-modules: Control.Monad.Bayes.Traced.Common- default-language: Haskell2010- default-extensions:- MultiParamTypeClasses RankNTypes FlexibleContexts FlexibleInstances- GeneralizedNewtypeDeriving TypeFamilies StandaloneDeriving GADTs- TupleSections+ hs-source-dirs: src+ other-modules: Control.Monad.Bayes.Traced.Common+ default-language: Haskell2010+ build-depends:+ base >=4.11 && <4.17+ , brick+ , containers >=0.5.10 && <0.7+ , foldl+ , free >=5.0.2 && <5.2+ , histogram-fill+ , ieee754 ^>=0.8.0+ , integration+ , lens+ , linear+ , log-domain >=0.12 && <0.14+ , math-functions >=0.2.1 && <0.4+ , matrix+ , monad-coroutine ^>=0.9.0+ , monad-extras+ , mtl ^>=2.2.2+ , mwc-random >=0.13.6 && <0.16+ , pipes+ , pretty-simple+ , primitive+ , random+ , safe ^>=0.3.17+ , scientific+ , statistics >=0.14.0 && <0.17+ , text+ , vector ^>=0.12.0+ , vty - other-extensions: ScopedTypeVariables DeriveFunctor- build-depends:- base >=4.11 && <4.14,- containers >=0.5.10 && <0.7,- free >=5.0.2 && <5.2,- ieee754 >=0.8.0 && <0.9,- log-domain >=0.12 && <0.14,- math-functions >=0.2.1 && <0.4,- monad-coroutine >=0.9.0 && <0.10,- mtl >=2.2.2 && <2.3,- mwc-random >=0.13.6 && <0.15,- safe >=0.3.17 && <0.4,- statistics >=0.14.0 && <0.16,- transformers >=0.5.2 && <0.6,- vector >=0.12.0 && <0.13+ default-extensions:+ BlockArguments+ FlexibleContexts+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ TupleSections - if flag(dev)- ghc-options:- -Wall -Wcompat -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wnoncanonical-monad-instances+ if flag(dev)+ ghc-options:+ -O2 -Wall -Wno-missing-local-signatures -Wno-trustworthy-safe+ -Wno-missing-import-lists -Wno-implicit-prelude+ -Wno-monomorphism-restriction - else- ghc-options: -Wall+ else+ ghc-options: -Wall executable example- main-is: Single.hs- hs-source-dirs: benchmark models- other-modules:- Dice- HMM- LDA- LogReg+ main-is: Single.hs+ hs-source-dirs: benchmark models+ other-modules:+ Dice+ HMM+ LDA+ LogReg - default-language: Haskell2010- build-depends:- base -any,- containers -any,- log-domain -any,- monad-bayes -any,- mwc-random -any,- optparse-applicative -any,- time -any,- vector -any+ default-language: Haskell2010+ build-depends:+ base+ , containers+ , log-domain+ , math-functions+ , monad-bayes+ , mwc-random+ , optparse-applicative+ , pipes+ , pretty-simple+ , random+ , text+ , time+ , vector - if flag(dev)- ghc-options:- -Wall -Wcompat -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wnoncanonical-monad-instances+ if flag(dev)+ ghc-options:+ -O2 -Wall -Wcompat -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wnoncanonical-monad-instances - else- ghc-options: -Wall+ else+ ghc-options: -Wall + default-extensions:+ BlockArguments+ FlexibleContexts+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ TupleSections+ test-suite monad-bayes-test- type: exitcode-stdio-1.0- main-is: Spec.hs- hs-source-dirs: test models- other-modules:- Sprinkler- TestEnumerator- TestInference- TestPopulation- TestSequential- TestWeighted+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs: test models+ other-modules:+ BetaBin+ ConjugatePriors+ HMM+ Sprinkler+ TestAdvanced+ TestDistribution+ TestEnumerator+ TestInference+ TestIntegrator+ TestPipes+ TestPopulation+ TestSampler+ TestSequential+ TestStormerVerlet+ TestWeighted - default-language: Haskell2010- build-depends:- base -any,- QuickCheck -any,- hspec -any,- ieee754 -any,- log-domain -any,- math-functions -any,- monad-bayes -any,- mtl -any,- transformers -any,- vector -any+ default-language: Haskell2010+ build-depends:+ base+ , containers+ , foldl+ , hspec+ , ieee754+ , lens+ , linear+ , log-domain+ , math-functions+ , matrix+ , monad-bayes+ , mtl+ , mwc-random+ , pipes+ , pretty-simple+ , profunctors+ , QuickCheck+ , random+ , statistics+ , text+ , transformers+ , vector - if flag(dev)- ghc-options:- -Wall -Wcompat -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wnoncanonical-monad-instances+ if flag(dev)+ ghc-options:+ -Wall -Wno-missing-local-signatures -Wno-unsafe+ -Wno-missing-import-lists -Wno-implicit-prelude - else- ghc-options: -Wall+ else+ ghc-options: -Wall + default-extensions:+ BlockArguments+ FlexibleContexts+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ TupleSections+ benchmark ssm-bench- type: exitcode-stdio-1.0- main-is: SSM.hs- hs-source-dirs: models benchmark- other-modules: NonlinearSSM- default-language: Haskell2010- default-extensions: RankNTypes- build-depends:- base -any,- monad-bayes -any+ type: exitcode-stdio-1.0+ main-is: SSM.hs+ hs-source-dirs: models benchmark+ other-modules: NonlinearSSM+ default-language: Haskell2010+ build-depends:+ base+ , monad-bayes+ , pretty-simple+ , random benchmark speed-bench- type: exitcode-stdio-1.0- main-is: Speed.hs- hs-source-dirs: models benchmark- other-modules:- HMM- LDA- LogReg+ type: exitcode-stdio-1.0+ main-is: Speed.hs+ hs-source-dirs: models benchmark+ other-modules:+ HMM+ LDA+ LogReg - default-language: Haskell2010- default-extensions: RankNTypes- build-depends:- base -any,- abstract-par -any,- containers -any,- criterion -any,- log-domain -any,- monad-bayes -any,- mwc-random -any,- process -any,- vector -any+ default-language: Haskell2010+ build-depends:+ abstract-par+ , base+ , containers+ , criterion+ , log-domain+ , monad-bayes+ , mwc-random+ , pipes+ , pretty-simple+ , process+ , random+ , text+ , typed-process+ , vector - if flag(dev)- ghc-options:- -Wall -Wcompat -Wincomplete-record-updates- -Wincomplete-uni-patterns -Wnoncanonical-monad-instances+ if flag(dev)+ ghc-options:+ -Wall -Wno-missing-local-signatures -Wno-unsafe+ -Wno-missing-import-lists -Wno-implicit-prelude - else- ghc-options: -Wall+ else+ ghc-options: -Wall++ default-extensions:+ BlockArguments+ FlexibleContexts+ ImportQualifiedPost+ LambdaCase+ OverloadedStrings+ TupleSections
src/Control/Monad/Bayes/Class.hs view
@@ -1,3 +1,8 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# OPTIONS_GHC -Wno-deprecations #-}+ -- | -- Module : Control.Monad.Bayes.Class -- Description : Types for probabilistic modelling@@ -7,8 +12,8 @@ -- Stability : experimental -- Portability : GHC ----- This module defines 'MonadInfer', which can be used to represent a simple model--- like the following:+-- This module defines 'MonadInfer', which can be used to represent any probabilistic program,+-- such as the following: -- -- @ -- import Control.Monad (when)@@ -52,28 +57,52 @@ MonadInfer, discrete, normalPdf,+ Bayesian (..),+ posterior,+ priorPredictive,+ posteriorPredictive,+ independent,+ mvNormal,+ Histogram,+ histogram,+ histogramToList,+ Distribution,+ Measure,+ Kernel,+ Log (ln, Exp), ) where -import Control.Monad (when)-import Control.Monad.Trans.Class-import Control.Monad.Trans.Cont-import Control.Monad.Trans.Identity-import Control.Monad.Trans.List-import Control.Monad.Trans.Maybe-import Control.Monad.Trans.RWS hiding (tell)-import Control.Monad.Trans.Reader-import Control.Monad.Trans.State-import Control.Monad.Trans.Writer-import qualified Data.Vector as V-import Data.Vector.Generic as VG-import Numeric.Log+import Control.Arrow (Arrow (second))+import Control.Monad (replicateM, when)+import Control.Monad.Cont (ContT)+import Control.Monad.Except (ExceptT, lift)+import Control.Monad.Identity (IdentityT)+import Control.Monad.List (ListT)+import Control.Monad.Reader (ReaderT)+import Control.Monad.State (StateT)+import Control.Monad.Writer (WriterT)+import Data.Histogram qualified as H+import Data.Histogram.Fill qualified as H+import Data.Matrix+ ( Matrix,+ cholDecomp,+ colVector,+ getCol,+ multStd,+ )+import Data.Vector qualified as V+import Data.Vector.Generic as VG (Vector, map, mapM, null, sum, (!))+import Numeric.Log (Log (..)) import Statistics.Distribution+ ( ContDistr (logDensity, quantile),+ DiscreteDistr (probability),+ ) import Statistics.Distribution.Beta (betaDistr) import Statistics.Distribution.Gamma (gammaDistr) import Statistics.Distribution.Geometric (geometric0) import Statistics.Distribution.Normal (normalDistr)-import qualified Statistics.Distribution.Poisson as Poisson+import Statistics.Distribution.Poisson qualified as Poisson import Statistics.Distribution.Uniform (uniformDistr) -- | Monads that can draw random variables.@@ -138,7 +167,7 @@ v Double -> -- | outcome category m Int- categorical ps = fromPMF (ps !)+ categorical ps = if VG.null ps then error "empty input list" else fromPMF (ps !) -- | Draw from a categorical distribution in the log domain. logCategorical ::@@ -226,10 +255,21 @@ m () factor = score +-- | synonym for pretty type signatures, but note that (A -> Distribution B) won't work as intended: for that, use Kernel+-- Also note that the use of RankNTypes means performance may take a hit: really the main point of these signatures is didactic+type Distribution a = forall m. MonadSample m => m a++type Measure a = forall m. MonadInfer m => m a++type Kernel a b = forall m. MonadInfer m => a -> m b+ -- | Hard conditioning. condition :: MonadCond m => Bool -> m () condition b = score $ if b then 1 else 0 +independent :: Applicative m => Int -> m a -> m [a]+independent = replicateM+ -- | Monads that support both sampling and scoring. class (MonadSample m, MonadCond m) => MonadInfer m @@ -245,6 +285,55 @@ Log Double normalPdf mu sigma x = Exp $ logDensity (normalDistr mu sigma) x +-- | multivariate normal+mvNormal :: MonadSample m => V.Vector Double -> Matrix Double -> m (V.Vector Double)+mvNormal mu bigSigma = do+ let n = length mu+ ss <- replicateM n (normal 0 1)+ let bigL = cholDecomp bigSigma+ let ts = (colVector mu) + bigL `multStd` (colVector $ V.fromList ss)+ return $ getCol 1 ts++-- | a useful datatype for expressing bayesian models+data Bayesian m z o = Bayesian+ { prior :: m z, -- prior over latent variable Z+ generative :: z -> m o, -- distribution over observations given Z=z+ likelihood :: z -> o -> Log Double -- p(o|z)+ }++posterior :: (MonadInfer m, Foldable f, Functor f) => Bayesian m z o -> f o -> m z+posterior Bayesian {..} os = do+ z <- prior+ factor $ product $ fmap (likelihood z) os+ return z++priorPredictive :: Monad m => Bayesian m a b -> m b+priorPredictive bm = prior bm >>= generative bm++posteriorPredictive ::+ (MonadInfer m, Foldable f, Functor f) =>+ Bayesian m a b ->+ f b ->+ m b+posteriorPredictive bm os = posterior bm os >>= generative bm++-- helper funcs+--------------------++type Histogram = H.Histogram H.BinD Double++histogram :: Int -> [(Double, Log Double)] -> Histogram+histogram n v = H.fillBuilder buildr $ fmap (second (ln . exp)) v+ where+ v1 = fmap fst v+ mi = Prelude.minimum v1+ ma = Prelude.maximum v1+ bins = H.binD mi n ma+ buildr = H.mkWeighted bins++histogramToList :: Histogram -> [(Double, Double)]+histogramToList = H.asList+ ---------------------------------------------------------------------------- -- Instances that lift probabilistic effects to standard tranformers. @@ -257,13 +346,14 @@ instance MonadInfer m => MonadInfer (IdentityT m) -instance MonadSample m => MonadSample (MaybeT m) where+instance MonadSample m => MonadSample (ExceptT e m) where random = lift random+ uniformD = lift . uniformD -instance MonadCond m => MonadCond (MaybeT m) where+instance MonadCond m => MonadCond (ExceptT e m) where score = lift . score -instance MonadInfer m => MonadInfer (MaybeT m)+instance MonadInfer m => MonadInfer (ExceptT e m) instance MonadSample m => MonadSample (ReaderT r m) where random = lift random@@ -288,19 +378,12 @@ random = lift random bernoulli = lift . bernoulli categorical = lift . categorical+ uniformD = lift . uniformD instance MonadCond m => MonadCond (StateT s m) where score = lift . score instance MonadInfer m => MonadInfer (StateT s m)--instance (MonadSample m, Monoid w) => MonadSample (RWST r w s m) where- random = lift random--instance (MonadCond m, Monoid w) => MonadCond (RWST r w s m) where- score = lift . score--instance (MonadInfer m, Monoid w) => MonadInfer (RWST r w s m) instance MonadSample m => MonadSample (ListT m) where random = lift random
+ src/Control/Monad/Bayes/Density/Free.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}++-- |+-- Module : Control.Monad.Bayes.Density.Free+-- Description : Free monad transformer over random sampling+-- Copyright : (c) Adam Scibior, 2015-2020+-- License : MIT+-- Maintainer : leonhard.markert@tweag.io+-- Stability : experimental+-- Portability : GHC+--+-- 'Density' is a free monad transformer over random sampling.+module Control.Monad.Bayes.Density.Free+ ( Density,+ hoist,+ interpret,+ withRandomness,+ density,+ traced,+ )+where++import Control.Monad.Bayes.Class (MonadSample (random))+import Control.Monad.RWS+import Control.Monad.State (evalStateT)+import Control.Monad.Trans.Free.Church (FT, MonadFree (..), hoistFT, iterT, iterTM, liftF)+import Control.Monad.Writer (WriterT (..))+import Data.Functor.Identity (Identity, runIdentity)++-- | Random sampling functor.+newtype SamF a = Random (Double -> a) deriving (Functor)++-- | Free monad transformer over random sampling.+--+-- Uses the Church-encoded version of the free monad for efficiency.+newtype Density m a = Density {runDensity :: FT SamF m a}+ deriving newtype (Functor, Applicative, Monad, MonadTrans)++instance MonadFree SamF (Density m) where+ wrap = Density . wrap . fmap runDensity++instance Monad m => MonadSample (Density m) where+ random = Density $ liftF (Random id)++-- | Hoist 'Density' through a monad transform.+hoist :: (Monad m, Monad n) => (forall x. m x -> n x) -> Density m a -> Density n a+hoist f (Density m) = Density (hoistFT f m)++-- | Execute random sampling in the transformed monad.+interpret :: MonadSample m => Density m a -> m a+interpret (Density m) = iterT f m+ where+ f (Random k) = random >>= k++-- | Execute computation with supplied values for random choices.+withRandomness :: Monad m => [Double] -> Density m a -> m a+withRandomness randomness (Density m) = evalStateT (iterTM f m) randomness+ where+ f (Random k) = do+ xs <- get+ case xs of+ [] -> error "Density: the list of randomness was too short"+ y : ys -> put ys >> k y++-- | Execute computation with supplied values for a subset of random choices.+-- Return the output value and a record of all random choices used, whether+-- taken as input or drawn using the transformed monad.+density :: MonadSample m => [Double] -> Density m a -> m (a, [Double])+density randomness (Density m) =+ runWriterT $ evalStateT (iterTM f $ hoistFT lift m) randomness+ where+ f (Random k) = do+ -- This block runs in StateT [Double] (WriterT [Double]) m.+ -- StateT propagates consumed randomness while WriterT records+ -- randomness used, whether old or new.+ xs <- get+ x <- case xs of+ [] -> random+ y : ys -> put ys >> return y+ tell [x]+ k x++-- | Like 'density', but use an arbitrary sampling monad.+traced :: MonadSample m => [Double] -> Density Identity a -> m (a, [Double])+traced randomness m = density randomness $ hoist (return . runIdentity) m
+ src/Control/Monad/Bayes/Density/State.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- |+-- Slower than Control.Monad.Bayes.Density.Free, so not used by default,+-- but more elementary to understand. Just uses standard+-- monad transformer techniques.+module Control.Monad.Bayes.Density.State where++import Control.Monad.Bayes.Class (MonadSample (random))+import Control.Monad.State (MonadState (get, put), StateT, evalStateT)+import Control.Monad.Writer++newtype Density m a = Density {runDensity :: WriterT [Double] (StateT [Double] m) a} deriving newtype (Functor, Applicative, Monad)++instance MonadTrans Density where+ lift = Density . lift . lift++instance Monad m => MonadState [Double] (Density m) where+ get = Density $ lift $ get+ put = Density . lift . put++instance Monad m => MonadWriter [Double] (Density m) where+ tell = Density . tell+ listen = Density . listen . runDensity+ pass = Density . pass . runDensity++instance MonadSample m => MonadSample (Density m) where+ random = do+ trace <- get+ x <- case trace of+ [] -> random+ r : xs -> put xs >> pure r+ tell [x]+ pure x++density :: Monad m => Density m b -> [Double] -> m (b, [Double])+density (Density m) = evalStateT (runWriterT m)
src/Control/Monad/Bayes/Enumerator.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+ -- | -- Module : Control.Monad.Bayes.Enumerator -- Description : Exhaustive enumeration of discrete random variables@@ -13,28 +17,40 @@ evidence, mass, compact,+ enumerator, enumerate, expectation, normalForm,+ toEmpirical,+ toEmpiricalWeighted,+ normalizeWeights,+ enumerateToDistribution,+ removeZeros,+ fromList, ) where import Control.Applicative (Alternative) import Control.Arrow (second)-import Control.Monad (MonadPlus) import Control.Monad.Bayes.Class-import Control.Monad.Trans.Writer-import Data.AEq ((===), AEq, (~==))-import qualified Data.Map as Map-import Data.Maybe-import Data.Monoid-import qualified Data.Vector.Generic as V-import Numeric.Log as Log+ ( MonadCond (..),+ MonadInfer,+ MonadSample (bernoulli, categorical, logCategorical, random),+ )+import Control.Monad.Writer+import Data.AEq (AEq, (===), (~==))+import Data.List (sortOn)+import Data.Map qualified as Map+import Data.Maybe (fromMaybe)+import Data.Ord (Down (Down))+import Data.Vector qualified as VV+import Data.Vector.Generic qualified as V+import Numeric.Log as Log (Log (..), sum) -- | An exact inference transformer that integrates -- discrete random variables by enumerating all execution paths. newtype Enumerator a = Enumerator (WriterT (Product (Log Double)) [] a)- deriving (Functor, Applicative, Monad, Alternative, MonadPlus)+ deriving newtype (Functor, Applicative, Monad, Alternative, MonadPlus) instance MonadSample Enumerator where random = error "Infinitely supported random variables not supported in Enumerator"@@ -68,33 +84,36 @@ mass d = f where f a = fromMaybe 0 $ lookup a m- m = enumerate d+ m = enumerator d -- | Aggregate weights of equal values. -- The resulting list is sorted ascendingly according to values.-compact :: (Num r, Ord a) => [(a, r)] -> [(a, r)]-compact = Map.toAscList . Map.fromListWith (+)+compact :: (Num r, Ord a, Ord r) => [(a, r)] -> [(a, r)]+compact = sortOn (Down . snd) . Map.toAscList . Map.fromListWith (+) -- | Aggregate and normalize of weights. -- The resulting list is sorted ascendingly according to values. ----- > enumerate = compact . explicit-enumerate :: Ord a => Enumerator a -> [(a, Double)]-enumerate d = compact (zip xs ws)+-- > enumerator = compact . explicit+enumerator, enumerate :: Ord a => Enumerator a -> [(a, Double)]+enumerator d = filter ((/= 0) . snd) $ compact (zip xs ws) where (xs, ws) = second (map (exp . ln) . normalize) $ unzip (logExplicit d) +-- | deprecated synonym+enumerate = enumerator+ -- | Expectation of a given function computed using normalized weights. expectation :: (a -> Double) -> Enumerator a -> Double expectation f = Prelude.sum . map (\(x, w) -> f x * (exp . ln) w) . normalizeWeights . logExplicit -normalize :: [Log Double] -> [Log Double]+normalize :: Fractional b => [b] -> [b] normalize xs = map (/ z) xs where- z = Log.sum xs+ z = Prelude.sum xs -- | Divide all weights by their sum.-normalizeWeights :: [(a, Log Double)] -> [(a, Log Double)]+normalizeWeights :: Fractional b => [(a, b)] -> [(a, b)] normalizeWeights ls = zip xs ps where (xs, ws) = unzip ls@@ -103,6 +122,22 @@ -- | 'compact' followed by removing values with zero weight. normalForm :: Ord a => Enumerator a -> [(a, Double)] normalForm = filter ((/= 0) . snd) . compact . explicit++toEmpirical :: (Fractional b, Ord a, Ord b) => [a] -> [(a, b)]+toEmpirical ls = normalizeWeights $ compact (zip ls (repeat 1))++toEmpiricalWeighted :: (Fractional b, Ord a, Ord b) => [(a, b)] -> [(a, b)]+toEmpiricalWeighted = normalizeWeights . compact++enumerateToDistribution :: (MonadSample n) => Enumerator a -> n a+enumerateToDistribution model = do+ let samples = logExplicit model+ let (support, logprobs) = unzip samples+ i <- logCategorical $ VV.fromList logprobs+ return $ support !! i++removeZeros :: Enumerator a -> Enumerator a+removeZeros (Enumerator (WriterT a)) = Enumerator $ WriterT $ filter ((\(Product x) -> x /= 0) . snd) a instance Ord a => Eq (Enumerator a) where p == q = normalForm p == normalForm q
− src/Control/Monad/Bayes/Free.hs
@@ -1,86 +0,0 @@--- |--- Module : Control.Monad.Bayes.Free--- Description : Free monad transformer over random sampling--- Copyright : (c) Adam Scibior, 2015-2020--- License : MIT--- Maintainer : leonhard.markert@tweag.io--- Stability : experimental--- Portability : GHC------ 'FreeSampler' is a free monad transformer over random sampling.-module Control.Monad.Bayes.Free- ( FreeSampler,- hoist,- interpret,- withRandomness,- withPartialRandomness,- runWith,- )-where--import Control.Monad.Bayes.Class-import Control.Monad.State (evalStateT, get, put)-import Control.Monad.Trans (MonadTrans (..))-import Control.Monad.Trans.Free.Church (FT, MonadFree (..), hoistFT, iterT, iterTM, liftF)-import Control.Monad.Writer (WriterT (..), tell)-import Data.Functor.Identity (Identity, runIdentity)---- | Random sampling functor.-newtype SamF a = Random (Double -> a)--instance Functor SamF where- fmap f (Random k) = Random (f . k)---- | Free monad transformer over random sampling.------ Uses the Church-encoded version of the free monad for efficiency.-newtype FreeSampler m a = FreeSampler {runFreeSampler :: FT SamF m a}- deriving (Functor, Applicative, Monad, MonadTrans)--instance Monad m => MonadFree SamF (FreeSampler m) where- wrap = FreeSampler . wrap . fmap runFreeSampler--instance Monad m => MonadSample (FreeSampler m) where- random = FreeSampler $ liftF (Random id)---- | Hoist 'FreeSampler' through a monad transform.-hoist :: (Monad m, Monad n) => (forall x. m x -> n x) -> FreeSampler m a -> FreeSampler n a-hoist f (FreeSampler m) = FreeSampler (hoistFT f m)---- | Execute random sampling in the transformed monad.-interpret :: MonadSample m => FreeSampler m a -> m a-interpret (FreeSampler m) = iterT f m- where- f (Random k) = random >>= k---- | Execute computation with supplied values for random choices.-withRandomness :: Monad m => [Double] -> FreeSampler m a -> m a-withRandomness randomness (FreeSampler m) = evalStateT (iterTM f m) randomness- where- f (Random k) = do- xs <- get- case xs of- [] -> error "FreeSampler: the list of randomness was too short"- y : ys -> put ys >> k y---- | Execute computation with supplied values for a subset of random choices.--- Return the output value and a record of all random choices used, whether--- taken as input or drawn using the transformed monad.-withPartialRandomness :: MonadSample m => [Double] -> FreeSampler m a -> m (a, [Double])-withPartialRandomness randomness (FreeSampler m) =- runWriterT $ evalStateT (iterTM f $ hoistFT lift m) randomness- where- f (Random k) = do- -- This block runs in StateT [Double] (WriterT [Double]) m.- -- StateT propagates consumed randomness while WriterT records- -- randomness used, whether old or new.- xs <- get- x <- case xs of- [] -> random- y : ys -> put ys >> return y- tell [x]- k x---- | Like 'withPartialRandomness', but use an arbitrary sampling monad.-runWith :: MonadSample m => [Double] -> FreeSampler Identity a -> m (a, [Double])-runWith randomness m = withPartialRandomness randomness $ hoist (return . runIdentity) m
− src/Control/Monad/Bayes/Helpers.hs
@@ -1,77 +0,0 @@--- |--- Module : Control.Monad.Bayes.Helpers--- Description : Helper functions for working with inference monads--- Copyright : (c) Adam Scibior, 2015-2020--- License : MIT--- Maintainer : leonhard.markert@tweag.io--- Stability : experimental--- Portability : GHC-module Control.Monad.Bayes.Helpers- ( W,- hoistW,- P,- hoistP,- S,- hoistS,- F,- hoistF,- T,- hoistT,- hoistWF,- hoistSP,- hoistSTP,- )-where--import Control.Monad.Bayes.Free as Free-import Control.Monad.Bayes.Population as Pop-import Control.Monad.Bayes.Sequential as Seq-import Control.Monad.Bayes.Traced as Tr-import Control.Monad.Bayes.Weighted as Weighted--type W = Weighted--type P = Population--type S = Sequential--type F = FreeSampler--type T = Traced--hoistW :: (forall x. m x -> n x) -> W m a -> W n a-hoistW = Weighted.hoist--hoistP ::- (Monad m, Monad n) =>- (forall x. m x -> n x) ->- P m a ->- P n a-hoistP = Pop.hoist--hoistS :: (forall x. m x -> m x) -> S m a -> S m a-hoistS = Seq.hoistFirst--hoistF :: (Monad m, Monad n) => (forall x. m x -> n x) -> F m a -> F n a-hoistF = Free.hoist--hoistWF ::- (Monad m, Monad n) =>- (forall x. m x -> n x) ->- W (F m) a ->- W (F n) a-hoistWF m = hoistW $ hoistF m--hoistSP ::- Monad m =>- (forall x. m x -> m x) ->- S (P m) a ->- S (P m) a-hoistSP m = hoistS $ hoistP m--hoistSTP ::- Monad m =>- (forall x. m x -> m x) ->- S (T (P m)) a ->- S (T (P m)) a-hoistSTP m = hoistS $ hoistT $ hoistP m
+ src/Control/Monad/Bayes/Inference/Lazy/MH.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-}++module Control.Monad.Bayes.Inference.Lazy.MH where++import Control.Monad.Bayes.Class (Log (ln))+import Control.Monad.Bayes.Sampler.Lazy+ ( Sampler (runSampler),+ Tree (..),+ Trees (..),+ randomTree,+ )+import Control.Monad.Bayes.Weighted (Weighted, weighted)+import Control.Monad.Extra (iterateM)+import Control.Monad.State.Lazy (MonadState (get, put), runState)+import System.Random (RandomGen (split), getStdGen, newStdGen)+import System.Random qualified as R++mh :: forall a. Double -> Weighted Sampler a -> IO [(a, Log Double)]+mh p m = do+ -- Top level: produce a stream of samples.+ -- Split the random number generator in two+ -- One part is used as the first seed for the simulation,+ -- and one part is used for the randomness in the MH algorithm.+ g <- newStdGen >> getStdGen+ let (g1, g2) = split g+ let t = randomTree g1+ let (x, w) = runSampler (weighted m) t+ -- Now run step over and over to get a stream of (tree,result,weight)s.+ let (samples, _) = runState (iterateM step (t, x, w)) g2+ -- The stream of seeds is used to produce a stream of result/weight pairs.+ return $ map (\(_, x, w) -> (x, w)) samples+ where+ -- where+ {- NB There are three kinds of randomness in the step function.+ 1. The start tree 't', which is the source of randomness for simulating the+ program m to start with. This is sort-of the point in the "state space".+ 2. The randomness needed to propose a new tree ('g1')+ 3. The randomness needed to decide whether to accept or reject that ('g2')+ The tree t is an argument and result,+ but we use a state monad ('get'/'put') to deal with the other randomness '(g,g1,g2)' -}++ -- step :: RandomGen g => (Tree, a, Log Double) -> State g (Tree, a, Log Double)+ step (t, x, w) = do+ -- Randomly change some sites+ g <- get+ let (g1, g2) = split g+ let t' = mutateTree p g1 t+ -- Rerun the model with the new tree, to get a new+ -- weight w'.+ let (x', w') = runSampler (weighted m) t'+ -- MH acceptance ratio. This is the probability of either+ -- returning the new seed or the old one.+ let ratio = w' / w+ let (r, g2') = R.random g2+ put g2'+ if r < min 1 (exp $ ln ratio)+ then return (t', x', w')+ else return (t, x, w)++-- Replace the labels of a tree randomly, with probability p+mutateTree :: forall g. RandomGen g => Double -> g -> Tree -> Tree+mutateTree p g (Tree a ts) =+ let (a', g') = (R.random g :: (Double, g))+ (a'', g'') = R.random g'+ in Tree+ { currentUniform = if a' < p then a'' else a,+ lazyUniforms = mutateTrees p g'' ts+ }++mutateTrees :: RandomGen g => Double -> g -> Trees -> Trees+mutateTrees p g (Trees t ts) =+ let (g1, g2) = split g+ in Trees+ { headTree = mutateTree p g1 t,+ tailTrees = mutateTrees p g2 ts+ }
+ src/Control/Monad/Bayes/Inference/Lazy/WIS.hs view
@@ -0,0 +1,23 @@+module Control.Monad.Bayes.Inference.Lazy.WIS where++import Control.Monad.Bayes.Sampler.Lazy (Sampler, weightedsamples)+import Control.Monad.Bayes.Weighted (Weighted)+import Numeric.Log (Log (Exp))+import System.Random (Random (randoms), getStdGen, newStdGen)++-- | Weighted Importance Sampling++-- | Likelihood weighted importance sampling first draws n weighted samples,+-- and then samples a stream of results from that regarded as an empirical distribution+lwis :: Int -> Weighted Sampler a -> IO [a]+lwis n m = do+ xws <- weightedsamples m+ let xws' = take n $ accumulate xws 0+ let max' = snd $ last xws'+ _ <- newStdGen+ rs <- randoms <$> getStdGen+ return $ fmap (\r -> fst $ head $ filter ((>= Exp (log r) * max') . snd) xws') rs+ where+ accumulate :: Num t => [(a, t)] -> t -> [(a, t)]+ accumulate ((x, w) : xws) a = (x, w + a) : (x, w + a) : accumulate xws (w + a)+ accumulate [] _ = []
+ src/Control/Monad/Bayes/Inference/MCMC.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}++-- |+-- Module : Control.Monad.Bayes.Inference.MCMC+-- Description : Markov Chain Monte Carlo (MCMC)+-- Copyright : (c) Adam Scibior, 2015-2020+-- License : MIT+-- Maintainer : tweag.io+-- Stability : experimental+-- Portability : GHC+module Control.Monad.Bayes.Inference.MCMC where++import Control.Monad.Bayes.Class+import qualified Control.Monad.Bayes.Traced.Basic as Basic+import Control.Monad.Bayes.Traced.Common+import qualified Control.Monad.Bayes.Traced.Dynamic as Dynamic+import qualified Control.Monad.Bayes.Traced.Static as Static+import Control.Monad.Bayes.Weighted+import Pipes ((>->))+import qualified Pipes as P+import qualified Pipes.Prelude as P++data Proposal = SingleSiteMH++data MCMCConfig = MCMCConfig {proposal :: Proposal, numMCMCSteps :: Int, numBurnIn :: Int}++defaultMCMCConfig :: MCMCConfig+defaultMCMCConfig = MCMCConfig {proposal = SingleSiteMH, numMCMCSteps = 1, numBurnIn = 0}++mcmc :: MonadSample m => MCMCConfig -> Static.Traced (Weighted m) a -> m [a]+mcmc (MCMCConfig {..}) m = burnIn numBurnIn $ unweighted $ Static.mh numMCMCSteps m++mcmcBasic :: MonadSample m => MCMCConfig -> Basic.Traced (Weighted m) a -> m [a]+mcmcBasic (MCMCConfig {..}) m = burnIn numBurnIn $ unweighted $ Basic.mh numMCMCSteps m++mcmcDynamic :: MonadSample m => MCMCConfig -> Dynamic.Traced (Weighted m) a -> m [a]+mcmcDynamic (MCMCConfig {..}) m = burnIn numBurnIn $ unweighted $ Dynamic.mh numMCMCSteps m++-- -- | draw iid samples until you get one that has non-zero likelihood+independentSamples :: Monad m => Static.Traced m a -> P.Producer (MHResult a) m (Trace a)+independentSamples (Static.Traced w d) =+ P.repeatM d+ >-> P.takeWhile' ((== 0) . probDensity)+ >-> P.map (MHResult False)++-- | convert a probabilistic program into a producer of samples+mcmcP :: MonadSample m => MCMCConfig -> Static.Traced m a -> P.Producer (MHResult a) m ()+mcmcP MCMCConfig {..} m@(Static.Traced w _) = do+ initialValue <- independentSamples m >-> P.drain+ ( P.unfoldr (fmap (Right . (\k -> (k, trace k))) . mhTransWithBool w) initialValue+ >-> P.drop numBurnIn+ )
src/Control/Monad/Bayes/Inference/PMMH.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE RankNTypes #-}+ -- | -- Module : Control.Monad.Bayes.Inference.PMMH -- Description : Particle Marginal Metropolis-Hastings (PMMH)@@ -12,30 +14,49 @@ -- Christophe Andrieu, Arnaud Doucet, and Roman Holenstein. 2010. Particle Markov chain Monte Carlo Methods. /Journal of the Royal Statistical Society/ 72 (2010), 269-342. <http://www.stats.ox.ac.uk/~doucet/andrieu_doucet_holenstein_PMCMC.pdf> module Control.Monad.Bayes.Inference.PMMH ( pmmh,+ pmmhBayesianModel, ) where -import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Inference.SMC+import Control.Monad.Bayes.Class (Bayesian (generative), MonadInfer, MonadSample, prior)+import Control.Monad.Bayes.Inference.MCMC (MCMCConfig, mcmc)+import Control.Monad.Bayes.Inference.SMC (SMCConfig (), smc) import Control.Monad.Bayes.Population as Pop-import Control.Monad.Bayes.Sequential-import Control.Monad.Bayes.Traced+ ( Population,+ hoist,+ population,+ pushEvidence,+ )+import Control.Monad.Bayes.Sequential.Coroutine (Sequential)+import Control.Monad.Bayes.Traced.Static (Traced)+import Control.Monad.Bayes.Weighted import Control.Monad.Trans (lift)-import Numeric.Log+import Numeric.Log (Log) -- | Particle Marginal Metropolis-Hastings sampling. pmmh ::+ MonadSample m =>+ MCMCConfig ->+ SMCConfig (Weighted m) ->+ Traced (Weighted m) a1 ->+ (a1 -> Sequential (Population (Weighted m)) a2) ->+ m [[(a2, Log Double)]]+pmmh mcmcConf smcConf param model =+ mcmc+ mcmcConf+ ( param+ >>= population+ . pushEvidence+ . Pop.hoist lift+ . smc smcConf+ . model+ )++-- | Particle Marginal Metropolis-Hastings sampling from a Bayesian model+pmmhBayesianModel :: MonadInfer m =>- -- | number of Metropolis-Hastings steps- Int ->- -- | number of time steps- Int ->- -- | number of particles- Int ->- -- | model parameters prior- Traced m b ->- -- | model- (b -> Sequential (Population m) a) ->- m [[(a, Log Double)]]-pmmh t k n param model =- mh t (param >>= runPopulation . pushEvidence . Pop.hoist lift . smcSystematic k n . model)+ MCMCConfig ->+ SMCConfig (Weighted m) ->+ (forall m'. MonadInfer m' => Bayesian m' a1 a2) ->+ m [[(a2, Log Double)]]+pmmhBayesianModel mcmcConf smcConf bm = pmmh mcmcConf smcConf (prior bm) (generative bm)
src/Control/Monad/Bayes/Inference/RMSMC.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Control.Monad.Bayes.Inference.RMSMC -- Description : Resample-Move Sequential Monte Carlo (RM-SMC)@@ -12,73 +15,76 @@ -- Walter Gilks and Carlo Berzuini. 2001. Following a moving target - Monte Carlo inference for dynamic Bayesian models. /Journal of the Royal Statistical Society/ 63 (2001), 127-146. <http://www.mathcs.emory.edu/~whalen/Papers/BNs/MonteCarlo-DBNs.pdf> module Control.Monad.Bayes.Inference.RMSMC ( rmsmc,- rmsmcLocal,+ rmsmcDynamic, rmsmcBasic, ) where -import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Helpers+import Control.Monad.Bayes.Class (MonadSample)+import Control.Monad.Bayes.Inference.MCMC (MCMCConfig (..))+import Control.Monad.Bayes.Inference.SMC import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sequential as Seq-import Control.Monad.Bayes.Traced as Tr-import qualified Control.Monad.Bayes.Traced.Basic as TrBas-import qualified Control.Monad.Bayes.Traced.Dynamic as TrDyn+ ( Population,+ spawn,+ withParticles,+ )+import Control.Monad.Bayes.Sequential.Coroutine as Seq+import Control.Monad.Bayes.Sequential.Coroutine qualified as S+import Control.Monad.Bayes.Traced.Basic qualified as TrBas+import Control.Monad.Bayes.Traced.Dynamic qualified as TrDyn+import Control.Monad.Bayes.Traced.Static as Tr+ ( Traced,+ marginal,+ mhStep,+ )+import Control.Monad.Bayes.Traced.Static qualified as TrStat+import Data.Monoid (Endo (..)) -- | Resample-move Sequential Monte Carlo. rmsmc :: MonadSample m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | number of Metropolis-Hastings transitions after each resampling- Int ->+ MCMCConfig ->+ SMCConfig m -> -- | model Sequential (Traced (Population m)) a -> Population m a-rmsmc k n t =+rmsmc (MCMCConfig {..}) (SMCConfig {..}) = marginal- . sis (composeCopies t mhStep . hoistT resampleSystematic) k- . hoistS (hoistT (spawn n >>))+ . S.sequentially (composeCopies numMCMCSteps mhStep . TrStat.hoist resampler) numSteps+ . S.hoistFirst (TrStat.hoist (spawn numParticles >>)) -- | Resample-move Sequential Monte Carlo with a more efficient -- tracing representation. rmsmcBasic :: MonadSample m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | number of Metropolis-Hastings transitions after each resampling- Int ->+ MCMCConfig ->+ SMCConfig m -> -- | model Sequential (TrBas.Traced (Population m)) a -> Population m a-rmsmcBasic k n t =+rmsmcBasic (MCMCConfig {..}) (SMCConfig {..}) = TrBas.marginal- . sis (composeCopies t TrBas.mhStep . TrBas.hoistT resampleSystematic) k- . hoistS (TrBas.hoistT (spawn n >>))+ . S.sequentially (composeCopies numMCMCSteps TrBas.mhStep . TrBas.hoist resampler) numSteps+ . S.hoistFirst (TrBas.hoist (withParticles numParticles)) -- | A variant of resample-move Sequential Monte Carlo -- where only random variables since last resampling are considered -- for rejuvenation.-rmsmcLocal ::+rmsmcDynamic :: MonadSample m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | number of Metropolis-Hastings transitions after each resampling- Int ->+ MCMCConfig ->+ SMCConfig m -> -- | model Sequential (TrDyn.Traced (Population m)) a -> Population m a-rmsmcLocal k n t =+rmsmcDynamic (MCMCConfig {..}) (SMCConfig {..}) = TrDyn.marginal- . sis (TrDyn.freeze . composeCopies t TrDyn.mhStep . TrDyn.hoistT resampleSystematic) k- . hoistS (TrDyn.hoistT (spawn n >>))+ . S.sequentially (TrDyn.freeze . composeCopies numMCMCSteps TrDyn.mhStep . TrDyn.hoist resampler) numSteps+ . S.hoistFirst (TrDyn.hoist (withParticles numParticles)) -- | Apply a function a given number of times. composeCopies :: Int -> (a -> a) -> (a -> a)-composeCopies k f = foldr (.) id (replicate k f)+composeCopies k = withEndo (mconcat . replicate k)++withEndo :: (Endo a -> Endo b) -> (a -> a) -> b -> b+withEndo f = appEndo . f . Endo
src/Control/Monad/Bayes/Inference/SMC.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Control.Monad.Bayes.Inference.SMC -- Description : Sequential Monte Carlo (SMC)@@ -11,83 +14,40 @@ -- -- Arnaud Doucet and Adam M. Johansen. 2011. A tutorial on particle filtering and smoothing: fifteen years later. In /The Oxford Handbook of Nonlinear Filtering/, Dan Crisan and Boris Rozovskii (Eds.). Oxford University Press, Chapter 8. module Control.Monad.Bayes.Inference.SMC- ( sir,- smcMultinomial,- smcSystematic,- smcMultinomialPush,- smcSystematicPush,+ ( smc,+ smcPush,+ SMCConfig (..), ) where -import Control.Monad.Bayes.Class+import Control.Monad.Bayes.Class (MonadInfer, MonadSample) import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sequential as Seq+ ( Population,+ pushEvidence,+ withParticles,+ )+import Control.Monad.Bayes.Sequential.Coroutine as Coroutine +data SMCConfig m = SMCConfig+ { resampler :: forall x. Population m x -> Population m x,+ numSteps :: Int,+ numParticles :: Int+ }+ -- | Sequential importance resampling. -- Basically an SMC template that takes a custom resampler.-sir ::- Monad m =>- -- | resampler- (forall x. Population m x -> Population m x) ->- -- | number of timesteps- Int ->- -- | population size- Int ->- -- | model- Sequential (Population m) a ->- Population m a-sir resampler k n = sis resampler k . Seq.hoistFirst (spawn n >>)---- | Sequential Monte Carlo with multinomial resampling at each timestep.--- Weights are not normalized.-smcMultinomial ::- MonadSample m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | model- Sequential (Population m) a ->- Population m a-smcMultinomial = sir resampleMultinomial---- | Sequential Monte Carlo with systematic resampling at each timestep.--- Weights are not normalized.-smcSystematic ::+smc :: MonadSample m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | model- Sequential (Population m) a ->+ SMCConfig m ->+ Coroutine.Sequential (Population m) a -> Population m a-smcSystematic = sir resampleSystematic+smc SMCConfig {..} =+ Coroutine.sequentially resampler numSteps+ . Coroutine.hoistFirst (withParticles numParticles) -- | Sequential Monte Carlo with multinomial resampling at each timestep. -- Weights are normalized at each timestep and the total weight is pushed -- as a score into the transformed monad.-smcMultinomialPush ::- MonadInfer m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | model- Sequential (Population m) a ->- Population m a-smcMultinomialPush = sir (pushEvidence . resampleMultinomial)---- | Sequential Monte Carlo with systematic resampling at each timestep.--- Weights are normalized at each timestep and the total weight is pushed--- as a score into the transformed monad.-smcSystematicPush ::- MonadInfer m =>- -- | number of timesteps- Int ->- -- | number of particles- Int ->- -- | model- Sequential (Population m) a ->- Population m a-smcSystematicPush = sir (pushEvidence . resampleSystematic)+smcPush ::+ MonadInfer m => SMCConfig m -> Coroutine.Sequential (Population m) a -> Population m a+smcPush config = smc config {resampler = (pushEvidence . resampler config)}
src/Control/Monad/Bayes/Inference/SMC2.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+ -- | -- Module : Control.Monad.Bayes.Inference.SMC2 -- Description : Sequential Monte Carlo squared (SMC²)@@ -12,22 +15,29 @@ -- Nicolas Chopin, Pierre E. Jacob, and Omiros Papaspiliopoulos. 2013. SMC²: an efficient algorithm for sequential analysis of state space models. /Journal of the Royal Statistical Society Series B: Statistical Methodology/ 75 (2013), 397-426. Issue 3. <https://doi.org/10.1111/j.1467-9868.2012.01046.x> module Control.Monad.Bayes.Inference.SMC2 ( smc2,+ SMC2, ) where import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Helpers-import Control.Monad.Bayes.Inference.RMSMC-import Control.Monad.Bayes.Inference.SMC-import Control.Monad.Bayes.Population as Pop-import Control.Monad.Trans-import Numeric.Log+ ( MonadCond (..),+ MonadInfer,+ MonadSample (random),+ )+import Control.Monad.Bayes.Inference.MCMC+import Control.Monad.Bayes.Inference.RMSMC (rmsmc)+import Control.Monad.Bayes.Inference.SMC (SMCConfig (SMCConfig, numParticles, numSteps, resampler), smcPush)+import Control.Monad.Bayes.Population as Pop (Population, population, resampleMultinomial)+import Control.Monad.Bayes.Sequential.Coroutine (Sequential)+import Control.Monad.Bayes.Traced+import Control.Monad.Trans (MonadTrans (..))+import Numeric.Log (Log) -- | Helper monad transformer for preprocessing the model for 'smc2'.-newtype SMC2 m a = SMC2 (S (T (P m)) a)- deriving (Functor, Applicative, Monad)+newtype SMC2 m a = SMC2 (Sequential (Traced (Population m)) a)+ deriving newtype (Functor, Applicative, Monad) -setup :: SMC2 m a -> S (T (P m)) a+setup :: SMC2 m a -> Sequential (Traced (Population m)) a setup (SMC2 m) = m instance MonadTrans SMC2 where@@ -53,9 +63,12 @@ -- | number of MH transitions Int -> -- | model parameters- S (T (P m)) b ->+ Sequential (Traced (Population m)) b -> -- | model- (b -> S (P (SMC2 m)) a) ->- P m [(a, Log Double)]-smc2 k n p t param model =- rmsmc k p t (param >>= setup . runPopulation . smcSystematicPush k n . model)+ (b -> Sequential (Population (SMC2 m)) a) ->+ Population m [(a, Log Double)]+smc2 k n p t param m =+ rmsmc+ MCMCConfig {numMCMCSteps = t, proposal = SingleSiteMH, numBurnIn = 0}+ SMCConfig {numParticles = p, numSteps = k, resampler = resampleMultinomial}+ (param >>= setup . population . smcPush (SMCConfig {numSteps = k, numParticles = n, resampler = resampleMultinomial}) . m)
+ src/Control/Monad/Bayes/Inference/TUI.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# OPTIONS_GHC -Wno-type-defaults #-}++module Control.Monad.Bayes.Inference.TUI where++import Brick+import Brick qualified as B+import Brick.BChan qualified as B+import Brick.Widgets.Border+import Brick.Widgets.Border.Style+import Brick.Widgets.Center+import Brick.Widgets.ProgressBar qualified as B+import Control.Arrow (Arrow (..))+import Control.Concurrent (forkIO)+import Control.Foldl qualified as Fold+import Control.Monad (void)+import Control.Monad.Bayes.Enumerator (toEmpirical)+import Control.Monad.Bayes.Inference.MCMC+import Control.Monad.Bayes.Sampler.Strict (SamplerIO, sampleIO)+import Control.Monad.Bayes.Traced (Traced)+import Control.Monad.Bayes.Traced.Common+import Control.Monad.Bayes.Weighted+import Data.Scientific (FPFormat (Exponent), formatScientific, fromFloatDigits)+import Data.Text qualified as T+import Data.Text.Lazy qualified as TL+import Data.Text.Lazy.IO qualified as TL+import GHC.Float (double2Float)+import Graphics.Vty+import Graphics.Vty qualified as V+import Numeric.Log (Log (ln))+import Pipes (runEffect, (>->))+import Pipes qualified as P+import Pipes.Prelude qualified as P+import Text.Pretty.Simple (pShow, pShowNoColor)++data MCMCData a = MCMCData+ { numSteps :: Int,+ numSuccesses :: Int,+ samples :: [a],+ lk :: [Double],+ totalSteps :: Int+ }+ deriving stock (Show)++-- | Brick is a terminal user interface (TUI)+-- which we use to display inference algorithms in progress++-- | draw the brick app+drawUI :: ([a] -> Widget n) -> MCMCData a -> [Widget n]+drawUI handleSamples state = [ui]+ where+ completionBar =+ updateAttrMap+ ( B.mapAttrNames+ [ (doneAttr, B.progressCompleteAttr),+ (toDoAttr, B.progressIncompleteAttr)+ ]+ )+ $ toBar $ fromIntegral $ numSteps state++ likelihoodBar =+ updateAttrMap+ ( B.mapAttrNames+ [ (doneAttr, B.progressCompleteAttr),+ (toDoAttr, B.progressIncompleteAttr)+ ]+ )+ $ B.progressBar+ (Just $ "Mean likelihood for last 1000 samples: " <> take 10 (show (head $ lk state <> [0])))+ (double2Float (Fold.fold Fold.mean $ take 1000 $ lk state) / double2Float (maximum $ 0 : lk state))++ displayStep c = Just $ "Step " <> show c+ numFailures = numSteps state - numSuccesses state+ toBar v = B.progressBar (displayStep v) (v / fromIntegral (totalSteps state))+ displaySuccessesAndFailures =+ withBorderStyle unicode $+ borderWithLabel (str "Successes and failures") $+ center (str (show $ numSuccesses state))+ <+> vBorder+ <+> center (str (show numFailures))+ warning =+ if numSteps state > 1000 && (fromIntegral (numSuccesses state) / fromIntegral (numSteps state)) < 0.1+ then withAttr (attrName "highlight") $ str "Warning: acceptance rate is rather low.\nThis probably means that your proposal isn't good."+ else str ""++ ui =+ (str "Progress: " <+> completionBar)+ <=> (str "Likelihood: " <+> likelihoodBar)+ <=> str "\n"+ <=> displaySuccessesAndFailures+ <=> warning+ <=> handleSamples (samples state)++noVisual :: b -> Widget n+noVisual = const emptyWidget++showEmpirical :: (Show a, Ord a) => [a] -> Widget n+showEmpirical =+ txt+ . T.pack+ . TL.unpack+ . pShow+ . (fmap (second (formatScientific Exponent (Just 3) . fromFloatDigits)))+ . toEmpirical++showVal :: Show a => [a] -> Widget n+showVal = txt . T.pack . (\case [] -> ""; a -> show $ head a)++-- | handler for events received by the TUI+appEvent :: s -> B.BrickEvent n1 s -> B.EventM n2 (B.Next s)+appEvent p (B.VtyEvent e) =+ case e of+ V.EvKey (V.KChar 'q') [] -> do+ B.halt p+ _ -> B.continue p+appEvent _ (B.AppEvent d) = B.continue d+appEvent _ _ = error "unknown event"++doneAttr, toDoAttr :: B.AttrName+doneAttr = B.attrName "theBase" <> B.attrName "done"+toDoAttr = B.attrName "theBase" <> B.attrName "remaining"++theMap :: B.AttrMap+theMap =+ B.attrMap+ V.defAttr+ [ (B.attrName "theBase", bg V.brightBlack),+ (doneAttr, V.black `on` V.white),+ (toDoAttr, V.white `on` V.black),+ (attrName "highlight", fg yellow)+ ]++tui :: Show a => Int -> Traced (Weighted SamplerIO) a -> ([a] -> Widget ()) -> IO ()+tui burnIn distribution visualizer = void do+ eventChan <- B.newBChan 10+ initialVty <- buildVty+ _ <- forkIO $ run (mcmcP MCMCConfig {numBurnIn = burnIn, proposal = SingleSiteMH, numMCMCSteps = -1} distribution) eventChan n+ samples <-+ B.customMain+ initialVty+ buildVty+ (Just eventChan)+ ( ( B.App+ { B.appDraw = drawUI visualizer,+ B.appChooseCursor = B.showFirstCursor,+ B.appHandleEvent = appEvent,+ B.appStartEvent = return,+ B.appAttrMap = const theMap+ }+ )+ )+ (initialState n)+ TL.writeFile "data/tui_output.txt" (pShowNoColor samples)+ return samples+ where+ buildVty = V.mkVty V.defaultConfig+ n = 100000+ initialState n = MCMCData {numSteps = 0, samples = [], lk = [], numSuccesses = 0, totalSteps = n}++ run prod chan i =+ runEffect $+ P.hoist (sampleIO . unweighted) prod+ >-> P.scan+ ( \mcmcdata@(MCMCData ns nsc smples lk _) a ->+ mcmcdata+ { numSteps = ns + 1,+ numSuccesses = nsc + if success a then 1 else 0,+ samples = output (trace a) : smples,+ lk = exp (ln (probDensity (trace a))) : lk+ }+ )+ (initialState i)+ id+ >-> P.take i+ >-> P.mapM_ (B.writeBChan chan)
+ src/Control/Monad/Bayes/Integrator.hs view
@@ -0,0 +1,167 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# OPTIONS_GHC -Wno-type-defaults #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}++-- |+-- This is adapted from https://jtobin.io/giry-monad-implementation+-- but brought into the monad-bayes framework (i.e. Integrator is an instance of MonadInfer)+-- It's largely for debugging other inference methods and didactic use,+-- because brute force integration of measures is+-- only practical for small programs+module Control.Monad.Bayes.Integrator+ ( probability,+ variance,+ expectation,+ cdf,+ empirical,+ enumeratorWith,+ histogram,+ plotCdf,+ volume,+ normalize,+ Integrator,+ momentGeneratingFunction,+ cumulantGeneratingFunction,+ integrator,+ runIntegrator,+ )+where++import Control.Applicative (Applicative (..))+import Control.Foldl (Fold)+import Control.Foldl qualified as Foldl+import Control.Monad.Bayes.Class (MonadSample (bernoulli, random, uniformD))+import Control.Monad.Bayes.Weighted (Weighted, weighted)+import Control.Monad.Cont+ ( Cont,+ ContT (ContT),+ cont,+ runCont,+ )+import Data.Foldable (Foldable (foldl'))+import Data.Set (Set, elems)+import Numeric.Integration.TanhSinh (Result (result), trap)+import Numeric.Log (Log (ln))+import Statistics.Distribution qualified as Statistics+import Statistics.Distribution.Uniform qualified as Statistics++newtype Integrator a = Integrator {getCont :: Cont Double a}+ deriving newtype (Functor, Applicative, Monad)++integrator, runIntegrator :: (a -> Double) -> Integrator a -> Double+integrator f (Integrator a) = runCont a f+runIntegrator = integrator++instance MonadSample Integrator where+ random = fromDensityFunction $ Statistics.density $ Statistics.uniformDistr 0 1+ bernoulli p = Integrator $ cont (\f -> p * f True + (1 - p) * f False)+ uniformD ls = fromMassFunction (const (1 / fromIntegral (length ls))) ls++fromDensityFunction :: (Double -> Double) -> Integrator Double+fromDensityFunction d = Integrator $+ cont $ \f ->+ integralWithQuadrature (\x -> f x * d x)+ where+ integralWithQuadrature = result . last . (\z -> trap z 0 1)++fromMassFunction :: Foldable f => (a -> Double) -> f a -> Integrator a+fromMassFunction f support = Integrator $ cont \g ->+ foldl' (\acc x -> acc + f x * g x) 0 support++empirical :: Foldable f => f a -> Integrator a+empirical = Integrator . cont . flip weightedAverage+ where+ weightedAverage :: (Foldable f, Fractional r) => (a -> r) -> f a -> r+ weightedAverage f = Foldl.fold (weightedAverageFold f)++ weightedAverageFold :: Fractional r => (a -> r) -> Fold a r+ weightedAverageFold f = Foldl.premap f averageFold++ averageFold :: Fractional a => Fold a a+ averageFold = (/) <$> Foldl.sum <*> Foldl.genericLength++expectation :: Integrator Double -> Double+expectation = integrator id++variance :: Integrator Double -> Double+variance nu = integrator (^ 2) nu - expectation nu ^ 2++momentGeneratingFunction :: Integrator Double -> Double -> Double+momentGeneratingFunction nu t = integrator (\x -> exp (t * x)) nu++cumulantGeneratingFunction :: Integrator Double -> Double -> Double+cumulantGeneratingFunction nu = log . momentGeneratingFunction nu++normalize :: Weighted Integrator a -> Integrator a+normalize m =+ let m' = weighted m+ z = integrator (ln . exp . snd) m'+ in do+ (x, d) <- weighted m+ Integrator $ cont $ \f -> (f () * (ln $ exp d)) / z+ return x++cdf :: Integrator Double -> Double -> Double+cdf nu x = integrator (negativeInfinity `to` x) nu+ where+ negativeInfinity :: Double+ negativeInfinity = negate (1 / 0)++ to :: (Num a, Ord a) => a -> a -> a -> a+ to a b k+ | k >= a && k <= b = 1+ | otherwise = 0++volume :: Integrator Double -> Double+volume = integrator (const 1)++containing :: (Num a, Eq b) => [b] -> b -> a+containing xs x+ | x `elem` xs = 1+ | otherwise = 0++instance Num a => Num (Integrator a) where+ (+) = liftA2 (+)+ (-) = liftA2 (-)+ (*) = liftA2 (*)+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++probability :: Ord a => (a, a) -> Integrator a -> Double+probability (lower, upper) = integrator (\x -> if x < upper && x >= lower then 1 else 0)++enumeratorWith :: Ord a => Set a -> Integrator a -> [(a, Double)]+enumeratorWith ls meas =+ [ ( val,+ integrator+ (\x -> if x == val then 1 else 0)+ meas+ )+ | val <- elems ls+ ]++histogram ::+ (Enum a, Ord a, Fractional a) =>+ Int ->+ a ->+ Weighted Integrator a ->+ [(a, Double)]+histogram nBins binSize model = do+ x <- take nBins [1 ..]+ let transform k = (k - (fromIntegral nBins / 2)) * binSize+ return+ ( (fst)+ (transform x, transform (x + 1)),+ probability (transform x, transform (x + 1)) $ normalize model+ )++plotCdf :: Int -> Double -> Double -> Integrator Double -> [(Double, Double)]+plotCdf nBins binSize middlePoint model = do+ x <- take nBins [1 ..]+ let transform k = (k - (fromIntegral nBins / 2)) * binSize + middlePoint+ return (transform x, cdf model (transform x))
src/Control/Monad/Bayes/Population.hs view
@@ -1,3 +1,9 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RankNTypes #-}+{-# OPTIONS_GHC -Wno-deprecations #-}+ -- | -- Module : Control.Monad.Bayes.Population -- Description : Representation of distributions using multiple samples@@ -10,51 +16,71 @@ -- 'Population' turns a single sample into a collection of weighted samples. module Control.Monad.Bayes.Population ( Population,+ population, runPopulation, explicitPopulation, fromWeightedList, spawn,+ multinomial, resampleMultinomial,+ systematic, resampleSystematic,+ stratified,+ resampleStratified, extractEvidence, pushEvidence, proper, evidence,+ hoist, collapse,- mapPopulation,- normalize, popAvg,- flatten,- hoist,+ withParticles, ) where import Control.Arrow (second) import Control.Monad (replicateM) import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Weighted hiding (flatten, hoist)-import Control.Monad.Trans-import Control.Monad.Trans.List-import qualified Data.List-import qualified Data.Vector as V-import Numeric.Log+ ( MonadCond,+ MonadInfer,+ MonadSample (categorical, logCategorical, random, uniform),+ factor,+ )+import Control.Monad.Bayes.Weighted+ ( Weighted,+ applyWeight,+ extractWeight,+ weighted,+ withWeight,+ )+import Control.Monad.List (ListT (..), MonadIO, MonadTrans (..))+import Data.List (unfoldr)+import Data.List qualified+import Data.Maybe (catMaybes)+import Data.Vector ((!))+import Data.Vector qualified as V+import Numeric.Log (Log, ln, sum)+import Numeric.Log qualified as Log import Prelude hiding (all, sum) -- | A collection of weighted samples, or particles. newtype Population m a = Population (Weighted (ListT m) a)- deriving (Functor, Applicative, Monad, MonadIO, MonadSample, MonadCond, MonadInfer)+ deriving newtype (Functor, Applicative, Monad, MonadIO, MonadSample, MonadCond, MonadInfer) instance MonadTrans Population where lift = Population . lift . lift -- | Explicit representation of the weighted sample with weights in the log -- domain.-runPopulation :: Functor m => Population m a -> m [(a, Log Double)]-runPopulation (Population m) = runListT $ runWeighted m+population, runPopulation :: Population m a -> m [(a, Log Double)]+population (Population m) = runListT $ weighted m +-- | deprecated synonym+runPopulation = population+ -- | Explicit representation of the weighted sample. explicitPopulation :: Functor m => Population m a -> m [(a, Double)]-explicitPopulation = fmap (map (second (exp . ln))) . runPopulation+explicitPopulation = fmap (map (second (exp . ln))) . population -- | Initialize 'Population' with a concrete weighted sample. fromWeightedList :: Monad m => m [(a, Log Double)] -> Population m a@@ -67,6 +93,9 @@ spawn :: Monad m => Int -> Population m () spawn n = fromWeightedList $ pure $ replicate n ((), 1 / fromIntegral n) +withParticles :: Monad m => Int -> Population m a -> Population m a+withParticles n = (spawn n >>)+ resampleGeneric :: MonadSample m => -- | resampler@@ -74,10 +103,10 @@ Population m a -> Population m a resampleGeneric resampler m = fromWeightedList $ do- pop <- runPopulation m+ pop <- population m let (xs, ps) = unzip pop let n = length xs- let z = sum ps+ let z = Log.sum ps if z > 0 then do let weights = V.fromList (map (exp . ln . (/ z)) ps)@@ -85,10 +114,26 @@ let xvec = V.fromList xs let offsprings = map (xvec V.!) ancestors return $ map (,z / fromIntegral n) offsprings- else-- if all weights are zero do not resample+ else -- if all weights are zero do not resample return pop --- | Systematic resampling helper.+-- | Systematic sampler.+-- Sample \(n\) values from \((0,1]\) as follows+-- \[+-- \begin{aligned}+-- u^{(1)} &\sim U\left(0, \frac{1}{n}\right] \\+-- u^{(i)} &=u^{(1)}+\frac{i-1}{n}, \quad i=2,3, \ldots, n+-- \end{aligned}+-- \]+-- and then pick integers \(m\) according to+-- \[+-- Q^{(m-1)}<u^{(n)} \leq Q^{(m)}+-- \]+-- where+-- \[+-- Q^{(m)}=\sum_{k=1}^{m} w^{(k)}+-- \]+-- and \(w^{(k)}\) are the weights. See also [Comparison of Resampling Schemes for Particle Filtering](https://arxiv.org/abs/cs/0507025). systematic :: Double -> V.Vector Double -> [Int] systematic u ps = f 0 (u / fromIntegral n) 0 0 [] where@@ -98,7 +143,7 @@ f i _ _ _ acc | i == n = acc f i v j q acc = if v < q- then f (i + 1) (v + inc) j q (j -1 : acc)+ then f (i + 1) (v + inc) j q (j - 1 : acc) else f i v (j + 1) (q + prob j) acc -- | Resample the population using the underlying monad and a systematic resampling scheme.@@ -109,7 +154,51 @@ Population m a resampleSystematic = resampleGeneric (\ps -> (`systematic` ps) <$> random) --- | Multinomial resampler.+-- | Stratified sampler.+--+-- Sample \(n\) values from \((0,1]\) as follows+-- \[+-- u^{(i)} \sim U\left(\frac{i-1}{n}, \frac{i}{n}\right], \quad i=1,2, \ldots, n+-- \]+-- and then pick integers \(m\) according to+-- \[+-- Q^{(m-1)}<u^{(n)} \leq Q^{(m)}+-- \]+-- where+-- \[+-- Q^{(m)}=\sum_{k=1}^{m} w^{(k)}+-- \]+-- and \(w^{(k)}\) are the weights.+--+-- The conditional variance of stratified sampling is always smaller than that of multinomial sampling and it is also unbiased - see [Comparison of Resampling Schemes for Particle Filtering](https://arxiv.org/abs/cs/0507025).+stratified :: MonadSample m => V.Vector Double -> m [Int]+stratified weights = do+ let bigN = V.length weights+ dithers <- V.replicateM bigN (uniform 0.0 1.0)+ let positions =+ V.map (/ fromIntegral bigN) $+ V.zipWith (+) dithers (V.map fromIntegral $ V.fromList [0 .. bigN - 1])+ cumulativeSum = V.scanl (+) 0.0 weights+ coalg (i, j)+ | i < bigN =+ if (positions ! i) < (cumulativeSum ! j)+ then Just (Just j, (i + 1, j))+ else Just (Nothing, (i, j + 1))+ | otherwise =+ Nothing+ return $ map (\i -> i - 1) $ catMaybes $ unfoldr coalg (0, 0)++-- | Resample the population using the underlying monad and a stratified resampling scheme.+-- The total weight is preserved.+resampleStratified ::+ (MonadSample m) =>+ Population m a ->+ Population m a+resampleStratified = resampleGeneric stratified++-- | Multinomial sampler. Sample from \(0, \ldots, n - 1\) \(n\)+-- times drawn at random according to the weights where \(n\) is the+-- length of vector of weights. multinomial :: MonadSample m => V.Vector Double -> m [Int] multinomial ps = replicateM (V.length ps) (categorical ps) @@ -128,7 +217,7 @@ Population m a -> Population (Weighted m) a extractEvidence m = fromWeightedList $ do- pop <- lift $ runPopulation m+ pop <- lift $ population m let (xs, ps) = unzip pop let z = sum ps let ws = map (if z > 0 then (/ z) else const (1 / fromIntegral (length ps))) ps@@ -150,7 +239,7 @@ Population m a -> Weighted m a proper m = do- pop <- runPopulation $ extractEvidence m+ pop <- population $ extractEvidence m let (xs, ps) = unzip pop index <- logCategorical $ V.fromList ps let x = xs !! index@@ -158,7 +247,7 @@ -- | Model evidence estimator, also known as pseudo-marginal likelihood. evidence :: (Monad m) => Population m a -> m (Log Double)-evidence = extractWeight . runPopulation . extractEvidence+evidence = extractWeight . population . extractEvidence -- | Picks one point from the population and uses model evidence as a 'score' -- in the transformed monad.@@ -170,19 +259,6 @@ m a collapse = applyWeight . proper --- | Applies a random transformation to a population.-mapPopulation ::- (Monad m) =>- ([(a, Log Double)] -> m [(a, Log Double)]) ->- Population m a ->- Population m a-mapPopulation f m = fromWeightedList $ runPopulation m >>= f---- | Normalizes the weights in the population so that their sum is 1.--- This transformation introduces bias.-normalize :: (Monad m) => Population m a -> Population m a-normalize = hoist prior . extractEvidence- -- | Population average of a function, computed using unnormalized weights. popAvg :: (Monad m) => (a -> Double) -> Population m a -> m Double popAvg f p = do@@ -191,20 +267,10 @@ let t = Data.List.sum ys return t --- | Combine a population of populations into a single population.-flatten :: Monad m => Population (Population m) a -> Population m a-flatten m = Population $ withWeight $ ListT t- where- t = f <$> (runPopulation . runPopulation) m- f d = do- (x, p) <- d- (y, q) <- x- return (y, p * q)- -- | Applies a transformation to the inner monad. hoist ::- (Monad m, Monad n) =>+ Monad n => (forall x. m x -> n x) -> Population m a -> Population n a-hoist f = fromWeightedList . f . runPopulation+hoist f = fromWeightedList . f . population
− src/Control/Monad/Bayes/Sampler.hs
@@ -1,105 +0,0 @@--- |--- Module : Control.Monad.Bayes.Sampler--- Description : Pseudo-random sampling monads--- Copyright : (c) Adam Scibior, 2015-2020--- License : MIT--- Maintainer : leonhard.markert@tweag.io--- Stability : experimental--- Portability : GHC------ 'SamplerIO' and 'SamplerST' are instances of 'MonadSample'. Apply a 'MonadCond'--- transformer to obtain a 'MonadInfer' that can execute probabilistic models.-module Control.Monad.Bayes.Sampler- ( SamplerIO,- sampleIO,- sampleIOfixed,- sampleIOwith,- Seed,- SamplerST (SamplerST),- runSamplerST,- sampleST,- sampleSTfixed,- )-where--import Control.Monad.Bayes.Class-import Control.Monad.ST (ST, runST, stToIO)-import Control.Monad.State (State, state)-import Control.Monad.Trans (MonadIO, lift)-import Control.Monad.Trans.Reader (ReaderT, ask, mapReaderT, runReaderT)-import System.Random.MWC-import qualified System.Random.MWC.Distributions as MWC---- | An 'IO' based random sampler using the MWC-Random package.-newtype SamplerIO a = SamplerIO (ReaderT GenIO IO a)- deriving (Functor, Applicative, Monad, MonadIO)---- | Initialize a pseudo-random number generator using randomness supplied by--- the operating system.--- For efficiency this operation should be applied at the very end, ideally--- once per program.-sampleIO :: SamplerIO a -> IO a-sampleIO (SamplerIO m) = createSystemRandom >>= runReaderT m---- | Like 'sampleIO', but with a fixed random seed.--- Useful for reproducibility.-sampleIOfixed :: SamplerIO a -> IO a-sampleIOfixed (SamplerIO m) = create >>= runReaderT m---- | Like 'sampleIO' but with a custom pseudo-random number generator.-sampleIOwith :: SamplerIO a -> GenIO -> IO a-sampleIOwith (SamplerIO m) = runReaderT m--fromSamplerST :: SamplerST a -> SamplerIO a-fromSamplerST (SamplerST m) = SamplerIO $ mapReaderT stToIO m--instance MonadSample SamplerIO where- random = fromSamplerST random---- | An 'ST' based random sampler using the @mwc-random@ package.-newtype SamplerST a = SamplerST (forall s. ReaderT (GenST s) (ST s) a)--runSamplerST :: SamplerST a -> ReaderT (GenST s) (ST s) a-runSamplerST (SamplerST s) = s--instance Functor SamplerST where- fmap f (SamplerST s) = SamplerST $ fmap f s--instance Applicative SamplerST where- pure x = SamplerST $ pure x- (SamplerST f) <*> (SamplerST x) = SamplerST $ f <*> x--instance Monad SamplerST where- (SamplerST x) >>= f = SamplerST $ x >>= runSamplerST . f---- | Run the sampler with a supplied seed.--- Note that 'State Seed' is much less efficient than 'SamplerST' for composing computation.-sampleST :: SamplerST a -> State Seed a-sampleST (SamplerST s) =- state $ \seed -> runST $ do- gen <- restore seed- y <- runReaderT s gen- finalSeed <- save gen- return (y, finalSeed)---- | Run the sampler with a fixed random seed.-sampleSTfixed :: SamplerST a -> a-sampleSTfixed (SamplerST s) = runST $ do- gen <- create- runReaderT s gen---- | Convert a distribution supplied by @mwc-random@.-fromMWC :: (forall s. GenST s -> ST s a) -> SamplerST a-fromMWC s = SamplerST $ ask >>= lift . s--instance MonadSample SamplerST where- random = fromMWC System.Random.MWC.uniform-- uniform a b = fromMWC $ uniformR (a, b)- normal m s = fromMWC $ MWC.normal m s- gamma shape scale = fromMWC $ MWC.gamma shape scale- beta a b = fromMWC $ MWC.beta a b-- bernoulli p = fromMWC $ MWC.bernoulli p- categorical ps = fromMWC $ MWC.categorical ps- geometric p = fromMWC $ MWC.geometric0 p
+ src/Control/Monad/Bayes/Sampler/Lazy.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | This is a port of the implementation of LazyPPL: https://lazyppl.bitbucket.io/+module Control.Monad.Bayes.Sampler.Lazy where++import Control.Monad (ap, liftM)+import Control.Monad.Bayes.Class (MonadSample (random))+import Control.Monad.Bayes.Weighted (Weighted, weighted)+import Numeric.Log (Log (..))+import System.Random+ ( RandomGen (split),+ getStdGen,+ newStdGen,+ )+import qualified System.Random as R++-- | A 'Tree' is a lazy, infinitely wide and infinitely deep tree, labelled by Doubles+-- | Our source of randomness will be a Tree, populated by uniform [0,1] choices for each label.+-- | Often people just use a list or stream instead of a tree.+-- | But a tree allows us to be lazy about how far we are going all the time.+data Tree = Tree+ { currentUniform :: Double,+ lazyUniforms :: Trees+ }++-- | An infinite stream of 'Tree's.+data Trees = Trees+ { headTree :: Tree,+ tailTrees :: Trees+ }++-- | A probability distribution over a is+-- | a function 'Tree -> a'+-- | The idea is that it uses up bits of the tree as it runs+newtype Sampler a = Sampler {runSampler :: Tree -> a}+ deriving (Functor)++-- | Two key things to do with trees:+-- | Split tree splits a tree in two (bijectively)+-- | Get the label at the head of the tree and discard the rest+splitTree :: Tree -> (Tree, Tree)+splitTree (Tree r (Trees t ts)) = (t, Tree r ts)++-- | Preliminaries for the simulation methods. Generate a tree with uniform random labels. This uses 'split' to split a random seed+randomTree :: RandomGen g => g -> Tree+randomTree g = let (a, g') = R.random g in Tree a (randomTrees g')++randomTrees :: RandomGen g => g -> Trees+randomTrees g = let (g1, g2) = split g in Trees (randomTree g1) (randomTrees g2)++instance Applicative Sampler where+ pure = Sampler . const+ (<*>) = ap++-- | probabilities for a monad.+-- | Sequencing is done by splitting the tree+-- | and using different bits for different computations.+instance Monad Sampler where+ return = pure+ (Sampler m) >>= f = Sampler \g ->+ let (g1, g2) = splitTree g+ (Sampler m') = f (m g1)+ in m' g2++instance MonadSample Sampler where+ random = Sampler \(Tree r _) -> r++sampler :: Sampler a -> IO a+sampler m = newStdGen *> (runSampler m . randomTree <$> getStdGen)++independent :: Monad m => m a -> m [a]+independent = sequence . repeat++-- | 'weightedsamples' runs a probability measure and gets out a stream of (result,weight) pairs+weightedsamples :: Weighted Sampler a -> IO [(a, Log Double)]+weightedsamples = sampler . independent . weighted
+ src/Control/Monad/Bayes/Sampler/Strict.hs view
@@ -0,0 +1,101 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}++-- |+-- Module : Control.Monad.Bayes.Sampler+-- Description : Pseudo-random sampling monads+-- Copyright : (c) Adam Scibior, 2015-2020+-- License : MIT+-- Maintainer : leonhard.markert@tweag.io+-- Stability : experimental+-- Portability : GHC+--+-- 'SamplerIO' and 'SamplerST' are instances of 'MonadSample'. Apply a 'MonadCond'+-- transformer to obtain a 'MonadInfer' that can execute probabilistic models.+module Control.Monad.Bayes.Sampler.Strict+ ( Sampler,+ SamplerIO,+ SamplerST,+ sampleIO,+ sampleIOfixed,+ sampleWith,+ sampleSTfixed,+ sampleMean,+ sampler,+ )+where++import Control.Foldl qualified as F hiding (random)+import Control.Monad.Bayes.Class+ ( MonadSample+ ( bernoulli,+ beta,+ categorical,+ gamma,+ geometric,+ normal,+ random,+ uniform+ ),+ )+import Control.Monad.Reader (MonadIO, ReaderT (..))+import Control.Monad.ST (ST)+import Numeric.Log (Log (ln))+import System.Random.MWC.Distributions qualified as MWC+import System.Random.Stateful (IOGenM (..), STGenM, StatefulGen, StdGen, initStdGen, mkStdGen, newIOGenM, newSTGenM, uniformDouble01M, uniformRM)++-- | The sampling interpretation of a probabilistic program+-- Here m is typically IO or ST+newtype Sampler g m a = Sampler (ReaderT g m a) deriving (Functor, Applicative, Monad, MonadIO)++-- | convenient type synonym to show specializations of Sampler+-- to particular pairs of monad and RNG+type SamplerIO = Sampler (IOGenM StdGen) IO++-- | convenient type synonym to show specializations of Sampler+-- to particular pairs of monad and RNG+type SamplerST s = Sampler (STGenM StdGen s) (ST s)++instance StatefulGen g m => MonadSample (Sampler g m) where+ random = Sampler (ReaderT uniformDouble01M)++ uniform a b = Sampler (ReaderT $ uniformRM (a, b))+ normal m s = Sampler (ReaderT (MWC.normal m s))+ gamma shape scale = Sampler (ReaderT $ MWC.gamma shape scale)+ beta a b = Sampler (ReaderT $ MWC.beta a b)++ bernoulli p = Sampler (ReaderT $ MWC.bernoulli p)+ categorical ps = Sampler (ReaderT $ MWC.categorical ps)+ geometric p = Sampler (ReaderT $ MWC.geometric0 p)++-- | Sample with a random number generator of your choice e.g. the one+-- from `System.Random`.+--+-- >>> import Control.Monad.Bayes.Class+-- >>> import System.Random.Stateful hiding (random)+-- >>> newIOGenM (mkStdGen 1729) >>= sampleWith random+-- 4.690861245089605e-2+sampleWith :: StatefulGen g m => Sampler g m a -> g -> m a+sampleWith (Sampler m) = runReaderT m++-- | initialize random seed using system entropy, and sample+sampleIO, sampler :: SamplerIO a -> IO a+sampleIO x = initStdGen >>= newIOGenM >>= sampleWith x+sampler = sampleIO++-- | Run the sampler with a fixed random seed+sampleIOfixed :: SamplerIO a -> IO a+sampleIOfixed x = newIOGenM (mkStdGen 1729) >>= sampleWith x++-- | Run the sampler with a fixed random seed+sampleSTfixed :: SamplerST s b -> ST s b+sampleSTfixed x = newSTGenM (mkStdGen 1729) >>= sampleWith x++sampleMean :: [(Double, Log Double)] -> Double+sampleMean samples =+ let z = F.premap (ln . exp . snd) F.sum+ w = (F.premap (\(x, y) -> x * ln (exp y)) F.sum)+ s = (/) <$> w <*> z+ in F.fold s samples
− src/Control/Monad/Bayes/Sequential.hs
@@ -1,98 +0,0 @@--- |--- Module : Control.Monad.Bayes.Sequential--- Description : Suspendable probabilistic computation--- Copyright : (c) Adam Scibior, 2015-2020--- License : MIT--- Maintainer : leonhard.markert@tweag.io--- Stability : experimental--- Portability : GHC------ 'Sequential' represents a computation that can be suspended.-module Control.Monad.Bayes.Sequential- ( Sequential,- suspend,- finish,- advance,- finished,- hoistFirst,- hoist,- sis,- )-where--import Control.Monad.Bayes.Class-import Control.Monad.Coroutine hiding (suspend)-import Control.Monad.Coroutine.SuspensionFunctors-import Control.Monad.Trans-import Data.Either---- | Represents a computation that can be suspended at certain points.--- The intermediate monadic effects can be extracted, which is particularly--- useful for implementation of Sequential Monte Carlo related methods.--- All the probabilistic effects are lifted from the transformed monad, but--- also `suspend` is inserted after each `factor`.-newtype Sequential m a = Sequential {runSequential :: Coroutine (Await ()) m a}- deriving (Functor, Applicative, Monad, MonadTrans, MonadIO)--extract :: Await () a -> a-extract (Await f) = f ()--instance MonadSample m => MonadSample (Sequential m) where- random = lift random- bernoulli = lift . bernoulli- categorical = lift . categorical---- | Execution is 'suspend'ed after each 'score'.-instance MonadCond m => MonadCond (Sequential m) where- score w = lift (score w) >> suspend--instance MonadInfer m => MonadInfer (Sequential m)---- | A point where the computation is paused.-suspend :: Monad m => Sequential m ()-suspend = Sequential await---- | Remove the remaining suspension points.-finish :: Monad m => Sequential m a -> m a-finish = pogoStick extract . runSequential---- | Execute to the next suspension point.--- If the computation is finished, do nothing.------ > finish = finish . advance-advance :: Monad m => Sequential m a -> Sequential m a-advance = Sequential . bounce extract . runSequential---- | Return True if no more suspension points remain.-finished :: Monad m => Sequential m a -> m Bool-finished = fmap isRight . resume . runSequential---- | Transform the inner monad.--- This operation only applies to computation up to the first suspension.-hoistFirst :: (forall x. m x -> m x) -> Sequential m a -> Sequential m a-hoistFirst f = Sequential . Coroutine . f . resume . runSequential---- | Transform the inner monad.--- The transformation is applied recursively through all the suspension points.-hoist ::- (Monad m, Monad n) =>- (forall x. m x -> n x) ->- Sequential m a ->- Sequential n a-hoist f = Sequential . mapMonad f . runSequential---- | Apply a function a given number of times.-composeCopies :: Int -> (a -> a) -> (a -> a)-composeCopies k f = foldr (.) id (replicate k f)---- | Sequential importance sampling.--- Applies a given transformation after each time step.-sis ::- Monad m =>- -- | transformation- (forall x. m x -> m x) ->- -- | number of time steps- Int ->- Sequential m a ->- m a-sis f k = finish . composeCopies k (advance . hoistFirst f)
+ src/Control/Monad/Bayes/Sequential/Coroutine.hs view
@@ -0,0 +1,119 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}++-- |+-- Module : Control.Monad.Bayes.Sequential+-- Description : Suspendable probabilistic computation+-- Copyright : (c) Adam Scibior, 2015-2020+-- License : MIT+-- Maintainer : leonhard.markert@tweag.io+-- Stability : experimental+-- Portability : GHC+--+-- 'Sequential' represents a computation that can be suspended.+module Control.Monad.Bayes.Sequential.Coroutine+ ( Sequential,+ suspend,+ finish,+ advance,+ finished,+ hoistFirst,+ hoist,+ sequentially,+ sis,+ )+where++import Control.Monad.Bayes.Class+ ( MonadCond (..),+ MonadInfer,+ MonadSample (bernoulli, categorical, random),+ )+import Control.Monad.Coroutine+ ( Coroutine (..),+ bounce,+ mapMonad,+ pogoStick,+ )+import Control.Monad.Coroutine.SuspensionFunctors+ ( Await (..),+ await,+ )+import Control.Monad.Trans (MonadIO, MonadTrans (..))+import Data.Either (isRight)++-- | Represents a computation that can be suspended at certain points.+-- The intermediate monadic effects can be extracted, which is particularly+-- useful for implementation of Sequential Monte Carlo related methods.+-- All the probabilistic effects are lifted from the transformed monad, but+-- also `suspend` is inserted after each `factor`.+newtype Sequential m a = Sequential {runSequential :: Coroutine (Await ()) m a}+ deriving newtype (Functor, Applicative, Monad, MonadTrans, MonadIO)++extract :: Await () a -> a+extract (Await f) = f ()++instance MonadSample m => MonadSample (Sequential m) where+ random = lift random+ bernoulli = lift . bernoulli+ categorical = lift . categorical++-- | Execution is 'suspend'ed after each 'score'.+instance MonadCond m => MonadCond (Sequential m) where+ score w = lift (score w) >> suspend++instance MonadInfer m => MonadInfer (Sequential m)++-- | A point where the computation is paused.+suspend :: Monad m => Sequential m ()+suspend = Sequential await++-- | Remove the remaining suspension points.+finish :: Monad m => Sequential m a -> m a+finish = pogoStick extract . runSequential++-- | Execute to the next suspension point.+-- If the computation is finished, do nothing.+--+-- > finish = finish . advance+advance :: Monad m => Sequential m a -> Sequential m a+advance = Sequential . bounce extract . runSequential++-- | Return True if no more suspension points remain.+finished :: Monad m => Sequential m a -> m Bool+finished = fmap isRight . resume . runSequential++-- | Transform the inner monad.+-- This operation only applies to computation up to the first suspension.+hoistFirst :: (forall x. m x -> m x) -> Sequential m a -> Sequential m a+hoistFirst f = Sequential . Coroutine . f . resume . runSequential++-- | Transform the inner monad.+-- The transformation is applied recursively through all the suspension points.+hoist ::+ (Monad m, Monad n) =>+ (forall x. m x -> n x) ->+ Sequential m a ->+ Sequential n a+hoist f = Sequential . mapMonad f . runSequential++-- | Apply a function a given number of times.+composeCopies :: Int -> (a -> a) -> (a -> a)+composeCopies k f = foldr (.) id (replicate k f)++-- | Sequential importance sampling.+-- Applies a given transformation after each time step.+sequentially,+ sis ::+ Monad m =>+ -- | transformation+ (forall x. m x -> m x) ->+ -- | number of time steps+ Int ->+ Sequential m a ->+ m a+sequentially f k = finish . composeCopies k (advance . hoistFirst f)++-- | synonym+sis = sequentially
src/Control/Monad/Bayes/Traced/Basic.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE RankNTypes #-}+ -- | -- Module : Control.Monad.Bayes.Traced.Basic -- Description : Distributions on full execution traces of full programs@@ -8,7 +10,7 @@ -- Portability : GHC module Control.Monad.Bayes.Traced.Basic ( Traced,- hoistT,+ hoist, marginal, mhStep, mh,@@ -17,19 +19,29 @@ import Control.Applicative (liftA2) import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Free (FreeSampler)+ ( MonadCond (..),+ MonadInfer,+ MonadSample (random),+ )+import Control.Monad.Bayes.Density.Free (Density) import Control.Monad.Bayes.Traced.Common+ ( Trace (..),+ bind,+ mhTrans',+ scored,+ singleton,+ ) import Control.Monad.Bayes.Weighted (Weighted) import Data.Functor.Identity (Identity)+import Data.List.NonEmpty as NE (NonEmpty ((:|)), toList) -- | Tracing monad that records random choices made in the program.-data Traced m a- = Traced- { -- | Run the program with a modified trace.- model :: Weighted (FreeSampler Identity) a,- -- | Record trace and output.- traceDist :: m (Trace a)- }+data Traced m a = Traced+ { -- | Run the program with a modified trace.+ model :: Weighted (Density Identity) a,+ -- | Record trace and output.+ traceDist :: m (Trace a)+ } instance Monad m => Functor (Traced m) where fmap f (Traced m d) = Traced (fmap f m) (fmap (fmap f) d)@@ -52,8 +64,8 @@ instance MonadInfer m => MonadInfer (Traced m) -hoistT :: (forall x. m x -> m x) -> Traced m a -> Traced m a-hoistT f (Traced m d) = Traced m (f d)+hoist :: (forall x. m x -> m x) -> Traced m a -> Traced m a+hoist f (Traced m d) = Traced m (f d) -- | Discard the trace and supporting infrastructure. marginal :: Monad m => Traced m a -> m a@@ -68,10 +80,11 @@ -- | Full run of the Trace Metropolis-Hastings algorithm with a specified -- number of steps. mh :: MonadSample m => Int -> Traced m a -> m [a]-mh n (Traced m d) = fmap (map output) (f n)+mh n (Traced m d) = fmap (map output . NE.toList) (f n) where- f 0 = fmap (: []) d- f k = do- ~(x : xs) <- f (k -1)- y <- mhTrans' m x- return (y : x : xs)+ f k+ | k <= 0 = fmap (:| []) d+ | otherwise = do+ (x :| xs) <- f (k - 1)+ y <- mhTrans' m x+ return (y :| x : xs)
src/Control/Monad/Bayes/Traced/Common.hs view
@@ -7,78 +7,118 @@ -- Stability : experimental -- Portability : GHC module Control.Monad.Bayes.Traced.Common- ( Trace,+ ( Trace (..), singleton,- output, scored, bind, mhTrans,+ mhTransWithBool,+ mhTransFree, mhTrans',+ burnIn,+ MHResult (..), ) where import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Free as FreeSampler+ ( MonadSample (bernoulli, random),+ discrete,+ )+import qualified Control.Monad.Bayes.Density.Free as Free+import qualified Control.Monad.Bayes.Density.State as State import Control.Monad.Bayes.Weighted as Weighted-import Control.Monad.Trans.Writer-import Data.Functor.Identity+ ( Weighted,+ hoist,+ weighted,+ )+import Control.Monad.Writer (WriterT (WriterT, runWriterT))+import Data.Functor.Identity (Identity (runIdentity)) import Numeric.Log (Log, ln) import Statistics.Distribution.DiscreteUniform (discreteUniformAB) --- | Collection of random variables sampled during the program's execution.-data Trace a- = Trace- { -- | Sequence of random variables sampled during the program's execution.- variables :: [Double],- -- |- output :: a,- -- | The probability of observing this particular sequence.- density :: Log Double- }+data MHResult a = MHResult+ { success :: Bool,+ trace :: Trace a+ } +-- | Collection of random variables sampler during the program's execution.+data Trace a = Trace+ { -- | Sequence of random variables sampler during the program's execution.+ variables :: [Double],+ --+ output :: a,+ -- | The probability of observing this particular sequence.+ probDensity :: Log Double+ }+ instance Functor Trace where fmap f t = t {output = f (output t)} instance Applicative Trace where- pure x = Trace {variables = [], output = x, density = 1}+ pure x = Trace {variables = [], output = x, probDensity = 1} tf <*> tx = Trace { variables = variables tf ++ variables tx, output = output tf (output tx),- density = density tf * density tx+ probDensity = probDensity tf * probDensity tx } instance Monad Trace where t >>= f = let t' = f (output t)- in t' {variables = variables t ++ variables t', density = density t * density t'}+ in t' {variables = variables t ++ variables t', probDensity = probDensity t * probDensity t'} singleton :: Double -> Trace Double-singleton u = Trace {variables = [u], output = u, density = 1}+singleton u = Trace {variables = [u], output = u, probDensity = 1} scored :: Log Double -> Trace ()-scored w = Trace {variables = [], output = (), density = w}+scored w = Trace {variables = [], output = (), probDensity = w} bind :: Monad m => m (Trace a) -> (a -> m (Trace b)) -> m (Trace b) bind dx f = do t1 <- dx t2 <- f (output t1)- return $ t2 {variables = variables t1 ++ variables t2, density = density t1 * density t2}+ return $ t2 {variables = variables t1 ++ variables t2, probDensity = probDensity t1 * probDensity t2} -- | A single Metropolis-corrected transition of single-site Trace MCMC.-mhTrans :: MonadSample m => Weighted (FreeSampler m) a -> Trace a -> m (Trace a)-mhTrans m t@Trace {variables = us, density = p} = do+mhTrans :: MonadSample m => (Weighted (State.Density m)) a -> Trace a -> m (Trace a)+mhTrans m t@Trace {variables = us, probDensity = p} = do let n = length us us' <- do- i <- discrete $ discreteUniformAB 0 (n -1)+ i <- discrete $ discreteUniformAB 0 (n - 1) u' <- random- let (xs, _ : ys) = splitAt i us- return $ xs ++ (u' : ys)- ((b, q), vs) <- runWriterT $ runWeighted $ Weighted.hoist (WriterT . withPartialRandomness us') m+ case splitAt i us of+ (xs, _ : ys) -> return $ xs ++ (u' : ys)+ _ -> error "impossible"+ ((b, q), vs) <- State.density (weighted m) us' let ratio = (exp . ln) $ min 1 (q * fromIntegral n / (p * fromIntegral (length vs))) accept <- bernoulli ratio return $ if accept then Trace vs b q else t +mhTransFree :: MonadSample m => Weighted (Free.Density m) a -> Trace a -> m (Trace a)+mhTransFree m t = trace <$> mhTransWithBool m t++-- | A single Metropolis-corrected transition of single-site Trace MCMC.+mhTransWithBool :: MonadSample m => Weighted (Free.Density m) a -> Trace a -> m (MHResult a)+mhTransWithBool m t@Trace {variables = us, probDensity = p} = do+ let n = length us+ us' <- do+ i <- discrete $ discreteUniformAB 0 (n - 1)+ u' <- random+ case splitAt i us of+ (xs, _ : ys) -> return $ xs ++ (u' : ys)+ _ -> error "impossible"+ ((b, q), vs) <- runWriterT $ weighted $ Weighted.hoist (WriterT . Free.density us') m+ let ratio = (exp . ln) $ min 1 (q * fromIntegral n / (p * fromIntegral (length vs)))+ accept <- bernoulli ratio+ return if accept then MHResult True (Trace vs b q) else MHResult False t+ -- | A variant of 'mhTrans' with an external sampling monad.-mhTrans' :: MonadSample m => Weighted (FreeSampler Identity) a -> Trace a -> m (Trace a)-mhTrans' m = mhTrans (Weighted.hoist (FreeSampler.hoist (return . runIdentity)) m)+mhTrans' :: MonadSample m => Weighted (Free.Density Identity) a -> Trace a -> m (Trace a)+mhTrans' m = mhTransFree (Weighted.hoist (Free.hoist (return . runIdentity)) m)++-- | burn in an MCMC chain for n steps (which amounts to dropping samples of the end of the list)+burnIn :: Functor m => Int -> m [a] -> m [a]+burnIn n = fmap dropEnd+ where+ dropEnd ls = let len = length ls in take (len - n) ls
src/Control/Monad/Bayes/Traced/Dynamic.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE RankNTypes #-}+ -- | -- Module : Control.Monad.Bayes.Traced.Dynamic -- Description : Distributions on execution traces that can be dynamically frozen@@ -8,7 +10,7 @@ -- Portability : GHC module Control.Monad.Bayes.Traced.Dynamic ( Traced,- hoistT,+ hoist, marginal, freeze, mhStep,@@ -18,16 +20,27 @@ import Control.Monad (join) import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Free (FreeSampler)+ ( MonadCond (..),+ MonadInfer,+ MonadSample (random),+ )+import Control.Monad.Bayes.Density.Free (Density) import Control.Monad.Bayes.Traced.Common+ ( Trace (..),+ bind,+ mhTransFree,+ scored,+ singleton,+ ) import Control.Monad.Bayes.Weighted (Weighted) import Control.Monad.Trans (MonadTrans (..))+import Data.List.NonEmpty as NE (NonEmpty ((:|)), toList) -- | A tracing monad where only a subset of random choices are traced and this -- subset can be adjusted dynamically.-newtype Traced m a = Traced {runTraced :: m (Weighted (FreeSampler m) a, Trace a)}+newtype Traced m a = Traced {runTraced :: m (Weighted (Density m) a, Trace a)} -pushM :: Monad m => m (Weighted (FreeSampler m) a) -> Weighted (FreeSampler m) a+pushM :: Monad m => m (Weighted (Density m) a) -> Weighted (Density m) a pushM = join . lift . lift instance Monad m => Functor (Traced m) where@@ -62,8 +75,8 @@ instance MonadInfer m => MonadInfer (Traced m) -hoistT :: (forall x. m x -> m x) -> Traced m a -> Traced m a-hoistT f (Traced c) = Traced (f c)+hoist :: (forall x. m x -> m x) -> Traced m a -> Traced m a+hoist f (Traced c) = Traced (f c) -- | Discard the trace and supporting infrastructure. marginal :: Monad m => Traced m a -> m a@@ -81,7 +94,7 @@ mhStep :: MonadSample m => Traced m a -> Traced m a mhStep (Traced c) = Traced $ do (m, t) <- c- t' <- mhTrans m t+ t' <- mhTransFree m t return (m, t') -- | Full run of the Trace Metropolis-Hastings algorithm with a specified@@ -89,11 +102,10 @@ mh :: MonadSample m => Int -> Traced m a -> m [a] mh n (Traced c) = do (m, t) <- c- let f 0 = return [t]- f k = do- ~(x : xs) <- f (k -1)- y <- mhTrans m x- return (y : x : xs)- ts <- f n- let xs = map output ts- return xs+ let f k+ | k <= 0 = return (t :| [])+ | otherwise = do+ (x :| xs) <- f (k - 1)+ y <- mhTransFree m x+ return (y :| x : xs)+ fmap (map output . NE.toList) (f n)
src/Control/Monad/Bayes/Traced/Static.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+ -- | -- Module : Control.Monad.Bayes.Traced.Static -- Description : Distributions on execution traces of full programs@@ -7,8 +10,8 @@ -- Stability : experimental -- Portability : GHC module Control.Monad.Bayes.Traced.Static- ( Traced,- hoistT,+ ( Traced (..),+ hoist, marginal, mhStep, mh,@@ -17,20 +20,30 @@ import Control.Applicative (liftA2) import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Free (FreeSampler)+ ( MonadCond (..),+ MonadInfer,+ MonadSample (random),+ )+import Control.Monad.Bayes.Density.Free (Density) import Control.Monad.Bayes.Traced.Common+ ( Trace (..),+ bind,+ mhTransFree,+ scored,+ singleton,+ ) import Control.Monad.Bayes.Weighted (Weighted) import Control.Monad.Trans (MonadTrans (..))+import Data.List.NonEmpty as NE (NonEmpty ((:|)), toList) -- | A tracing monad where only a subset of random choices are traced. -- -- The random choices that are not to be traced should be lifted from the -- transformed monad.-data Traced m a- = Traced- { model :: Weighted (FreeSampler m) a,- traceDist :: m (Trace a)- }+data Traced m a = Traced+ { model :: Weighted (Density m) a,+ traceDist :: m (Trace a)+ } instance Monad m => Functor (Traced m) where fmap f (Traced m d) = Traced (fmap f m) (fmap (fmap f) d)@@ -56,8 +69,8 @@ instance MonadInfer m => MonadInfer (Traced m) -hoistT :: (forall x. m x -> m x) -> Traced m a -> Traced m a-hoistT f (Traced m d) = Traced m (f d)+hoist :: (forall x. m x -> m x) -> Traced m a -> Traced m a+hoist f (Traced m d) = Traced m (f d) -- | Discard the trace and supporting infrastructure. marginal :: Monad m => Traced m a -> m a@@ -67,15 +80,16 @@ mhStep :: MonadSample m => Traced m a -> Traced m a mhStep (Traced m d) = Traced m d' where- d' = d >>= mhTrans m+ d' = d >>= mhTransFree m -- | Full run of the Trace Metropolis-Hastings algorithm with a specified--- number of steps.+-- number of steps. Newest samples are at the head of the list. mh :: MonadSample m => Int -> Traced m a -> m [a]-mh n (Traced m d) = fmap (map output) (f n)+mh n (Traced m d) = fmap (map output . NE.toList) (f n) where- f 0 = fmap (: []) d- f k = do- ~(x : xs) <- f (k -1)- y <- mhTrans m x- return (y : x : xs)+ f k+ | k <= 0 = fmap (:| []) d+ | otherwise = do+ (x :| xs) <- f (k - 1)+ y <- mhTransFree m x+ return (y :| x : xs)
src/Control/Monad/Bayes/Weighted.hs view
@@ -1,3 +1,7 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE RankNTypes #-}+ -- | -- Module : Control.Monad.Bayes.Weighted -- Description : Probability monad accumulating the likelihood@@ -12,24 +16,28 @@ module Control.Monad.Bayes.Weighted ( Weighted, withWeight,- runWeighted,+ weighted, extractWeight,- prior,- flatten,+ unweighted, applyWeight, hoist,+ runWeighted, ) where import Control.Monad.Bayes.Class-import Control.Monad.Trans (MonadIO, MonadTrans (..))-import Control.Monad.Trans.State (StateT (..), mapStateT, modify)+ ( MonadCond (..),+ MonadInfer,+ MonadSample,+ factor,+ )+import Control.Monad.State (MonadIO, MonadTrans, StateT (..), lift, mapStateT, modify) import Numeric.Log (Log) -- | Execute the program using the prior distribution, while accumulating likelihood. newtype Weighted m a = Weighted (StateT (Log Double) m a) -- StateT is more efficient than WriterT- deriving (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadSample)+ deriving newtype (Functor, Applicative, Monad, MonadIO, MonadTrans, MonadSample) instance Monad m => MonadCond (Weighted m) where score w = Weighted (modify (* w))@@ -37,36 +45,33 @@ instance MonadSample m => MonadInfer (Weighted m) -- | Obtain an explicit value of the likelihood for a given value.-runWeighted :: (Functor m) => Weighted m a -> m (a, Log Double)-runWeighted (Weighted m) = runStateT m 1+weighted, runWeighted :: Weighted m a -> m (a, Log Double)+weighted (Weighted m) = runStateT m 1+runWeighted = weighted -- | Compute the sample and discard the weight. -- -- This operation introduces bias.-prior :: Functor m => Weighted m a -> m a-prior = fmap fst . runWeighted+unweighted :: Functor m => Weighted m a -> m a+unweighted = fmap fst . weighted -- | Compute the weight and discard the sample. extractWeight :: Functor m => Weighted m a -> m (Log Double)-extractWeight = fmap snd . runWeighted+extractWeight = fmap snd . weighted -- | Embed a random variable with explicitly given likelihood. ----- > runWeighted . withWeight = id+-- > weighted . withWeight = id withWeight :: (Monad m) => m (a, Log Double) -> Weighted m a withWeight m = Weighted $ do (x, w) <- lift m modify (* w) return x --- | Combine weights from two different levels.-flatten :: Monad m => Weighted (Weighted m) a -> Weighted m a-flatten m = withWeight $ (\((x, p), q) -> (x, p * q)) <$> runWeighted (runWeighted m)- -- | Use the weight as a factor in the transformed monad. applyWeight :: MonadCond m => Weighted m a -> m a applyWeight m = do- (x, w) <- runWeighted m+ (x, w) <- weighted m factor w return x
+ src/Math/Integrators/StormerVerlet.hs view
@@ -0,0 +1,77 @@+module Math.Integrators.StormerVerlet+ ( integrateV,+ stormerVerlet2H,+ Integrator,+ )+where++import Control.Lens+import Control.Monad.Primitive+import Data.Vector (Vector, (!))+import qualified Data.Vector as V+import Data.Vector.Mutable+import Linear (V2 (..))++-- | Integrator function+-- - \Phi [h] |-> y_0 -> y_1+type Integrator a =+ -- | Step+ Double ->+ -- | Initial value+ a ->+ -- | Next value+ a++-- | Störmer-Verlet integration scheme for systems of the form+-- \(\mathbb{H}(p,q) = T(p) + V(q)\)+stormerVerlet2H ::+ (Applicative f, Num (f a), Show (f a), Fractional a) =>+ -- | Step size+ a ->+ -- | \(\frac{\partial H}{\partial q}\)+ (f a -> f a) ->+ -- | \(\frac{\partial H}{\partial p}\)+ (f a -> f a) ->+ -- | Current \((p, q)\) as a 2-dimensional vector+ V2 (f a) ->+ -- | New \((p, q)\) as a 2-dimensional vector+ V2 (f a)+stormerVerlet2H hh nablaQ nablaP prev =+ V2 qNew pNew+ where+ h2 = hh / 2+ hhs = pure hh+ hh2s = pure h2+ qsPrev = prev ^. _1+ psPrev = prev ^. _2+ pp2 = psPrev - hh2s * nablaQ qsPrev+ qNew = qsPrev + hhs * nablaP pp2+ pNew = pp2 - hh2s * nablaQ qNew++-- |+-- Integrate ODE equation using fixed steps set by a vector, and returns a vector+-- of solutions corrensdonded to times that was requested.+-- It takes Vector of time points as a parameter and returns a vector of results+integrateV ::+ PrimMonad m =>+ -- | Internal integrator+ Integrator a ->+ -- | initial value+ a ->+ -- | vector of time points+ Vector Double ->+ -- | vector of solution+ m (Vector a)+integrateV integrator initial times = do+ out <- new (V.length times)+ write out 0 initial+ compute initial 1 out+ V.unsafeFreeze out+ where+ compute y i out+ | i == V.length times = return ()+ | otherwise = do+ let h = (times ! i) - (times ! (i - 1))+ y' = integrator h y+ write out i y'+ compute y' (i + 1) out
test/Spec.hs view
@@ -1,67 +1,166 @@-import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck-import qualified TestEnumerator-import qualified TestInference-import qualified TestPopulation-import qualified TestSequential-import qualified TestWeighted+{-# LANGUAGE ImportQualifiedPost #-} +import Data.AEq (AEq ((~==)))+import Test.Hspec (context, describe, hspec, it, shouldBe)+import Test.Hspec.QuickCheck (prop)+import Test.QuickCheck (ioProperty, property, (==>))+import TestAdvanced qualified+import TestDistribution qualified+import TestEnumerator qualified+import TestInference qualified+import TestIntegrator qualified+import TestPipes (hmms)+import TestPipes qualified+import TestPopulation qualified+import TestSampler qualified+import TestSequential qualified+import TestStormerVerlet qualified+import TestWeighted qualified+ main :: IO ()-main = hspec $ do- describe "Weighted"- $ it "accumulates likelihood correctly"- $ do- passed <- TestWeighted.passed- passed `shouldBe` True- describe "Dist" $ do+main = hspec do+ describe "Stormer Verlet" $+ it "conserves energy" $+ do+ p1 <- TestStormerVerlet.passed1+ p1 `shouldBe` True+ describe "Distribution" $+ it "gives correct mean, variance and covariance" $+ do+ p1 <- TestDistribution.passed1+ p1 `shouldBe` True+ p2 <- TestDistribution.passed2+ p2 `shouldBe` True+ p3 <- TestDistribution.passed3+ p3 `shouldBe` True+ describe "Weighted" $+ it "accumulates likelihood correctly" $+ do+ passed <- TestWeighted.passed+ passed `shouldBe` True+ describe "Enumerator" do it "sorts samples and aggregates weights" $ TestEnumerator.passed2 `shouldBe` True it "gives correct answer for the sprinkler model" $ TestEnumerator.passed3 `shouldBe` True it "computes expectation correctly" $ TestEnumerator.passed4 `shouldBe` True- describe "Population" $ do- context "controlling population" $ do- it "preserves the population when not explicitly altered" $ do+ describe "Integrator Expectation" do+ prop "expectation numerically" $+ \mean var ->+ var > 0 ==> property $ TestIntegrator.normalExpectation mean (sqrt var) ~== mean+ describe "Integrator Variance" do+ prop "variance numerically" $+ \mean var ->+ var > 0 ==> property $ TestIntegrator.normalVariance mean (sqrt var) ~== var+ describe "Sampler mean and variance" do+ it "gets right mean and variance" $+ TestSampler.testMeanAndVariance `shouldBe` True+ describe "Integrator Volume" do+ prop "volume sums to 1" $+ property $ \case+ [] -> True+ ls -> (TestIntegrator.volumeIsOne ls)++ describe "Integrator" do+ it "" $+ all+ (== True)+ [ TestIntegrator.passed1,+ TestIntegrator.passed2,+ TestIntegrator.passed3,+ TestIntegrator.passed4,+ TestIntegrator.passed5,+ TestIntegrator.passed6,+ TestIntegrator.passed7,+ TestIntegrator.passed8,+ TestIntegrator.passed9,+ TestIntegrator.passed10,+ TestIntegrator.passed11,+ TestIntegrator.passed12,+ TestIntegrator.passed13,+ TestIntegrator.passed14+ ]+ `shouldBe` True++ describe "Population" do+ context "controlling population" do+ it "preserves the population when not explicitly altered" do popSize <- TestPopulation.popSize popSize `shouldBe` 5- it "multiplies the number of samples when spawn invoked twice" $ do+ it "multiplies the number of samples when spawn invoked twice" do manySize <- TestPopulation.manySize manySize `shouldBe` 15 it "correctly computes population average" $ TestPopulation.popAvgCheck `shouldBe` True- context "distribution-preserving transformations" $ do- it "collapse preserves the distribution" $ do+ context "distribution-preserving transformations" do+ it "collapse preserves the distribution" do TestPopulation.transCheck1 `shouldBe` True TestPopulation.transCheck2 `shouldBe` True- it "resample preserves the distribution" $ do+ it "resample preserves the distribution" do TestPopulation.resampleCheck 1 `shouldBe` True TestPopulation.resampleCheck 2 `shouldBe` True- describe "Sequential" $ do- it "stops at every factor" $ do+ describe "Sequential" do+ it "stops at every factor" do TestSequential.checkTwoSync 0 `shouldBe` True TestSequential.checkTwoSync 1 `shouldBe` True TestSequential.checkTwoSync 2 `shouldBe` True it "preserves the distribution" $ TestSequential.checkPreserve `shouldBe` True- it "produces correct intermediate weights" $ do+ it "produces correct intermediate weights" do TestSequential.checkSync 0 `shouldBe` True TestSequential.checkSync 1 `shouldBe` True TestSequential.checkSync 2 `shouldBe` True- describe "SMC" $ do+ describe "SMC" do it "terminates" $ seq TestInference.checkTerminateSMC () `shouldBe` () it "preserves the distribution on the sprinkler model" $ TestInference.checkPreserveSMC `shouldBe` True prop "number of particles is equal to its second parameter" $ \observations particles ->- observations >= 0 && particles >= 1 ==> ioProperty $ do+ observations >= 0 && particles >= 1 ==> ioProperty do checkParticles <- TestInference.checkParticles observations particles return $ checkParticles == particles- describe "SMC with systematic resampling"- $ prop "number of particles is equal to its second parameter"- $ \observations particles ->- observations >= 0 && particles >= 1 ==> ioProperty $ do- checkParticles <- TestInference.checkParticlesSystematic observations particles- return $ checkParticles == particles+ describe "SMC with systematic resampling" $+ prop "number of particles is equal to its second parameter" $+ \observations particles ->+ observations >= 0 && particles >= 1 ==> ioProperty do+ checkParticles <- TestInference.checkParticlesSystematic observations particles+ return $ checkParticles == particles+ describe "Equivalent Expectations" do+ prop "Gamma Normal" $+ ioProperty . TestInference.testGammaNormal+ prop "Normal Normal" $+ \n -> ioProperty (TestInference.testNormalNormal [max (-3) $ min 3 n])+ prop "Beta Bernoulli" $+ ioProperty . TestInference.testBetaBernoulli+ describe "Pipes: Urn" do+ it "Distributions are equivalent" do+ TestPipes.urns 10 `shouldBe` True+ describe "Pipes: HMM" do+ prop "pipe model is equivalent to standard model" $+ \num -> property $ hmms $ take 5 num++ describe "SMC with stratified resampling" $+ prop "number of particles is equal to its second parameter" $+ \observations particles ->+ observations >= 0 && particles >= 1 ==> ioProperty do+ checkParticles <- TestInference.checkParticlesStratified observations particles+ return $ checkParticles == particles++ describe "Expectation from all inference methods" $+ it "gives correct answer for the sprinkler model" do+ passed1 <- TestAdvanced.passed1+ passed1 `shouldBe` True+ passed2 <- TestAdvanced.passed2+ passed2 `shouldBe` True+ passed3 <- TestAdvanced.passed3+ passed3 `shouldBe` True+ passed4 <- TestAdvanced.passed4+ passed4 `shouldBe` True+ passed5 <- TestAdvanced.passed5+ passed5 `shouldBe` True+ passed6 <- TestAdvanced.passed6+ passed6 `shouldBe` True+ passed7 <- TestAdvanced.passed7+ passed7 `shouldBe` True
+ test/TestAdvanced.hs view
@@ -0,0 +1,65 @@+module TestAdvanced where++import ConjugatePriors+ ( betaBernoulli',+ betaBernoulliAnalytic,+ gammaNormal',+ gammaNormalAnalytic,+ normalNormal',+ normalNormalAnalytic,+ )+import Control.Arrow+import Control.Monad (join, replicateM)+import Control.Monad.Bayes.Class+import Control.Monad.Bayes.Enumerator+import Control.Monad.Bayes.Inference.MCMC+import Control.Monad.Bayes.Inference.PMMH+import Control.Monad.Bayes.Inference.RMSMC+import Control.Monad.Bayes.Inference.SMC+import Control.Monad.Bayes.Inference.SMC2+import Control.Monad.Bayes.Population+import Control.Monad.Bayes.Sampler.Strict+import Control.Monad.Bayes.Traced+import Control.Monad.Bayes.Weighted+import Numeric.Log (Log)++mcmcConfig :: MCMCConfig+mcmcConfig = MCMCConfig {numMCMCSteps = 0, numBurnIn = 0, proposal = SingleSiteMH}++smcConfig :: MonadSample m => SMCConfig m+smcConfig = SMCConfig {numSteps = 0, numParticles = 1000, resampler = resampleMultinomial}++passed1, passed2, passed3, passed4, passed5, passed6, passed7 :: IO Bool+passed1 = do+ sample <- sampleIOfixed $ mcmc MCMCConfig {numMCMCSteps = 10000, numBurnIn = 5000, proposal = SingleSiteMH} random+ return $ abs (0.5 - (expectation id $ fromList $ toEmpirical sample)) < 0.01+passed2 = do+ sample <- sampleIOfixed $ population $ smc (SMCConfig {numSteps = 0, numParticles = 10000, resampler = resampleMultinomial}) random+ return $ close 0.5 sample+passed3 = do+ sample <- sampleIOfixed $ population $ rmsmcDynamic mcmcConfig smcConfig random+ return $ close 0.5 sample+passed4 = do+ sample <- sampleIOfixed $ population $ rmsmcBasic mcmcConfig smcConfig random+ return $ close 0.5 sample+passed5 = do+ sample <- sampleIOfixed $ population $ rmsmc mcmcConfig smcConfig random+ return $ close 0.5 sample+passed6 = do+ sample <-+ fmap join $+ sampleIOfixed $+ pmmh+ mcmcConfig {numMCMCSteps = 100}+ smcConfig {numSteps = 0, numParticles = 100}+ random+ (normal 0)+ return $ close 0.0 sample++close :: Double -> [(Double, Log Double)] -> Bool++passed7 = do+ sample <- fmap join $ sampleIOfixed $ fmap (fmap (\(x, y) -> fmap (second (* y)) x)) $ population $ smc2 0 100 100 100 random (normal 0)+ return $ close 0.0 sample++close n sample = abs (n - (expectation id $ fromList $ toEmpiricalWeighted sample)) < 0.01
+ test/TestDistribution.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE Trustworthy #-}++module TestDistribution+ ( passed1,+ passed2,+ passed3,+ )+where++import Control.Monad (replicateM)+import Control.Monad.Bayes.Class (MonadSample, mvNormal)+import Control.Monad.Bayes.Sampler.Strict+import Control.Monad.Identity (runIdentity)+import Control.Monad.State (evalStateT)+import Data.Matrix (fromList)+import Data.Vector qualified as V+import System.Random.MWC (toSeed)++-- Test the sampled covariance is approximately the same as the+-- specified covariance.+passed1 :: IO Bool+passed1 = sampleIOfixed $ do+ let mu = (V.fromList [0.0, 0.0])+ sigma11 = 2.0+ sigma12 = 1.0+ bigSigma = (fromList 2 2 [sigma11, sigma12, sigma12, sigma11])+ nSamples = 200000+ nSamples' = fromIntegral nSamples+ ss <- replicateM nSamples $ (mvNormal mu bigSigma)+ let xbar = (/ nSamples') $ sum $ fmap (V.! 0) ss+ ybar = (/ nSamples') $ sum $ fmap (V.! 1) ss+ let term1 = (/ nSamples') $ sum $ zipWith (*) (fmap (V.! 0) ss) (fmap (V.! 1) ss)+ let term2 = xbar * ybar+ return $ abs (sigma12 - (term1 - term2)) < 2e-2++-- Test the sampled means are approximately the same as the specified+-- means.+passed2 :: IO Bool+passed2 = sampleIOfixed $ do+ let mu = (V.fromList [0.0, 0.0])+ sigma11 = 2.0+ sigma12 = 1.0+ bigSigma = (fromList 2 2 [sigma11, sigma12, sigma12, sigma11])+ nSamples = 100000+ nSamples' = fromIntegral nSamples+ ss <- replicateM nSamples $ (mvNormal mu bigSigma)+ let xbar = (/ nSamples') $ sum $ fmap (V.! 0) ss+ ybar = (/ nSamples') $ sum $ fmap (V.! 1) ss+ return $ abs xbar < 1e-2 && abs ybar < 1e-2++-- Test the sampled variances are approximately the same as the+-- specified variances.+passed3 :: IO Bool+passed3 = sampleIOfixed $ do+ let mu = (V.fromList [0.0, 0.0])+ sigma11 = 2.0+ sigma12 = 1.0+ bigSigma = (fromList 2 2 [sigma11, sigma12, sigma12, sigma11])+ nSamples = 200000+ nSamples' = fromIntegral nSamples+ ss <- replicateM nSamples $ (mvNormal mu bigSigma)+ let xbar = (/ nSamples') $ sum $ fmap (V.! 0) ss+ ybar = (/ nSamples') $ sum $ fmap (V.! 1) ss+ let xbar2 = (/ nSamples') $ sum $ fmap (\x -> x * x) $ fmap (V.! 0) ss+ ybar2 = (/ nSamples') $ sum $ fmap (\x -> x * x) $ fmap (V.! 1) ss+ let xvar = xbar2 - xbar * xbar+ let yvar = ybar2 - ybar * ybar+ return $ abs (xvar - sigma11) < 1e-2 && abs (yvar - sigma11) < 2e-2
test/TestEnumerator.hs view
@@ -1,11 +1,19 @@-module TestEnumerator where+{-# LANGUAGE ImportQualifiedPost #-} +module TestEnumerator (passed1, passed2, passed3, passed4) where+ import Control.Monad.Bayes.Class+ ( MonadSample (categorical, uniformD),+ ) import Control.Monad.Bayes.Enumerator-import Data.AEq-import qualified Data.Vector as V-import Numeric.Log-import Sprinkler+ ( enumerator,+ evidence,+ expectation,+ )+import Data.AEq (AEq ((~==)))+import Data.Vector qualified as V+import Numeric.Log (Log (ln))+import Sprinkler (hard, soft) unnorm :: MonadSample m => m Int unnorm = categorical $ V.fromList [0.5, 0.8]@@ -20,10 +28,10 @@ return (x + y) passed2 :: Bool-passed2 = enumerate agg ~== [(1, 0.25), (2, 0.5), (3, 0.25)]+passed2 = enumerator agg ~== [(2, 0.5), (1, 0.25), (3, 0.25)] passed3 :: Bool-passed3 = enumerate Sprinkler.hard ~== enumerate Sprinkler.soft+passed3 = enumerator Sprinkler.hard ~== enumerator Sprinkler.soft passed4 :: Bool passed4 =
test/TestInference.hs view
@@ -1,16 +1,34 @@+{-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE Rank2Types #-} {-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wno-missing-export-lists #-} module TestInference where -import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Enumerator+import ConjugatePriors+ ( betaBernoulli',+ betaBernoulliAnalytic,+ gammaNormal',+ gammaNormalAnalytic,+ normalNormal',+ normalNormalAnalytic,+ )+import Control.Monad (replicateM)+import Control.Monad.Bayes.Class (MonadInfer, posterior)+import Control.Monad.Bayes.Enumerator (enumerator) import Control.Monad.Bayes.Inference.SMC+import Control.Monad.Bayes.Integrator (normalize)+import Control.Monad.Bayes.Integrator qualified as Integrator import Control.Monad.Bayes.Population-import Control.Monad.Bayes.Sampler-import Data.AEq-import Numeric.Log-import Sprinkler+import Control.Monad.Bayes.Population (collapse, runPopulation)+import Control.Monad.Bayes.Sampler.Strict (Sampler, sampleIOfixed)+import Control.Monad.Bayes.Sampler.Strict qualified as Sampler+import Control.Monad.Bayes.Weighted (Weighted)+import Control.Monad.Bayes.Weighted qualified as Weighted+import Data.AEq (AEq ((~==)))+import Numeric.Log (Log)+import Sprinkler (soft)+import System.Random.Stateful (IOGenM, StdGen, mkStdGen, newIOGenM) sprinkler :: MonadInfer m => m Bool sprinkler = Sprinkler.soft@@ -18,16 +36,62 @@ -- | Count the number of particles produced by SMC checkParticles :: Int -> Int -> IO Int checkParticles observations particles =- sampleIOfixed (fmap length (runPopulation $ smcMultinomial observations particles Sprinkler.soft))+ sampleIOfixed (fmap length (population $ smc SMCConfig {numSteps = observations, numParticles = particles, resampler = resampleMultinomial} Sprinkler.soft)) checkParticlesSystematic :: Int -> Int -> IO Int checkParticlesSystematic observations particles =- sampleIOfixed (fmap length (runPopulation $ smcSystematic observations particles Sprinkler.soft))+ sampleIOfixed (fmap length (population $ smc SMCConfig {numSteps = observations, numParticles = particles, resampler = resampleSystematic} Sprinkler.soft)) +checkParticlesStratified :: Int -> Int -> IO Int+checkParticlesStratified observations particles =+ sampleIOfixed (fmap length (population $ smc SMCConfig {numSteps = observations, numParticles = particles, resampler = resampleStratified} Sprinkler.soft))+ checkTerminateSMC :: IO [(Bool, Log Double)]-checkTerminateSMC = sampleIOfixed (runPopulation $ smcMultinomial 2 5 sprinkler)+checkTerminateSMC = sampleIOfixed (population $ smc SMCConfig {numSteps = 2, numParticles = 5, resampler = resampleMultinomial} sprinkler) checkPreserveSMC :: Bool checkPreserveSMC =- (enumerate . collapse . smcMultinomial 2 2) sprinkler- ~== enumerate sprinkler+ (enumerator . collapse . smc SMCConfig {numSteps = 2, numParticles = 2, resampler = resampleMultinomial}) sprinkler+ ~== enumerator sprinkler++expectationNearNumeric ::+ Weighted Integrator.Integrator Double ->+ Weighted Integrator.Integrator Double ->+ Double+expectationNearNumeric x y =+ let e1 = Integrator.expectation $ normalize x+ e2 = Integrator.expectation $ normalize y+ in (abs (e1 - e2))++expectationNearSampling ::+ Weighted (Sampler (IOGenM StdGen) IO) Double ->+ Weighted (Sampler (IOGenM StdGen) IO) Double ->+ IO Double+expectationNearSampling x y = do+ e1 <- sampleIOfixed $ fmap Sampler.sampleMean $ replicateM 10 $ Weighted.weighted x+ e2 <- sampleIOfixed $ fmap Sampler.sampleMean $ replicateM 10 $ Weighted.weighted y+ return (abs (e1 - e2))++testNormalNormal :: [Double] -> IO Bool+testNormalNormal n = do+ let e =+ expectationNearNumeric+ (posterior (normalNormal' 1 (1, 10)) n)+ (normalNormalAnalytic 1 (1, 10) n)+ return (e < 1e-0)++testGammaNormal :: [Double] -> IO Bool+testGammaNormal n = do+ let e =+ expectationNearNumeric+ (posterior (gammaNormal' (1, 1)) n)+ (gammaNormalAnalytic (1, 1) n)+ return (e < 1e-1)++testBetaBernoulli :: [Bool] -> IO Bool+testBetaBernoulli bs = do+ let e =+ expectationNearNumeric+ (posterior (betaBernoulli' (1, 1)) bs)+ (betaBernoulliAnalytic (1, 1) bs)+ return (e < 1e-1)
+ test/TestIntegrator.hs view
@@ -0,0 +1,151 @@+{-# LANGUAGE BlockArguments #-}++module TestIntegrator where++import Control.Monad (replicateM)+import Control.Monad.Bayes.Class+ ( MonadCond (score),+ MonadInfer,+ MonadSample (bernoulli, gamma, normal, random, uniformD),+ condition,+ factor,+ normalPdf,+ )+import Control.Monad.Bayes.Integrator+import Control.Monad.Bayes.Sampler.Strict+import Control.Monad.Bayes.Weighted (weighted)+import Control.Monad.ST (ST, runST)+import Data.AEq (AEq ((~==)))+import Data.List (sortOn)+import Data.Set (fromList)+import Numeric.Log (Log (Exp, ln))+import Sprinkler (hard, soft)+import Statistics.Distribution (Distribution (cumulative))+import Statistics.Distribution.Normal (normalDistr)++normalExpectation :: Double -> Double -> Double+normalExpectation mean std = expectation (normal mean std)++normalVariance :: Double -> Double -> Double+normalVariance mean std = variance (normal mean std)++volumeIsOne :: [Double] -> Bool+volumeIsOne = (~== 1.0) . volume . uniformD++agg :: MonadSample m => m Int+agg = do+ x <- uniformD [0, 1]+ y <- uniformD [2, 1]+ return (x + y)++within :: (Ord a, Num a) => a -> a -> a -> Bool+within n x y = abs (x - y) < n++passed1,+ passed2,+ passed3,+ passed4,+ passed5,+ passed6,+ passed7,+ passed8,+ passed9,+ passed10,+ passed11,+ passed12,+ passed13,+ passed14 ::+ Bool+-- enumerator from Integrator works+passed1 =+ sortOn fst (enumeratorWith (fromList [3, 1, 2]) agg)+ ~== sortOn fst [(2, 0.5), (1, 0.25), (3, 0.25)]+-- hard and soft sprinkers are equivalent under enumerator from Integrator+passed2 =+ enumeratorWith (fromList [True, False]) (normalize (Sprinkler.hard))+ ~== enumeratorWith (fromList [True, False]) (normalize (Sprinkler.soft))+-- expectation is as expected+passed3 =+ expectation (fmap ((** 2) . (+ 1)) $ uniformD [0, 1]) == 2.5+-- distribution is normalized+passed4 = volume (uniformD [1, 2]) ~== 1.0+-- enumerator is as expected+passed5 =+ sortOn fst (enumeratorWith (fromList [0, 1 :: Int]) (empirical [0 :: Int, 1, 1, 1]))+ == sortOn fst [(1, 0.75), (0, 0.25)]+-- normalization works right for enumerator, when there is conditioning+passed6 =+ sortOn fst [(2, 0.5), (3, 0.5), (1, 0.0)]+ == sortOn+ fst+ ( enumeratorWith (fromList [1, 2, 3]) $+ normalize $ do+ x <- uniformD [1 :: Int, 2, 3]+ condition (x > 1)+ return x+ )+-- soft factor statements work with enumerator and normalization+passed7 =+ sortOn fst [(True, 0.75), (False, 0.25)]+ ~== sortOn+ fst+ ( enumeratorWith (fromList [True, False]) $ normalize do+ x <- bernoulli 0.5+ factor $ if x then 0.3 else 0.1+ return x+ )+-- volume of weight remains 1+passed8 =+ 1+ == ( volume $+ fmap (ln . exp . snd) $ weighted do+ x <- bernoulli 0.5+ factor $ if x then 0.2 else 0.1+ return x+ )+-- normal probability in positive region is half+passed9 = probability (1, 1000) (normal 1 10) - 0.5 < 0.05+-- cdf as expected+passed10 = cdf (normal 5 5) 5 - 0.5 < 0.05+-- cdf as expected+passed11 =+ (within 0.001)+ ( cdf+ ( do+ x <- normal 0 1+ return x+ )+ 3+ )+ (cumulative (normalDistr 0 1) 3)+-- volume as expected+passed12 =+ volume+ ( do+ x <- gamma 2 3+ return x+ )+ ~== 1+-- normalization preserves volume+passed13 =+ (volume . normalize)+ ( do+ x <- gamma 2 3+ factor (normalPdf 0 1 x)+ return x+ )+ ~== 1+-- sampler and integrator agree on a non-trivial model+passed14 =+ let sample = runST $ sampleSTfixed $ fmap sampleMean $ replicateM 10000 $ weighted $ model1+ quadrature = expectation $ normalize $ model1+ in abs (sample - quadrature) < 0.01++model1 :: MonadInfer m => m Double+model1 = do+ x <- random+ y <- random+ score (Exp $ log (f x + y))+ return x+ where+ f x = cos (x ** 4) + x ** 3
+ test/TestPipes.hs view
@@ -0,0 +1,21 @@+{-# OPTIONS_GHC -Wno-monomorphism-restriction #-}++module TestPipes where++import BetaBin (urn, urnP)+import Control.Monad.Bayes.Class ()+import Control.Monad.Bayes.Enumerator (enumerator)+import Data.AEq (AEq ((~==)))+import HMM (hmm, hmmPosterior)+import Pipes ((>->))+import Pipes.Prelude (toListM)+import qualified Pipes.Prelude as Pipes++urns :: Int -> Bool+urns n = enumerator (urn n) ~== enumerator (urnP n)++hmms :: [Double] -> Bool+hmms observations =+ let hmmWithoutPipe = hmm observations+ hmmWithPipe = reverse . init <$> toListM (hmmPosterior observations)+ in enumerator hmmWithPipe ~== enumerator hmmWithoutPipe
test/TestPopulation.hs view
@@ -1,42 +1,51 @@-module TestPopulation where+module TestPopulation (weightedSampleSize, popSize, manySize, sprinkler, sprinklerExact, transCheck1, transCheck2, resampleCheck, popAvgCheck) where -import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Enumerator+import Control.Monad.Bayes.Class (MonadInfer, MonadSample)+import Control.Monad.Bayes.Enumerator (enumerator, expectation) import Control.Monad.Bayes.Population as Population-import Control.Monad.Bayes.Sampler-import Data.AEq-import Sprinkler+ ( Population,+ collapse,+ popAvg,+ population,+ pushEvidence,+ resampleMultinomial,+ spawn,+ )+import Control.Monad.Bayes.Sampler.Strict (sampleIOfixed)+import Data.AEq (AEq ((~==)))+import Sprinkler (soft)+import System.Random.Stateful (mkStdGen, newIOGenM) weightedSampleSize :: MonadSample m => Population m a -> m Int-weightedSampleSize = fmap length . runPopulation+weightedSampleSize = fmap length . population popSize :: IO Int-popSize = sampleIOfixed $ weightedSampleSize $ spawn 5 >> sprinkler+popSize =+ sampleIOfixed (weightedSampleSize $ spawn 5 >> sprinkler) manySize :: IO Int-manySize = sampleIOfixed $ weightedSampleSize $ spawn 5 >> sprinkler >> spawn 3+manySize =+ sampleIOfixed (weightedSampleSize $ spawn 5 >> sprinkler >> spawn 3) sprinkler :: MonadInfer m => m Bool sprinkler = Sprinkler.soft sprinklerExact :: [(Bool, Double)]-sprinklerExact = enumerate Sprinkler.soft----all_check = (mass (Population.all id (spawn 2 >> sprinkler)) True) ~== 0.09+sprinklerExact = enumerator Sprinkler.soft transCheck1 :: Bool transCheck1 =- enumerate (collapse sprinkler)+ enumerator (collapse sprinkler) ~== sprinklerExact transCheck2 :: Bool transCheck2 =- enumerate (collapse (spawn 2 >> sprinkler))+ enumerator (collapse (spawn 2 >> sprinkler)) ~== sprinklerExact resampleCheck :: Int -> Bool resampleCheck n =- (enumerate . collapse . resampleMultinomial) (spawn n >> sprinkler)+ (enumerator . collapse . resampleMultinomial) (spawn n >> sprinkler) ~== sprinklerExact popAvgCheck :: Bool
+ test/TestSampler.hs view
@@ -0,0 +1,15 @@+module TestSampler where++import qualified Control.Foldl as Fold+import Control.Monad (replicateM)+import Control.Monad.Bayes.Class (MonadSample (normal))+import Control.Monad.Bayes.Sampler.Strict (sampleSTfixed)+import Control.Monad.ST (ST, runST)++testMeanAndVariance :: Bool+testMeanAndVariance = isDiff+ where+ m = runST (sampleSTfixed (foldWith Fold.mean (normal 2 4)))+ v = runST (sampleSTfixed (foldWith Fold.variance (normal 2 4)))+ foldWith f = fmap (Fold.fold f) . replicateM 100000+ isDiff = abs (2 - m) < 0.01 && abs (16 - v) < 0.1
test/TestSequential.hs view
@@ -1,10 +1,14 @@-module TestSequential where+module TestSequential (twoSync, finishedTwoSync, checkTwoSync, checkPreserve, pFinished, isFinished, checkSync) where import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Enumerator as Dist-import Control.Monad.Bayes.Sequential-import Data.AEq-import Sprinkler+ ( MonadInfer,+ MonadSample (uniformD),+ factor,+ )+import Control.Monad.Bayes.Enumerator as Dist (enumerator, mass)+import Control.Monad.Bayes.Sequential.Coroutine (advance, finish, finished)+import Data.AEq (AEq ((~==)))+import Sprinkler (soft) twoSync :: MonadInfer m => m Int twoSync = do@@ -18,7 +22,7 @@ finishedTwoSync n = finished (run n twoSync) where run 0 d = d- run k d = run (k -1) (advance d)+ run k d = run (k - 1) (advance d) checkTwoSync :: Int -> Bool checkTwoSync 0 = mass (finishedTwoSync 0) False ~== 1@@ -30,7 +34,7 @@ sprinkler = Sprinkler.soft checkPreserve :: Bool-checkPreserve = enumerate (finish sprinkler) ~== enumerate sprinkler+checkPreserve = enumerator (finish sprinkler) ~== enumerator sprinkler pFinished :: Int -> Double pFinished 0 = 0.8267716535433071@@ -42,7 +46,7 @@ isFinished n = finished (run n sprinkler) where run 0 d = d- run k d = run (k -1) (advance d)+ run k d = run (k - 1) (advance d) checkSync :: Int -> Bool checkSync n = mass (isFinished n) True ~== pFinished n
+ test/TestStormerVerlet.hs view
@@ -0,0 +1,98 @@+module TestStormerVerlet+ ( passed1,+ )+where++import Control.Lens+import Control.Monad.ST+import Data.Maybe (fromJust)+import qualified Data.Vector as V+import qualified Linear as L+import Linear.V+import Math.Integrators.StormerVerlet++gConst :: Double+gConst = 6.67384e-11++nStepsTwoPlanets :: Int+nStepsTwoPlanets = 44++stepTwoPlanets :: Double+stepTwoPlanets = 24 * 60 * 60 * 100++sunMass, jupiterMass :: Double+sunMass = 1.9889e30+jupiterMass = 1.8986e27++jupiterPerihelion :: Double+jupiterPerihelion = 7.405736e11++jupiterV :: [Double]+jupiterV = [-1.0965244901087316e02, -1.3710001990210707e04, 0.0]++jupiterQ :: [Double]+jupiterQ = [negate jupiterPerihelion, 0.0, 0.0]++sunV :: [Double]+sunV = [0.0, 0.0, 0.0]++sunQ :: [Double]+sunQ = [0.0, 0.0, 0.0]++tm :: V.Vector Double+tm = V.enumFromStepN 0 stepTwoPlanets nStepsTwoPlanets++keplerP :: L.V2 (L.V3 Double) -> L.V2 (L.V3 Double)+keplerP (L.V2 p1 p2) = L.V2 dHdP1 dHdP2+ where+ dHdP1 = p1 / pure jupiterMass+ dHdP2 = p2 / pure sunMass++keplerQ :: L.V2 (L.V3 Double) -> L.V2 (L.V3 Double)+keplerQ (L.V2 q1 q2) = L.V2 dHdQ1 dHdQ2+ where+ r = q2 L.^-^ q1+ ri = r `L.dot` r+ rr = ri * (sqrt ri)+ q1' = pure gConst * r / pure rr+ q2' = negate q1'+ dHdQ1 = q1' * pure sunMass * pure jupiterMass+ dHdQ2 = q2' * pure sunMass * pure jupiterMass++listToV3 :: [a] -> L.V3 a+listToV3 [x, y, z] = fromV . fromJust . fromVector . V.fromList $ [x, y, z]+listToV3 xs = error $ "Only supply 3 elements not: " ++ show (length xs)++initPQ2s :: L.V2 (L.V2 (L.V3 Double))+initPQ2s =+ L.V2+ (L.V2 (listToV3 jupiterQ) (listToV3 sunQ))+ (L.V2 (pure jupiterMass * listToV3 jupiterV) (pure sunMass * listToV3 sunV))++result2 :: V.Vector (L.V2 (L.V2 (L.V3 Double)))+result2 = runST $ integrateV (\h -> stormerVerlet2H (pure h) keplerQ keplerP) initPQ2s tm++energy :: (L.V2 (L.V2 (L.V3 Double))) -> Double+energy x = keJ + keS + peJ + peS+ where+ qs = x ^. _1+ ps = x ^. _2+ qJ = qs ^. _1+ qS = qs ^. _2+ pJ = ps ^. _1+ pS = ps ^. _2+ keJ = (* 0.5) $ (/ jupiterMass) $ sum $ fmap (^ 2) pJ+ keS = (* 0.5) $ (/ sunMass) $ sum $ fmap (^ 2) pS+ r = qJ L.^-^ qS+ ri = r `L.dot` r+ peJ = 0.5 * gConst * sunMass * jupiterMass / (sqrt ri)+ peS = 0.5 * gConst * sunMass * jupiterMass / (sqrt ri)++energies :: V.Vector Double+energies = fmap energy result2++diffs :: V.Vector Double+diffs = V.zipWith (\x y -> abs (x - y) / x) energies (V.tail energies)++passed1 :: IO Bool+passed1 = return $ V.all (< 1.0e-3) diffs
test/TestWeighted.hs view
@@ -1,14 +1,19 @@ {-# LANGUAGE TypeFamilies #-} -module TestWeighted where+module TestWeighted (check, passed, result, model) where import Control.Monad.Bayes.Class-import Control.Monad.Bayes.Sampler-import Control.Monad.Bayes.Weighted-import Control.Monad.State-import Data.AEq+ ( MonadInfer,+ MonadSample (normal, uniformD),+ factor,+ )+import Control.Monad.Bayes.Sampler.Strict (sampleIOfixed)+import Control.Monad.Bayes.Weighted (weighted)+import Control.Monad.State (unless, when)+import Data.AEq (AEq ((~==))) import Data.Bifunctor (second)-import Numeric.Log+import Numeric.Log (Log (Exp, ln))+import System.Random.Stateful (mkStdGen, newIOGenM) model :: MonadInfer m => m (Int, Double) model = do@@ -19,7 +24,7 @@ return (n, x) result :: MonadSample m => m ((Int, Double), Double)-result = second (exp . ln) <$> runWeighted model+result = second (exp . ln) <$> weighted model passed :: IO Bool passed = fmap check (sampleIOfixed result)