hgg-core-0.1.0.0: src/Graphics/Hgg/Math/Special.hs
-- |
-- Module : Graphics.Hgg.Math.Special
-- Description : 特殊関数 (log-gamma / 正則化不完全ベータ / ベータ分位点)
-- Copyright : (c) 2026 Aelysce Project (Toshiaki Honda)
-- License : BSD-3-Clause
--
-- backend 非依存の数値特殊関数。 確率プロットの厳密 rank-based CI
-- (順序統計量 U_(i) ~ Beta(i, n-i+1)) などで必要になる:
--
-- * 'logGamma' : ln Γ(x) (Lanczos 近似、 x > 0)
-- * 'regIncompleteBeta' : 正則化不完全ベータ I_x(a,b) (連分数 / Lentz 法)
-- * 'betaQuantile' : I_x(a,b) = q を満たす x (二分法による逆関数)
--
-- アルゴリズムは Numerical Recipes の @gammln@ / @betai@ / @betacf@ に準ずる。
-- core 内に他の特殊関数 (invNormCdf) は 'Graphics.Hgg.Layout.RangeOf' にあるが、
-- ベータ系はサイズが大きいので本 module に分離する。
module Graphics.Hgg.Math.Special
( logGamma
, regIncompleteBeta
, betaQuantile
) where
-- ===========================================================================
-- log-gamma (Lanczos 近似、 g=5 / 6 係数)
-- ===========================================================================
-- | ln Γ(x) (x > 0 を仮定)。 相対誤差 < 2e-10。
-- Numerical Recipes @gammln@ と同一係数 (Lanczos, g=5)。
logGamma :: Double -> Double
logGamma x =
let tmp0 = x + 5.5
tmp = tmp0 - (x + 0.5) * log tmp0
ser = 1.000000000190015
+ sum [ c / (x + fromIntegral j) | (j, c) <- zip [(1 :: Int) ..] cof ]
in -tmp + log (2.5066282746310005 * ser / x)
where
cof = [ 76.18009172947146, -86.50532032941677
, 24.01409824083091, -1.231739572450155
, 0.1208650973866179e-2, -0.5395239384953e-5 ]
-- ===========================================================================
-- 正則化不完全ベータ I_x(a,b)
-- ===========================================================================
-- | 正則化不完全ベータ関数 I_x(a,b) = B(x;a,b) / B(a,b) ∈ [0,1]。
-- a,b > 0、 x ∈ [0,1]。 x < (a+1)/(a+b+2) で連分数を直接、 それ以外は
-- 対称性 I_x(a,b) = 1 - I_{1-x}(b,a) を使い収束を確保する。
regIncompleteBeta :: Double -> Double -> Double -> Double
regIncompleteBeta a b x
| x <= 0 = 0
| x >= 1 = 1
| otherwise =
let bt = exp ( logGamma (a + b) - logGamma a - logGamma b
+ a * log x + b * log (1 - x) )
in if x < (a + 1) / (a + b + 2)
then bt * betacf a b x / a
else 1 - bt * betacf b a (1 - x) / b
-- | I_x(a,b) の連分数展開 (Lentz の修正法)。 NR @betacf@ と同型。
betacf :: Double -> Double -> Double -> Double
betacf a b x = go 1 h0 c0 d0
where
fpmin = 1e-30
eps = 3e-12
maxit = 300 :: Int
qab = a + b
qap = a + 1
qam = a - 1
fix v = if abs v < fpmin then fpmin else v
d0 = 1 / fix (1 - qab * x / qap)
c0 = 1
h0 = d0
go m h c d
| m > maxit = h
| abs (del - 1) < eps = h2
| otherwise = go (m + 1) h2 c2 d2
where
m2 = fromIntegral (2 * m) :: Double
fm = fromIntegral m :: Double
-- 偶数ステップ
aa1 = fm * (b - fm) * x / ((qam + m2) * (a + m2))
d1 = 1 / fix (1 + aa1 * d)
c1 = fix (1 + aa1 / c)
h1 = h * d1 * c1
-- 奇数ステップ
aa2 = negate (a + fm) * (qab + fm) * x / ((a + m2) * (qap + m2))
d2 = 1 / fix (1 + aa2 * d1)
c2 = fix (1 + aa2 / c1)
del = d2 * c2
h2 = h1 * del
-- ===========================================================================
-- ベータ分位点 (I_x(a,b) = q の逆関数)
-- ===========================================================================
-- | I_x(a,b) = q を満たす x ∈ [0,1] を二分法で求める (= Beta(a,b) の q 分位点)。
-- 'regIncompleteBeta' は x について単調増加なので二分法が確実に収束する。
-- 80 反復で区間幅は 2^-80 (≈ 1e-24) になり double 精度では完全収束。
betaQuantile :: Double -> Double -> Double -> Double
betaQuantile q a b
| q <= 0 = 0
| q >= 1 = 1
| otherwise = bisect 0 1 (80 :: Int)
where
bisect lo hi n
| n <= 0 = mid
| regIncompleteBeta a b mid < q = bisect mid hi (n - 1)
| otherwise = bisect lo mid (n - 1)
where mid = (lo + hi) / 2