packages feed

lazyppl-1.1.0: test/Chain.hs

{-# LANGUAGE ScopedTypeVariables #-}
module Main where

import Control.DeepSeq (force)
import Control.Exception (SomeException, evaluate, try)
import Control.Monad (liftM, unless, forM, forM_)
import Control.Monad.State.Lazy (State, get, put, runState)
import Control.Monad.Trans.Writer (runWriterT)
import Data.Monoid (Product(..))
import GHC.Float (castDoubleToWord64)
import LazyPPL
import LazyPPL.Internal.Sampler (randomLabel)
import Numeric.Log (Log, ln)
import System.Random hiding (uniform)
import Test.QuickCheck hiding (sample)
import Test.QuickCheck.Random (mkQCGen)

-- The tree-keeping transition and lazy State traversal are from
-- 8168aff:src/LazyPPL.hs. They do not use the replay history or checkpoints.
reference :: forall a. Double -> Maybe Double -> Meas a -> IO [(a, Product (Log Double))]
reference p refresh (Meas m) = do
  g <- newStdGen
  let (g1,g2) = split g
      t = randomTree g1
      (x,w) = runProb (runWriterT m) t
      (samples,_) = runState (iterateM step (t,x,w)) g2
  pure (map (\(_,v,weight) -> (v,weight)) samples)
  where
    step :: (Tree,a,Product (Log Double)) -> State StdGen (Tree,a,Product (Log Double))
    step (t,x,w) = do
      g <- get
      let (g1,g2) = split g
          t' = case refresh of
            Nothing -> mutate p g1 t
            Just q -> let (r,g1') = random g1
                      in if r < q then randomTree g1' else mutate p g1' t
          (x',w') = runProb (runWriterT m) t'
          ratio = getProduct w' / getProduct w
          (r,g2') = random g2
      put g2'
      if r < min 1 (exp (ln ratio)) then pure (t',x',w') else pure (t,x,w)
    iterateM f x = do
      x' <- f x
      (x':) `liftM` iterateM f x'

mutate :: Double -> StdGen -> Tree -> Tree
mutate p g (Tree a ts) =
  let (a',g') = randomLabel g
      (a'',g'') = randomLabel g'
      children seed (t:rest) = let (g1,g2) = split seed
                              in mutate p g1 t : children g2 rest
      children _ [] = []
  in Tree (if a' < p then a'' else a) (children g'' ts)

at :: [Int] -> Tree -> Double
at [] (Tree x _) = x
at (i:is) (Tree _ ts) = at is (ts !! i)

model :: Int -> Meas ([Int] -> Double)
model kind = do
  tree <- sample (Prob id)
  let root = at [] tree
      branch = if root < 0.5 then [0,2] else [2,0]
  case kind of
    0 -> pure ()
    1 -> score (if root < 0.3 then 1 else 1e-12)
    _ -> score (0.01 + at branch tree)
  pure (\path -> if path == [99] then error "delayed payload" else at path tree)

chainProperty :: Property
chainProperty = forAll arbitrary $ \seed ->
  forAll (elements [0,0.001,0.2,1]) $ \rate ->
  forAll (elements [Nothing,Just 0.05,Just 1]) $ \refresh ->
  forAll (chooseInt (0,2)) $ \kind ->
  forAll (elements [33,97,257,4101]) $ \count -> ioProperty $ do
    let measure = model kind
        sampler = maybe (mh rate) (mhirreducible rate) refresh
        paths = [[],[0],[1,0],[0,2],[2,0],[5,3,1]]
        observe (f,w) = map (castDoubleToWord64 . f) paths ++ [castDoubleToWord64 (ln (getProduct w))]
    setStdGen (mkStdGen seed)
    actual <- sampler measure
    setStdGen (mkStdGen seed)
    expected <- reference rate refresh measure
    -- Advance before demanding the old payloads, including across seed-store growth.
    _ <- evaluate (snd (actual !! (count-1)))
    _ <- evaluate (snd (expected !! (count-1)))
    let indices = [0 .. min 256 (count-1)] ++ [count-1]
    observed <- evaluate (force (map (observe . (actual !!)) indices))
    oracle <- evaluate (force (map (observe . (expected !!)) indices))
    failures <- forM [actual,expected] $ \xs -> do
      outcome <- try (evaluate (fst (head xs) [99])) :: IO (Either SomeException Double)
      pure (either (const True) (const False) outcome)
    pure $ counterexample (show (seed,rate,refresh,kind,count)) $
      observed === oracle .&&. failures === [True,True]

main :: IO ()
main = do
  forM_ [Nothing, Just 0.05, Just 1] $ \refresh -> do
    let sampler = maybe (mh 0.2) (mhirreducible 0.2) refresh
    forM_ [sampler, reference 0.2 refresh] $ \run -> do
      xs <- run (pure (error "undemanded payload" :: Bool))
      count <- evaluate (length (take 128 xs))
      unless (count == 128) (fail "Sampler forced an undemanded payload")
      bad <- run (score (error "demanded score") >> pure True)
      outcome <- try (evaluate (head bad)) :: IO (Either SomeException (Bool,Product (Log Double)))
      unless (either (const True) (const False) outcome) (fail "Sampler hid a score exception")
  result <- quickCheckWithResult stdArgs {maxSuccess=200, replay=Just (mkQCGen 20261026,0)} chainProperty
  unless (isSuccess result) (fail "Seeded chains differ from the tree-keeping sampler")