{-# LANGUAGE ParallelListComp #-}
-- |Lanczos' approximation to the gamma function, as described at
-- http:\/\/en.wikipedia.org\/wiki\/Lanczos_approximation
-- (fetched 11 June 2010).
-- 
-- Constants to be supplied by user.  There is a file \"extras/LanczosConstants.hs\"
-- in the source repository that implements a technique by Paul Godfrey for
-- calculating the coefficients.  It is not included in the distribution yet 
-- because it makes use of a linear algebra library I have not yet released 
-- (though I eventually intend to).
module Math.Gamma.Lanczos
    ( gammaLanczos, lnGammaLanczos
    , reflect, reflectC
    , reflectLn, reflectLnC
    ) where

import Data.Complex (Complex, imagPart, realPart)

-- |Compute Lanczos' approximation to the gamma function, using the specified
-- constants.  Valid for Re(x) > 0.5.  Use 'reflect' or 'reflectC' to extend
-- to the whole real line or complex plane, respectively.
{-# INLINE gammaLanczos #-}
gammaLanczos :: Floating a => a -> [a] -> a -> a
gammaLanczos _ [] _ = error "gammaLanczos: empty coefficient list"
gammaLanczos g cs zp1
    = sqrt (2*pi) * x ** (zp1 - 0.5) * exp (negate x) * a cs z
    where
        x = zp1 + (g - 0.5)
        z = zp1 - 1

-- |Compute Lanczos' approximation to the natural logarithm of the gamma
-- function, using the specified constants.  Valid for Re(x) > 0.5.  Use
-- 'reflectLn' or 'reflectLnC' to extend to the whole real line or complex
-- plane, respectively.
{-# INLINE lnGammaLanczos #-}
lnGammaLanczos :: Floating a => a -> [a] -> a -> a
lnGammaLanczos _ [] _ = error "lnGammaLanczos: empty coefficient list"
lnGammaLanczos g cs zp1
    = log (sqrt (2*pi)) + log x * (zp1 - 0.5) - x + log (a cs z)
    where
        x = zp1 + (g - 0.5)
        z = zp1 - 1

{-# INLINE a #-}
a :: Fractional t => [t] -> t -> t
a [] _ = error "Math.Gamma.Lanczos.a: empty coefficient list"
a cs z = head cs + sum [c / (z + k) | c <- tail cs | k <- iterate (1+) 1]

fractionalPart :: RealFloat a => a -> a
fractionalPart x = case properFraction x of
    (i,f) -> let _ = i :: Int in f

-- |Extend an approximation of the gamma function from the domain x > 0.5 to
-- the whole real line.
{-# INLINE reflect #-}
reflect :: (RealFloat a, Ord a) => (a -> a) -> a -> a
reflect gamma z
    | z > 0.5               = gamma z
    | fractionalPart z == 0 = 0
    | otherwise             = pi / (sin (pi * z) * gamma (1-z))

-- |Extend an approximation of the gamma function from the domain Re(x) > 0.5
-- to the whole complex plane.
{-# INLINE reflectC #-}
reflectC :: RealFloat a => (Complex a -> Complex a) -> Complex a -> Complex a
reflectC gamma z
    | realPart z > 0.5  = gamma z
    | imagPart z == 0
    && fractionalPart (realPart z) == 0
                        = 0/0
    | otherwise         = pi / (sin (pi * z) * gamma (1-z))

-- |Extend an approximation of the natural logarithm of the gamma function 
-- from the domain x > 0.5 to the whole real line.
{-# INLINE reflectLn #-}
reflectLn :: (RealFloat a, Ord a) => (a -> a) -> a -> a
reflectLn lnGamma z
    | z > 0.5               = lnGamma z
    | fractionalPart z == 0 = log (0/0)
    | otherwise             = log pi - log (sin (pi * z)) - lnGamma (1-z)

-- |Extend an approximation of the natural logarithm of the gamma function 
-- from the domain Re(x) > 0.5 to the whole complex plane.
{-# INLINE reflectLnC #-}
reflectLnC :: RealFloat a => (Complex a -> Complex a) -> Complex a -> Complex a
reflectLnC lnGamma z
    | realPart z > 0.5  = lnGamma z
    | imagPart z == 0
    && fractionalPart (realPart z) == 0
                        = log (0/0)
    | otherwise = log pi - log (sin (pi * z)) - lnGamma (1-z)