Skip to content

Commit b3096ae

Browse files
augustt198stevengj
andauthored
expint: series around gamma poles (#281)
* series around gamma poles * even higher order * fix mistakes * don't hang on large integer orders * test * float32 * use relative error again, type stability * iszero * remove div * remove rounding check * fix for big integer > typemax(Int) * whoops * still need integer form even when ν is not representable as Int * broadcast over tuple to avoid allocating a temporary array * more safety in computing (-z)^n / n! * typo * another test * whoops * use niter in En_expand_origin * simplify Co-authored-by: Steven G. Johnson <[email protected]> Co-authored-by: Steven G. Johnson <[email protected]>
1 parent 72064db commit b3096ae

File tree

2 files changed

+73
-24
lines changed

2 files changed

+73
-24
lines changed

src/expint.jl

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -292,38 +292,77 @@ end
292292

293293
# series about origin, general ν
294294
# https://functions.wolfram.com/GammaBetaErf/ExpIntegralE/06/01/04/01/01/0003/
295-
function En_expand_origin_general::Number, z::Number)
296-
gammaterm = En_safe_gamma_term(ν, z)
297-
frac = one(real(z))
298-
sumterm = frac / (1 - ν)
299-
k, maxiter = 1, 100
300-
ϵ = 10*eps(real(sumterm))
301-
while k < maxiter
295+
function En_expand_origin_general::Number, z::Number, niter::Integer)
296+
# gammaterm = En_safe_gamma_term(ν, z)
297+
gammaterm = gamma(1-ν)*z^-1)
298+
frac = one(z)
299+
blowup = abs(1 - ν) < 0.5 ? frac / (1 - ν) : zero(z)
300+
sumterm = abs(1 - ν) < 0.5 ? zero(z) : frac / (1 - ν)
301+
k = 1
302+
ε = 10*eps(typeof(abs(frac)))
303+
while k < niter
302304
frac *= -z / k
303305
prev = sumterm
304-
sumterm += frac / (k + 1 - ν)
305-
if abs(sumterm - prev) < ϵ
306-
break
306+
if abs(k + 1 - ν) < 0.5
307+
blowup += frac / (k + 1 - ν)
308+
else
309+
sumterm += frac / (k + 1 - ν)
310+
if abs(sumterm - prev) < ε*abs(prev)
311+
break
312+
end
307313
end
308314
k += 1
309315
end
310-
return gammaterm - sumterm
316+
317+
if real+z) isa Union{Float64, Float32} && abs(gammaterm - blowup) < 1e-3 * abs(blowup)
318+
δ = round(ν) - ν
319+
n = real(round(ν)) - 1
320+
321+
# (1 - z^δ)/δ series
322+
logz = log(z)
323+
series1 = -logz - logz^2*δ/2 - logz^3*δ^2/6 - logz^4*δ^3/24 - logz^5*δ^4/120
324+
325+
# due to https://functions.wolfram.com/GammaBetaErf/Gamma/06/01/05/01/0004/
326+
# expressions for higher order terms found using:
327+
# https://gist.github.com/augustt198/348e8f9ba33c0248f1548309c47c6d0e
328+
ψ₀, ψ₁, ψ₂, ψ₃, ψ₄ = polygamma.((0,1,2,3,4), n+1)
329+
series2 = ψ₀ + (3*ψ₀^2 + π^2 - 3*ψ₁)*δ/6 + (ψ₀^3 +^2 - 3ψ₁)*ψ₀ + ψ₂)δ^2/6
330+
series2 += (7π^4 + 15*(ψ₀^4 + 2ψ₀^2 *^2 - 3ψ₁) + ψ₁*(-2π^2 + 3ψ₁) + 4ψ₀*ψ₂) - 15ψ₃)*δ^3/360
331+
series2 += (3ψ₀^5 + ψ₀^3*(10π^2 - 30ψ₁) + 30ψ₀^2*ψ₂ + ψ₀*(45ψ₁^2 - 30π^2*ψ₁ - 15ψ₃ + 7π^4) - 30ψ₁*ψ₂ + 10π^2*ψ₂ + 3ψ₄)*δ^4/360
332+
333+
return (series1 + series2) * En_safe_expfact(n, z) * z^-n-1) - sumterm
334+
end
335+
return gammaterm - (blowup + sumterm)
311336
end
312337

313-
# series about the origin, special case for integer n > 0
314-
# https://functions.wolfram.com/GammaBetaErf/ExpIntegralE/06/01/04/01/02/0005/
315-
function En_expand_origin_posint(n::Integer, z::Number)
316-
gammaterm = 1 # (-z)^(n-1) / (n-1)!
317-
for i = 1:n-1
318-
gammaterm *= -z / i
338+
# compute (-z)^n / n!, avoiding overflow if possible, where n is an integer ≥ 0 (but not necessarily an Integer)
339+
function En_safe_expfact(n::Real, z::Number)
340+
if n < 100
341+
powerterm = one(z)
342+
for i = 1:Int(n)
343+
powerterm *= -z/i
344+
end
345+
return powerterm
346+
else
347+
if z isa Real
348+
sgn = z 0 ? one(n) : (n <= typemax(Int) ? (isodd(Int(n)) ? -one(n) : one(n)) : (-1)^n)
349+
return sgn * exp(n * log(abs(z)) - loggamma(n+1))
350+
else
351+
return exp(n * log(-z) - loggamma(n+1))
352+
end
319353
end
354+
end
320355

356+
# series about the origin, special case for integer n > 0
357+
# https://functions.wolfram.com/GammaBetaErf/ExpIntegralE/06/01/04/01/02/0005/
358+
function En_expand_origin_posint(n, z::Number, niter::Integer)
359+
gammaterm = En_safe_expfact(n-1, z) # (-z)^(n-1) / (n-1)!
321360
frac = one(real(z))
322361
gammaterm *= digamma(oftype(frac,n)) - log(z)
323362
sumterm = n == 1 ? zero(frac) : frac / (1 - n)
324-
k, maxiter = 1, 100
363+
k = 1
325364
ϵ = 10*eps(real(sumterm))
326-
while k < maxiter
365+
while k < niter
327366
frac *= -z / k
328367
# skip term with zero denominator
329368
if k != n-1
@@ -338,11 +377,11 @@ function En_expand_origin_posint(n::Integer, z::Number)
338377
return gammaterm - sumterm
339378
end
340379

341-
function En_expand_origin::Number, z::Number)
380+
function En_expand_origin::Number, z::Number, niter::Integer)
342381
if isinteger(ν) && real(ν) > 0
343-
return En_expand_origin_posint(Integer(ν), z)
382+
return real(ν) < (typemax(Int)>>2) ? En_expand_origin_posint(Int(real(ν)), z, niter) : En_expand_origin_posint(real(ν), z, niter)
344383
else
345-
return En_expand_origin_general(ν, z)
384+
return En_expand_origin_general(ν, z, niter)
346385
end
347386
end
348387

@@ -398,7 +437,7 @@ function _expint(ν::Number, z::Number, niter::Int=1000, ::Val{expscaled}=Val{fa
398437
if abs2(z) < 9
399438
# use Taylor series about the origin for small z
400439
mult = expscaled ? exp(z) : 1
401-
return mult * En_expand_origin(ν, z)
440+
return mult * En_expand_origin(ν, z, niter)
402441
end
403442

404443
if z isa Real || real(z) > 0
@@ -409,7 +448,7 @@ function _expint(ν::Number, z::Number, niter::Int=1000, ::Val{expscaled}=Val{fa
409448
g, cf, _ = zero(z), En_cf_nogamma(ν, z, niter)...
410449
end
411450
g != 0 && (g *= gmult)
412-
cf = expscaled ? cf : En_safeexpmult(-z, cf)
451+
cf = expscaled ? cf : En_safeexpmult(-z, cf)
413452
return g + cf
414453
else
415454
# Procedure for z near the negative real axis based on

test/expint.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ using Base.MathConstants
8484
@test expint(-4.3+2im, -10+0.0im) -1473.52621376586428327992147406861600035297263354271049988247182 - 210.712752794950368400059514411922967744944073260391136148642008im
8585
@test expint(-4.3+2im, -10-0.0im) -1473.47816311715810169554659685200228583538709701122499753349598 - 210.761182017526309473296237005277228612276963738826032570499367im
8686

87+
# don't hang on large integer order
88+
@test expint(1e15, 2.5) 8.20849986238987e-17
89+
@test expint(1e63, 2.5) 8.20849986238988e-65
90+
91+
# handle gamma pole
92+
near_pole5_ans = [0.0120728157429194, 0.0119237297690539, 0.0119090129077972, 0.0119075431153846, 0.0119073961550572, 0.0119073814592135, 0.0119073799896311, 0.0119073798426728, 0.011907379827977, 0.0119073798265074, 0.0119073798263605, 0.0119073798263458, 0.0119073798263443, 0.0119073798263441, 0.0119073798263441]
93+
for (i, ans) in zip(1:15, near_pole5_ans)
94+
@test expint(5 - 10.0^(-i), 2.5) ans
95+
end
96+
8797
@test expint(2.000001, 4.3) 0.0022470372637239675640390482141893761501016099509821867746
8898
@test expint(1.999999, 4.3) 0.0022470379319936678143503120160911823783417969321242814713
8999

0 commit comments

Comments
 (0)