packages feed

goal-probability-0.1: Goal/Probability/Distributions.hs

-- | Various instances of 'Statistical' 'Manifold's.
module Goal.Probability.Distributions (
    -- * General Statistical Manifolds
      CurvedCategorical (CurvedCategorical)
    , Uniform (Uniform)
    -- * Exponential Family Manifolds
    , Bernoulli (Bernoulli)
    , Binomial (Binomial)
    , Categorical (Categorical)
    , Poisson (Poisson)
    , Normal (Normal)
    , MeanNormal (MeanNormal)
    , MultivariateNormal (MultivariateNormal)
    -- * Util
    , muSigmaToMultivariateNormal
    ) where

-- Package --

import Goal.Core
import Goal.Probability.Statistical
import Goal.Probability.ExponentialFamily

import Goal.Geometry

-- Qualified --

import qualified Data.Vector.Storable as C
import qualified Numeric.LinearAlgebra.HMatrix as M

-- Unqualified --

import System.Random.MWC.Monad
import System.Random.MWC.Distributions.Monad
import Statistics.Sample hiding (mean)
import Numeric.SpecFunctions

-- Uniform --

data Uniform = Uniform Double Double deriving (Eq, Read, Show)

instance Manifold Uniform where
    dimension _ = 0

instance Statistical Uniform where
    type SampleSpace Uniform = Continuum
    sampleSpace _ = Continuum

instance Generative Standard Uniform where
    generate p =
        let (Uniform a b) = manifold p
         in uniformR (a,b)

instance AbsolutelyContinuous Standard Uniform where
    density p x =
        let (Uniform a b) = manifold p
         in if x >= a && x <= b
               then recip $ b - a
               else 0

-- Bernoulli Distribution --

-- | The Bernoulli 'Family' with 'SampleSpace' 'Bernoulli' = 'Bool' (because why not).
data Bernoulli = Bernoulli deriving (Eq, Read, Show)

instance Manifold Bernoulli where
    dimension _ = 1

instance Statistical Bernoulli where
    type SampleSpace Bernoulli = Boolean
    sampleSpace Bernoulli = Boolean

instance Generative Standard Bernoulli where
    generate p = bernoulli . C.head $ coordinates p

instance AbsolutelyContinuous Standard Bernoulli where
    density p True = C.head $ coordinates p
    density p False = 1 - C.head (coordinates p)

instance MaximumLikelihood Standard Bernoulli where
    mle _ bls = fromList Bernoulli [mean $ toDouble <$> bls]
        where toDouble True = 1
              toDouble False = 0

instance Legendre Natural Bernoulli where
    potential p = log $ 1 + exp (coordinate 0 p)
    potentialDifferentials p = fromList (Tangent p) [logistic $ coordinate 0 p]

instance Legendre Mixture Bernoulli where
    potential p =
        let eta = coordinate 0 p
         in logit eta * eta - log (1 / (1 - eta))
    potentialDifferentials p = fromList (Tangent p) [logit $ coordinate 0 p]

instance ExponentialFamily Bernoulli where
    baseMeasure _ _ = 1
    sufficientStatistic Bernoulli True = fromList Bernoulli [1]
    sufficientStatistic Bernoulli False = fromList Bernoulli [0]

instance Riemannian Natural Bernoulli where
    metric p =
        let tht = coordinate 0 p
            stht = logistic tht
         in fromList (Tensor (Tangent p) (Tangent p)) [stht * (1-stht)]

instance Transition Standard Mixture Bernoulli where
    transition = breakChart

instance Transition Mixture Standard Bernoulli where
    transition = breakChart

instance Transition Standard Natural Bernoulli where
    transition = potentialMapping . chart Mixture . transition

instance Transition Natural Standard Bernoulli where
    transition = transition . potentialMapping

instance Generative Natural Bernoulli where
    generate = standardGenerate


-- Binomial Distribution --

newtype Binomial = Binomial { binomialTrials :: Int } deriving (Eq, Read, Show)

instance Manifold Binomial where
    dimension _ = 1

instance Statistical Binomial where
    type SampleSpace Binomial = [Int]
    sampleSpace (Binomial n) = [0..n]

instance Generative Standard Binomial where
    generate p = do
        let n = binomialTrials $ manifold p
        bls <- replicateM n . bernoulli . head $ listCoordinates p
        return $ sum [ if bl then 1 else 0 | bl <- bls ]

instance AbsolutelyContinuous Standard Binomial where
    density p k =
        let n = binomialTrials $ manifold p
            [c] = listCoordinates p
         in choose n k * c^k * (1 - c)^(n-k)

instance Legendre Natural Binomial where
    potential p =
        let n = fromIntegral . binomialTrials $ manifold p
            tht = coordinate 0 p
         in n * log (1 + exp tht)
    potentialDifferentials p =
        let n = fromIntegral . binomialTrials $ manifold p
         in fromList (Tangent p) [n * logistic (coordinate 0 p)]


instance Legendre Mixture Binomial where
    potential p =
        let n = fromIntegral . binomialTrials $ manifold p
            eta = coordinate 0 p
        in eta * log (eta / (n - eta)) - n * log (n / (n - eta))
    potentialDifferentials p =
        let n = fromIntegral . binomialTrials $ manifold p
            eta = coordinate 0 p
         in fromList (Tangent p) [log $ eta / (n - eta) ]

instance ExponentialFamily Binomial where
    baseMeasure (Binomial n) = choose n
    sufficientStatistic s k = fromList s [fromIntegral k]

instance Transition Standard Natural Binomial where
    transition = potentialMapping . chart Mixture . transition

instance Transition Natural Standard Binomial where
    transition = chart Standard . transition . potentialMapping

instance Transition Standard Mixture Binomial where
    transition p = breakChart $ alterCoordinates (* (fromIntegral . binomialTrials $ manifold p)) p

instance Transition Mixture Standard Binomial where
    transition p = breakChart $ alterCoordinates (/ (fromIntegral . binomialTrials $ manifold p)) p

-- Categorical Distribution --

newtype Categorical s = Categorical s deriving (Show,Eq,Read)
-- | A 'Categorical' distribution where the probability of the last category is
-- given by the normalization constraint.

generateCategorical :: [k] -> Coordinates -> RandST s k
-- | Takes a weighted list of elements representing a probability mass function, and
-- returns a sample from the Categorical distribution.
generateCategorical ks0 cs0 = do
    c0 <- uniform
    return $ findProbability ks0 cs0 c0
    where findProbability ks cs c
              | C.null cs = head ks
              | c < C.head cs = head ks
              | otherwise = findProbability (tail ks) (C.tail cs) (c - C.head cs)

instance Discrete s => Manifold (Categorical s) where
    dimension (Categorical s) = length (elements s) - 1

instance Discrete s => Statistical (Categorical s) where
    type SampleSpace (Categorical s) = s
    sampleSpace (Categorical ks) = ks

instance Discrete s => Generative Standard (Categorical s) where
    generate p = generateCategorical (samples $ manifold p) (coordinates p)

instance Discrete s => AbsolutelyContinuous Standard (Categorical s) where
    density p k
        | idx == dimension (manifold p) = 1 - C.sum cs
        | otherwise = cs C.! idx
          where cs = coordinates p
                idx = fromMaybe (error "attempted to calculate density of non-categorical element")
                    $ elemIndex k (samples $ manifold p)

instance Discrete s => MaximumLikelihood Standard (Categorical s) where
    mle m ks0' = fromIntegral (length ks0') /> fromList m (builder $ samples m)
        where builder ks
                | null $ tail ks = []
                | otherwise =
                    let k = head ks
                        kn = length $ filter (== k) ks0'
                     in fromIntegral kn : builder (tail ks)

instance Discrete s => Legendre Natural (Categorical s) where
    potential p = log $ 1 + C.sum (exp $ coordinates p)
    potentialDifferentials p =
        let exps = exp $ coordinates p
            nrm = 1 + C.sum exps
         in nrm /> fromCoordinates (Tangent p) exps

instance Discrete s => Legendre Mixture (Categorical s) where
    potential p =
        let cs = coordinates p
            scs = 1 - C.sum cs
         in C.sum (C.zipWith (*) cs $ log cs) + scs * log scs
    potentialDifferentials p =
        let ps = coordinates p
            nrm = 1 - C.sum ps
         in fromCoordinates (Tangent p) (log $ C.map (/nrm) ps)

instance Discrete s => ExponentialFamily (Categorical s) where
    baseMeasure _ _ = 1
    sufficientStatistic m k = fromCoordinates m $ C.generate (dimension m) (\j -> if i == j then 1 else 0)
      where ks = samples m
            i = fromMaybe (error "Categorical distribution given uncategorized element") $ elemIndex k ks

instance Discrete s => Transition Standard Mixture (Categorical s) where
    transition = breakChart

instance Discrete s => Transition Mixture Standard (Categorical s) where
    transition = breakChart

instance Discrete s => Transition Standard Natural (Categorical s) where
    transition = potentialMapping . chart Mixture . transition

instance Discrete s => Transition Natural Standard (Categorical s) where
    transition = transition . potentialMapping

-- Curved Categorical Distribution --

newtype CurvedCategorical s = CurvedCategorical s deriving (Show,Eq,Read)

instance Discrete s => Manifold (CurvedCategorical s) where
    dimension = length . samples

instance Discrete s => Statistical (CurvedCategorical s) where
    type SampleSpace (CurvedCategorical s) = s
    sampleSpace (CurvedCategorical s) = s

instance Discrete s => Generative Standard (CurvedCategorical s) where
    generate p = generateCategorical (samples $ manifold p) (coordinates p)

instance Discrete s => AbsolutelyContinuous Standard (CurvedCategorical s) where
    density p k = cs C.! idx
          where ks = samples $ manifold p
                cs = coordinates p
                idx = fromMaybe (error "attempted to calculate density of non-categorical element")
                    $ elemIndex k ks

-- Poisson Distribution --

generatePoisson :: Double -> RandST s Int
-- | Returns a sample from a Poisson distribution with the given rate.
generatePoisson rt =
    uniform >>= renew 0
    where l = exp (-rt)
          renew k p
              | p <= l = return k
              | otherwise = do
                  u <- uniform
                  renew (k+1) (p*u)

data Poisson = Poisson deriving (Eq, Read, Show)

instance Manifold Poisson where
    dimension _ = 1

instance Statistical Poisson where
    type SampleSpace Poisson = NaturalNumbers
    sampleSpace _ = NaturalNumbers

instance Generative Standard Poisson where
    generate d = generatePoisson . C.head $ coordinates d

instance AbsolutelyContinuous Standard Poisson where
    density d k =
        let ps = coordinates d
            lmda = C.head ps
        in  lmda^k / factorial k * exp (-lmda)

instance MaximumLikelihood Standard Poisson where
    mle _ xs = fromList Poisson . (:[]) . mean $ fromIntegral <$> xs

instance ExponentialFamily Poisson where
    sufficientStatistic Poisson = fromCoordinates Poisson . C.singleton . fromIntegral
    baseMeasure _ k = recip $ factorial k

instance Legendre Natural Poisson where
    potential p = exp $ coordinate 0 p
    potentialDifferentials p = fromCoordinates (Tangent p) . exp $ coordinates p

instance Legendre Mixture Poisson where
    potential p =
        let eta = coordinate 0 p
         in eta * log eta - eta
    potentialDifferentials p = fromCoordinates (Tangent p) . log $ coordinates p

instance Riemannian Natural Poisson where
    metric p =
        let tht = coordinate 0 p
         in fromList (Tensor (Tangent p) (Tangent p)) [exp tht]

instance Transition Standard Natural Poisson where
    transition = transition . chart Mixture . transition

instance Transition Natural Standard Poisson where
    transition = transition . potentialMapping

instance Transition Standard Mixture Poisson where
    transition = breakChart

instance Transition Mixture Standard Poisson where
    transition = breakChart

instance Generative Natural Poisson where
    generate = standardGenerate

-- Normal Distribution --

data Normal = Normal deriving (Show,Eq,Read)

instance Manifold Normal where
    dimension _ = 2

instance Statistical Normal where
    type SampleSpace Normal = Continuum
    sampleSpace _ = Continuum

instance Generative Standard Normal where
    generate p =
        let [mu,vr] = listCoordinates p
         in normal mu $ sqrt vr

instance AbsolutelyContinuous Standard Normal where
    density p x =
        let [mu,vr] = listCoordinates p
         in recip (sqrt $ vr*2*pi) * exp (negate $ (x - mu) ** 2 / (2*vr))

instance MaximumLikelihood Standard Normal where
    mle _ xs =
        let (mu,vr) = meanVariance $ C.fromList xs
        in fromList Normal [mu,vr]

instance ExponentialFamily Normal where
    sufficientStatistic Normal x = fromList Normal [x,x**2]
    baseMeasure _ _ = recip . sqrt $ 2 * pi

instance Legendre Natural Normal where
    potential p =
        let [tht0,tht1] = listCoordinates p
         in -(tht0^2 / (4*tht1)) - 0.5 * log(-2*tht1)
    potentialDifferentials p =
        let [tht0,tht1] = listCoordinates p
            dv = tht0/tht1
         in fromList (Tangent p) [-0.5*dv, 0.25 * dv^2 - 0.5/tht1]

instance Legendre Mixture Normal where
    potential p =
        let [eta0,eta1] = listCoordinates p
         in -0.5 * log(eta1 - eta0^2) - 1/2
    potentialDifferentials p =
        let [eta0,eta1] = listCoordinates p
            dff = eta0^2 - eta1
         in fromList (Tangent p) [-eta0 / dff, 0.5 / dff]

instance Riemannian Natural Normal where
    metric p =
        let [tht1,tht2] = listCoordinates p
         in fromList (Tensor (Tangent p) (Tangent p))
                [-1/(2*tht2),tht1/(2*tht2^2),tht1/(2*tht2^2),(-tht1^2 + tht2)/(2*tht2^3) ]

instance Riemannian Standard Normal where
    metric p =
        let [_,vr] = listCoordinates p
         in fromList (Tensor (Tangent p) (Tangent p)) [recip vr,0,0,recip $ 2*vr^2]

instance Transition Standard Mixture Normal where
    transition p =
        let [mu,vr] = listCoordinates p
         in fromList Normal [mu, vr + mu^2]

instance Transition Mixture Standard Normal where
    transition p =
        let [eta0,eta1] = listCoordinates p
         in fromList Normal [eta0, eta1 - eta0^2]

instance Transition Standard Natural Normal where
    transition p =
        let [mu,vr] = listCoordinates p
         in fromList Normal [mu / vr, negate . recip $ 2 * vr]

instance Transition Natural Standard Normal where
    transition p =
        let [tht0,tht1] = listCoordinates p
         in fromList Normal [-0.5 * tht0 / tht1, negate . recip $ 2 * tht1]

instance Generative Natural Normal where
    generate = standardGenerate

-- MeanNormal Distribution --

data MeanNormal = MeanNormal Double deriving (Show,Eq,Read)

instance Manifold MeanNormal where
    dimension _ = 1


instance Statistical MeanNormal where
    type SampleSpace MeanNormal = Continuum
    sampleSpace _ = Continuum

instance Generative Standard MeanNormal where
    generate p = do
        let (MeanNormal vr) = manifold p
        normal (coordinate 0 p) $ sqrt vr

instance AbsolutelyContinuous Standard MeanNormal where
    density p =
        let (MeanNormal vr) = manifold p
            mu = coordinate 0 p
         in density . chart Standard $ fromList Normal [mu,vr]

instance MaximumLikelihood Standard MeanNormal where
    mle mnrm xs = fromList mnrm [mean xs]

instance Legendre Natural MeanNormal where
    potential p =
        let (MeanNormal vr) = manifold p
         in 0.5 * vr * coordinate 0 p^2
    potentialDifferentials p =
        let (MeanNormal vr) = manifold p
         in fromList (Tangent p) [vr * coordinate 0 p]

instance Legendre Mixture MeanNormal where
    potential p =
        let (MeanNormal vr) = manifold p
         in 0.5 / vr * coordinate 0 p^2
    potentialDifferentials p =
        let (MeanNormal vr) = manifold p
         in fromList (Tangent p) [coordinate 0 p / vr]

instance ExponentialFamily MeanNormal where
    sufficientStatistic mnrm x = fromList mnrm [x]
    baseMeasure (MeanNormal vr) x = (exp . negate $ 0.5 * x^2 / vr) / sqrt (2*pi*vr)

instance Riemannian Natural MeanNormal where
    metric p =
        let (MeanNormal vr) = manifold p
         in fromList (Tensor (Tangent p) (Tangent p)) [vr]

instance Transition Standard Natural MeanNormal where
    transition = potentialMapping . chart Mixture . breakChart

instance Transition Natural Standard MeanNormal where
    transition = breakChart . potentialMapping

instance Transition Standard Mixture MeanNormal where
    transition = breakChart

instance Transition Mixture Standard MeanNormal where
    transition = breakChart

-- Multivariate Normal --

data MultivariateNormal = MultivariateNormal { sampleSpaceDimension :: Int } deriving (Eq, Read, Show)

generateMultivariateNormal :: C.Vector Double -> M.Matrix Double -> RandST s (C.Vector Double)
-- | Samples from a multivariate Normal.
generateMultivariateNormal mus rtsgma = do
    nrms <- C.replicateM n $ normal 0 1
    return $ mus + (M.#>) rtsgma nrms
    where n = C.length mus

muSigmaToMultivariateNormal :: C.Vector Double -> M.Matrix Double -> Standard :#: MultivariateNormal
-- | Generates a multivariateNormal by way of a covariance matrix i.e. by taking
-- the square root.
muSigmaToMultivariateNormal mus sgma =
    fromCoordinates (MultivariateNormal $ C.length mus) $ mus C.++ M.flatten sgma

splitCoordinates :: c :#: MultivariateNormal -> (Coordinates, M.Matrix Double)
splitCoordinates p =
    let (MultivariateNormal n) = manifold p
        (mus,sgms) = C.splitAt n $ coordinates p
     in (mus,M.reshape n sgms)

instance Manifold MultivariateNormal where
    dimension (MultivariateNormal n) = n + n^2

instance Statistical MultivariateNormal where
    type SampleSpace MultivariateNormal = Euclidean
    sampleSpace (MultivariateNormal n) = Euclidean n

instance Generative Standard MultivariateNormal where
    generate p =
        let n = sampleSpaceDimension $ manifold p
            (mus,sds) = C.splitAt n $ coordinates p
         in generateMultivariateNormal mus $ M.reshape n sds

instance AbsolutelyContinuous Standard MultivariateNormal where
    density p xs =
        let n = sampleSpaceDimension $ manifold p
            (mus,sgma) = splitCoordinates p
            flx = M.sqrtm sgma
         in recip ((2*pi)**(fromIntegral n / 2) * M.det flx)
            * exp (-0.5 * ((M.tr (M.inv sgma) M.#> C.zipWith (-) xs mus) `M.dot` C.zipWith (-) xs mus))

instance MaximumLikelihood Standard MultivariateNormal where
    mle _ xss =
        let n = fromIntegral $ length xss
            mus = recip (fromIntegral n) * sum xss
            sgma = recip (fromIntegral $ n - 1)
                * sum (map (\xs -> let xs' = xs - mus in M.outer xs' xs') xss)
        in  muSigmaToMultivariateNormal mus sgma

instance ExponentialFamily MultivariateNormal where
    sufficientStatistic m x = fromCoordinates m $ x C.++ M.flatten (M.outer x x)
    baseMeasure (MultivariateNormal n) _ = (2*pi)**(-fromIntegral n/2)

instance Legendre Natural MultivariateNormal where
    potential p =
        let (tmu,tsgma) = splitCoordinates p
            invtsgma = M.inv tsgma
         in -0.25 * M.dot tmu (invtsgma M.#> tmu) - 0.5 * log(M.det $ M.scale (-2) tsgma)
    potentialDifferentials p =
        let (tmu,tsgma) = splitCoordinates p
            invtsgma = M.inv tsgma
            invapp = M.app invtsgma tmu
         in fromCoordinates (Tangent p) $ (-0.5 * invapp)
                C.++ M.flatten (M.scale (-0.5) invtsgma + M.scale 0.25 (M.outer invapp invapp))

instance Legendre Mixture MultivariateNormal where
    potential p =
        let (mmu,msgma) = splitCoordinates p
            --n = fromIntegral . sampleSpaceDimension $ manifold p
         in -0.5 * (1 + M.dot mmu (M.inv msgma M.#> mmu)) - 0.5 * log (M.det msgma)
    potentialDifferentials p =
        let (mmu,msgma) = splitCoordinates p
            invmsgma' = M.inv $ M.outer mmu mmu - msgma
         in fromCoordinates (Tangent p) $ (negate invmsgma' M.#> mmu) C.++ M.flatten (M.scale 0.5 invmsgma')

instance Transition Standard Natural MultivariateNormal where
    transition p =
        let (mu,sgma) = splitCoordinates p
            invsgma = M.inv sgma
         in fromCoordinates (manifold p) $ (invsgma M.#> mu) C.++ M.flatten (M.scale (-0.5) invsgma)

instance Transition Natural Standard MultivariateNormal where
    transition p =
        let (emu,esgma) = splitCoordinates p
            invesgma = M.inv esgma
         in fromCoordinates (manifold p) $ M.scale 0.5 (invesgma M.#> emu) C.++ M.flatten (M.scale 0.5 invesgma)

instance Transition Standard Mixture MultivariateNormal where
    transition p =
        let (mu,sgma) = splitCoordinates p
         in fromCoordinates (manifold p) $ mu C.++ M.flatten (sgma + M.outer mu mu)

instance Transition Mixture Standard MultivariateNormal where
    transition p =
        let (mmu,msgma) = splitCoordinates p
         in fromCoordinates (manifold p) $ mmu C.++ M.flatten (msgma -M.outer mmu mmu)


{-
--- Graveyard ---


functionToCategorical :: Double -> Double -> Int -> (Double -> Double) -> Standard :#: Categorical Double
-- | Takes range information in the form of a minimum, maximum, and sample count,
-- and a function which represents an unnomralized pdf, and returns a normalized list of
-- pairs (x,f(x)) over the specified range such that the sum of the f(x)s is 1.
--
-- In principle, f should be strictly positive, but this is not checked.
functionToCategorical mn mx n f =
    let (ks,fks) = unzip $ discretizeFunction mn mx n f
     in recip (sum fks) .> fromList (Categorical ks) fks

-- Exponential Distribution --

data Exponential = Exponential deriving (Eq,Read,Show)

instance Manifold Exponential where
    dimension _ = 1

type instance SampleSpace Exponential = Continuum

instance Statistical Exponential where
    sampleSpace _ = Continuum

instance Generative Standard Exponential where
    generate = exponential . C.head . coordinates

instance AbsolutelyContinuous Standard Exponential where
    density p x =
        let lmda = C.head $ coordinates p
         in lmda * exp (negate $ lmda * x)

instance MaximumLikelihood Standard Exponential where
    mle _ xs = chart Standard . fromList Exponential . (:[]) . recip . mean $ xs

instance Legendre Natural Exponential where
    potential p = negate . log . negate $ coordinate 0 p
    potentialDifferentials p = fromCoordinates (Tangent p) . negate $ coordinates p

instance Legendre Mixture Exponential where
    potential p = 1 - log eta
    potentialDifferentials p =

instance ExponentialFamily Exponential where
    sufficientStatistic Exponential = fromCoordinates Exponential . C.singleton
    baseMeasure _ _ = 1

instance Transition Standard Natural Exponential where
    transition = breakChart . alterCoordinates negate

instance Transition Natural Standard Exponential where
    transition = breakChart . alterCoordinates negate

-}