Mpmath: Numerical integration#
General quadrature interface#
- ctx.quad(f, *points, **kwargs)#
where
ctxisdec,mpm, orgmp.See also: Wikipedia [1211]
See also: MathWorld [874].
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) \, \mathrm{d} x\)quad(f, [x1, x2], [y1, y2])– calculates \(\int_{x_1}^{x_2} \int_{y_1}^{y_2} f(x,y) \, \mathrm{d} y \, \mathrm{d} x\)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 \, \mathrm{d} y \, \mathrm{d} x\)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
quad()recognizes the following keyword arguments:- method
Chooses integration algorithm (described below).
- error
If set to true,
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
quadts()andquadgl()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
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
TanhSinhandGaussLegendrefor 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)\,\mathrm{d} x = \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]) 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
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
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”,
quad()may fail to provide full accuracy. For example, with default settings,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
Quadrature with subdivision#
- ctx.quadsubdiv(f, interval, tol=None, maxintervals=None, **kwargs)#
where
ctxismpmordec.Computes the integral of f over the interval or path specified by interval, using quad() together with adaptive subdivision of the interval.
This function gives an accurate answer for some integrals where quad() fails:
>>> ctx.dps = 15; ctx.pretty = True >>> ctx.quad(lambda x: abs(ctx.sin(x)), [0, 2*ctx.pi]) 3.99900894176779 >>> ctx.quadsubdiv(lambda x: abs(ctx.sin(x)), [0, 2*ctx.pi]) 4.0 >>> ctx.quadsubdiv(ctx.sin, [0, 1000]) 0.437620923709297 >>> ctx.quadsubdiv(lambda x: 1/(1+x**2), [-100, 100]) 3.12159332021646 >>> ctx.quadsubdiv(lambda x: ctx.ceil(x), [0, 100]) 5050.0 >>> ctx.quadsubdiv(lambda x: ctx.sin(x+ctx.exp(x)), [0,8]) 0.347400172657248
The argument maxintervals can be set to limit the permissible subdivision:
>>> ctx.quadsubdiv(lambda x: ctx.sin(x**2), [0,100], maxintervals=5, error=True) (-5.40487904307774, 5.011) >>> ctx.quadsubdiv(lambda x: ctx.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:
>>> ctx.quadsubdiv(lambda x: ctx.sech(10*x-2)**2 + ctx.sech(100*x-40)**4 \ ... + ctx.sech(1000*x-600)**6, [0,1], error=True) (0.210802735500549, 1.0001111101e-17) >>> ctx.dps = 20 >>> ctx.quadsubdiv(lambda x: ctx.sech(10*x-2)**2 + ctx.sech(100*x-40)**4 \ ... + ctx.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:
>>> ctx.dps = 15 >>> ctx.quadsubdiv(lambda x: ctx.sech(10*x-2)**2 + ctx.sech(100*x-40)**4 \ ... + ctx.sech(1000*x-600)**6, ctx.linspace(0,1,5)) 0.210802735500549
The following integral is too oscillatory for convergence, but we can get a reasonable estimate:
>>> v, err = ctx.quadsubdiv(lambda x: ctx.sin(1/x), [0,1], error=True) >>> round(v, 6), round(err, 6) (0.504067, 1e-06) >>> ctx.sin(1) - ctx.ci(1) 0.504067061906928
Quadrature of oscillatory functions (Filon)#
- ctx.quadosc(f, interval, omega=None, period=None, zeros=None)#
where
ctxisdec,mpm, orgmp.See also Filon’s rule: MathWorld [873]
Calculates
\[I = \int_a^b f(x) \mathrm{d} x\]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,
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\),
quadosc()works by evaluating the infinite series\[I = \int_a^{x_1} f(x) \mathrm{d} x + \sum_{k=1}^{\infty} \int_{x_k}^{x_{k+1}} f(x) \mathrm{d} x\]where \(x_k\) are consecutive zeros (alternatively some other periodic reference point) of \(f(x)\). Accordingly,
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
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
\[\int_0^{\infty} \cos x^2\,\mathrm{d} x = \int_0^{\infty} \sin x^2\,\mathrm{d} x = \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}\)),
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
quadosc()is primarily useful for slowly decaying integrands. If the integrand decreases exponentially or faster,quad()will likely handle it without trouble (and generally be much faster thanquadosc()):>>> quadosc(lambda x: cos(x)/exp(x), [0, inf], omega=1) 0.5 >>> quad(lambda x: cos(x)/exp(x), [0, inf]) 0.5
Verified numerical integration (Okayama)#
- quad_verified(f, *points, **kwargs)#
where
ctxisipm,dec,mpm, orgmp.Computes a single integral over a given 1D interval, providing a guaranteed error bound.
This function implements an efficient algorithm for 1-dimensional numerical real integration with rigorous error bounds. See Petras [492], Petras [493] for an overview. The algorithms used in this section have been described in detail in a series of papers by Okayama et al. [453], Okayama [452] and Okayama [454], where explicit error bounds for the double exponential formulas proposed by Takahasi and Mori [537] are provided.
See also fredrik-johansson/arb#196 (DH comment on integration, contains better description).
The type of integrals considered is as follows:
\[I = \int_{a}^{b} f(x) \mathrm{d} x = \int_{a}^{b} \frac{g(x)}{(x-a)^{1-\alpha} (b-x)^{1-\beta}} \mathrm{d} x, \quad \text{where}\]\[g(x) = f(x) (x-a)^{\alpha-1} (b-x)^{\beta-1}\]and \(\alpha\) and \(\beta\) are positive constants. We assume that the integrand \(f(x)\) is a function on an open interval \((a,b)\) and may have an integrable algebraic singularity of the type shown above at the end-points \(x=a\) and/or \(x=b\). The function \(f\) (or the function \(g\)) needs to be analytic on the following complex domain \(\mathcal{D}\):
\[\mathcal{D} = \left \{ z \in \mathbb{C} : \left\rvert \arg \left[ \frac{1}{\pi} \log \left( \frac{z-a}{b-z} \right) + \sqrt{1 + \left( \frac{1}{\pi} \log \left( \frac{z-a}{b-z} \right) \right)^2 } \right] \right\rvert < d \right \}\]where \(0<d<\pi/2\), and satisfy, for some positive constant \(K\), for all \(z \in \mathcal{D}\) the condition
\[\rvert f(z) \rvert \leq\ K \rvert z-a \rvert^{\alpha-1} \rvert b-z \rvert^{\beta-1}, \quad \text{or, equivalently,}\]\[\rvert g(z) \rvert \leq\ K\]A rigorous upper bound for \(K\), given \(d\) (which can be freely chosen observing the condition \(0<d<\pi/2\)) can be computed using complex interval arithmetic; details will be discussed below.
For a given choice of \(d\) and a positive constant \(\epsilon_{abs}\), we have the following key result, which establishes an explicit rigorous bound for the absolute error of the approximation to the integral, provided that all calculations are carried out in real interval arithmetic:
\[\frac{1}{C_1} \left\rvert \int_{a}^{b} f(x) \mathrm{d} x - h \sum_{k=-M}^{N} g(\phi(kh)) P(kh) \right\rvert \leq \epsilon_{abs}, \quad \text{where}\]\[\phi(t) = \tanh \left( \frac{\pi}{2} \sinh(t) \right), \quad \text{and}\]\[P(u) = \frac{\pi}{2} \left( \frac{b-a}{2} \right)^{\alpha+\beta-1} \cosh(u) (1+\phi(u))^\alpha (1-\phi(u))^\beta\]Note that in formula ref{eq:KeyIntegrationformula} and in the subsequent formulas \(C_1, C_2, h, M, N\) only depend on \(d\) and \(\epsilon_{abs}\) and the function parameters \(\alpha\) and \(\beta\). The number of intervals and the interval width \(h\) are automatically chosen in a way which balances the discretization error and the truncation error, resulting in a “nearly optimal” number of function evaluations. \(C_1, C_2, h, M, N\) are calculated as follows:
\[C_1 = \frac{2K(b-a)^{\alpha+\beta-1}}{\min(\alpha,\beta)}\]\[C_2 = \frac{2}{\cos^{\alpha+\beta}\left(\tfrac{\pi}{2} \sin(d) \right)\cos(d)}\]\[h = \frac{2 \pi d}{\log \left(1+\frac{2C_2}{\epsilon_{abs}}\right)}\]\[n =\left \lceil \frac{1}{h} \log \left( \frac{2}{\pi \min(\alpha,\beta)} \log \left( \frac{2e^{\max(\alpha,\beta)\pi/2}}{\epsilon_{abs}} \right)\right)\right\rceil\]\[\begin{split}\begin{cases} M = n, \quad N=n-\lfloor \log(\beta/\alpha)/h \rfloor, & \text{for } \alpha \le \beta\\ N = n, \quad M=n-\lfloor \log(\alpha/\beta)/h \rfloor, & \text{for } \alpha > \beta\\ \end{cases}\end{split}\]The main challenge in implementing the algorithm is to determine a rigorous upper bound for \(K\). We are going to achieve this by using the complex interval arithmetic which is implemented in mpmath. Since the domain \(\mathcal{D}\) is not of rectangular shape, we need to compute a rectangular domain, say \(\mathcal{D}_{\text{Rect}}\), which includes \(\mathcal{D}\): \(\mathcal{D} \subset \mathcal{D}_{\text{Rect}}\).
The rectangular domain \(\mathcal{D}_{\text{Rect}}\) corresponds to a complex interval, say \(z_1\). The function \(g\) is then evaluated in complex interval arithmetic with \(z_1\) as argument: \(z_2 = g(z_1)\). The absolute value of \(z_2\) is a real interval, say \(x_1\). The supremum of \(x_1\) is a rigorous upper bound of \(K\) (see equation ref{eq:BoundFor_K_G}).
The choice of \(d\) has a great impact on \(M+N+1\), the total number of function evaluations, which are required to achieve the desired absolute error. To be able to select a near-optimal value of \(d\) resulting in the lowest number of function evaluations, we calculate \(n\) for a number of values for \(d\), ranging from \(d=1.5\) to \(d=0.1\), using pre-computed values for the matching rectangular domains \(\mathcal{D}_{\text{Rect}}\), and choose the value for \(d\) which results in the smallest \(n\).
If equation ref{eq:gFormula} does not give a useful alternative representation of \(f\), we set \(\alpha = \beta = 1\), so that \(f(x) = g(x)\). However, taking \(a=0, b=1\), \(\alpha=\tfrac{1}{2}\) and \(\beta=1\) let us consider the integral
\[I = \int_{0}^{1} \frac{\sin(\exp(x))}{\sqrt{x}} \mathrm{d} x = \int_{0}^{1} f(x) \mathrm{d} x = \int_{0}^{1} \frac{g(x)}{\sqrt{x}} \mathrm{d} x, \text{ i.e}\]\[f = \frac{\sin(\exp(x))}{\sqrt{x}} \text{ and } g = \sin(\exp(x))\]In this situation, equation ref{eq:BoundFor_K_F} cannot be used to determine \(K\) because \(f(z)\) has a singularity at \(z=0\); on the other hand, equation ref{eq:BoundFor_K_G} can still be used because \(g(z)\) has no singularities in the interval \((0, 1)\).
It is possible to generalize this methodology to integrals for which \(a=-\infty\) and/or \(b=+\infty\); details can be found in Okayama (2014). Unfortunately, the resulting domains are of a shape which makes it impossible to include them in a rectangular domain, unless this domain spans the entire complex plane. Therefore, except for very few, extremely well behaved functions, the constant \(K\) cannot be determined using complex interval arithmetic (but can possibly be determined by analytical methods). The usual transformation formulas, which are widely used when performing non-verified numerical integration, are not applicable, because they will (almost always) produce a singularity at one of the endpoints, which will prevent the determination of the constant \(K\) .
Specialized Gauss quadrature rules#
- ctx.gauss_quad_rules(ctx, n, qtype='legendre', alpha=0, beta=0)#
where
ctxisdec,mpm, orgmp.This routine calulates Gaussian quadrature rules for different families of orthogonal polynomials. Let (a, b) be an interval, W(x) a positive weight function and n a positive integer.
Then the purpose of this routine is to calculate pairs (x_k, w_k) for \(k=0, 1, 2, ... (n-1)\) which give
\[\int_a^b W(x) * F(x) \mathrm{d} x = \sum_0^{n-1} w_k * F(x_k)\]exact for all polynomials F(x) of degree (strictly) less than 2*n. For all integrable functions F(x) the sum is (more or less) good approximation to the integral. The x_k are called nodes (which are the zeros of the related orthogonal polynomials) and the w_k are called the weights.
parameters n (input) The degree of the quadrature rule, i.e. its number of nodes. qtype (input) The family of orthogonal polynmomials for which to compute the quadrature rule. See the list below. alpha (input) real number, used as parameter for some orthogonal polynomials beta (input) real number, used as parameter for some orthogonal polynomials. return value (X, W) a pair of two real arrays where x_k = X[k] and w_k = W[k]. orthogonal polynomials: qtype polynomial ----- ---------- "legendre" Legendre polynomials, W(x)=1 on the interval (-1, +1) "legendre01" shifted Legendre polynomials, W(x)=1 on the interval (0, +1) "hermite" Hermite polynomials, W(x)=exp(-x*x) on (-infinity,+infinity) "laguerre" Laguerre polynomials, W(x)=exp(-x) on (0,+infinity) "glaguerre" generalized Laguerre polynomials, W(x)=exp(-x)*x**alpha on (0, +infinity) "chebyshev1" Chebyshev polynomials of the first kind, W(x)=1/sqrt(1-x*x) on (-1, +1) "chebyshev2" Chebyshev polynomials of the second kind, W(x)=sqrt(1-x*x) on (-1, +1) "jacobi" Jacobi polynomials, W(x)=(1-x)**alpha * (1+x)**beta on (-1, +1) with alpha>-1 and beta>-1 references: - golub and welsch, "calculations of gaussian quadrature rules", mathematics of computation 23, p. 221-230 (1969) - golub, "some modified matrix eigenvalue problems", siam review 15, p. 318-334 (1973) - stroud and secrest, "gaussian quadrature formulas", prentice-hall (1966) See also the routine gaussq.f in netlog.org or ACM Transactions on Mathematical Software algorithm 726.Gauss-Chebyshev quadrature, first and second kind
See also: MathWorld [878], Wikipedia [1203], Gatteschi [359], Petras [492], Petras [493].
See also: https://en.wikipedia.org/wiki/Chebyshev%E2%80%93Gauss_quadrature
See also: https://mathworld.wolfram.com/Chebyshev-GaussQuadrature.html
See also: Abramowitz, p. 383
For the first kind:
\[\int_{-1}^{1} \frac{f(x)}{\sqrt{1-x^2}} \mathrm{d} x = \sum_{i=1}^n w_i f(x_i) + R_n, \quad \text{where}\]\[x_i = \cos \left( \frac{2i-1}{2n} \pi \right), w_i = \frac{\pi}{n}, \quad R_n=\frac{f^{(2n)}(\xi) \pi}{(2n)! 2^{2n-1}} \text{ for } -1<\xi<1.\]For the second kind:
\[\int_{-1}^{1} \sqrt{1-x^2} g(x) \mathrm{d} x = \sum_{i=1}^n w_i g(x_i) + R_n, \quad \text{where}\]\[x_i = \cos \left( \frac{i}{n+1} \pi \right), w_i = \frac{\pi}{n+1} \sin^2 \left( \frac{i}{n+1} \pi \right), \quad R_n=\frac{g^{(2n)}(\xi) \pi}{(2n)! 2^{2n+1}} \text{ for } -1<\xi<1.\]def demo_gauss_quadrature_chebyshev(ctx): # orthogonality of the chebyshev polynomials: f = lambda x: ctx.chebyt(3, x) * ctx.chebyt(2, x) # problem with gpm.chebyt X, W = gauss_quadrature(ctx, 3, "chebyshev1") A = ctx.fdot([(f(x), w) for x, w in zip(X, W)]) print("A:", A) print(ctx.chop(A, tol = 1e-10))
Gauss-Legendre quadrature
See also: MathWorld [879], Wikipedia [1204], Petras [492], Petras [493].
\[\int_a^b f(x) \mathrm{d} x = \frac{b-a}{2} \sum_{i=1}^n w_i f\left(\tfrac{1}{2}(b-a)(x_i +1)\right) + R_n, \quad \text{where}\]\[w_i = \frac{2}{((1-x^2)P_n' (x_i))^2}, \quad R_n=\frac{f^{(2n)}(\xi)(b-a)^{2n+1}(n!)^4}{((2n+1)(2n)!)^3} \text{ for } a<\xi<b,\]\(P_n\) are the Legendre polynomials of degree \(n\), and \(x_i\) is the \(i^{\text{th}}\) zero of \(P_n\).
def demo_gauss_quadrature_laguerre_mp(): from mpfunlab.mpmath import mp f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 X, W = mp.gauss_quadrature(3, "laguerre") A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) B = 76 C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) print("A:", A) print("B:", B) print("C:", C) print(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))
Gauss-Jacobi quadrature
See also: MathWorld [877], Wikipedia [1201], Petras [492], Petras [493].
\[\int_a^b f(x) (1 - x)^\alpha (1 + x)^\beta \mathrm{d} x = \frac{b-a}{2} \sum_{i=1}^n w_i f\left(\tfrac{1}{2}(b-a)(x_i +1)\right) + R_n, \quad \text{where } \alpha, \beta >-1,\]\[w_i = -\frac{2n + \alpha + \beta + 2} {n + \alpha + \beta + 1}\, \frac{\Gamma(n + \alpha + 1)\Gamma(n + \beta + 1)} {\Gamma(n + \alpha + \beta + 1)(n + 1)!}\, \frac{2^{\alpha + \beta}} {P_{n}^{(\alpha,\beta)\,\prime}(x_i) P_{n+1}^{(\alpha,\beta)}(x_i)},\]\[R_n=\frac{\Gamma(n+\alpha+1) \Gamma(n+\beta+1) \Gamma(n+\alpha+\beta+1)}{(2n+\alpha+\beta+1)[\Gamma(2n+\alpha+\beta+1)]^2} \frac{2^{2+\alpha+\beta+1}}{(2n)!} f^{(2n)}(\xi), \text{ for } a<\xi<b,\]\(P_{n}^{(\alpha,\beta)}\) are the Jacobi polynomials of degree \(n\), and \(x_i\) is the \(i^{\text{th}}\) zero of \(P_{n}^{(\alpha,\beta)}\).
Gauss-Legendre quadrature is a special case of Gauss-Jacobi quadrature with \(\alpha=\beta=0\). Similarly, the Chebyshev-Gauss quadrature of the first (second) kind arises when one takes \(\alpha=\beta=-0.5 (+0.5)\). The special case \(\alpha=\beta\) turns Jacobi polynomials into Gegenbauer polynomials, in which case the technique is sometimes called Gauss-Gegenbauer quadrature. Gauss-Jacobi quadrature can be used to approximate integrals with singularities at the end points.
def demo_gauss_quadrature_laguerre_mp(): from mpfunlab.mpmath import mp f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 X, W = mp.gauss_quadrature(3, "laguerre") A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) B = 76 C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) print("A:", A) print("B:", B) print("C:", C) print(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))
Gauss-Laguerre quadrature, classic and generalized
See also: MathWorld [878], Wikipedia [1203], Gatteschi [359], Petras [492], Petras [493].
\[\int_0^\infty e^{-ax} f(x) \mathrm{d} x = \frac{1}{a} \sum_{i=1}^n w_i f\left(\frac{x_i}{a}\right) + R_n, \quad \text{where}\]\[w_i = \frac{x_i}{((n+1)L_{n+1} (x_i))^2}, \quad R_n=\frac{f^{(2n)}(\xi) (n!)^2}{(2n)!} \text{ for } 0<\xi<\infty,\]\(L_n\) are the Laguerre polynomials of degree \(n\), and \(x_i\) is the \(i^{\text{th}}\) zero of \(L_n\).
def demo_gauss_quadrature_laguerre_mp(): from mpfunlab.mpmath import mp f = lambda x: x**5 - 2 * x**4 + 3 * x**3 - 5 * x**2 + 7 * x - 11 X, W = mp.gauss_quadrature(3, "laguerre") A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) B = 76 C = mp.quad(lambda x: mp.exp(-x) * f(x), [0, +mp.inf]) print("A:", A) print("B:", B) print("C:", C) print(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))
Gauss-Hermite quadrature
See also: MathWorld [876], Wikipedia [1200], Townsend et al. [856], Petras [492], Petras [493].
\[\int_{-\infty}^\infty e^{-ax^2} f(x) \mathrm{d} x = \frac{1}{\sqrt{a}} \sum_{i=1}^n w_i f\left(\frac{x_i}{\sqrt{a}}\right) + R_n, \quad \text{where}\]\[w_i = \frac{2^{n-1} n! \sqrt{\pi}}{(nH_{n-1} (x_i))^2}, \quad R_n=\frac{f^{(2n)}(\xi) n! \sqrt{\pi}}{2^n (2n)!} \text{ for } -\infty<\xi<\infty,\]\(H_n\) are the Hermite polynomials of degree \(n\), and \(x_i\) is the \(i^{\text{th}}\) zero of \(H_n\).
def demo_gauss_quadrature_hermite_mp(): from mpfunlab.mpmath import mp f = lambda x: x**8 + 2 * x**6 - 3 * x**4 + 5 * x**2 - 7 X, W = mp.gauss_quadrature(5, "hermite") A = mp.fdot([(f(x), w) for x, w in zip(X, W)]) B = mp.sqrt(mp.pi) * 57 / 16 C = mp.quad(lambda x: mp.exp(- x * x) * f(x), [-mp.inf, +mp.inf]) print("A:", A) print("B:", B) print("C:", C) print(mp.chop(A-B, tol = 1e-10), mp.chop(A-C, tol = 1e-10))