def diffc(f, x, n=1, radius=mpf(0.5)): """ Compute an approximation of the nth derivative of f at the point x using the Cauchy integral formula. This only works for analytic functions. A circular path with the given radius is used. diffc increases the working precision slightly to avoid simple rounding errors. Note that, especially for large n, differentiation is extremely ill-conditioned, so this precaution does not guarantee a correct result. (Provided there are no singularities in the way, increasing the radius may help.) The returned value will be a complex number; a large imaginary part for a derivative that should be real may indicate a large numerical error. """ prec = mp.prec try: mp.prec += 10 def g(t): rei = radius*exp(j*t) z = x + rei return f(z) / rei**n d = quadts(g, [0, 2*pi]) return d * factorial(n) / (2*pi) finally: mp.prec = prec
def sumem(f, interval, N=None, integral=None, fderiv=None, error=False, verbose=False): """ Sum f(k) for k = a, a+1, ..., b where [a, b] = interval, using Euler-Maclaurin summation. This algorithm is efficient for slowly convergent nonoscillatory sums; the essential condition is that f must be analytic. The method relies on approximating the sum by an integral, so f must be smooth and well-behaved enough to be integrated numerically. With error=True, a tuple (s, err) is returned where s is the calculated sum and err is the estimated magnitude of the error. With verbose=True, detailed information about progress and errors is printed. >>> mp.dps = 15 >>> s, err = sumem(lambda n: 1/n**2, 1, inf, error=True) >>> print s 1.64493406684823 >>> print pi**2 / 6 1.64493406684823 >>> nprint(err) 2.22045e-16 N is the number of terms to compute directly before using the Euler-Maclaurin formula to approximate the tail. It must be set high enough; often roughly N ~ dps is the right size. High-order derivatives of f are also needed. By default, these are computed using numerical integration, which is the most expensive part of the calculation. The default method assumes that all poles of f are located close to the origin. A custom nth derivative function fderiv(x, n) can be provided as a keyword parameter. This is much more efficient: >>> f = lambda n: 1/n**2 >>> fp = lambda x, n: (-1)**n * factorial(n+1) * x**(-2-n) >>> mp.dps = 50 >>> print sumem(lambda n: 1/n**2, 1, inf, fderiv=fp) 1.6449340668482264364724151666460251892189499012068 >>> print pi**2 / 6 1.6449340668482264364724151666460251892189499012068 If b = inf, f and its derivatives are all assumed to vanish at infinity. It is assumed that a is finite, so doubly infinite sums cannot be evaluated directly. """ a, b = AS_POINTS(interval) if N is None: N = 3*mp.dps + 20 a, b, N = mpf(a), mpf(b), mpf(N) infinite = (b == inf) weps = eps * 2**8 if verbose: print "Summing f(k) from k = %i to %i" % (a, a+N-1) S = sum(f(mpf(k)) for k in xrange(a, a+N)) if integral is None: if verbose: print "Integrating f(x) from x = %i to %s" % (a+N, nstr(b)) I, ierr = quadts(f, [a+N, b], error=1) # XXX: hack for relative error ierr /= abs(I) else: I, ierr = integral(a+N, b), mpf(0) # There is little hope if the tail cannot be integrated # accurately. Estimate magnitude of tail as the error. if ierr > weps: if verbose: print "Failed to converge to target accuracy (integration failed)" if error: return S+I, abs(I) + ierr else: return S+I if infinite: C = f(a+N) / 2 else: C = (f(a+N) + f(b)) / 2 # Default (inefficient) approach for derivatives if not fderiv: fderiv = lambda x, n: diffc(f, x, n, radius=N*0.75) k = 1 prev = 0 if verbose: print "Summing tail" fac = 2 while 1: if infinite: D = fderiv(a+N, 2*k-1) else: D = fderiv(a+N, 2*k-1) - fderiv(b, 2*k-1) # B(2*k) / fac(2*k) term = bernoulli(2*k) / fac * D mag = abs(term) if verbose: print "term", k, "magnitude =", nstr(mag) # Error can be estimated as the magnitude of the smallest term if k >= 2: if mag < weps: if verbose: print "Converged to target accuracy" res, err = I + C + S, eps * 2**15 break if mag > abs(prev): if verbose: print "Failed to converge to target accuracy (N too low)" res, err = I + C + S, abs(term) break S -= term k += 1 fac *= (2*k) * (2*k-1) prev = term if isinstance(res, mpc) and not isinstance(I, mpc): res, err = res.real, err if error: return res, err else: return res