|
-- | A sort of slapdash but obviously correct implementation of |
|
-- compositional discrete probability calculations. This module is |
|
-- broadly unconcerned with efficiency. Use a real library for large |
|
-- probability calculations. |
|
module Probability |
|
( Prob |
|
, fromProb |
|
, uniformP |
|
, canonicalizeP |
|
) where |
|
|
|
import Control.Monad (ap) |
|
import Control.Applicative (Alternative(..)) |
|
import Data.Bifunctor (bimap) |
|
import Data.Ratio (numerator, denominator) |
|
import GHC.Conc (pseq) |
|
|
|
import qualified Data.List.NonEmpty as NE |
|
|
|
|
|
-- | Represents a rational-weighted set of discrete outcomes that can |
|
-- arise from a probabilistic process. The set of outcomes may be |
|
-- empty, representing an impossible situation. |
|
-- |
|
-- This module exports only tools that enforce that the sum of the |
|
-- weights is 0 (if there are no possible outcomes) or 1. |
|
newtype Prob a = Prob { fromProb :: [(Rational, a)] } |
|
|
|
instance Show a => Show (Prob a) where |
|
show (Prob []) = "Contradiction" |
|
show (Prob (x:xs)) = (++) "Distribution [" . pair x . remaining xs $ "]" |
|
where |
|
pair (w, o) = (:) '(' . shows o . (++) ": " . weight w . (:) ')' |
|
weight w = shows (numerator w) . (:) '/' . shows (denominator w) |
|
remaining = foldr (\p r -> (:) ',' . pair p . r) id |
|
|
|
instance Functor Prob where |
|
fmap f (Prob xs) = Prob $ map (fmap f) xs |
|
|
|
instance Applicative Prob where |
|
pure x = Prob [(1, x)] |
|
(<*>) = ap |
|
|
|
-- | Models non-deterministic exploration of a weighted decision tree. |
|
instance Monad Prob where |
|
Prob xs >>= f = Prob $ norm outcomes |
|
where |
|
outcomes = [ (wx * wy, y) | (wx, x) <- xs, (wy, y) <- fromProb $ f x ] |
|
|
|
instance MonadFail Prob where |
|
fail _ = empty |
|
|
|
-- | This instance provides catch-like semantics. (<|>) is |
|
-- left-biased, returning the left argument unless it's the empty |
|
-- outcome set. In that case, the right argument is returned. |
|
instance Alternative Prob where |
|
empty = Prob [] |
|
Prob [] <|> p = p |
|
p <|> _ = p |
|
|
|
|
|
-- | Uniform probability of each element from a list |
|
-- |
|
-- Produces a contradiction if the input was empty |
|
uniformP :: [a] -> Prob a |
|
uniformP xs = Prob $ norm [ (1, x) | x <- xs ] |
|
|
|
|
|
-- Normalize weights. |
|
-- |
|
-- Precondition: All input weights are positive. |
|
-- Postconditions: |
|
-- |
|
-- the output list contains the same sequence of `a' values as the |
|
-- input |
|
-- |
|
-- the output list weights sum to 1 if the output is non-empty |
|
-- |
|
-- Space invariants: |
|
-- |
|
-- only evaluating an `a' value in the output list causes evaluation |
|
-- of the corresponding value in the input list |
|
-- |
|
-- evaluating the outermost output list constructor causes |
|
-- evaluation of the spine of the input list, all weights in the |
|
-- input list, the spine of the output list, and the weights in the |
|
-- output list |
|
norm :: [(Rational, a)] -> [(Rational, a)] |
|
norm = snd . go 0 |
|
where |
|
go acc [] = (acc, []) |
|
go acc ((weight, x) : xs) = w `seq` (total, (w, x) : updated) |
|
where |
|
(total, updated) = a `pseq` go a xs |
|
w = weight / total |
|
a = acc + weight |
|
|
|
|
|
-- | Sort the internal storage and coalesce multiple entries for equal |
|
-- outcomes |
|
canonicalizeP :: Ord a => Prob a -> Prob a |
|
canonicalizeP xs = Prob $ map (bimap sum NE.head . NE.unzip) grouped |
|
where |
|
grouped = NE.groupAllWith snd $ fromProb xs |