You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1414 lines
50 KiB

from ..libmp.backend import xrange
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.rgamma(b)
#for w, c in zip(w_s, c_s): v *= ctx.power(w, c)
v = ctx.fprod([ctx.hyper(a_s, b_s, z, **kwargs)] + \
[ctx.gamma(a) for a in alpha_s] + \
[ctx.rgamma(b) for b in beta_s] + \
[ctx.power(w,c) for (w,c) in zip(w_s,c_s)])
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 = [ctx._convert_param(a) for a in a_s]
b_s = [ctx._convert_param(b) for b in b_s]
# Reduce degree by eliminating common parameters
if kwargs.get('eliminate', True):
elim_nonpositive = kwargs.get('eliminate_all', False)
i = 0
while i < q and a_s:
b = b_s[i]
if b in a_s and (elim_nonpositive or not ctx.isnpint(b[0])):
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/
# 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
def h():
w = ctx.sqrt(-z)
jw = ctx.j*w
u = 1/(4*jw)
c = ctx.mpq_1_2 - b
E = ctx.exp(2*jw)
T1 = ([-jw,E], [c,-1], [], [], [b-ctx.mpq_1_2, ctx.mpq_3_2-b], [], -u)
T2 = ([jw,E], [c,1], [], [], [b-ctx.mpq_1_2, ctx.mpq_3_2-b], [], u)
return T1, T2
v = ctx.hypercomb(h, [], force_series=True)
v = ctx.gamma(b)/(2*ctx.sqrt(ctx.pi))*v
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
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(kk, _cache={0:ctx.one}):
k = int(kk)
if k != kk:
t = z ** ctx.mpf(kk) / ctx.fac(kk)
for a in a_s: t *= ctx.rf(a,kk)
for b in b_s: t /= ctx.rf(b,kk)
return t
if k in _cache:
return _cache[k]
t = term(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
sum_method = kwargs.get('sum_method', 'r+s+e')
try:
return ctx.nsum(term, [0,ctx.inf], verbose=kwargs.get('verbose'),
strict=kwargs.get('strict', True),
method=sum_method.replace('e',''))
except ctx.NoConvergence:
if 'e' not in sum_method:
raise
pass
if kwargs.get('verbose'):
print("Attempting Euler-Maclaurin summation")
"""
Somewhat slower version (one diffs_exp for each factor).
However, this would be faster with fast direct derivatives
of the gamma function.
def power_diffs(k0):
r = 0
l = ctx.log(z)
while 1:
yield z**ctx.mpf(k0) * l**r
r += 1
def loggamma_diffs(x, reciprocal=False):
sign = (-1) ** reciprocal
yield sign * ctx.loggamma(x)
i = 0
while 1:
yield sign * ctx.psi(i,x)
i += 1
def hyper_diffs(k0):
b2 = b_s + [1]
A = [ctx.diffs_exp(loggamma_diffs(a+k0)) for a in a_s]
B = [ctx.diffs_exp(loggamma_diffs(b+k0,True)) for b in b2]
Z = [power_diffs(k0)]
C = ctx.gammaprod([b for b in b2], [a for a in a_s])
for d in ctx.diffs_prod(A + B + Z):
v = C * d
yield v
"""
def log_diffs(k0):
b2 = b_s + [1]
yield sum(ctx.loggamma(a+k0) for a in a_s) - \
sum(ctx.loggamma(b+k0) for b in b2) + k0*ctx.log(z)
i = 0
while 1:
v = sum(ctx.psi(i,a+k0) for a in a_s) - \
sum(ctx.psi(i,b+k0) for b in b2)
if i == 0:
v += ctx.log(z)
yield v
i += 1
def hyper_diffs(k0):
C = ctx.gammaprod([b for b in b_s], [a for a in a_s])
for d in ctx.diffs_exp(log_diffs(k0)):
v = C * d
yield v
tol = ctx.eps / 1024
prec = ctx.prec
try:
trunc = 50 * ctx.dps
ctx.prec += 20
for i in xrange(5):
head = ctx.fsum(term(k) for k in xrange(trunc))
tail, err = ctx.sumem(term, [trunc, ctx.inf], tol=tol,
adiffs=hyper_diffs(trunc),
verbose=kwargs.get('verbose'),
error=True,
_fast_abort=True)
if err < tol:
v = head + tail
break
trunc *= 2
# Need to increase precision because calculation of
# derivatives may be inaccurate
ctx.prec += ctx.prec//2
if i == 4:
raise ctx.NoConvergence(\
"Euler-Maclaurin summation did not converge")
finally:
ctx.prec = prec
return +v
# 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 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 = [ctx.convert(_) for _ in a]
b = [ctx.convert(_) for _ in 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,x,y,**kwargs):
# Assume x smaller
# We will use x for the outer loop
if abs(x) > abs(y):
x, y = y, x
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):
x, y, b1, b2 = y, x, b2, b1
else:
#print x, y
# Note: ok if |y| > 1, because
# 2F1 implements analytic continuation
if not ok(x):
u1 = (x-y)/(x-1)
if not ok(u1):
raise ValueError("Analytic continuation not implemented")
#print "Using analytic continuation"
return (1-x)**(-b1)*(1-y)**(c-a-b2)*\
ctx.appellf1(c-a,b1,c-b1-b2,c,u1,y,**kwargs)
return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]}, {'m+n':[c]}, x,y, **kwargs)
@defun
def appellf2(ctx,a,b1,b2,c1,c2,x,y,**kwargs):
# TODO: continuation
return ctx.hyper2d({'m+n':[a],'m':[b1],'n':[b2]},
{'m':[c1],'n':[c2]}, x,y, **kwargs)
@defun
def appellf3(ctx,a1,a2,b1,b2,c,x,y,**kwargs):
outer_polynomial = ctx.isnpint(a1) or ctx.isnpint(b1)
inner_polynomial = ctx.isnpint(a2) or ctx.isnpint(b2)
if not outer_polynomial:
if inner_polynomial or abs(x) > abs(y):
x, y = y, x
a1,a2,b1,b2 = a2,a1,b2,b1
return ctx.hyper2d({'m':[a1,b1],'n':[a2,b2]}, {'m+n':[c]},x,y,**kwargs)
@defun
def appellf4(ctx,a,b,c1,c2,x,y,**kwargs):
# TODO: continuation
return ctx.hyper2d({'m+n':[a,b]}, {'m':[c1],'n':[c2]},x,y,**kwargs)
@defun
def hyper2d(ctx, a, b, x, y, **kwargs):
r"""
Sums the generalized 2D hypergeometric series
.. math ::
\sum_{m=0}^{\infty} \sum_{n=0}^{\infty}
\frac{P((a),m,n)}{Q((b),m,n)}
\frac{x^m y^n} {m! n!}
where `(a) = (a_1,\ldots,a_r)`, `(b) = (b_1,\ldots,b_s)` and where
`P` and `Q` are products of rising factorials such as `(a_j)_n` or
`(a_j)_{m+n}`. `P` and `Q` are specified in the form of dicts, with
the `m` and `n` dependence as keys and parameter lists as values.
The supported rising factorials are given in the following table
(note that only a few are supported in `Q`):
+------------+-------------------+--------+
| Key | Rising factorial | `Q` |
+============+===================+========+
| ``'m'`` | `(a_j)_m` | Yes |
+------------+-------------------+--------+
| ``'n'`` | `(a_j)_n` | Yes |
+------------+-------------------+--------+
| ``'m+n'`` | `(a_j)_{m+n}` | Yes |
+------------+-------------------+--------+
| ``'m-n'`` | `(a_j)_{m-n}` | No |
+------------+-------------------+--------+
| ``'n-m'`` | `(a_j)_{n-m}` | No |
+------------+-------------------+--------+
| ``'2m+n'`` | `(a_j)_{2m+n}` | No |
+------------+-------------------+--------+
| ``'2m-n'`` | `(a_j)_{2m-n}` | No |
+------------+-------------------+--------+
| ``'2n-m'`` | `(a_j)_{2n-m}` | No |
+------------+-------------------+--------+
For example, the Appell F1 and F4 functions
.. math ::
F_1 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty}
\frac{(a)_{m+n} (b)_m (c)_n}{(d)_{m+n}}
\frac{x^m y^n}{m! n!}
F_4 = \sum_{m=0}^{\infty} \sum_{n=0}^{\infty}
\frac{(a)_{m+n} (b)_{m+n}}{(c)_m (d)_{n}}
\frac{x^m y^n}{m! n!}
can be represented respectively as
``hyper2d({'m+n':[a], 'm':[b], 'n':[c]}, {'m+n':[d]}, x, y)``
``hyper2d({'m+n':[a,b]}, {'m':[c], 'n':[d]}, x, y)``
More generally, :func:`~mpmath.hyper2d` can evaluate any of the 34 distinct
convergent second-order (generalized Gaussian) hypergeometric
series enumerated by Horn, as well as the Kampe de Feriet
function.
The series is computed by rewriting it so that the inner
series (i.e. the series containing `n` and `y`) has the form of an
ordinary generalized hypergeometric series and thereby can be
evaluated efficiently using :func:`~mpmath.hyper`. If possible,
manually swapping `x` and `y` and the corresponding parameters
can sometimes give better results.
**Examples**
Two separable cases: a product of two geometric series, and a
product of two Gaussian hypergeometric functions::
>>> from mpmath import *
>>> mp.dps = 25; mp.pretty = True
>>> x, y = mpf(0.25), mpf(0.5)
>>> hyper2d({'m':1,'n':1}, {}, x,y)
2.666666666666666666666667
>>> 1/(1-x)/(1-y)
2.666666666666666666666667
>>> hyper2d({'m':[1,2],'n':[3,4]}, {'m':[5],'n':[6]}, x,y)
4.164358531238938319669856
>>> hyp2f1(1,2,5,x)*hyp2f1(3,4,6,y)
4.164358531238938319669856
Some more series that can be done in closed form::
>>> hyper2d({'m':1,'n':1},{'m+n':1},x,y)
2.013417124712514809623881
>>> (exp(x)*x-exp(y)*y)/(x-y)
2.013417124712514809623881
Six of the 34 Horn functions, G1-G3 and H1-H3::
>>> from mpmath import *
>>> mp.dps = 10; mp.pretty = True
>>> x, y = 0.0625, 0.125
>>> a1,a2,b1,b2,c1,c2,d = 1.1,-1.2,-1.3,-1.4,1.5,-1.6,1.7
>>> hyper2d({'m+n':a1,'n-m':b1,'m-n':b2},{},x,y) # G1
1.139090746
>>> nsum(lambda m,n: rf(a1,m+n)*rf(b1,n-m)*rf(b2,m-n)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
1.139090746
>>> hyper2d({'m':a1,'n':a2,'n-m':b1,'m-n':b2},{},x,y) # G2
0.9503682696
>>> nsum(lambda m,n: rf(a1,m)*rf(a2,n)*rf(b1,n-m)*rf(b2,m-n)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
0.9503682696
>>> hyper2d({'2n-m':a1,'2m-n':a2},{},x,y) # G3
1.029372029
>>> nsum(lambda m,n: rf(a1,2*n-m)*rf(a2,2*m-n)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
1.029372029
>>> hyper2d({'m-n':a1,'m+n':b1,'n':c1},{'m':d},x,y) # H1
-1.605331256
>>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m+n)*rf(c1,n)/rf(d,m)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
-1.605331256
>>> hyper2d({'m-n':a1,'m':b1,'n':[c1,c2]},{'m':d},x,y) # H2
-2.35405404
>>> nsum(lambda m,n: rf(a1,m-n)*rf(b1,m)*rf(c1,n)*rf(c2,n)/rf(d,m)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
-2.35405404
>>> hyper2d({'2m+n':a1,'n':b1},{'m+n':c1},x,y) # H3
0.974479074
>>> nsum(lambda m,n: rf(a1,2*m+n)*rf(b1,n)/rf(c1,m+n)*\
... x**m*y**n/fac(m)/fac(n), [0,inf], [0,inf])
0.974479074
**References**
1. [SrivastavaKarlsson]_
2. [Weisstein]_ http://mathworld.wolfram.com/HornFunction.html
3. [Weisstein]_ http://mathworld.wolfram.com/AppellHypergeometricFunction.html
"""
x = ctx.convert(x)
y = ctx.convert(y)
def parse(dct, key):
args = dct.pop(key, [])
try:
args = list(args)
except TypeError:
args = [args]
return [ctx.convert(arg) for arg in args]
a_s = dict(a)
b_s = dict(b)
a_m = parse(a, 'm')
a_n = parse(a, 'n')
a_m_add_n = parse(a, 'm+n')
a_m_sub_n = parse(a, 'm-n')
a_n_sub_m = parse(a, 'n-m')
a_2m_add_n = parse(a, '2m+n')
a_2m_sub_n = parse(a, '2m-n')
a_2n_sub_m = parse(a, '2n-m')
b_m = parse(b, 'm')
b_n = parse(b, 'n')
b_m_add_n = parse(b, 'm+n')
if a: raise ValueError("unsupported key: %r" % a.keys()[0])
if b: raise ValueError("unsupported key: %r" % b.keys()[0])
s = 0
outer = ctx.one
m = ctx.mpf(0)
ok_count = 0
prec = ctx.prec
maxterms = kwargs.get('maxterms', 20*prec)
try:
ctx.prec += 10
tol = +ctx.eps
while 1:
inner_sign = 1
outer_sign = 1
inner_a = list(a_n)
inner_b = list(b_n)
outer_a = [a+m for a in a_m]
outer_b = [b+m for b in b_m]
# (a)_{m+n} = (a)_m (a+m)_n
for a in a_m_add_n:
a = a+m
inner_a.append(a)
outer_a.append(a)
# (b)_{m+n} = (b)_m (b+m)_n
for b in b_m_add_n:
b = b+m
inner_b.append(b)
outer_b.append(b)
# (a)_{n-m} = (a-m)_n / (a-m)_m
for a in a_n_sub_m:
inner_a.append(a-m)
outer_b.append(a-m-1)
# (a)_{m-n} = (-1)^(m+n) (1-a-m)_m / (1-a-m)_n
for a in a_m_sub_n:
inner_sign *= (-1)
outer_sign *= (-1)**(m)
inner_b.append(1-a-m)
outer_a.append(-a-m)
# (a)_{2m+n} = (a)_{2m} (a+2m)_n
for a in a_2m_add_n:
inner_a.append(a+2*m)
outer_a.append((a+2*m)*(1+a+2*m))
# (a)_{2m-n} = (-1)^(2m+n) (1-a-2m)_{2m} / (1-a-2m)_n
for a in a_2m_sub_n:
inner_sign *= (-1)
inner_b.append(1-a-2*m)
outer_a.append((a+2*m)*(1+a+2*m))
# (a)_{2n-m} = 4^n ((a-m)/2)_n ((a-m+1)/2)_n / (a-m)_m
for a in a_2n_sub_m:
inner_sign *= 4
inner_a.append(0.5*(a-m))
inner_a.append(0.5*(a-m+1))
outer_b.append(a-m-1)
inner = ctx.hyper(inner_a, inner_b, inner_sign*y,
zeroprec=ctx.prec, **kwargs)
term = outer * inner * outer_sign
if abs(term) < tol:
ok_count += 1
else:
ok_count = 0
if ok_count >= 3 or not outer:
break
s += term
for a in outer_a: outer *= a
for b in outer_b: outer /= b
m += 1
outer = outer * x / m
if m > maxterms:
raise ctx.NoConvergence("maxterms exceeded in hyper2d")
finally:
ctx.prec = prec
return +s
"""
@defun
def kampe_de_feriet(ctx,a,b,c,d,e,f,x,y,**kwargs):
return ctx.hyper2d({'m+n':a,'m':b,'n':c},
{'m+n':d,'m':e,'n':f}, x,y, **kwargs)
"""
@defun
def bihyper(ctx, a_s, b_s, z, **kwargs):
r"""
Evaluates the bilateral hypergeometric series
.. math ::
\,_AH_B(a_1, \ldots, a_k; b_1, \ldots, b_B; z) =
\sum_{n=-\infty}^{\infty}
\frac{(a_1)_n \ldots (a_A)_n}
{(b_1)_n \ldots (b_B)_n} \, z^n
where, for direct convergence, `A = B` and `|z| = 1`, although a
regularized sum exists more generally by considering the
bilateral series as a sum of two ordinary hypergeometric
functions. In order for the series to make sense, none of the
parameters may be integers.
**Examples**
The value of `\,_2H_2` at `z = 1` is given by Dougall's formula::
>>> from mpmath import *
>>> mp.dps = 25; mp.pretty = True
>>> a,b,c,d = 0.5, 1.5, 2.25, 3.25
>>> bihyper([a,b],[c,d],1)
-14.49118026212345786148847
>>> gammaprod([c,d,1-a,1-b,c+d-a-b-1],[c-a,d-a,c-b,d-b])
-14.49118026212345786148847
The regularized function `\,_1H_0` can be expressed as the
sum of one `\,_2F_0` function and one `\,_1F_1` function::
>>> a = mpf(0.25)
>>> z = mpf(0.75)
>>> bihyper([a], [], z)
(0.2454393389657273841385582 + 0.2454393389657273841385582j)
>>> hyper([a,1],[],z) + (hyper([1],[1-a],-1/z)-1)
(0.2454393389657273841385582 + 0.2454393389657273841385582j)
>>> hyper([a,1],[],z) + hyper([1],[2-a],-1/z)/z/(a-1)
(0.2454393389657273841385582 + 0.2454393389657273841385582j)
**References**
1. [Slater]_ (chapter 6: "Bilateral Series", pp. 180-189)
2. [Wikipedia]_ http://en.wikipedia.org/wiki/Bilateral_hypergeometric_series
"""
z = ctx.convert(z)
c_s = a_s + b_s
p = len(a_s)
q = len(b_s)
if (p, q) == (0,0) or (p, q) == (1,1):
return ctx.zero * z
neg = (p-q) % 2
def h(*c_s):
a_s = list(c_s[:p])
b_s = list(c_s[p:])
aa_s = [2-b for b in b_s]
bb_s = [2-a for a in a_s]
rp = [(-1)**neg * z] + [1-b for b in b_s] + [1-a for a in a_s]
rc = [-1] + [1]*len(b_s) + [-1]*len(a_s)
T1 = [], [], [], [], a_s + [1], b_s, z
T2 = rp, rc, [], [], aa_s + [1], bb_s, (-1)**neg / z
return T1, T2
return ctx.hypercomb(h, c_s, **kwargs)