@@ -48,15 +48,12 @@ const exp_inf = 709.7827128933841
4848# Compute reciprocal gamma via loggamma
4949@inline rgamma (y:: Real ) = exp (- loggamma (y))
5050
51- # Compute exp(x) / Γ(y) safely via loggamma to avoid overflow
52- @inline _exp_rgamma (x:: Real , y:: Real ) = exp (x - loggamma (y))
53-
5451
5552# 1. Taylor series expansion in x=0, for x <= 1
5653# Φ(a, b, x) = sum_k x^k / k! / Γ(a*k+b)
5754# Note that every term, and therefore also Φ(a, b, x), is monotone
5855# decreasing with increasing a or b.
59- function _wb_series (a:: Float64 , b:: Float64 , x:: Float64 , nstart:: Int , nstop:: Int )
56+ function _wb_series (a:: Float64 , b:: Float64 , x:: Float64 , nstart:: Int , nstop:: Int , logret :: Bool )
6057 xk_k = x^ nstart * rgamma (nstart + 1 ) # x^k/k!
6158 res = xk_k * rgamma (nstart * a + b)
6259 # term k=nstart+1 , +2 +3, ...
@@ -71,27 +68,37 @@ function _wb_series(a::Float64, b::Float64, x::Float64, nstart::Int, nstop::Int)
7168 res += xk_k * rgamma (a * k + b)
7269 end
7370 end
74- return res
71+ return logret ? log (res) : res
7572end
7673
7774
7875# 2. Taylor series expansion in x=0, for large a.
7976# Φ(a, b, x) = sum_k x^k / k! / Γ(a*k+b)
8077# Use Stirling formula to find k=k_max, the maximum term.
8178# Then use n terms of Taylor series around k_max.
82- function _wb_large_a (a:: Float64 , b:: Float64 , x:: Float64 , n:: Int )
79+ function _wb_large_a (a:: Float64 , b:: Float64 , x:: Float64 , n:: Int , logret :: Bool )
8380 k_max = floor (Int, (a^ (- a) * x)^ (1 / (1 + a)))
8481 n_start = k_max - (n ÷ 2 )
8582 if n_start < 0
8683 n_start = 0
8784 end
8885
89- res = 0.0
9086 lnx = log (x)
87+
88+ # For numerical stability, we factor out the maximum term exp(..) with k=k_max
89+ # but only if it is larger than 0.
90+ max_exponent = max (0.0 , k_max * lnx - loggamma (k_max + 1.0 ) - loggamma (a * k_max + b))
91+ res = 0.0
92+
9193 for k in n_start: (n_start + n - 1 )
92- res += exp (k * lnx - loggamma (k + 1.0 ) - loggamma (a * k + b))
94+ res += exp (k * lnx - loggamma (k + 1.0 ) - loggamma (a * k + b) - max_exponent)
95+ end
96+
97+ if logret
98+ return max_exponent + log (res)
99+ else
100+ return exp (max_exponent) * res
93101 end
94- return res
95102end
96103
97104
111118# For small b, i.e. b <= 1e-3, cancellation of poles of Ψ(b)/Γ(b)
112119# and polygamma needs to be carried out => series expansion in a=0 to order 5
113120# and in b=0 to order 4.
114- function _wb_small_a (a:: Float64 , b:: Float64 , x:: Float64 , order:: Int )
121+ function _wb_small_a (a:: Float64 , b:: Float64 , x:: Float64 , order:: Int , logret :: Bool )
115122 # A: coefficients of a^k (1, -x * Ψ(b), ...)
116123 # B: powers of b^k/k! or terms in polygamma functions
117124 # C: coefficients of a^k1 * b^k2
@@ -144,7 +151,9 @@ function _wb_small_a(a::Float64, b::Float64, x::Float64, order::Int)
144151 X5 / 24.0 * (C4 + C5 * b),
145152 X6 / 120.0 * (C5)
146153 )
147- res = exp (x) * evalpoly (a, A)
154+
155+ res = evalpoly (a, A)
156+ return logret ? x + log (res) : exp (x) * res
148157 else
149158 # Φ(a, b, x) = exp(x)/Γ(b) * sum(A[i] * X[i] * B[i], i=0..5)
150159 # A[n] = a^n/n!
@@ -198,10 +207,13 @@ function _wb_small_a(a::Float64, b::Float64, x::Float64, order::Int)
198207 res = evalpoly (a, (A1, A2, A3, A4, A5, A6)[1 : (order+ 1 )])
199208 end
200209
201- # res *= exp(x) * rgamma(b)
202- res *= _exp_rgamma (x, b)
210+ if logret
211+ return log (res) + x - loggamma (b)
212+ else
213+ # Compute exp(x) / Γ(b) via loggamma to avoid overflow
214+ return res * exp (x - loggamma (b))
215+ end
203216 end
204- return res
205217end
206218
207219
210222# Φ(a, b, x) ~ Z^(1/2-b) * exp((1+a)/a * Z) * sum_k (-1)^k * C_k / Z^k
211223#
212224# with Z = (a*x)^(1/(1+a)).
213- function _wb_asymptotic (a:: Float64 , b:: Float64 , x:: Float64 )
225+ function _wb_asymptotic (a:: Float64 , b:: Float64 , x:: Float64 , logret :: Bool )
214226 A = ntuple (i -> a^ (i- 1 ), 15 ) # powers of a
215227 B = ntuple (i -> b^ (i- 1 ), 17 ) # powers of b
216228 Ap1 = ntuple (i -> (1 + a)^ (i- 1 ), 9 ) # powers of (1+a)
@@ -540,16 +552,25 @@ function _wb_asymptotic(a::Float64, b::Float64, x::Float64)
540552 Zp /= Z
541553 res += (- 1 )^ (k + 1 ) * C[k] * Zp
542554 end
543- res *= Z^ (0.5 - b) * exp (Ap1[2 ] / a * Z)
544- return res
555+ if logret
556+ return log (res) + log (Z) * (0.5 - b) + Ap1[2 ] / a * Z
557+ else
558+ return res * Z^ (0.5 - b) * exp (Ap1[2 ] / a * Z)
559+ end
545560end
546561
547562
548563# Compute integrand Kmod(eps, a, b, x, r) for Gauss-Laguerre quadrature.
549564# K(a, b, x, r+eps) = exp(-r-eps) * Kmod(eps, a, b, x, r)
550- @inline function _Kmod (eps:: Float64 , a:: Float64 , b:: Float64 , x:: Float64 , r:: Float64 )
565+ #
566+ # Kmod(eps, a, b, x, r) = exp(x * (r+eps)^(-a) * cos(pi*a)) * (r+eps)^(-b)
567+ # * sin(x * (r+eps)^(-a) * sin(pi*a) + pi * b)
568+ #
569+ # Note that we additionally factor out exp(exp_term) which helps with large
570+ # terms in the exponent of exp(...)
571+ @inline function _Kmod (exp_term:: Float64 , eps:: Float64 , a:: Float64 , b:: Float64 , x:: Float64 , r:: Float64 )
551572 x_r_a = x * (r + eps)^ (- a)
552- return exp (x_r_a * cospi (a)) * (r + eps)^ (- b) * sin (x_r_a * sinpi (a) + π * b)
573+ return exp (x_r_a * cospi (a) + exp_term ) * (r + eps)^ (- b) * sin (x_r_a * sinpi (a) + π * b)
553574end
554575
555576
558579# * exp(eps * cos(ϕ) + x * eps^(-a) * cos(a*ϕ))
559580# * cos(eps * sin(ϕ) - x * eps^(-a) * sin(a*ϕ)
560581# + (1-b)*ϕ)
561- @inline function _P (eps:: Float64 , a:: Float64 , b:: Float64 , x:: Float64 , ϕ:: Float64 )
582+ @inline function _P (exp_term :: Float64 , eps:: Float64 , a:: Float64 , b:: Float64 , x:: Float64 , ϕ:: Float64 )
562583 x_eps_a = x * eps^ (- a)
563- return exp (eps * cos (ϕ) + x_eps_a * cos (a * ϕ)) *
584+ return exp (eps * cos (ϕ) + x_eps_a * cos (a * ϕ) + exp_term ) *
564585 cos (eps * sin (ϕ) - x_eps_a * sin (a * ϕ) + (1.0 - b) * ϕ)
565586end
566587
584605# the end of the second line should read +(1-b)*ϕ.
585606# This integral representation introduced the free parameter eps (from the
586607# radius of complex contour integration). We try to choose eps such that
587- # the integrand behaves smoothly.
608+ # the integrand behaves smoothly. Note that this is quite diffrent from how
609+ # Luchko (2008) deals with eps: he is either looking for the limit eps -> 0
610+ # or he sets (silently) eps=1. But having the freedom to set eps is much more
611+ # powerful for numerical evaluation.
588612#
589613# As K has a leading exp(-r), we factor this out and apply Gauss-Laguerre
590614# quadrature rule:
@@ -681,7 +705,7 @@ const w_legendre = [
681705 0.006759799195745401 , 0.002908622553155141
682706 ]
683707
684- function _wb_integral (a:: Float64 , b:: Float64 , x:: Float64 )
708+ function _wb_integral (a:: Float64 , b:: Float64 , x:: Float64 , logret :: Bool )
685709 # Fitted parameters for optimal choice of eps
686710 A = (0.41037 , 0.30833 , 6.9952 , 18.382 , - 2.8566 , 2.1122 )
687711
@@ -714,19 +738,38 @@ function _wb_integral(a::Float64, b::Float64, x::Float64)
714738 eps = min (eps, 150.0 )
715739 eps = max (eps, 3.0 ) # Note: 3 seems to be a pretty good choice in general.
716740
741+ # We factor out exp(-exp_term) from _Kmod and _P to avoid overflow of exp(..).
742+ exp_term = 0.0
743+ # From the exponent of K:
744+ r = x_laguerre[end ] # largest value of x used in _Kmod
745+ x_r_a = x * (r + eps)^ (- a)
746+ exp_term = max (exp_term, x_r_a * cospi (a))
747+ # From the exponent of P:
748+ x_eps_a = x * eps^ (- a)
749+ # phi = 0 => cos(phi) = cos(a * phi) = 1
750+ exp_term = max (exp_term, eps + x_eps_a)
751+ # phi = pi => cos(phi) = -1
752+ exp_term = max (exp_term, - eps + x_eps_a * cospi (a))
753+
717754 res1 = 0.0
718755 res2 = 0.0
719756 for k in eachindex (x_laguerre, w_laguerre, x_legendre)
720- res1 += w_laguerre[k] * _Kmod (eps, a, b, x, x_laguerre[k])
757+ res1 += w_laguerre[k] * _Kmod (- exp_term, eps, a, b, x, x_laguerre[k])
721758 # y = (b-a)*(x+1)/2 + a for integration from a=0 to b=π
722759 y = π * (x_legendre[k] + 1.0 ) / 2.0
723- res2 += w_legendre[k] * _P (eps, a, b, x, y)
760+ res2 += w_legendre[k] * _P (- exp_term, eps, a, b, x, y)
724761 end
725762 res1 *= exp (- eps)
726763 res2 *= π / 2.0
727764 res2 *= eps^ (1.0 - b)
728765
729- return (res1 + res2) / π
766+ # Remember the factored out exp_term from _Kmod and _P
767+ if logret
768+ res = res1 + res2
769+ return res > 0 ? exp_term + log (res) - log (π) : NaN
770+ else
771+ return exp (exp_term) / π * (res1 + res2)
772+ end
730773end
731774
732775
@@ -759,7 +802,17 @@ One of 5 different computation methods is used depending on the ranges of the ar
759802Accuracy is generally higher than 1e-11, though for some parameter values it can
760803be as low as 1e-8.
761804"""
762- function wrightbessel (a:: Float64 , b:: Float64 , x:: Float64 )
805+ wrightbessel (a:: Float64 , b:: Float64 , x:: Float64 ) = _wrightbessel (a, b, x, false )
806+
807+ """
808+ logwrightbessel(a::Float64, b::Float64, x::Float64)
809+
810+ Compute the logarithm of Wright's generalized Bessel function, i.e., `log(wrightbessel(a, b, x))`.
811+ See [`wrightbessel`](@ref) for details.
812+ """
813+ logwrightbessel (a:: Float64 , b:: Float64 , x:: Float64 ) = _wrightbessel (a, b, x, true )
814+
815+ function _wrightbessel (a:: Float64 , b:: Float64 , x:: Float64 , logret:: Bool )
763816 if isnan (a) || isnan (b) || isnan (x)
764817 throw (ArgumentError (" arguments cannot be NaN (got a=$a , b=$b , x=$x )" ))
765818 elseif isinf (a) || isinf (b)
@@ -771,13 +824,13 @@ function wrightbessel(a::Float64, b::Float64, x::Float64)
771824 elseif a >= rgamma_zero || b >= rgamma_zero
772825 return NaN
773826 elseif x == 0.0
774- return rgamma (b)
827+ return logret ? - loggamma (b) : rgamma (b)
775828 elseif a == 0.0
776- # return exp(x) * rgamma (b)
777- return _exp_rgamma (x, b )
829+ # Compute exp(x) / Γ (b) via loggamma to avoid overflow
830+ return logret ? x - loggamma (b) : exp (x - loggamma (b) )
778831 elseif (a <= 1e-3 && b <= 50.0 && x <= 9.0 ) ||
779832 (a <= 1e-4 && b <= 70.0 && x <= 100.0 ) ||
780- (a <= 1e-5 && b <= 170.0 && x < exp_inf)
833+ (a <= 1e-5 && b <= 170.0 && ( x < exp_inf || (logret && x <= 1e3 )) )
781834 # Taylor Series expansion in a=0 to order=order => precision <= 1e-11
782835 # If beta is also small => precision <= 1e-11.
783836 # max order = 5
@@ -812,13 +865,13 @@ function wrightbessel(a::Float64, b::Float64, x::Float64)
812865 order = 5
813866 end
814867 end
815- return _wb_small_a (a, b, x, order)
868+ return _wb_small_a (a, b, x, order, logret )
816869 elseif x <= 1.0
817870 # 18 term Taylor Series => error mostly smaller 5e-14
818- return _wb_series (a, b, x, 0 , 18 )
871+ return _wb_series (a, b, x, 0 , 18 , logret )
819872 elseif x <= 2.0
820873 # 20 term Taylor Series => error mostly smaller 1e-12 to 1e-13
821- return _wb_series (a, b, x, 0 , 20 )
874+ return _wb_series (a, b, x, 0 , 20 , logret )
822875 elseif a >= 5.0
823876 # Taylor series around the approximate maximum term.
824877 # Set number of terms=order.
@@ -839,18 +892,20 @@ function wrightbessel(a::Float64, b::Float64, x::Float64)
839892 order = min (floor (Int, 6 * log10 (x) - 36 ), 100 )
840893 end
841894 end
842- return _wb_large_a (a, b, x, order)
843- elseif (0.5 <= a) && (a <= 1.8 ) && (100.0 <= b) && (1e5 <= x)
844- return NaN
895+ return _wb_large_a (a, b, x, order, logret)
845896 elseif (a * x)^ (1 / (1 + a)) >= 14 + b * b / (2 * (1 + a))
846897 # Asymptotic expansion in Z = (a*x)^(1/(1+a)) up to 8th term 1/Z^8.
847898 # For 1/Z^k, the highest term in b is b^(2*k) * a0 / (2^k k! (1+a)^k).
848899 # As a0 is a common factor to all orders, this explains a bit the
849900 # domain of good convergence set above.
850901 # => precision ~ 1e-11 but can go down to ~1e-8 or 1e-7
851902 # Note: We ensured a <= 5 as this is a bad approximation for large a.
852- return _wb_asymptotic (a, b, x)
903+ return _wb_asymptotic (a, b, x, logret)
904+ elseif (0.5 <= a) && (a <= 1.8 ) && (100.0 <= b) && (1e5 <= x)
905+ # This is a very hard domain. This condition is placed after wb_asymptotic.
906+ # TODO : Explore ways to cover this domain.
907+ return NaN
853908 else
854- return _wb_integral (a, b, x)
909+ return _wb_integral (a, b, x, logret )
855910 end
856911end
0 commit comments