Skip to content

Commit bc4ec0e

Browse files
committed
add docs and besselk tests
1 parent b4a8832 commit bc4ec0e

File tree

3 files changed

+104
-70
lines changed

3 files changed

+104
-70
lines changed

src/besseli.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ function besselix(nu, x::T) where T <: Union{Float32, Float64}
161161
if x > maximum((T(30), nu^2 / 4))
162162
return T(besseli_large_argument_scaled(nu, x))
163163
elseif x <= 2 * sqrt(nu + 1)
164-
return T(besseli_small_arguments(nu, x)) * exp(-x)
164+
return T(besseli_power_series(nu, x)) * exp(-x)
165165
elseif nu < 100
166166
return T(_besseli_continued_fractions_scaled(nu, x))
167167
else

src/besselk.jl

Lines changed: 86 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -145,43 +145,63 @@ function besselk1x(x::T) where T <: Union{Float32, Float64}
145145
return muladd(evalpoly(inv(x), P3_k1(T)), inv(evalpoly(inv(x), Q3_k1(T))), Y2_k1(T)) / sqrt(x)
146146
end
147147
end
148-
#=
149-
If besselk0(x) or besselk1(0) is equal to zero
150-
this will underflow and always return zero even if besselk(x, nu)
151-
is larger than the smallest representing floating point value.
152-
In other words, for large values of x and small to moderate values of nu,
153-
this routine will underflow to zero.
154-
For small to medium values of x and large values of nu this will overflow and return Inf.
155-
=#
156-
#=
148+
157149
"""
158150
besselk(x::T) where T <: Union{Float32, Float64}
159151
160152
Modified Bessel function of the second kind of order nu, ``K_{nu}(x)``.
161153
"""
162-
function besselk(nu, x::T) where T <: Union{Float32, Float64, BigFloat}
163-
T == Float32 ? branch = 20 : branch = 50
164-
if nu < branch
165-
return besselk_up_recurrence(x, besselk1(x), besselk0(x), 1, nu)[1]
166-
else
167-
return besselk_large_orders(nu, x)
168-
end
154+
function besselk(nu, x::T) where T <: Union{Float32, Float64}
155+
# check to make sure nu isn't zero
156+
iszero(nu) && return besselk0(x)
157+
158+
# use uniform debye expansion if x or nu is large
159+
besselik_debye_cutoff(nu, x) && return besselk_large_orders(nu, x)
160+
161+
# for integer nu use forward recurrence starting with K_0 and K_1
162+
isinteger(nu) && return besselk_up_recurrence(x, besselk1(x), besselk0(x), 1, nu)[1]
163+
164+
# for small x and nu > x use power series
165+
besselk_power_series_cutoff(nu, x) && return besselk_power_series(nu, x)
166+
167+
# for rest of values use the continued fraction approach
168+
return besselk_continued_fraction(nu, x)
169169
end
170-
=#
171170
"""
172-
besselk(x::T) where T <: Union{Float32, Float64}
171+
besselkx(x::T) where T <: Union{Float32, Float64}
173172
174173
Scaled modified Bessel function of the second kind of order nu, ``K_{nu}(x)*e^{x}``.
175174
"""
176-
function besselkx(nu::Int, x::T) where T <: Union{Float32, Float64}
177-
T == Float32 ? branch = 20 : branch = 50
178-
if nu < branch
179-
return besselk_up_recurrence(x, besselk1x(x), besselk0x(x), 1, nu)[1]
180-
else
181-
return besselk_large_orders_scaled(nu, x)
182-
end
175+
function besselkx(nu, x::T) where T <: Union{Float32, Float64}
176+
# check to make sure nu isn't zero
177+
iszero(nu) && return besselk0x(x)
178+
179+
# use uniform debye expansion if x or nu is large
180+
besselik_debye_cutoff(nu, x) && return besselk_large_orders_scaled(nu, x)
181+
182+
# for integer nu use forward recurrence starting with K_0 and K_1
183+
isinteger(nu) && return besselk_up_recurrence(x, besselk1x(x), besselk0x(x), 1, nu)[1]
184+
185+
# for small x and nu > x use power series
186+
besselk_power_series_cutoff(nu, x) && return besselk_power_series(nu, x) * exp(x)
187+
188+
# for rest of values use the continued fraction approach
189+
return besselk_continued_fraction(nu, x) * exp(x)
183190
end
184-
function besselk_large_orders(v, x::T) where T <: Union{Float32, Float64, BigFloat}
191+
192+
#####
193+
##### Debye's uniform asymptotic for K_{nu}(x)
194+
#####
195+
196+
# Implements the uniform asymptotic expansion https://dlmf.nist.gov/10.41
197+
# In general this is valid when either x or nu is gets large
198+
# see the file src/U_polynomials.jl for more details
199+
"""
200+
besselk_large_orders(nu, x::T)
201+
202+
Debey's uniform asymptotic expansion for large order valid when v-> ∞ or x -> ∞
203+
"""
204+
function besselk_large_orders(v, x::T) where T
185205
S = promote_type(T, Float64)
186206
x = S(x)
187207
z = x / v
@@ -193,7 +213,7 @@ function besselk_large_orders(v, x::T) where T <: Union{Float32, Float64, BigFlo
193213

194214
return T(coef*Uk_poly_Kn(p, v, p2, T))
195215
end
196-
function besselk_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64, BigFloat}
216+
function besselk_large_orders_scaled(v, x::T) where T
197217
S = promote_type(T, Float64)
198218
x = S(x)
199219
z = x / v
@@ -205,42 +225,44 @@ function besselk_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64,
205225

206226
return T(coef*Uk_poly_Kn(p, v, p2, T))
207227
end
228+
besselik_debye_cutoff(nu, x::Float64) = nu > 25.0 || x > 35.0
229+
besselik_debye_cutoff(nu, x::Float32) = nu > 15.0 || x > 20.0
208230

209-
function besselk(nu, x::T) where T <: Union{Float32, Float64, BigFloat}
210-
(isinteger(nu) && nu < 250) && return besselk_up_recurrence(x, besselk1(x), besselk0(x), 1, nu)[1]
211-
212-
if nu > 25.0 || x > 35.0
213-
return besselk_large_orders(nu, x)
214-
elseif x < 2.0
215-
return besselk_power_series(nu, x)
216-
else
217-
return besselk_continued_fraction(nu, x)
218-
end
219-
end
231+
#####
232+
##### Continued fraction with Wronskian for K_{nu}(x)
233+
#####
220234

221-
# could also use the continued fraction for inu/inmu1
222-
# but it seems like adapting the besseli_power series
223-
# to give both nu and nu-1 is faster
235+
# Use the ratio K_{nu+1}/K_{nu} and I_{nu-1}, I_{nu}
236+
# along with the Wronskian (NIST https://dlmf.nist.gov/10.28.E2) to calculate K_{nu}
237+
# Inu and Inum1 are generated from the power series form where K_{nu_1}/K_{nu}
238+
# is calculated with continued fractions.
239+
# The continued fraction K_{nu_1}/K_{nu} method is a slightly modified form
240+
# https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1195726642 by @cgeoga
241+
#
242+
# It is also possible to use continued fraction to calculate inu/inmu1 such as
243+
# inum1 = besseli_power_series(nu-1, x)
244+
# H_inu = steed(nu, x)
245+
# inu = besseli_power_series(nu, x)#inum1 * H_inu
246+
# but it appears to be faster to modify the series to calculate both Inu and Inum1
224247

225-
#inum1 = besseli_power_series(nu-1, x)
226-
#H_inu = steed(nu, x)
227-
#inu = besseli_power_series(nu, x)#inum1 * H_inu
228248
function besselk_continued_fraction(nu, x)
229249
inu, inum1 = besseli_power_series_inu_inum1(nu, x)
230250
H_knu = besselk_ratio_knu_knup1(nu-1, x)
231251
return 1 / (x * (inum1 + inu / H_knu))
232252
end
233253

254+
# a modified version of the I_{nu} power series to compute both I_{nu} and I_{nu-1}
255+
# use this along with the continued fractions for besselk
234256
function besseli_power_series_inu_inum1(v, x::T) where T
235257
MaxIter = 3000
236258
out = zero(T)
237259
out2 = zero(T)
238260
x2 = x / 2
239-
xs = (x2)^v
261+
xs = x2^v
240262
gmx = xs / gamma(v)
241263
a = gmx / v
242264
b = gmx / x2
243-
t2 = x2*x2
265+
t2 = x2 * x2
244266
for i in 0:MaxIter
245267
out += a
246268
out2 += b
@@ -251,20 +273,19 @@ function besseli_power_series_inu_inum1(v, x::T) where T
251273
return out, out2
252274
end
253275

254-
255-
# slightly modified version of https://github.com/heltonmc/Bessels.jl/issues/17#issuecomment-1195726642 from @cgeoga
276+
# computes K_{nu+1}/K_{nu} using continued fractions and the modified Lentz method
277+
# generally slow to converge for small x
256278
function besselk_ratio_knu_knup1(v, x::T) where T
257279
MaxIter = 1000
258-
# do the modified Lentz method:
259280
(hn, Dn, Cn) = (1e-50, zero(v), 1e-50)
260281

261-
jf = one(T)
262-
vv = v*v
282+
jf = one(T)
283+
vv = v * v
263284
for _ in 1:MaxIter
264-
an = (vv - ((2*jf - 1)^2) * T(0.25))
265-
bn = 2 * (x + jf)
266-
Cn = an / Cn + bn
267-
Dn = inv(muladd(an, Dn, bn))
285+
an = (vv - ((2*jf - 1)^2) * T(0.25))
286+
bn = 2 * (x + jf)
287+
Cn = an / Cn + bn
288+
Dn = inv(muladd(an, Dn, bn))
268289
del = Dn * Cn
269290
hn *= del
270291
abs(del - 1) < eps(T) && break
@@ -274,7 +295,6 @@ function besselk_ratio_knu_knup1(v, x::T) where T
274295
return xinv * (v + x + 1/2) + xinv * hn
275296
end
276297

277-
278298
#####
279299
##### Power series for K_{nu}(x)
280300
#####
@@ -315,15 +335,16 @@ function besselk_power_series(v, x::T) where T
315335
_t2 = gam_nv * xd2_v * gam_1mnv
316336
(xd2_pow, fact_k, out) = (one(T), one(T), zero(T))
317337
for k in 0:MaxIter
318-
t1 = xd2_pow * T(0.5)
319-
tmp = muladd(_t1, gam_1mnv, _t2 * gam_1mv)
320-
tmp *= inv(gam_1mv * gam_1mnv * fact_k)
321-
term = t1 * tmp
322-
out += term
323-
abs(term / out) < eps(T) && return out
324-
(gam_1mnv, gam_1mv) = (gam_1mnv*(one(T) + v + k), gam_1mv*(one(T) - v + k))
325-
xd2_pow *= zz
326-
fact_k *= k + one(T)
338+
t1 = xd2_pow * T(0.5)
339+
tmp = muladd(_t1, gam_1mnv, _t2 * gam_1mv)
340+
tmp *= inv(gam_1mv * gam_1mnv * fact_k)
341+
term = t1 * tmp
342+
out += term
343+
abs(term / out) < eps(T) && return out
344+
(gam_1mnv, gam_1mv) = (gam_1mnv*(one(T) + v + k), gam_1mv*(one(T) - v + k))
345+
xd2_pow *= zz
346+
fact_k *= k + one(T)
327347
end
328348
return out
329349
end
350+
besselk_power_series_cutoff(nu, x) = x < 2.0 || nu > 1.6x - 1.0

test/besselk_test.jl

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ k1x_32 = besselk1x.(Float32.(x))
6868
@test besselk(100, 234.0) SpecialFunctions.besselk(100, 234.0)
6969

7070
# test small arguments and order
71-
m = 0:40; x = [1e-6; 1e-4; 1e-3; 1e-2; 0.1; 1.0:2.0:700.0]
72-
@test [besselk(m, x) for m in m, x in x] [SpecialFunctions.besselk(m, x) for m in m, x in x]
71+
m = 0:40; x = [1e-6; 1e-4; 1e-3; 1e-2; 0.1; 1.0:2.0:500.0]
72+
for m in m, x in x
73+
@test besselk(m, x) SpecialFunctions.besselk(m, x)
74+
end
7375

7476
# test medium arguments and order
7577
m = 30:200; x = 5.0:5.0:100.0
@@ -97,8 +99,19 @@ t = [besselk(m, x) for m in m, x in x]
9799
#@test isinf(besselk(250, 5.0))
98100

99101
### Tests for besselkx
100-
@test besselkx(0, 12.0) == besselk0x(12.0)
101-
@test besselkx(1, 89.0) == besselk1x(89.0)
102+
@test besselkx(0, 12.0) besselk0x(12.0)
103+
@test besselkx(1, 89.0) besselk1x(89.0)
102104

103105
@test besselkx(15, 82.123) SpecialFunctions.besselk(15, 82.123)*exp(82.123)
104106
@test besselkx(105, 182.123) SpecialFunctions.besselk(105, 182.123)*exp(182.123)
107+
108+
## Tests for besselk
109+
110+
## test all numbers and orders for 0<nu<100
111+
x = [0.01, 0.05, 0.1, 0.2, 0.4, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.91, 0.92, 0.93, 0.95, 0.96, 0.97, 0.98, 0.99, 0.995, 0.999, 1.0, 1.001, 1.01, 1.05, 1.1, 1.2, 1.4, 1.6, 1.8, 1.9, 2.5, 3.0, 3.5, 4.0]
112+
nu = [0.01,0.1, 0.5, 0.8, 1, 1.23, 2,2.56, 4,5.23, 6,9.2, 10,12.89, 15, 19.1, 20, 25, 30, 33.123, 40, 45, 50, 51.5, 55, 60, 65, 70, 72.34, 75, 80, 82.1, 85, 88.76, 90, 92.334, 95, 99.87,100, 110, 125, 145.123, 150, 160.789]
113+
for v in nu, xx in x
114+
xx *= v
115+
sf = SpecialFunctions.besselk(v, xx)
116+
@test isapprox(besselk(v, xx), Float64(sf), rtol=2e-13)
117+
end

0 commit comments

Comments
 (0)