{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE RecordWildCards #-}
import qualified Control.Foldl as L
import Data.Functor.Identity
import Data.Maybe (mapMaybe)
import Data.Sampling.Types
import Numeric.MCMC.Metropolis (chain, chain')
import System.Random.MWC
import Test.Hspec
withinPercent :: Double -> Double -> Double -> Bool
withinPercent b n a
| b == 0 = a == 0
| otherwise = d / b < n / 100
where
d = abs (a - b)
mean :: [Double] -> Double
mean = L.fold L.mean
variance :: [Double] -> Double
variance xs = L.fold alg xs where
alg = (/) <$> L.premap csq L.sum <*> L.genericLength - 1
csq = (** 2.0) . subtract m
m = mean xs
stdDev :: [Double] -> Double
stdDev = sqrt . variance
stdErr :: [Double] -> Double
stdErr xs = stdDev xs / sqrt n where
n = fromIntegral (length xs)
thin :: Int -> [a] -> [a]
thin n xs = case xs of
(h:t) -> h : thin n (drop (pred n) t)
_ -> mempty
data Params = Params {
pepochs :: Int
, pradial :: Double
, porigin :: Identity Double
, ptunable :: Maybe (Identity Double -> Double)
, pltarget :: Identity Double -> Double
, pthin :: Int
}
testParams :: Params
testParams = Params {
pepochs = 1000000
, pradial = 0.2
, porigin = Identity 1.0
, ptunable = Just (\(Identity x) -> x ** 3.0)
, pltarget = \(Identity x) -> if x > 0 then negate x else negate 1 / 0
, pthin = 1000
}
vanillaTrace :: IO [Double]
vanillaTrace = do
let Params {..} = testParams
boxed <- withSystemRandom . asGenIO $
chain pepochs pradial porigin pltarget
let positions = fmap (runIdentity . chainPosition) boxed
pure (thin pthin positions)
tunedTrace :: IO [Double]
tunedTrace = do
let Params {..} = testParams
boxed <- withSystemRandom . asGenIO $
chain' pepochs pradial porigin pltarget ptunable
let positions = mapMaybe chainTunables boxed
pure (thin pthin positions)
testWithinPercent :: SpecWith ()
testWithinPercent = describe "withinPercent" $
it "works as expected" $ do
106 `shouldNotSatisfy` withinPercent 100 5
105 `shouldNotSatisfy` withinPercent 100 5
104 `shouldSatisfy` withinPercent 100 5
96 `shouldSatisfy` withinPercent 100 5
95 `shouldNotSatisfy` withinPercent 100 5
94 `shouldNotSatisfy` withinPercent 100 5
testMean :: SpecWith ()
testMean = describe "mean" $
it "works as expected" $ do
mean [1, 2, 3] `shouldSatisfy` withinPercent 2 1e-3
mean [1..100] `shouldSatisfy` withinPercent 50.5 1e-3
mean [1..1000000] `shouldSatisfy` withinPercent 500000.5 1e-3
testVariance :: SpecWith ()
testVariance = describe "variance" $
it "works as expected" $ do
variance [0, 1] `shouldSatisfy` withinPercent 0.5 1e-3
variance [1, 1, 1] `shouldSatisfy` withinPercent 0 1e-3
variance [1..100] `shouldSatisfy` withinPercent 841.66666666 1e-3
testStdErr :: SpecWith ()
testStdErr = describe "stdErr" $
it "works as expected" $ do
stdErr [1..100] `shouldSatisfy` withinPercent 2.901149 1e-3
stdErr [1..1000] `shouldSatisfy` withinPercent 9.133273 1e-3
testHelperFunctions :: SpecWith ()
testHelperFunctions = describe "helper functions" $ do
testWithinPercent
testMean
testVariance
testStdErr
testSamples :: [Double] -> SpecWith ()
testSamples xs = describe "sampled trace over exp(1)" $ do
let meanStdErr = stdErr xs
varStdErr = stdErr (fmap (\x -> pred x ** 2.0) xs)
context "within three standard errors" $ do
it "has the expected mean" $ do
mean xs `shouldSatisfy` (< 1 + 3 * meanStdErr)
mean xs `shouldSatisfy` (> 1 - 3 * meanStdErr)
it "has the expected variance" $ do
variance xs `shouldSatisfy` (< 1 + 3 * varStdErr)
variance xs `shouldSatisfy` (> 1 - 3 * varStdErr)
testTunables :: [Double] -> SpecWith ()
testTunables ts = describe "sampled tunables over exp(1)" $ do
let meanStdErr = stdErr ts
context "within three standard errors" $
it "has the expected third moment" $ do
mean ts `shouldSatisfy` (< 6 + 3 * meanStdErr)
mean ts `shouldSatisfy` (> 6 - 3 * meanStdErr)
main :: IO ()
main = do
xs <- vanillaTrace
ts <- tunedTrace
hspec $ do
testHelperFunctions
testSamples xs
testTunables ts