Skip to content

Commit 497b118

Browse files
authored
Merge pull request #29 from heltonmc/besseli_all_orders
Besselj and Besselk for all integer and noninteger postive orders
2 parents 527eb38 + dfbd16d commit 497b118

File tree

6 files changed

+498
-119
lines changed

6 files changed

+498
-119
lines changed

src/U_polynomials.jl

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,10 @@ function Uk_poly_Hankel(p, v, p2, x::T) where T <: Float64
8686
end
8787
Uk_poly_Hankel(p, v, p2, x) = Uk_poly_Jn(p, v, p2, x::BigFloat)
8888

89-
Uk_poly_In(p, v, p2, ::Type{Float32}) = Uk_poly3(p, v, p2)[1]
90-
Uk_poly_In(p, v, p2, ::Type{Float64}) = Uk_poly5(p, v, p2)[1]
91-
Uk_poly_Kn(p, v, p2, ::Type{Float32}) = Uk_poly3(p, v, p2)[2]
92-
Uk_poly_Kn(p, v, p2, ::Type{Float64}) = Uk_poly5(p, v, p2)[2]
89+
Uk_poly_In(p, v, p2, ::Type{Float32}) = Uk_poly5(p, v, p2)[1]
90+
Uk_poly_In(p, v, p2, ::Type{Float64}) = Uk_poly10(p, v, p2)[1]
91+
Uk_poly_Kn(p, v, p2, ::Type{Float32}) = Uk_poly5(p, v, p2)[2]
92+
Uk_poly_Kn(p, v, p2, ::Type{Float64}) = Uk_poly10(p, v, p2)[2]
9393

9494
@inline function split_evalpoly(x, P)
9595
# polynomial P must have an even number of terms
@@ -113,16 +113,6 @@ Uk_poly_Kn(p, v, p2, ::Type{Float64}) = Uk_poly5(p, v, p2)[2]
113113
end
114114
end
115115

116-
function Uk_poly3(p, v, p2)
117-
u0 = 1.0
118-
u1 = evalpoly(p2, (0.125, -0.20833333333333334))
119-
u2 = evalpoly(p2, (0.0703125, -0.4010416666666667, 0.3342013888888889))
120-
u3 = evalpoly(p2, (0.0732421875, -0.8912109375, 1.8464626736111112, -1.0258125964506173))
121-
122-
Poly = (u0, u1, u2, u3)
123-
124-
return split_evalpoly(-p/v, Poly)
125-
end
126116
function Uk_poly5(p, v, p2)
127117
u0 = 1.0
128118
u1 = evalpoly(p2, (0.125, -0.20833333333333334))

src/besseli.jl

Lines changed: 178 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -130,26 +130,91 @@ function besseli1x(x::T) where T <: Union{Float32, Float64}
130130
return z
131131
end
132132

133+
# Modified Bessel functions of the first kind of order nu
134+
# besseli(nu, x)
135+
#
136+
# A numerical routine to compute the modified Bessel function of the first kind I_{ν}(x) [1]
137+
# for real orders and arguments of positive or negative value. The routine is based on several
138+
# publications [2-6] that calculate I_{ν}(x) for positive arguments and orders where
139+
# reflection identities are used to compute negative arguments and orders.
140+
#
141+
# In particular, the reflectance identities for negative noninteger orders I_{−ν}(x) = I_{ν}(x) + 2 / πsin(πν)*Kν(x)
142+
# and for negative integer orders I_{−n}(x) = I_n(x) are used.
143+
# For negative arguments of integer order, In(−x) = (−1)^n In(x) is used and for
144+
# noninteger orders, Iν(−x) = exp(iπν) Iν(x) is used. For negative orders and arguments the previous identities are combined.
145+
#
146+
# The identities are computed by calling the `besseli_positive_args(nu, x)` function which computes I_{ν}(x)
147+
# for positive arguments and orders. For large orders, Debye's uniform asymptotic expansions are used where large arguments (x>>nu)
148+
# a large argument expansion is used. The rest of the values are computed using the power series.
149+
150+
# [1] https://dlmf.nist.gov/10.40.E1
151+
# [2] Amos, Donald E. "Computation of modified Bessel functions and their ratios." Mathematics of computation 28.125 (1974): 239-251.
152+
# [3] Gatto, M. A., and J. B. Seery. "Numerical evaluation of the modified Bessel functions I and K."
153+
# Computers & Mathematics with Applications 7.3 (1981): 203-209.
154+
# [4] Temme, Nico M. "On the numerical evaluation of the modified Bessel function of the third kind."
155+
# Journal of Computational Physics 19.3 (1975): 324-337.
156+
# [5] Amos, DEv. "Algorithm 644: A portable package for Bessel functions of a complex argument and nonnegative order."
157+
# ACM Transactions on Mathematical Software (TOMS) 12.3 (1986): 265-273.
158+
# [6] Segura, Javier, P. Fernández de Córdoba, and Yu L. Ratis. "A code to evaluate modified bessel functions based on thecontinued fraction method."
159+
# Computer physics communications 105.2-3 (1997): 263-272.
160+
#
161+
133162
"""
134-
besseli(nu, x::T) where T <: Union{Float32, Float64}
163+
besseli(x::T) where T <: Union{Float32, Float64}
135164
136-
Modified Bessel function of the first kind of order nu, ``I_{nu}(x)``.
137-
Nu must be real.
165+
Modified Bessel function of the second kind of order nu, ``I_{nu}(x)``.
138166
"""
139-
function besseli(nu, x::T) where T <: Union{Float32, Float64}
140-
nu == 0 && return besseli0(x)
141-
nu == 1 && return besseli1(x)
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))
167+
function besseli(nu::Real, x::T) where T
168+
isinteger(nu) && return besseli(Int(nu), x)
169+
abs_nu = abs(nu)
170+
abs_x = abs(x)
171+
172+
if nu >= 0
173+
if x >= 0
174+
return besseli_positive_args(abs_nu, abs_x)
175+
else
176+
return cispi(abs_nu) * besseli_positive_args(abs_nu, abs_x)
177+
end
149178
else
150-
return T(besseli_large_orders(nu, x))
179+
if x >= 0
180+
return besseli_positive_args(abs_nu, abs_x) + 2 / π * sinpi(abs_nu) * besselk_positive_args(abs_nu, abs_x)
181+
else
182+
Iv = besseli_positive_args(abs_nu, abs_x)
183+
Kv = besselk_positive_args(abs_nu, abs_x)
184+
return cispi(abs_nu) * Iv + 2 / π * sinpi(abs_nu) * (cispi(-abs_nu) * Kv - im * π * Iv)
185+
end
151186
end
152187
end
188+
function besseli(nu::Integer, x::T) where T
189+
abs_nu = abs(nu)
190+
abs_x = abs(x)
191+
sg = iseven(abs_nu) ? 1 : -1
192+
193+
if x >= 0
194+
return besseli_positive_args(abs_nu, abs_x)
195+
else
196+
return sg * besseli_positive_args(abs_nu, abs_x)
197+
end
198+
end
199+
200+
"""
201+
besseli_positive_args(nu, x::T) where T <: Union{Float32, Float64}
202+
203+
Modified Bessel function of the first kind of order nu, ``I_{nu}(x)`` for positive arguments.
204+
"""
205+
function besseli_positive_args(nu, x::T) where T <: Union{Float32, Float64}
206+
iszero(nu) && return besseli0(x)
207+
isone(nu) && return besseli1(x)
208+
209+
# use large argument expansion if x >> nu
210+
besseli_large_argument_cutoff(nu, x) && return besseli_large_argument(nu, x)
211+
212+
# use uniform debye expansion if x or nu is large
213+
besselik_debye_cutoff(nu, x) && return T(besseli_large_orders(nu, x))
214+
215+
# for rest of values use the power series
216+
return besseli_power_series(nu, x)
217+
end
153218

154219
"""
155220
besselix(nu, x::T) where T <: Union{Float32, Float64}
@@ -158,19 +223,31 @@ Scaled modified Bessel function of the first kind of order nu, ``I_{nu}(x)*e^{-x
158223
Nu must be real.
159224
"""
160225
function besselix(nu, x::T) where T <: Union{Float32, Float64}
161-
nu == 0 && return besseli0x(x)
162-
nu == 1 && return besseli1x(x)
163-
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))
170-
else
171-
return besseli_large_orders_scaled(nu, x)
172-
end
226+
iszero(nu) && return besseli0x(x)
227+
isone(nu) && return besseli1x(x)
228+
229+
# use large argument expansion if x >> nu
230+
besseli_large_argument_cutoff(nu, x) && return besseli_large_argument_scaled(nu, x)
231+
232+
# use uniform debye expansion if x or nu is large
233+
besselik_debye_cutoff(nu, x) && return T(besseli_large_orders_scaled(nu, x))
234+
235+
# for rest of values use the power series
236+
return besseli_power_series(nu, x) * exp(-x)
173237
end
238+
239+
#####
240+
##### Debye's uniform asymptotic for I_{nu}(x)
241+
#####
242+
243+
# Implements the uniform asymptotic expansion https://dlmf.nist.gov/10.41
244+
# In general this is valid when either x or nu is gets large
245+
# see the file src/U_polynomials.jl for more details
246+
"""
247+
besseli_large_orders(nu, x::T)
248+
249+
Debey's uniform asymptotic expansion for large order valid when v-> ∞ or x -> ∞
250+
"""
174251
function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64}
175252
S = promote_type(T, Float64)
176253
x = S(x)
@@ -183,6 +260,7 @@ function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64}
183260

184261
return coef*Uk_poly_In(p, v, p2, T)
185262
end
263+
186264
function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64}
187265
S = promote_type(T, Float64)
188266
x = S(x)
@@ -195,39 +273,18 @@ function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64}
195273

196274
return T(coef*Uk_poly_In(p, v, p2, T))
197275
end
198-
function _besseli_continued_fractions(nu, x::T) where T
199-
S = promote_type(T, Float64)
200-
xx = S(x)
201-
knum1, knu = besselk_up_recurrence(xx, besselk1(xx), besselk0(xx), 1, nu-1)
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-
knum1, knu = besselk_up_recurrence(xx, besselk1x(xx), besselk0x(xx), 1, nu-1)
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
276+
277+
#####
278+
##### Large argument expansion (x>>nu) for I_{nu}(x)
279+
#####
280+
281+
# Implements the uniform asymptotic expansion https://dlmf.nist.gov/10.40.E1
282+
# In general this is valid when x > nu^2
283+
"""
284+
besseli_large_orders(nu, x::T)
285+
286+
Debey's uniform asymptotic expansion for large order valid when v-> ∞ or x -> ∞
287+
"""
231288
function besseli_large_argument(v, z::T) where T
232289
MaxIter = 1000
233290
a = exp(z / 2)
@@ -263,26 +320,71 @@ function besseli_large_argument_scaled(v, z::T) where T
263320
end
264321
return res * coef
265322
end
323+
besseli_large_argument_cutoff(nu, x) = x > maximum((30.0, nu^2 / 6))
266324

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))
325+
#####
326+
##### Power series for I_{nu}(x)
327+
#####
328+
329+
# Use power series form of I_v(x) which is generally accurate across all values though slower for larger x
330+
# https://dlmf.nist.gov/10.25.E2
331+
"""
332+
besseli_power_series(nu, x::T) where T <: Float64
333+
334+
Computes ``I_{nu}(x)`` using the power series for any value of nu.
335+
"""
336+
function besseli_power_series(v, x::T) where T
337+
MaxIter = 3000
338+
out = zero(T)
339+
xs = (x/2)^v
340+
a = xs / gamma(v + one(T))
341+
t2 = (x/2)^2
342+
for i in 0:MaxIter
343+
out += a
344+
abs(a) < eps(T) * abs(out) && break
345+
a *= inv((v + i + one(T)) * (i + one(T))) * t2
276346
end
347+
return out
348+
end
349+
350+
#=
351+
# the following is a deprecated version of the continued fraction approach
352+
# using K0 and K1 as starting values then forward recurrence up till nu
353+
# then using the wronskian to getting I_{nu}
354+
# in general this method is slow and depends on starting values of K0 and K1
355+
# which is not very flexible for arbitary orders
277356
357+
function _besseli_continued_fractions(nu, x::T) where T
358+
S = promote_type(T, Float64)
359+
xx = S(x)
360+
knum1, knu = besselk_up_recurrence(xx, besselk1(xx), besselk0(xx), 1, nu-1)
361+
# if knu or knum1 is zero then besseli will likely overflow
362+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
363+
return 1 / (x * (knum1 + knu / steed(nu, x)))
364+
end
365+
function _besseli_continued_fractions_scaled(nu, x::T) where T
366+
S = promote_type(T, Float64)
367+
xx = S(x)
368+
knum1, knu = besselk_up_recurrence(xx, besselk1x(xx), besselk0x(xx), 1, nu-1)
369+
# if knu or knum1 is zero then besseli will likely overflow
370+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
371+
return 1 / (x * (knum1 + knu / steed(nu, x)))
372+
end
373+
function steed(n, x::T) where T
278374
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
375+
xinv = inv(x)
376+
xinv2 = 2 * xinv
377+
d = x / (n + n)
378+
a = d
379+
h = a
380+
b = muladd(2, n, 2) * xinv
381+
for _ in 1:MaxIter
382+
d = inv(b + d)
383+
a *= muladd(b, d, -1)
384+
h = h + a
385+
b = b + xinv2
386+
abs(a / h) <= eps(T) && break
286387
end
287-
return coef * out
388+
return h
288389
end
390+
=#

0 commit comments

Comments
 (0)