OpenRAM/compiler/gdsMill/mpmath/functions/hypergeometric.py

2061 lines
70 KiB
Python
Raw Normal View History

2016-11-08 18:57:35 +01:00
from functions import defun, defun_wrapped
def _check_need_perturb(ctx, terms, prec, discard_known_zeros):
perturb = recompute = False
extraprec = 0
discard = []
for term_index, term in enumerate(terms):
w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term
have_singular_nongamma_weight = False
# Avoid division by zero in leading factors (TODO:
# also check for near division by zero?)
for k, w in enumerate(w_s):
if not w:
if ctx.re(c_s[k]) <= 0 and c_s[k]:
perturb = recompute = True
have_singular_nongamma_weight = True
pole_count = [0, 0, 0]
# Check for gamma and series poles and near-poles
for data_index, data in enumerate([alpha_s, beta_s, b_s]):
for i, x in enumerate(data):
n, d = ctx.nint_distance(x)
# Poles
if n > 0:
continue
if d == ctx.ninf:
# OK if we have a polynomial
# ------------------------------
ok = False
if data_index == 2:
for u in a_s:
if ctx.isnpint(u) and u >= int(n):
ok = True
break
if ok:
continue
pole_count[data_index] += 1
# ------------------------------
#perturb = recompute = True
#return perturb, recompute, extraprec
elif d < -4:
extraprec += -d
recompute = True
if discard_known_zeros and pole_count[1] > pole_count[0] + pole_count[2] \
and not have_singular_nongamma_weight:
discard.append(term_index)
elif sum(pole_count):
perturb = recompute = True
return perturb, recompute, extraprec, discard
_hypercomb_msg = """
hypercomb() failed to converge to the requested %i bits of accuracy
using a working precision of %i bits. The function value may be zero or
infinite; try passing zeroprec=N or infprec=M to bound finite values between
2^(-N) and 2^M. Otherwise try a higher maxprec or maxterms.
"""
@defun
def hypercomb(ctx, function, params=[], discard_known_zeros=True, **kwargs):
orig = ctx.prec
sumvalue = ctx.zero
dist = ctx.nint_distance
ninf = ctx.ninf
orig_params = params[:]
verbose = kwargs.get('verbose', False)
maxprec = kwargs.get('maxprec', ctx._default_hyper_maxprec(orig))
kwargs['maxprec'] = maxprec # For calls to hypsum
zeroprec = kwargs.get('zeroprec')
infprec = kwargs.get('infprec')
perturbed_reference_value = None
hextra = 0
try:
while 1:
ctx.prec += 10
if ctx.prec > maxprec:
raise ValueError(_hypercomb_msg % (orig, ctx.prec))
orig2 = ctx.prec
params = orig_params[:]
terms = function(*params)
if verbose:
print
print "ENTERING hypercomb main loop"
print "prec =", ctx.prec
print "hextra", hextra
perturb, recompute, extraprec, discard = \
_check_need_perturb(ctx, terms, orig, discard_known_zeros)
ctx.prec += extraprec
if perturb:
if "hmag" in kwargs:
hmag = kwargs["hmag"]
elif ctx._fixed_precision:
hmag = int(ctx.prec*0.3)
else:
hmag = orig + 10 + hextra
h = ctx.ldexp(ctx.one, -hmag)
ctx.prec = orig2 + 10 + hmag + 10
for k in range(len(params)):
params[k] += h
# Heuristically ensure that the perturbations
# are "independent" so that two perturbations
# don't accidentally cancel each other out
# in a subtraction.
h += h/(k+1)
if recompute:
terms = function(*params)
if discard_known_zeros:
terms = [term for (i, term) in enumerate(terms) if i not in discard]
if not terms:
return ctx.zero
evaluated_terms = []
for term_index, term_data in enumerate(terms):
w_s, c_s, alpha_s, beta_s, a_s, b_s, z = term_data
if verbose:
print
print " Evaluating term %i/%i : %iF%i" % \
(term_index+1, len(terms), len(a_s), len(b_s))
print " powers", ctx.nstr(w_s), ctx.nstr(c_s)
print " gamma", ctx.nstr(alpha_s), ctx.nstr(beta_s)
print " hyper", ctx.nstr(a_s), ctx.nstr(b_s)
print " z", ctx.nstr(z)
v = ctx.hyper(a_s, b_s, z, **kwargs)
for a in alpha_s: v *= ctx.gamma(a)
for b in beta_s: v /= ctx.gamma(b)
for w, c in zip(w_s, c_s): v *= ctx.power(w, c)
if verbose:
print " Value:", v
evaluated_terms.append(v)
if len(terms) == 1 and (not perturb):
sumvalue = evaluated_terms[0]
break
if ctx._fixed_precision:
sumvalue = ctx.fsum(evaluated_terms)
break
sumvalue = ctx.fsum(evaluated_terms)
term_magnitudes = [ctx.mag(x) for x in evaluated_terms]
max_magnitude = max(term_magnitudes)
sum_magnitude = ctx.mag(sumvalue)
cancellation = max_magnitude - sum_magnitude
if verbose:
print
print " Cancellation:", cancellation, "bits"
print " Increased precision:", ctx.prec - orig, "bits"
precision_ok = cancellation < ctx.prec - orig
if zeroprec is None:
zero_ok = False
else:
zero_ok = max_magnitude - ctx.prec < -zeroprec
if infprec is None:
inf_ok = False
else:
inf_ok = max_magnitude > infprec
if precision_ok and (not perturb) or ctx.isnan(cancellation):
break
elif precision_ok:
if perturbed_reference_value is None:
hextra += 20
perturbed_reference_value = sumvalue
continue
elif ctx.mag(sumvalue - perturbed_reference_value) <= \
ctx.mag(sumvalue) - orig:
break
elif zero_ok:
sumvalue = ctx.zero
break
elif inf_ok:
sumvalue = ctx.inf
break
elif 'hmag' in kwargs:
break
else:
hextra *= 2
perturbed_reference_value = sumvalue
# Increase precision
else:
increment = min(max(cancellation, orig//2), max(extraprec,orig))
ctx.prec += increment
if verbose:
print " Must start over with increased precision"
continue
finally:
ctx.prec = orig
return +sumvalue
@defun
def hyper(ctx, a_s, b_s, z, **kwargs):
"""
Hypergeometric function, general case.
"""
z = ctx.convert(z)
p = len(a_s)
q = len(b_s)
a_s = map(ctx._convert_param, a_s)
b_s = map(ctx._convert_param, b_s)
# Reduce degree by eliminating common parameters
if kwargs.get('eliminate', True):
i = 0
while i < q and a_s:
b = b_s[i]
if b in a_s:
a_s.remove(b)
b_s.remove(b)
p -= 1
q -= 1
else:
i += 1
# Handle special cases
if p == 0:
if q == 1: return ctx._hyp0f1(b_s, z, **kwargs)
elif q == 0: return ctx.exp(z)
elif p == 1:
if q == 1: return ctx._hyp1f1(a_s, b_s, z, **kwargs)
elif q == 2: return ctx._hyp1f2(a_s, b_s, z, **kwargs)
elif q == 0: return ctx._hyp1f0(a_s[0][0], z)
elif p == 2:
if q == 1: return ctx._hyp2f1(a_s, b_s, z, **kwargs)
elif q == 2: return ctx._hyp2f2(a_s, b_s, z, **kwargs)
elif q == 3: return ctx._hyp2f3(a_s, b_s, z, **kwargs)
elif q == 0: return ctx._hyp2f0(a_s, b_s, z, **kwargs)
elif p == q+1:
return ctx._hypq1fq(p, q, a_s, b_s, z, **kwargs)
elif p > q+1 and not kwargs.get('force_series'):
return ctx._hyp_borel(p, q, a_s, b_s, z, **kwargs)
coeffs, types = zip(*(a_s+b_s))
return ctx.hypsum(p, q, types, coeffs, z, **kwargs)
@defun
def hyp0f1(ctx,b,z,**kwargs):
return ctx.hyper([],[b],z,**kwargs)
@defun
def hyp1f1(ctx,a,b,z,**kwargs):
return ctx.hyper([a],[b],z,**kwargs)
@defun
def hyp1f2(ctx,a1,b1,b2,z,**kwargs):
return ctx.hyper([a1],[b1,b2],z,**kwargs)
@defun
def hyp2f1(ctx,a,b,c,z,**kwargs):
return ctx.hyper([a,b],[c],z,**kwargs)
@defun
def hyp2f2(ctx,a1,a2,b1,b2,z,**kwargs):
return ctx.hyper([a1,a2],[b1,b2],z,**kwargs)
@defun
def hyp2f3(ctx,a1,a2,b1,b2,b3,z,**kwargs):
return ctx.hyper([a1,a2],[b1,b2,b3],z,**kwargs)
@defun
def hyp2f0(ctx,a,b,z,**kwargs):
return ctx.hyper([a,b],[],z,**kwargs)
@defun
def hyp3f2(ctx,a1,a2,a3,b1,b2,z,**kwargs):
return ctx.hyper([a1,a2,a3],[b1,b2],z,**kwargs)
@defun_wrapped
def _hyp1f0(ctx, a, z):
return (1-z) ** (-a)
@defun
def _hyp0f1(ctx, b_s, z, **kwargs):
(b, btype), = b_s
if z:
magz = ctx.mag(z)
else:
magz = 0
if magz >= 8 and not kwargs.get('force_series'):
try:
# http://functions.wolfram.com/HypergeometricFunctions/
# Hypergeometric0F1/06/02/03/0004/
# We don't need hypercomb because the only possible singularity
# occurs when the value is undefined. However, we should perhaps
# still check for cancellation...
# TODO: handle the all-real case more efficiently!
# TODO: figure out how much precision is needed (exponential growth)
orig = ctx.prec
try:
ctx.prec += 12 + magz//2
w = ctx.sqrt(-z)
jw = ctx.j*w
u = 1/(4*jw)
c = ctx.mpq_1_2 - b
E = ctx.exp(2*jw)
H1 = (-jw)**c/E*ctx.hyp2f0(b-ctx.mpq_1_2, ctx.mpq_3_2-b, -u,
force_series=True)
H2 = (jw)**c*E*ctx.hyp2f0(b-ctx.mpq_1_2, ctx.mpq_3_2-b, u,
force_series=True)
v = ctx.gamma(b)/(2*ctx.sqrt(ctx.pi))*(H1 + H2)
finally:
ctx.prec = orig
if ctx._is_real_type(b) and ctx._is_real_type(z):
v = ctx._re(v)
return +v
except ctx.NoConvergence:
pass
return ctx.hypsum(0, 1, (btype,), [b], z, **kwargs)
@defun
def _hyp1f1(ctx, a_s, b_s, z, **kwargs):
(a, atype), = a_s
(b, btype), = b_s
if not z:
return ctx.one+z
magz = ctx.mag(z)
if magz >= 7 and not (ctx.isint(a) and ctx.re(a) <= 0):
if ctx.isinf(z):
if ctx.sign(a) == ctx.sign(b) == ctx.sign(z) == 1:
return ctx.inf
return ctx.nan * z
try:
try:
ctx.prec += magz
sector = ctx._im(z) < 0 and ctx._re(z) <= 0
def h(a,b):
if sector:
E = ctx.expjpi(ctx.fneg(a, exact=True))
else:
E = ctx.expjpi(a)
rz = 1/z
T1 = ([E,z], [1,-a], [b], [b-a], [a, 1+a-b], [], -rz)
T2 = ([ctx.exp(z),z], [1,a-b], [b], [a], [b-a, 1-a], [], rz)
return T1, T2
v = ctx.hypercomb(h, [a,b], force_series=True)
if ctx._is_real_type(a) and ctx._is_real_type(b) and ctx._is_real_type(z):
v = ctx._re(v)
return +v
except ctx.NoConvergence:
pass
finally:
ctx.prec -= magz
v = ctx.hypsum(1, 1, (atype, btype), [a, b], z, **kwargs)
return v
def _hyp2f1_gosper(ctx,a,b,c,z,**kwargs):
# Use Gosper's recurrence
# See http://www.math.utexas.edu/pipermail/maxima/2006/000126.html
_a,_b,_c,_z = a, b, c, z
orig = ctx.prec
maxprec = kwargs.get('maxprec', 100*orig)
extra = 10
while 1:
ctx.prec = orig + extra
#a = ctx.convert(_a)
#b = ctx.convert(_b)
#c = ctx.convert(_c)
z = ctx.convert(_z)
d = ctx.mpf(0)
e = ctx.mpf(1)
f = ctx.mpf(0)
k = 0
# Common subexpression elimination, unfortunately making
# things a bit unreadable. The formula is quite messy to begin
# with, though...
abz = a*b*z
ch = c * ctx.mpq_1_2
c1h = (c+1) * ctx.mpq_1_2
nz = 1-z
g = z/nz
abg = a*b*g
cba = c-b-a
z2 = z-2
tol = -ctx.prec - 10
nstr = ctx.nstr
nprint = ctx.nprint
mag = ctx.mag
maxmag = ctx.ninf
while 1:
kch = k+ch
kakbz = (k+a)*(k+b)*z / (4*(k+1)*kch*(k+c1h))
d1 = kakbz*(e-(k+cba)*d*g)
e1 = kakbz*(d*abg+(k+c)*e)
ft = d*(k*(cba*z+k*z2-c)-abz)/(2*kch*nz)
f1 = f + e - ft
maxmag = max(maxmag, mag(f1))
if mag(f1-f) < tol:
break
d, e, f = d1, e1, f1
k += 1
cancellation = maxmag - mag(f1)
if cancellation < extra:
break
else:
extra += cancellation
if extra > maxprec:
raise ctx.NoConvergence
return f1
@defun
def _hyp2f1(ctx, a_s, b_s, z, **kwargs):
(a, atype), (b, btype) = a_s
(c, ctype), = b_s
if z == 1:
# TODO: the following logic can be simplified
convergent = ctx.re(c-a-b) > 0
finite = (ctx.isint(a) and a <= 0) or (ctx.isint(b) and b <= 0)
zerodiv = ctx.isint(c) and c <= 0 and not \
((ctx.isint(a) and c <= a <= 0) or (ctx.isint(b) and c <= b <= 0))
#print "bz", a, b, c, z, convergent, finite, zerodiv
# Gauss's theorem gives the value if convergent
if (convergent or finite) and not zerodiv:
return ctx.gammaprod([c, c-a-b], [c-a, c-b], _infsign=True)
# Otherwise, there is a pole and we take the
# sign to be that when approaching from below
# XXX: this evaluation is not necessarily correct in all cases
return ctx.hyp2f1(a,b,c,1-ctx.eps*2) * ctx.inf
# Equal to 1 (first term), unless there is a subsequent
# division by zero
if not z:
# Division by zero but power of z is higher than
# first order so cancels
if c or a == 0 or b == 0:
return 1+z
# Indeterminate
return ctx.nan
# Hit zero denominator unless numerator goes to 0 first
if ctx.isint(c) and c <= 0:
if (ctx.isint(a) and c <= a <= 0) or \
(ctx.isint(b) and c <= b <= 0):
pass
else:
# Pole in series
return ctx.inf
absz = abs(z)
# Fast case: standard series converges rapidly,
# possibly in finitely many terms
if absz <= 0.8 or (ctx.isint(a) and a <= 0 and a >= -1000) or \
(ctx.isint(b) and b <= 0 and b >= -1000):
return ctx.hypsum(2, 1, (atype, btype, ctype), [a, b, c], z, **kwargs)
orig = ctx.prec
try:
ctx.prec += 10
# Use 1/z transformation
if absz >= 1.3:
def h(a,b):
t = ctx.mpq_1-c; ab = a-b; rz = 1/z
T1 = ([-z],[-a], [c,-ab],[b,c-a], [a,t+a],[ctx.mpq_1+ab], rz)
T2 = ([-z],[-b], [c,ab],[a,c-b], [b,t+b],[ctx.mpq_1-ab], rz)
return T1, T2
v = ctx.hypercomb(h, [a,b], **kwargs)
# Use 1-z transformation
elif abs(1-z) <= 0.75:
def h(a,b):
t = c-a-b; ca = c-a; cb = c-b; rz = 1-z
T1 = [], [], [c,t], [ca,cb], [a,b], [1-t], rz
T2 = [rz], [t], [c,a+b-c], [a,b], [ca,cb], [1+t], rz
return T1, T2
v = ctx.hypercomb(h, [a,b], **kwargs)
# Use z/(z-1) transformation
elif abs(z/(z-1)) <= 0.75:
v = ctx.hyp2f1(a, c-b, c, z/(z-1)) / (1-z)**a
# Remaining part of unit circle
else:
v = _hyp2f1_gosper(ctx,a,b,c,z,**kwargs)
finally:
ctx.prec = orig
return +v
@defun
def _hypq1fq(ctx, p, q, a_s, b_s, z, **kwargs):
r"""
Evaluates 3F2, 4F3, 5F4, ...
"""
a_s, a_types = zip(*a_s)
b_s, b_types = zip(*b_s)
a_s = list(a_s)
b_s = list(b_s)
absz = abs(z)
ispoly = False
for a in a_s:
if ctx.isint(a) and a <= 0:
ispoly = True
break
# Direct summation
if absz < 1 or ispoly:
try:
return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs)
except ctx.NoConvergence:
if absz > 1.1 or ispoly:
raise
# Use expansion at |z-1| -> 0.
# Reference: Wolfgang Buhring, "Generalized Hypergeometric Functions at
# Unit Argument", Proc. Amer. Math. Soc., Vol. 114, No. 1 (Jan. 1992),
# pp.145-153
# The current implementation has several problems:
# 1. We only implement it for 3F2. The expansion coefficients are
# given by extremely messy nested sums in the higher degree cases
# (see reference). Is efficient sequential generation of the coefficients
# possible in the > 3F2 case?
# 2. Although the series converges, it may do so slowly, so we need
# convergence acceleration. The acceleration implemented by
# nsum does not always help, so results returned are sometimes
# inaccurate! Can we do better?
# 3. We should check conditions for convergence, and possibly
# do a better job of cancelling out gamma poles if possible.
if z == 1:
# XXX: should also check for division by zero in the
# denominator of the series (cf. hyp2f1)
S = ctx.re(sum(b_s)-sum(a_s))
if S <= 0:
#return ctx.hyper(a_s, b_s, 1-ctx.eps*2, **kwargs) * ctx.inf
return ctx.hyper(a_s, b_s, 0.9, **kwargs) * ctx.inf
if (p,q) == (3,2) and abs(z-1) < 0.05: # and kwargs.get('sum1')
#print "Using alternate summation (experimental)"
a1,a2,a3 = a_s
b1,b2 = b_s
u = b1+b2-a3
initial = ctx.gammaprod([b2-a3,b1-a3,a1,a2],[b2-a3,b1-a3,1,u])
def term(k, _cache={0:initial}):
u = b1+b2-a3+k
if k in _cache:
t = _cache[k]
else:
t = _cache[k-1]
t *= (b1+k-a3-1)*(b2+k-a3-1)
t /= k*(u-1)
_cache[k] = t
return t * ctx.hyp2f1(a1,a2,u,z)
try:
S = ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'),
strict=kwargs.get('strict', True))
return S * ctx.gammaprod([b1,b2],[a1,a2,a3])
except ctx.NoConvergence:
pass
# Try to use convergence acceleration on and close to the unit circle.
# Problem: the convergence acceleration degenerates as |z-1| -> 0,
# except for special cases. Everywhere else, the Shanks transformation
# is very efficient.
if absz < 1.1 and ctx._re(z) <= 1:
def term(k, _cache={0:ctx.one}):
k = int(k)
if k in _cache:
return _cache[k]
t = _cache[k-1]
m = k-1
for j in xrange(p): t *= (a_s[j]+m)
for j in xrange(q): t /= (b_s[j]+m)
t *= z
t /= k
_cache[k] = t
return t
return ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'),
strict=kwargs.get('strict', True))
# Use 1/z transformation
# http://functions.wolfram.com/HypergeometricFunctions/
# HypergeometricPFQ/06/01/05/02/0004/
def h(*args):
a_s = list(args[:p])
b_s = list(args[p:])
Ts = []
recz = ctx.one/z
negz = ctx.fneg(z, exact=True)
for k in range(q+1):
ak = a_s[k]
C = [negz]
Cp = [-ak]
Gn = b_s + [ak] + [a_s[j]-ak for j in range(q+1) if j != k]
Gd = a_s + [b_s[j]-ak for j in range(q)]
Fn = [ak] + [ak-b_s[j]+1 for j in range(q)]
Fd = [1-a_s[j]+ak for j in range(q+1) if j != k]
Ts.append((C, Cp, Gn, Gd, Fn, Fd, recz))
return Ts
return ctx.hypercomb(h, a_s+b_s, **kwargs)
@defun
def _hyp_borel(ctx, p, q, a_s, b_s, z, **kwargs):
if a_s:
a_s, a_types = zip(*a_s)
a_s = list(a_s)
else:
a_s, a_types = [], ()
if b_s:
b_s, b_types = zip(*b_s)
b_s = list(b_s)
else:
b_s, b_types = [], ()
kwargs['maxterms'] = kwargs.get('maxterms', ctx.prec)
try:
return ctx.hypsum(p, q, a_types+b_types, a_s+b_s, z, **kwargs)
except ctx.NoConvergence:
pass
prec = ctx.prec
try:
tol = kwargs.get('asymp_tol', ctx.eps/4)
ctx.prec += 10
# hypsum is has a conservative tolerance. So we try again:
def term(k, cache={0:ctx.one}):
if k in cache:
return cache[k]
t = term(k-1)
for a in a_s: t *= (a+(k-1))
for b in b_s: t /= (b+(k-1))
t *= z
t /= k
cache[k] = t
return t
s = ctx.one
for k in xrange(1, ctx.prec):
t = term(k)
s += t
if abs(t) <= tol:
return s
finally:
ctx.prec = prec
if p <= q+3:
contour = kwargs.get('contour')
if not contour:
if ctx.arg(z) < 0.25:
u = z / max(1, abs(z))
if ctx.arg(z) >= 0:
contour = [0, 2j, (2j+2)/u, 2/u, ctx.inf]
else:
contour = [0, -2j, (-2j+2)/u, 2/u, ctx.inf]
#contour = [0, 2j/z, 2/z, ctx.inf]
#contour = [0, 2j, 2/z, ctx.inf]
#contour = [0, 2j, ctx.inf]
else:
contour = [0, ctx.inf]
quad_kwargs = kwargs.get('quad_kwargs', {})
def g(t):
return ctx.exp(-t)*ctx.hyper(a_s, b_s+[1], t*z)
I, err = ctx.quad(g, contour, error=True, **quad_kwargs)
if err <= abs(I)*ctx.eps*8:
return I
raise ctx.NoConvergence
@defun
def _hyp2f2(ctx, a_s, b_s, z, **kwargs):
(a1, a1type), (a2, a2type) = a_s
(b1, b1type), (b2, b2type) = b_s
absz = abs(z)
magz = ctx.mag(z)
orig = ctx.prec
# Asymptotic expansion is ~ exp(z)
asymp_extraprec = magz
# Asymptotic series is in terms of 3F1
can_use_asymptotic = (not kwargs.get('force_series')) and \
(ctx.mag(absz) > 3)
# TODO: much of the following could be shared with 2F3 instead of
# copypasted
if can_use_asymptotic:
#print "using asymp"
try:
try:
ctx.prec += asymp_extraprec
# http://functions.wolfram.com/HypergeometricFunctions/
# Hypergeometric2F2/06/02/02/0002/
def h(a1,a2,b1,b2):
X = a1+a2-b1-b2
A2 = a1+a2
B2 = b1+b2
c = {}
c[0] = ctx.one
c[1] = (A2-1)*X+b1*b2-a1*a2
s1 = 0
k = 0
tprev = 0
while 1:
if k not in c:
uu1 = 1-B2+2*a1+a1**2+2*a2+a2**2-A2*B2+a1*a2+b1*b2+(2*B2-3*(A2+1))*k+2*k**2
uu2 = (k-A2+b1-1)*(k-A2+b2-1)*(k-X-2)
c[k] = ctx.one/k * (uu1*c[k-1]-uu2*c[k-2])
t1 = c[k] * z**(-k)
if abs(t1) < 0.1*ctx.eps:
#print "Convergence :)"
break
# Quit if the series doesn't converge quickly enough
if k > 5 and abs(tprev) / abs(t1) < 1.5:
#print "No convergence :("
raise ctx.NoConvergence
s1 += t1
tprev = t1
k += 1
S = ctx.exp(z)*s1
T1 = [z,S], [X,1], [b1,b2],[a1,a2],[],[],0
T2 = [-z],[-a1],[b1,b2,a2-a1],[a2,b1-a1,b2-a1],[a1,a1-b1+1,a1-b2+1],[a1-a2+1],-1/z
T3 = [-z],[-a2],[b1,b2,a1-a2],[a1,b1-a2,b2-a2],[a2,a2-b1+1,a2-b2+1],[-a1+a2+1],-1/z
return T1, T2, T3
v = ctx.hypercomb(h, [a1,a2,b1,b2], force_series=True, maxterms=4*ctx.prec)
if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,z]) == 5:
v = ctx.re(v)
return v
except ctx.NoConvergence:
pass
finally:
ctx.prec = orig
return ctx.hypsum(2, 2, (a1type, a2type, b1type, b2type), [a1, a2, b1, b2], z, **kwargs)
@defun
def _hyp1f2(ctx, a_s, b_s, z, **kwargs):
(a1, a1type), = a_s
(b1, b1type), (b2, b2type) = b_s
absz = abs(z)
magz = ctx.mag(z)
orig = ctx.prec
# Asymptotic expansion is ~ exp(sqrt(z))
asymp_extraprec = z and magz//2
# Asymptotic series is in terms of 3F0
can_use_asymptotic = (not kwargs.get('force_series')) and \
(ctx.mag(absz) > 19) and \
(ctx.sqrt(absz) > 1.5*orig) #and \
#ctx._hyp_check_convergence([a1, a1-b1+1, a1-b2+1], [],
# 1/absz, orig+40+asymp_extraprec)
# TODO: much of the following could be shared with 2F3 instead of
# copypasted
if can_use_asymptotic:
#print "using asymp"
try:
try:
ctx.prec += asymp_extraprec
# http://functions.wolfram.com/HypergeometricFunctions/
# Hypergeometric1F2/06/02/03/
def h(a1,b1,b2):
X = ctx.mpq_1_2*(a1-b1-b2+ctx.mpq_1_2)
c = {}
c[0] = ctx.one
c[1] = 2*(ctx.mpq_1_4*(3*a1+b1+b2-2)*(a1-b1-b2)+b1*b2-ctx.mpq_3_16)
c[2] = 2*(b1*b2+ctx.mpq_1_4*(a1-b1-b2)*(3*a1+b1+b2-2)-ctx.mpq_3_16)**2+\
ctx.mpq_1_16*(-16*(2*a1-3)*b1*b2 + \
4*(a1-b1-b2)*(-8*a1**2+11*a1+b1+b2-2)-3)
s1 = 0
s2 = 0
k = 0
tprev = 0
while 1:
if k not in c:
uu1 = (3*k**2+(-6*a1+2*b1+2*b2-4)*k + 3*a1**2 - \
(b1-b2)**2 - 2*a1*(b1+b2-2) + ctx.mpq_1_4)
uu2 = (k-a1+b1-b2-ctx.mpq_1_2)*(k-a1-b1+b2-ctx.mpq_1_2)*\
(k-a1+b1+b2-ctx.mpq_5_2)
c[k] = ctx.one/(2*k)*(uu1*c[k-1]-uu2*c[k-2])
w = c[k] * (-z)**(-0.5*k)
t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w
t2 = ctx.j**k * ctx.mpf(2)**(-k) * w
if abs(t1) < 0.1*ctx.eps:
#print "Convergence :)"
break
# Quit if the series doesn't converge quickly enough
if k > 5 and abs(tprev) / abs(t1) < 1.5:
#print "No convergence :("
raise ctx.NoConvergence
s1 += t1
s2 += t2
tprev = t1
k += 1
S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \
ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2
T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2], [a1],\
[], [], 0
T2 = [-z], [-a1], [b1,b2],[b1-a1,b2-a1], \
[a1,a1-b1+1,a1-b2+1], [], 1/z
return T1, T2
v = ctx.hypercomb(h, [a1,b1,b2], force_series=True, maxterms=4*ctx.prec)
if sum(ctx._is_real_type(u) for u in [a1,b1,b2,z]) == 4:
v = ctx.re(v)
return v
except ctx.NoConvergence:
pass
finally:
ctx.prec = orig
#print "not using asymp"
return ctx.hypsum(1, 2, (a1type, b1type, b2type), [a1, b1, b2], z, **kwargs)
@defun
def _hyp2f3(ctx, a_s, b_s, z, **kwargs):
(a1, a1type), (a2, a2type) = a_s
(b1, b1type), (b2, b2type), (b3, b3type) = b_s
absz = abs(z)
magz = ctx.mag(z)
# Asymptotic expansion is ~ exp(sqrt(z))
asymp_extraprec = z and magz//2
orig = ctx.prec
# Asymptotic series is in terms of 4F1
# The square root below empirically provides a plausible criterion
# for the leading series to converge
can_use_asymptotic = (not kwargs.get('force_series')) and \
(ctx.mag(absz) > 19) and (ctx.sqrt(absz) > 1.5*orig)
if can_use_asymptotic:
#print "using asymp"
try:
try:
ctx.prec += asymp_extraprec
# http://functions.wolfram.com/HypergeometricFunctions/
# Hypergeometric2F3/06/02/03/01/0002/
def h(a1,a2,b1,b2,b3):
X = ctx.mpq_1_2*(a1+a2-b1-b2-b3+ctx.mpq_1_2)
A2 = a1+a2
B3 = b1+b2+b3
A = a1*a2
B = b1*b2+b3*b2+b1*b3
R = b1*b2*b3
c = {}
c[0] = ctx.one
c[1] = 2*(B - A + ctx.mpq_1_4*(3*A2+B3-2)*(A2-B3) - ctx.mpq_3_16)
c[2] = ctx.mpq_1_2*c[1]**2 + ctx.mpq_1_16*(-16*(2*A2-3)*(B-A) + 32*R +\
4*(-8*A2**2 + 11*A2 + 8*A + B3 - 2)*(A2-B3)-3)
s1 = 0
s2 = 0
k = 0
tprev = 0
while 1:
if k not in c:
uu1 = (k-2*X-3)*(k-2*X-2*b1-1)*(k-2*X-2*b2-1)*\
(k-2*X-2*b3-1)
uu2 = (4*(k-1)**3 - 6*(4*X+B3)*(k-1)**2 + \
2*(24*X**2+12*B3*X+4*B+B3-1)*(k-1) - 32*X**3 - \
24*B3*X**2 - 4*B - 8*R - 4*(4*B+B3-1)*X + 2*B3-1)
uu3 = (5*(k-1)**2+2*(-10*X+A2-3*B3+3)*(k-1)+2*c[1])
c[k] = ctx.one/(2*k)*(uu1*c[k-3]-uu2*c[k-2]+uu3*c[k-1])
w = c[k] * ctx.power(-z, -0.5*k)
t1 = (-ctx.j)**k * ctx.mpf(2)**(-k) * w
t2 = ctx.j**k * ctx.mpf(2)**(-k) * w
if abs(t1) < 0.1*ctx.eps:
break
# Quit if the series doesn't converge quickly enough
if k > 5 and abs(tprev) / abs(t1) < 1.5:
raise ctx.NoConvergence
s1 += t1
s2 += t2
tprev = t1
k += 1
S = ctx.expj(ctx.pi*X+2*ctx.sqrt(-z))*s1 + \
ctx.expj(-(ctx.pi*X+2*ctx.sqrt(-z)))*s2
T1 = [0.5*S, ctx.pi, -z], [1, -0.5, X], [b1, b2, b3], [a1, a2],\
[], [], 0
T2 = [-z], [-a1], [b1,b2,b3,a2-a1],[a2,b1-a1,b2-a1,b3-a1], \
[a1,a1-b1+1,a1-b2+1,a1-b3+1], [a1-a2+1], 1/z
T3 = [-z], [-a2], [b1,b2,b3,a1-a2],[a1,b1-a2,b2-a2,b3-a2], \
[a2,a2-b1+1,a2-b2+1,a2-b3+1],[-a1+a2+1], 1/z
return T1, T2, T3
v = ctx.hypercomb(h, [a1,a2,b1,b2,b3], force_series=True, maxterms=4*ctx.prec)
if sum(ctx._is_real_type(u) for u in [a1,a2,b1,b2,b3,z]) == 6:
v = ctx.re(v)
return v
except ctx.NoConvergence:
pass
finally:
ctx.prec = orig
return ctx.hypsum(2, 3, (a1type, a2type, b1type, b2type, b3type), [a1, a2, b1, b2, b3], z, **kwargs)
@defun
def _hyp2f0(ctx, a_s, b_s, z, **kwargs):
(a, atype), (b, btype) = a_s
# We want to try aggressively to use the asymptotic expansion,
# and fall back only when absolutely necessary
try:
kwargsb = kwargs.copy()
kwargsb['maxterms'] = kwargsb.get('maxterms', ctx.prec)
return ctx.hypsum(2, 0, (atype,btype), [a,b], z, **kwargsb)
except ctx.NoConvergence:
if kwargs.get('force_series'):
raise
pass
def h(a, b):
w = ctx.sinpi(b)
rz = -1/z
T1 = ([ctx.pi,w,rz],[1,-1,a],[],[a-b+1,b],[a],[b],rz)
T2 = ([-ctx.pi,w,rz],[1,-1,1+a-b],[],[a,2-b],[a-b+1],[2-b],rz)
return T1, T2
return ctx.hypercomb(h, [a, 1+a-b], **kwargs)
@defun
def hyperu(ctx, a, b, z, **kwargs):
a, atype = ctx._convert_param(a)
b, btype = ctx._convert_param(b)
z = ctx.convert(z)
if not z:
if ctx.re(b) <= 1:
return ctx.gammaprod([1-b],[a-b+1])
else:
return ctx.inf + z
bb = 1+a-b
bb, bbtype = ctx._convert_param(bb)
try:
orig = ctx.prec
try:
ctx.prec += 10
v = ctx.hypsum(2, 0, (atype, bbtype), [a, bb], -1/z, maxterms=ctx.prec)
return v / z**a
finally:
ctx.prec = orig
except ctx.NoConvergence:
pass
def h(a,b):
w = ctx.sinpi(b)
T1 = ([ctx.pi,w],[1,-1],[],[a-b+1,b],[a],[b],z)
T2 = ([-ctx.pi,w,z],[1,-1,1-b],[],[a,2-b],[a-b+1],[2-b],z)
return T1, T2
return ctx.hypercomb(h, [a,b], **kwargs)
@defun_wrapped
def _erf_complex(ctx, z):
z2 = ctx.square_exp_arg(z, -1)
#z2 = -z**2
v = (2/ctx.sqrt(ctx.pi))*z * ctx.hyp1f1((1,2),(3,2), z2)
if not ctx._re(z):
v = ctx._im(v)*ctx.j
return v
@defun_wrapped
def _erfc_complex(ctx, z):
if ctx.re(z) > 2:
z2 = ctx.square_exp_arg(z)
nz2 = ctx.fneg(z2, exact=True)
v = ctx.exp(nz2)/ctx.sqrt(ctx.pi) * ctx.hyperu((1,2),(1,2), z2)
else:
v = 1 - ctx._erf_complex(z)
if not ctx._re(z):
v = 1+ctx._im(v)*ctx.j
return v
@defun
def erf(ctx, z):
z = ctx.convert(z)
if ctx._is_real_type(z):
try:
return ctx._erf(z)
except NotImplementedError:
pass
if ctx._is_complex_type(z) and not z.imag:
try:
return type(z)(ctx._erf(z.real))
except NotImplementedError:
pass
return ctx._erf_complex(z)
@defun
def erfc(ctx, z):
z = ctx.convert(z)
if ctx._is_real_type(z):
try:
return ctx._erfc(z)
except NotImplementedError:
pass
if ctx._is_complex_type(z) and not z.imag:
try:
return type(z)(ctx._erfc(z.real))
except NotImplementedError:
pass
return ctx._erfc_complex(z)
@defun
def square_exp_arg(ctx, z, mult=1, reciprocal=False):
prec = ctx.prec*4+20
if reciprocal:
z2 = ctx.fmul(z, z, prec=prec)
z2 = ctx.fdiv(ctx.one, z2, prec=prec)
else:
z2 = ctx.fmul(z, z, prec=prec)
if mult != 1:
z2 = ctx.fmul(z2, mult, exact=True)
return z2
@defun_wrapped
def erfi(ctx, z):
if not z:
return z
z2 = ctx.square_exp_arg(z)
v = (2/ctx.sqrt(ctx.pi)*z) * ctx.hyp1f1((1,2), (3,2), z2)
if not ctx._re(z):
v = ctx._im(v)*ctx.j
return v
@defun_wrapped
def erfinv(ctx, x):
xre = ctx._re(x)
if (xre != x) or (xre < -1) or (xre > 1):
return ctx.bad_domain("erfinv(x) is defined only for -1 <= x <= 1")
x = xre
#if ctx.isnan(x): return x
if not x: return x
if x == 1: return ctx.inf
if x == -1: return ctx.ninf
if abs(x) < 0.9:
a = 0.53728*x**3 + 0.813198*x
else:
# An asymptotic formula
u = ctx.ln(2/ctx.pi/(abs(x)-1)**2)
a = ctx.sign(x) * ctx.sqrt(u - ctx.ln(u))/ctx.sqrt(2)
ctx.prec += 10
return ctx.findroot(lambda t: ctx.erf(t)-x, a)
@defun_wrapped
def npdf(ctx, x, mu=0, sigma=1):
sigma = ctx.convert(sigma)
return ctx.exp(-(x-mu)**2/(2*sigma**2)) / (sigma*ctx.sqrt(2*ctx.pi))
@defun_wrapped
def ncdf(ctx, x, mu=0, sigma=1):
a = (x-mu)/(sigma*ctx.sqrt(2))
if a < 0:
return ctx.erfc(-a)/2
else:
return (1+ctx.erf(a))/2
@defun_wrapped
def betainc(ctx, a, b, x1=0, x2=1, regularized=False):
if x1 == x2:
v = 0
elif not x1:
if x1 == 0 and x2 == 1:
v = ctx.beta(a, b)
else:
v = x2**a * ctx.hyp2f1(a, 1-b, a+1, x2) / a
else:
m, d = ctx.nint_distance(a)
if m <= 0:
if d < -ctx.prec:
h = +ctx.eps
ctx.prec *= 2
a += h
elif d < -4:
ctx.prec -= d
s1 = x2**a * ctx.hyp2f1(a,1-b,a+1,x2)
s2 = x1**a * ctx.hyp2f1(a,1-b,a+1,x1)
v = (s1 - s2) / a
if regularized:
v /= ctx.beta(a,b)
return v
@defun
def gammainc(ctx, z, a=0, b=None, regularized=False):
regularized = bool(regularized)
z = ctx.convert(z)
if a is None:
a = ctx.zero
lower_modified = False
else:
a = ctx.convert(a)
lower_modified = a != ctx.zero
if b is None:
b = ctx.inf
upper_modified = False
else:
b = ctx.convert(b)
upper_modified = b != ctx.inf
# Complete gamma function
if not (upper_modified or lower_modified):
if regularized:
if ctx.re(z) < 0:
return ctx.inf
elif ctx.re(z) > 0:
return ctx.one
else:
return ctx.nan
return ctx.gamma(z)
if a == b:
return ctx.zero
# Standardize
if ctx.re(a) > ctx.re(b):
return -ctx.gammainc(z, b, a, regularized)
# Generalized gamma
if upper_modified and lower_modified:
return +ctx._gamma3(z, a, b, regularized)
# Upper gamma
elif lower_modified:
return ctx._upper_gamma(z, a, regularized)
# Lower gamma
elif upper_modified:
return ctx._lower_gamma(z, b, regularized)
@defun
def _lower_gamma(ctx, z, b, regularized=False):
# Pole
if ctx.isnpint(z):
return type(z)(ctx.inf)
G = [z] * regularized
negb = ctx.fneg(b, exact=True)
def h(z):
T1 = [ctx.exp(negb), b, z], [1, z, -1], [], G, [1], [1+z], b
return (T1,)
return ctx.hypercomb(h, [z])
@defun
def _upper_gamma(ctx, z, a, regularized=False):
# Fast integer case, when available
if ctx.isint(z):
try:
if regularized:
# Gamma pole
if ctx.isnpint(z):
return type(z)(ctx.zero)
orig = ctx.prec
try:
ctx.prec += 10
return ctx._gamma_upper_int(z, a) / ctx.gamma(z)
finally:
ctx.prec = orig
else:
return ctx._gamma_upper_int(z, a)
except NotImplementedError:
pass
nega = ctx.fneg(a, exact=True)
G = [z] * regularized
# Use 2F0 series when possible; fall back to lower gamma representation
try:
def h(z):
r = z-1
return [([ctx.exp(nega), a], [1, r], [], G, [1, -r], [], 1/nega)]
return ctx.hypercomb(h, [z], force_series=True)
except ctx.NoConvergence:
def h(z):
T1 = [], [1, z-1], [z], G, [], [], 0
T2 = [-ctx.exp(nega), a, z], [1, z, -1], [], G, [1], [1+z], a
return T1, T2
return ctx.hypercomb(h, [z])
@defun
def _gamma3(ctx, z, a, b, regularized=False):
pole = ctx.isnpint(z)
if regularized and pole:
return ctx.zero
try:
ctx.prec += 15
# We don't know in advance whether it's better to write as a difference
# of lower or upper gamma functions, so try both
T1 = ctx.gammainc(z, a, regularized=regularized)
T2 = ctx.gammainc(z, b, regularized=regularized)
R = T1 - T2
if ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10:
return R
if not pole:
T1 = ctx.gammainc(z, 0, b, regularized=regularized)
T2 = ctx.gammainc(z, 0, a, regularized=regularized)
R = T1 - T2
# May be ok, but should probably at least print a warning
# about possible cancellation
if 1: #ctx.mag(R) - max(ctx.mag(T1), ctx.mag(T2)) > -10:
return R
finally:
ctx.prec -= 15
raise NotImplementedError
@defun_wrapped
def expint(ctx, n, z):
if ctx.isint(n) and ctx._is_real_type(z):
try:
return ctx._expint_int(n, z)
except NotImplementedError:
pass
if ctx.isnan(n) or ctx.isnan(z):
return z*n
if z == ctx.inf:
return 1/z
if z == 0:
# integral from 1 to infinity of t^n
if ctx.re(n) <= 1:
# TODO: reasonable sign of infinity
return type(z)(ctx.inf)
else:
return ctx.one/(n-1)
if n == 0:
return ctx.exp(-z)/z
if n == -1:
return ctx.exp(-z)*(z+1)/z**2
return z**(n-1) * ctx.gammainc(1-n, z)
@defun_wrapped
def li(ctx, z, offset=False):
if offset:
if z == 2:
return ctx.zero
return ctx.ei(ctx.ln(z)) - ctx.ei(ctx.ln2)
if not z:
return z
if z == 1:
return ctx.ninf
return ctx.ei(ctx.ln(z))
@defun
def ei(ctx, z):
try:
return ctx._ei(z)
except NotImplementedError:
return ctx._ei_generic(z)
@defun_wrapped
def _ei_generic(ctx, z):
# Note: the following is currently untested because mp and fp
# both use special-case ei code
if z == ctx.inf:
return z
if z == ctx.ninf:
return ctx.zero
if ctx.mag(z) > 1:
try:
r = ctx.one/z
v = ctx.exp(z)*ctx.hyper([1,1],[],r,
maxterms=ctx.prec, force_series=True)/z
im = ctx._im(z)
if im > 0:
v += ctx.pi*ctx.j
if im < 0:
v -= ctx.pi*ctx.j
return v
except ctx.NoConvergence:
pass
v = z*ctx.hyp2f2(1,1,2,2,z) + ctx.euler
if ctx._im(z):
v += 0.5*(ctx.log(z) - ctx.log(ctx.one/z))
else:
v += ctx.log(abs(z))
return v
@defun
def e1(ctx, z):
try:
return ctx._e1(z)
except NotImplementedError:
return ctx.expint(1, z)
@defun
def ci(ctx, z):
try:
return ctx._ci(z)
except NotImplementedError:
return ctx._ci_generic(z)
@defun_wrapped
def _ci_generic(ctx, z):
if ctx.isinf(z):
if z == ctx.inf: return ctx.zero
if z == ctx.ninf: return ctx.pi*1j
jz = ctx.fmul(ctx.j,z,exact=True)
njz = ctx.fneg(jz,exact=True)
v = 0.5*(ctx.ei(jz) + ctx.ei(njz))
zreal = ctx._re(z)
zimag = ctx._im(z)
if zreal == 0:
if zimag > 0: v += ctx.pi*0.5j
if zimag < 0: v -= ctx.pi*0.5j
if zreal < 0:
if zimag >= 0: v += ctx.pi*1j
if zimag < 0: v -= ctx.pi*1j
if ctx._is_real_type(z) and zreal > 0:
v = ctx._re(v)
return v
@defun
def si(ctx, z):
try:
return ctx._si(z)
except NotImplementedError:
return ctx._si_generic(z)
@defun_wrapped
def _si_generic(ctx, z):
if ctx.isinf(z):
if z == ctx.inf: return 0.5*ctx.pi
if z == ctx.ninf: return -0.5*ctx.pi
# Suffers from cancellation near 0
if ctx.mag(z) >= -1:
jz = ctx.fmul(ctx.j,z,exact=True)
njz = ctx.fneg(jz,exact=True)
v = (-0.5j)*(ctx.ei(jz) - ctx.ei(njz))
zreal = ctx._re(z)
if zreal > 0:
v -= 0.5*ctx.pi
if zreal < 0:
v += 0.5*ctx.pi
if ctx._is_real_type(z):
v = ctx._re(v)
return v
else:
return z*ctx.hyp1f2((1,2),(3,2),(3,2),-0.25*z*z)
@defun_wrapped
def chi(ctx, z):
nz = ctx.fneg(z, exact=True)
v = 0.5*(ctx.ei(z) + ctx.ei(nz))
zreal = ctx._re(z)
zimag = ctx._im(z)
if zimag > 0:
v += ctx.pi*0.5j
elif zimag < 0:
v -= ctx.pi*0.5j
elif zreal < 0:
v += ctx.pi*1j
return v
@defun_wrapped
def shi(ctx, z):
# Suffers from cancellation near 0
if ctx.mag(z) >= -1:
nz = ctx.fneg(z, exact=True)
v = 0.5*(ctx.ei(z) - ctx.ei(nz))
zimag = ctx._im(z)
if zimag > 0: v -= 0.5j*ctx.pi
if zimag < 0: v += 0.5j*ctx.pi
return v
else:
return z * ctx.hyp1f2((1,2),(3,2),(3,2),0.25*z*z)
@defun_wrapped
def fresnels(ctx, z):
if z == ctx.inf:
return ctx.mpf(0.5)
if z == ctx.ninf:
return ctx.mpf(-0.5)
return ctx.pi*z**3/6*ctx.hyp1f2((3,4),(3,2),(7,4),-ctx.pi**2*z**4/16)
@defun_wrapped
def fresnelc(ctx, z):
if z == ctx.inf:
return ctx.mpf(0.5)
if z == ctx.ninf:
return ctx.mpf(-0.5)
return z*ctx.hyp1f2((1,4),(1,2),(5,4),-ctx.pi**2*z**4/16)
@defun_wrapped
def airyai(ctx, z):
if z == ctx.inf or z == ctx.ninf:
return ctx.zero
if z:
# Account for exponential scaling
ctx.prec += max(0, int(1.5*ctx.mag(z)))
if ctx._re(z) > 4:
# We could still use 1F1, but it results in huge cancellation;
# the following expansion is better
w = z**1.5
r = -ctx.mpf(3)/(4*w)
v = ctx.exp(-2*w/3)/(2*ctx.sqrt(ctx.pi)*ctx.nthroot(z,4))
v *= ctx.hyp2f0((1,6),(5,6),r)
return v
elif ctx._re(z) > 1:
# If not using asymptotic series:
# cancellation: both terms are ~ 2^(z^1.5),
# result is ~ 2^(-z^1.5), so need ~2*z^1.5 extra bits
ctx.prec += 2*int(ctx._re(z)**1.5)
z3 = z**3 / 9
a = ctx.hyp0f1((2,3), z3) / (ctx.cbrt(9) * ctx.gamma(ctx.mpf(2)/3))
b = z * ctx.hyp0f1((4,3), z3) / (ctx.cbrt(3) * ctx.gamma(ctx.mpf(1)/3))
return a - b
@defun_wrapped
def airybi(ctx, z):
if z == ctx.inf:
return z
if z == ctx.ninf:
return 1/z
if z:
# Account for exponential scaling
ctx.prec += max(0, int(1.5*ctx.mag(z)))
z3 = z**3 / 9
rt = ctx.nthroot(3, 6)
a = ctx.hyp0f1((2,3), z3) / (rt * ctx.gamma(ctx.mpf(2)/3))
b = z * rt * ctx.hyp0f1((4,3), z3) / ctx.gamma(ctx.mpf(1)/3)
return a + b
@defun_wrapped
def hermite(ctx, n, z, **kwargs):
if not z:
try:
return 2**n * ctx.sqrt(ctx.pi) / ctx.gamma(0.5*(1-n))
except ValueError:
return 0.0*(n+z)
if ctx.re(z) > 0 or (ctx.re(z) == 0 and ctx.im(z) > 0) or ctx.isnpint(-n):
prec = ctx.prec
ctx.prec = ctx.prec*4+20
z2 = -z**(-2)
ctx.prec = prec
return (2*z)**n * ctx.hyp2f0(-0.5*n, -0.5*(n-1), z2, **kwargs)
else:
prec = ctx.prec
ctx.prec = ctx.prec*4+20
z2 = z**2
ctx.prec = prec
return ctx.hermite(n,-z) + 2**(n+2)*ctx.sqrt(ctx.pi) * (-z) / \
ctx.gamma(-0.5*n) * ctx.hyp1f1((1-n)*0.5, 1.5, z2, **kwargs)
@defun_wrapped
def gegenbauer(ctx, n, a, z, **kwargs):
# Special cases: a+0.5, a*2 poles
if ctx.isnpint(a):
return 0*(z+n)
if ctx.isnpint(a+0.5):
# TODO: something else is required here
# E.g.: gegenbauer(-2, -0.5, 3) == -12
if ctx.isnpint(n+1):
raise NotImplementedError("Gegenbauer function with two limits")
def h(a):
a2 = 2*a
T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z)
return [T]
return ctx.hypercomb(h, [a], **kwargs)
def h(n):
a2 = 2*a
T = [], [], [n+a2], [n+1, a2], [-n, n+a2], [a+0.5], 0.5*(1-z)
return [T]
return ctx.hypercomb(h, [n], **kwargs)
@defun_wrapped
def jacobi(ctx, n, a, b, x, **kwargs):
if not ctx.isnpint(a):
def h(n):
return (([], [], [a+n+1], [n+1, a+1], [-n, a+b+n+1], [a+1], (1-x)*0.5),)
return ctx.hypercomb(h, [n], **kwargs)
if not ctx.isint(b):
def h(n, a):
return (([], [], [-b], [n+1, -b-n], [-n, a+b+n+1], [b+1], (x+1)*0.5),)
return ctx.hypercomb(h, [n, a], **kwargs)
# XXX: determine appropriate limit
return ctx.binomial(n+a,n) * ctx.hyp2f1(-n,1+n+a+b,a+1,(1-x)/2, **kwargs)
@defun_wrapped
def laguerre(ctx, n, a, z, **kwargs):
# XXX: limits, poles
#if ctx.isnpint(n):
# return 0*(a+z)
def h(a):
return (([], [], [a+n+1], [a+1, n+1], [-n], [a+1], z),)
return ctx.hypercomb(h, [a], **kwargs)
@defun_wrapped
def legendre(ctx, n, x, **kwargs):
if ctx.isint(n):
n = int(n)
# Accuracy near zeros
if (n + (n < 0)) & 1:
if not x:
return x
mag = ctx.mag(x)
if mag < -2*ctx.prec-10:
return x
if mag < -5:
ctx.prec += -mag
return ctx.hyp2f1(-n,n+1,1,(1-x)/2, **kwargs)
@defun
def legenp(ctx, n, m, z, type=2, **kwargs):
# Legendre function, 1st kind
n = ctx.convert(n)
m = ctx.convert(m)
# Faster
if not m:
return ctx.legendre(n, z, **kwargs)
# TODO: correct evaluation at singularities
if type == 2:
def h(n,m):
g = m*0.5
T = [1+z, 1-z], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z)
return (T,)
return ctx.hypercomb(h, [n,m], **kwargs)
if type == 3:
def h(n,m):
g = m*0.5
T = [z+1, z-1], [g, -g], [], [1-m], [-n, n+1], [1-m], 0.5*(1-z)
return (T,)
return ctx.hypercomb(h, [n,m], **kwargs)
raise ValueError("requires type=2 or type=3")
@defun
def legenq(ctx, n, m, z, type=2, **kwargs):
# Legendre function, 2nd kind
n = ctx.convert(n)
m = ctx.convert(m)
z = ctx.convert(z)
if z in (1, -1):
#if ctx.isint(m):
# return ctx.nan
#return ctx.inf # unsigned
return ctx.nan
if type == 2:
def h(n, m):
s = 2 * ctx.sinpi(m) / ctx.pi
c = ctx.cospi(m)
a = 1+z
b = 1-z
u = m/2
w = (1-z)/2
T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \
[-n, n+1], [1-m], w
T2 = [-s, a, b], [-1, -u, u], [n+m+1], [n-m+1, m+1], \
[-n, n+1], [m+1], w
return T1, T2
return ctx.hypercomb(h, [n, m], **kwargs)
if type == 3:
# The following is faster when there only is a single series
# Note: not valid for -1 < z < 0 (?)
if abs(z) > 1:
def h(n, m):
T1 = [ctx.expjpi(m), 2, ctx.pi, z, z-1, z+1], \
[1, -n-1, 0.5, -n-m-1, 0.5*m, 0.5*m], \
[n+m+1], [n+1.5], \
[0.5*(2+n+m), 0.5*(1+n+m)], [n+1.5], z**(-2)
return [T1]
return ctx.hypercomb(h, [n, m], **kwargs)
else:
# not valid for 1 < z < inf ?
def h(n, m):
s = 2 * ctx.sinpi(m) / ctx.pi
c = ctx.expjpi(m)
a = 1+z
b = z-1
u = m/2
w = (1-z)/2
T1 = [s, c, a, b], [-1, 1, u, -u], [], [1-m], \
[-n, n+1], [1-m], w
T2 = [-s, c, a, b], [-1, 1, -u, u], [n+m+1], [n-m+1, m+1], \
[-n, n+1], [m+1], w
return T1, T2
return ctx.hypercomb(h, [n, m], **kwargs)
raise ValueError("requires type=2 or type=3")
@defun_wrapped
def chebyt(ctx, n, x, **kwargs):
return ctx.hyp2f1(-n,n,(1,2),(1-x)/2, **kwargs)
@defun_wrapped
def chebyu(ctx, n, x, **kwargs):
return (n+1) * ctx.hyp2f1(-n, n+2, (3,2), (1-x)/2, **kwargs)
@defun
def j0(ctx, x):
"""Computes the Bessel function `J_0(x)`. See :func:`besselj`."""
return ctx.besselj(0, x)
@defun
def j1(ctx, x):
"""Computes the Bessel function `J_1(x)`. See :func:`besselj`."""
return ctx.besselj(1, x)
@defun
def besselj(ctx, n, z, derivative=0, **kwargs):
if type(n) is int:
n_isint = True
else:
n = ctx.convert(n)
n_isint = ctx.isint(n)
if n_isint:
n = int(n)
if n_isint and n < 0:
return (-1)**n * ctx.besselj(-n, z, derivative, **kwargs)
z = ctx.convert(z)
M = ctx.mag(z)
if derivative:
d = ctx.convert(derivative)
# TODO: the integer special-casing shouldn't be necessary.
# However, the hypergeometric series gets inaccurate for large d
# because of inaccurate pole cancellation at a pole far from
# zero (needs to be fixed in hypercomb or hypsum)
if ctx.isint(d) and d >= 0:
d = int(d)
orig = ctx.prec
try:
ctx.prec += 15
v = ctx.fsum((-1)**k * ctx.binomial(d,k) * ctx.besselj(2*k+n-d,z)
for k in range(d+1))
finally:
ctx.prec = orig
v *= ctx.mpf(2)**(-d)
else:
def h(n,d):
r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), -0.25, exact=True)
B = [0.5*(n-d+1), 0.5*(n-d+2)]
T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[],B,[(n+1)*0.5,(n+2)*0.5],B+[n+1],r)]
return T
v = ctx.hypercomb(h, [n,d], **kwargs)
else:
# Fast case: J_n(x), n int, appropriate magnitude for fixed-point calculation
if (not derivative) and n_isint and abs(M) < 10 and abs(n) < 20:
try:
return ctx._besselj(n, z)
except NotImplementedError:
pass
if not z:
if not n:
v = ctx.one + n+z
elif ctx.re(n) > 0:
v = n*z
else:
v = ctx.inf + z + n
else:
#v = 0
orig = ctx.prec
try:
# XXX: workaround for accuracy in low level hypergeometric series
# when alternating, large arguments
ctx.prec += min(3*abs(M), ctx.prec)
w = ctx.fmul(z, 0.5, exact=True)
def h(n):
r = ctx.fneg(ctx.fmul(w, w, prec=max(0,ctx.prec+M)), exact=True)
return [([w], [n], [], [n+1], [], [n+1], r)]
v = ctx.hypercomb(h, [n], **kwargs)
finally:
ctx.prec = orig
v = +v
return v
@defun
def besseli(ctx, n, z, derivative=0, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
if not z:
if derivative:
raise ValueError
if not n:
# I(0,0) = 1
return 1+n+z
if ctx.isint(n):
return 0*(n+z)
r = ctx.re(n)
if r == 0:
return ctx.nan*(n+z)
elif r > 0:
return 0*(n+z)
else:
return ctx.inf+(n+z)
M = ctx.mag(z)
if derivative:
d = ctx.convert(derivative)
def h(n,d):
r = ctx.fmul(ctx.fmul(z, z, prec=ctx.prec+M), 0.25, exact=True)
B = [0.5*(n-d+1), 0.5*(n-d+2), n+1]
T = [([2,ctx.pi,z],[d-2*n,0.5,n-d],[n+1],B,[(n+1)*0.5,(n+2)*0.5],B,r)]
return T
v = ctx.hypercomb(h, [n,d], **kwargs)
else:
def h(n):
w = ctx.fmul(z, 0.5, exact=True)
r = ctx.fmul(w, w, prec=max(0,ctx.prec+M))
return [([w], [n], [], [n+1], [], [n+1], r)]
v = ctx.hypercomb(h, [n], **kwargs)
return v
@defun_wrapped
def bessely(ctx, n, z, derivative=0, **kwargs):
if not z:
if derivative:
# Not implemented
raise ValueError
if not n:
# ~ log(z/2)
return -ctx.inf + (n+z)
if ctx.im(n):
return nan * (n+z)
r = ctx.re(n)
q = n+0.5
if ctx.isint(q):
if n > 0:
return -ctx.inf + (n+z)
else:
return 0 * (n+z)
if r < 0 and int(ctx.floor(q)) % 2:
return ctx.inf + (n+z)
else:
return ctx.ninf + (n+z)
# XXX: use hypercomb
ctx.prec += 10
m, d = ctx.nint_distance(n)
if d < -ctx.prec:
h = +ctx.eps
ctx.prec *= 2
n += h
elif d < 0:
ctx.prec -= d
# TODO: avoid cancellation for imaginary arguments
return (ctx.besselj(n,z,derivative,**kwargs)*ctx.cospi(n) - \
ctx.besselj(-n,z,derivative,**kwargs))/ctx.sinpi(n)
@defun_wrapped
def besselk(ctx, n, z, **kwargs):
if not z:
return ctx.inf
M = ctx.mag(z)
if M < 1:
# Represent as limit definition
def h(n):
r = (z/2)**2
T1 = [z, 2], [-n, n-1], [n], [], [], [1-n], r
T2 = [z, 2], [n, -n-1], [-n], [], [], [1+n], r
return T1, T2
# We could use the limit definition always, but it leads
# to very bad cancellation (of exponentially large terms)
# for large real z
# Instead represent in terms of 2F0
else:
ctx.prec += M
def h(n):
return [([ctx.pi/2, z, ctx.exp(-z)], [0.5,-0.5,1], [], [], \
[n+0.5, 0.5-n], [], -1/(2*z))]
return ctx.hypercomb(h, [n], **kwargs)
@defun_wrapped
def hankel1(ctx,n,x,**kwargs):
return ctx.besselj(n,x,**kwargs) + ctx.j*ctx.bessely(n,x,**kwargs)
@defun_wrapped
def hankel2(ctx,n,x,**kwargs):
return ctx.besselj(n,x,**kwargs) - ctx.j*ctx.bessely(n,x,**kwargs)
@defun_wrapped
def whitm(ctx,k,m,z,**kwargs):
if z == 0:
# M(k,m,z) = 0^(1/2+m)
if ctx.re(m) > -0.5:
return z
elif ctx.re(m) < -0.5:
return ctx.inf + z
else:
return ctx.nan * z
x = ctx.fmul(-0.5, z, exact=True)
y = 0.5+m
return ctx.exp(x) * z**y * ctx.hyp1f1(y-k, 1+2*m, z, **kwargs)
@defun_wrapped
def whitw(ctx,k,m,z,**kwargs):
if z == 0:
g = abs(ctx.re(m))
if g < 0.5:
return z
elif g > 0.5:
return ctx.inf + z
else:
return ctx.nan * z
x = ctx.fmul(-0.5, z, exact=True)
y = 0.5+m
return ctx.exp(x) * z**y * ctx.hyperu(y-k, 1+2*m, z, **kwargs)
@defun
def struveh(ctx,n,z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/StruveH/26/01/02/
def h(n):
return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], -(z/2)**2)]
return ctx.hypercomb(h, [n], **kwargs)
@defun
def struvel(ctx,n,z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/StruveL/26/01/02/
def h(n):
return [([z/2, 0.5*ctx.sqrt(ctx.pi)], [n+1, -1], [], [n+1.5], [1], [1.5, n+1.5], (z/2)**2)]
return ctx.hypercomb(h, [n], **kwargs)
@defun
def ber(ctx, n, z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBer2/26/01/02/0001/
def h(n):
r = -(z/4)**4
T1 = [ctx.cospi(0.75*n), z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r
T2 = [-ctx.sinpi(0.75*n), z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r
return T1, T2
return ctx.hypercomb(h, [n], **kwargs)
@defun
def bei(ctx, n, z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/KelvinBei2/26/01/02/0001/
def h(n):
r = -(z/4)**4
T1 = [ctx.cospi(0.75*n), z/2], [1, n+2], [], [n+2], [], [1.5, 0.5*(n+3), 0.5*n+1], r
T2 = [ctx.sinpi(0.75*n), z/2], [1, n], [], [n+1], [], [0.5, 0.5*(n+1), 0.5*n+1], r
return T1, T2
return ctx.hypercomb(h, [n], **kwargs)
@defun
def ker(ctx, n, z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKer2/26/01/02/0001/
def h(n):
r = -(z/4)**4
T1 = [2, z, 4*ctx.cospi(0.25*n)], [-n-3, n, 1], [-n], [], [], [0.5, 0.5*(1+n), 0.5*(n+2)], r
T2 = [2, z, -ctx.sinpi(0.25*n)], [-n-3, 2+n, 1], [-n-1], [], [], [1.5, 0.5*(3+n), 0.5*(n+2)], r
T3 = [2, z, 4*ctx.cospi(0.75*n)], [n-3, -n, 1], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r
T4 = [2, z, -ctx.sinpi(0.75*n)], [n-3, 2-n, 1], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r
return T1, T2, T3, T4
return ctx.hypercomb(h, [n], **kwargs)
@defun
def kei(ctx, n, z, **kwargs):
n = ctx.convert(n)
z = ctx.convert(z)
# http://functions.wolfram.com/Bessel-TypeFunctions/KelvinKei2/26/01/02/0001/
def h(n):
r = -(z/4)**4
T1 = [-ctx.cospi(0.75*n), 2, z], [1, n-3, 2-n], [n-1], [], [], [1.5, 0.5*(3-n), 1-0.5*n], r
T2 = [-ctx.sinpi(0.75*n), 2, z], [1, n-1, -n], [n], [], [], [0.5, 0.5*(1-n), 1-0.5*n], r
T3 = [-ctx.sinpi(0.25*n), 2, z], [1, -n-1, n], [-n], [], [], [0.5, 0.5*(n+1), 0.5*(n+2)], r
T4 = [-ctx.cospi(0.25*n), 2, z], [1, -n-3, n+2], [-n-1], [], [], [1.5, 0.5*(n+3), 0.5*(n+2)], r
return T1, T2, T3, T4
return ctx.hypercomb(h, [n], **kwargs)
@defun
def meijerg(ctx, a_s, b_s, z, r=1, series=None, **kwargs):
an, ap = a_s
bm, bq = b_s
n = len(an)
p = n + len(ap)
m = len(bm)
q = m + len(bq)
a = an+ap
b = bm+bq
a = map(ctx.convert, a)
b = map(ctx.convert, b)
z = ctx.convert(z)
if series is None:
if p < q: series = 1
if p > q: series = 2
if p == q:
if m+n == p and abs(z) > 1:
series = 2
else:
series = 1
if kwargs.get('verbose'):
print "Meijer G m,n,p,q,series =", m,n,p,q,series
if series == 1:
def h(*args):
a = args[:p]
b = args[p:]
terms = []
for k in range(m):
bases = [z]
expts = [b[k]/r]
gn = [b[j]-b[k] for j in range(m) if j != k]
gn += [1-a[j]+b[k] for j in range(n)]
gd = [a[j]-b[k] for j in range(n,p)]
gd += [1-b[j]+b[k] for j in range(m,q)]
hn = [1-a[j]+b[k] for j in range(p)]
hd = [1-b[j]+b[k] for j in range(q) if j != k]
hz = (-ctx.one)**(p-m-n) * z**(ctx.one/r)
terms.append((bases, expts, gn, gd, hn, hd, hz))
return terms
else:
def h(*args):
a = args[:p]
b = args[p:]
terms = []
for k in range(n):
bases = [z]
if r == 1:
expts = [a[k]-1]
else:
expts = [(a[k]-1)/ctx.convert(r)]
gn = [a[k]-a[j] for j in range(n) if j != k]
gn += [1-a[k]+b[j] for j in range(m)]
gd = [a[k]-b[j] for j in range(m,q)]
gd += [1-a[k]+a[j] for j in range(n,p)]
hn = [1-a[k]+b[j] for j in range(q)]
hd = [1+a[j]-a[k] for j in range(p) if j != k]
hz = (-ctx.one)**(q-m-n) / z**(ctx.one/r)
terms.append((bases, expts, gn, gd, hn, hd, hz))
return terms
return ctx.hypercomb(h, a+b, **kwargs)
@defun_wrapped
def appellf1(ctx,a,b1,b2,c,z1,z2,**kwargs):
# Assume z1 smaller
# We will use z1 for the outer loop
if abs(z1) > abs(z2):
z1, z2 = z2, z1
b1, b2 = b2, b1
def ok(x):
return abs(x) < 0.99
# Finite cases
if ctx.isnpint(a):
pass
elif ctx.isnpint(b1):
pass
elif ctx.isnpint(b2):
z1, z2, b1, b2 = z2, z1, b2, b1
else:
#print z1, z2
# Note: ok if |z2| > 1, because
# 2F1 implements analytic continuation
if not ok(z1):
u1 = (z1-z2)/(z1-1)
if not ok(u1):
raise ValueError("Analytic continuation not implemented")
#print "Using analytic continuation"
return (1-z1)**(-b1)*(1-z2)**(c-a-b2)*\
ctx.appellf1(c-a,b1,c-b1-b2,c,u1,z2,**kwargs)
#print "inner is", a, b2, c
one = ctx.one
s = 0
t = 1
k = 0
while 1:
h = ctx.hyp2f1(a,b2,c,z2,zeroprec=ctx.prec,**kwargs)
term = t * h
if abs(term) < ctx.eps and abs(h) > 10*ctx.eps:
break
s += term
k += 1
t = (t*a*b1*z1) / (c*k)
c += one
a += one
b1 += one
return s
@defun_wrapped
def coulombc(ctx, l, eta, _cache={}):
if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec:
return +_cache[l,eta][1]
G3 = ctx.loggamma(2*l+2)
G1 = ctx.loggamma(1+l+ctx.j*eta)
G2 = ctx.loggamma(1+l-ctx.j*eta)
v = 2**l * ctx.exp((-ctx.pi*eta+G1+G2)/2 - G3)
if not (ctx.im(l) or ctx.im(eta)):
v = ctx.re(v)
_cache[l,eta] = (ctx.prec, v)
return v
@defun_wrapped
def coulombf(ctx, l, eta, z, w=1, chop=True, **kwargs):
# Regular Coulomb wave function
# Note: w can be either 1 or -1; the other may be better in some cases
# TODO: check that chop=True chops when and only when it should
#ctx.prec += 10
def h(l, eta):
try:
jw = ctx.j*w
jwz = ctx.fmul(jw, z, exact=True)
jwz2 = ctx.fmul(jwz, -2, exact=True)
C = ctx.coulombc(l, eta)
T1 = [C, z, ctx.exp(jwz)], [1, l+1, 1], [], [], [1+l+jw*eta], \
[2*l+2], jwz2
except ValueError:
T1 = [0], [-1], [], [], [], [], 0
return (T1,)
v = ctx.hypercomb(h, [l,eta], **kwargs)
if chop and (not ctx.im(l)) and (not ctx.im(eta)) and (not ctx.im(z)) and \
(ctx.re(z) >= 0):
v = ctx.re(v)
return v
@defun_wrapped
def _coulomb_chi(ctx, l, eta, _cache={}):
if (l, eta) in _cache and _cache[l,eta][0] >= ctx.prec:
return _cache[l,eta][1]
def terms():
l2 = -l-1
jeta = ctx.j*eta
return [ctx.loggamma(1+l+jeta) * (-0.5j),
ctx.loggamma(1+l-jeta) * (0.5j),
ctx.loggamma(1+l2+jeta) * (0.5j),
ctx.loggamma(1+l2-jeta) * (-0.5j),
-(l+0.5)*ctx.pi]
v = ctx.sum_accurately(terms, 1)
_cache[l,eta] = (ctx.prec, v)
return v
@defun_wrapped
def coulombg(ctx, l, eta, z, w=1, chop=True, **kwargs):
# Irregular Coulomb wave function
# Note: w can be either 1 or -1; the other may be better in some cases
# TODO: check that chop=True chops when and only when it should
if not ctx._im(l):
l = ctx._re(l) # XXX: for isint
def h(l, eta):
# Force perturbation for integers and half-integers
if ctx.isint(l*2):
T1 = [0], [-1], [], [], [], [], 0
return (T1,)
l2 = -l-1
try:
chi = ctx._coulomb_chi(l, eta)
jw = ctx.j*w
s = ctx.sin(chi); c = ctx.cos(chi)
C1 = ctx.coulombc(l,eta)
C2 = ctx.coulombc(l2,eta)
u = ctx.exp(jw*z)
x = -2*jw*z
T1 = [s, C1, z, u, c], [-1, 1, l+1, 1, 1], [], [], \
[1+l+jw*eta], [2*l+2], x
T2 = [-s, C2, z, u], [-1, 1, l2+1, 1], [], [], \
[1+l2+jw*eta], [2*l2+2], x
return T1, T2
except ValueError:
T1 = [0], [-1], [], [], [], [], 0
return (T1,)
v = ctx.hypercomb(h, [l,eta], **kwargs)
if chop and (not ctx._im(l)) and (not ctx._im(eta)) and (not ctx._im(z)) and \
(ctx._re(z) >= 0):
v = ctx._re(v)
return v
@defun
def spherharm(ctx, l, m, theta, phi, **kwargs):
l = ctx.convert(l)
m = ctx.convert(m)
theta = ctx.convert(theta)
phi = ctx.convert(phi)
l_isint = ctx.isint(l)
l_natural = l_isint and l >= 0
m_isint = ctx.isint(m)
if l_isint and l < 0 and m_isint:
return ctx.spherharm(-(l+1), m, theta, phi, **kwargs)
if theta == 0 and m_isint and m < 0:
return ctx.zero * 1j
if l_natural and m_isint:
if abs(m) > l:
return ctx.zero * 1j
# http://functions.wolfram.com/Polynomials/
# SphericalHarmonicY/26/01/02/0004/
def h(l,m):
C = [-1, ctx.expj(m*phi),
(2*l+1)*ctx.fac(l+abs(m))/ctx.pi/ctx.fac(l-abs(m)),
ctx.sin(theta)**2,
ctx.fac(abs(m)), 2]
P = [0.5*m*(ctx.sign(m)+1), 1, 0.5, 0.5*abs(m), -1, -abs(m)-1]
return ((C, P, [], [], [abs(m)-l, l+abs(m)+1], [abs(m)+1],
ctx.sin(0.5*theta)**2),)
else:
# http://functions.wolfram.com/HypergeometricFunctions/
# SphericalHarmonicYGeneral/26/01/02/0001/
def h(l,m):
if ctx.isnpint(l-m+1) or ctx.isnpint(l+m+1) or ctx.isnpint(1-m):
return (([0], [-1], [], [], [], [], 0),)
C = [0.5*ctx.expj(m*phi),
(2*l+1)/ctx.pi,
ctx.gamma(l-m+1),
ctx.gamma(l+m+1),
ctx.cos(0.5*theta)**2,
ctx.sin(0.5*theta)**2]
P = [1, 0.5, 0.5, -0.5, 0.5*m, -0.5*m]
return ((C, P, [], [1-m], [-l,l+1], [1-m], ctx.sin(0.5*theta)**2),)
return ctx.hypercomb(h, [l,m], **kwargs)