Skip to content

Commit 1b29b0b

Browse files
authored
Merge pull request #354 from devmotion/dw/beta_inc
Improve `beta_inc` and `beta_inc_inv`
2 parents a1df96d + 93c8518 commit 1b29b0b

File tree

2 files changed

+74
-63
lines changed

2 files changed

+74
-63
lines changed

Diff for: src/beta_inc.jl

+61-58
Original file line numberDiff line numberDiff line change
@@ -712,19 +712,24 @@ end
712712
#Wikipedia : https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function
713713

714714
"""
715-
beta_inc(a, b, x[, y])
715+
beta_inc(a, b, x, y=1-x)
716716
717-
Returns a tuple ``(I_{x}(a,b),1.0-I_{x}(a,b))`` where the Regularized Incomplete Beta Function is given by:
717+
Return a tuple ``(I_{x}(a,b), 1-I_{x}(a,b))`` where ``I_{x}(a,b)`` is the regularized
718+
incomplete beta function given by
718719
```math
719720
I_{x}(a,b) = \\frac{1}{B(a,b)} \\int_{0}^{x} t^{a-1}(1-t)^{b-1} dt,
720721
```
721722
where ``B(a,b) = \\Gamma(a)\\Gamma(b)/\\Gamma(a+b)``.
722723
723724
External links: [DLMF](https://dlmf.nist.gov/8.17.1), [Wikipedia](https://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function)
724725
725-
See also: [`beta_inc_inv(a, b, p, q)`](@ref SpecialFunctions.beta_inc_inv)
726+
See also: [`beta_inc_inv`](@ref)
726727
"""
727-
function beta_inc(a::Float64, b::Float64, x::Float64, y::Float64)
728+
function beta_inc(a::Real, b::Real, x::Real, y::Real=1-x)
729+
return _beta_inc(map(float, promote(a, b, x, y))...)
730+
end
731+
732+
function _beta_inc(a::Float64, b::Float64, x::Float64, y::Float64)
728733
p = 0.0
729734
q = 0.0
730735
# lambda = a - (a+b)*x
@@ -878,69 +883,63 @@ function beta_inc(a::Float64, b::Float64, x::Float64, y::Float64)
878883
return ind ? (q, p) : (p, q)
879884
end
880885

881-
beta_inc(a::Real, b::Real, x::Real) = beta_inc(a, b, x, 1 - x)
882-
function beta_inc(a::T, b::T, x::T, y::T) where {T<:Union{Float16, Float32}}
883-
T.(beta_inc(Float64(a), Float64(b), Float64(x), Float64(y)))
886+
function _beta_inc(a::T, b::T, x::T, y::T) where {T<:Union{Float16, Float32}}
887+
p, q = _beta_inc(Float64(a), Float64(b), Float64(x), Float64(y))
888+
T(p), T(q)
884889
end
885-
beta_inc(a::Real, b::Real, x::Real, y::Real) = beta_inc(promote(float(a), float(b), float(x), float(y))...)
886-
beta_inc(a::T, b::T, x::T, y::T) where {T<:AbstractFloat} = throw(MethodError(beta_inc,(a, b, x, y,"")))
890+
887891

888892
#GW Cran, KJ Martin, GE Thomas, Remark AS R19 and Algorithm AS 109: A Remark on Algorithms AS 63: The Incomplete Beta Integral and AS 64: Inverse of the Incomplete Beta Integeral,
889893
#Applied Statistics,
890894
#Volume 26, Number 1, 1977, pages 111-114.
891895

892896
"""
893-
beta_inc_inv(a, b, p, q, lb=logbeta(a,b)[1])
897+
beta_inc_inv(a, b, p, q=1-p)
894898
895-
Computes inverse of incomplete beta function. Given `a`,`b` and ``I_{x}(a,b) = p`` find `x` and return tuple `(x,y)`.
899+
Return a tuple `(x, 1-x)` where `x` satisfies ``I_{x}(a, b) = p``, i.e., `x` is the inverse
900+
of the regularized incomplete beta function ``I_{x}(a, b)``.
896901
897-
See also: [`beta_inc(a,b,x)`](@ref SpecialFunctions.beta_inc)
902+
See also: [`beta_inc`](@ref)
898903
"""
899-
function beta_inc_inv(a::Float64, b::Float64, p::Float64, q::Float64; lb = logbeta(a,b)[1])
900-
fpu = 1e-30
901-
x = p
902-
if p == 0.0
903-
return (0.0, 1.0)
904-
elseif p == 1.0
905-
return (1.0, 0.0)
906-
end
904+
function beta_inc_inv(a::Real, b::Real, p::Real, q::Real=1-p)
905+
return _beta_inc_inv(map(float, promote(a, b, p, q))...)
906+
end
907907

908+
function _beta_inc_inv(a::Float64, b::Float64, p::Float64, q::Float64)
908909
#change tail if necessary
909-
910910
if p > 0.5
911-
pp = q
912-
aa = b
913-
bb = a
914-
indx = true
915-
else
916-
pp = p
917-
aa = a
918-
bb = b
919-
indx = false
911+
y, x = _beta_inc_inv(b, a, q, p)
912+
return x, y
920913
end
921914

922-
#Initial approx
915+
if p == 0.0
916+
return (0.0, 1.0)
917+
end
923918

924-
r = sqrt(-2*log(pp))
925-
pp_approx = r - @horner(r, 2.30753e+00, 0.27061e+00) / @horner(r, 1.0, .99229e+00, .04481e+00)
919+
#Initial approx
920+
x = p
921+
r = sqrt(-2*log(p))
922+
p_approx = r - @horner(r, 2.30753e+00, 0.27061e+00) / @horner(r, 1.0, .99229e+00, .04481e+00)
923+
fpu = 1e-30
924+
lb = logbeta(a, b)
926925

927926
if a > 1.0 && b > 1.0
928-
r = (pp_approx^2 - 3.0)/6.0
929-
s = 1.0/(2*aa - 1.0)
930-
t = 1.0/(2*bb - 1.0)
927+
r = (p_approx^2 - 3.0)/6.0
928+
s = 1.0/(2*a - 1.0)
929+
t = 1.0/(2*b - 1.0)
931930
h = 2.0/(s + t)
932-
w = pp_approx*sqrt(h + r)/h - (t - s)*(r + 5.0/6.0 - 2.0/(3.0*h))
933-
x = aa/(aa + bb*exp(w + w))
931+
w = p_approx*sqrt(h + r)/h - (t - s)*(r + 5.0/6.0 - 2.0/(3.0*h))
932+
x = a/(a + b*exp(w + w))
934933
else
935-
r = 2.0*bb
936-
t = 1.0/(9.0*bb)
937-
t = r*(1.0 - t + pp_approx*sqrt(t))^3
934+
r = 2.0*b
935+
t = 1.0/(9.0*b)
936+
t = r*(1.0 - t + p_approx*sqrt(t))^3
938937
if t <= 0.0
939-
x = -expm1((log((1.0 - pp)*bb) + lb)/bb)
938+
x = -expm1((log((1.0 - p)*b) + lb)/b)
940939
else
941-
t = (4.0*aa + r - 2.0)/t
940+
t = (4.0*a + r - 2.0)/t
942941
if t <= 1.0
943-
x = exp((log(pp*aa) + lb)/aa)
942+
x = exp((log(p*a) + lb)/a)
944943
else
945944
x = 1.0 - 2.0/(t + 1.0)
946945
end
@@ -949,9 +948,9 @@ function beta_inc_inv(a::Float64, b::Float64, p::Float64, q::Float64; lb = logbe
949948

950949
#solve x using modified newton-raphson iteration
951950

952-
r = 1.0 - aa
953-
t = 1.0 - bb
954-
pp_approx_prev = 0.0
951+
r = 1.0 - a
952+
t = 1.0 - b
953+
p_approx_prev = 0.0
955954
sq = 1.0
956955
prev = 1.0
957956

@@ -962,23 +961,23 @@ function beta_inc_inv(a::Float64, b::Float64, p::Float64, q::Float64; lb = logbe
962961
x = .9999
963962
end
964963

965-
iex = max(-5.0/aa^2 - 1.0/pp^0.2 - 13.0, -30.0)
964+
iex = max(-5.0/a^2 - 1.0/p^0.2 - 13.0, -30.0)
966965
acu = 10.0^iex
967966

968967
#iterate
969968
while true
970-
pp_approx = beta_inc(aa, bb, x)[1]
969+
p_approx = beta_inc(a, b, x)[1]
971970
xin = x
972-
pp_approx = (pp_approx - pp)*min(floatmax(), exp(lb + r*log(xin) + t*log1p(-xin)))
971+
p_approx = (p_approx - p)*min(floatmax(), exp(lb + r*log(xin) + t*log1p(-xin)))
973972

974-
if pp_approx * pp_approx_prev <= 0.0
973+
if p_approx * p_approx_prev <= 0.0
975974
prev = max(sq, fpu)
976975
end
977976
g = 1.0
978977

979-
tx = x - g*pp_approx
978+
tx = x - g*p_approx
980979
while true
981-
adj = g*pp_approx
980+
adj = g*p_approx
982981
sq = adj^2
983982
tx = x - adj
984983
if (prev > sq && tx >= 0.0 && tx <= 1.0)
@@ -989,18 +988,22 @@ function beta_inc_inv(a::Float64, b::Float64, p::Float64, q::Float64; lb = logbe
989988

990989
#check if current estimate is acceptable
991990

992-
if prev <= acu || pp_approx^2 <= acu
991+
if prev <= acu || p_approx^2 <= acu
993992
x = tx
994-
return indx ? (1.0 - x, x) : (x, 1.0 - x)
993+
return (x, 1.0 - x)
995994
end
996995

997996
if tx == x
998-
return indx ? (1.0 - x, x) : (x, 1.0 - x)
997+
return (x, 1.0 - x)
999998
end
1000999

10011000
x = tx
1002-
pp_approx_prev = pp_approx
1001+
p_approx_prev = p_approx
10031002
end
10041003
end
10051004

1006-
beta_inc_inv(a::Float64, b::Float64, p::Float64) = beta_inc_inv(a, b, p, 1.0-p)
1005+
function _beta_inc_inv(a::T, b::T, p::T, q::T) where {T<:Union{Float16, Float32}}
1006+
x, y = _beta_inc_inv(Float64(a), Float64(b), Float64(p), Float64(q))
1007+
T(x), T(y)
1008+
end
1009+

Diff for: test/beta_inc.jl

+13-5
Original file line numberDiff line numberDiff line change
@@ -211,12 +211,12 @@
211211
@test beta_inc(1e-20, 0.000001, 0.2)[2] 1.0000013862929421e-14
212212

213213
# test promotions and return types
214-
for T in (Float64, Float32)
215-
a, b = randexp(T, 2)
214+
for T in (Float16, Float32, Float64)
216215
x = rand(T)
217-
@test beta_inc(a, b, x) isa Tuple{T,T}
218-
@test beta_inc(a, b, x, 1 - x) isa Tuple{T,T}
219-
@test beta_inc(a, b, x) == beta_inc(a, b, x, 1 - x)
216+
for a in (1, randexp(T)), b in (1, randexp(T))
217+
@test beta_inc(a, b, x) isa Tuple{T,T}
218+
@test beta_inc(a, b, x, 1 - x) === beta_inc(a, b, x)
219+
end
220220
end
221221
a = randexp()
222222
b = randexp(Float32)
@@ -279,4 +279,12 @@ end
279279
@test f(1.30625000, 11.75620000, 0.21053116418502513) 0.033557
280280
@test f(1.30625000, 11.75620000, 0.18241165418408148) 0.029522
281281
@test f(1000.0, 2.0, 9.0797754e-317) 0.48 # This one is a bit slow (but also hard)
282+
283+
for T in (Float16, Float32, Float64)
284+
p = rand(T)
285+
for a in (1, randexp(T)), b in (1, randexp(T))
286+
@test beta_inc_inv(a, b, p) isa Tuple{T,T}
287+
@test beta_inc_inv(a, b, p, 1 - p) === beta_inc_inv(a, b, p)
288+
end
289+
end
282290
end

0 commit comments

Comments
 (0)