Buckets:
MisterAI/LocalAI_Demo_backends / cpu-diffusers.upgrade-tmp /venv /lib /python3.10 /site-packages /mpmath /calculus /quadrature.py
| import math | |
| from ..libmp.backend import xrange | |
| class QuadratureRule(object): | |
| """ | |
| Quadrature rules are implemented using this class, in order to | |
| simplify the code and provide a common infrastructure | |
| for tasks such as error estimation and node caching. | |
| You can implement a custom quadrature rule by subclassing | |
| :class:`QuadratureRule` and implementing the appropriate | |
| methods. The subclass can then be used by :func:`~mpmath.quad` by | |
| passing it as the *method* argument. | |
| :class:`QuadratureRule` instances are supposed to be singletons. | |
| :class:`QuadratureRule` therefore implements instance caching | |
| in :func:`~mpmath.__new__`. | |
| """ | |
| def __init__(self, ctx): | |
| self.ctx = ctx | |
| self.standard_cache = {} | |
| self.transformed_cache = {} | |
| self.interval_count = {} | |
| def clear(self): | |
| """ | |
| Delete cached node data. | |
| """ | |
| self.standard_cache = {} | |
| self.transformed_cache = {} | |
| self.interval_count = {} | |
| def calc_nodes(self, degree, prec, verbose=False): | |
| r""" | |
| Compute nodes for the standard interval `[-1, 1]`. Subclasses | |
| should probably implement only this method, and use | |
| :func:`~mpmath.get_nodes` method to retrieve the nodes. | |
| """ | |
| raise NotImplementedError | |
| def get_nodes(self, a, b, degree, prec, verbose=False): | |
| """ | |
| Return nodes for given interval, degree and precision. The | |
| nodes are retrieved from a cache if already computed; | |
| otherwise they are computed by calling :func:`~mpmath.calc_nodes` | |
| and are then cached. | |
| Subclasses should probably not implement this method, | |
| but just implement :func:`~mpmath.calc_nodes` for the actual | |
| node computation. | |
| """ | |
| key = (a, b, degree, prec) | |
| if key in self.transformed_cache: | |
| return self.transformed_cache[key] | |
| orig = self.ctx.prec | |
| try: | |
| self.ctx.prec = prec+20 | |
| # Get nodes on standard interval | |
| if (degree, prec) in self.standard_cache: | |
| nodes = self.standard_cache[degree, prec] | |
| else: | |
| nodes = self.calc_nodes(degree, prec, verbose) | |
| self.standard_cache[degree, prec] = nodes | |
| # Transform to general interval | |
| nodes = self.transform_nodes(nodes, a, b, verbose) | |
| if key in self.interval_count: | |
| self.transformed_cache[key] = nodes | |
| else: | |
| self.interval_count[key] = True | |
| finally: | |
| self.ctx.prec = orig | |
| return nodes | |
| def transform_nodes(self, nodes, a, b, verbose=False): | |
| r""" | |
| Rescale standardized nodes (for `[-1, 1]`) to a general | |
| interval `[a, b]`. For a finite interval, a simple linear | |
| change of variables is used. Otherwise, the following | |
| transformations are used: | |
| .. math :: | |
| \lbrack a, \infty \rbrack : t = \frac{1}{x} + (a-1) | |
| \lbrack -\infty, b \rbrack : t = (b+1) - \frac{1}{x} | |
| \lbrack -\infty, \infty \rbrack : t = \frac{x}{\sqrt{1-x^2}} | |
| """ | |
| ctx = self.ctx | |
| a = ctx.convert(a) | |
| b = ctx.convert(b) | |
| one = ctx.one | |
| if (a, b) == (-one, one): | |
| return nodes | |
| half = ctx.mpf(0.5) | |
| new_nodes = [] | |
| if ctx.isinf(a) or ctx.isinf(b): | |
| if (a, b) == (ctx.ninf, ctx.inf): | |
| p05 = -half | |
| for x, w in nodes: | |
| x2 = x*x | |
| px1 = one-x2 | |
| spx1 = px1**p05 | |
| x = x*spx1 | |
| w *= spx1/px1 | |
| new_nodes.append((x, w)) | |
| elif a == ctx.ninf: | |
| b1 = b+1 | |
| for x, w in nodes: | |
| u = 2/(x+one) | |
| x = b1-u | |
| w *= half*u**2 | |
| new_nodes.append((x, w)) | |
| elif b == ctx.inf: | |
| a1 = a-1 | |
| for x, w in nodes: | |
| u = 2/(x+one) | |
| x = a1+u | |
| w *= half*u**2 | |
| new_nodes.append((x, w)) | |
| elif a == ctx.inf or b == ctx.ninf: | |
| return [(x,-w) for (x,w) in self.transform_nodes(nodes, b, a, verbose)] | |
| else: | |
| raise NotImplementedError | |
| else: | |
| # Simple linear change of variables | |
| C = (b-a)/2 | |
| D = (b+a)/2 | |
| for x, w in nodes: | |
| new_nodes.append((D+C*x, C*w)) | |
| return new_nodes | |
| def guess_degree(self, prec): | |
| """ | |
| Given a desired precision `p` in bits, estimate the degree `m` | |
| of the quadrature required to accomplish full accuracy for | |
| typical integrals. By default, :func:`~mpmath.quad` will perform up | |
| to `m` iterations. The value of `m` should be a slight | |
| overestimate, so that "slightly bad" integrals can be dealt | |
| with automatically using a few extra iterations. On the | |
| other hand, it should not be too big, so :func:`~mpmath.quad` can | |
| quit within a reasonable amount of time when it is given | |
| an "unsolvable" integral. | |
| The default formula used by :func:`~mpmath.guess_degree` is tuned | |
| for both :class:`TanhSinh` and :class:`GaussLegendre`. | |
| The output is roughly as follows: | |
| +---------+---------+ | |
| | `p` | `m` | | |
| +=========+=========+ | |
| | 50 | 6 | | |
| +---------+---------+ | |
| | 100 | 7 | | |
| +---------+---------+ | |
| | 500 | 10 | | |
| +---------+---------+ | |
| | 3000 | 12 | | |
| +---------+---------+ | |
| This formula is based purely on a limited amount of | |
| experimentation and will sometimes be wrong. | |
| """ | |
| # Expected degree | |
| # XXX: use mag | |
| g = int(4 + max(0, self.ctx.log(prec/30.0, 2))) | |
| # Reasonable "worst case" | |
| g += 2 | |
| return g | |
| def estimate_error(self, results, prec, epsilon): | |
| r""" | |
| Given results from integrations `[I_1, I_2, \ldots, I_k]` done | |
| with a quadrature of rule of degree `1, 2, \ldots, k`, estimate | |
| the error of `I_k`. | |
| For `k = 2`, we estimate `|I_{\infty}-I_2|` as `|I_2-I_1|`. | |
| For `k > 2`, we extrapolate `|I_{\infty}-I_k| \approx |I_{k+1}-I_k|` | |
| from `|I_k-I_{k-1}|` and `|I_k-I_{k-2}|` under the assumption | |
| that each degree increment roughly doubles the accuracy of | |
| the quadrature rule (this is true for both :class:`TanhSinh` | |
| and :class:`GaussLegendre`). The extrapolation formula is given | |
| by Borwein, Bailey & Girgensohn. Although not very conservative, | |
| this method seems to be very robust in practice. | |
| """ | |
| if len(results) == 2: | |
| return abs(results[0]-results[1]) | |
| try: | |
| if results[-1] == results[-2] == results[-3]: | |
| return self.ctx.zero | |
| D1 = self.ctx.log(abs(results[-1]-results[-2]), 10) | |
| D2 = self.ctx.log(abs(results[-1]-results[-3]), 10) | |
| except ValueError: | |
| return epsilon | |
| D3 = -prec | |
| D4 = min(0, max(D1**2/D2, 2*D1, D3)) | |
| return self.ctx.mpf(10) ** int(D4) | |
| def summation(self, f, points, prec, epsilon, max_degree, verbose=False): | |
| """ | |
| Main integration function. Computes the 1D integral over | |
| the interval specified by *points*. For each subinterval, | |
| performs quadrature of degree from 1 up to *max_degree* | |
| until :func:`~mpmath.estimate_error` signals convergence. | |
| :func:`~mpmath.summation` transforms each subintegration to | |
| the standard interval and then calls :func:`~mpmath.sum_next`. | |
| """ | |
| ctx = self.ctx | |
| I = total_err = ctx.zero | |
| for i in xrange(len(points)-1): | |
| a, b = points[i], points[i+1] | |
| if a == b: | |
| continue | |
| # XXX: we could use a single variable transformation, | |
| # but this is not good in practice. We get better accuracy | |
| # by having 0 as an endpoint. | |
| if (a, b) == (ctx.ninf, ctx.inf): | |
| _f = f | |
| f = lambda x: _f(-x) + _f(x) | |
| a, b = (ctx.zero, ctx.inf) | |
| results = [] | |
| err = ctx.zero | |
| for degree in xrange(1, max_degree+1): | |
| nodes = self.get_nodes(a, b, degree, prec, verbose) | |
| if verbose: | |
| print("Integrating from %s to %s (degree %s of %s)" % \ | |
| (ctx.nstr(a), ctx.nstr(b), degree, max_degree)) | |
| result = self.sum_next(f, nodes, degree, prec, results, verbose) | |
| results.append(result) | |
| if degree > 1: | |
| err = self.estimate_error(results, prec, epsilon) | |
| if verbose: | |
| print("Estimated error:", ctx.nstr(err), " epsilon:", ctx.nstr(epsilon), " result: ", ctx.nstr(result)) | |
| if err <= epsilon: | |
| break | |
| I += results[-1] | |
| total_err += err | |
| if total_err > epsilon: | |
| if verbose: | |
| print("Failed to reach full accuracy. Estimated error:", ctx.nstr(total_err)) | |
| return I, total_err | |
| def sum_next(self, f, nodes, degree, prec, previous, verbose=False): | |
| r""" | |
| Evaluates the step sum `\sum w_k f(x_k)` where the *nodes* list | |
| contains the `(w_k, x_k)` pairs. | |
| :func:`~mpmath.summation` will supply the list *results* of | |
| values computed by :func:`~mpmath.sum_next` at previous degrees, in | |
| case the quadrature rule is able to reuse them. | |
| """ | |
| return self.ctx.fdot((w, f(x)) for (x,w) in nodes) | |
| class TanhSinh(QuadratureRule): | |
| r""" | |
| This class implements "tanh-sinh" or "doubly exponential" | |
| quadrature. This quadrature rule is based on the Euler-Maclaurin | |
| integral formula. By performing a change of variables involving | |
| nested exponentials / hyperbolic functions (hence the name), the | |
| derivatives at the endpoints vanish rapidly. Since the error term | |
| in the Euler-Maclaurin formula depends on the derivatives at the | |
| endpoints, a simple step sum becomes extremely accurate. In | |
| practice, this means that doubling the number of evaluation | |
| points roughly doubles the number of accurate digits. | |
| Comparison to Gauss-Legendre: | |
| * Initial computation of nodes is usually faster | |
| * Handles endpoint singularities better | |
| * Handles infinite integration intervals better | |
| * Is slower for smooth integrands once nodes have been computed | |
| The implementation of the tanh-sinh algorithm is based on the | |
| description given in Borwein, Bailey & Girgensohn, "Experimentation | |
| in Mathematics - Computational Paths to Discovery", A K Peters, | |
| 2003, pages 312-313. In the present implementation, a few | |
| improvements have been made: | |
| * A more efficient scheme is used to compute nodes (exploiting | |
| recurrence for the exponential function) | |
| * The nodes are computed successively instead of all at once | |
| **References** | |
| * [Bailey]_ | |
| * http://users.cs.dal.ca/~jborwein/tanh-sinh.pdf | |
| """ | |
| def sum_next(self, f, nodes, degree, prec, previous, verbose=False): | |
| """ | |
| Step sum for tanh-sinh quadrature of degree `m`. We exploit the | |
| fact that half of the abscissas at degree `m` are precisely the | |
| abscissas from degree `m-1`. Thus reusing the result from | |
| the previous level allows a 2x speedup. | |
| """ | |
| h = self.ctx.mpf(2)**(-degree) | |
| # Abscissas overlap, so reusing saves half of the time | |
| if previous: | |
| S = previous[-1]/(h*2) | |
| else: | |
| S = self.ctx.zero | |
| S += self.ctx.fdot((w,f(x)) for (x,w) in nodes) | |
| return h*S | |
| def calc_nodes(self, degree, prec, verbose=False): | |
| r""" | |
| The abscissas and weights for tanh-sinh quadrature of degree | |
| `m` are given by | |
| .. math:: | |
| x_k = \tanh(\pi/2 \sinh(t_k)) | |
| w_k = \pi/2 \cosh(t_k) / \cosh(\pi/2 \sinh(t_k))^2 | |
| where `t_k = t_0 + hk` for a step length `h \sim 2^{-m}`. The | |
| list of nodes is actually infinite, but the weights die off so | |
| rapidly that only a few are needed. | |
| """ | |
| ctx = self.ctx | |
| nodes = [] | |
| extra = 20 | |
| ctx.prec += extra | |
| tol = ctx.ldexp(1, -prec-10) | |
| pi4 = ctx.pi/4 | |
| # For simplicity, we work in steps h = 1/2^n, with the first point | |
| # offset so that we can reuse the sum from the previous degree | |
| # We define degree 1 to include the "degree 0" steps, including | |
| # the point x = 0. (It doesn't work well otherwise; not sure why.) | |
| t0 = ctx.ldexp(1, -degree) | |
| if degree == 1: | |
| #nodes.append((mpf(0), pi4)) | |
| #nodes.append((-mpf(0), pi4)) | |
| nodes.append((ctx.zero, ctx.pi/2)) | |
| h = t0 | |
| else: | |
| h = t0*2 | |
| # Since h is fixed, we can compute the next exponential | |
| # by simply multiplying by exp(h) | |
| expt0 = ctx.exp(t0) | |
| a = pi4 * expt0 | |
| b = pi4 / expt0 | |
| udelta = ctx.exp(h) | |
| urdelta = 1/udelta | |
| for k in xrange(0, 20*2**degree+1): | |
| # Reference implementation: | |
| # t = t0 + k*h | |
| # x = tanh(pi/2 * sinh(t)) | |
| # w = pi/2 * cosh(t) / cosh(pi/2 * sinh(t))**2 | |
| # Fast implementation. Note that c = exp(pi/2 * sinh(t)) | |
| c = ctx.exp(a-b) | |
| d = 1/c | |
| co = (c+d)/2 | |
| si = (c-d)/2 | |
| x = si / co | |
| w = (a+b) / co**2 | |
| diff = abs(x-1) | |
| if diff <= tol: | |
| break | |
| nodes.append((x, w)) | |
| nodes.append((-x, w)) | |
| a *= udelta | |
| b *= urdelta | |
| if verbose and k % 300 == 150: | |
| # Note: the number displayed is rather arbitrary. Should | |
| # figure out how to print something that looks more like a | |
| # percentage | |
| print("Calculating nodes:", ctx.nstr(-ctx.log(diff, 10) / prec)) | |
| ctx.prec -= extra | |
| return nodes | |
| class GaussLegendre(QuadratureRule): | |
| r""" | |
| This class implements Gauss-Legendre quadrature, which is | |
| exceptionally efficient for polynomials and polynomial-like (i.e. | |
| very smooth) integrands. | |
| The abscissas and weights are given by roots and values of | |
| Legendre polynomials, which are the orthogonal polynomials | |
| on `[-1, 1]` with respect to the unit weight | |
| (see :func:`~mpmath.legendre`). | |
| In this implementation, we take the "degree" `m` of the quadrature | |
| to denote a Gauss-Legendre rule of degree `3 \cdot 2^m` (following | |
| Borwein, Bailey & Girgensohn). This way we get quadratic, rather | |
| than linear, convergence as the degree is incremented. | |
| Comparison to tanh-sinh quadrature: | |
| * Is faster for smooth integrands once nodes have been computed | |
| * Initial computation of nodes is usually slower | |
| * Handles endpoint singularities worse | |
| * Handles infinite integration intervals worse | |
| """ | |
| def calc_nodes(self, degree, prec, verbose=False): | |
| r""" | |
| Calculates the abscissas and weights for Gauss-Legendre | |
| quadrature of degree of given degree (actually `3 \cdot 2^m`). | |
| """ | |
| ctx = self.ctx | |
| # It is important that the epsilon is set lower than the | |
| # "real" epsilon | |
| epsilon = ctx.ldexp(1, -prec-8) | |
| # Fairly high precision might be required for accurate | |
| # evaluation of the roots | |
| orig = ctx.prec | |
| ctx.prec = int(prec*1.5) | |
| if degree == 1: | |
| x = ctx.sqrt(ctx.mpf(3)/5) | |
| w = ctx.mpf(5)/9 | |
| nodes = [(-x,w),(ctx.zero,ctx.mpf(8)/9),(x,w)] | |
| ctx.prec = orig | |
| return nodes | |
| nodes = [] | |
| n = 3*2**(degree-1) | |
| upto = n//2 + 1 | |
| for j in xrange(1, upto): | |
| # Asymptotic formula for the roots | |
| r = ctx.mpf(math.cos(math.pi*(j-0.25)/(n+0.5))) | |
| # Newton iteration | |
| while 1: | |
| t1, t2 = 1, 0 | |
| # Evaluates the Legendre polynomial using its defining | |
| # recurrence relation | |
| for j1 in xrange(1,n+1): | |
| t3, t2, t1 = t2, t1, ((2*j1-1)*r*t1 - (j1-1)*t2)/j1 | |
| t4 = n*(r*t1-t2)/(r**2-1) | |
| a = t1/t4 | |
| r = r - a | |
| if abs(a) < epsilon: | |
| break | |
| x = r | |
| w = 2/((1-r**2)*t4**2) | |
| if verbose and j % 30 == 15: | |
| print("Computing nodes (%i of %i)" % (j, upto)) | |
| nodes.append((x, w)) | |
| nodes.append((-x, w)) | |
| ctx.prec = orig | |
| return nodes | |
| class QuadratureMethods(object): | |
| def __init__(ctx, *args, **kwargs): | |
| ctx._gauss_legendre = GaussLegendre(ctx) | |
| ctx._tanh_sinh = TanhSinh(ctx) | |
| def quad(ctx, 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; mp.pretty = True | |
| >>> quad(sin, [0, pi]) | |
| 2.0 | |
| A basic 2D integral:: | |
| >>> f = lambda x, y: cos(x+y/2) | |
| >>> 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:`~mpmath.quad` recognizes the following keyword arguments: | |
| *method* | |
| Chooses integration algorithm (described below). | |
| *error* | |
| If set to true, :func:`~mpmath.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:`~mpmath.quadts` and :func:`~mpmath.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:`~mpmath.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 | |
| >>> quad(lambda x: 2/(x**2+1), [0, inf]) | |
| 3.14159265358979 | |
| >>> 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 | |
| >>> 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 | |
| >>> 2*quad(lambda x: sqrt(1-x**2), [-1, 1]) #doctest:+ELLIPSIS | |
| 3.141592653589793238462643383279502884...216420199 | |
| 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 | |
| >>> chop(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:`~mpmath.quad`:: | |
| >>> mp.dps = 30 | |
| >>> f = lambda x, y: (x-1)/((1-x*y)*log(x*y)) | |
| >>> quad(f, [0, 1], [0, 1]) | |
| 0.577215664901532860606512090082 | |
| >>> +euler | |
| 0.577215664901532860606512090082 | |
| >>> f = lambda x, y: 1/sqrt(1+x**2+y**2) | |
| >>> quad(f, [-1, 1], [-1, 1]) | |
| 3.17343648530607134219175646705 | |
| >>> 4*log(2+sqrt(3))-2*pi/3 | |
| 3.17343648530607134219175646705 | |
| >>> f = lambda x, y: 1/(1-x**2 * y**2) | |
| >>> quad(f, [0, 1], [0, 1]) | |
| 1.23370055013616982735431137498 | |
| >>> pi**2 / 8 | |
| 1.23370055013616982735431137498 | |
| >>> quad(lambda x, y: 1/(1-x*y), [0, 1], [0, 1]) | |
| 1.64493406684822643647241516665 | |
| >>> 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:`~mpmath.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)]) | |
| >>> quad(f, [-1, 1]) | |
| 3.14159265358979 | |
| Here is a simple triple integral:: | |
| >>> mp.dps = 15 | |
| >>> f = lambda x,y,z: x*y/(1+z) | |
| >>> quad(f, [0,1], [0,1], [1,2], method='gauss-legendre') | |
| 0.101366277027041 | |
| >>> (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 | |
| >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) # Bad | |
| 3.99900894176779 | |
| >>> 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 | |
| >>> quad(log, [0, 1], method='tanh-sinh') # Good | |
| -1.0 | |
| >>> quad(log, [0, 1], method='gauss-legendre') # Bad | |
| -0.999932197413801 | |
| However, the result may still be inaccurate for some functions:: | |
| >>> 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 | |
| >>> +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:`~mpmath.quad` may fail to provide full accuracy. For | |
| example, with default settings, :func:`~mpmath.quad` is able to integrate | |
| `\sin(x)` accurately over an interval of length 100 but not over | |
| length 1000:: | |
| >>> quad(sin, [0, 100]); 1-cos(100) # Good | |
| 0.137681127712316 | |
| 0.137681127712316 | |
| >>> 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:: | |
| >>> quad(sin, linspace(0, 1000, 10)) # Good | |
| 0.437620923709297 | |
| Another is to increase the degree of the quadrature:: | |
| >>> 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) | |
| >>> quad(f, [-100, 100]) # Bad | |
| 3.64804647105268 | |
| >>> quad(f, [-100, 100], maxdegree=10) # Good | |
| 3.12159332021646 | |
| >>> quad(f, [-100, 0, 100]) # Also good | |
| 3.12159332021646 | |
| **References** | |
| 1. http://mathworld.wolfram.com/DoubleIntegral.html | |
| """ | |
| rule = kwargs.get('method', 'tanh-sinh') | |
| if type(rule) is str: | |
| if rule == 'tanh-sinh': | |
| rule = ctx._tanh_sinh | |
| elif rule == 'gauss-legendre': | |
| rule = ctx._gauss_legendre | |
| else: | |
| raise ValueError("unknown quadrature rule: %s" % rule) | |
| else: | |
| rule = rule(ctx) | |
| verbose = kwargs.get('verbose') | |
| dim = len(points) | |
| orig = prec = ctx.prec | |
| epsilon = ctx.eps/8 | |
| m = kwargs.get('maxdegree') or rule.guess_degree(prec) | |
| points = [ctx._as_points(p) for p in points] | |
| try: | |
| ctx.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: | |
| ctx.prec = orig | |
| if kwargs.get("error"): | |
| return +v, err | |
| return +v | |
| def quadts(ctx, *args, **kwargs): | |
| """ | |
| Performs tanh-sinh quadrature. The call | |
| quadts(func, *points, ...) | |
| is simply a shortcut for: | |
| quad(func, *points, ..., method=TanhSinh) | |
| For example, a single integral and a double integral: | |
| quadts(lambda x: exp(cos(x)), [0, 1]) | |
| quadts(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) | |
| See the documentation for quad for information about how points | |
| arguments and keyword arguments are parsed. | |
| See documentation for TanhSinh for algorithmic information about | |
| tanh-sinh quadrature. | |
| """ | |
| kwargs['method'] = 'tanh-sinh' | |
| return ctx.quad(*args, **kwargs) | |
| def quadgl(ctx, *args, **kwargs): | |
| """ | |
| Performs Gauss-Legendre quadrature. The call | |
| quadgl(func, *points, ...) | |
| is simply a shortcut for: | |
| quad(func, *points, ..., method=GaussLegendre) | |
| For example, a single integral and a double integral: | |
| quadgl(lambda x: exp(cos(x)), [0, 1]) | |
| quadgl(lambda x, y: exp(cos(x+y)), [0, 1], [0, 1]) | |
| See the documentation for quad for information about how points | |
| arguments and keyword arguments are parsed. | |
| See documentation for TanhSinh for algorithmic information about | |
| tanh-sinh quadrature. | |
| """ | |
| kwargs['method'] = 'gauss-legendre' | |
| return ctx.quad(*args, **kwargs) | |
| def quadosc(ctx, 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:`~mpmath.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:`~mpmath.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:`~mpmath.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; mp.pretty = True | |
| >>> f = lambda x: sin(3*x)/(x**2+1) | |
| >>> quadosc(f, [0,inf], omega=3) | |
| 0.37833007080198 | |
| >>> quadosc(f, [0,inf], period=2*pi/3) | |
| 0.37833007080198 | |
| >>> quadosc(f, [0,inf], zeros=lambda n: pi*n/3) | |
| 0.37833007080198 | |
| >>> (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:`~mpmath.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`:: | |
| >>> quadosc(lambda x: cos(x)/(1+x**2), [-inf, inf], omega=1) | |
| 1.15572734979092 | |
| >>> pi/e | |
| 1.15572734979092 | |
| >>> quadosc(lambda x: cos(x)/x**2, [-inf, -1], period=2*pi) | |
| -0.0844109505595739 | |
| >>> 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:: | |
| >>> quadosc(lambda x: exp(3*j*x)/(1+x**2), [-inf,inf], omega=3) | |
| (0.156410688228254 + 0.0j) | |
| >>> pi/e**3 | |
| 0.156410688228254 | |
| >>> quadosc(lambda x: exp(3*j*x)/(2+x+x**2), [-inf,inf], omega=3) | |
| (0.00317486988463794 - 0.0447701735209082j) | |
| >>> 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:: | |
| >>> quadosc(j0, [0, inf], period=2*pi) | |
| 1.0 | |
| >>> 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)) | |
| >>> 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:`~mpmath.quadosc` works:: | |
| >>> mp.dps = 30 | |
| >>> f = lambda x: cos(x**2) | |
| >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) | |
| 0.626657068657750125603941321203 | |
| >>> f = lambda x: sin(x**2) | |
| >>> quadosc(f, [0,inf], zeros=lambda n:sqrt(pi*n)) | |
| 0.626657068657750125603941321203 | |
| >>> 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)) | |
| >>> quadosc(f, [1,inf], zeros=lambda n: log(n)) | |
| -0.25024394235267 | |
| >>> 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 | |
| >>> quadosc(f, [1,inf], omega=1) # Bad | |
| 1.28642190869861 | |
| >>> quadosc(f, [1,inf], omega=0.5) # Perfect | |
| 1.28652953559617 | |
| >>> 1+(cos(1)+ci(1)+sin(1))/6 | |
| 1.28652953559617 | |
| **Fast decay** | |
| :func:`~mpmath.quadosc` is primarily useful for slowly decaying | |
| integrands. If the integrand decreases exponentially or faster, | |
| :func:`~mpmath.quad` will likely handle it without trouble (and generally be | |
| much faster than :func:`~mpmath.quadosc`):: | |
| >>> quadosc(lambda x: cos(x)/exp(x), [0, inf], omega=1) | |
| 0.5 | |
| >>> quad(lambda x: cos(x)/exp(x), [0, inf]) | |
| 0.5 | |
| """ | |
| a, b = ctx._as_points(interval) | |
| a = ctx.convert(a) | |
| b = ctx.convert(b) | |
| if [omega, period, zeros].count(None) != 2: | |
| raise ValueError( \ | |
| "must specify exactly one of omega, period, zeros") | |
| if a == ctx.ninf and b == ctx.inf: | |
| s1 = ctx.quadosc(f, [a, 0], omega=omega, zeros=zeros, period=period) | |
| s2 = ctx.quadosc(f, [0, b], omega=omega, zeros=zeros, period=period) | |
| return s1 + s2 | |
| if a == ctx.ninf: | |
| if zeros: | |
| return ctx.quadosc(lambda x:f(-x), [-b,-a], lambda n: zeros(-n)) | |
| else: | |
| return ctx.quadosc(lambda x:f(-x), [-b,-a], omega=omega, period=period) | |
| if b != ctx.inf: | |
| raise ValueError("quadosc requires an infinite integration interval") | |
| if not zeros: | |
| if omega: | |
| period = 2*ctx.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 | |
| s = ctx.quadgl(f, [a, zeros(n)]) | |
| def term(k): | |
| return ctx.quadgl(f, [zeros(k), zeros(k+1)]) | |
| s += ctx.nsum(term, [n, ctx.inf]) | |
| return s | |
| def quadsubdiv(ctx, f, interval, tol=None, maxintervals=None, **kwargs): | |
| """ | |
| Computes the integral of *f* over the interval or path specified | |
| by *interval*, using :func:`~mpmath.quad` together with adaptive | |
| subdivision of the interval. | |
| This function gives an accurate answer for some integrals where | |
| :func:`~mpmath.quad` fails:: | |
| >>> from mpmath import * | |
| >>> mp.dps = 15; mp.pretty = True | |
| >>> quad(lambda x: abs(sin(x)), [0, 2*pi]) | |
| 3.99900894176779 | |
| >>> quadsubdiv(lambda x: abs(sin(x)), [0, 2*pi]) | |
| 4.0 | |
| >>> quadsubdiv(sin, [0, 1000]) | |
| 0.437620923709297 | |
| >>> quadsubdiv(lambda x: 1/(1+x**2), [-100, 100]) | |
| 3.12159332021646 | |
| >>> quadsubdiv(lambda x: ceil(x), [0, 100]) | |
| 5050.0 | |
| >>> quadsubdiv(lambda x: sin(x+exp(x)), [0,8]) | |
| 0.347400172657248 | |
| The argument *maxintervals* can be set to limit the permissible | |
| subdivision:: | |
| >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=5, error=True) | |
| (-5.40487904307774, 5.011) | |
| >>> quadsubdiv(lambda x: sin(x**2), [0,100], maxintervals=100, error=True) | |
| (0.631417921866934, 1.10101120134116e-17) | |
| Subdivision does not guarantee a correct answer since, the error | |
| estimate on subintervals may be inaccurate:: | |
| >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) | |
| (0.210802735500549, 1.0001111101e-17) | |
| >>> mp.dps = 20 | |
| >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, [0,1], error=True) | |
| (0.21080273550054927738, 2.200000001e-24) | |
| The second answer is correct. We can get an accurate result at lower | |
| precision by forcing a finer initial subdivision:: | |
| >>> mp.dps = 15 | |
| >>> quadsubdiv(lambda x: sech(10*x-2)**2 + sech(100*x-40)**4 + sech(1000*x-600)**6, linspace(0,1,5)) | |
| 0.210802735500549 | |
| The following integral is too oscillatory for convergence, but we can get a | |
| reasonable estimate:: | |
| >>> v, err = fp.quadsubdiv(lambda x: fp.sin(1/x), [0,1], error=True) | |
| >>> round(v, 6), round(err, 6) | |
| (0.504067, 1e-06) | |
| >>> sin(1) - ci(1) | |
| 0.504067061906928 | |
| """ | |
| queue = [] | |
| for i in range(len(interval)-1): | |
| queue.append((interval[i], interval[i+1])) | |
| total = ctx.zero | |
| total_error = ctx.zero | |
| if maxintervals is None: | |
| maxintervals = 10 * ctx.prec | |
| count = 0 | |
| quad_args = kwargs.copy() | |
| quad_args["verbose"] = False | |
| quad_args["error"] = True | |
| if tol is None: | |
| tol = +ctx.eps | |
| orig = ctx.prec | |
| try: | |
| ctx.prec += 5 | |
| while queue: | |
| a, b = queue.pop() | |
| s, err = ctx.quad(f, [a, b], **quad_args) | |
| if kwargs.get("verbose"): | |
| print("subinterval", count, a, b, err) | |
| if err < tol or count > maxintervals: | |
| total += s | |
| total_error += err | |
| else: | |
| count += 1 | |
| if count == maxintervals and kwargs.get("verbose"): | |
| print("warning: number of intervals exceeded maxintervals") | |
| if a == -ctx.inf and b == ctx.inf: | |
| m = 0 | |
| elif a == -ctx.inf: | |
| m = min(b-1, 2*b) | |
| elif b == ctx.inf: | |
| m = max(a+1, 2*a) | |
| else: | |
| m = a + (b - a) / 2 | |
| queue.append((a, m)) | |
| queue.append((m, b)) | |
| finally: | |
| ctx.prec = orig | |
| if kwargs.get("error"): | |
| return +total, +total_error | |
| else: | |
| return +total | |
| if __name__ == '__main__': | |
| import doctest | |
| doctest.testmod() | |
Xet Storage Details
- Size:
- 42.4 kB
- Xet hash:
- e81a7dddc03c251e12ba3d2f918a5f4bc2be30ca11834b42c5c1573138fc0427
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.