goal-probability-0.20: Goal/Probability/Distributions/CoMPoisson.hs
{-# OPTIONS_GHC -fplugin=GHC.TypeLits.KnownNat.Solver -fplugin=GHC.TypeLits.Normalise -fconstraint-solver-iterations=10 #-}
{-# LANGUAGE UndecidableInstances #-}
-- | Implementation of Conway-Maxwell Poisson distributions (CoMPoisson).
-- (<https://rss.onlinelibrary.wiley.com/doi/abs/10.1111/j.1467-9876.2005.00474.x>) CoMPoisson distributions generalize Poisson distributions with
-- a shape parameter that can concentrate or disperse the underlying Poisson
-- distribution.
module Goal.Probability.Distributions.CoMPoisson
(
-- * CoMPoisson
CoMPoisson
, CoMShape
-- ** Numerics
, comPoissonLogPartitionSum
, comPoissonExpectations
) where
-- Package --
import Goal.Core
import Goal.Geometry
import Goal.Probability.Statistical
import Goal.Probability.ExponentialFamily
import Goal.Probability.Distributions
import qualified Goal.Core.Vector.Storable as S
import qualified System.Random.MWC as R
--- Analysis ---
--- CoMPoisson Distribution ---
-- | A type for storing the shape of a 'CoMPoisson' distribution.
data CoMShape
-- | The 'Manifold' of 'CoMPoisson' distributions. The 'Source' coordinates of the
-- 'CoMPoisson' are the mode $\mu$ and the "pseudo-precision" parameter $\nu$, such that $\mu / \nu$ is approximately the variance of the distribution.
type CoMPoisson = LocationShape Poisson CoMShape
-- | Approximates the log-partition function of the given CoMPoisson
-- distribution up to the specified precision.
comPoissonLogPartitionSum :: Double -> Natural # CoMPoisson -> Double
{-# INLINE comPoissonLogPartitionSum #-}
comPoissonLogPartitionSum eps np =
let (tht1,tht2) = S.toPair $ coordinates np
in fst $ comPoissonLogPartitionSum0 eps tht1 tht2
-- | Approximates the expectations of functions given the natural parameters of
-- a CoM-Poisson distribution.
comPoissonExpectations
:: KnownNat n
=> Double
-> (Int -> S.Vector n Double)
-> Natural # CoMPoisson
-> S.Vector n Double
{-# INLINE comPoissonExpectations #-}
comPoissonExpectations eps f np =
let (tht1,tht2) = S.toPair $ coordinates np
(lgprt,ln) = comPoissonLogPartitionSum0 eps tht1 tht2
js = [0..ln]
dns = exp . subtract lgprt <$> unnormalizedLogDensities np js
in sum $ zipWith S.scale dns (f <$> js)
-- | Approximates the mean mparameters of a CoM-Poisson distribution.
comPoissonMeans :: Double -> Natural # CoMPoisson -> Mean # CoMPoisson
{-# INLINE comPoissonMeans #-}
comPoissonMeans eps cp =
let ss :: Int -> Mean # CoMPoisson
ss = sufficientStatistic
in Point $ comPoissonExpectations eps (coordinates . ss) cp
--- Internal ---
comPoissonSequence :: Double -> Double -> [Double]
comPoissonSequence tht1 tht2 =
[ tht1 * fromIntegral j + logFactorial j *tht2 | (j :: Int) <- [0..] ]
comPoissonLogPartitionSum0 :: Double -> Double -> Double -> (Double, Int)
{-# INLINE comPoissonLogPartitionSum0 #-}
comPoissonLogPartitionSum0 eps tht1 tht2 =
let md = floor $ comPoissonSmoothMode tht1 tht2
(hdsqs,tlsqs) = splitAt md $ comPoissonSequence tht1 tht2
mx = tht1 * fromIntegral md + logFactorial md *tht2
ehdsqs = exp . subtract mx <$> hdsqs
etlsqs = exp . subtract mx <$> tlsqs
sqs' = ehdsqs ++ takeWhile (> eps) etlsqs
in ((+ mx) . log1p . subtract 1 $ sum sqs' , length sqs')
comPoissonSmoothMode :: Double -> Double -> Double
comPoissonSmoothMode tht1 tht2 = exp (tht1/negate tht2)
--comPoissonApproximateMean :: Double -> Double -> Double
--comPoissonApproximateMean mu nu =
-- mu + 1/(2*nu) - 0.5
--
--comPoissonApproximateVariance :: Double -> Double -> Double
--comPoissonApproximateVariance mu nu = mu / nu
overDispersedEnvelope :: Double -> Double -> Double -> Double
overDispersedEnvelope p mu nu =
let mnm1 = 1 - p
flrd = max 0 . floor $ mu / (mnm1**recip nu)
nmr = mu**(nu * fromIntegral flrd)
dmr = (mnm1^flrd) * (factorial flrd ** nu)
in recip p * nmr / dmr
underDispersedEnvelope :: Double -> Double -> Double
underDispersedEnvelope mu nu =
let fmu = floor mu
in (mu ^ fmu / factorial fmu)** (nu - 1)
sampleOverDispersed :: Double -> Double -> Double -> Double -> Random Int
sampleOverDispersed p bnd0 mu nu = do
u0 <- Random R.uniform
let y' = max 0 . floor $ logBase (1 - p) u0
nmr = (mu^y' / factorial y')**nu
dmr = bnd0 * (1-p)^y' * p
alph = nmr/dmr
u <- Random R.uniform
if isNaN alph
then error "NaN in sampling CoMPoisson: Parameters out of bounds"
else if u <= alph
then return y'
else sampleOverDispersed p bnd0 mu nu
sampleUnderDispersed :: Double -> Double -> Double -> Random Int
sampleUnderDispersed bnd0 mu nu = do
let psn :: Source # Poisson
psn = Point $ S.singleton mu
y' <- samplePoint psn
let alph0 = mu^y' / factorial y'
alph = alph0**nu / (bnd0*alph0)
u <- Random R.uniform
if u <= alph
then return y'
else sampleUnderDispersed bnd0 mu nu
sampleCoMPoisson :: Int -> Double -> Double -> Random [Int]
sampleCoMPoisson n mu nu
| nu >= 1 =
let bnd0 = underDispersedEnvelope mu nu
in replicateM n $ sampleUnderDispersed bnd0 mu nu
| otherwise =
let p = 2*nu / (2*mu*nu + 1 + nu)
bnd0 = overDispersedEnvelope p mu nu
in replicateM n $ sampleOverDispersed p bnd0 mu nu
-- Instances --
instance ExponentialFamily CoMPoisson where
sufficientStatistic k = fromTuple (fromIntegral k, logFactorial k)
logBaseMeasure _ _ = 0
type instance PotentialCoordinates CoMPoisson = Natural
instance Legendre CoMPoisson where
potential =
comPoissonLogPartitionSum 1e-16
instance AbsolutelyContinuous Natural CoMPoisson where
logDensities = exponentialFamilyLogDensities
instance Transition Source Natural CoMPoisson where
transition p =
let (mu,nu) = S.toPair $ coordinates p
in fromTuple (nu * log mu, -nu)
instance Transition Natural Source CoMPoisson where
transition p =
let (tht1,tht2) = S.toPair $ coordinates p
in fromTuple (exp (-tht1/tht2), -tht2)
instance (Transition c Source CoMPoisson) => Generative c CoMPoisson where
sample n p = do
let (mu,nu) = S.toPair . coordinates $ toSource p
in sampleCoMPoisson n mu nu
instance Transition Natural Mean CoMPoisson where
transition = comPoissonMeans 1e-16
instance Transition Source Mean CoMPoisson where
transition = toMean . toNatural
instance LogLikelihood Natural CoMPoisson Int where
logLikelihood = exponentialFamilyLogLikelihood
logLikelihoodDifferential = exponentialFamilyLogLikelihoodDifferential
instance Manifold CoMShape where
type Dimension CoMShape = 1