Skip to content

Commit 9dd63bf

Browse files
Sumegh-gitsimonbyrne
authored andcommitted
Add CDF of noncentral beta and F distribution (#172)
1 parent f3d5bd7 commit 9dd63bf

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed

src/SpecialFunctions.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ export
5151
beta_inc,
5252
beta_inc_inv,
5353
gamma_inc_inv,
54+
ncbeta,
55+
ncF,
5456
hankelh1,
5557
hankelh1x,
5658
hankelh2,
@@ -66,6 +68,7 @@ include("ellip.jl")
6668
include("sincosint.jl")
6769
include("gamma.jl")
6870
include("gamma_inc.jl")
71+
include("betanc.jl")
6972
include("beta_inc.jl")
7073
include("deprecated.jl")
7174

src/betanc.jl

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
const errmax = 1e-15
2+
3+
#Compute tail of noncentral Beta distribution
4+
#Russell Lenth, Algorithm AS 226: Computing Noncentral Beta Probabilities,
5+
#Applied Statistics,Volume 36, Number 2, 1987, pages 241-244
6+
7+
"""
8+
ncbeta_tail(x,a,b,lambda)
9+
10+
Compute tail of the noncentral beta distribution.
11+
Uses the recursive relation
12+
```math
13+
I_{x}(a,b+1;0) = I_{x}(a,b;0) - \\Gamma(a+b)/\\Gamma(a+1)\\Gamma(b)x^{a}(1-x)^{b}
14+
```
15+
and ``\\Gamma(a+1) = a\\Gamma(a)`` given in https://dlmf.nist.gov/8.17.21.
16+
"""
17+
function ncbeta_tail(a::Float64, b::Float64, lambda::Float64, x::Float64)
18+
if x <= 0.0
19+
return 0.0
20+
elseif x >= 1.0
21+
return 1.0
22+
end
23+
24+
c = 0.5*lambda
25+
#Init series
26+
27+
beta = logabsbeta(a,b)[1]
28+
temp = beta_inc(a,b,x)[1]
29+
gx = (beta_integrand(a,b,x,1.0-x))/a
30+
q = exp(-c)
31+
xj = 0.0
32+
ax = q*temp
33+
sumq = 1.0 - q
34+
ans = ax
35+
36+
while true
37+
xj += 1.0
38+
temp -= gx
39+
gx *= x*(a+b+xj-1.0)/(a+xj)
40+
q *= c/xj
41+
sumq -= q
42+
ax = temp*q
43+
ans += ax
44+
45+
#Check convergence
46+
errbd = abs((temp-gx)*sumq)
47+
if xj > 1000 || errbd < 1e-10
48+
break
49+
end
50+
end
51+
return ans
52+
end
53+
54+
"""
55+
ncbeta_poisson(a,b,lambda,x)
56+
57+
Compute CDF of noncentral beta if lambda >= 54 using:
58+
First ``\\lambda/2`` is calculated and the Poisson term is calculated using ``P(j-1)=j/\\lambda P(j)`` and ``P(j+1) = \\lambda/(j+1) P(j)``.
59+
Then backward recurrences are used until either the Poisson weights fall below `errmax` or `iterlo` is reached.
60+
```math
61+
I_{x}(a+j-1,b) = I_{x}(a+j,b) + \\Gamma(a+b+j-1)/\\Gamma(a+j)\\Gamma(b)x^{a+j-1}(1-x)^{b}
62+
```
63+
Then forward recurrences are used until error bound falls below `errmax`.
64+
```math
65+
I_{x}(a+j+1,b) = I_{x}(a+j,b) - \\Gamma(a+b+j)/\\Gamma(a+j)\\Gamma(b)x^{a+j}(1-x)^{b}
66+
```
67+
"""
68+
function ncbeta_poisson(a::Float64, b::Float64, lambda::Float64, x::Float64)
69+
c = 0.5*lambda
70+
xj = 0.0
71+
m = round(Int, c)
72+
mr = float(m)
73+
iterlo = m - trunc(Int, 5.0*sqrt(mr))
74+
iterhi = m + trunc(Int, 5.0*sqrt(mr))
75+
t = -c + mr*log(c) - logabsgamma(mr + 1.0)[1]
76+
q = exp(t)
77+
r = q
78+
psum = q
79+
80+
beta = logabsbeta(a+mr,b)[1]
81+
gx = beta_integrand(a+mr,b,x,1.0-x)/(a + mr)
82+
fx = gx
83+
temp = beta_inc(a+mr,b,x)[1]
84+
ftemp = temp
85+
xj += 1.0
86+
87+
sm = q*temp
88+
iter1 = m
89+
90+
#Iterations start from M and goes downwards
91+
92+
for iter1 = m:-1:iterlo
93+
if q < errmax
94+
break
95+
end
96+
97+
q *= iter1/c
98+
xj += 1.0
99+
gx *= (a + iter1)/(x*(a+b+iter1-1.0))
100+
iter1 -= 1
101+
temp += gx
102+
psum += q
103+
sm += q*temp
104+
end
105+
106+
t0 = logabsgamma(a+b)[1] - logabsgamma(a+1.0)[1] - logabsgamma(b)[1]
107+
s0 = a*log(x) + b*log(1.0-x)
108+
109+
s = 0.0
110+
for j = 0:iter1-1
111+
s += exp(t0+s0+j*log(x))
112+
t1 = log(a+b+j) - log(a+j+1.0) + t0
113+
t0 = t1
114+
end
115+
#Compute first part of error bound
116+
117+
errbd = (gamma_inc(float(iter1),c,0)[2])*(temp+s)
118+
q = r
119+
temp = ftemp
120+
gx = fx
121+
iter2 = m
122+
#Iterations for the higher part
123+
124+
for iter2 = m:iterhi-1
125+
ebd = errbd + (1.0 - psum)*temp
126+
if ebd < errmax
127+
return sm
128+
end
129+
iter2 += 1
130+
xj += 1.0
131+
q *= c/iter2
132+
psum += q
133+
temp -= gx
134+
gx *= x*(a+b+iter2-1.0)/(a+iter2)
135+
sm += q*temp
136+
end
137+
return sm
138+
end
139+
140+
#R Chattamvelli, R Shanmugam, Algorithm AS 310: Computing the Non-central Beta Distribution Function,
141+
#Applied Statistics, Volume 46, Number 1, 1997, pages 146-156
142+
143+
"""
144+
ncbeta(a,b,lambda,x)
145+
146+
Compute the CDF of the noncentral beta distribution given by
147+
```math
148+
I_{x}(a,b;\\lambda ) = \\sum_{j=0}^{\\infty}q(\\lambda/2,j)I_{x}(a+j,b;0)
149+
```
150+
For lambda < 54 : algorithm suggested by Lenth(1987) in ncbeta_tail(a,b,lambda,x).
151+
Else for lambda >= 54 : modification in Chattamvelli(1997) in ncbeta_poisson(a,b,lambda,x) by using both forward and backward recurrences.
152+
"""
153+
function ncbeta(a::Float64, b::Float64, lambda::Float64, x::Float64)
154+
ans = x
155+
if x <= 0.0
156+
return 0.0
157+
elseif x >= 1.0
158+
return 1.0
159+
end
160+
161+
if lambda < 54.0
162+
return ncbeta_tail(a,b,lambda,x)
163+
else
164+
return ncbeta_poisson(a,b,lambda,x)
165+
end
166+
end
167+
168+
"""
169+
ncF(x,v1,v2,lambda)
170+
171+
Compute CDF of noncentral F distribution given by:
172+
```math
173+
F(x, v1, v2; lambda) = I_{v1*x/(v1*x + v2)}(v1/2, v2/2; \\lambda)
174+
```
175+
where ``I_{x}(a,b; lambda)`` is the noncentral beta function computed above.
176+
177+
Wikipedia: https://en.wikipedia.org/wiki/Noncentral_F-distribution
178+
"""
179+
function ncF(x::Float64, v1::Float64, v2::Float64, lambda::Float64)
180+
return ncbeta(v1/2, v2/2, lambda, (v1*x)/(v1*x + v2))
181+
end
182+
183+
function ncbeta(a::T,b::T,lambda::T,x::T) where {T<:Union{Float16,Float32}}
184+
T.(ncbeta(Float64(a),Float64(b),Float64(lambda),Float64(x)))
185+
end
186+
187+
function ncF(x::T,v1::T,v2::T,lambda::T) where {T<:Union{Float16,Float32}}
188+
T.(ncF(Float64(x),Float64(v1),Float64(v2),Float64(lambda)))
189+
end
190+
191+
ncbeta(a::Real,b::Real,lambda::Real,x::Real) = ncbeta(promote(float(a),float(b),float(lambda),float(x))...)
192+
ncbeta(a::T,b::T,lambda::T,x::T) where {T<:AbstractFloat} = throw(MethodError(ncbeta,(a,b,lambda,x,"")))
193+
ncF(x::Real,v1::Real,v2::Real,lambda::Real) = ncF(promote(float(x),float(v1),float(v2),float(lambda))...)
194+
ncF(x::T,v1::T,v2::T,lambda::T) where {T<:AbstractFloat} = throw(MethodError(ncF,(x,v1,v2,lambda,"")))
195+

test/runtests.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,33 @@ end
214214
@test f(1.30625000,11.75620000,0.21053116418502513) 0.033557
215215
@test f(1.30625000,11.75620000,0.18241165418408148) 0.029522
216216
end
217+
@testset "noncentral beta cdf" begin
218+
@test ncbeta(Float32(5.0),Float32(5.0),Float32(54.0),Float32(0.8640)) Float32(0.456302)
219+
@test ncbeta(Float32(5.0),Float32(5.0),Float32(140.0),Float32(0.90)) Float32(0.104134)
220+
@test ncbeta(Float32(5.0),Float32(5.0),Float32(170.0),Float32(0.9560)) Float32(0.602235)
221+
@test ncbeta(Float32(10.0),Float32(10.0),Float32(140.0),Float32(0.90)) Float32(0.600811)
222+
@test ncbeta(Float32(10.0),Float32(10.0),Float32(250.0),Float32(0.9)) Float32(0.902850E-01)
223+
@test ncbeta(Float32(20.0),Float32(20.0),Float32(54.0),Float32(0.8787)) Float32(0.999865)
224+
@test ncbeta(Float32(20.0),Float32(20.0),Float32(140.0),Float32(0.9)) Float32(0.992600)
225+
@test ncbeta(Float32(20.0),Float32(20.0),Float32(250.0),Float32(0.922)) Float32(0.964111)
226+
@test ncbeta(Float32(10.0),Float32(20.0),Float32(150.0),Float32(0.868)) Float32(0.937663)
227+
@test ncbeta(Float32(10.0),Float32(10.0),Float32(120.0),Float32(0.9)) Float32(0.730682)
228+
@test ncbeta(Float32(15.0),Float32(5.0),Float32(80.0),Float32(0.88)) Float32(0.160426)
229+
@test ncbeta(Float32(20.0),Float32(10.0),Float32(110.0),Float32(0.85)) Float32(0.186749)
230+
@test ncbeta(Float32(20.0),Float32(30.0),Float32(65.0),Float32(0.66)) Float32(0.655939)
231+
@test ncbeta(Float32(20.0),Float32(50.0),Float32(130.0),Float32(0.720)) Float32(0.979688)
232+
@test ncbeta(Float32(30.0),Float32(20.0),Float32(80.0),Float32(0.720)) Float32(0.116239)
233+
@test ncbeta(Float32(30.0),Float32(40.0),Float32(130.0),Float32(0.8)) Float32(0.993043)
234+
@test ncbeta(Float32(10.0),Float32(5.0),Float32(20.0),Float32(0.644)) Float32(0.506899E-01)
235+
@test ncbeta(Float32(10.0),Float32(10.0),Float32(54.0),Float32(0.7)) Float32(0.103096)
236+
@test ncbeta(Float32(10.0),Float32(30.0),Float32(80.0),Float32(0.78)) Float32(0.997842)
237+
@test ncbeta(Float32(15.0),Float32(20.0),Float32(120.0),Float32(0.76)) Float32(0.255555)
238+
@test ncbeta(Float32(10.0),Float32(5.0),Float32(55.0),Float32(0.795)) Float32(0.668307E-01)
239+
@test ncbeta(Float32(12.0),Float32(17.0),Float32(64.0),Float32(0.56)) Float32(0.113601E-01)
240+
@test ncbeta(Float32(30.0),Float32(30.0),Float32(140.0),Float32(0.80)) Float32(0.781337)
241+
@test ncbeta(Float32(35.0),Float32(30.0),Float32(20.0),Float32(0.670)) Float32(0.886713)
242+
@test ncF(Float32(2.0), Float32(3.0), Float32(4.0), Float32(5.0)) Float32(0.3761448105)
243+
end
217244
@testset "elliptic integrals" begin
218245
#Computed using Wolframalpha EllipticK and EllipticE functions.
219246
@test ellipk(0) 1.570796326794896619231322 rtol=2*eps()

0 commit comments

Comments
 (0)