packages feed

lazyppl-1.0.1: src/LazyPPL/Distributions.hs

{- | Handy distributions for the `LazyPPL` library, based on the `uniform` distribution. Mostly defined using the `Statistics.Distribution` module and family. 

Sometimes both a distribution (type @Prob a@) and pdf (type @a -> Double@) are given. Distributions are useful for sampling, densities are used for scoring. 

For more distributions, see the Statistics.Distribution in the statistics package. -}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Avoid lambda using `infix`" #-}


module LazyPPL.Distributions (
        -- * Continuous distributions
        normal,normalPdf,normalLogPdf,exponential,expPdf,exponentialLogPdf,
        gamma, gammaLogPdf, beta, betaLogPdf, dirichlet, dirichletLogPdf,
        uniformbounded, uniformBoundedLogPdf,
        logNormalLogPdf,
        -- * Discrete distributions
        bernoulli, bernoulliLogPmf, uniformdiscrete, categorical, categoricalLogPmf,
        poisson, poissonPdf, poissonLogPmf,
        binomialLogPmf,
        -- * Streams
        iid)
      where

import LazyPPL (Prob,uniform)
import Data.List (findIndex)
import Numeric.SpecFunctions
import Numeric.MathFunctions.Constants

{-|
  [Normal distribution](https://en.wikipedia.org/wiki/Normal_distribution)
-}
normal :: Double -- ^ mu, mean
        -> Double -- ^ sigma, standard deviation
        -> Prob Double
normal m s = do
  x <- uniform
  return $ (- invErfc (2 * x)) * (m_sqrt_2 * s) + m
normalPdf :: Double -> Double -> Double -> Double
normalPdf m s x = exp (- ((x - m) * (x - m) / (2 * s * s)) - log (m_sqrt_2_pi * s))


{-|
  [Exponential distribution](https://en.wikipedia.org/wiki/Exponential_distribution)
-}
exponential :: Double -- ^ lambda, rate
            -> Prob Double
exponential rate = do
  x <- uniform
  return $ - (log x / rate)
expPdf :: Double -> Double -> Double
expPdf rate x = exp (- (rate * x)) * rate

{-|
  [Gamma distribution](https://en.wikipedia.org/wiki/Gamma_distribution)
-}
gamma :: Double -- ^ k, shape
      -> Double -- ^ theta, scale
      -> Prob Double
gamma a b = do
  x <- uniform
  return $ b * invIncompleteGamma a x

{-|
  [Beta distribution](https://en.wikipedia.org/wiki/Beta_distribution)
-}
beta :: Double -- ^ alpha
      -> Double -- ^ beta
      -> Prob Double
beta a b = do
  x <- uniform
  return $ invIncompleteBeta a b x

{-|
  [Poisson distribution](https://en.wikipedia.org/wiki/Poisson_distribution)
-}
poisson :: Double -- ^ lambda, rate
        -> Prob Integer
poisson lambda = do
  x <- uniform
  let cmf = map (\x -> 1 - incompleteGamma (fromIntegral (x + 1)) lambda) [0,1..]
  let (Just n) = findIndex (> x) cmf
  return $ fromIntegral n

poissonPdf :: Double -> Integer -> Double
poissonPdf rate n = let result = exp(-rate) * rate ^^ fromIntegral n / factorial (fromIntegral n) in
  if isInfinite result || isNaN result then exp (-rate + fromIntegral n * log rate - logGamma (fromIntegral (n+1))) else result


{-|
  [Dirichlet distribution](https://en.wikipedia.org/wiki/Dirichlet_distribution)
-}
dirichlet :: [Double] -- ^ vector of alphas; length is dimension
          -> Prob[Double]
dirichlet as = do
  xs <- mapM (\a -> gamma a 1) as
  let s = Prelude.sum xs
  let ys = map (/ s) xs
  return ys

-- | [Continuous uniform distribution on a bounded interval](https://en.wikipedia.org/wiki/Continuous_uniform_distribution)
uniformbounded :: Double -- ^ lower
                -> Double -- ^ upper
                -> Prob Double
uniformbounded lower upper = do
  x <- uniform
  return $ (upper - lower) * x + lower

-- | [Bernoulli distribution](https://en.wikipedia.org/wiki/Bernoulli_distribution)
bernoulli :: Double -- ^ bias
          -> Prob Bool
bernoulli r = do
  x <- uniform
  return $ x < r

{-|
  [Discrete uniform distribution](https://en.wikipedia.org/wiki/Discrete_uniform_distribution) on [0, ..., n-1]
-}
uniformdiscrete :: Int -- ^ n
                -> Prob Int
uniformdiscrete n =
  do
    let upper = fromIntegral n
    r <- uniformbounded 0 upper
    return $ floor r

{-| [Categorical distribution](https://www.google.com/search?client=safari&rls=en&q=categorical+distribution&ie=UTF-8&oe=UTF-8): Takes a list of k numbers that sum to 1, 
    and returns a random number between 0 and (k-1), weighted accordingly -}
categorical :: [Double] -> Prob Int
categorical xs = do
  r <- uniform
  case findIndex (>r) $ tail $ scanl (+) 0 xs of
    Just i -> return i
    Nothing -> error "categorical: probabilities do not sum to 1"

{-| Returns an infinite stream of samples from the given distribution. --}
iid :: Prob a -> Prob [a]
iid p = do r <- p; rs <- iid p; return $ r : rs

normalLogPdf :: Double -> Double -> Double -> Double
normalLogPdf mu sigma x =
  let d = x - mu
  in -0.5 * log (2 * pi) - log sigma - (d * d) / (2 * sigma * sigma)

exponentialLogPdf :: Double -> Double -> Double
exponentialLogPdf rate x
  | x < 0    = -1e300
  | otherwise = log rate - rate * x

gammaLogPdf :: Double -> Double -> Double -> Double
gammaLogPdf k theta x
  | x <= 0   = -1e300
  | otherwise = (k - 1) * log x - x / theta - k * log theta - logGamma k

betaLogPdf :: Double -> Double -> Double -> Double
betaLogPdf a b x
  | x <= 0 || x >= 1 = -1e300
  | otherwise = (a - 1) * log x + (b - 1) * log (1 - x) - logBeta a b

bernoulliLogPmf :: Double -> Bool -> Double
bernoulliLogPmf p True  = log (max 1e-300 p)
bernoulliLogPmf p False = log (max 1e-300 (1 - p))

categoricalLogPmf :: [Double] -> Int -> Double
categoricalLogPmf probs k
  | k < 0 || k >= length probs = -1e300
  | otherwise = log (max 1e-300 (probs !! k))

uniformBoundedLogPdf :: Double -> Double -> Double -> Double
uniformBoundedLogPdf lower upper x
  | x < lower || x > upper = -1e300
  | otherwise = -log (upper - lower)

dirichletLogPdf :: [Double] -> [Double] -> Double
dirichletLogPdf alphas xs
  | length alphas /= length xs = -1e300
  | any (<= 0) xs              = -1e300
  | otherwise =
      let lnB = sum (map logGamma alphas) - logGamma (sum alphas)
      in sum (zipWith (\a x -> (a - 1) * log x) alphas xs) - lnB

logNormalLogPdf :: Double -> Double -> Double -> Double
logNormalLogPdf mu sigma x
  | x <= 0   = -1e300
  | otherwise =
      let lx = log x
          d  = lx - mu
      in -0.5 * log (2 * pi) - log sigma - lx - (d * d) / (2 * sigma * sigma)

binomialLogPmf :: Int -> Double -> Int -> Double
binomialLogPmf n p k
  | k < 0 || k > n  = -1e300
  | p <= 0 && k > 0 = -1e300
  | p >= 1 && k < n = -1e300
  | otherwise =
      logGamma (fromIntegral n + 1)
      - logGamma (fromIntegral k + 1)
      - logGamma (fromIntegral (n - k) + 1)
      + fromIntegral k * log (max 1e-300 p)
      + fromIntegral (n - k) * log (max 1e-300 (1 - p))

poissonLogPmf :: Double -> Integer -> Double
poissonLogPmf lambda k
  | k < 0    = -1e300
  | otherwise = fromIntegral k * log lambda - lambda - logGamma (fromIntegral k + 1)