Skip to content

Commit ea701f7

Browse files
authored
Merge pull request #39 from JuliaMath/airy
support for Airy functions
2 parents acc0893 + 43b195a commit ea701f7

File tree

10 files changed

+158
-5
lines changed

10 files changed

+158
-5
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ For bug fixes, performance enhancements, or fixes to unexported functions we wil
1414
- add an unexport method (`Bessels.besseljy(nu, x)`) for faster computation of `besselj` and `bessely` (#33)
1515
- add exported methods for Hankel functions `besselh(nu, k, x)`, `hankelh1(nu, x)`, `hankelh2(nu, x)` (#33)
1616
- add exported methods for spherical bessel function `sphericalbesselj(nu, x)`, `sphericalbesselj(nu, x)`, (#38)
17+
- add exported methods for airy functions `airyai(x)`, `airyaiprime(x)`, `airybi(x)`, `airybiprime(x)`, (#39)
1718

1819
### Fixed
1920
- fix cutoff in `bessely` to not return error for integer orders and small arguments (#33)

README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
[![Build Status](https://github.com/heltonmc/Bessels.jl/actions/workflows/CI.yml/badge.svg?branch=master)](https://github.com/heltonmc/Bessels.jl/actions/workflows/CI.yml?query=branch%3Amaster)
33
[![Coverage](https://codecov.io/gh/heltonmc/Bessels.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/heltonmc/Bessels.jl)
44

5-
Numerical routines for computing Bessel and Hankel functions for real arguments. These routines are written in the Julia programming language and are self contained without any external dependencies.
5+
Numerical routines for computing Bessel, Airy, and Hankel functions for real arguments. These routines are written in the Julia programming language and are self contained without any external dependencies.
66

77
The goal of the library is to provide high quality numerical implementations of Bessel functions with high accuracy without comprimising on computational time. In general, we try to match (and often exceed) the accuracy of other open source routines such as those provided by [SpecialFunctions.jl](https://github.com/JuliaMath/SpecialFunctions.jl). There are instances where we don't quite match that desired accuracy (within a digit or two) but in general will provide implementations that are 5-10x faster (see [benchmarks](https://github.com/heltonmc/Bessels.jl/edit/update_readme/README.md#benchmarks)).
88

9-
The library currently supports Bessel functions, modified Bessel functions, Hankel functions and spherical Bessel functions of the first and second kind for positive real arguments and integer and noninteger orders. Negative arguments are also supported only if the return value is real. We plan to support complex arguments in the future. An unexported gamma function is also provided.
9+
The library currently supports Bessel functions, modified Bessel functions, Hankel functions, spherical Bessel functions, and Airy functions of the first and second kind for positive real arguments and integer and noninteger orders. Negative arguments are also supported only if the return value is real. We plan to support complex arguments in the future. An unexported gamma function is also provided.
1010

1111
# Quick start
1212

@@ -170,7 +170,6 @@ We report the relative errors (`abs(1 - Bessels.f(x)/ArbNumerics.f(ArbFloat(x)))
170170
| besseli(92.12, x) | 9e-15 | 7e-14 |
171171
| Bessels.gamma(x) | 1.3e-16 | 5e-16
172172

173-
174173
In general the largest relative errors are observed near the zeros of Bessel functions for `besselj` and `bessely`. Accuracy might also be slightly worse for very large arguments when using `Float64` precision.
175174

176175
# Benchmarks
@@ -189,7 +188,6 @@ We give brief performance comparisons to the implementations provided by [Specia
189188
| besselk(nu, x) | 4x |
190189
| Bessels.gamma(x) | 5x |
191190

192-
193191
Benchmarks were run using Julia Version 1.7.2 on an Apple M1 using Rosetta.
194192

195193
# API
@@ -211,13 +209,16 @@ Benchmarks were run using Julia Version 1.7.2 on an Apple M1 using Rosetta.
211209
- `hankelh2(nu, x)`
212210
- `sphericalbesselj(nu, x)`
213211
- `sphericalbessely(nu, x)`
212+
- `airyai(x)`
213+
- `airyaiprime(x)`
214+
- `airybi(x)`
215+
- `airybiprime(x)`
214216
- `Bessels.gamma(x)`
215217

216218
# Current Development Plans
217219

218220
- Support for higher precision `Double64`, `Float128`
219221
- Support for complex arguments (`x` and `nu`)
220-
- Airy functions
221222
- Support for derivatives with respect to argument and order
222223

223224
# Contributing

src/Bessels.jl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ export besselh
2929
export hankelh1
3030
export hankelh2
3131

32+
export airyai
33+
export airyaiprime
34+
export airybi
35+
export airybiprime
36+
3237
include("besseli.jl")
3338
include("besselj.jl")
3439
include("besselk.jl")
3540
include("bessely.jl")
3641
include("hankel.jl")
42+
include("airy.jl")
3743
include("sphericalbessel.jl")
3844

3945
include("Float128/besseli.jl")

src/airy.jl

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Airy functions
2+
#
3+
# airyai(x), airybi(nu, x)
4+
# airyaiprime(x), airybiprime(nu, x)
5+
#
6+
# A numerical routine to compute the airy functions and their derivatives.
7+
# These routines use their relations to other special functions using https://dlmf.nist.gov/9.6.
8+
# Specifically see (NIST 9.6.E1 - 9.6.E9) for computation from the defined bessel functions.
9+
# For negative arguments these definitions are prone to some cancellation leading to higher errors.
10+
# In the future, these could be replaced with more custom routines as they depend on a single variable.
11+
#
12+
13+
"""
14+
airyai(x)
15+
Airy function of the first kind ``\\operatorname{Ai}(x)``.
16+
"""
17+
airyai(x::Real) = _airyai(float(x))
18+
19+
_airyai(x::Float16) = Float16(_airyai(Float32(x)))
20+
21+
function _airyai(x::T) where T <: Union{Float32, Float64}
22+
isnan(x) && return x
23+
x_abs = abs(x)
24+
z = 2 * x_abs^(T(3)/2) / 3
25+
26+
if x > zero(T)
27+
return isinf(z) ? zero(T) : sqrt(x / 3) * besselk(one(T)/3, z) / π
28+
elseif x < zero(T)
29+
Jv, Yv = besseljy_positive_args(one(T)/3, z)
30+
Jmv = (Jv - sqrt(T(3)) * Yv) / 2
31+
return isinf(z) ? throw(DomainError(x, "airyai(x) is not defined.")) : sqrt(x_abs) * (Jmv + Jv) / 3
32+
elseif iszero(x)
33+
return T(0.3550280538878172)
34+
end
35+
end
36+
37+
"""
38+
airyaiprime(x)
39+
Derivative of the Airy function of the first kind ``\\operatorname{Ai}'(x)``.
40+
"""
41+
airyaiprime(x::Real) = _airyaiprime(float(x))
42+
43+
_airyaiprime(x::Float16) = Float16(_airyaiprime(Float32(x)))
44+
45+
function _airyaiprime(x::T) where T <: Union{Float32, Float64}
46+
isnan(x) && return x
47+
x_abs = abs(x)
48+
z = 2 * x_abs^(T(3)/2) / 3
49+
50+
if x > zero(T)
51+
return isinf(z) ? zero(T) : -x * besselk(T(2)/3, z) / (sqrt(T(3)) * π)
52+
elseif x < zero(T)
53+
Jv, Yv = besseljy_positive_args(T(2)/3, z)
54+
Jmv = -(Jv + sqrt(T(3))*Yv) / 2
55+
return isinf(z) ? throw(DomainError(x, "airyaiprime(x) is not defined.")) : x_abs * (Jv - Jmv) / 3
56+
elseif iszero(x)
57+
return T(-0.2588194037928068)
58+
end
59+
end
60+
61+
"""
62+
airybi(x)
63+
Airy function of the second kind ``\\operatorname{Bi}(x)``.
64+
"""
65+
airybi(x::Real) = _airybi(float(x))
66+
67+
_airybi(x::Float16) = Float16(_airybi(Float32(x)))
68+
69+
function _airybi(x::T) where T <: Union{Float32, Float64}
70+
isnan(x) && return x
71+
x_abs = abs(x)
72+
z = 2 * x_abs^(T(3)/2) / 3
73+
74+
if x > zero(T)
75+
return isinf(z) ? z : sqrt(x / 3) * (besseli(-one(T)/3, z) + besseli(one(T)/3, z))
76+
elseif x < zero(T)
77+
Jv, Yv = besseljy_positive_args(one(T)/3, z)
78+
Jmv = (Jv - sqrt(T(3)) * Yv) / 2
79+
return isinf(z) ? throw(DomainError(x, "airybi(x) is not defined.")) : sqrt(x_abs/3) * (Jmv - Jv)
80+
elseif iszero(x)
81+
return T(0.6149266274460007)
82+
end
83+
end
84+
85+
"""
86+
airybiprime(x)
87+
Derivative of the Airy function of the second kind ``\\operatorname{Bi}'(x)``.
88+
"""
89+
airybiprime(x::Real) = _airybiprime(float(x))
90+
91+
_airybiprime(x::Float16) = Float16(_airybiprime(Float32(x)))
92+
93+
function _airybiprime(x::T) where T <: Union{Float32, Float64}
94+
isnan(x) && return x
95+
x_abs = abs(x)
96+
z = 2 * x_abs^(T(3)/2) / 3
97+
98+
if x > zero(T)
99+
return isinf(z) ? z : x * (besseli(T(2)/3, z) + besseli(-T(2)/3, z)) / sqrt(T(3))
100+
elseif x < zero(T)
101+
Jv, Yv = besseljy_positive_args(T(2)/3, z)
102+
Jmv = -(Jv + sqrt(T(3))*Yv) / 2
103+
return isinf(z) ? throw(DomainError(x, "airybiprime(x) is not defined.")) : x_abs * (Jv + Jmv) / sqrt(T(3))
104+
elseif iszero(x)
105+
return T(0.4482883573538264)
106+
end
107+
end

src/besseli.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ Modified Bessel function of the first kind of order nu, ``I_{nu}(x)`` for positi
207207
function besseli_positive_args(nu, x::T) where T <: Union{Float32, Float64}
208208
iszero(nu) && return besseli0(x)
209209
isone(nu) && return besseli1(x)
210+
isinf(x) && return T(Inf)
210211

211212
# use large argument expansion if x >> nu
212213
besseli_large_argument_cutoff(nu, x) && return besseli_large_argument(nu, x)

src/besselk.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ Modified Bessel function of the second kind of order nu, ``K_{nu}(x)`` valid for
217217
"""
218218
function besselk_positive_args(nu, x::T) where T <: Union{Float32, Float64}
219219
iszero(x) && return T(Inf)
220+
isinf(x) && return zero(T)
220221

221222
# dispatch to avoid uniform expansion when nu = 0
222223
iszero(nu) && return besselk0(x)

test/airy_test.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# test the airy functions
2+
# these are prone to some cancellation as they are indirectly calculated from relations to bessel functions
3+
# this is amplified for negative arguments
4+
# the implementations provided by SpecialFunctions.jl suffer from same inaccuracies
5+
for x in [0.0; 1e-17:0.1:100.0]
6+
@test isapprox(airyai(x), SpecialFunctions.airyai(x), rtol=1e-12)
7+
@test isapprox(airyai(-x), SpecialFunctions.airyai(-x), rtol=1e-9)
8+
9+
@test isapprox(airyaiprime(x), SpecialFunctions.airyaiprime(x), rtol=1e-12)
10+
@test isapprox(airyaiprime(-x), SpecialFunctions.airyaiprime(-x), rtol=1e-9)
11+
12+
@test isapprox(airybi(x), SpecialFunctions.airybi(x), rtol=1e-12)
13+
@test isapprox(airybi(-x), SpecialFunctions.airybi(-x), rtol=5e-8)
14+
15+
@test isapprox(airybiprime(x), SpecialFunctions.airybiprime(x), rtol=1e-12)
16+
@test isapprox(airybiprime(-x), SpecialFunctions.airybiprime(-x), rtol=1e-9)
17+
end
18+
19+
# test Inf
20+
@test iszero(airyai(Inf))
21+
@test iszero(airyaiprime(Inf))
22+
@test isinf(airybi(Inf))
23+
@test isinf(airybiprime(Inf))
24+
25+
# test Float16 types
26+
@test airyai(Float16(1.2)) isa Float16
27+
@test airyaiprime(Float16(1.9)) isa Float16
28+
@test airybi(Float16(1.2)) isa Float16
29+
@test airybiprime(Float16(1.9)) isa Float16

test/besseli_test.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ for v in nu, xx in x
102102
@test isapprox(besseli(v, xx), Float64(sf), rtol=2e-13)
103103
end
104104

105+
# test Inf
106+
@test isinf(besseli(2, Inf))
107+
105108
### tests for negative arguments
106109

107110
(v, x) = 12.0, 3.2

test/besselk_test.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ for v in nu, xx in x
120120
@test isapprox(besselk(v, xx), Float64(sf), rtol=2e-13)
121121
end
122122

123+
# test Inf
124+
@test iszero(besselk(2, Inf))
125+
123126
### tests for negative arguments
124127

125128
(v, x) = 12.0, 3.2

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ import SpecialFunctions
88
@time @testset "bessely" begin include("bessely_test.jl") end
99
@time @testset "hankel" begin include("hankel_test.jl") end
1010
@time @testset "gamma" begin include("gamma_test.jl") end
11+
@time @testset "airy" begin include("airy_test.jl") end
1112
@time @testset "sphericalbessel" begin include("sphericalbessel_test.jl") end

0 commit comments

Comments
 (0)