-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
more accurate and faster lgamma, and use a more standard branch cut #18330
Merged
Merged
Changes from 9 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
69542e8
more accurate and faster lgamma, and use a more standard branch cut f…
stevengj f2db1ca
more tests
stevengj e1d72fc
use trick from Hare (1997) to compute log(prod of shifts) rather than…
stevengj be2084c
even more tests
stevengj d2aa66d
whoops, use 1e14 and not 10^14 to avoid integer overflow on 32-bit Wi…
stevengj afbcdbf
fix accuracy near zero at z=2
stevengj cb8caba
update manual for lgamma
stevengj 7f5cede
news for lgamma changes
stevengj 0debff5
can use a lower-degree Taylor series around z=2 because the coefficie…
stevengj 6f6ff37
linewrap poly coefs
stevengj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,49 +33,91 @@ Compute the logarithmic factorial of `x` | |
lfact(x::Real) = (x<=1 ? zero(float(x)) : lgamma(x+one(x))) | ||
@vectorize_1arg Number lfact | ||
|
||
const clg_coeff = [76.18009172947146, | ||
-86.50532032941677, | ||
24.01409824083091, | ||
-1.231739572450155, | ||
0.1208650973866179e-2, | ||
-0.5395239384953e-5] | ||
|
||
function clgamma_lanczos(z) | ||
const sqrt2pi = 2.5066282746310005 | ||
|
||
y = x = z | ||
temp = x + 5.5 | ||
zz = log(temp) | ||
zz = zz * (x+0.5) | ||
temp -= zz | ||
ser = complex(1.000000000190015, 0) | ||
for j=1:6 | ||
y += 1.0 | ||
zz = clg_coeff[j]/y | ||
ser += zz | ||
end | ||
zz = sqrt2pi*ser / x | ||
return log(zz) - temp | ||
end | ||
|
||
""" | ||
lgamma(x) | ||
|
||
Compute the logarithm of the absolute value of [`gamma`](:func:`gamma`) for | ||
[`Real`](:obj:`Real`) `x`, while for [`Complex`](:obj:`Complex`) `x` it computes the | ||
logarithm of `gamma(x)`. | ||
principal branch cut of the logarithm of `gamma(x)` (defined for negative `real(x)` | ||
by analytic continuation from positive `real(x)`). | ||
""" | ||
function lgamma(z::Complex) | ||
if real(z) <= 0.5 | ||
a = clgamma_lanczos(1-z) | ||
b = log(sinpi(z)) | ||
const logpi = 1.14472988584940017 | ||
z = logpi - b - a | ||
function lgamma end | ||
|
||
# asymptotic series for log(gamma(z)), valid for sufficiently large real(z) or |imag(z)| | ||
@inline function lgamma_asymptotic(z::Complex{Float64}) | ||
zinv = inv(z) | ||
t = zinv*zinv | ||
# coefficients are bernoulli[2:n+1] .// (2*(1:n).*(2*(1:n) - 1)) | ||
return (z-0.5)*log(z) - z + 9.1893853320467274178032927e-01 + # <-- log(2pi)/2 | ||
zinv*@evalpoly(t, 8.3333333333333333333333368e-02,-2.7777777777777777777777776e-03,7.9365079365079365079365075e-04,-5.9523809523809523809523806e-04,8.4175084175084175084175104e-04,-1.9175269175269175269175262e-03,6.4102564102564102564102561e-03,-2.9550653594771241830065352e-02) | ||
end | ||
|
||
# Compute the logΓ(z) function using a combination of the asymptotic series, | ||
# the Taylor series around z=1 and z=2, the reflection formula, and the shift formula. | ||
# Many details of these techniques are discussed in D. E. G. Hare, | ||
# "Computing the principal branch of log-Gamma," J. Algorithms 25, pp. 221-236 (1997), | ||
# and similar techniques are used (in a somewhat different way) by the | ||
# SciPy loggamma function. The key identities are also described | ||
# at http://functions.wolfram.com/GammaBetaErf/LogGamma/ | ||
function lgamma(z::Complex{Float64}) | ||
x = real(z) | ||
y = imag(z) | ||
yabs = abs(y) | ||
if !isfinite(x) || !isfinite(y) # Inf or NaN | ||
if isinf(x) && isfinite(y) | ||
return Complex(x, x > 0 ? (y == 0 ? y : copysign(Inf, y)) : copysign(Inf, -y)) | ||
elseif isfinite(x) && isinf(y) | ||
return Complex(-Inf, y) | ||
else | ||
return Complex(NaN, NaN) | ||
end | ||
elseif x > 7 || yabs > 7 # use the Stirling asymptotic series for sufficiently large x or |y| | ||
return lgamma_asymptotic(z) | ||
elseif x < 0.1 # use reflection formula to transform to x > 0 | ||
if x == 0 && y == 0 # return Inf with the correct imaginary part for z == 0 | ||
return Complex(Inf, signbit(x) ? copysign(oftype(x, pi), -y) : -y) | ||
end | ||
# the 2pi * floor(...) stuff is to choose the correct branch cut for log(sinpi(z)) | ||
return Complex(1.1447298858494001741434262, # log(pi) | ||
copysign(6.2831853071795864769252842, y) # 2pi | ||
* floor(0.5*x+0.25)) - | ||
log(sinpi(z)) - lgamma(1-z) | ||
elseif abs(x - 1) + yabs < 0.1 | ||
# taylor series around zero at z=1 | ||
# ... coefficients are [-eulergamma; [(-1)^k * zeta(k)/k for k in 2:15]] | ||
w = Complex(x - 1, y) | ||
return w * @evalpoly(w, -5.7721566490153286060651188e-01,8.2246703342411321823620794e-01,-4.0068563438653142846657956e-01,2.705808084277845478790009e-01,-2.0738555102867398526627303e-01,1.6955717699740818995241986e-01,-1.4404989676884611811997107e-01,1.2550966952474304242233559e-01,-1.1133426586956469049087244e-01,1.000994575127818085337147e-01,-9.0954017145829042232609344e-02,8.3353840546109004024886499e-02,-7.6932516411352191472827157e-02,7.1432946295361336059232779e-02,-6.6668705882420468032903454e-02) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. likewise |
||
elseif abs(x - 2) + yabs < 0.1 | ||
# taylor series around zero at z=2 | ||
# ... coefficients are [1-eulergamma; [(-1)^k * (zeta(k)-1)/k for k in 2:12]] | ||
w = Complex(x - 2, y) | ||
return w * @evalpoly(w, 4.2278433509846713939348812e-01,3.2246703342411321823620794e-01,-6.7352301053198095133246196e-02,2.0580808427784547879000897e-02,-7.3855510286739852662729527e-03,2.8905103307415232857531201e-03,-1.1927539117032609771139825e-03,5.0966952474304242233558822e-04,-2.2315475845357937976132853e-04,9.9457512781808533714662972e-05,-4.4926236738133141700224489e-05,2.0507212775670691553131246e-05) | ||
end | ||
# use recurrence relation lgamma(z) = lgamma(z+1) - log(z) to shift to x > 7 for asymptotic series | ||
shiftprod = Complex(x,yabs) | ||
x += 1 | ||
sb = false # == signbit(imag(shiftprod)) == signbit(yabs) | ||
# To use log(product of shifts) rather than sum(logs of shifts), | ||
# we need to keep track of the number of + to - sign flips in | ||
# imag(shiftprod), as described in Hare (1997), proposition 2.2. | ||
signflips = 0 | ||
while x <= 7 | ||
shiftprod *= Complex(x,yabs) | ||
sb′ = signbit(imag(shiftprod)) | ||
signflips += sb′ & (sb′ != sb) | ||
sb = sb′ | ||
x += 1 | ||
end | ||
shift = log(shiftprod) | ||
if signbit(y) # if y is negative, conjugate the shift | ||
shift = Complex(real(shift), signflips*-6.2831853071795864769252842 - imag(shift)) | ||
else | ||
z = clgamma_lanczos(z) | ||
shift = Complex(real(shift), imag(shift) + signflips*6.2831853071795864769252842) | ||
end | ||
complex(real(z), angle_restrict_symm(imag(z))) | ||
return lgamma_asymptotic(Complex(x,y)) - shift | ||
end | ||
lgamma{T<:Union{Integer,Rational}}(z::Complex{T}) = lgamma(float(z)) | ||
lgamma{T<:Union{Float32,Float16}}(z::Complex{T}) = Complex{T}(lgamma(Complex{Float64}(z))) | ||
|
||
gamma(z::Complex) = exp(lgamma(z)) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrap this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I felt like it was actually easier to read the code when these lines were left unwrapped, especially on github or in an editor like Atom that does not line-wrap for you, because then you don't have several lines of indecipherable numbers interrupting the flow of the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code is several lines of numbers. The flow is line continuation / indent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, fine