packages feed

mcmc-0.5.0.0: src/Mcmc/Prior.hs

{-# LANGUAGE BangPatterns #-}

-- |
-- Module      :  Prior
-- Description :  Types and convenience functions for computing priors
-- Copyright   :  (c) Dominik Schrempf, 2021
-- License     :  GPL-3.0-or-later
--
-- Maintainer  :  dominik.schrempf@gmail.com
-- Stability   :  unstable
-- Portability :  portable
--
-- Creation date: Thu Jul 23 13:26:14 2020.
module Mcmc.Prior
  ( Prior,
    PriorFunction,
    noPrior,

    -- * Improper priors
    largerThan,
    positive,
    lowerThan,
    negative,

    -- * Continuous priors
    exponential,
    gamma,
    gammaMeanVariance,
    gammaMeanOne,
    gammaShapeScaleToMeanVariance,
    gammaMeanVarianceToShapeScale,
    normal,
    uniform,

    -- * Discrete priors
    poisson,

    -- * Auxiliary functions
    product',
  )
where

import Control.Monad
import Data.Maybe (fromMaybe)
import Mcmc.Statistics.Types
import Numeric.Log
import qualified Statistics.Distribution as S
import qualified Statistics.Distribution.Exponential as S
import qualified Statistics.Distribution.Gamma as S
import qualified Statistics.Distribution.Normal as S
import qualified Statistics.Distribution.Poisson as S

-- | Prior values are stored in log domain.
type Prior = Log Double

-- | Prior function.
type PriorFunction a = a -> Prior

-- | Flat prior function. Useful for testing and debugging.
noPrior :: PriorFunction a
noPrior = const 1.0

-- | Improper uniform prior; strictly larger than a given value.
largerThan :: LowerBoundary -> PriorFunction Double
largerThan a x
  | x <= a = 0
  | otherwise = 1

-- | Improper uniform prior; strictly larger than zero.
positive :: PriorFunction Double
positive = largerThan 0

-- | Improper uniform prior; strictly lower than a given value.
lowerThan :: UpperBoundary -> PriorFunction Double
lowerThan b x
  | x >= b = 0
  | otherwise = 1

-- | Improper uniform prior; strictly lower than zero.
negative :: PriorFunction Double
negative = lowerThan 0

-- | Exponential distributed prior.
exponential :: Rate -> PriorFunction Double
exponential l = Exp . S.logDensity d
  where
    d = S.exponential l

-- | Gamma distributed prior.
gamma :: Shape -> Scale -> PriorFunction Double
gamma k t = Exp . S.logDensity d
  where
    d = S.gammaDistr k t

-- | See 'gamma' but parametrized using mean and variance.
gammaMeanVariance :: Mean -> Variance -> PriorFunction Double
gammaMeanVariance m v = Exp . S.logDensity d
  where (k, th) = gammaMeanVarianceToShapeScale m v
        d = S.gammaDistr k th

-- | Gamma disstributed prior with given shape and mean 1.0.
gammaMeanOne :: Shape -> PriorFunction Double
gammaMeanOne k = Exp . S.logDensity d
  where
    d = S.gammaDistr k (recip k)

-- The mean and variance of the gamma distribution are
--
-- m = k*t
--
-- v = k*t*t
--
-- Hence, the shape and scale are
--
-- k = m^2/v
--
-- t = v/m

-- | Calculate mean and variance of the gamma distribution given the shape and
-- the scale.
gammaShapeScaleToMeanVariance :: Shape -> Scale -> (Mean, Variance)
gammaShapeScaleToMeanVariance k t = let m = k * t in (m, m * t)

-- | Calculate shape and scale of the gamma distribution given the mean and
-- the variance.
gammaMeanVarianceToShapeScale :: Mean -> Variance -> (Shape, Scale)
gammaMeanVarianceToShapeScale m v = (m * m / v, v / m)

-- | Normal distributed prior.
normal :: Mean -> StandardDeviation -> PriorFunction Double
normal m s = Exp . S.logDensity d
  where
    d = S.normalDistr m s

-- | Uniform prior on [a, b].
uniform :: LowerBoundary -> UpperBoundary -> PriorFunction Double
uniform a b x
  | x <= a = 0
  | x >= b = 0
  | otherwise = Exp 0

-- | Poisson distributed prior.
poisson :: Rate -> PriorFunction Int
poisson l = Exp . S.logProbability d
  where
    d = S.poisson l

-- | Intelligent product that stops when encountering a zero.
--
-- Use with care because the elements are checked for positiveness, and this can
-- take some time if the list is long and does not contain any zeroes.
product' :: [Log Double] -> Log Double
product' = fromMaybe 0 . prodM

-- The type could be generalized to any MonadPlus Integer
prodM :: [Log Double] -> Maybe (Log Double)
prodM = foldM (\ !acc x -> (acc * x) <$ guard (acc /= 0)) 1