-
Notifications
You must be signed in to change notification settings - Fork 3
/
lerch.py
148 lines (111 loc) · 4.31 KB
/
lerch.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Module of routines for computing the Lerch Trancident and related
functions.
This file is only used to check the polynomial implementation in polylog.py
for correctness.
Written by Michael Forbes.
"""
#__all__ = ['LerchPhi','Li']
from numpy import isreal,iscomplex, inf, finfo
from scipy.integrate import quad
from scipy.special import gamma
import math
import cmath
import numpy as np
_eps = finfo(type(1.0)).eps
_epsrel = _eps*64
_epsabs = 0.0
def LerchPhi_real(z,s,a):
"""Return the Lerch Trancident Phi(z,s,a) for real arguments (see
LerchPhi for more details.
Computes the result using the integral representation
Phi(z,s,a) = 1/\Gamma(s)*\int_0^\infty dt t^{s-1}e^{-at}/(1-ze^{-t})
The following analysis is for real arguments: complex values may
mess up the integration.
The asymptotics of the integrand are:
Near t == 0:
* If z != 1: t^{s-1}
* If z == 1: t^{s-2}
Near t == inf:
* t^{s-1}e^{-at}
In addition, if 1 < z then there is an additional pole at
t = t0 = -log(1/z): ~ 1/(t-t0)
The asymptotics dictate that the integral is convergent iff:
(0 < a) and ((z != 1 and 0 < s) or (z == 1 and 1 < s))
For speed, however, we assume that the calling routine has checked
for convergence.
The Lerch function is actually defined for smaller
The integrand only switches sign if z > 1 in which case we use the
'cauchy' form of the adaptive quad integrator to deal with the
pole. The only potential for roundoff is in the computation of
the denominator if z*exp(-t) ~ 1.
The integrand is:
exp((s-1)*log(t)-a*t)/(1-z*exp(-t))
"""
# We break up the integral using 1/a as the length scale
# because quad can't handle both infinite range and a
# singularity.
b0 = max(1./a,1.0)
if z < 1.0:
# Here we use the asymptotic forms of quad with power-law
# divergences at the endpoints:
# integrand = (t-a0)**alpha*(t-b0)**beta*f()
# where a0 and b0 are the endpoints.
#
alpha = s-1.0
beta = 0.0
# Determine cutoff for approximation
cutoff = (720.0*_eps)**(0.25)
def f1(t,s=s,a=a,cutoff=cutoff,exp=math.exp):
# This should be computed robustly for small z and t
return exp(-a*t)/(1.0-z*exp(-t))
elif z == 1.0:
# Here we use the asymptotic forms of quad with power-law
# divergences at the endpoints:
# integrand = (t-a0)**alpha*(t-b0)**beta*f()
# where a0 and b0 are the endpoints.
#
# We break up the integral using 1/a as the length scale
# because quad can't handle both infinite range and a
# singularity.
alpha = s-2.0
beta = 0.0
# Determine cutoff for approximation
cutoff = (720.0*_eps)**(0.25)
def f1(t,s=s,a=a,cutoff=cutoff,exp=math.exp):
# Compute t/(1.0-exp(-t)) robustly using series expansion:
# 1 + t/2 + t**2/12 - t**4/720
# It alternates beyond this point.
if t < cutoff:
factor = 1.0 + t*(0.5 + t/12.0)
else:
factor = t/(1.0-exp(-t))
return exp(-a*t)*factor
else:
raise Exception("Not supported yet.")
(result1,err1) = quad(f1,0,b0,epsabs=_epsabs,epsrel=_epsrel,
weight='alg',wvar=(alpha,beta))
def f(t,s=s,a=a,exp=math.exp,log=math.log):
"""Integrand"""
return exp((s - 1.0)*log(t) - a*t)/(1.0 - z*exp(-t))
(result2,err2) = quad(f,b0,inf,epsabs=_epsabs,epsrel=_epsrel)
result = result1+result2
err = math.sqrt(err1*err1+err2*err2)
return result/gamma(s)
def LerchPhi(z,s,a):
"""Return the Lerch Trancident function Phi(z,s,a) on a restricted
domain.
Phi(z,s,a) = \sum_{n=0}^\infty \frac{z^n}{(n+a)^s}
Computes the result using the integral representation
Phi(z,s,a) = 1/\Gamma(s)*\int_0^\infty dt t^{s-1}e^{-at}/(1-ze^{-t})
"""
#Special Cases:
if isreal(a) and isreal(s) and isreal(z) and \
((0 < a) and (((0 < s) and (z < 1)) or
((1 < s) and (1 == z)))):
return LerchPhi_real(z,s,a)
else:
raise Warning("Lerch arguments "+`(z,s,a)`+"not supported.")
def Li(s,z):
"""Return the polylogarithm Li_s(z)."""
return z*LerchPhi(z,s,1.0)
Li = np.vectorize(Li)