Skip to content

Commit 527eb38

Browse files
authored
Merge pull request #28 from heltonmc/besselj_docs
Clean up besselj(nu,x) and add docs
2 parents a09b94d + 4fd03bd commit 527eb38

File tree

7 files changed

+142
-64
lines changed

7 files changed

+142
-64
lines changed

Project.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@ uuid = "0e736298-9ec6-45e8-9647-e4fc86a2fe38"
33
authors = ["Michael Helton <[email protected]> and contributors"]
44
version = "0.1.0"
55

6-
[deps]
7-
SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b"
8-
96
[compat]
10-
julia = "1.5"
7+
julia = "1.6"
118

129
[extras]
1310
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/Bessels.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
module Bessels
22

3-
using SpecialFunctions: loggamma
4-
53
export besselj0
64
export besselj1
75
export besselj

src/U_polynomials.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1-
besseljy_debye_cutoff(nu, x) = nu > 2.0 + 1.00035*x + Base.Math._approx_cbrt(Float64(302.681)*x) && x > 15
2-
# valid when x < v (uniform asymptotic expansions)
1+
# Debye asymptotic expansions
2+
# `besseljy_debye`, `hankel_debye`
3+
#
4+
# This file contains the debye asymptotic asymptotic expansions for large orders.
5+
# These routines can be used to calculate `besselj`, `bessely`, `besselk`, `besseli`
6+
# and the Hankel functions. These are uniform expansions for `besselk` and `besseli` for large orders.
7+
# The forms used for `besselj` and `bessely` as well as the Hankel functions follow the notation by Matviyenko [1]
8+
# but also see similar forms provided by NIST 10.19. All of these routines use a core routine for calculating
9+
# the U-polynomials (NIST 10.41.E9) where the coefficients are dependent on each function. The forms for the modified
10+
# Bessel functions `besselk` and `besseli` follow NIST 10.41 [3].
11+
#
12+
# [1] Matviyenko, Gregory. "On the evaluation of Bessel functions."
13+
# Applied and Computational Harmonic Analysis 1.1 (1993): 116-135.
14+
# [2] http://dlmf.nist.gov/10.41.E9
15+
# [3] https://dlmf.nist.gov/10.41
16+
317
"""
418
besseljy_debye(nu, x::T)
519
@@ -26,9 +40,9 @@ function besseljy_debye(v, x)
2640

2741
return coef_Jn * Uk_Jn, coef_Yn * Uk_Yn
2842
end
29-
hankel_debye_cutoff(nu, x) = nu < 0.2 + x + Base.Math._approx_cbrt(-411*x)
3043

31-
# valid when v < x (uniform asymptotic expansions)
44+
besseljy_debye_cutoff(nu, x) = nu > 2.0 + 1.00035*x + Base.Math._approx_cbrt(Float64(302.681)*x) && x > 15
45+
3246
"""
3347
hankel_debye(nu, x::T)
3448
@@ -54,6 +68,8 @@ function hankel_debye(v, x::T) where T
5468
return coef_Yn * Uk_Yn
5569
end
5670

71+
hankel_debye_cutoff(nu, x) = nu < 0.2 + x + Base.Math._approx_cbrt(-411*x)
72+
5773
function Uk_poly_Jn(p, v, p2, x::T) where T <: Float64
5874
if v > 5.0 + 1.00033*x + Base.Math._approx_cbrt(1427.61*x)
5975
return Uk_poly10(p, v, p2)

src/asymptotics.jl

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
# Asymptotics expansions for x > nu
2+
# `besseljy_large_argument`
3+
#
4+
# This file contains the asymptotic asymptotic expansions for large arguments
5+
# which is accurate in the fresnel regime |x| > nu following the derivations given by Heitman [1].
6+
# These routines can be used to calculate `besselj` and `bessely` using phase functions.
7+
#
8+
# [1] Heitman, Z., Bremer, J., Rokhlin, V., & Vioreanu, B. (2015). On the asymptotics of Bessel functions in the Fresnel regime.
9+
# Applied and Computational Harmonic Analysis, 39(2), 347-356.
10+
111
#besseljy_large_argument_min(::Type{Float32}) = 15.0f0
212
besseljy_large_argument_min(::Type{Float64}) = 20.0
313
besseljy_large_argument_min(::Type{T}) where T <: AbstractFloat = 40.0
@@ -6,7 +16,12 @@ besseljy_large_argument_min(::Type{T}) where T <: AbstractFloat = 40.0
616
besseljy_large_argument_cutoff(v, x::Float64) = (x > 1.65*v && x > besseljy_large_argument_min(Float64))
717
besseljy_large_argument_cutoff(v, x::T) where T = (x > 4*v && x > besseljy_large_argument_min(T))
818

19+
"""
20+
besseljy_large_argument(nu, x::T)
921
22+
Asymptotic expansions for large arguments valid when x > 1.6*nu and x > 20.0.
23+
Returns both (besselj(nu, x), bessely(nu, x)).
24+
"""
1025
function besseljy_large_argument(v, x::T) where T
1126
# gives both (besselj, bessely) for x > 1.6*v
1227
α, αp = _α_αp_asymptotic(v, x)
@@ -119,7 +134,6 @@ function _α_αp_poly_10(v, x::T) where T
119134
αp = evalpoly(xinv, (s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10))
120135
α = x * evalpoly(xinv, (s0, -s1, -s2/3, -s3/5, -s4/7, -s5/9, -s6/11, -s7/13, -s8/15, -s9/17, -s10/19))
121136
return α, αp
122-
return α, αp
123137
end
124138
#=
125139
function _α_αp_poly_15(v, x::T) where T

src/besselj.jl

Lines changed: 96 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,47 @@ function besselj1(x::Float32)
151151
end
152152
end
153153

154+
# Bessel functions of the first kind of order nu
155+
# besselj(nu, x)
156+
#
157+
# A numerical routine to compute the Bessel function of the first kind J_{ν}(x) [1]
158+
# for real orders and arguments of positive or negative value. The routine is based on several
159+
# publications [2, 3, 4, 5] that calculate J_{ν}(x) for positive arguments and orders where
160+
# reflection identities are used to compute negative arguments and orders.
161+
#
162+
# In particular, the reflectance identities for negative integer orders J_{-n}(x) = (-1)^n * J_{n}(x) (Eq. 9.1.5; [6])
163+
# and for negative noninteger orders J_{−ν}(x) = cos(πν) * J_{ν}(x) - sin(πν) * Y_{ν}(x) are used.
164+
# For negative arguments of integer order, J_{n}(-x) = (-1)^n * J_{n}(x) is used and for
165+
# noninteger orders, J_{ν}(−x) = exp(im*π*ν) * J_{ν}(x) is used. For negative orders and arguments the previous identities are combined.
166+
#
167+
# The identities are computed by calling the `besselj_positive_args(nu, x)` function which computes J_{ν}(x)
168+
# for positive arguments and orders. When x < ν and ν being reasonably large, the debye asymptotic expansion (Eq. 32; [3]) is used `besseljy_debye(nu, x)`.
169+
# For large arguments x >> ν, the phase functions are used (Eq. 14 [4]) with `besseljy_large_argument(nu, x)`.
170+
# When x > ν and x being reasonably large, the Hankel function is calculated from the debye expansion (Eq. 29; [3]) with `hankel_debye(nu, x)`
171+
# and J_{n}(x) is calculated from the real part of the Hankel function. These expansions are not uniform so are not strictly used when the above inequalities are true, therefore, cutoffs
172+
# were determined depending on the desired accuracy. For large arguments x >> ν, the phase functions are used (Eq. 15 [4]) with `besseljy_large_argument(nu, x)`.
173+
# For small arguments, J_{ν}(x) is calculated from the power series (`bessely_power_series(nu, x`)
174+
#
175+
# For values where the expansions for large arguments and orders are not valid, backward recurrence is employed after shifting the order up
176+
# to where `besseljy_debye` is accurate then using downward recurrence. In general, the routine will be the slowest when ν ≈ x as all methods struggle at this point.
177+
#
178+
# [1] http://dlmf.nist.gov/10.2.E2
179+
# [2] Bremer, James. "An algorithm for the rapid numerical evaluation of Bessel functions of real orders and arguments."
180+
# Advances in Computational Mathematics 45.1 (2019): 173-211.
181+
# [3] Matviyenko, Gregory. "On the evaluation of Bessel functions."
182+
# Applied and Computational Harmonic Analysis 1.1 (1993): 116-135.
183+
# [4] Heitman, Z., Bremer, J., Rokhlin, V., & Vioreanu, B. (2015). On the asymptotics of Bessel functions in the Fresnel regime.
184+
# Applied and Computational Harmonic Analysis, 39(2), 347-356.
185+
# [5] Ratis, Yu L., and P. Fernández de Córdoba. "A code to calculate (high order) Bessel functions based on the continued fractions method."
186+
# Computer physics communications 76.3 (1993): 381-388.
187+
# [6] Abramowitz, Milton, and Irene A. Stegun, eds. Handbook of mathematical functions with formulas, graphs, and mathematical tables.
188+
# Vol. 55. US Government printing office, 1964.
189+
#
190+
191+
#####
192+
##### Generic routine for `besselj`
193+
#####
194+
154195
function besselj(nu::Real, x::T) where T
155196
isinteger(nu) && return besselj(Int(nu), x)
156197
abs_nu = abs(nu)
@@ -186,59 +227,65 @@ function besselj(nu::Integer, x::T) where T
186227
end
187228
end
188229

230+
"""
231+
besselj_positive_args(nu, x::T) where T <: Float64
232+
233+
Bessel function of the first kind of order nu, ``J_{nu}(x)``.
234+
nu and x must be real and nu and x must be positive.
235+
236+
No checks on arguments are performed and should only be called if certain nu, x >= 0.
237+
"""
189238
function besselj_positive_args(nu::Real, x::T) where T
190239
nu == 0 && return besselj0(x)
191240
nu == 1 && return besselj1(x)
192241

193-
x < 4.0 && return besselj_small_arguments_orders(nu, x)
242+
# x < ~nu branch see src/U_polynomials.jl
243+
besseljy_debye_cutoff(nu, x) && return besseljy_debye(nu, x)[1]
194244

195-
large_arg_cutoff = 1.65*nu
196-
(x > large_arg_cutoff && x > 20.0) && return besseljy_large_argument(nu, x)[1]
245+
# large argument branch see src/asymptotics.jl
246+
besseljy_large_argument_cutoff(nu, x) && return besseljy_large_argument(nu, x)[1]
197247

248+
# x > ~nu branch see src/U_polynomials.jl on computing Hankel function
249+
hankel_debye_cutoff(nu, x) && return real(hankel_debye(nu, x))
198250

199-
debye_cutoff = 2.0 + 1.00035*x + Base.Math._approx_cbrt(302.681*Float64(x))
200-
nu > debye_cutoff && return besseljy_debye(nu, x)[1]
201-
202-
if nu >= x
203-
nu_shift = ceil(Int, debye_cutoff - nu)
204-
v = nu + nu_shift
205-
jnu = besseljy_debye(v, x)[1]
206-
jnup1 = besseljy_debye(v+1, x)[1]
207-
return besselj_down_recurrence(x, jnu, jnup1, v, nu)[1]
208-
end
251+
# use power series for small x and for when nu > x
252+
besselj_series_cutoff(nu, x) && return besselj_power_series(nu, x)
209253

210-
# at this point x > nu and x < nu * 1.65
211-
# in this region forward recurrence is stable
212-
# we must decide if we should do backward recurrence if we are closer to debye accuracy
213-
# or if we should do forward recurrence if we are closer to large argument expansion
214-
debye_cutoff = 5.0 + 1.00033*x + Base.Math._approx_cbrt(1427.61*Float64(x))
215-
216-
debye_diff = debye_cutoff - nu
217-
large_arg_diff = nu - x / 2.0
218-
219-
if (debye_diff > large_arg_diff && x > 20.0)
220-
nu_shift = ceil(Int, large_arg_diff)
221-
v2 = nu - nu_shift
222-
jnu = besseljy_large_argument(v2, x)[1]
223-
jnum1 = besseljy_large_argument(v2 - 1, x)[1]
224-
return besselj_up_recurrence(x, jnu, jnum1, v2, nu)[1]
225-
else
226-
nu_shift = ceil(Int, debye_diff)
227-
v = nu + nu_shift
228-
jnu = besseljy_debye(v, x)[1]
229-
jnup1 = besseljy_debye(v+1, x)[1]
230-
return besselj_down_recurrence(x, jnu, jnup1, v, nu)[1]
231-
end
254+
# At this point we must fill the region when x ≈ v with recurrence
255+
# Backward recurrence is always stable and forward recurrence is stable when x > nu
256+
# However, we only use backward recurrence by shifting the order up and using `besseljy_debye` to generate start values
257+
# Both `besseljy_debye` and `hankel_debye` get more accurate for large orders,
258+
# however `besseljy_debye` is slightly more efficient (no complex variables) and we need no branches if only consider one direction.
259+
# On the other hand, shifting the order down avoids any concern about underflow for large orders
260+
# Shifting the order too high while keeping x fixed could result in numerical underflow
261+
# Therefore we need to shift up only until the `besseljy_debye` is accurate and need to test that no underflow occurs
262+
# Shifting the order up decreases the value substantially for high orders and results in a stable forward recurrence
263+
# as the values rapidly increase
264+
265+
debye_cutoff = 2.0 + 1.00035*x + Base.Math._approx_cbrt(302.681*Float64(x))
266+
nu_shift = ceil(Int, debye_cutoff - nu)
267+
v = nu + nu_shift
268+
jnu = besseljy_debye(v, x)[1]
269+
jnup1 = besseljy_debye(v+1, x)[1]
270+
return besselj_down_recurrence(x, jnu, jnup1, v, nu)[1]
232271
end
233272

234-
# generally can only use for x < 4.0
235-
# this needs a better way to sum these as it produces large errors
273+
#####
274+
##### Power series for J_{nu}(x)
275+
#####
276+
277+
# accurate for x < 7.0 or nu > 2+ 0.109x + 0.062x^2 for Float64
278+
# accurate for x < 20.0 or nu > 14.4 - 0.455x + 0.027x^2 for Float32 (when using F64 precision)
236279
# only valid in non-oscillatory regime (v>1/2, 0<t<sqrt(v^2 - 0.25))
237-
# power series has premature underflow for large orders
238-
function besselj_small_arguments_orders(v, x::T) where T
239-
v > 60 && return log_besselj_small_arguments_orders(v, x)
280+
# power series has premature underflow for large orders though use besseljy_debye for large orders
281+
"""
282+
besselj_power_series(nu, x::T) where T <: Float64
240283
241-
MaxIter = 2000
284+
Computes ``J_{nu}(x)`` using the power series.
285+
In general, this is most accurate for small arguments and when nu > x.
286+
"""
287+
function besselj_power_series(v, x::T) where T
288+
MaxIter = 3000
242289
out = zero(T)
243290
a = (x/2)^v / gamma(v + one(T))
244291
t2 = (x/2)^2
@@ -250,11 +297,16 @@ function besselj_small_arguments_orders(v, x::T) where T
250297
return out
251298
end
252299

300+
besselj_series_cutoff(v, x::Float64) = (x < 7.0) || v > (2 + x*(0.109 + 0.062x))
301+
besselj_series_cutoff(v, x::Float32) = (x < 20.0) || v > (14.4 + x*(-0.455 + 0.027x))
302+
303+
#=
253304
# this needs a better way to sum these as it produces large errors
254305
# use when v is large and x is small
255-
# need for bessely
306+
# though when v is large we should use the debye expansion instead
307+
# also do not have a julia implementation of loggamma so will not use for now
256308
function log_besselj_small_arguments_orders(v, x::T) where T
257-
MaxIter = 2000
309+
MaxIter = 3000
258310
out = zero(T)
259311
a = one(T)
260312
x2 = (x/2)^2
@@ -266,3 +318,4 @@ function log_besselj_small_arguments_orders(v, x::T) where T
266318
logout = -loggamma(v + 1) + fma(v, log(x/2), log(out))
267319
return exp(logout)
268320
end
321+
=#

src/bessely.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ end
190190
# reflection identities are used to compute negative arguments and orders.
191191
#
192192
# In particular, the reflectance identities for negative integer orders Y_{-n}(x) = (-1)^n * Y_{n}(x) (Eq. 9.1.5; [6])
193-
# and for negative noninteger orders Y_{−ν}(x) = cos(πν) * Y_{ν}(x) + sin(πν) * J_{ν}(x) (Eq. 9.1.2; [6]) are used.
193+
# and for negative noninteger orders Y_{−ν}(x) = cos(πν) * Y_{ν}(x) + sin(πν) * J_{ν}(x) are used.
194194
# For negative arguments of integer order, Y_{n}(-x) = (-1)^n * Y_{n}(x) + (-1)^n * 2im * J_{n}(x) is used and for
195195
# noninteger orders, Y_{ν}(−x) = exp(−im*π*ν) * Y_{ν}(x) + 2im * cos(πν) * J_{ν}(x) is used.
196196
# For negative orders and arguments the previous identities are combined.
@@ -277,7 +277,7 @@ end
277277
"""
278278
bessely_positive_args(nu, x::T) where T <: Float64
279279
280-
Bessel function of the first kind of order nu, ``Y_{nu}(x)``.
280+
Bessel function of the second kind of order nu, ``Y_{nu}(x)``.
281281
nu and x must be real and nu and x must be positive.
282282
283283
No checks on arguments are performed and should only be called if certain nu, x >= 0.

test/besselj_test.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,23 +109,23 @@ end
109109
@test isapprox(besselj(10.0, 150.0), SpecialFunctions.besselj(10.0, 150.0), rtol=1e-12)
110110

111111
# test BigFloat for single point
112-
@test isapprox(besselj(big"2000", big"1500.0"), SpecialFunctions.besselj(big"2000", big"1500"), rtol=5e-20)
113-
@test isapprox(besselj(big"20", big"1500.0"), SpecialFunctions.besselj(big"20", big"1500"), rtol=5e-20)
112+
@test isapprox(Bessels.besseljy_debye(big"2000", big"1500.0")[1], SpecialFunctions.besselj(big"2000", big"1500"), rtol=5e-20)
113+
@test isapprox(Bessels.besseljy_large_argument(big"20", big"1500.0")[1], SpecialFunctions.besselj(big"20", big"1500"), rtol=5e-20)
114114

115115

116116
# need to test accuracy of negative orders and negative arguments and all combinations within
117117
# SpecialFunctions.jl doesn't provide these so will hand check against hard values
118118
# values taken from https://keisan.casio.com/exec/system/1180573474 which match mathematica
119119
# need to also account for different branches when nu isa integer
120120
nu = -9.102; x = -12.48
121-
@test isapprox(besselj(nu, x), 0.09842356047575545808128 -0.03266486217437818486161im, rtol=1e-14)
121+
@test isapprox(besselj(nu, x), 0.09842356047575545808128 -0.03266486217437818486161im, rtol=8e-14)
122122
nu = -5.0; x = -5.1
123-
@test isapprox(besselj(nu, x), 0.2740038554704588327387, rtol=1e-14)
123+
@test isapprox(besselj(nu, x), 0.2740038554704588327387, rtol=8e-14)
124124
nu = -7.3; x = 19.1
125-
@test isapprox(besselj(nu, x), 0.1848055978553359009813, rtol=1e-14)
125+
@test isapprox(besselj(nu, x), 0.1848055978553359009813, rtol=8e-14)
126126
nu = -14.0; x = 21.3
127-
@test isapprox(besselj(nu, x), -0.1962844898264965120021, rtol=1e-14)
127+
@test isapprox(besselj(nu, x), -0.1962844898264965120021, rtol=8e-14)
128128
nu = 13.0; x = -8.5
129-
@test isapprox(besselj(nu, x), -0.006128034621313167000171, rtol=1e-14)
129+
@test isapprox(besselj(nu, x), -0.006128034621313167000171, rtol=8e-14)
130130
nu = 17.45; x = -16.23
131-
@test isapprox(besselj(nu, x), -0.01607335977752705869797 -0.1014831996412783806255im, rtol=1e-14)
131+
@test isapprox(besselj(nu, x), -0.01607335977752705869797 -0.1014831996412783806255im, rtol=8e-14)

0 commit comments

Comments
 (0)