lazyppl-singlesite-0.1.0: src/LazyPPL/SingleSite/Internal.hs
{-# LANGUAGE MagicHash, UnboxedTuples #-}
{-# LANGUAGE ScopedTypeVariables, CPP
#-}
module LazyPPL.SingleSite.Internal where
import LazyPPL
import LazyPPL.Internal.Sampler (randomLabel, checkedLogWeight)
import Control.Monad.Trans.Writer
import Data.Monoid
import Numeric.Log
import qualified Data.Map as M
import System.Random hiding (uniform)
import Control.DeepSeq
import Control.Exception (evaluate)
import System.IO.Unsafe
#ifdef LAZYPPL_BROWSER
import Foreign.Marshal.Alloc (alloca)
import Foreign.Marshal.Array (pokeArray)
import Foreign.Ptr (castPtr)
import Foreign.Storable (peek, sizeOf)
#else
import Unsafe.Coerce
#endif
import Data.IORef
import GHC.Exts (Ptr(..), unpackClosure#, indexArray#, sizeofArray#, Int(I#))
import GHC.Exts.Heap.Utils (dataConNames)
import GHC.Exts.Heap
{-- | Single-site Metropolis-Hastings --}
-- | A partial tree, used for examining finite evaluated subtrees of the working infinite
-- | random tree.
data PTree = PTree (Maybe Double) [Maybe PTree] deriving Show
type Site = [Int]
type Subst = M.Map Site Double
getSites :: PTree -> [Site]
getSites p = getSites' p []
getSites' :: PTree -> Site -> [Site]
getSites' (PTree (Just v) ts) site = site : concat [ getSites' t (n:site) | (n, Just t) <- zip [0..] ts ]
getSites' (PTree Nothing ts) site = concat [ getSites' t (n:site) | (n, Just t) <- zip [0..] ts ]
mutateNode :: Tree -> Site -> Double -> Tree
mutateNode (Tree _ ts) [] d = Tree d ts
mutateNode (Tree v ts) (n:ns) d = Tree v $ take n ts ++ mutateNode (ts!!n) ns d : drop (n+1) ts
mutateNodes :: Tree -> Subst -> Tree
mutateNodes = M.foldrWithKey (\k d t -> mutateNode t k d)
randomElement :: RandomGen g => g -> [a] -> (g, a)
randomElement g xs = if null xs then error "0d sample space" else (g', xs !! n)
where (n, g') = uniformR (0, length xs - 1) g
-- ^^^^^^^^
-- Depending on the version of `random` this can be either uniformR or randomR.
mh1 :: forall a. NFData a => Meas a -> IO [(a, Product (Log Double))]
mh1 (Meas m) = do
g <- newStdGen
let (gTree,g') = split g
tree = randomTree gTree
(x0, w0) = checkedSample (runProb (runWriterT m) tree)
p = unsafePerformIO $ do { evaluate (rnf x0); evaluate (rnf w0) ; trunc tree }
samples = map (\(_,_,_,_,s) -> s) $ iterate (mhStep (Meas m)) (gTree, g', M.empty, p, (x0, w0))
return samples
-- | One transition of 'mh1'. The state holds the seed of the initial tree, the
-- proposal seed, the accepted substitutions, the evaluated sites, and the
-- current sample with its weight.
mhStep :: forall a g. (NFData a, RandomGen g) => Meas a -> (g, g, Subst, PTree, (a, Product (Log Double))) -> (g, g, Subst, PTree, (a, Product (Log Double)))
mhStep (Meas m) (treeSeed, seed, sub, ptree, (x,w)) =
let (seed1, seed2) = split seed
sites = getSites ptree
-- A measure that reads no random site has a one-point sample space:
-- there is no site to mutate, so the chain keeps its sample.
in if null sites
then (treeSeed, seed2, sub, ptree, (x,w))
else
let (seed1', randSite) = (\(x,y) -> (x, reverse y)) $ randomElement seed1 sites
(newNode :: Double, seed1'') = randomLabel seed1'
(u :: Double, _) = randomLabel seed1''
sub' = M.insert randSite newNode sub
t' = mutateNodes (randomTree treeSeed) sub'
(x',w') = checkedSample (runProb (runWriterT m) t')
ptree' = unsafePerformIO $ do { evaluate (rnf x'); evaluate (rnf w'); trunc t' }
sites' = getSites ptree'
alpha = fromIntegral (length sites) / fromIntegral (length sites')
* exp (ln (getProduct w' / getProduct w))
-- The reverse proposal cannot select a site absent from the proposed tree.
-- A move is accepted with probability min 1 alpha, so u < alpha; with
-- u <= alpha, u = 0 would accept a move to weight 0. u is a label, in
-- (0,1): random's 1 would reject a move with alpha = 1.
in if reverse randSite `elem` sites' && (getProduct w == 0 || u < alpha)
then (treeSeed, seed2, sub', ptree', (x',w'))
else (treeSeed, seed2, sub, ptree, (x,w))
-- A product of valid weights can still overflow to a logarithm of +Infinity.
checkedSample :: (a, Product (Log Double)) -> (a, Product (Log Double))
checkedSample (v, Product l) = (v, Product (checkedLogWeight "mh1" l))
-- Functions for truncating a tree.
getGCClosureData inspect b = do
c <- inspect b
case c of
BlackholeClosure _ n -> getGCClosureData inspect n
_ -> return c
helperT :: (Box -> IO Closure) -> Box -> IO PTree
helperT inspect b = do
c <- getGCClosureData inspect b
case c of
ConstrClosure _ [n,l] [] _ _ "Tree" ->
do n' <- getGCClosureData inspect n
l' <- getGCClosureData inspect l
let missing :: IO x
missing = return $ error $ "Missing case:\n" ++ show n' ++ "\n" ++ show l'
-- The label is a site once it is evaluated, and the children were
-- demanded once the list is evaluated; each field is read on its own,
-- so a node with both fields unevaluated has no site.
label <- case n' of
#ifdef LAZYPPL_BROWSER
ConstrClosure {dataArgs = ds, name = "D#"} -> Just <$> doubleFromWords ds
#else
ConstrClosure {dataArgs = [d], name = "D#"} -> return (Just (unsafeCoerce d))
#endif
ThunkClosure {} -> return Nothing
SelectorClosure {} -> return Nothing
_ -> missing
children <- case l' of
ConstrClosure {name = ":"} -> helperB inspect l
ThunkClosure {} -> return []
_ -> missing
return $ PTree label children
ThunkClosure {} -> return $ PTree Nothing []
#ifdef LAZYPPL_BROWSER
-- A Double occupies one 64-bit word or two 32-bit words, in host byte order.
doubleFromWords :: [Word] -> IO Double
doubleFromWords ws
| length ws * sizeOf (0 :: Word) /= sizeOf (0 :: Double) =
fail "Unexpected Double heap payload size"
| otherwise = alloca $ \ptr -> do
pokeArray (castPtr ptr) ws
peek ptr
#endif
helperB :: (Box -> IO Closure) -> Box -> IO [Maybe PTree]
helperB inspect b = do
c <- getGCClosureData inspect b
case c of
ConstrClosure _ [n,l] [] _ _ ":" ->
do n' <- getGCClosureData inspect n
l' <- getGCClosureData inspect l
case (n',l') of
(ConstrClosure {name = "Tree"}, ConstrClosure {name = ":"}) ->
do n'' <- helperT inspect n
l'' <- helperB inspect l
return $ Just n'' : l''
(ConstrClosure {name = "Tree"}, ThunkClosure {}) ->
do n'' <- helperT inspect n
return [Just n'']
(ThunkClosure {}, ConstrClosure {name = ":"}) ->
do l'' <- helperB inspect l
return $ Nothing : l''
(ThunkClosure {}, ThunkClosure {}) ->
return [] -- alternatively, Nothing : []
_ -> return $ error $ "Missing case:\n" ++ show n' ++ "\n" ++ show l'
ThunkClosure {} -> return []
trunc :: Tree -> IO PTree
trunc t = do
cache <- newIORef M.empty
helperT (getCachedClosureData cache) (asBox t)
-- Constructor descriptions are immutable during this traversal.
getCachedClosureData :: IORef (M.Map (Ptr StgInfoTable) (String, String, String)) -> Box -> IO Closure
getCachedClosureData cache (Box x) =
case unpackClosure# x of
(# infoAddress, heapRep, pointers #) -> do
let infoPointer = Ptr infoAddress
boxes = [case indexArray# pointers i of { (# p #) -> Box p }
| I# i <- [0 .. I# (sizeofArray# pointers) - 1]]
names = do
known <- readIORef cache
case M.lookup infoPointer known of
Just result -> return result
Nothing -> do
result <- dataConNames infoPointer
writeIORef cache (M.insert infoPointer result known)
return result
table <- peekItbl infoPointer
case tipe table of
TSO -> return (UnsupportedClosure table)
STACK -> return (UnsupportedClosure table)
_ -> getClosureDataFromHeapRepPrim names (const (return Nothing)) table heapRep boxes