Skip to content

Commit 6d1f74d

Browse files
committed
add docs
1 parent 6731e44 commit 6d1f74d

File tree

1 file changed

+124
-44
lines changed

1 file changed

+124
-44
lines changed

src/bessely.jl

Lines changed: 124 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -181,28 +181,74 @@ function _bessely1_compute(x::Float32)
181181
end
182182
end
183183

184-
"""
185-
bessely(nu, x::T) where T <: Union{Float32, Float64}
184+
# Bessel functions of the second kind of order nu
185+
# bessely(nu, x)
186+
#
187+
# A numerical routine to compute the Bessel function of the second kind Y_{ν}(x) [1]
188+
# for real orders and arguments of positive or negative value. The routine is based on several
189+
# publications [2, 3, 4, 5] that calculate Y_{ν}(x) for positive arguments and orders where
190+
# reflection identities are used to compute negative arguments and orders.
191+
#
192+
# In particular, the reflectance identities for negative integer orders Y_{-n}(x) = (-1)^n * Y_{n}(x) (Eq. 9.1.5; [6])
193+
# and for negative noninteger orders Y_{−ν}(x) = cos(πν) * Y_{ν}(x) + sin(πν) * J_{ν}(x) (Eq. 9.1.2; [6]) are used.
194+
# For negative arguments of integer order, Y_{n}(-x) = (-1)^n * Y_{n}(x) + (-1)^n * 2im * J_{n}(x) is used and for
195+
# noninteger orders, Y_{ν}(−x) = exp(−im*π*ν) * Y_{ν}(x) + 2im * cos(πν) * J_{ν}(x) is used.
196+
# For negative orders and arguments the previous identities are combined.
197+
#
198+
# The identities are computed by calling the `bessely_positive_args(nu, x)` function which computes Y_{ν}(x)
199+
# for positive arguments and orders. For integer orders up to 250, forward recurrence is used starting from
200+
# `bessely0` and `bessely1` routines for calculation of Y_{n}(x) of the zero and first order.
201+
# For small arguments, Y_{ν}(x) is calculated from the power series (`bessely_power_series(nu, x`) form of J_{ν}(x) using the connection formula [1].
202+
#
203+
# When x < ν and ν being reasonably large, the debye asymptotic expansion (Eq. 33; [3]) is used `besseljy_debye(nu, x)`.
204+
# When x > ν and x being reasonably large, the Hankel function is calculated from the debye expansion (Eq. 29; [3]) with `hankel_debye(nu, x)`
205+
# and Y_{n}(x) is calculated from the imaginary part of the Hankel function.
206+
# These expansions are not uniform so are not strictly used when the above inequalities are true, therefore, cutoffs
207+
# were determined depending on the desired accuracy. For large arguments x >> ν, the phase functions are used (Eq. 15 [4]) with `besseljy_large_argument(nu, x)`.
208+
#
209+
# For values where the expansions for large arguments and orders are not valid, forward recurrence is employed after shifting the order down
210+
# to where these expansions are valid then using recurrence. In general, the routine will be the slowest when ν ≈ x as all methods struggle at this point.
211+
# Additionally, the Hankel expansion is only accurate (to Float64 precision) when x > 19 and the power series can only be computed for x < 7
212+
# without loss of precision. Therefore, when x > 19 the Hankel expansion is used to generate starting values after shifting the order down.
213+
# When x ∈ (7, 19) and ν ∈ (0, 2) Chebyshev approximation is used with higher orders filled by recurrence.
214+
#
215+
# [1] https://dlmf.nist.gov/10.2#E3
216+
# [2] Bremer, James. "An algorithm for the rapid numerical evaluation of Bessel functions of real orders and arguments."
217+
# Advances in Computational Mathematics 45.1 (2019): 173-211.
218+
# [3] Matviyenko, Gregory. "On the evaluation of Bessel functions."
219+
# Applied and Computational Harmonic Analysis 1.1 (1993): 116-135.
220+
# [4] Heitman, Z., Bremer, J., Rokhlin, V., & Vioreanu, B. (2015). On the asymptotics of Bessel functions in the Fresnel regime.
221+
# Applied and Computational Harmonic Analysis, 39(2), 347-356.
222+
# [5] Ratis, Yu L., and P. Fernández de Córdoba. "A code to calculate (high order) Bessel functions based on the continued fractions method."
223+
# Computer physics communications 76.3 (1993): 381-388.
224+
# [6] Abramowitz, Milton, and Irene A. Stegun, eds. Handbook of mathematical functions with formulas, graphs, and mathematical tables.
225+
# Vol. 55. US Government printing office, 1964.
226+
#
186227

187-
Bessel function of the first kind of order nu, ``J_{nu}(x)``.
188-
Nu must be real.
228+
#####
229+
##### Generic routine for `bessely`
230+
#####
189231
"""
232+
bessely(nu, x::T) where T <: Float64
190233
234+
Bessel function of the first kind of order nu, ``Y_{nu}(x)``.
235+
nu and x must be real where nu and x can be positive or negative.
236+
"""
191237
function bessely(nu::Real, x::T) where T
192238
isinteger(nu) && return bessely(Int(nu), x)
193239
abs_nu = abs(nu)
194240
abs_x = abs(x)
195241

196242
Ynu = bessely_positive_args(abs_nu, abs_x)
197-
spi, cpi = sincospi(abs_nu)
198243
if nu >= zero(T)
199244
if x >= zero(T)
200245
return Ynu
201246
else
202-
return Ynu * cispi(-nu) + 2im * besselj_positive_args(abs_nu, abs_x) * cpi
247+
return Ynu * cispi(-nu) + 2im * besselj_positive_args(abs_nu, abs_x) * cospi(abs_nu)
203248
end
204249
else
205250
Jnu = besselj_positive_args(abs_nu, abs_x)
251+
spi, cpi = sincospi(abs_nu)
206252
if x >= zero(T)
207253
return Ynu * cpi + Jnu * spi
208254
else
@@ -224,6 +270,18 @@ function bessely(nu::Integer, x::T) where T
224270
end
225271
end
226272

273+
#####
274+
##### `bessely` for positive arguments and orders
275+
#####
276+
277+
"""
278+
bessely_positive_args(nu, x::T) where T <: Float64
279+
280+
Bessel function of the first kind of order nu, ``Y_{nu}(x)``.
281+
nu and x must be real and nu and x must be positive.
282+
283+
No checks on arguments are performed and should only be called if certain nu, x >= 0.
284+
"""
227285
function bessely_positive_args(nu, x::T) where T
228286
iszero(x) && return -T(Inf)
229287

@@ -252,16 +310,25 @@ function bessely_positive_args(nu, x::T) where T
252310
return besselj_up_recurrence(x, imag(hankel_debye(v2, x)), imag(hankel_debye(v2 - 1, x)), v2, nu)[1]
253311
end
254312

313+
#####
314+
##### Power series for Y_{nu}(x)
315+
#####
316+
255317
# Use power series form of J_v(x) to calculate Y_v(x) with
256318
# Y_v(x) = (J_v(x)cos(v*π) - J_{-v}(x)) / sin(v*π), v ~= 0, 1, 2, ...
257319
# combined to calculate both J_v and J_{-v} in the same loop
258320
# J_{-v} always converges slower so just check that convergence
259321
# Combining the loop was faster than using two separate loops
260322
#
261-
# this seems to work well for small arguments x < 7.0 for rel. error ~1e-14
323+
# this works well for small arguments x < 7.0 for rel. error ~1e-14
262324
# this also works well for nu > 1.35x - 4.5
263325
# for nu > 25 more cancellation occurs near integer values
264-
bessely_series_cutoff(v, x) = (x < 7.0) || v > 1.35*x - 4.5
326+
"""
327+
bessely_power_series(nu, x::T) where T <: Float64
328+
329+
Computes ``Y_{nu}(x)`` using the power series when nu is not an integer.
330+
In general, this is most accurate for small arguments and when nu > x.
331+
"""
265332
function bessely_power_series(v, x::T) where T
266333
MaxIter = 3000
267334
out = zero(T)
@@ -281,53 +348,30 @@ function bessely_power_series(v, x::T) where T
281348
s, c = sincospi(v)
282349
return (out*c - out2) / s
283350
end
351+
bessely_series_cutoff(v, x) = (x < 7.0) || v > 1.35*x - 4.5
284352

285-
#=
286-
### don't quite have this right
287-
# probably not needed because we should use debye exapnsion for large nu
288-
function log_bessely_power_series(v, x::T) where T
289-
MaxIter = 2000
290-
out = zero(T)
291-
out2 = zero(T)
292-
a = one(T)
293-
b = inv(gamma(1-v))
294-
x2 = (x/2)^2
295-
for i in 0:MaxIter
296-
out += a
297-
out2 += b
298-
a *= -x2 * inv((i + one(T)) * (v + i + one(T)))
299-
b *= -x2 * inv((i + one(T)) * (-v + i + one(T)))
300-
(abs(b) < eps(T) * abs(out2)) && break
301-
end
302-
logout = -loggamma(v + 1) + fma(v, log(x/2), log(out))
303-
sign = 1
304-
305-
if out2 <= zero(T)
306-
sign = -1
307-
out2 = -out2
308-
end
309-
logout2 = -v * log(x/2) + log(out2)
310-
311-
spi, cpi = sincospi(v)
312-
313-
#tmp = logout2 + log(abs(sign*(inv(spi)) - exp(logout - logout2) * cpi / spi))
314-
tmp = logout + log((-cpi + exp(logout2) - logout) / spi)
353+
#####
354+
##### Chebyshev approximation for Y_{nu}(x)
355+
#####
315356

316-
return -exp(tmp)
317-
end
318-
=#
357+
"""
358+
bessely_chebyshev(nu, x::T) where T <: Float64
319359
320-
# only implemented for Float64 so far
321-
besseljy_chebyshev_cutoff(nu, x) = (x <= 19.0 && x >= 6.0)
360+
Computes ``Y_{nu}(x)`` for medium arguments x ∈ (6, 19) for any positive order using a Chebyshev approximation.
361+
Forward recurrence is used to fill orders starting at low orders ν ∈ (0, 2).
362+
"""
322363
function bessely_chebyshev(v, x)
323364
v_floor, _ = modf(v)
324365
Y0, Y1 = bessely_chebyshev_low_orders(v_floor, x)
325366
return besselj_up_recurrence(x, Y1, Y0, v_floor + 1, v)[1]
326367
end
327368

369+
# only implemented for Float64 so far
370+
besseljy_chebyshev_cutoff(::Number, x) = (x <= 19.0 && x >= 6.0)
371+
328372
# compute bessely for x ∈ (6, 19) and ν ∈ (0, 2) using chebyshev approximation with a (16, 28) grid (see notes to generate below)
329373
# optimized to return both (ν, ν + 1) in around the same time, therefore ν must be in (0, 1)
330-
# no checks are performed on
374+
# no checks are performed on arguments
331375
function bessely_chebyshev_low_orders(v, x)
332376
# need to rescale inputs according to
333377
#x0 = (x - lb) * 2 / (ub - lb) - 1
@@ -348,6 +392,7 @@ function clenshaw_chebyshev(x, c)
348392
end
349393
return c0 + c1 * x
350394
end
395+
351396
# to generate the Chebyshev weights
352397
#=
353398
using ArbNumerics, FastChebInterp
@@ -376,3 +421,38 @@ const bessely_cheb_weights = (
376421
(4.9388506677207545e-15, -8.11264980108463e-15, 8.15564695267678e-15, -6.6179014247901285e-15, 1.2560443562777774e-15, -1.1626485837362674e-15, -8.428647433288211e-15, 8.040878625052107e-15, 7.604076442794149e-16, -2.509940602859596e-15, 4.433411320740124e-16, 2.4051574424535735e-16, -7.97263854654364e-17, -8.468775456115063e-18, 3.0977204928150507e-18, -2.763609694292828e-18, -4.528057493615123e-18, 8.090667992714159e-18, -5.7670273029389466e-18, 6.935237082901993e-18, 2.3956539089710175e-18, 3.5297683311975285e-18, -2.6680862870119677e-19, -1.124331067925833e-18, -3.969891564639305e-18, 3.4988194829519685e-18, 8.636411459915624e-19, -1.3895984430563314e-18, 1.7657006809049643e-18),
377422
(-2.058125495426769e-16, 6.08114692374987e-16, -4.430737266654462e-16, 7.070791826861885e-16, -4.496672312473068e-16, 4.992680927678948e-16, -2.0005398723221536e-16, -4.085152288266179e-16, 2.963046065367326e-16, 1.9081397648228198e-17, -6.065778883208153e-17, 1.2267254817829564e-17, 2.5241246780253446e-18, 5.364001077188067e-19, -1.1151793774136653e-18, -2.647497194530543e-19, -9.99558548618923e-19, 1.9545043306813755e-18, 1.0295729550672243e-18, 5.850363951633064e-19, 2.039938658283451e-19, -1.622964422184252e-18, -2.1240860691038883e-18, 2.0835979355758296e-18, -1.5423943263121515e-19, -5.576729517866199e-19, -3.7259501367076117e-20, -1.8905808347578018e-19, -1.4869058365515489e-18)
378423
)
424+
425+
#=
426+
### don't quite have this right (issue with signs)
427+
# probably not needed because we should use debye exapnsion for large nu
428+
function log_bessely_power_series(v, x::T) where T
429+
MaxIter = 2000
430+
out = zero(T)
431+
out2 = zero(T)
432+
a = one(T)
433+
b = inv(gamma(1-v))
434+
x2 = (x/2)^2
435+
for i in 0:MaxIter
436+
out += a
437+
out2 += b
438+
a *= -x2 * inv((i + one(T)) * (v + i + one(T)))
439+
b *= -x2 * inv((i + one(T)) * (-v + i + one(T)))
440+
(abs(b) < eps(T) * abs(out2)) && break
441+
end
442+
logout = -loggamma(v + 1) + fma(v, log(x/2), log(out))
443+
sign = 1
444+
445+
if out2 <= zero(T)
446+
sign = -1
447+
out2 = -out2
448+
end
449+
logout2 = -v * log(x/2) + log(out2)
450+
451+
spi, cpi = sincospi(v)
452+
453+
#tmp = logout2 + log(abs(sign*(inv(spi)) - exp(logout - logout2) * cpi / spi))
454+
tmp = logout + log((-cpi + exp(logout2) - logout) / spi)
455+
456+
return -exp(tmp)
457+
end
458+
=#

0 commit comments

Comments
 (0)