Skip to content
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

Polylogarithm and assorted functions #30

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions examples/bernoulli_ex.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Example of Bernoulli functions
# Replicate (approximately) the plot from http://mathworld.wolfram.com/BernoulliPolynomial.html

using Plots; plotlyjs()
default(show = true) # shouldn't need display commands

using SpecialFunctions
const SF = SpecialFunctions

x = -0.4:0.01:1.0
plt = plot( x, SF.bernoulli.(1,x),
label="B_1(x)", legendfont=font(20, "Courier"), legend=:bottomright,
xaxis = ("x", font(20, "Courier")),
yaxis = ("", font(20, "Courier"), (-0.15, 0.15), -0.15:0.05:1.5),
size = (1200, 800)
)

for i=2:5
plot!( x, SF.bernoulli.(i,x), label="B_$i(x)")
end

savefig("bernoulli_ex.pdf")
Binary file added examples/bernoulli_ex.pdf
Binary file not shown.
36 changes: 36 additions & 0 deletions examples/li_ex.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# do plots of the Li function,



using Plots
plotlyjs()

default(show = true) # shouldn't need display commands
theme(:solarized_light)

using SpecialFunctions
const SF = SpecialFunctions

using LaTeXStrings

# replicate (approximately) the plot from http://mathworld.wolfram.com/Polylogarithm.html
x = -2.0 : 0.01 : 1.0
plt = plot( x, real(SF.Li.(-2,x)),
label="Li$(Char(8331))$(Char(8322))(x)",
legendfont=font(20, "Courier"), legend=:bottomright,
xaxis = ("x", font(20, "Courier")),
yaxis = ("", font(20, "Courier"), (-1, 1), -1.0:0.25:1.0),
size = (1200, 800)
);

for i=-1:-1
plot!( x, real(SF.Li.(i,x)), label="Li$(Char(8331))$(Char(8320 - i))(x)");
end
for i=0:2
plot!( x, real(SF.Li.(i,x)), label="Li$(Char(8320 + i))(x)");
end
gui()

sleep(1)
savefig("li_ex.pdf")

Binary file added examples/li_ex.pdf
Binary file not shown.
35 changes: 35 additions & 0 deletions examples/li_ex2.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# do plots of the Li function,
# this one takes a while

using Plots; plotlyjs()
using PlotUtils # already from Plots, but as a reminder
using PlotThemes # already from Plots, but as a reminder
default(show = true) # shouldn't need display commands

using SpecialFunctions
const SF = SpecialFunctions

# one of the figures from https://en.wikipedia.org/wiki/Polylogarithm
# presuming this is a phase plot (there is not scale)
# step = 0.02 $ took about 200 seconds
# step = 0.01 # took about 11 minutes
step = 0.0025
xs = -2.0 : step : 2.0
ys = -2.0 : step : 2.0
Z = [Complex(x, y) for x in xs, y in ys]'
tic()
L = SF.Li.(-2,Z)
theTime = toc()
a = angle(L)/pi
i = abs(imag(L)) .< 1.0e-8
a[i] = (-sign(real(L[i]))+1.0)/2.0
j = abs(L) .< 1.0e-8
a[j] = NaN
heatmap(xs, ys, a,
xaxis = ("real", font(20, "Courier")),
yaxis = ("imag", font(20, "Courier")),
size = (800, 800),
color = :Spectral)

sleep(1)
savefig("li_ex2.pdf")
38 changes: 38 additions & 0 deletions examples/li_ex2.jl~
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# do plots of the Li function,



using Plots; plotlyjs()
default(show = true) # shouldn't need display commands

using SpecialFunctions
const SF = SpecialFunctions

# replicate (approximately) the plot from http://mathworld.wolfram.com/Polylogarithm.html
x = -2.0 : 0.01 : 1.0
plt = plot( x, real(SF.Li.(-2,x)),
label="Li_{-2}(x)", legendfont=font(20, "Courier"), legend=:bottomright,
xaxis = ("x", font(20, "Courier")),
yaxis = ("", font(20, "Courier"), (-1, 1), -1.0:0.25:1.0),
size = (1200, 800)
)

for i=-1:2
plot!( x, real(SF.Li.(i,x)), label="Li_{$i}(x)")
end

sleep(1)
savefig("li_ex.pdf")


# one of the figures from https://en.wikipedia.org/wiki/Polylogarithm
step = 0.01
xs = -2.0 : step : 2.0
ys = -2.0 : step : 2.0
Z = [Complex(x, y) for x in xs, y in ys]'
tic()
L = SF.Li.(-2,Z)
theTime = toc()
heatmap(xs, ys, angle(L),
size = (800, 800), reuse=false)

Binary file added examples/li_ex2.pdf
Binary file not shown.
10 changes: 9 additions & 1 deletion src/SpecialFunctions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ if VERSION >= v"0.6.0-dev.2767"
hankelh1x,
hankelh2,
hankelh2x,
zeta
zeta,
Li,
polylog,
bernoulli,
harmonic
end
end

Expand All @@ -70,4 +74,8 @@ include("erf.jl")
include("gamma.jl")
include("deprecated.jl")

include("harmonic.jl")
include("bernoulli.jl")
include("li.jl")

end # module
118 changes: 118 additions & 0 deletions src/bernoulli.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#
# Bernoulli numbers (as rationals up to B_{34}) and Bernoulli polynomials
# The alg. for the latter could use some optimisation, as
# (1) the choice of domains for the different approaches was a bit arbitrary
# (2) the errors start to creep up for larger n, looks roughly like 1 order per increase in n of 5
#

"""
bernoulli(n)

created: Tue May 23 2017
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically don't include this information in docstrings, as its readily available in the Git history.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Will delete.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

email: [email protected]

Calculates (first-kind or NIST) Bernoulli numbers, B_n (or at least the first 35 of them)
e.g., see

+ http://mathworld.wolfram.com/BernoulliNumber.html
+ https://en.wikipedia.org/wiki/Bernoulli_number
+ http://dlmf.nist.gov/24

N.B. Bernoulli numbers of second kind only seem to differ in that B_1 = + 1/2 (instead of -1/2)

## Arguments
* `n::Integer`: the index into the series, n=0,1,2,3,...

## Examples
```jldoctest
julia> bernoulli(6)
1 // 42
```
"""
function bernoulli(n::Int)
# this just does a lookup -- seemed like it would be easier to code and faster
# for the size of numbers I am working with
if n<0
warn("n should be > 0")
throw(DomainError)
end
if n>34
warn("If n > 34, then the numerator needs Int128 at least, and worse, so this code is not the code you want. Try using bernoulli(n, 0.0) to get a floating point approximation to the result.")
throw(DomainError)
end

# Denominator of Bernoulli number B_n
# http://oeis.org/A027642
D = [2, 6, 1, 30, 1, 42, 1, 30, 1, 66, 1, 2730, 1, 6, 1, 510, 1, 798, 1, 330, 1, 138, 1, 2730, 1, 6, 1, 870, 1, 14322, 1, 510, 1, 6, 1, 1919190, 1, 6, 1, 13530, 1, 1806, 1, 690, 1, 282, 1, 46410, 1, 66, 1, 1590, 1, 798, 1, 870, 1, 354, 1, 56786730]

# Numerator of Bernoulli number B_n (storing 62 of these because they are easy)
# http://oeis.org/A027641
N = [-1, 1, 0, -1, 0, 1, 0, -1, 0, 5, 0, -691, 0, 7, 0, -3617, 0, 43867, 0, -174611, 0, 854513, 0, -236364091, 0, 8553103, 0, -23749461029, 0, 8615841276005, 0, -7709321041217, 0, 2577687858367, 1]

if n==0
return 1
else
return N[n] // D[n]
end
end

# get the Bernoulli polynomials from the Hurwitz-zeta function (which is already defined)
#
#
"""
bernoulli(n, x)

created: Tue May 23 2017
email: [email protected]
(c) M Roughan, 2017

Calculates Bernoulli polynomials from the Hurwitz-zeta function using
``ζ(-n,x) = -B_{n+1}(x)/(n+1), for Re(x)>0 ``
Its probably not the fastest approach, but we get it almost for free.

e.g., see

+ https://en.wikipedia.org/wiki/Bernoulli_polynomials
+ http://dlmf.nist.gov/24

## Arguments
* `n::Integer`: the index into the series, n=0,1,2,3,...
* `x::Real`: the point at which to calculate the polynomial

## Examples
```jldoctest
julia> bernoulli(6, 1.2)
0.008833523809524069
```
"""
function bernoulli(n::Int, x::Real)
if n<0
warn("n should be > 0")
throw(DomainError)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just throw an error, don't print a warning.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be throw(DomainError()).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have fixed, and added domain check for harmonics.

end
if n == 0
return 1 # zeta formula doesn't hold for n=0, so return explicit value
elseif n == 1
return x-0.5 # get some easy cases out of the way quickly
elseif n == 2
return x^2 - x + 1.0/6.0
elseif n == 3
return x^3 - 1.5*x^2 + 0.5*x
elseif n == 4
return x^4 - 2.0*x^3 + x^2 - 1/30.0
elseif n == 5
return x^5 - 2.5*x^4 +(5.0/3.0)*x^3 - x/6.0
end

if n <= 34
# direct summation for reasonably small values of coefficients
k = 0:n
return sum( binomial.(n,k) .* bernoulli.(k) .* x.^(n-k) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just write out the loop, don't use sum of a broadcast.

You should time this to see if it is actually faster than a zeta call.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely faster to use zeta. Have changed.

elseif x > 0
return -n*zeta(1-n, x)
else
# comments in the gamma.jl note that identity with zeta(s,z) only works for Re(z)>0
# so exploit symmetries in B_n(x) to compute recursively for x<=0
return bernoulli(n, x+1) - n*x^(n-1)
end
end
9 changes: 9 additions & 0 deletions src/gamma.jl
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,15 @@ function zeta(s::ComplexOrReal{Float64}, z::ComplexOrReal{Float64})
return ζ
end


"""
hurwitz_zeta(s, z)

An alias for zeta(s, z)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No indent here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will fix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

"""
hurwitz_zeta(s::ComplexOrReal{Float64}, z::ComplexOrReal{Float64}) = zeta(s, z)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is an issue with how Julia does docs, but in my initial reading I hadn't realised that Julia had a Hurwitz zeta function already. I know the information is there, but it wasn't obvious for a newbie (like me). If its not standard for Julia to include aliases like this let me know, but it seemed like a trivial way to make life easier for users?

I did the same with Li=polylog. I realised after writing code that the naming convention for Julia would usually give us something like polylog?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, aliases like this aren't typically provided. If it isn't obvious what something is, that means the documentation should be better. 🙂

Typical Julia conventions prefer more descriptive names for functions, and function names are typically all lowercase. Since polylog is the more descriptive name (and jives with polygamma), I think we should just stick to that one.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed Li to polylog.



"""
polygamma(m, x)

Expand Down
48 changes: 48 additions & 0 deletions src/harmonic.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""
harmonic(n)

Calculates harmonic numbers
e.g., see http://mathworld.wolfram.com/HarmonicNumber.html

## Arguments
* `n::Integer`: index of the Harmonic number to calculate

## Examples
```jldoctest
julia> harmonic(2)
1.5
```
"""
function harmonic(n::Integer)
# γ = euler_mascheroni_const = 0.577215664901532860606512090082402431042 # http://oeis.org/A001620
if n <=10
# get exact values for small n
return sum( 1.0./(1:n) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write out loops, don't use sum of a broadcast. This isn't Matlab.

See Why vectorized code is not as fast as it could be and here for example. You are allocating a temporary array and then looping over it with sum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Too much history programming matlab.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have fixed. Very interesting reading. Not sure I understand everything yet, but I get the main point.

It sounds like there are new things happening for v0.6, which could be cool.

else
# numerical approximation for larger n
return γ + digamma(n+1)
end
end


"""
harmonic(n,r)

Calculates generalized harmonic numbers
e.g., see http://mathworld.wolfram.com/HarmonicNumber.html

## Arguments
* `n::Integer`: index 1 of the Harmonic number to calculate
* `r::Real`: index 2 of the Harmonic number to calculate

It should be possible to extend this to complex r, but hey.

## Examples
```jldoctest
julia> harmonic(2,1)
1.5
```
"""
function harmonic(n::Integer, r::Real)
sum( 1.0 ./ (1:n).^r )
end
Loading