gamma-0.10.0.0: src/Math/Gamma/Incomplete.hs
{-# LANGUAGE ParallelListComp #-}
module Math.Gamma.Incomplete
( lowerGammaCF, pCF
, lowerGammaHypGeom, lnLowerGammaHypGeom, pHypGeom
, upperGammaCF, lnUpperGammaConvergents, qCF
) where
import {-# SOURCE #-} Math.Gamma
import Math.ContinuedFraction (CF, gcf, modifiedLentzWith, sumPartialProducts)
import Math.Sequence.Converge (converge)
-- |Continued fraction representation of the lower incomplete gamma function.
lowerGammaCF :: (Floating a, Ord a) => a -> a -> Math.ContinuedFraction.CF a
lowerGammaCF s z = gcf 0
[ (p,q)
| p <- pow_x_s_div_exp_x s z
: interleave
[negate spn * z | spn <- iterate (1+) s]
[n * z | n <- iterate (1+) 1]
| q <- iterate (1+) s
]
-- |Lower incomplete gamma function, computed using Kummer's confluent
-- hypergeometric function M(a;b;x). Specifically, this uses the identity:
--
-- gamma(s,x) = x**s * exp (-x) / s * M(1; 1+s; x)
--
-- From Abramowitz & Stegun (6.5.12).
--
-- Recommended for use when x < s+1
lowerGammaHypGeom :: (Eq b, Floating b) => b -> b -> b
lowerGammaHypGeom 0 0 = 0/0
lowerGammaHypGeom s x = x ** s * exp (negate x) / s * m_1_sp1 s x
-- |Natural logarithm of lower gamma function, based on the same identity as
-- 'lowerGammaHypGeom' and evaluated carefully to avoid overflow and underflow.
-- Recommended for use when x < s+1
lnLowerGammaHypGeom :: (Eq a, Floating a) => a -> a -> a
lnLowerGammaHypGeom 0 0 = 0/0
lnLowerGammaHypGeom s x
= log ((signum x)**s * sign_m / signum s)
+ s*log (abs x) - x - log (abs s) + log_m
where
(sign_m, log_m) = log_m_1_sp1 s x
-- |Continued fraction representation of the regularized lower incomplete gamma function.
pCF :: (Gamma a, Ord a, Enum a) => a -> a -> CF a
pCF s x = gcf 0
[ (p,q)
| p <- pow_x_s_div_gamma_s_div_exp_x s x
: interleave
[negate spn * x | spn <- [s..]]
[n * x | n <- [1..]]
| q <- [s..]
]
-- |Regularized lower incomplete gamma function, computed using Kummer's
-- confluent hypergeometric function. Uses same identity as 'lowerGammaHypGeom'.
--
-- Recommended for use when x < s+1
pHypGeom :: (Gamma a, Ord a) => a -> a -> a
pHypGeom 0 0 = 0/0
pHypGeom s x
| s < 0
= signum x ** s * sin (pi*s) / (-pi)
* exp (s * log (abs x) - x + lnGamma (-s)) * m_1_sp1 s x
| s == 0 || x == 0
= 0
| otherwise
= signum x ** s * exp (s * log (abs x) - x - lnGamma (s+1)) * m_1_sp1 s x
-- |Continued fraction representation of the regularized upper incomplete gamma function.
-- Recommended for use when x >= s+1
qCF :: (Gamma a, Ord a, Enum a) => a -> a -> CF a
qCF s x = gcf 0
[ (p,q)
| p <- pow_x_s_div_gamma_s_div_exp_x s x
: zipWith (*) [1..] (iterate (subtract 1) (s-1))
| q <- [n + x - s | n <- [1,3..]]
]
-- |Continued fraction representation of the upper incomplete gamma function.
-- Recommended for use when x >= s+1
upperGammaCF :: (Floating a, Ord a) => a -> a -> CF a
upperGammaCF s z = gcf 0
[ (p,q)
| p <- pow_x_s_div_exp_x s z
: zipWith (*) (iterate (1+) 1) (iterate (subtract 1) (s-1))
| q <- [n + z - s | n <- iterate (2+) 1]
]
-- |Natural logarithms of the convergents of the upper gamma function,
-- evaluated carefully to avoid overflow and underflow.
-- Recommended for use when x >= s+1
lnUpperGammaConvergents :: (Eq a, Floating a) => a -> a -> [a]
lnUpperGammaConvergents s x = map (a -) (concat (eval theCF))
where
eval = map (map evalSign) . modifiedLentzWith signLog addSignLog negateSignLog 1e-30
a = s * log x - x
theCF = gcf (x + 1 - s)
[ (p,q)
| p <- zipWith (*) (iterate (1+) 1) (iterate (subtract 1) (s-1))
| q <- [n + x - s | n <- iterate (2+) 3]
]
---- various utility functions ----
evalSign :: Floating a => (a,a) -> a
evalSign (s,x) = log s + x
signLog :: Floating a => a -> (a,a)
signLog x = (signum x, log (abs x))
addSignLog :: (Num a, Num b) => (a,b) -> (a,b) -> (a,b)
addSignLog (xS,xL) (yS,yL) = (xS*yS, xL+yL)
negateSignLog :: (Num b) => (a,b) -> (a,b)
negateSignLog (s,l) = (s, negate l)
-- |Special case of Kummer's confluent hypergeometric function, used
-- in lower gamma functions.
--
-- m_1_sp1 s z = M(1;s+1;z)
--
m_1_sp1 :: (Eq a, Fractional a) => a -> a -> a
m_1_sp1 s z = converge . scanl (+) 0 . scanl (*) 1 $
[z / x | x <- iterate (1+) (s+1)]
log_m_1_sp1 :: (Eq a, Floating a) => a -> a -> (a,a)
log_m_1_sp1 s z = converge (concat (log_m_1_sp1_convergents s z))
log_m_1_sp1_convergents :: (Eq a, Floating a) => a -> a -> [[(a,a)]]
log_m_1_sp1_convergents s z
= modifiedLentzWith signLog addSignLog negateSignLog 1e-30
$ sumPartialProducts (1:[z / x | x <- iterate (1+) (s+1)])
interleave :: [a] -> [a] -> [a]
interleave [] _ = []
interleave _ [] = []
interleave (x:xs) ys = x:interleave ys xs
-- A common subexpression appearing in both 'pCF' and 'qCF'.
pow_x_s_div_gamma_s_div_exp_x :: (Gamma a, Ord a) => a -> a -> a
pow_x_s_div_gamma_s_div_exp_x s x
| x > 0 = exp (log x * s - x - lnGamma s)
| otherwise = x ** s / (exp x * gamma s)
-- The corresponding subexpression from 'lowerGammaCF' and 'upperGammaCF'
pow_x_s_div_exp_x :: (Floating a, Ord a) => a -> a -> a
pow_x_s_div_exp_x s x
| x > 0 = exp (log x * s - x)
| otherwise = x ** s / exp x