Skip to content

Commit 3c30394

Browse files
author
Chris Geoga
committed
Power series Temme-like method for near-integer/integer orders that
gives valid derivatives. Tests TODO/to debug still.
1 parent 6f3445c commit 3c30394

File tree

3 files changed

+88
-31
lines changed

3 files changed

+88
-31
lines changed

src/BesselFunctions/besselk.jl

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -499,38 +499,25 @@ besselk_power_series(v, x::Float32) = Float32(besselk_power_series(v, Float64(x)
499499
besselk_power_series(v, x::ComplexF32) = ComplexF32(besselk_power_series(v, ComplexF64(x)))
500500

501501
function besselk_power_series(v, x::ComplexOrReal{T}) where T
502-
MaxIter = 1000
503-
S = eltype(x)
504-
v, x = S(v), S(x)
505-
506-
z = x / 2
507-
zz = z * z
508-
logz = log(z)
509-
xd2_v = exp(v*logz)
510-
xd2_nv = inv(xd2_v)
511-
512-
# use the reflection identify to calculate gamma(-v)
513-
# use relation gamma(v)*v = gamma(v+1) to avoid two gamma calls
514-
gam_v = gamma(v)
515-
gam_nv = π / (sinpi(-abs(v)) * gam_v * v)
516-
gam_1mv = -gam_nv * v
517-
gam_1mnv = gam_v * v
518-
519-
_t1 = gam_v * xd2_nv * gam_1mv
520-
_t2 = gam_nv * xd2_v * gam_1mnv
521-
(xd2_pow, fact_k, out) = (one(S), one(S), zero(S))
522-
for k in 0:MaxIter
523-
t1 = xd2_pow * T(0.5)
524-
tmp = muladd(_t1, gam_1mnv, _t2 * gam_1mv)
525-
tmp *= inv(gam_1mv * gam_1mnv * fact_k)
526-
term = t1 * tmp
527-
out += term
528-
abs(term / out) < eps(T) && break
529-
(gam_1mnv, gam_1mv) = (gam_1mnv*(one(S) + v + k), gam_1mv*(one(S) - v + k))
530-
xd2_pow *= zz
531-
fact_k *= k + one(S)
502+
Math.isnearint(v) && return besselk_power_series_int(v, x)
503+
MaxIter = 5000
504+
gam = gamma(v)
505+
ngam = π / (sinpi(-abs(v)) * gam * v)
506+
507+
s1, s2 = zero(T), zero(T)
508+
t1, t2 = one(T), one(T)
509+
510+
for k in 1:MaxIter
511+
s1 += t1
512+
s2 += t2
513+
t1 *= x^2 / (4k * (k - v))
514+
t2 *= x^2 / (4k * (k + v))
515+
abs(t1) < eps(T) && break
532516
end
533-
return out
517+
518+
xpv = (x/2)^v
519+
s = gam * s1 + xpv^2 * ngam * s2
520+
return s / (2*xpv)
534521
end
535522
besselk_power_series_cutoff(nu, x::Float64) = x < 2.0 || nu > 1.6x - 1.0
536523
besselk_power_series_cutoff(nu, x::Float32) = x < 10.0f0 || nu > 1.65f0*x - 8.0f0
@@ -615,3 +602,68 @@ end
615602
end
616603
)
617604
end
605+
606+
# This is an expansion of the function
607+
#
608+
# f_0(v, x) = (x^v)*gamma(-v) + (x^(-v))*gamma(v)
609+
# = (x^v)*(gamma(-v) + (x^(-2*v))*gamma(v))
610+
#
611+
# around v ∼ 0. As you can see by plugging that second form into Wolfram Alpha
612+
# and getting an expansion back, this is actually a bivariate polynomial in
613+
# (v^2, log(x)). So that's how this is structured.
614+
@inline function f0_local_expansion_v0(v, x)
615+
lx = log(x)
616+
c0 = evalpoly(lx, (-1.1544313298030657, -2.0))
617+
c2 = evalpoly(lx, ( 1.4336878944573288, -1.978111990655945, -0.5772156649015329, -0.3333333333333333))
618+
c4 = evalpoly(lx, (-0.6290784463642211, -1.4584260788225176, -0.23263776388631713, -0.32968533177599085, -0.048101305408461074, -0.016666666666666666))
619+
evalpoly(v*v, (c0,c2,c4))/2
620+
end
621+
622+
# This function assumes |v| < 1e-6 or 1e-7!
623+
#
624+
# TODO (cg 2023/05/16 18:07): lots of micro-optimizations.
625+
function besselk_power_series_temme_basal(v::V, x::Float64) where{V}
626+
max_iter = 50
627+
T = promote_type(V,Float64)
628+
z = x/2
629+
zz = z*z
630+
fk = f0_local_expansion_v0(v, x/2)
631+
zv = z^v
632+
znv = inv(zv)
633+
gam_1pv = GammaFunctions.gamma_near_1(1+v)
634+
gam_1nv = GammaFunctions.gamma_near_1(1-v)
635+
(pk, qk, _ck, factk, vv) = (znv*gam_1pv/2, zv*gam_1nv/2, one(T), one(T), v*v)
636+
(out_v, out_vp1) = (zero(T), zero(T))
637+
for k in 1:max_iter
638+
# add to the series:
639+
ck = _ck/factk
640+
term_v = ck*fk
641+
term_vp1 = ck*(pk - (k-1)*fk)
642+
out_v += term_v
643+
out_vp1 += term_vp1
644+
# check for convergence:
645+
((abs(term_v) < eps(T)) && (abs(term_vp1) < eps(T))) && break
646+
# otherwise, increment new quantities:
647+
fk = (k*fk + pk + qk)/(k^2 - vv)
648+
pk /= (k-v)
649+
qk /= (k+v)
650+
_ck *= zz
651+
factk *= k
652+
end
653+
(out_v, out_vp1/z)
654+
end
655+
656+
function besselk_power_series_int(v, x::Float64)
657+
v < zero(v) && return besselk_power_series_int(-v, x)
658+
flv = Int(floor(v))
659+
_v = v - flv
660+
(kv, kvp1) = besselk_power_series_temme_basal(_v, x)
661+
abs(v) < 1/2 && return kv
662+
twodx = 2/x
663+
for _ in 1:(flv-1)
664+
_v += 1
665+
(kv, kvp1) = (kvp1, muladd(twodx*_v, kvp1, kv))
666+
end
667+
kvp1
668+
end
669+

src/GammaFunctions/gamma.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,5 @@ function gamma(n::Integer)
113113
n > 20 && return gamma(float(n))
114114
@inbounds return Float64(factorial(n-1))
115115
end
116+
117+
gamma_near_1(x) = evalpoly(x-one(x), (1.0, -0.5772156649015329, 0.9890559953279725, -0.23263776388631713))

src/Math/Math.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,7 @@ end
154154
)
155155
end
156156

157+
# TODO (cg 2023/05/16 18:09): dispute this cutoff.
158+
isnearint(x) = abs(x-round(x)) < 1e-7
159+
157160
end

0 commit comments

Comments
 (0)