probability-polynomial-1.0.0.0: test/Numeric/Measure/ProbabilitySpec.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-|
Copyright : Predictable Network Solutions Ltd., 2020-2024
License : BSD-3-Clause
-}
module Numeric.Measure.ProbabilitySpec
( spec
) where
import Prelude
import Data.Function.Class
( eval
)
import Data.Ratio
( (%)
)
import Numeric.Polynomial.SimpleSpec
( genPositivePoly
)
import Numeric.Measure.Probability
( Prob
, choice
, convolve
, dirac
, distribution
, expectation
, fromDistribution
, fromMeasure
, unsafeFromMeasure
, measure
, moments
, support
, translate
, uniform
)
import Numeric.Probability.Moments
( Moments (..)
)
import Test.Hspec
( Spec
, describe
, it
)
import Test.QuickCheck
( Arbitrary
, Gen
, NonNegative (..)
, Positive (..)
, (===)
, (==>)
, arbitrary
, choose
, chooseInteger
, frequency
, getSize
, mapSize
, oneof
, property
, scale
, vectorOf
)
import qualified Numeric.Measure.Finite.Mixed as M
import qualified Numeric.Polynomial.Simple as Poly
{-----------------------------------------------------------------------------
Tests
------------------------------------------------------------------------------}
spec :: Spec
spec = do
describe "uniform" $ do
it "support" $ property $
\(x :: Rational) y ->
support (uniform x y) === Just (min x y, max x y)
it "distribution at midpoint" $ property $
\(x :: Rational) (y :: Rational) ->
x /= y ==>
eval (distribution (uniform x y)) ((x + y) / 2) === 1/2
describe "instance Eq" $ do
it "dirac x /= dirac y" $ property $
\(x :: Rational) (y :: Rational) ->
x /= y ==> dirac x /= dirac y
describe "elimination . introduction" $ do
it "unsafe fromMeasure . measure" $ property $
\(m :: Prob Rational) ->
m === (unsafeFromMeasure . measure) m
it "fromMeasure . measure" $ property $
\(m :: Prob Rational) ->
Just m === (fromMeasure . measure) m
it "unsafe fromDistribution . distribution" $ property $
\(m :: Prob Rational) ->
Just m ===
(fmap unsafeFromMeasure . M.fromDistribution . distribution) m
it "fromDistribution . distribution" $ property $
\(m :: Prob Rational) ->
Just m ===
(fromDistribution . distribution) m
describe "expectation" $ do
it "unit" $ property $
\(m :: Prob Rational) ->
expectation (Poly.constant 1) m
=== 1
it "positivity" $ mapSize (`div` 2) $ property $
\(m :: Prob Rational) (PositivePoly p) ->
expectation p m
>= 0
describe "moments" $ do
it "mean is additive" $ mapSize (`div` 10) $ property $
\(mx :: Prob Rational) my ->
let mean' = mean . moments
in mean' (convolve mx my)
=== mean' mx + mean' my
it "variance is additive" $ mapSize (`div` 10) $ property $
\(mx :: Prob Rational) my ->
let variance' = variance . moments
in variance' (convolve mx my)
=== variance' mx + variance' my
it "skewness absorbs translate" $ property $
\(m :: Prob Rational) y ->
let skewness' = skewness . moments
in skewness' (translate y m)
=== skewness' m
it "kurtosis absorbs translate" $ property $
\(m :: Prob Rational) y ->
let kurtosis' = kurtosis . moments
in kurtosis' (translate y m)
=== kurtosis' m
it "kurtosis bounded below" $ property $
\(m :: Prob Rational) ->
let ms = moments m
in kurtosis ms
>= (skewness ms)^(2 :: Int) + 1
describe "choice" $ do
it "distribution" $ property $
\(Probability p) (mx :: Prob Rational) my z ->
eval (distribution (choice p mx my)) z
=== p * eval (distribution mx) z
+ (1-p) * eval (distribution my) z
describe "translate" $ do
it "distribution" $ property $
\(m :: Prob Rational) y x ->
eval (distribution (translate y m)) x
=== eval (distribution m) (x - y)
describe "convolve" $ do
it "dirac dirac" $ property $
\(x :: Rational) y ->
convolve (dirac x) (dirac y)
=== dirac (x + y)
it "dirac translate, left" $ property $ mapSize (`div` 10) $
\(mx :: Prob Rational) (y :: Rational) ->
convolve mx (dirac y)
=== translate y mx
it "dirac translate, right" $ property $ mapSize (`div` 10) $
\(x :: Rational) (my :: Prob Rational) ->
convolve (dirac x) my
=== translate x my
it "symmetric" $ property $ mapSize (`div` 10) $
\mx (my :: Prob Rational) ->
convolve mx my
=== convolve my mx
it "translate, left" $ property $ mapSize (`div` 10) $
\mx (my :: Prob Rational) (Positive z) ->
translate z (convolve mx my)
=== convolve (translate z mx) my
{-----------------------------------------------------------------------------
Random generators
------------------------------------------------------------------------------}
newtype PositivePoly = PositivePoly (Poly.Poly Rational)
deriving (Eq, Show)
instance Arbitrary PositivePoly where
arbitrary = PositivePoly <$> genPositivePoly
newtype Probability = Probability Rational
deriving (Eq, Show)
instance Arbitrary Probability where
arbitrary = Probability <$> genProbability
instance Arbitrary (Prob Rational) where
arbitrary = scale (`div` 15) genProb
-- | Generate a random 'Prob' by generating a random expression.
genProb :: Gen (Prob Rational)
genProb = do
size <- getSize
genProbFromList =<< vectorOf size genSimpleProb
-- | Generate a 'uniform'.
genUniform :: Gen (Prob Rational)
genUniform = do
NonNegative a <- arbitrary
Positive d <- arbitrary
pure $ uniform a (a + d)
-- | Generate a 'dirac'.
genDirac :: Gen (Prob Rational)
genDirac = do
NonNegative a <- arbitrary
pure $ dirac a
-- | Generate a simple probability measure — one of 'uniform', 'dirac'.
genSimpleProb :: Gen (Prob Rational)
genSimpleProb =
frequency [(20, genUniform), (4, genDirac)]
-- | Generate a random probability in the interval (0,1).
genProbability :: Gen Rational
genProbability = do
denominator <- chooseInteger (1,2^(20 :: Int))
numerator <- chooseInteger (0, denominator)
pure (numerator % denominator)
-- | Generate a random 'Prob' by combining a given list
-- of 'Prob' with random operations.
genProbFromList :: [Prob Rational] -> Gen (Prob Rational)
genProbFromList [] = pure $ dirac 0
genProbFromList [x] = pure x
genProbFromList xs = do
n <- choose (1, length xs - 1)
let (ys, zs) = splitAt n xs
genOp <*> genProbFromList ys <*> genProbFromList zs
where
genChoice = do
p <- genProbability
pure $ choice p
genOp = oneof [pure convolve, genChoice]