module Numeric.Probability.Distribution where
import Numeric.Probability.Show (showR)
import qualified Numeric.Probability.Shape as Shape
import Control.Applicative (Applicative(..))
import Control.Monad (liftM, liftM2, join, )
import qualified Data.Foldable as Fold
import qualified Data.List.HT as ListHT
import qualified Data.Map as Map
import qualified Data.List as List
import qualified Data.Maybe as Maybe
import Data.Tuple.HT (mapFst, )
import Data.Ord.HT (comparing, )
import Data.Eq.HT (equating, )
import Prelude hiding (map, filter)
type Event a = a -> Bool
oneOf :: Eq a => [a] -> Event a
oneOf = flip elem
just :: Eq a => a -> Event a
just = (==)
newtype T prob a = Cons {decons :: [(a,prob)]}
certainly :: Num prob => a -> T prob a
certainly x = Cons [(x,1)]
instance Num prob => Monad (T prob) where
return = certainly
d >>= f = Cons [(y,q*p) | (x,p) <- decons d, (y,q) <- decons (f x)]
fail _ = Cons []
instance Num prob => Applicative (T prob) where
pure = certainly
fm <*> m = Cons [(f x,q*p) | (f,p) <- decons fm, (x,q) <- decons m]
instance Functor (T prob) where
fmap f (Cons d) = Cons [(f x,p) | (x,p) <- d]
errorMargin :: RealFloat prob => prob
errorMargin =
let eps = 10 / fromInteger (floatRadix eps) ^ floatDigits eps
in eps
approx :: (RealFloat prob, Ord a) =>
T prob a -> T prob a ->
Bool
approx (Cons xs) (Cons ys) =
let (xse, xsp) = unzip (norm' xs)
(yse, ysp) = unzip (norm' ys)
in xse == yse &&
all (\p -> abs p < errorMargin) (zipWith (-) xsp ysp)
lift :: (Num prob) =>
([(a,prob)] -> [(a,prob)]) ->
T prob a -> T prob a
lift f = Cons . f . decons
size :: T prob a -> Int
size = length . decons
check :: (RealFloat prob, Show prob) => T prob a -> T prob a
check (Cons d) =
if abs (1-sumP d) < errorMargin
then Cons d
else error ("Illegal distribution: total probability = "++show (sumP d))
cons :: (RealFloat prob, Show prob) => [(a,prob)] -> T prob a
cons = check . Cons
sumP :: Num prob => [(a,prob)] -> prob
sumP = sum . List.map snd
sortP :: Ord prob => [(a,prob)] -> [(a,prob)]
sortP = List.sortBy (comparing snd)
sortPDesc :: Ord prob => [(a,prob)] -> [(a,prob)]
sortPDesc = List.sortBy (flip $ comparing snd)
sortElem :: Ord a => [(a,prob)] -> [(a,prob)]
sortElem = List.sortBy (comparing fst)
norm :: (Num prob, Ord a) => T prob a -> T prob a
norm = lift norm'
norm' :: (Num prob, Ord a) => [(a,prob)] -> [(a,prob)]
norm' =
Map.toAscList . Map.fromListWith (+)
norm'' :: (Num prob, Ord a) => [(a,prob)] -> [(a,prob)]
norm'' =
List.map (\xs ->
case xs of
((x,_):_) -> (x, sum (List.map snd xs))
_ -> error "Probability.Distribution.norm'': every sub-list in groupBy must be non-empty") .
ListHT.groupBy (equating fst) . sortElem
pretty :: (Ord a, Show a, Num prob, Ord prob) =>
(prob -> String) -> T prob a -> String
pretty _ (Cons []) = "Impossible"
pretty showProb (Cons xs) =
let w = maximum (List.map (length.show.fst) xs)
in concatMap
(\(x,p) -> showR w x++' ': showProb p++"\n")
(sortPDesc (norm' xs))
infix 0 //%
(//%) :: (Ord a, Show a) => T Rational a -> () -> IO ()
(//%) x () = putStr (pretty show x)
instance (Num prob, Ord prob, Show prob, Ord a, Show a) =>
Show (T prob a) where
showsPrec p (Cons xs) =
showParen (p>10)
(showString "fromFreqs " . showsPrec 11 (sortPDesc (norm' xs)))
instance Eq (T prob a) where
(==) = error "Probability.Distribution.== cannot be implemented sensibly."
equal :: (Num prob, Eq prob, Ord a) => T prob a -> T prob a -> Bool
equal = equating (decons . norm)
instance (Num prob, Ord prob, Ord a, Num a) => Num (T prob a) where
fromInteger = return . fromInteger
x + y = norm (liftM2 (+) x y)
x - y = norm (liftM2 (-) x y)
x * y = norm (liftM2 (*) x y)
abs x = norm (liftM abs x)
signum x = norm (liftM signum x)
negate x = liftM negate x
instance (Num prob, Ord prob, Ord a, Fractional a) =>
Fractional (T prob a) where
fromRational = return . fromRational
recip x = liftM recip x
x / y = norm (liftM2 (/) x y)
type Spread prob a = [a] -> T prob a
choose :: Num prob => prob -> a -> a -> T prob a
choose p x y = Cons $ zip [x,y] [p,1-p]
enum :: Fractional prob => [Int] -> Spread prob a
enum = relative . List.map fromIntegral
relative :: Fractional prob => [prob] -> Spread prob a
relative ns = fromFreqs . flip zip ns
shape :: Fractional prob =>
(prob -> prob) -> Spread prob a
shape _ [] = error "Probability.shape: empty list"
shape f xs =
let incr = 1 / fromIntegral (length xs - 1)
ps = List.map f (iterate (+incr) 0)
in fromFreqs (zip xs ps)
linear :: Fractional prob => Spread prob a
linear = shape Shape.linear
uniform :: Fractional prob => Spread prob a
uniform = shape Shape.uniform
negExp :: Floating prob => Spread prob a
negExp = shape Shape.negExp
normal :: Floating prob => Spread prob a
normal = shape Shape.normal
extract :: T prob a -> [a]
extract = List.map fst . decons
map :: (Num prob, Ord b) =>
(a -> b) -> T prob a -> T prob b
map f = norm . fmap f
unfold :: (Num prob, Ord a) =>
T prob (T prob a) -> T prob a
unfold = norm . join
cond :: (Num prob) =>
T prob Bool ->
T prob a ->
T prob a ->
T prob a
cond b d d' = b >>= \c -> if c then d else d'
truth :: (Num prob) => T prob Bool -> prob
truth dist =
case decons (norm dist) of
(b,p):_ -> if b then p else 1-p
[] -> error "Probability.truth: corrupt boolean random variable"
infixl 1 >>=?
infixr 1 ?=<<
(?=<<) :: (Fractional prob) =>
(a -> Bool) -> T prob a -> T prob a
(?=<<) = filter
(>>=?) :: (Fractional prob) =>
T prob a -> (a -> Bool) -> T prob a
(>>=?) = flip filter
data Select a = Case a | Other
deriving (Eq,Ord,Show)
above, below :: (Num prob, Ord prob, Ord a) =>
prob -> T prob a -> T prob (Select a)
above p = select (>=p)
below p = select (<=p)
select :: (Num prob, Ord prob, Ord a) =>
(prob -> Bool) -> T prob a -> T prob (Select a)
select condp (Cons d) =
let (d1,d2) = Map.partition condp $ Map.fromListWith (+) d
in Cons $
(Other, Fold.sum d2) :
List.map (mapFst Case) (Map.toAscList d1)
fromFreqs :: (Fractional prob) => [(a,prob)] -> T prob a
fromFreqs xs = Cons (List.map (\(x,p)->(x,p/q)) xs)
where q = sumP xs
filter :: (Fractional prob) =>
(a -> Bool) -> T prob a -> T prob a
filter p = fromFreqs . List.filter (p . fst) . decons
mapMaybe :: (Fractional prob) =>
(a -> Maybe b) -> T prob a -> T prob b
mapMaybe f =
fromFreqs . Maybe.mapMaybe (\(x,p) -> fmap (flip (,) p) $ f x) . decons
selectP :: (Num prob, Ord prob) => T prob a -> prob -> a
selectP (Cons d) p = scanP p d
scanP :: (Num prob, Ord prob) => prob -> [(a,prob)] -> a
scanP p ((x,q):ps) =
if p<=q || null ps
then x
else scanP (p-q) ps
scanP _ [] = error "Probability.scanP: distribution must be non-empty"
infixr 1 ??
(??) :: Num prob => Event a -> T prob a -> prob
(??) p = sumP . List.filter (p . fst) . decons
expected :: (Num a) => T a a -> a
expected = sum . List.map (\(x,p) -> x * p) . decons
variance :: (Num a) => T a a -> a
variance x =
expected (fmap ((^(2::Int)) . subtract (expected x)) x)
stdDev :: (Floating a) => T a a -> a
stdDev = sqrt . variance