|
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} |
11 | 60 |
|
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))) |
16 | 70 | 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 |
19 | 74 | end
|
20 | 75 | end
|
| 76 | +""" |
| 77 | + besselk0x(x::T) where T <: Union{Float32, Float64} |
21 | 78 |
|
| 79 | +Scaled modified Bessel function of the second kind of order zero, ``K_0(x)*e^{x}``. |
| 80 | +""" |
22 | 81 | 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) |
30 | 88 | 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) |
33 | 90 | end
|
34 | 91 | 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 | +""" |
35 | 97 | 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) |
44 | 107 | 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 |
46 | 111 | end
|
47 | 112 | 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 | +""" |
48 | 118 | 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) |
57 | 128 | 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 |
59 | 173 | end
|
| 174 | + return k2 |
60 | 175 | end
|
0 commit comments