def chebyfit(f, interval, N, error=False): """ Chebyshev approximation: returns coefficients of a degree N-1 polynomial that approximates f on the interval [a, b]. With error=True, also returns an estimate of the maximum error. """ a, b = AS_POINTS(interval) orig = mp.prec try: mp.prec = orig + int(N**0.5) + 20 c = [chebcoeff(f,a,b,k,N) for k in range(N)] d = [mpf(0)] * N d[0] = -c[0]/2 h = mpf(0.5) T = chebT(mpf(2)/(b-a), mpf(-1)*(b+a)/(b-a)) for k in range(N): Tk = T.next() for i in range(len(Tk)): d[i] += c[k]*Tk[i] d = d[::-1] # Estimate maximum error err = mpf(0) for k in range(N): x = cos(pi*k/N) * (b-a)*h + (b+a)*h err = max(err, abs(f(x) - polyval(d, x))) finally: mp.prec = orig if error: return d, +err else: return d
def sumsh(f, interval, n=None, m=None): """ Sum f(k) for k = a, a+1, ..., b where [a, b] = interval, using an n-term Shanks transformation. With m > 1, the Shanks transformation is applied recursively m times. Shanks summation often works well for slowly convergent and/or alternating Taylor series. """ a, b = AS_POINTS(interval) assert b == inf if not n: n = 5 + int(mp.dps * 1.2) if not m: m = 2 + n//3 orig = mp.prec try: mp.prec = 2*orig s = mpf(0) tbl = [] for k in range(a, a+n+m+2): s += f(mpf(k)) tbl.append(s) s = shanks_extrapolation(tbl, n, m) finally: mp.prec = orig return +s
def quadosc(f, interval, period=None, zeros=None, alt=1): """ Integrates f(x) over interval = [a, b] where at least one of a and b is infinite and f is a slowly decaying oscillatory function. The zeros of f must be provided, either by specifying a period (suitable when f contains a pure sine or cosine factor) or providing a function that returns the nth zero (suitable when the oscillation is not strictly periodic). """ a, b = AS_POINTS(interval) a = convert_lossless(a) b = convert_lossless(b) if period is None and zeros is None: raise ValueError( \ "either the period or zeros keyword parameter must be specified") if a == -inf and b == inf: s1 = quadosc(f, [a, 0], zeros=zeros, period=period, alt=alt) s2 = quadosc(f, [0, b], zeros=zeros, period=period, alt=alt) return s1 + s2 if a == -inf: if zeros: return quadosc(lambda x:f(-x), [-b,-a], lambda n: zeros(-n), alt=alt) else: return quadosc(lambda x:f(-x), [-b,-a], period=period, alt=alt) if b != inf: raise ValueError("quadosc requires an infinite integration interval") if not zeros: zeros = lambda n: n*period/2 for n in range(1,10): p = zeros(n) if p > a: break if n >= 9: raise ValueError("zeros do not appear to be correctly indexed") if alt == 0: s = quadgl(f, [a, zeros(n+1)]) s += sumrich(lambda k: quadgl(f, [zeros(2*k), zeros(2*k+2)]), [n, inf]) else: s = quadgl(f, [a, zeros(n)]) s += sumsh(lambda k: quadgl(f, [zeros(k), zeros(k+1)]), [n, inf]) return s
def sumrich(f, interval, n=None, N=None): """ Sum f(k) for k = a, a+1, ..., b where [a, b] = interval, using Richardson extrapolation. This function is essentially equivalent to using limit() on the sequence of partial sums. """ a, b = AS_POINTS(interval) assert b == inf if not n: n = 3 + int(mp.dps * 0.5) if not N: N = 2*n orig = mp.prec try: mp.prec = 2*orig s = mpf(0) tbl = [] for k in range(a, a+n+N+1): s += f(mpf(k)) tbl.append(s) s = richardson_extrapolation(lambda k: tbl[int(k)], n, N) finally: mp.prec = orig return +s
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
def quadosc(f, interval, omega=None, period=None, zeros=None): r""" Calculates .. math :: I = \int_a^b f(x) dx where at least one of `a` and `b` is infinite and where `f(x) = g(x) \cos(\omega x + \phi)` for some slowly decreasing function `g(x)`. With proper input, :func:`quadosc` can also handle oscillatory integrals where the oscillation rate is different from a pure sine or cosine wave. In the standard case when `|a| < \infty, b = \infty`, :func:`quadosc` works by evaluating the infinite series .. math :: I = \int_a^{x_1} f(x) dx + \sum_{k=1}^{\infty} \int_{x_k}^{x_{k+1}} f(x) dx where `x_k` are consecutive zeros (alternatively some other periodic reference point) of `f(x)`. Accordingly, :func:`quadosc` requires information about the zeros of `f(x)`. For a periodic function, you can specify the zeros by either providing the angular frequency `\omega` (*omega*) or the *period* `2 \pi/\omega`. In general, you can specify the `n`-th zero by providing the *zeros* arguments. Below is an example of each:: >>> from mpmath import * >>> mp.dps = 15 >>> f = lambda x: sin(3*x)/(x**2+1) >>> print quadosc(f, [0,inf], omega=3) 0.37833007080198 >>> print quadosc(f, [0,inf], period=2*pi/3) 0.37833007080198 >>> print quadosc(f, [0,inf], zeros=lambda n: pi*n/3) 0.37833007080198 >>> print (ei(3)*exp(-3)-exp(3)*ei(-3))/2 # Computed by Mathematica 0.37833007080198 Note that *zeros* was specified to multiply `n` by the *half-period*, not the full period. In theory, it does not matter whether each partial integral is done over a half period or a full period. However, if done over half-periods, the infinite series passed to :func:`nsum` becomes an *alternating series* and this typically makes the extrapolation much more efficient. Here is an example of an integration over the entire real line, and a half-infinite integration starting at `-\infty`:: >>> print quadosc(lambda x: cos(x)/(1+x**2), [-inf, inf], omega=1) 1.15572734979092 >>> print pi/e 1.15572734979092 >>> print quadosc(lambda x: cos(x)/x**2, [-inf, -1], period=2*pi) -0.0844109505595739 >>> print cos(1)+si(1)-pi/2 -0.0844109505595738 Of course, the integrand may contain a complex exponential just as well as a real sine or cosine:: >>> print quadosc(lambda x: exp(3*j*x)/(1+x**2), [-inf,inf], omega=3) (0.156410688228254 + 0.0j) >>> print pi/e**3 0.156410688228254 >>> print quadosc(lambda x: exp(3*j*x)/(2+x+x**2), [-inf,inf], omega=3) (0.00317486988463794 - 0.0447701735209082j) >>> print 2*pi/sqrt(7)/exp(3*(j+sqrt(7))/2) (0.00317486988463794 - 0.0447701735209082j) **Non-periodic functions** If `f(x) = g(x) h(x)` for some function `h(x)` that is not strictly periodic, *omega* or *period* might not work, and it might be necessary to use *zeros*. A notable exception can be made for Bessel functions which, though not periodic, are "asymptotically periodic" in a sufficiently strong sense that the sum extrapolation will work out:: >>> print quadosc(j0, [0, inf], period=2*pi) 1.0 >>> print quadosc(j1, [0, inf], period=2*pi) 1.0 More properly, one should provide the exact Bessel function zeros:: >>> j0zero = lambda n: findroot(j0, pi*(n-0.25)) >>> print quadosc(j0, [0, inf], zeros=j0zero) 1.0 For an example where *zeros* becomes necessary, consider the complete Fresnel integrals .. math :: \int_0^{\infty} \cos x^2\,dx = \int_0^{\infty} \sin x^2\,dx = \sqrt{\frac{\pi}{8}}. Although the integrands do not decrease in magnitude as `x \to \infty`, the integrals are convergent since the oscillation rate increases (causing consecutive periods to asymptotically cancel out). These integrals are virtually impossible to calculate to any kind of accuracy using standard quadrature rules. However, if one provides the correct asymptotic distribution of zeros (`x_n \sim \sqrt{n}`), :func:`quadosc` works:: >>> mp.dps = 30 >>> f = lambda x: cos(x**2) >>> print quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) 0.626657068657750125603941321203 >>> f = lambda x: sin(x**2) >>> print quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) 0.626657068657750125603941321203 >>> print sqrt(pi/8) 0.626657068657750125603941321203 (Interestingly, these integrals can still be evaluated if one places some other constant than `\pi` in the square root sign.) In general, if `f(x) \sim g(x) \cos(h(x))`, the zeros follow the inverse-function distribution `h^{-1}(x)`:: >>> mp.dps = 15 >>> f = lambda x: sin(exp(x)) >>> print quadosc(f, [1,inf], zeros=lambda n: log(n)) -0.25024394235267 >>> print pi/2-si(e) -0.250243942352671 **Non-alternating functions** If the integrand oscillates around a positive value, without alternating signs, the extrapolation might fail. A simple trick that sometimes works is to multiply or divide the frequency by 2:: >>> f = lambda x: 1/x**2+sin(x)/x**4 >>> print quadosc(f, [1,inf], omega=1) # Bad 1.28642190869921 >>> print quadosc(f, [1,inf], omega=0.5) # Perfect 1.28652953559617 >>> print 1+(cos(1)+ci(1)+sin(1))/6 1.28652953559617 **Fast decay** :func:`quadosc` is primarily useful for slowly decaying integrands. If the integrand decreases exponentially or faster, :func:`quad` will likely handle it without trouble (and generally be much faster than :func:`quadosc`):: >>> print quadosc(lambda x: cos(x)/exp(x), [0, inf], omega=1) 0.5 >>> print quad(lambda x: cos(x)/exp(x), [0, inf]) 0.5 """ a, b = AS_POINTS(interval) a = mpmathify(a) b = mpmathify(b) if [omega, period, zeros].count(None) != 2: raise ValueError( \ "must specify exactly one of omega, period, zeros") if a == -inf and b == inf: s1 = quadosc(f, [a, 0], omega=omega, zeros=zeros, period=period) s2 = quadosc(f, [0, b], omega=omega, zeros=zeros, period=period) return s1 + s2 if a == -inf: if zeros: return quadosc(lambda x:f(-x), [-b,-a], lambda n: zeros(-n)) else: return quadosc(lambda x:f(-x), [-b,-a], omega=omega, period=period) if b != inf: raise ValueError("quadosc requires an infinite integration interval") if not zeros: if omega: period = 2*pi/omega zeros = lambda n: n*period/2 #for n in range(1,10): # p = zeros(n) # if p > a: # break #if n >= 9: # raise ValueError("zeros do not appear to be correctly indexed") n = 1 from calculus import nsum s = quadgl(f, [a, zeros(n)]) s += nsum(lambda k: quadgl(f, [zeros(k), zeros(k+1)]), [n, inf]) return s
def quad(f, *points, **kwargs): r""" Computes a single, double or triple integral over a given 1D interval, 2D rectangle, or 3D cuboid. A basic example:: >>> from mpmath import * >>> mp.dps = 15 >>> print quad(sin, [0, pi]) 2.0 A basic 2D integral:: >>> f = lambda x, y: cos(x+y/2) >>> print quad(f, [-pi/2, pi/2], [0, pi]) 4.0 **Interval format** The integration range for each dimension may be specified using a list or tuple. Arguments are interpreted as follows: ``quad(f, [x1, x2])`` -- calculates `\int_{x_1}^{x_2} f(x) \, dx` ``quad(f, [x1, x2], [y1, y2])`` -- calculates `\int_{x_1}^{x_2} \int_{y_1}^{y_2} f(x,y) \, dy \, dx` ``quad(f, [x1, x2], [y1, y2], [z1, z2])`` -- calculates `\int_{x_1}^{x_2} \int_{y_1}^{y_2} \int_{z_1}^{z_2} f(x,y,z) \, dz \, dy \, dx` Endpoints may be finite or infinite. An interval descriptor may also contain more than two points. In this case, the integration is split into subintervals, between each pair of consecutive points. This is useful for dealing with mid-interval discontinuities, or integrating over large intervals where the function is irregular or oscillates. **Options** :func:`quad` recognizes the following keyword arguments: *method* Chooses integration algorithm (described below). *error* If set to true, :func:`quad` returns `(v, e)` where `v` is the integral and `e` is the estimated error. *maxdegree* Maximum degree of the quadrature rule to try before quitting. *verbose* Print details about progress. **Algorithms** Mpmath presently implements two integration algorithms: tanh-sinh quadrature and Gauss-Legendre quadrature. These can be selected using *method='tanh-sinh'* or *method='gauss-legendre'* or by passing the classes *method=TanhSinh*, *method=GaussLegendre*. The functions :func:`quadts` and :func:`quadgl` are also available as shortcuts. Both algorithms have the property that doubling the number of evaluation points roughly doubles the accuracy, so both are ideal for high precision quadrature (hundreds or thousands of digits). At high precision, computing the nodes and weights for the integration can be expensive (more expensive than computing the function values). To make repeated integrations fast, nodes are automatically cached. The advantages of the tanh-sinh algorithm are that it tends to handle endpoint singularities well, and that the nodes are cheap to compute on the first run. For these reasons, it is used by :func:`quad` as the default algorithm. Gauss-Legendre quadrature often requires fewer function evaluations, and is therefore often faster for repeated use, but the algorithm does not handle endpoint singularities as well and the nodes are more expensive to compute. Gauss-Legendre quadrature can be a better choice if the integrand is smooth and repeated integrations are required (e.g. for multiple integrals). See the documentation for :class:`TanhSinh` and :class:`GaussLegendre` for additional details. **Examples of 1D integrals** Intervals may be infinite or half-infinite. The following two examples evaluate the limits of the inverse tangent function (`\int 1/(1+x^2) = \tan^{-1} x`), and the Gaussian integral `\int_{\infty}^{\infty} \exp(-x^2)\,dx = \sqrt{\pi}`:: >>> mp.dps = 15 >>> print quad(lambda x: 2/(x**2+1), [0, inf]) 3.14159265358979 >>> print quad(lambda x: exp(-x**2), [-inf, inf])**2 3.14159265358979 Integrals can typically be resolved to high precision. The following computes 50 digits of `\pi` by integrating the area of the half-circle defined by `x^2 + y^2 \le 1`, `-1 \le x \le 1`, `y \ge 0`:: >>> mp.dps = 50 >>> print 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) 3.1415926535897932384626433832795028841971693993751 One can just as well compute 1000 digits (output truncated):: >>> mp.dps = 1000 >>> print 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) #doctest:+ELLIPSIS 3.141592653589793238462643383279502884...216420198 Complex integrals are supported. The following computes a residue at `z = 0` by integrating counterclockwise along the diamond-shaped path from `1` to `+i` to `-1` to `-i` to `1`:: >>> mp.dps = 15 >>> print quad(lambda z: 1/z, [1,j,-1,-j,1]) (0.0 + 6.28318530717959j) **Examples of 2D and 3D integrals** Here are several nice examples of analytically solvable 2D integrals (taken from MathWorld [1]) that can be evaluated to high precision fairly rapidly by :func:`quad`:: >>> mp.dps = 30 >>> f = lambda x, y: (x-1)/((1-x*y)*log(x*y)) >>> print quad(f, [0, 1], [0, 1]) 0.577215664901532860606512090082 >>> print euler 0.577215664901532860606512090082 >>> f = lambda x, y: 1/sqrt(1+x**2+y**2) >>> print quad(f, [-1, 1], [-1, 1]) 3.17343648530607134219175646705 >>> print 4*log(2+sqrt(3))-2*pi/3 3.17343648530607134219175646705 >>> f = lambda x, y: 1/(1-x**2 * y**2) >>> print quad(f, [0, 1], [0, 1]) 1.23370055013616982735431137498 >>> print pi**2 / 8 1.23370055013616982735431137498 >>> print quad(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]) 1.64493406684822643647241516665 >>> print pi**2 / 6 1.64493406684822643647241516665 Multiple integrals may be done over infinite ranges:: >>> mp.dps = 15 >>> print quad(lambda x,y: exp(-x-y), [0, inf], [1, inf]) 0.367879441171442 >>> print 1/e 0.367879441171442 For nonrectangular areas, one can call :func:`quad` recursively. For example, we can replicate the earlier example of calculating `\pi` by integrating over the unit-circle, and actually use double quadrature to actually measure the area circle:: >>> f = lambda x: quad(lambda y: 1, [-sqrt(1-x**2), sqrt(1-x**2)]) >>> print quad(f, [-1, 1]) 3.14159265358979 Here is a simple triple integral:: >>> mp.dps = 15 >>> f = lambda x,y,z: x*y/(1+z) >>> print quad(f, [0,1], [0,1], [1,2], method='gauss-legendre') 0.101366277027041 >>> print (log(3)-log(2))/4 0.101366277027041 **Singularities** Both tanh-sinh and Gauss-Legendre quadrature are designed to integrate smooth (infinitely differentiable) functions. Neither algorithm copes well with mid-interval singularities (such as mid-interval discontinuities in `f(x)` or `f'(x)`). The best solution is to split the integral into parts:: >>> mp.dps = 15 >>> print quad(lambda x: abs(sin(x)), [0, 2*pi]) # Bad 3.99900894176779 >>> print quad(lambda x: abs(sin(x)), [0, pi, 2*pi]) # Good 4.0 The tanh-sinh rule often works well for integrands having a singularity at one or both endpoints:: >>> mp.dps = 15 >>> print quad(log, [0, 1], method='tanh-sinh') # Good -1.0 >>> print quad(log, [0, 1], method='gauss-legendre') # Bad -0.999932197413801 However, the result may still be inaccurate for some functions:: >>> print quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') 1.99999999946942 This problem is not due to the quadrature rule per se, but to numerical amplification of errors in the nodes. The problem can be circumvented by temporarily increasing the precision:: >>> mp.dps = 30 >>> a = quad(lambda x: 1/sqrt(x), [0, 1], method='tanh-sinh') >>> mp.dps = 15 >>> print +a 2.0 **Highly variable functions** For functions that are smooth (in the sense of being infinitely differentiable) but contain sharp mid-interval peaks or many "bumps", :func:`quad` may fail to provide full accuracy. For example, with default settings, :func:`quad` is able to integrate `\sin(x)` accurately over an interval of length 100 but not over length 1000:: >>> print quad(sin, [0, 100]), 1-cos(100) # Good 0.137681127712316 0.137681127712316 >>> print quad(sin, [0, 1000]), 1-cos(1000) # Bad -37.8587612408485 0.437620923709297 One solution is to break the integration into 10 intervals of length 100:: >>> print quad(sin, linspace(0, 1000, 10)) # Good 0.437620923709297 Another is to increase the degree of the quadrature:: >>> print quad(sin, [0, 1000], maxdegree=10) # Also good 0.437620923709297 Whether splitting the interval or increasing the degree is more efficient differs from case to case. Another example is the function `1/(1+x^2)`, which has a sharp peak centered around `x = 0`:: >>> f = lambda x: 1/(1+x**2) >>> print quad(f, [-100, 100]) # Bad 3.64804647105268 >>> print quad(f, [-100, 100], maxdegree=10) # Good 3.12159332021646 >>> print quad(f, [-100, 0, 100]) # Also good 3.12159332021646 **References** 1. http://mathworld.wolfram.com/DoubleIntegral.html """ rule = kwargs.get('method', TanhSinh) if type(rule) is str: rule = {'tanh-sinh':TanhSinh, 'gauss-legendre':GaussLegendre}[rule] rule = rule() verbose = kwargs.get('verbose') dim = len(points) orig = prec = mp.prec epsilon = eps/8 m = kwargs.get('maxdegree') or rule.guess_degree(prec) points = [AS_POINTS(p) for p in points] try: mp.prec += 20 if dim == 1: v, err = rule.summation(f, points[0], prec, epsilon, m, verbose) elif dim == 2: v, err = rule.summation(lambda x: \ rule.summation(lambda y: f(x,y), \ points[1], prec, epsilon, m)[0], points[0], prec, epsilon, m, verbose) elif dim == 3: v, err = rule.summation(lambda x: \ rule.summation(lambda y: \ rule.summation(lambda z: f(x,y,z), \ points[2], prec, epsilon, m)[0], points[1], prec, epsilon, m)[0], points[0], prec, epsilon, m, verbose) else: raise NotImplementedError("quadrature must have dim 1, 2 or 3") finally: mp.prec = orig if kwargs.get("error"): return +v, err return +v
def quad(f, *points, **kwargs): """ Computes a single, double or triple integral over a given interval, rectangle, or box. quad(f(x), [x1, x2]) quad(f(x,y), [x1, x2], [y1, y2]) quad(f(x,y,z), [x1, x2], [y1, y2], [z1, z2]) By default, tanh-sinh quadrature is used. A custom method can be specified via the 'method' keyword argument. Basic examples: >>> from mpmath import * >>> print quad(lambda x: exp(-x**2), [0, inf]) 0.886226925452758 >>> f = lambda x, y: exp(x*sin(y)) >>> print quad(f, [0, 1], [0, pi]) 4.47046663466179 The functions quadgl(...) and quadts(...) act as shortcuts for quad(..., method='gauss-legendre') and quad(..., method='tanh-sinh'). An interval may contain more than two points. In this case, the integration is split into subintervals, between each pair of consecutive points. This is useful for dealing with mid-interval discontinuities, or integrating over large intervals where the function is irregular or oscillates: >>> print quadgl(lambda x: abs(sin(x)), [0, pi, 2*pi]) 4.0 >>> print quadgl(sin, arange(0, 1000+1, 10)) 0.437620923709297 >>> print cos(0) - cos(1000) 0.437620923709297 Additional keyword options: verbose=True -- print details about progress error=True -- return (value, err) where err is the estimated error """ rule = kwargs.get('method', TanhSinh) if type(rule) is str: rule = {'tanh-sinh': TanhSinh, 'gauss-legendre': GaussLegendre}[rule] verbose = kwargs.get('verbose') dim = len(points) orig = prec = mp.prec epsilon = eps / 8 m = kwargs.get('maxlevel') or rule.guess_level(prec) points = [AS_POINTS(p) for p in points] try: mp.prec += 20 if dim == 1: v, err = rule.summation(f, points[0], prec, epsilon, m, verbose) elif dim == 2: v, err = rule.summation(lambda x: \ rule.summation(lambda y: f(x,y), \ points[1], prec, epsilon, m)[0], points[0], prec, epsilon, m, verbose) elif dim == 3: v, err = rule.summation(lambda x: \ rule.summation(lambda y: \ rule.summation(lambda z: f(x,y,z), \ points[2], prec, epsilon, m)[0], points[1], prec, epsilon, m)[0], points[0], prec, epsilon, m, verbose) else: raise NotImplementedError("quadrature must have dim 1, 2 or 3") finally: mp.prec = orig if kwargs.get("error"): return +v, err return +v