packages feed

probability-dist-0.1.0.0: src/Probability/Math.hs

module Probability.Math
    ( logFactorial
    , logCombination
    , logGamma
    , logBeta
    ,erf
    ,expm1
    ) where

import Probability.Error (ProbabilityError(..))


logFactorial :: Int -> Either ProbabilityError Double
logFactorial n
    | n < 0  = Left (NegativeValue (fromIntegral n))
    | n < 35 = Right (sum [log (fromIntegral i) | i <- [1 .. n]])
    | otherwise = logGamma (fromIntegral n + 1)


logCombination :: Int -> Int -> Either ProbabilityError Double
logCombination n r
    | n < 0 = Left (NegativeValue (fromIntegral n))
    | r < 0 = Left (NegativeValue (fromIntegral r))
    | r > n = Left (InvalidSuccessCount r)
    | otherwise = do
        lnN  <- logFactorial n
        lnR  <- logFactorial r
        lnNR <- logFactorial (n - r)

        pure (lnN - lnR - lnNR)


logGamma :: Double -> Either ProbabilityError Double
logGamma z
    | z <= 0 = Left (NegativeValue z)
    | otherwise = Right result
  where
    g :: Double
    g = 7.0

    coefficients :: [Double]
    coefficients =
        [ 0.99999999999980993
        , 676.5203681218851
        , -1259.1392167224028
        , 771.32342877765313
        , -176.61502916214059
        , 12.507343278686905
        , -0.13857109526572012
        , 9.9843695780195716e-6
        , 1.5056327351493116e-7
        ]

    x :: Double
    x = z - 1.0

    a :: Double
    a =
        foldl
            (\acc (i, c) ->
                acc + c / (x + fromIntegral i))
            (head coefficients)
            (zip [1 ..] (tail coefficients))

    t :: Double
    t = x + g + 0.5

    result :: Double
    result =
        0.5 * log (2.0 * pi)
            + (x + 0.5) * log t
            - t
            + log a


logBeta :: Double -> Double -> Either ProbabilityError Double
logBeta a b
    | a <= 0 = Left (InvalidShape a)
    | b <= 0 = Left (InvalidShape b)
    | otherwise = do
        logA   <- logGamma a
        logB   <- logGamma b
        logAB  <- logGamma (a + b)

        pure (logA + logB - logAB)

erf
    :: Double
    -> Double
erf x
    | x == 0.0 = 0.0
    | otherwise =
        let sign = if x < 0.0 then -1.0 else 1.0
            ax   = abs x

            p    = 0.3275911
            t    = 1.0 / (1.0 + p * ax)

            poly =
                t * exp
                    ( -ax * ax
                        -1.26551223
                        + t * ( 1.00002368
                        + t * ( 0.37409196
                        + t * ( 0.09678418
                        + t * (-0.18628806
                        + t * (0.27886807
                        + t * (-1.13520398
                        + t * (1.48851587
                        + t * (-0.82215223
                        + t * 0.17087277)))))))))

        in sign * (1.0 - poly)

expm1
    :: Double
    -> Double
expm1 x
    | abs x < 1.0e-5 =
        x
        + x^2 / 2.0
        + x^3 / 6.0
        + x^4 / 24.0
        + x^5 / 120.0
        + x^6 / 720.0

    | otherwise =
        exp x - 1.0