Skip to content

Commit e5dfe6c

Browse files
authored
Merge pull request #7 from heltonmc/besselk
Faster implementations of Besselk0 and Besselk1
2 parents 435417d + ad66a26 commit e5dfe6c

File tree

5 files changed

+287
-112
lines changed

5 files changed

+287
-112
lines changed

src/Bessels.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export besseli0x
1212
export besseli1
1313
export besseli1x
1414

15+
export besselk
16+
export besselkx
1517
export besselk0
1618
export besselk0x
1719
export besselk1
@@ -29,7 +31,6 @@ include("Float128/besselk.jl")
2931
include("Float128/bessely.jl")
3032
include("Float128/constants.jl")
3133

32-
include("chebyshev.jl")
3334
include("math_constants.jl")
3435
#include("parse.jl")
3536
#include("hankel.jl")

src/besselk.jl

Lines changed: 158 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,175 @@
1-
#=
2-
Cephes Math Library Release 2.8: June, 2000
3-
Copyright 1984, 1987, 2000 by Stephen L. Moshier
4-
https://github.com/jeremybarnes/cephes/blob/master/bessel/k0.c
5-
https://github.com/jeremybarnes/cephes/blob/master/bessel/k1.c
6-
=#
7-
function besselk0(x::T) where T <: Union{Float32, Float64}
8-
if x <= zero(x)
9-
return throw(DomainError(x, "NaN result for non-NaN input."))
10-
end
1+
# Modified Bessel functions of the second kind of order zero and one
2+
# besselk0, besselk1
3+
#
4+
# Scaled modified Bessel functions of the second kind of order zero and one
5+
# besselk0x, besselk1x
6+
#
7+
# (Scaled) Modified Bessel functions of the second kind of order nu
8+
# besselk, besselkx
9+
#
10+
#
11+
# Calculation of besselk0 is done in two branches using polynomial approximations [1]
12+
#
13+
# Branch 1: x < 1.0
14+
# besselk0(x) + log(x)*besseli0(x) = P7(x^2)
15+
# besseli0(x) = [x/2]^2 * P6([x/2]^2) + 1
16+
# Branch 2: x >= 1.0
17+
# sqrt(x) * exp(x) * besselk0(x) = P22(1/x) / Q2(1/x)
18+
# where P7, P6, P22, and Q2 are 7, 6, 22, and 2 degree polynomials respectively.
19+
#
20+
#
21+
#
22+
# Calculation of besselk1 is done in two branches using polynomial approximations [2]
23+
#
24+
# Branch 1: x < 1.0
25+
# besselk1(x) - log(x)*besseli1(x) - 1/x = x*P8(x^2)
26+
# besseli1(x) = [x/2]^2 * (1 + 0.5 * (*x/2)^2 + (x/2)^4 * P5([x/2]^2))
27+
# Branch 2: x >= 1.0
28+
# sqrt(x) * exp(x) * besselk1(x) = P22(1/x) / Q2(1/x)
29+
# where P8, P5, P22, and Q2 are 8, 5, 22, and 2 degree polynomials respectively.
30+
#
31+
#
32+
# The polynomial coefficients are taken from boost math library [3].
33+
# Evaluation of the coefficients using Remez.jl is prohibitive due to the increase
34+
# precision required in ArbNumerics.jl.
35+
#
36+
# Horner's scheme is then used to evaluate all polynomials.
37+
#
38+
# Calculation of besselk and besselkx can be done with recursion starting with
39+
# besselk0 and besselk1 and using upward recursion for any value of nu (order).
40+
#
41+
# K_{nu+1} = (2*nu/x)*K_{nu} + K_{nu-1}
42+
#
43+
# When nu is large, a large amount of recurrence is necesary.
44+
# At this point as nu -> Inf it is more efficient to use a uniform expansion.
45+
# The boundary of the expansion depends on the accuracy required.
46+
# For more information see [4]. This approach is not yet implemented, so recurrence
47+
# is used for all values of nu.
48+
#
49+
#
50+
# [1] "Rational Approximations for the Modified Bessel Function of the Second Kind
51+
# - K0(x) for Computations with Double Precision" by Pavel Holoborodko
52+
# [2] "Rational Approximations for the Modified Bessel Function of the Second Kind
53+
# - K1(x) for Computations with Double Precision" by Pavel Holoborodko
54+
# [3] https://github.com/boostorg/math/tree/develop/include/boost/math/special_functions/detail
55+
# [4] "Computation of Bessel Functions of Complex Argument and Large ORder" by Donald E. Amos
56+
# Sandia National Laboratories
57+
#
58+
"""
59+
besselk0(x::T) where T <: Union{Float32, Float64}
1160
12-
if x <= 2
13-
y = muladd(x, x, T(-2))
14-
y = chbevl(y, A_k0(T)) - log(T(.5) * x) * besseli0(x)
15-
return y
61+
Modified Bessel function of the second kind of order zero, ``K_0(x)``.
62+
"""
63+
function besselk0(x::T) where T <: Union{Float32, Float64}
64+
x <= zero(T) && return throw(DomainError(x, "`x` must be nonnegative."))
65+
if x <= one(T)
66+
a = x * x / 4
67+
s = muladd(evalpoly(a, P1_k0(T)), inv(evalpoly(a, Q1_k0(T))), T(Y_k0))
68+
a = muladd(s, a, 1)
69+
return muladd(-a, log(x), evalpoly(x * x, P2_k0(T)))
1670
else
17-
z = T(8) / x - T(2)
18-
return exp(-x) * chbevl(z, B_k0(T)) / sqrt(x)
71+
s = exp(-x / 2)
72+
a = muladd(evalpoly(inv(x), P3_k0(T)), inv(evalpoly(inv(x), Q3_k0(T))), one(T)) * s / sqrt(x)
73+
return a * s
1974
end
2075
end
76+
"""
77+
besselk0x(x::T) where T <: Union{Float32, Float64}
2178
79+
Scaled modified Bessel function of the second kind of order zero, ``K_0(x)*e^{x}``.
80+
"""
2281
function besselk0x(x::T) where T <: Union{Float32, Float64}
23-
if x <= zero(x)
24-
return throw(DomainError(x, "NaN result for non-NaN input."))
25-
end
26-
if x <= 2
27-
y = muladd(x, x, T(-2))
28-
y = chbevl(y, A_k0(T)) - log(T(.5) * x) * besseli0(x)
29-
return y * exp(x)
82+
x <= zero(T) && return throw(DomainError(x, "`x` must be nonnegative."))
83+
if x <= one(T)
84+
a = x * x / 4
85+
s = muladd(evalpoly(a, P1_k0(T)), inv(evalpoly(a, Q1_k0(T))), T(Y_k0))
86+
a = muladd(s, a, 1)
87+
return muladd(-a, log(x), evalpoly(x * x, P2_k0(T))) * exp(x)
3088
else
31-
z = T(8) / x - T(2)
32-
return chbevl(z, B_k0(T)) / sqrt(x)
89+
return muladd(evalpoly(inv(x), P3_k0(T)), inv(evalpoly(inv(x), Q3_k0(T))), one(T)) / sqrt(x)
3390
end
3491
end
92+
"""
93+
besselk1(x::T) where T <: Union{Float32, Float64}
94+
95+
Modified Bessel function of the second kind of order one, ``K_1(x)``.
96+
"""
3597
function besselk1(x::T) where T <: Union{Float32, Float64}
36-
z = T(.5) * x
37-
if x <= zero(x)
38-
return throw(DomainError(x, "NaN result for non-NaN input."))
39-
end
40-
if x <= 2
41-
y = muladd(x, x, T(-2))
42-
y = log(z) * besseli1(x) + chbevl(y, A_k1(T)) / x
43-
return y
98+
x <= zero(T) && return throw(DomainError(x, "`x` must be nonnegative."))
99+
if x <= one(T)
100+
z = x * x
101+
a = z / 4
102+
pq = muladd(evalpoly(a, P1_k1(T)), inv(evalpoly(a, Q1_k1(T))), Y_k1(T))
103+
pq = muladd(pq * a, a, (a / 2 + 1))
104+
a = pq * x / 2
105+
pq = muladd(evalpoly(z, P2_k1(T)) / evalpoly(z, Q2_k1(T)), x, inv(x))
106+
return muladd(a, log(x), pq)
44107
else
45-
return exp(-x) * chbevl(T(8) / x - T(2), B_k1(T)) / sqrt(x)
108+
s = exp(-x / 2)
109+
a = muladd(evalpoly(inv(x), P3_k1(T)), inv(evalpoly(inv(x), Q3_k1(T))), Y2_k1(T)) * s / sqrt(x)
110+
return a * s
46111
end
47112
end
113+
"""
114+
besselk1x(x::T) where T <: Union{Float32, Float64}
115+
116+
Scaled modified Bessel function of the second kind of order one, ``K_1(x)*e^{x}``.
117+
"""
48118
function besselk1x(x::T) where T <: Union{Float32, Float64}
49-
z = T(.5) * x
50-
if x <= zero(x)
51-
return throw(DomainError(x, "NaN result for non-NaN input."))
52-
end
53-
if x <= 2
54-
y = muladd(x, x, T(-2))
55-
y = log(z) * besseli1(x) + chbevl(y, A_k1(T)) / x
56-
return y * exp(x)
119+
x <= zero(T) && return throw(DomainError(x, "`x` must be nonnegative."))
120+
if x <= one(T)
121+
z = x * x
122+
a = z / 4
123+
pq = muladd(evalpoly(a, P1_k1(T)), inv(evalpoly(a, Q1_k1(T))), Y_k1(T))
124+
pq = muladd(pq * a, a, (a / 2 + 1))
125+
a = pq * x / 2
126+
pq = muladd(evalpoly(z, P2_k1(T)) / evalpoly(z, Q2_k1(T)), x, inv(x))
127+
return muladd(a, log(x), pq) * exp(x)
57128
else
58-
return chbevl(T(8) / x - T(2), B_k1(T)) / sqrt(x)
129+
return muladd(evalpoly(inv(x), P3_k1(T)), inv(evalpoly(inv(x), Q3_k1(T))), Y2_k1(T)) / sqrt(x)
130+
end
131+
end
132+
#=
133+
Recurrence is used for all values of nu. If besselk0(x) or besselk1(0) is equal to zero
134+
this will underflow and always return zero even if besselk(x, nu)
135+
is larger than the smallest representing floating point value.
136+
In other words, for large values of x and small to moderate values of nu,
137+
this routine will underflow to zero.
138+
For small to medium values of x and large values of nu this will overflow and return Inf.
139+
140+
In the future, a more efficient algorithm for large nu should be incorporated.
141+
=#
142+
"""
143+
besselk(x::T) where T <: Union{Float32, Float64}
144+
145+
Modified Bessel function of the second kind of order nu, ``K_{nu}(x)``.
146+
"""
147+
function besselk(nu::Int, x::T) where T <: Union{Float32, Float64}
148+
return three_term_recurrence(x, besselk0(x), besselk1(x), nu)
149+
end
150+
"""
151+
besselk(x::T) where T <: Union{Float32, Float64}
152+
153+
Scaled modified Bessel function of the second kind of order nu, ``K_{nu}(x)*e^{x}``.
154+
"""
155+
function besselkx(nu::Int, x::T) where T <: Union{Float32, Float64}
156+
return three_term_recurrence(x, besselk0x(x), besselk1x(x), nu)
157+
end
158+
159+
@inline function three_term_recurrence(x, k0, k1, nu)
160+
nu == 0 && return k0
161+
nu == 1 && return k1
162+
163+
# this prevents us from looping through large values of nu when the loop will always return zero
164+
(iszero(k0) || iszero(k1)) && return zero(x)
165+
166+
k2 = k0
167+
x2 = 2 / x
168+
for n in 1:nu-1
169+
a = x2 * n
170+
k2 = muladd(a, k1, k0)
171+
k0 = k1
172+
k1 = k2
59173
end
174+
return k2
60175
end

src/chebyshev.jl

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)