mighty-metropolis

The classic Metropolis algorithm.
Log | Files | Refs | README | LICENSE

commit 161bcad923a05676b2ec84e1ec9cd267b1d6a980
parent 79110ce6e9d0dcacf3fc0cb894af22bca88919aa
Author: Jared Tobin <jared@jtobin.ca>
Date:   Tue,  6 Oct 2015 12:18:36 +1300

Much-needed update.

Diffstat:
M.gitignore | 3+++
DExamples/code/Rosenbrock_MH.hs | 39---------------------------------------
MNumeric/MCMC/Metropolis.hs | 160++++++++++++++++++++++++++++++++++++++-----------------------------------------
MREADME.md | 6+++---
Mmighty-metropolis.cabal | 43++++++++++++++++++++++++++++---------------
Astack.yaml | 5+++++
Atest/Rosenbrock.hs | 15+++++++++++++++
7 files changed, 131 insertions(+), 140 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -12,3 +12,6 @@ _site/ build cabal-dev site +debug +.stack-work +Examples diff --git a/Examples/code/Rosenbrock_MH.hs b/Examples/code/Rosenbrock_MH.hs @@ -1,39 +0,0 @@ -import Numeric.MCMC.Metropolis -import System.Random.MWC -import System.Environment -import System.Exit -import System.IO -import Control.Monad - -target :: RealFloat a => [a] -> a -target [x0, x1] = (-1)*(5*(x1 - x0^2)^2 + 0.05*(1 - x0)^2) - -main = do - args <- getArgs - when (args == []) $ do - putStrLn "(mighty-metropolis) Rosenbrock density " - putStrLn "Usage: ./Rosenbrock_MH <numSteps> <stepSize> <inits> " - putStrLn " " - putStrLn "numSteps : Number of Markov chain iterations to run." - putStrLn "stepSize : Perturbation scaling parameter. " - putStrLn "inits : Filepath containing points at which to " - putStrLn " initialize the chain. " - exitSuccess - - inits <- fmap (map read . words) (readFile (args !! 2)) :: IO [Double] - - let nepochs = read (head args) :: Int - eps = read (args !! 1) :: Double - params = Options target eps - config = MarkovChain inits 0 - - g <- create - results <- runChain params nepochs 1 config g - - hPutStrLn stderr $ - let nAcc = accepts results - total = nepochs - in show nAcc ++ " / " ++ show total ++ " (" ++ - show ((fromIntegral nAcc / fromIntegral total) :: Float) ++ - ") proposals accepted" - diff --git a/Numeric/MCMC/Metropolis.hs b/Numeric/MCMC/Metropolis.hs @@ -1,95 +1,89 @@ {-# OPTIONS_GHC -Wall #-} -{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ViewPatterns #-} -module Numeric.MCMC.Metropolis ( - MarkovChain(..), Options(..) - , runChain - ) where +module Numeric.MCMC.Metropolis (mcmc, metropolis) where -import Control.Monad.Trans -import Control.Monad.Reader -import Control.Monad.Primitive -import Control.Arrow -import System.Random.MWC -import System.Random.MWC.Distributions -import Data.List -import Statistics.Distribution -import Statistics.Distribution.Normal hiding (standard) -import GHC.Float +import Control.Monad (when) +import Control.Monad.Primitive (PrimMonad, PrimState) +import Control.Monad.Trans.Class (lift) +import Control.Monad.Trans.State.Strict (StateT, execStateT, get, put) +import Data.Vector.Unboxed (Vector) +import qualified Data.Vector.Unboxed as Vector (mapM, fromList) +import GHC.Prim (RealWorld) +import Pipes (Producer, yield, (>->), runEffect) +import qualified Pipes.Prelude as Pipes (mapM_, take) +import System.Random.MWC.Probability (Gen, Prob) +import qualified System.Random.MWC.Probability as MWC --- | State of the Markov chain. Current parameter values are held in 'theta', --- while accepts counts the number of proposals accepted. -data MarkovChain = MarkovChain { theta :: [Double] - , accepts :: {-# UNPACK #-} !Int } +-- | A transition operator. +type Transition m = StateT Chain (Prob m) () --- | Options for the chain. The target (expected to be a log density) and --- a step size tuning parameter. -data Options = Options { _target :: [Double] -> Double - , _eps :: {-# UNPACK #-} !Double } +-- | The @Chain@ type specifies the state of a Markov chain at any given +-- iteration. +data Chain = Chain { + chainTarget :: Vector Double -> Double + , chainScore :: !Double + , chainPosition :: !(Vector Double) + } --- | A result with this type has a view of the chain options. -type ViewsOptions = ReaderT Options +instance Show Chain where + show Chain {..} = filter (`notElem` "fromList []") (show chainPosition) --- | Display the current state. -instance Show MarkovChain where - show config = filter (`notElem` "[]") $ show (map double2Float (theta config)) +-- | Propose a state transition according to a Gaussian proposal distribution +-- with the specified standard deviation. +propose + :: PrimMonad m + => Double + -> Vector Double + -> Prob m (Vector Double) +propose radial = Vector.mapM perturb where + perturb m = MWC.normal m radial --- | Density function for an isotropic Gaussian. The (identity) covariance --- matrix is multiplied by the scalar 'sig'. -isoGauss :: [Double] -> [Double] -> Double -> Double -isoGauss xs mu sig = foldl1' (*) (zipWith density nds xs) - where nds = map (`normalDistr` sig) mu -{-# INLINE isoGauss #-} +-- | A Metropolis transition operator. +metropolis + :: PrimMonad m + => Double + -> Transition m +metropolis radial = do + Chain {..} <- get + proposal <- lift (propose radial chainPosition) + let proposalScore = chainTarget proposal + acceptProb = whenNaN 0 (exp (min 0 (proposalScore - chainScore))) --- | Perturb the state, creating a new proposal. -perturb :: PrimMonad m - => [Double] -- Current state - -> Gen (PrimState m) -- MWC PRNG - -> ViewsOptions m [Double] -- Resulting perturbation. -perturb t g = do - Options _ e <- ask - mapM (\m -> lift $ normal m e g) t -{-# INLINE perturb #-} + accept <- lift (MWC.bernoulli acceptProb) + when accept (put (Chain chainTarget proposalScore proposal)) --- | Perform a Metropolis accept/reject step. -metropolisStep :: PrimMonad m - => MarkovChain -- Current state - -> Gen (PrimState m) -- MWC PRNG - -> ViewsOptions m MarkovChain -- New state -metropolisStep state g = do - Options target e <- ask - let (t0, nacc) = (theta &&& accepts) state - zc <- lift $ uniformR (0, 1) g - proposal <- perturb t0 g - let mc = if zc < acceptProb - then (proposal, 1) - else (t0, 0) - - acceptProb = if isNaN val then 0 else val where val = arRatio - - arRatio = exp . min 0 $ - target proposal + log (isoGauss t0 proposal e) - - target t0 - log (isoGauss proposal t0 e) - - return $! MarkovChain (fst mc) (nacc + snd mc) -{-# INLINE metropolisStep #-} - --- | Diffuse through states. -runChain :: Options -- Options of the Markov chain. - -> Int -- Number of epochs to iterate the chain. - -> Int -- Print every nth iteration - -> MarkovChain -- Initial state of the Markov chain. - -> Gen RealWorld -- MWC PRNG - -> IO MarkovChain -- End state of the Markov chain, wrapped in IO. -runChain = go - where go o n t !c g | n == 0 = return c - | n `rem` t /= 0 = do - r <- runReaderT (metropolisStep c g) o - go o (n - 1) t r g - | otherwise = do - r <- runReaderT (metropolisStep c g) o - print r - go o (n - 1) t r g -{-# INLINE runChain #-} +-- | A Markov chain. +chain + :: PrimMonad m + => Double + -> Chain + -> Gen (PrimState m) + -> Producer Chain m () +chain radial = loop where + loop state prng = do + next <- lift (MWC.sample (execStateT (metropolis radial) state) prng) + yield next + loop next prng +-- | Trace 'n' iterations of a Markov chain. +mcmc + :: Int + -> Double + -> [Double] + -> (Vector Double -> Double) + -> Gen RealWorld + -> IO () +mcmc n radial (Vector.fromList -> chainPosition) chainTarget gen = runEffect $ + chain radial Chain {..} gen + >-> Pipes.take n + >-> Pipes.mapM_ print + where + chainScore = chainTarget chainPosition +-- | Use a provided default value when the argument is NaN. +whenNaN :: RealFloat a => a -> a -> a +whenNaN val x + | isNaN x = val + | otherwise = x diff --git a/README.md b/README.md @@ -1,6 +1,6 @@ -# mighty-metropolis [![Build Status](https://secure.travis-ci.org/jtobin/mighty-metropolis.png)](http://travis-ci.org/jtobin/mighty-metropolis) +# mighty-metropolis [![Build Status](https://secure.travis-ci.org/jtobin/mighty-metropolis.png)](http://travis-ci.org/jtobin/mighty-metropolis) -The classic Metropolis-Hastings sampling algorithm. +The classic Metropolis sampling algorithm. -See the *Examples* folder for example usage. +See the *test* folder for example usage. diff --git a/mighty-metropolis.cabal b/mighty-metropolis.cabal @@ -1,29 +1,42 @@ --- Initial mighty-metropolis.cabal generated by cabal init. For further --- documentation, see http://haskell.org/cabal/users-guide/ - name: mighty-metropolis -version: 0.1.0.0 -synopsis: The classic Metropolis-Hastings sampling algorithm. --- description: +version: 0.2.0.0 +synopsis: The Metropolis algorithm. homepage: http://github.com/jtobin/mighty-metropolis license: BSD3 license-file: LICENSE author: Jared Tobin maintainer: jared@jtobin.ca --- copyright: category: Numeric build-type: Simple -cabal-version: >=1.8 -Description: - - Sampling via Gaussian perturbations. +cabal-version: >= 1.18 +description: + The Metropolis algorithm. Source-repository head Type: git Location: http://github.com/jtobin/mighty-metropolis.git library - exposed-modules: Numeric.MCMC.Metropolis - -- other-modules: - build-depends: base ==4.*, mtl ==2.1.*, primitive ==0.4.*, mwc-random ==0.12.*, statistics ==0.10.* - ghc-options: -Wall + ghc-options: + -Wall + exposed-modules: + Numeric.MCMC.Metropolis + build-depends: + base + , pipes + , primitive + , mwc-probability + , vector + +Test-suite rosenbrock + type: exitcode-stdio-1.0 + hs-source-dirs: test + main-is: Rosenbrock.hs + default-language: Haskell2010 + ghc-options: + -rtsopts -Wall + build-depends: + base + , mwc-probability + , vector + diff --git a/stack.yaml b/stack.yaml @@ -0,0 +1,5 @@ +flags: {} +packages: + - ../mwc-probability +extra-deps: [] +resolver: lts-3.3 diff --git a/test/Rosenbrock.hs b/test/Rosenbrock.hs @@ -0,0 +1,15 @@ + +module Rosenbrock where + +import Numeric.MCMC.Metropolis +import qualified System.Random.MWC.Probability as MWC +import Data.Vector.Unboxed (Vector, unsafeIndex) + +rosenbrock :: Vector Double -> Double +rosenbrock xs = (-1)*(5*(x1 - x0^2)^2 + 0.05*(1 - x0)^2) where + x0 = unsafeIndex xs 0 + x1 = unsafeIndex xs 1 + +main :: IO () +main = MWC.withSystemRandom . MWC.asGenIO $ mcmc 100000 1 [0, 0] rosenbrock +