Skip to content

Commit 47bf072

Browse files
authored
Merge pull request #10 from heltonmc/continued_fraction
Use continued fractions for besseli for medium sized orders 2<nu<100
2 parents 2c7ced1 + 5393cc8 commit 47bf072

File tree

5 files changed

+135
-39
lines changed

5 files changed

+135
-39
lines changed

src/U_polynomials.jl

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,29 @@ function Uk_poly_Kn(p, v, p2, ::Type{Float32})
22
u0 = one(p)
33
u1 = 1 / 24 * evalpoly(p2, (3, -5))
44
u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
5-
u3 = 1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
6-
u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
7-
return evalpoly(-p/v, (u0, u1, u2, u3, u4))
5+
return evalpoly(-p/v, (u0, u1, u2))
86
end
97
function Uk_poly_Kn(p, v, p2, ::Type{T}) where T <: Float64
108
u0 = one(T)
119
u1 = 1 / 24 * evalpoly(p2, (3, -5))
1210
u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
1311
u3 = 1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
1412
u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
15-
u5 = 1 / 6688604160 * evalpoly(p2, (1519035525, -49286948607, 284499769554, -614135872350, 566098157625, -188699385875))
16-
u6 = 1 / 4815794995200 * evalpoly(p2, (2757049477875, -127577298354750, 1050760774457901, -3369032068261860,5104696716244125, -3685299006138750, 1023694168371875))
17-
return evalpoly(-p/v, (u0, u1, u2, u3, u4, u5, u6))
13+
return evalpoly(-p/v, (u0, u1, u2, u3, u4))
1814
end
1915
function Uk_poly_In(p, v, p2, ::Type{T}) where T <: Float64
2016
u0 = one(T)
2117
u1 = -1 / 24 * evalpoly(p2, (3, -5))
2218
u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
2319
u3 = -1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
2420
u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
25-
u5 = -1 / 6688604160 * evalpoly(p2, (1519035525, -49286948607, 284499769554, -614135872350, 566098157625, -188699385875))
26-
u6 = 1 / 4815794995200 * evalpoly(p2, (2757049477875, -127577298354750, 1050760774457901, -3369032068261860,5104696716244125, -3685299006138750, 1023694168371875))
27-
return evalpoly(-p/v, (u0, u1, u2, u3, u4, u5, u6))
21+
return evalpoly(-p/v, (u0, u1, u2, u3, u4))
2822
end
2923
function Uk_poly_In(p, v, p2, ::Type{Float32})
3024
u0 = one(p)
3125
u1 = -1 / 24 * evalpoly(p2, (3, -5))
3226
u2 = 1 / 1152 * evalpoly(p2, (81, -462, 385))
33-
u3 = -1 / 414720 * evalpoly(p2, (30375, -369603, 765765, -425425))
34-
u4 = 1 / 39813120 * evalpoly(p2, (4465125, -94121676, 349922430, -446185740, 185910725))
35-
return evalpoly(-p/v, (u0, u1, u2, u3, u4))
27+
return evalpoly(-p/v, (u0, u1, u2))
3628
end
3729

3830
#=

src/besseli.jl

Lines changed: 110 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -136,17 +136,18 @@ end
136136
Modified Bessel function of the first kind of order nu, ``I_{nu}(x)``.
137137
Nu must be real.
138138
"""
139-
function besseli(nu, x::T) where T <: Union{Float32, Float64, BigFloat}
139+
function besseli(nu, x::T) where T <: Union{Float32, Float64}
140140
nu == 0 && return besseli0(x)
141141
nu == 1 && return besseli1(x)
142-
143-
branch = 60
144-
if nu < branch
145-
inp1 = besseli_large_orders(branch + 1, x)
146-
in = besseli_large_orders(branch, x)
147-
return down_recurrence(x, in, inp1, nu, branch)
142+
143+
if x > maximum((T(30), nu^2 / 4))
144+
return T(besseli_large_argument(nu, x))
145+
elseif x <= 2 * sqrt(nu + 1)
146+
return T(besseli_small_arguments(nu, x))
147+
elseif nu < 100
148+
return T(_besseli_continued_fractions(nu, x))
148149
else
149-
return besseli_large_orders(nu, x)
150+
return T(besseli_large_orders(nu, x))
150151
end
151152
end
152153

@@ -156,20 +157,21 @@ end
156157
Scaled modified Bessel function of the first kind of order nu, ``I_{nu}(x)*e^{-x}``.
157158
Nu must be real.
158159
"""
159-
function besselix(nu, x::T) where T <: Union{Float32, Float64, BigFloat}
160+
function besselix(nu, x::T) where T <: Union{Float32, Float64}
160161
nu == 0 && return besseli0x(x)
161162
nu == 1 && return besseli1x(x)
162163

163-
branch = 60
164-
if nu < branch
165-
inp1 = besseli_large_orders_scaled(branch + 1, x)
166-
in = besseli_large_orders_scaled(branch, x)
167-
return down_recurrence(x, in, inp1, nu, branch)
164+
if x > maximum((T(30), nu^2 / 4))
165+
return T(besseli_large_argument_scaled(nu, x))
166+
elseif x <= 2 * sqrt(nu + 1)
167+
return T(besseli_small_arguments(nu, x)) * exp(-x)
168+
elseif nu < 100
169+
return T(_besseli_continued_fractions_scaled(nu, x))
168170
else
169171
return besseli_large_orders_scaled(nu, x)
170172
end
171173
end
172-
function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64, BigFloat}
174+
function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64}
173175
S = promote_type(T, Float64)
174176
x = S(x)
175177
z = x / v
@@ -179,9 +181,9 @@ function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64, BigFlo
179181
p = inv(zs)
180182
p2 = v^2/fma(max(v,x), max(v,x), min(v,x)^2)
181183

182-
return T(coef*Uk_poly_In(p, v, p2, T))
184+
return coef*Uk_poly_In(p, v, p2, T)
183185
end
184-
function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64, BigFloat}
186+
function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64}
185187
S = promote_type(T, Float64)
186188
x = S(x)
187189
z = x / v
@@ -193,3 +195,94 @@ function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64,
193195

194196
return T(coef*Uk_poly_In(p, v, p2, T))
195197
end
198+
function _besseli_continued_fractions(nu, x::T) where T
199+
S = promote_type(T, Float64)
200+
xx = S(x)
201+
knu, knum1 = up_recurrence(xx, besselk0(xx), besselk1(xx), nu)
202+
# if knu or knum1 is zero then besseli will likely overflow
203+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
204+
return 1 / (x * (knum1 + knu / steed(nu, x)))
205+
end
206+
function _besseli_continued_fractions_scaled(nu, x::T) where T
207+
S = promote_type(T, Float64)
208+
xx = S(x)
209+
knu, knum1 = up_recurrence(xx, besselk0x(xx), besselk1x(xx), nu)
210+
# if knu or knum1 is zero then besseli will likely overflow
211+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
212+
return 1 / (x * (knum1 + knu / steed(nu, x)))
213+
end
214+
function steed(n, x::T) where T
215+
MaxIter = 1000
216+
xinv = inv(x)
217+
xinv2 = 2 * xinv
218+
d = x / (n + n)
219+
a = d
220+
h = a
221+
b = muladd(2, n, 2) * xinv
222+
for _ in 1:MaxIter
223+
d = inv(b + d)
224+
a *= muladd(b, d, -1)
225+
h = h + a
226+
b = b + xinv2
227+
abs(a / h) <= eps(T) && break
228+
end
229+
return h
230+
end
231+
function besseli_large_argument(v, z::T) where T
232+
MaxIter = 1000
233+
a = exp(z / 2)
234+
coef = a / sqrt(2 * T(pi) * z)
235+
fv2 = 4 * v^2
236+
term = one(T)
237+
res = term
238+
s = -term
239+
for i in 1:MaxIter
240+
i = T(i)
241+
offset = muladd(2, i, -1)
242+
term *= T(0.125) * muladd(offset, -offset, fv2) / (z * i)
243+
res = muladd(term, s, res)
244+
s = -s
245+
abs(term) <= eps(T) && break
246+
end
247+
return res * coef * a
248+
end
249+
function besseli_large_argument_scaled(v, z::T) where T
250+
MaxIter = 1000
251+
coef = inv(sqrt(2 * T(pi) * z))
252+
fv2 = 4 * v^2
253+
term = one(T)
254+
res = term
255+
s = -term
256+
for i in 1:MaxIter
257+
i = T(i)
258+
offset = muladd(2, i, -1)
259+
term *= T(0.125) * muladd(offset, -offset, fv2) / (z * i)
260+
res = muladd(term, s, res)
261+
s = -s
262+
abs(term) <= eps(T) && break
263+
end
264+
return res * coef
265+
end
266+
267+
function besseli_small_arguments(v, z::T) where T
268+
S = promote_type(T, Float64)
269+
x = S(z)
270+
if v < 20
271+
coef = (x / 2)^v / factorial(v)
272+
else
273+
vinv = inv(v)
274+
coef = sqrt(vinv / (2 * π)) * MathConstants.e^(v * (log(x / (2 * v)) + 1))
275+
coef *= evalpoly(vinv, (1, -1/12, 1/288, 139/51840, -571/2488320, -163879/209018880, 5246819/75246796800, 534703531/902961561600))
276+
end
277+
278+
MaxIter = 1000
279+
out = one(S)
280+
zz = x^2 / 4
281+
a = one(S)
282+
for k in 1:MaxIter
283+
a *= zz / (k * (k + v))
284+
out += a
285+
a <= eps(T) && break
286+
end
287+
return coef * out
288+
end

src/besselk.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ Modified Bessel function of the second kind of order nu, ``K_{nu}(x)``.
161161
function besselk(nu, x::T) where T <: Union{Float32, Float64, BigFloat}
162162
T == Float32 ? branch = 20 : branch = 50
163163
if nu < branch
164-
return up_recurrence(x, besselk0(x), besselk1(x), nu)
164+
return up_recurrence(x, besselk0(x), besselk1(x), nu)[1]
165165
else
166166
return besselk_large_orders(nu, x)
167167
end
@@ -175,7 +175,7 @@ Scaled modified Bessel function of the second kind of order nu, ``K_{nu}(x)*e^{x
175175
function besselkx(nu::Int, x::T) where T <: Union{Float32, Float64}
176176
T == Float32 ? branch = 20 : branch = 50
177177
if nu < branch
178-
return up_recurrence(x, besselk0x(x), besselk1x(x), nu)
178+
return up_recurrence(x, besselk0x(x), besselk1x(x), nu)[1]
179179
else
180180
return besselk_large_orders_scaled(nu, x)
181181
end

src/recurrence.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# no longer used for besseli but could be used in future for Jn, Yn
2+
#=
13
@inline function down_recurrence(x, in, inp1, nu, branch)
24
# this prevents us from looping through large values of nu when the loop will always return zero
35
(iszero(in) || iszero(inp1)) && return zero(x)
@@ -13,6 +15,7 @@
1315
end
1416
return inm1
1517
end
18+
=#
1619
@inline function up_recurrence(x, k0, k1, nu)
1720
nu == 0 && return k0
1821
nu == 1 && return k1
@@ -28,5 +31,5 @@ end
2831
k0 = k1
2932
k1 = k2
3033
end
31-
return k2
34+
return k2, k0
3235
end

test/besseli_test.jl

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,26 @@ i1x_32 = besseli1x.(Float32.(x32))
6868

6969
# test for besseli
7070
# test small arguments and order
71-
m = 0:1:200; x = 0.1f0:0.5f0:90.0f0
72-
t = [besseli(m, x) for m in m, x in x]
73-
@test t[10] isa Float32
74-
@test t Float32.([SpecialFunctions.besseli(m, x) for m in m, x in x])
71+
m = 0:1:200; x = 0.5f0:0.5f0:90.0f0
72+
@test besseli(10, 1.0f0) isa Float32
73+
@test besseli(2, 80.0f0) isa Float32
74+
@test besseli(112, 80.0f0) isa Float32
75+
76+
for m in m, x in x
77+
@test besseli(m, x) Float32(SpecialFunctions.besseli(m, x))
78+
end
7579

7680
#Float 64
7781
m = 0:1:200; x = 0.1:0.5:150.0
82+
@test besseli(10, 1.0) isa Float64
83+
@test besseli(2, 80.0) isa Float64
84+
@test besseli(112, 80.0) isa Float64
7885
t = [besseli(m, x) for m in m, x in x]
7986

8087
@test t[10] isa Float64
8188
@test t [SpecialFunctions.besseli(m, x) for m in m, x in x]
8289

83-
@test besselix(10, 2.0) SpecialFunctions.besselix(10, 2.0)
84-
@test besselix(100, 14.0) SpecialFunctions.besselix(100, 14.0)
85-
@test besselix(120, 504.0) SpecialFunctions.besselix(120, 504.0)
90+
t = [besselix(m, x) for m in m, x in x]
91+
@test t[10] isa Float64
92+
@test t [SpecialFunctions.besselix(m, x) for m in m, x in x]
93+

0 commit comments

Comments
 (0)