-- | Probability-related types. -- -- TODO instances for serialization and further stuff -- TODO vector instances module Statistics.Probability where import Control.Lens import Numeric.Log import Data.Vector.Unboxed.Deriving import Data.Vector.Unboxed (Unbox) import Algebra.Structure.SemiRing import Numeric.LogDomain import Numeric.Limits data IsNormalized = Normalized | NotNormalized -- * Probability in linear space -- | @Prob@ wraps a @Double@ that encodes probabilities. If @Prob@ is tagged as -- @Normalized@, the contained values are in the range @[0,...,1]@, otherwise -- they are in the range @[0,...,∞]@. newtype Prob (n ∷ IsNormalized) x = Prob { getProb ∷ x } deriving (Eq,Ord,Show,Read) derivingUnbox "Prob" [t| forall n x. Unbox x ⇒ Prob n x → x |] [| getProb |] [| Prob |] deriving instance (Enum x) ⇒ Enum (Prob n x) deriving instance (Num x) ⇒ Num (Prob n x) deriving instance (Fractional x) ⇒ Fractional (Prob n x) deriving instance (Floating x) ⇒ Floating (Prob n x) deriving instance (Real x) ⇒ Real (Prob n x) deriving instance (RealFrac x) ⇒ RealFrac (Prob n x) deriving instance (RealFloat x) ⇒ RealFloat (Prob n x) instance (Num r) ⇒ SemiRing (Prob n r) where srplus = (+) srmul = (*) srzero = 0 srone = 1 -- | Turns a value into a normalized probability. @error@ if the value is not -- in the range @[0,...,1]@. prob ∷ (Ord x, Num x, Show x) ⇒ x → Prob Normalized x prob x | x >= 0 && x <= 1 = Prob x | otherwise = error $ show x ++ " not in range of [0,...,1]" {-# Inline prob #-} -- | Simple wrapper around @Prob@ that fixes non-normalization. prob' ∷ (Ord x, Num x, Show x) ⇒ x → Prob NotNormalized x prob' = Prob {-# Inline prob' #-} -- * Probability in log space. A number of operations internally cast to @Log@ -- from @log-domain@, but the values themselves are *not* newtype-wrapped @Log -- x@ values. This is because we want to be *explicit* that these are -- log-probabilities. -- -- @Log@ numbers in cases like @fromIntegral 1 :: Log Double@ are treated as -- not being in the log-domain, hence @fromIntegral performs a @log@ -- operations. newtype LogProb (n ∷ IsNormalized) x = LogProb { getLogProb ∷ x } deriving (Eq,Ord,Show) derivingUnbox "LogProb" [t| forall n x. Unbox x ⇒ LogProb n x → x |] [| getLogProb |] [| LogProb |] instance (Precise x, RealFloat x) ⇒ Num (LogProb n x) where (+) = withLog2 (+) (*) = withLog2 (*) abs = withLog1 abs signum = withLog1 signum fromInteger = LogProb . fromInteger negate = withLog1 negate (-) = withLog2 (-) instance (Num d, Fractional d) ⇒ NumericLimits (LogProb n d) where minFinite = LogProb 0 maxFinite = LogProb (1/0) withLog1 ∷ (Log x → Log y) → LogProb n x → LogProb n y withLog1 op (LogProb x) = LogProb . ln $ op (Exp x) {-# Inline withLog1 #-} withLog2 ∷ (Log x → Log y → Log z) → LogProb n x → LogProb n y → LogProb n z withLog2 op (LogProb x) (LogProb y) = LogProb . ln $ op (Exp x) (Exp y) {-# Inline withLog2 #-} -- * Conversion between probability in linear and log-space. -- | Turn probability into log-probability. p2lp ∷ (Floating x) ⇒ Prob n x → LogProb n x p2lp (Prob x) = LogProb $ log x {-# Inline p2lp #-} -- | Turn log-probability into probability. lp2p ∷ (Floating x) ⇒ LogProb n x → Prob n x lp2p (LogProb x) = Prob $ exp x {-# Inline lp2p #-} -- | An isomorphism between @Prob@ and @LogProb@. aslp ∷ (Floating x) ⇒ Iso' (Prob n x) (LogProb n x) aslp = iso p2lp lp2p {-# Inline aslp #-}