Decay | Represents information about the asymptotic form of indefinite integrals. |
PowerDecay(x, n[, x0]) | Represents a power-law decay. |
ExpDecay(x, b[, x0]) | Represents an exponential decay. |
IntegrationError([mesg, res, err]) | Error during integration. |
get_weights(x, roots[, cond]) | Return the list of weights wx for quadratures of increasing |
get_weights_y(x, y, wy) | Return the weights wx for a quadrature of order at least n >= len(y) for the abscissa x give the n exact quadrature abscissa y and weights wy defining a quadrature of order 2n. |
integrate(f, a, b[, abs_tol, rel_tol, ...]) | Return (res, err) where res is the integral of the function f(x) from a to b and err is an estimate of the absolute error. |
quad(f, a, b, *varargin, **kwargs) | An improved version of integrate.quad that does some argument checking and deals with points properly. |
mquad(f, a, b[, abs_tol, verbosity, fa, fb, ...]) | Return (res, err) where res is the numerically evaluated |
h_roots(n) | Return the abscissa and weights for the Gaussian quadrature of |
Inheritance diagram for mmf.math.integrate.integrate_1d.quadrature:
Adaptive quadrature routines for one-dimensional integrals.
This module provides access to several adaptive quadrature routines for performing one-dimensional integrals. The main routine is integrate(), which performs a one-dimensional integral. It allows the user to specify properties of the integrand to facilitate an accurate computation.
In particular, the nature of the decay at inf can be described by using a subclass of Decay such as PowerDecay or ExpDecay to effect a transformation for dealing with these tails.
Here we review some properties of orthogonal polynomials and define notations that are useful for quadratures. We consider polynomials orthogonal to some measure and define the induced inner product:
Formally we shall work with orthonormal polynomials – – though the standard representations typically are not normalized:
The polynomials are characterized in terms of the coefficients , , , , , and ; and the functions , , and . In addition, for a given degree , one has quadrature abscissa – which are the roots of – and weights :
where the last integral is exact for polynomials of degree or less. The weights are sometimes called the “Christoffel Numbers” and may be denoted . Additional useful relationships are
Todo
Check formulae for the weights with h_roots() which seems to work.
Here we summarize some of the known and useful analytic results:
Name | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
Hermite | 2 | 0 | 2n | 2n | 1 | -2x | ||||
Laguerre | n | x |
The general idea of a quadrature is to express an integral as
where is a weighting function, are a set of weights and are a set of abscissa. In general, given weights and abscissa, we have degrees of freedom, and so we can in general choose these so that the integral is exact for special forms of : Typically, polynomials of degree . Since the quadrature is linear, this is realized through the equations:
where is the m‘th moment of the degree m polynomial . (One can simply use but the system is typically somewhat better conditioned if one chooses orthogonal polynomials).
If the abscissa are given, we can determine the weights to satisfy “normal” equations through the linear system. We start with the problem of determining the weights for a given set of abscissa. We want as high an order as possible, but with all the weights being positive. (One can relax this somewhat by formally minimizing to minimize roundoff error from cancellation when computing the integral.) One strategy might be to solve as many equations as possible while minimizing the norm of w. This leads to the solution
where is the vector of moments and is the incomplete Vandermonde matrix at the points :
If there is an all positive solution, then this will be all positive. To see this, consider the singular value decomposition of mat{X}:
We may write the solution as
where the projection of w in the subspace spanned by is fixed by the equation and the projection in the complementary subspace spanned by is arbitrary (). The set of all valid w spans a linear subspace. With a little thought about this in lower dimensions, one can convince oneself that if any points lie within the given “quadrant” of positive w, then the point closest to the origin will satisfy this (i.e. the point w with minimal norm). This is the solution with which is given by the normal equation above.
Note
This assumes some properties about the nature of the set of solutions that are typically true for the integration problem with reasonable (positive) weights. This should be clarified at some point.
This formula is acceptable only for low orders, otherwise the Vandermonde matrix quickly becomes ill-conditioned. A better solution is to use a robust algorithm such as that in Demmel and Koev, “Accurate SVDs of polynomial Vandermonde matrices involving orthonormal polynomials”, Linear Algebra and its Applications 411 (2006) 382–396. There they point out that the Vandermonde matrix we need can be expressed in terms of the square Vandermonde matrix where are the roots of for the order n orthogonal polynomial through the Lagrange interpolation formula
For classical orthogonal polynomials, the roots can be accurately calculated from the recurrence relationships (see below). Some algebra shows that our weights can be expressed in terms of the classical weights as
This can also be directly computed from the SVD or QR decompositions of . As pointed out in A. J. Cox and N. J. Higham, “Stability of Householder QR factorization for weighted least squares problems”, Numerical Analysis 1997 (Dundee), Pitman Res. Notes Math. Ser. 380, pp. 57–73, Longman, Harlow, 1998, if we first sort the rows in order of decreasing norm, then the standard implementation of the QR decomposition is stable. We do this in pqr(). Thus:
This requires the following knowledge:
The Vandermonde matrix should be formed from orthogonal polynomials :
Note that one can scale out the weights if needed, so this is not an arduous requirement in most cases.
The algorithm requires the roots of the highest order :
The algorithm requires the Christoffel numbers :
Here is an example of abscissa for integrals between 0 and 1.
>>> def m_(n):
... return 1./(np.arange(n).reshape((n,1))+1)
>>> def X_(x,n):
... return x**np.arange(n).reshape((n,1))
>>> def w(x,n):
... X = np.mat(X_(x,n))
... l = np.linalg.solve(X*X.T, m_(n))
... return X.T*l
As an example, let’s consider order two quadratures over the interval [0,1] containing the endpoints and one interior point. An analysis of Simpsons rule shows that positive weights exist if the midpoint lies between 1/3 and 2/3:
>>> np.min(w(np.array([0,0.9/3,1]),3))
-0.0555555555...
>>> round(np.min(w(np.array([0,1/3,1]),3)),14)
0.0
>>> np.min(w(np.array([0,1/2,1]),3))
0.1666666...
>>> round(np.min(w(np.array([0,2/3,1]),3)),14)
0.0
>>> np.min(w(np.array([0,2.1/3,1]),3))
-0.055555555555...
Now, suppose we have
Bases: float
Represents information about the asymptotic form of indefinite integrals.
Methods
as_integer_ratio() -> (int, int) | Returns a pair of integers, whose ratio is exactly equal to the original float and with a positive denominator. |
conjugate | Returns self, the complex conjugate of any float. |
fromhex((string) -> float) | Create a floating-point number from a hexadecimal string. |
hex | float.hex() -> string |
is_integer | Returns True if the float is an integer. |
x.__init__(...) initializes x; see help(type(x)) for signature
Bases: mmf.math.integrate.integrate_1d.quadrature.Decay
Represents a power-law decay.
The function should behave as
where this behaviour becomes dominant for .
Parameters : | n : float
x0 : float
|
---|
Notes
The transformation used maps to .
If as , then approaches a constant as which is very easy to integrate.
Examples
>>> f = lambda p:1./(p**2 + 1)
>>> a = PowerDecay(-np.inf, x0=-1.0, n=2)
>>> b = PowerDecay(np.inf, x0=1.0, n=2)
>>> a.y_x(a.x_y(0.5))
0.5
>>> a.y_x(-1.0)
0.0
>>> b.y_x(1.0)
0.0
>>> quad(f, -np.inf, -1.0)
(0.785398163397448..., 1.2888...e-10)
>>> quad(a.get_g(f), a.y_x(-np.inf), a.y_x(-1.0))
(0.785398163397448..., 8.7196...e-15)
>>> quad(f, 1.0, np.inf)
(0.785398163397448..., 1.2888...e-10)
>>> quad(b.get_g(f), b.y_x(1.0), b.y_x(np.inf))
(0.785398163397448..., 8.71967...e-15)
This is what integrate does automatically:
>>> integrate(f, a, -1.0)
(0.785398163397448..., 8.71967...e-15)
>>> integrate(f, 1.0, b)
(0.785398163397448..., 8.71967...e-15)
Methods
as_integer_ratio() -> (int, int) | Returns a pair of integers, whose ratio is exactly equal to the original float and with a positive denominator. |
conjugate | Returns self, the complex conjugate of any float. |
fromhex((string) -> float) | Create a floating-point number from a hexadecimal string. |
get_g(f) | Return the function g(y) which, when integrated over |
hex | float.hex() -> string |
is_integer | Returns True if the float is an integer. |
x_y(y) | Return x(y) where and |
y_x(x) | Return y(x) where and |
Bases: mmf.math.integrate.integrate_1d.quadrature.Decay
Represents an exponential decay.
The function should behave as
where this behaviour becomes dominant for .
Parameters : | b : float
x0 : float
|
---|
Notes
The transformation used maps to yin[0,1).
Examples
>>> f = lambda p:3/np.sqrt(np.pi)/(np.exp((3.0*p)**2))
>>> a = ExpDecay(-np.inf, b=3)
>>> b = ExpDecay(np.inf, b=3)
>>> a.y_x(a.x_y(0.5))
0.5...
>>> a.y_x(-3.0)
0.0
>>> b.y_x(3.0)
0.0
>>> quad(f, -np.inf, 0)
(0.49999999..., 2.2113658...e-09)
>>> quad(a.get_g(f), a.y_x(-np.inf), a.y_x(0))
(0.5000000..., 1.177...e-09)
This is what integrate does automatically:
>>> integrate(f, a, 0)
(0.5, 4.8318...e-13)
Methods
as_integer_ratio() -> (int, int) | Returns a pair of integers, whose ratio is exactly equal to the original float and with a positive denominator. |
conjugate | Returns self, the complex conjugate of any float. |
fromhex((string) -> float) | Create a floating-point number from a hexadecimal string. |
get_g(f) | Return the function g(y) which, when integrated over |
hex | float.hex() -> string |
is_integer | Returns True if the float is an integer. |
x_y(y) | Return x(y) where and |
y_x(x) | Return y(x) where and |
Bases: exceptions.Exception
Error during integration.
Return the list of weights wx for quadratures of increasing order with positive weights for the abscissa x give (y, wy) = roots(n) being n and weights for some other (Gaussian) quadrature.
Note
The formulation is based on the assumption that the weights and abscissa are quadratures for polynomials (not including the weight). The weight is implicit.
Parameters : | x : array
roots : function
cond : float
|
---|
See also
Examples
>>> x = np.linspace(-1, 1, 102)[1:-1]
>>> wx = get_weights(x, sp.special.orthogonal.p_roots)
>>> print len(wx)
18
>>> np.allclose(np.dot(wx[-1], x**16), 2./17)
True
>>> np.allclose(np.dot(wx[-1], x**17), 0)
True
Return the weights wx for a quadrature of order at least n >= len(y) for the abscissa x give the n exact quadrature abscissa y and weights wy defining a quadrature of order 2n.
Parameters : | x : array
y : array
wy : array
|
---|
Notes
Forms the matrix
that interpolates from y to x using the Lagrange interpolation formula. The forms the QR factorization using row-sorting:
where is square. The interpolated coefficients are:
We use sp.linalg.lu_solve() to solve but this expects to have arguments R^T = L*U where L has unit diagonals, so we need to rescale R slightly.
Examples
>>> x = np.linspace(-1, 1, 102)[1:-1]
>>> wx_ = [2]
>>> n = 0
>>> while min(wx_) >= 0:
... # Find maximum order with positive weights.
... n += 1
... y, wy = sp.special.orthogonal.p_roots(n)
... wx = wx_
... wx_ = get_weights_y(x, y, wy)
>>> n = n - 1
>>> print n
18
>>> np.allclose(np.dot(wx, x**16), 2./17)
True
>>> np.allclose(np.dot(wx, x**17), 0)
True
Return (res, err) where res is the integral of the function f(x) from a to b and err is an estimate of the absolute error.
Parameters : | a, b : float
abs_tol, rel_tol : float
points : list of floats
singular_points : list of floats
|
---|
An improved version of integrate.quad that does some argument checking and deals with points properly.
Return (ans, err).
Examples
>>> def f(x): return 1./x**2
>>> (ans, err) = quad(f, 1, np.inf, points=[])
>>> abs(ans - 1.0) < err
True
>>> (ans, err) = quad(f, 1, np.inf, points=[3.0, 2.0])
>>> abs(ans - 1.0) < err
True
Return (res, err) where res is the numerically evaluated integral using adaptive Simpson quadrature.
mquad tries to approximate the integral of function f from a to b to within an error of abs_tol using recursive adaptive Simpson quadrature. mquad allows the function y = f(x) to be array-valued. In the matrix valued case, the infinity norm of the matrix is used as it’s “absolute value”.
Parameters : | f : function
a, b : float
fa, fb : float
abs_tol : float
verbosity : int
save_fx : bool
res_dict : dict
max_fcnt : int
min_step_size : float
norm : function
points : list of floats
|
---|
Notes
The basic idea here is to use a nested quadrature rule to assess the value of the integrand over a given integral, and then subdivide if tolerances are not met.
Based on “adaptsim” by Walter Gander. Ref: W. Gander and W. Gautschi, “Adaptive Quadrature Revisited”, 1998. http://www.inf.ethz.ch/personal/gander
Examples
Orthogonality of planewaves on [0, 2pi]
>>> def f(x):
... v = np.exp(1j*np.array([[1.0, 2.0, 3.0]])*x)
... return v.T.conj()*v/2.0/np.pi
>>> ans = mquad(f, 0, 2*np.pi)
>>> abs(ans - np.eye(ans.shape[0])).max() < _abs_tol
True
>>> res_dict = {}
>>> def f(x): return x**2
>>> ans = mquad(f, -2, 1, res_dict=res_dict, save_fx=True)
>>> abs(ans - 3.0) < _abs_tol
True
>>> x = np.array([xy[0] for xy in res_dict['xy']])
>>> y = np.array([xy[1] for xy in res_dict['xy']])
>>> abs(y - f(x)).max()
0.0
Handling singularities.
>>> def f(x): return 1.0/np.sqrt(x) + 1.0/np.sqrt(1.0-x)
>>> abs(mquad(f, 0, 1, abs_tol=1e-8) - 4.0) < 2e-7
True
Handling discontinuities.
>>> def f(x):
... if x < 0:
... return 0.0
... else:
... return 1.0
>>> abs(mquad(f, -2.0, 1.0) - 1.0) < 1e-10
True
>>> def f(x): return 1./x
>>> mquad(f, 1, np.inf)
Traceback (most recent call last):
...
ValueError: Infinite endpoints not supported.
Return the abscissa and weights for the Gaussian quadrature of order n in the Hermite polynomials.
Notes
We use the scipy routines for the roots, but our own custom formula for the weights because the scipy routine screws up these for large n.