packages feed

affine-invariant-ensemble-mcmc (empty) → 0.1.0.0

raw patch · 5 files changed

+299/−0 lines, 5 filesdep +basedep +containersdep +mwc-randomsetup-changed

Dependencies added: base, containers, mwc-random, primitive, split, vector

Files

+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (C) 2012 Jared Tobin++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++   1. Redistributions of source code must retain the above copyright+      notice, this list of conditions, and the following disclaimer.++   2. Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions, and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++   3. The name of the author may not be used to endorse or promote+      products derived from this software without specific prior+      written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ Numeric/MCMC/AffineInvariantEnsemble.hs view
@@ -0,0 +1,183 @@+{-# LANGUAGE FlexibleInstances, FlexibleContexts #-}++-- | A Haskell implementation of Goodman & Weare (2010)'s /affine invariant ensemble MCMC/, a family of Markov+--   Chain Monte Carlo methods that can efficiently sample from highly skewed or anisotropic distributions. +--+--   See 'runChain' for an overview of use, and <http://msp.berkeley.edu/camcos/2010/5-1/p04.xhtml> for details +--   of the general sampling routine.+module Numeric.MCMC.AffineInvariantEnsemble ( +           -- * Data structures+             Config(..), AffineTransform(..), Trace+           -- * Chain management+           , runChain, initializeEnsemble, defaultSeed, prune+           ) where++import Numeric.MCMC.Util +import Data.List                        (foldl')+import Data.List.Split                  (splitEvery) +import Data.IntMap.Strict               (IntMap)+import qualified Data.IntMap.Strict  as  IntMap+import qualified Data.Vector.Unboxed as  U+import Data.Vector.Generic              (Vector)+import System.Random.MWC +import System.Random.MWC.Distributions  (standard)+import Control.Monad.ST                 (ST, runST)+import Data.STRef                       (STRef, newSTRef, readSTRef, writeSTRef)+import Control.Monad                    (forM_, replicateM, when)+import Data.Maybe                       (fromJust)+import Control.Monad.Primitive          (PrimMonad)+import Data.Word                        (Word32)++libError :: String+libError = "Numeric.MCMC.AffineInvariantEnsemble."++-- | A data type holding the configuration of the Markov chain at any given epoch.  `ensemble` accesses+--   the IntMap constituting the current ensemble, while 'accepts' records the number of proposals that +--   have been accepted up to the current epoch.+data Config   = Config { ensemble    :: !(IntMap [Double]) +                       , accepts     :: {-# UNPACK #-} !Int+                       } ++-- | A data type representing the affine transformation to be used on particles in an ensemble.  The general-purpose+--   /stretch/ and /walk/ transformations described in Goodman and Weare (2010) are supported.+data AffineTransform a = Stretch | Walk a deriving (Eq, Read)+ +-- | A data type holding a chain's trace.+newtype Trace a = Trace [[a]]++instance Show (Trace Double) where+    show (Trace xs) = unlines $ map (unwords . map show) xs++-- The `stretch` affine transform.  +stretch :: [Double]             -- ^ Focused walker+        -> [Double]             -- ^ Alternate walker+        -> Int                  -- ^ Number of walkers in ensemble+        -> Double               -- ^ Random double drawn from appropriate distribution+        -> ([Double] -> Double) -- ^ Target function+        -> ([Double], Double)   -- ^ Tuple containing proposed move and its log acceptance prob+stretch xk xj nw z target = (proposal, logAP)+    where proposal = zipWith (+) (map (*z) xk) (map (*(1-z)) xj)+          logAP    = let val = target proposal - target xk + (fromIntegral nw - 1) * log z+                     in  if val > 0 then 0 else val++-- The `walk` affine transform.+walk :: (Fractional c, Num t, Ord t) +     => [c]                     -- ^ Focused walker+     -> [[c]]                   -- ^ Sub-ensemble of n alternate walkers+     -> [c]                     -- ^ n random doubles drawn from a standard normal+     -> ([c] -> t)              -- ^ Target function+     -> ([c], t)                -- ^ Tuple containing proposed move and its log acceptance prob+walk xk xjs zs target = let val = target proposal - target xk in (proposal, if val > 0 then 0 else val)+    where nxjs          = length xjs+          xjsmean       = map (/ fromIntegral nxjs) $ listReducer (length xk) xjs+          xjscentd      = zipWith (zipWith (-)) xjs (replicate nxjs xjsmean)+          listReducer n = foldl' (zipWith (+)) (replicate n 0.0)+          proposal      = zipWith (+) xk (listReducer nxjs $ zipWith (\z -> map (*z)) zs xjscentd)++-- | Naively initialize an ensemble.  Creates a 'Config' containing /nw/ walkers, each of dimension /nd/,+--   and initializes 'accepts' at 0.  Each dimensional element is drawn randomly from (0,1] (using a different +--   seed than 'defaultSeed').  +--+--   If this is expected to be a region of low density, you'll probably want to specify+--   your own initial configuration.+initializeEnsemble :: PrimMonad m => Int -> Int -> m Config +initializeEnsemble nw nd+    | nw < 2    = error $ libError ++ "initializeEnsemble: Number of walkers must be >= 2."+    | nd < 1    = error $ libError ++ "initializeEnsemble: Number of dimensions must be >= 1."+    | nw < nd   = error $ libError ++ "initializeEnsemble: Number of walkers should be greater than number of dimensions."+    | otherwise = do+        gen   <- create+        inits <- replicateM (nw * nd) (uniformR (0 :: Double, 1) gen)+        let arr        = IntMap.fromList $ zip [1..] (splitEvery nd inits)+            initConfig = Config {ensemble = arr, accepts = 0}+        return initConfig++-- Move an ensemble forward one step.+moveEnsemble :: Int                          -- Number of walkers in the ensemble.+             -> STRef s Config               -- A STRef storing the ensemble configuration.+             -> Gen s                        -- Random number generator+             -> ([Double] -> Double)         -- Desired target +             -> AffineTransform Int          -- Affine transformation to use+             -> ST s (IntMap [Double]) +moveEnsemble numWalkers _        _   _      (Walk n) +    | n >= numWalkers = error "Numeric.MCMC.AffineInvariantEnsemble moveEnsemble: size of `Walk` sub-ensemble must be strictly less than size of full ensemble."+moveEnsemble numWalkers stConfig gen target xform = do+    forM_ [1..numWalkers] $ \targetWalkerIndex -> do+        -- Inits+        config <- readSTRef stConfig +        let walkers = ensemble config+            nacc    = accepts  config+            targetWalker = fromJust $ IntMap.lookup targetWalkerIndex walkers++        -- Randomness+        zc     <- uniformR (0, 1) gen        +        seed   <- save gen++        -- Generate proposal and acceptance probability+        let (proposal, logAcceptanceProb) = case xform of+                Stretch -> runST $ do+                    g0             <- restore seed+                    altWalkerIndex <- genDiffInt targetWalkerIndex (1, numWalkers) g0+                    z0             <- uniformR (0, 1) g0+                    let z         = 0.5 * (z0 + 1) * (z0 + 1)+                        altWalker = fromJust $ IntMap.lookup altWalkerIndex walkers+                    return $ stretch targetWalker altWalker numWalkers z target++                Walk n -> runST $ do +                    g0 <- restore seed+                    zs <- replicateM n (standard g0)+                    g1 <- save g0+                    let subMapKeys        = sample n (IntMap.keys walkers) g1+                        altWalkerEnsemble = createEnsemble subMapKeys+                            where createEnsemble = map (\k -> fromJust (IntMap.lookup k walkers))   +                    return $ walk targetWalker altWalkerEnsemble zs target++        -- Compare and possible accept proposal+        when (zc <= exp logAcceptanceProb) $ +            writeSTRef stConfig Config {ensemble = IntMap.update (\_ -> Just proposal) targetWalkerIndex walkers, accepts = nacc + 1}++    -- Return end state+    endConfig       <- readSTRef stConfig+    let endPosition =  ensemble endConfig +    return endPosition++-- | Typical use:+--+--   @+--   runChain steps target initConfig seed xform+--   @+--+--   Run the Markov chain for /steps/ epochs.  The chain will wander over /target/'s parameter space such that,+--   after \"long enough\", the points it visits will effectively be independent samples from the distribution +--   proportional to /target/.  The Markov chain procedes by possibly applying an affine transformation to each of +--   the particles contained in 'ensemble' /initConfig/, sequentially.+--+--   This function will return a tuple contanining 1) the 'Config' corresponding to the final epoch of the chain, +--   and 2) the chain's 'Trace'.  The 'Trace' can be used, for example, to approximate integrals of the target function.+--+--   The /target/ must be a function with type @[Double] -> Double@.  Functions using more complicated data structures+--   internally can simply be curried to this type.  +--+--   Examples of use can be found at <http://github.com/jtobin/affine-invariant-ensemble-mcmc/Numeric/MCMC/Examples>.+runChain :: Vector v Word32 => Int -> ([Double] -> Double) -> Config -> v Word32 -> AffineTransform Int -> (Config, Trace Double)+runChain steps target initConfig seed xform +    | steps < 1 = error $ libError ++ "runChain: `steps` must be >= 1."+    | otherwise = runST $ do+        let nw =  IntMap.size (ensemble initConfig)+        gen    <- initialize seed+        config <- newSTRef initConfig++        frames <- replicateM steps (moveEnsemble nw config gen target xform)  +        let trace = Trace (map snd $ concatMap IntMap.toList frames)++        results <- readSTRef config +        return (results, trace)++-- | Prune some initial epochs (i.e. suspected burn-in) from a 'Trace'.+prune :: Int -> Trace Double -> Trace Double+prune n (Trace xs) = Trace (drop n xs)++-- | The default seed provided by the library.  This seed is different from the one used internally in 'initializeEnsemble'.+defaultSeed :: U.Vector Word32+defaultSeed = U.singleton 42 +
+ Numeric/MCMC/Util.hs view
@@ -0,0 +1,59 @@+-- | Various internal utilities.+module Numeric.MCMC.Util ++where++import Control.Monad.Primitive (PrimMonad, PrimState)+import System.Random.MWC +import Data.Function           (fix)+import qualified Data.Sequence as Seq+import Data.Foldable           (toList)+import Control.Monad+import Control.Monad.ST+import Data.STRef++-- | Given Int, bounds, and generator, generate a different Int in the bound.+genDiffInt :: PrimMonad m => Int -> (Int, Int) -> Gen (PrimState m) -> m Int+genDiffInt a bounds gen = fix $ \loopB -> +    do  b <- uniformR bounds gen+        if a == b then loopB else return b++-- | Tail-recursive, list-fused mean function.+mean :: [Double] -> Double +mean = go 0.0 0 +    where go :: Double -> Int -> [Double] -> Double+          go s l []     = s / fromIntegral l+          go s l (x:xs) = go (s + x) (l + 1) xs++-- | Map a function over a pair.+mapPair :: (a -> b) -> (a, a) -> (b, b)+mapPair f (a, b) = (f a, f b) ++-- | Convert a list to a pair.+shortListToPair :: [a] -> (a, a)+shortListToPair [x0, x1]  = (x0, x1)+shortListToPair _         = error "shortListToPair - list must have length 2."++-- | Knuth-shuffle a list.  Uses 'Seq' internally.+shuffle :: [a] -> Seed -> [a]+shuffle xs seed = runST $ do+    xsref <- newSTRef $ Seq.fromList xs+    gen   <- restore seed+    let n = length xs+    forM_ [n-1,n-2..1] $ \i -> do+        j <- uniformR (0, i) gen++        xst <- readSTRef xsref+        let tmp = Seq.index xst j++        modifySTRef xsref $ Seq.update j (Seq.index xst i)+        modifySTRef xsref $ Seq.update i tmp++    result <- readSTRef xsref+    return $ toList result++-- | Sample from a list without replacement.+sample :: Int -> [a] -> Seed -> [a]+sample k xs seed = take k $ shuffle xs seed++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ affine-invariant-ensemble-mcmc.cabal view
@@ -0,0 +1,26 @@+Name:                affine-invariant-ensemble-mcmc+Homepage:            http://github.com/jtobin/affine-invariant-ensemble-mcmc+Version:             0.1.0.0+Cabal-version:       >=1.8+build-type:          Simple+License:             BSD3+License-file:        LICENSE+Author:              Jared Tobin+Maintainer:          jared@jtobin.ca+Category:            Numerical, Machine Learning, Statistics+Synopsis:            General-purpose sampling +Description:++    A general-purpose sampling routine for badly-scaled distributions.    ++Source-repository head+  Type:     git+  Location: http://github.com/jtobin/affine-invariant-ensemble-mcmc.git++Library+  Build-depends:       base ==4.5.*, containers ==0.5.*, vector ==0.9.*, mwc-random ==0.12.*, primitive ==0.4.*, split ==0.1.*++  ghc-options:         -Wall+  Exposed-modules:     Numeric.MCMC.AffineInvariantEnsemble, Numeric.MCMC.Util+  Extensions:          FlexibleContexts, FlexibleInstances+