Skip to content

Commit cd97b85

Browse files
committed
add tests, fix F32
1 parent bc4ec0e commit cd97b85

File tree

3 files changed

+116
-54
lines changed

3 files changed

+116
-54
lines changed

src/besseli.jl

Lines changed: 102 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -137,37 +137,51 @@ Modified Bessel function of the first kind of order nu, ``I_{nu}(x)``.
137137
Nu must be real.
138138
"""
139139
function besseli(nu, x::T) where T <: Union{Float32, Float64}
140-
nu == 0 && return besseli0(x)
141-
nu == 1 && return besseli1(x)
140+
iszero(nu) && return besseli0(x)
141+
isone(nu) && return besseli1(x)
142142

143-
if x > maximum((T(30), nu^2 / 6))
144-
return T(besseli_large_argument(nu, x))
145-
elseif nu > 25.0 || x > 35.0
146-
return T(besseli_large_orders(nu, x))
147-
else
148-
return T(besseli_power_series(nu, x))
149-
end
143+
# use large argument expansion if x >> nu
144+
besseli_large_argument_cutoff(nu, x) && return besseli_large_argument(nu, x)
145+
146+
# use uniform debye expansion if x or nu is large
147+
besselik_debye_cutoff(nu, x) && return T(besseli_large_orders(nu, x))
148+
149+
# for rest of values use the power series
150+
return besseli_power_series(nu, x)
150151
end
152+
151153
"""
152154
besselix(nu, x::T) where T <: Union{Float32, Float64}
153155
154156
Scaled modified Bessel function of the first kind of order nu, ``I_{nu}(x)*e^{-x}``.
155157
Nu must be real.
156158
"""
157159
function besselix(nu, x::T) where T <: Union{Float32, Float64}
158-
nu == 0 && return besseli0x(x)
159-
nu == 1 && return besseli1x(x)
160-
161-
if x > maximum((T(30), nu^2 / 4))
162-
return T(besseli_large_argument_scaled(nu, x))
163-
elseif x <= 2 * sqrt(nu + 1)
164-
return T(besseli_power_series(nu, x)) * exp(-x)
165-
elseif nu < 100
166-
return T(_besseli_continued_fractions_scaled(nu, x))
167-
else
168-
return besseli_large_orders_scaled(nu, x)
169-
end
160+
iszero(nu) && return besseli0x(x)
161+
isone(nu) && return besseli1x(x)
162+
163+
# use large argument expansion if x >> nu
164+
besseli_large_argument_cutoff(nu, x) && return besseli_large_argument_scaled(nu, x)
165+
166+
# use uniform debye expansion if x or nu is large
167+
besselik_debye_cutoff(nu, x) && return T(besseli_large_orders_scaled(nu, x))
168+
169+
# for rest of values use the power series
170+
return besseli_power_series(nu, x) * exp(-x)
170171
end
172+
173+
#####
174+
##### Debye's uniform asymptotic for I_{nu}(x)
175+
#####
176+
177+
# Implements the uniform asymptotic expansion https://dlmf.nist.gov/10.41
178+
# In general this is valid when either x or nu is gets large
179+
# see the file src/U_polynomials.jl for more details
180+
"""
181+
besseli_large_orders(nu, x::T)
182+
183+
Debey's uniform asymptotic expansion for large order valid when v-> ∞ or x -> ∞
184+
"""
171185
function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64}
172186
S = promote_type(T, Float64)
173187
x = S(x)
@@ -180,6 +194,7 @@ function besseli_large_orders(v, x::T) where T <: Union{Float32, Float64}
180194

181195
return coef*Uk_poly_In(p, v, p2, T)
182196
end
197+
183198
function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64}
184199
S = promote_type(T, Float64)
185200
x = S(x)
@@ -192,39 +207,18 @@ function besseli_large_orders_scaled(v, x::T) where T <: Union{Float32, Float64}
192207

193208
return T(coef*Uk_poly_In(p, v, p2, T))
194209
end
195-
function _besseli_continued_fractions(nu, x::T) where T
196-
S = promote_type(T, Float64)
197-
xx = S(x)
198-
knum1, knu = besselk_up_recurrence(xx, besselk1(xx), besselk0(xx), 1, nu-1)
199-
# if knu or knum1 is zero then besseli will likely overflow
200-
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
201-
return 1 / (x * (knum1 + knu / steed(nu, x)))
202-
end
203-
function _besseli_continued_fractions_scaled(nu, x::T) where T
204-
S = promote_type(T, Float64)
205-
xx = S(x)
206-
knum1, knu = besselk_up_recurrence(xx, besselk1x(xx), besselk0x(xx), 1, nu-1)
207-
# if knu or knum1 is zero then besseli will likely overflow
208-
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
209-
return 1 / (x * (knum1 + knu / steed(nu, x)))
210-
end
211-
function steed(n, x::T) where T
212-
MaxIter = 1000
213-
xinv = inv(x)
214-
xinv2 = 2 * xinv
215-
d = x / (n + n)
216-
a = d
217-
h = a
218-
b = muladd(2, n, 2) * xinv
219-
for _ in 1:MaxIter
220-
d = inv(b + d)
221-
a *= muladd(b, d, -1)
222-
h = h + a
223-
b = b + xinv2
224-
abs(a / h) <= eps(T) && break
225-
end
226-
return h
227-
end
210+
211+
#####
212+
##### Large argument expansion (x>>nu) for I_{nu}(x)
213+
#####
214+
215+
# Implements the uniform asymptotic expansion https://dlmf.nist.gov/10.40.E1
216+
# In general this is valid when x > nu^2
217+
"""
218+
besseli_large_orders(nu, x::T)
219+
220+
Debey's uniform asymptotic expansion for large order valid when v-> ∞ or x -> ∞
221+
"""
228222
function besseli_large_argument(v, z::T) where T
229223
MaxIter = 1000
230224
a = exp(z / 2)
@@ -260,7 +254,19 @@ function besseli_large_argument_scaled(v, z::T) where T
260254
end
261255
return res * coef
262256
end
257+
besseli_large_argument_cutoff(nu, x) = x > maximum((30.0, nu^2 / 6))
258+
259+
#####
260+
##### Power series for I_{nu}(x)
261+
#####
262+
263+
# Use power series form of I_v(x) which is generally accurate across all values though slower for larger x
264+
# https://dlmf.nist.gov/10.25.E2
265+
"""
266+
besseli_power_series(nu, x::T) where T <: Float64
263267
268+
Computes ``I_{nu}(x)`` using the power series for any value of nu.
269+
"""
264270
function besseli_power_series(v, x::T) where T
265271
MaxIter = 3000
266272
out = zero(T)
@@ -274,3 +280,45 @@ function besseli_power_series(v, x::T) where T
274280
end
275281
return out
276282
end
283+
284+
#=
285+
# the following is a deprecated version of the continued fraction approach
286+
# using K0 and K1 as starting values then forward recurrence up till nu
287+
# then using the wronskian to getting I_{nu}
288+
# in general this method is slow and depends on starting values of K0 and K1
289+
# which is not very flexible for arbitary orders
290+
291+
function _besseli_continued_fractions(nu, x::T) where T
292+
S = promote_type(T, Float64)
293+
xx = S(x)
294+
knum1, knu = besselk_up_recurrence(xx, besselk1(xx), besselk0(xx), 1, nu-1)
295+
# if knu or knum1 is zero then besseli will likely overflow
296+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
297+
return 1 / (x * (knum1 + knu / steed(nu, x)))
298+
end
299+
function _besseli_continued_fractions_scaled(nu, x::T) where T
300+
S = promote_type(T, Float64)
301+
xx = S(x)
302+
knum1, knu = besselk_up_recurrence(xx, besselk1x(xx), besselk0x(xx), 1, nu-1)
303+
# if knu or knum1 is zero then besseli will likely overflow
304+
(iszero(knu) || iszero(knum1)) && return throw(DomainError(x, "Overflow error"))
305+
return 1 / (x * (knum1 + knu / steed(nu, x)))
306+
end
307+
function steed(n, x::T) where T
308+
MaxIter = 1000
309+
xinv = inv(x)
310+
xinv2 = 2 * xinv
311+
d = x / (n + n)
312+
a = d
313+
h = a
314+
b = muladd(2, n, 2) * xinv
315+
for _ in 1:MaxIter
316+
d = inv(b + d)
317+
a *= muladd(b, d, -1)
318+
h = h + a
319+
b = b + xinv2
320+
abs(a / h) <= eps(T) && break
321+
end
322+
return h
323+
end
324+
=#

src/gamma.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ function gamma(x)
88
return _gamma(x)
99
end
1010
end
11+
# only have a Float64 implementations
12+
gamma(x::Float32) = Float32(gamma(Float64(x)))
1113
function _gamma(x)
1214
if x > 11.5
1315
return large_gamma(x)

test/besseli_test.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,16 @@ t = [besseli(m, x) for m in m, x in x]
9090
t = [besselix(m, x) for m in m, x in x]
9191
@test t[10] isa Float64
9292
@test t [SpecialFunctions.besselix(m, x) for m in m, x in x]
93+
94+
## Tests for besselk
95+
96+
## test all numbers and orders for 0<nu<100
97+
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]
98+
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]
99+
for v in nu, xx in x
100+
xx *= v
101+
sf = SpecialFunctions.besseli(v, xx)
102+
@test isapprox(besseli(v, xx), Float64(sf), rtol=2e-13)
103+
end
104+
93105

0 commit comments

Comments
 (0)