Skip to content

Commit 1f35f5c

Browse files
authored
Merge pull request #20 from heltonmc/besselj_y_large_arguments
Implementation for besselj of any (positive) real order and argument
2 parents ded7c2d + d430821 commit 1f35f5c

File tree

8 files changed

+593
-10
lines changed

8 files changed

+593
-10
lines changed

Project.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ 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+
69
[compat]
710
julia = "1.5"
811

src/Bessels.jl

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

3+
using SpecialFunctions: loggamma
4+
35
export besselj0
46
export besselj1
57

@@ -37,6 +39,7 @@ include("U_polynomials.jl")
3739
include("recurrence.jl")
3840
include("misc.jl")
3941
include("Polynomials/besselj_polys.jl")
42+
include("asymptotics.jl")
4043
#include("hankel.jl")
4144

4245
end

src/U_polynomials.jl

Lines changed: 105 additions & 0 deletions
Large diffs are not rendered by default.

src/asymptotics.jl

Lines changed: 222 additions & 0 deletions
Large diffs are not rendered by default.

src/besselj.jl

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,186 @@ function besselj1(x::Float32)
150150
return p * s
151151
end
152152
end
153+
"""
154+
besselj(nu, x::T) where T <: Union{Float32, Float64}
155+
156+
Bessel function of the first kind of order nu, ``J_{nu}(x)``.
157+
Nu must be real.
158+
"""
159+
function _besselj(nu, x)
160+
nu == 0 && return besselj0(x)
161+
nu == 1 && return besselj1(x)
162+
163+
x < 4.0 && return besselj_small_arguments_orders(nu, x)
164+
165+
large_arg_cutoff = 1.65*nu
166+
(x > large_arg_cutoff && x > 20.0) && return besselj_large_argument(nu, x)
167+
168+
169+
debye_cutoff = 2.0 + 1.00035*x + (302.681*x)^(1/3)
170+
nu > debye_cutoff && return besselj_debye(nu, x)
171+
172+
if nu >= x
173+
nu_shift = ceil(Int, 5.2 + 1.00033*x + (1427.61*x)^(1/3) - nu)
174+
v = nu + nu_shift
175+
arr = range(v, stop = nu, length = nu_shift + 1)
176+
jnu = besselj_debye(v, x)
177+
jnup1 = besselj_debye(v+1, x)
178+
return besselj_down_recurrence(x, jnu, jnup1, arr)[2]
179+
end
180+
181+
# at this point x > nu and x < nu * 1.65
182+
# in this region forward recurrence is stable
183+
# we must decide if we should do backward recurrence if we are closer to debye accuracy
184+
# or if we should do forward recurrence if we are closer to large argument expansion
185+
debye_cutoff = 5.0 + 1.00033*x + (1427.61*x)^(1/3)
186+
187+
debye_diff = debye_cutoff - nu
188+
large_arg_diff = nu - x / 2.0
189+
190+
if (debye_diff > large_arg_diff && x > 20.0)
191+
nu_shift = ceil(large_arg_diff)
192+
v2 = nu - nu_shift
193+
jnu = besselj_large_argument(v2, x)
194+
jnum1 = besselj_large_argument(v2 - 1, x)
195+
return besselj_up_recurrence(x, jnu, jnum1, v2, nu)[2]
196+
else
197+
nu_shift = ceil(Int, debye_diff)
198+
v = nu + nu_shift
199+
arr = range(v, stop = nu, length = nu_shift + 1)
200+
jnu = besselj_debye(v, x)
201+
jnup1 = besselj_debye(v+1, x)
202+
return besselj_down_recurrence(x, jnu, jnup1, arr)[2]
203+
end
204+
end
205+
206+
# for moderate size arguments of x and v this has relative errors ~9e-15
207+
# for large arguments relative errors ~1e-13
208+
function besselj_large_argument(v, x::T) where T
209+
α, αp = _α_αp_asymptotic(v, x)
210+
b = SQ2OPI(T) / sqrt(αp * x)
211+
212+
S, C = sincos(PIO2(T)*v)
213+
Sα, Cα = sincos(α)
214+
s1 = (C - S) *
215+
s2 = (C + S) *
216+
217+
return SQ2O2(T) * (s1 + s2) * b
218+
end
219+
220+
# generally can only use for x < 4.0
221+
# this needs a better way to sum these as it produces large errors
222+
# only valid in non-oscillatory regime (v>1/2, 0<t<sqrt(v^2 - 0.25))
223+
# power series has premature underflow for large orders
224+
function besselj_small_arguments_orders(v, x::T) where T
225+
v > 60 && return log_besselj_small_arguments_orders(v, x)
226+
227+
MaxIter = 2000
228+
out = zero(T)
229+
a = (x/2)^v / gamma(v + one(T))
230+
t2 = (x/2)^2
231+
for i in 0:MaxIter
232+
out += a
233+
abs(a) < eps(T) * abs(out) && break
234+
a *= -inv((v + i + one(T)) * (i + one(T))) * t2
235+
end
236+
return out
237+
end
238+
239+
# this needs a better way to sum these as it produces large errors
240+
# use when v is large and x is small
241+
# need for bessely
242+
function log_besselj_small_arguments_orders(v, x::T) where T
243+
MaxIter = 2000
244+
out = zero(T)
245+
a = one(T)
246+
x2 = (x/2)^2
247+
for i in 0:MaxIter
248+
out += a
249+
a *= -x2 * inv((i + one(T)) * (v + i + one(T)))
250+
(abs(a) < eps(T) * abs(out)) && break
251+
end
252+
logout = -loggamma(v + 1) + fma(v, log(x/2), log(out))
253+
return exp(logout)
254+
end
255+
256+
# valid when x < v (uniform asymptotic expansions)
257+
function besselj_debye(v, x)
258+
T = eltype(x)
259+
S = promote_type(T, Float64)
260+
x = S(x)
261+
262+
vmx = (v + x) * (v - x)
263+
vs = sqrt(vmx)
264+
n = muladd(v, -log(x / (v + vs)), -vs)
265+
266+
coef = SQ1O2PI(S) * exp(-n) / sqrt(vs)
267+
p = v / vs
268+
p2 = v^2 / vmx
269+
270+
return coef * Uk_poly_Jn(p, v, p2, x, T)
271+
end
272+
273+
# For 0.0 <= x < 171.5
274+
# Mean ULP = 0.55
275+
# Max ULP = 2.4
276+
# Adapted from Cephes Mathematical Library (MIT license https://en.smath.com/view/CephesMathLibrary/license) by Stephen L. Moshier
277+
function gamma(x)
278+
if x > 11.5
279+
return large_gamma(x)
280+
elseif x < 0.0
281+
#p = floor(x)
282+
#isequal(p, abs(x)) && return throw(DomainError(x, "NaN result for non-NaN input."))
283+
# need reflection formula
284+
return throw(DomainError(x, "Negative numbers are currently not implemented"))
285+
elseif x <= 11.5
286+
return small_gamma(x)
287+
elseif isnan(x)
288+
return x
289+
end
290+
end
291+
function large_gamma(x)
292+
isinf(x) && return x
293+
T = Float64
294+
w = inv(x)
295+
s = (
296+
8.333333333333331800504e-2, 3.472222222230075327854e-3, -2.681327161876304418288e-3, -2.294719747873185405699e-4,
297+
7.840334842744753003862e-4, 6.989332260623193171870e-5, -5.950237554056330156018e-4, -2.363848809501759061727e-5,
298+
7.147391378143610789273e-4
299+
)
300+
w = w * evalpoly(w, s) + one(T)
301+
# lose precision on following block
302+
y = exp((x))
303+
# avoid overflow
304+
v = x^(0.5 * x - 0.25)
305+
y = v * (v / y)
306+
307+
return SQ2PI(T) * y * w
308+
end
309+
function small_gamma(x)
310+
T = Float64
311+
P = (
312+
1.000000000000000000009e0, 8.378004301573126728826e-1, 3.629515436640239168939e-1, 1.113062816019361559013e-1,
313+
2.385363243461108252554e-2, 4.092666828394035500949e-3, 4.542931960608009155600e-4, 4.212760487471622013093e-5
314+
)
315+
Q = (
316+
9.999999999999999999908e-1, 4.150160950588455434583e-1, -2.243510905670329164562e-1, -4.633887671244534213831e-2,
317+
2.773706565840072979165e-2, -7.955933682494738320586e-4, -1.237799246653152231188e-3, 2.346584059160635244282e-4,
318+
-1.397148517476170440917e-5
319+
)
320+
321+
z = one(T)
322+
while x >= 3.0
323+
x -= one(T)
324+
z *= x
325+
end
326+
while x < 2.0
327+
z /= x
328+
x += one(T)
329+
end
330+
331+
x -= T(2)
332+
p = evalpoly(x, P)
333+
q = evalpoly(x, Q)
334+
return z * p / q
335+
end

src/math_constants.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
const ONEOSQPI(::Type{BigFloat}) = big"5.6418958354775628694807945156077258584405E-1"
22
const TWOOPI(::Type{BigFloat}) = big"6.3661977236758134307553505349005744813784E-1"
3-
#const SQPIO2(::Type{BigFloat}) = big"1.253314137315500251207882642405522626503493370304969158314961788171146827303924"
4-
#const SQ1O2PI(::Type{BigFloat}) = big"0.3989422804014326779399460599343818684758586311649346576659258296706579258993008"
5-
#const SQ2OPI(::Type{BigFloat}) = big"0.7978845608028653558798921198687637369517172623298693153318516593413158517986017"
6-
#const PIO4(::Type{BigFloat}) = big"0.7853981633974483096156608458198757210492923498437764552437361480769541015715495"
3+
const PIO2(::Type{BigFloat}) = big"1.570796326794896619231321691639751442098584699687552910487472296153908203143099"
4+
const SQPIO2(::Type{BigFloat}) = big"1.253314137315500251207882642405522626503493370304969158314961788171146827303924"
5+
const SQ1O2PI(::Type{BigFloat}) = big"0.3989422804014326779399460599343818684758586311649346576659258296706579258993008"
6+
const SQ2OPI(::Type{BigFloat}) = big"0.7978845608028653558798921198687637369517172623298693153318516593413158517986017"
7+
const PIO4(::Type{BigFloat}) = big"0.7853981633974483096156608458198757210492923498437764552437361480769541015715495"
8+
const SQ2O2(::Type{BigFloat}) = big"0.707106781186547524400844362104849039284835937688474036588339868995366239231051"
79

810
const PIO4(::Type{Float64}) = .78539816339744830962
11+
const PIO2(::Type{Float64}) = 1.5707963267948966
912
const THPIO4(::Type{Float64}) = 2.35619449019234492885
1013
const SQ2OPI(::Type{Float64}) = .79788456080286535588
1114
const TWOOPI(::Type{Float64}) = 0.6366197723675814
1215
const SQPIO2(::Type{Float64}) = 1.25331413731550025
1316
const SQ1O2PI(::Type{Float64}) = 0.3989422804014327
14-
17+
const SQ2PI(::Type{Float64}) = 2.5066282746310007
18+
const SQ2O2(::Type{Float64}) = 0.7071067811865476
1519

1620
const PIO4(::Type{Float32}) = 0.78539816339744830962f0
1721
const TWOOPI(::Type{Float32}) = 0.636619772367581343075535f0
1822
const THPIO4(::Type{Float32}) = 2.35619449019234492885f0
23+
const SQ2O2(::Type{Float32}) = 0.7071067811865476f0

src/recurrence.jl

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,31 @@ end
3232
k1 = k2
3333
end
3434
return k2, k0
35-
end
35+
end
36+
37+
@inline function besselj_up_recurrence(x, jnu, jnum1, nu_start, nu_end)
38+
jnup1 = jnum1
39+
x2 = 2 / x
40+
for n in nu_start:nu_end
41+
a = x2 * n
42+
jnup1 = muladd(a, jnu, -jnum1)
43+
jnum1 = jnu
44+
jnu = jnup1
45+
end
46+
return jnup1, jnum1
47+
end
48+
@inline function besselj_down_recurrence(x, jnu, jnup1, arr)
49+
# arr is the index of Bessel orders arr = nu_start:-1:nu_end
50+
# but needs special care if nu is a decimal
51+
# use v = nu + nu_shift
52+
# arr = range(v, stop = nu, length = nu_shift + 1)
53+
jnum1 = jnup1
54+
x2 = 2 / x
55+
for n in arr
56+
a = x2 * n
57+
jnum1 = muladd(a, jnu, -jnup1)
58+
jnup1 = jnu
59+
jnu = jnum1
60+
end
61+
return jnum1, jnup1
62+
end

test/besselj_test.jl

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,43 @@ j1_32 = besselj1.(Float32.(x))
7171
@test besselj1(-80.0) SpecialFunctions.besselj1(-80.0)
7272

7373
## Tests for besselj
74-
# note this is not complete just a simple test
75-
# this needs work and removing for now
7674

77-
#@test besselj(3, 1.0) ≈ SpecialFunctions.besselj(3, 1.0)
78-
#@test besselj(-5, 6.1) ≈ SpecialFunctions.besselj(-5, 6.1)
75+
#=
76+
Notes
77+
- power series shows larger error when nu is large (146) and x is small (1.46)
78+
- asymptotic expansion shows larger error when nu is large or x is large
79+
=#
80+
81+
## test all numbers and orders for 0<nu<100
82+
x = [0.05, 0.1, 0.2, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.92, 0.95, 0.97, 0.99, 1.0, 1.01, 1.05]
83+
nu = [2, 4, 6, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80, 90, 100]
84+
for v in nu, xx in x
85+
xx *= v
86+
sf = SpecialFunctions.besselj(BigFloat(v), BigFloat(xx))
87+
@test isapprox(Bessels._besselj(v, xx), Float64(sf), rtol=5e-14)
88+
end
89+
90+
# test half orders (SpecialFunctions does not give big float precision)
91+
# The SpecialFunctions implementation is only accurate to about 1e-11 - 1e-13
92+
93+
x = [0.05, 0.1, 0.2, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.92, 0.95, 0.97, 0.99, 1.0, 1.01, 1.05, 1.08, 1.1, 1.2, 1.4, 1.5, 1.6, 1.8, 2.0, 2.5, 3.0]
94+
nu = [0.1, 0.4567, 0.8123, 1.5, 2.5, 4.1234, 6.8, 12.3, 18.9, 28.2345, 38.1235, 51.23, 72.23435, 80.5, 98.5, 104.2]
95+
for v in nu, xx in x
96+
xx *= v
97+
@test isapprox(Bessels._besselj(v, xx), SpecialFunctions.besselj(v, xx), rtol=1e-12)
98+
end
99+
100+
## test large orders
101+
nu = [150, 165.2, 200.0, 300.0, 500.0, 1000.0, 5000.2, 10000.0, 50000.0]
102+
x = [0.2, 0.4, 0.5, 0.6, 0.7, 0.75, 0.8, 0.85, 0.9, 0.92,0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99,0.995, 0.999, 1.0, 1.01, 1.05, 1.08, 1.1, 1.2]
103+
for v in nu, xx in x
104+
xx *= v
105+
@test isapprox(Bessels._besselj(v, xx), SpecialFunctions.besselj(v, xx), rtol=5e-11)
106+
end
107+
108+
## test large arguments
109+
@test isapprox(Bessels._besselj(10.0, 150.0), SpecialFunctions.besselj(10.0, 150.0), rtol=1e-12)
110+
111+
# test BigFloat for single point
112+
@test isapprox(Bessels._besselj(big"2000", big"1500.0"), SpecialFunctions.besselj(big"2000", big"1500"), rtol=5e-20)
113+
@test isapprox(Bessels._besselj(big"20", big"1500.0"), SpecialFunctions.besselj(big"20", big"1500"), rtol=5e-20)

0 commit comments

Comments
 (0)