packages feed

goal-probability-0.20: Goal/Probability/Statistical.hs

{-# LANGUAGE UndecidableInstances #-}

-- | Core types, classes, and functions for working with manifolds of
-- probability distributions.
module Goal.Probability.Statistical
    ( -- * Random
      Random (Random)
    , Statistical (SamplePoint)
    , Sample
    , realize
    -- * Initializiation
    , initialize
    , uniformInitialize
    , uniformInitialize'
    -- * Properties of Distributions
    , Generative (sample,samplePoint)
    , AbsolutelyContinuous (densities,logDensities)
    , density
    , logDensity
    , Discrete (Cardinality,sampleSpace)
    , pointSampleSpace
    , expectation
    -- ** Maximum Likelihood Estimation
    , MaximumLikelihood (mle)
    , LogLikelihood (logLikelihood,logLikelihoodDifferential)
    ) where


--- Imports ---


-- Package --

import Goal.Core
import Goal.Geometry

import qualified Goal.Core.Vector.Boxed as B
import qualified Goal.Core.Vector.Storable as S
import qualified Goal.Core.Vector.Generic as G

-- Qualified --

import qualified Data.List as L
import qualified System.Random.MWC as R

import Foreign.Storable


--- Probability Theory ---


-- | A 'Manifold' is 'Statistical' if it is a set of probability distributions
-- over a particular sample space, where the sample space is a set of the
-- specified 'SamplePoint's.
class Manifold x => Statistical x where
    type SamplePoint x :: Type

-- | A 'Sample' is a list of 'SamplePoint's.
type Sample x = [SamplePoint x]

-- | A random variable.
newtype Random a = Random (forall s. R.Gen s -> ST s a)

-- | Turn a random variable into an IO action.
realize :: Random a -> IO a
realize (Random rv) = R.withSystemRandomST rv

-- | Probability distributions for which the sample space is countable. This
-- affords brute force computation of expectations.
class KnownNat (Cardinality x) => Discrete x where
    type Cardinality x :: Nat
    sampleSpace :: Proxy x -> Sample x

-- | Convenience function for getting the sample space of a 'Discrete'
-- probability distribution.
pointSampleSpace :: forall c x . Discrete x => c # x -> Sample x
pointSampleSpace _ = sampleSpace (Proxy :: Proxy x)

-- | A distribution is 'Generative' if we can 'sample' from it. Generation is
-- powered by @mwc-random@.
class Statistical x => Generative c x where
    samplePoint :: Point c x -> Random (SamplePoint x)
    samplePoint = fmap head . sample 1
    sample :: Int -> Point c x -> Random (Sample x)
    sample n = replicateM n . samplePoint


-- | The distributions \(P \in \mathcal M\) in a 'Statistical' 'Manifold'
-- \(\mathcal M\) are 'AbsolutelyContinuous' if there is a reference measure
-- \(\mu\) and a function \(p\) such that
-- \(P(A) = \int_A p d\mu\). We refer to \(p(x)\) as the 'density' of the
-- probability distribution.
class Statistical x => AbsolutelyContinuous c x where
    logDensities :: Point c x -> Sample x -> [Double]
    logDensities p = map log . densities p

    densities :: Point c x -> Sample x -> [Double]
    densities p = map exp . logDensities p

logDensity :: AbsolutelyContinuous c x => Point c x -> SamplePoint x -> Double
logDensity p = head . logDensities p . (:[])

density :: AbsolutelyContinuous c x => Point c x -> SamplePoint x -> Double
density p = exp . logDensity p

-- | 'expectation' computes the brute force expected value of a 'Finite' set
-- given an appropriate 'density'.
expectation
    :: forall c x . (AbsolutelyContinuous c x, Discrete x)
    => Point c x
    -> (SamplePoint x -> Double)
    -> Double
expectation p f =
    let xs = sampleSpace (Proxy :: Proxy x)
     in sum $ zipWith (*) (f <$> xs) (densities p xs)

-- Maximum Likelihood Estimation

-- | 'mle' computes the 'MaximumLikelihood' estimator.
class Statistical x => MaximumLikelihood c x where
    mle :: Sample x -> c # x

-- | Average log-likelihood and the differential for gradient ascent.
class Manifold x => LogLikelihood c x s where
    logLikelihood :: [s] -> c # x -> Double
    --logLikelihood xs p = average $ log <$> densities p xs
    logLikelihoodDifferential :: [s] -> c # x -> c #* x


--- Construction ---


-- | Generates a random point on the target 'Manifold' by generating random
-- samples from the given distribution.
initialize
    :: (Manifold x, Generative d y, SamplePoint y ~ Double)
    => d # y
    -> Random (c # x)
initialize q = Point <$> S.replicateM (samplePoint q)

-- | Generates an initial point on the target 'Manifold' by generating uniform
-- samples from the given vector of bounds.
uniformInitialize' :: Manifold x => B.Vector (Dimension x) (Double,Double) -> Random (Point c x)
uniformInitialize' bnds =
    Random $ \gn -> Point . G.convert <$> mapM (`R.uniformR` gn) bnds

-- | Generates an initial point on the target 'Manifold' by generating uniform
-- samples from the given vector of bounds.
uniformInitialize :: Manifold x => (Double,Double) -> Random (Point c x)
uniformInitialize bnds =
    Random $ \gn -> Point <$> S.replicateM (R.uniformR bnds gn)


--- Instances ---


-- Random --

instance Functor Random where
    fmap f (Random rx) =
        Random $ fmap f . rx

instance Applicative Random where
    pure x = Random $ \_ -> return x
    (<*>) = ap

instance Monad Random where
    (>>=) (Random rx) rf =
        Random $ \gn -> do
            a <- rx gn
            let (Random rv) = rf a
            rv gn


-- Replicated --

instance (Statistical x, KnownNat k, Storable (SamplePoint x))
  => Statistical (Replicated k x) where
    type SamplePoint (Replicated k x) = S.Vector k (SamplePoint x)

instance (KnownNat k, Generative c x, Storable (SamplePoint x))
  => Generative c (Replicated k x) where
    samplePoint = S.mapM samplePoint . splitReplicated

instance (KnownNat k, Storable (SamplePoint x), AbsolutelyContinuous c x)
  => AbsolutelyContinuous c (Replicated k x) where
    densities cx sxss =
        let sxss' = L.transpose $ S.toList <$> sxss
            cxs = S.toList $ splitReplicated cx
            dnss = zipWith densities cxs sxss'
         in product <$> L.transpose dnss

instance (KnownNat k, LogLikelihood c x s, Storable s)
  => LogLikelihood c (Replicated k x) (S.Vector k s) where
    logLikelihood cxs ps = S.sum . S.imap subLogLikelihood $ splitReplicated ps
        where subLogLikelihood fn = logLikelihood (flip S.index fn <$> cxs)
    logLikelihoodDifferential cxs ps =
        joinReplicated . S.imap subLogLikelihoodDifferential $ splitReplicated ps
            where subLogLikelihoodDifferential fn =
                    logLikelihoodDifferential (flip S.index fn <$> cxs)


-- Pair --


instance (Statistical x) => Statistical [x] where
    type SamplePoint [x] = [SamplePoint x]

instance (Statistical x, Statistical y)
  => Statistical (x,y) where
    type SamplePoint (x,y) = (SamplePoint x, SamplePoint y)

instance (Generative c x, Generative c y) => Generative c (x,y) where
    samplePoint pmn = do
        let (pm,pn) = split pmn
        xm <- samplePoint pm
        xn <- samplePoint pn
        return (xm,xn)

instance (AbsolutelyContinuous c x, AbsolutelyContinuous c y)
  => AbsolutelyContinuous c (x,y) where
    densities cxy sxys =
        let (cx,cy) = split cxy
            (sxs,sys) = unzip sxys
         in zipWith (*) (densities cx sxs) $ densities cy sys