Simplified complexity

master
Per A Brodtkorb 8 years ago
parent d3a51f90a1
commit 78e9e3b1f9

@ -21,8 +21,18 @@ __all__ = ['dea3', 'clencurt', 'romberg',
'gaussq', 'richardson', 'quadgr', 'qdemo'] 'gaussq', 'richardson', 'quadgr', 'qdemo']
def _assert(cond, msg):
if not cond:
raise ValueError(msg)
def _assert_warn(cond, msg):
if not cond:
warnings.warn(msg)
def clencurt(fun, a, b, n0=5, trace=False): def clencurt(fun, a, b, n0=5, trace=False):
''' """
Numerical evaluation of an integral, Clenshaw-Curtis method. Numerical evaluation of an integral, Clenshaw-Curtis method.
Parameters Parameters
@ -67,7 +77,7 @@ def clencurt(fun, a, b, n0=5, trace=False):
[2] Clenshaw, C.W. and Curtis, A.R. (1960), [2] Clenshaw, C.W. and Curtis, A.R. (1960),
Numerische Matematik, Vol. 2, pp. 197--205 Numerische Matematik, Vol. 2, pp. 197--205
''' """
# make sure n is even # make sure n is even
n = 2 * int(n0) n = 2 * int(n0)
@ -129,7 +139,7 @@ def clencurt(fun, a, b, n0=5, trace=False):
def romberg(fun, a, b, releps=1e-3, abseps=1e-3): def romberg(fun, a, b, releps=1e-3, abseps=1e-3):
''' """
Numerical integration with the Romberg method Numerical integration with the Romberg method
Parameters Parameters
@ -161,7 +171,7 @@ def romberg(fun, a, b, releps=1e-3, abseps=1e-3):
True True
>>> err[0] < 1e-4 >>> err[0] < 1e-4
True True
''' """
h = b - a h = b - a
h_min = 1.0e-9 h_min = 1.0e-9
# Max size of extrapolation table # Max size of extrapolation table
@ -274,7 +284,7 @@ def _h_roots_newton(n, releps=3e-14, max_iter=10):
def h_roots(n, method='newton'): def h_roots(n, method='newton'):
''' """
Returns the roots (x) of the nth order Hermite polynomial, Returns the roots (x) of the nth order Hermite polynomial,
H_n(x), and weights (w) to use in Gaussian Quadrature over H_n(x), and weights (w) to use in Gaussian Quadrature over
[-inf,inf] with weighting function exp(-x**2). [-inf,inf] with weighting function exp(-x**2).
@ -314,7 +324,7 @@ def h_roots(n, method='newton'):
[2]. Stroud and Secrest (1966), 'gaussian quadrature formulas', [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
prentice-hall, Englewood cliffs, n.j. prentice-hall, Englewood cliffs, n.j.
''' """
if not method.startswith('n'): if not method.startswith('n'):
return ort.h_roots(n) return ort.h_roots(n)
@ -374,7 +384,7 @@ def _j_roots_newton(n, alpha, beta, releps=3e-14, max_iter=10):
def j_roots(n, alpha, beta, method='newton'): def j_roots(n, alpha, beta, method='newton'):
''' """
Returns the roots of the nth order Jacobi polynomial, P^(alpha,beta)_n(x) Returns the roots of the nth order Jacobi polynomial, P^(alpha,beta)_n(x)
and weights (w) to use in Gaussian Quadrature over [-1,1] with weighting and weights (w) to use in Gaussian Quadrature over [-1,1] with weighting
function (1-x)**alpha (1+x)**beta with alpha,beta > -1. function (1-x)**alpha (1+x)**beta with alpha,beta > -1.
@ -417,15 +427,16 @@ def j_roots(n, alpha, beta, method='newton'):
[2]. Stroud and Secrest (1966), 'gaussian quadrature formulas', [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
prentice-hall, Englewood cliffs, n.j. prentice-hall, Englewood cliffs, n.j.
''' """
_assert((-1 < alpha) & (-1 < beta),
'alpha and beta must be greater than -1')
if not method.startswith('n'): if not method.startswith('n'):
return ort.j_roots(n, alpha, beta) return ort.j_roots(n, alpha, beta)
return _j_roots_newton(n, alpha, beta) return _j_roots_newton(n, alpha, beta)
def la_roots(n, alpha=0, method='newton'): def la_roots(n, alpha=0, method='newton'):
''' """
Returns the roots (x) of the nth order generalized (associated) Laguerre Returns the roots (x) of the nth order generalized (associated) Laguerre
polynomial, L^(alpha)_n(x), and weights (w) to use in Gaussian quadrature polynomial, L^(alpha)_n(x), and weights (w) to use in Gaussian quadrature
over [0,inf] with weighting function exp(-x) x**alpha with alpha > -1. over [0,inf] with weighting function exp(-x) x**alpha with alpha > -1.
@ -465,10 +476,8 @@ def la_roots(n, alpha=0, method='newton'):
[2]. Stroud and Secrest (1966), 'gaussian quadrature formulas', [2]. Stroud and Secrest (1966), 'gaussian quadrature formulas',
prentice-hall, Englewood cliffs, n.j. prentice-hall, Englewood cliffs, n.j.
''' """
_assert(-1 < alpha, 'alpha must be greater than -1')
if alpha <= -1:
raise ValueError('alpha must be greater than -1')
if not method.startswith('n'): if not method.startswith('n'):
return ort.la_roots(n, alpha) return ort.la_roots(n, alpha)
@ -636,7 +645,7 @@ def _expand_roots(x, w, n, m):
def p_roots(n, method='newton', a=-1, b=1): def p_roots(n, method='newton', a=-1, b=1):
''' """
Returns the roots (x) of the nth order Legendre polynomial, P_n(x), Returns the roots (x) of the nth order Legendre polynomial, P_n(x),
and weights (w) to use in Gaussian Quadrature over [-1,1] with weighting and weights (w) to use in Gaussian Quadrature over [-1,1] with weighting
function 1. function 1.
@ -682,7 +691,7 @@ def p_roots(n, method='newton', a=-1, b=1):
[3] Stroud and Secrest (1966), 'gaussian quadrature formulas', [3] Stroud and Secrest (1966), 'gaussian quadrature formulas',
prentice-hall, Englewood cliffs, n.j. prentice-hall, Englewood cliffs, n.j.
''' """
if not method.startswith('n'): if not method.startswith('n'):
x, w = ort.p_roots(n) x, w = ort.p_roots(n)
@ -701,8 +710,62 @@ def p_roots(n, method='newton', a=-1, b=1):
return x, w return x, w
def q5_roots(n):
"""
5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 1'st kind
"""
jj = np.arange(1, n + 1)
wf = ones(n) * np.pi / n
bp = np.cos((2 * jj - 1) * np.pi / (2 * n))
return bp, wf
def q6_roots(n):
"""
6 : p(x) = sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 2'nd kind
"""
jj = np.arange(1, n + 1)
xj = jj * np.pi / (n + 1)
wf = np.pi / (n + 1) * np.sin(xj) ** 2
bp = np.cos(xj)
return bp, wf
def q7_roots(n):
"""
7 : p(x) = sqrt((x-a)/(b-x)), a = 0, b = 1
"""
jj = np.arange(1, n + 1)
xj = (jj - 0.5) * pi / (2 * n + 1)
bp = np.cos(xj) ** 2
wf = 2 * np.pi * bp / (2 * n + 1)
return bp, wf
def q8_roots(n):
"""
8 : p(x) = 1/sqrt(b-x), a = 0, b = 1
"""
bp1, wf1 = p_roots(2 * n)
k, = np.where(0 <= bp1)
wf = 2 * wf1[k]
bp = 1 - bp1[k] ** 2
return bp, wf
def q9_roots(n):
"""
9 : p(x) = sqrt(b-x), a = 0, b = 1
"""
bp1, wf1 = p_roots(2 * n + 1)
k, = np.where(0 < bp1)
wf = 2 * bp1[k] ** 2 * wf1[k]
bp = 1 - bp1[k] ** 2
return bp, wf
def qrule(n, wfun=1, alpha=0, beta=0): def qrule(n, wfun=1, alpha=0, beta=0):
''' """
Return nodes and weights for Gaussian quadratures. Return nodes and weights for Gaussian quadratures.
Parameters Parameters
@ -711,15 +774,15 @@ def qrule(n, wfun=1, alpha=0, beta=0):
number of base points number of base points
wfun : integer wfun : integer
defining the weight function, p(x). (default wfun = 1) defining the weight function, p(x). (default wfun = 1)
1,11,21: p(x) = 1 a =-1, b = 1 Gauss-Legendre 1 : p(x) = 1 a =-1, b = 1 Gauss-Legendre
2,12 : p(x) = exp(-x^2) a =-inf, b = inf Hermite 2 : p(x) = exp(-x^2) a =-inf, b = inf Hermite
3,13 : p(x) = x^alpha*exp(-x) a = 0, b = inf Laguerre 3 : p(x) = x^alpha*exp(-x) a = 0, b = inf Laguerre
4,14 : p(x) = (x-a)^alpha*(b-x)^beta a =-1, b = 1 Jacobi 4 : p(x) = (x-a)^alpha*(b-x)^beta a =-1, b = 1 Jacobi
5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 1'st kind 5 : p(x) = 1/sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 1'st kind
6 : p(x) = sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 2'nd kind 6 : p(x) = sqrt((x-a)*(b-x)), a =-1, b = 1 Chebyshev 2'nd kind
7 : p(x) = sqrt((x-a)/(b-x)), a = 0, b = 1 7 : p(x) = sqrt((x-a)/(b-x)), a = 0, b = 1
8 : p(x) = 1/sqrt(b-x), a = 0, b = 1 8 : p(x) = 1/sqrt(b-x), a = 0, b = 1
9 : p(x) = sqrt(b-x), a = 0, b = 1 9 : p(x) = sqrt(b-x), a = 0, b = 1
Returns Returns
------- -------
@ -761,54 +824,22 @@ def qrule(n, wfun=1, alpha=0, beta=0):
--------- ---------
Abromowitz and Stegun (1954) Abromowitz and Stegun (1954)
(for method 5 to 9) (for method 5 to 9)
''' """
if (alpha <= -1) | (beta <= -1): if wfun == 3: # Generalized Laguerre
raise ValueError('alpha and beta must be greater than -1') return la_roots(n, alpha)
if wfun == 4: # Gauss-Jacobi
if wfun == 1: # Gauss-Legendre return j_roots(n, alpha, beta)
[bp, wf] = p_roots(n)
elif wfun == 2: # Hermite _assert(0 < wfun < 10, 'unknown weight function')
[bp, wf] = h_roots(n)
elif wfun == 3: # Generalized Laguerre root_fun = [None, p_roots, h_roots, la_roots, j_roots, q5_roots, q6_roots,
[bp, wf] = la_roots(n, alpha) q7_roots, q8_roots, q9_roots][wfun]
elif wfun == 4: # Gauss-Jacobi return root_fun(n)
[bp, wf] = j_roots(n, alpha, beta)
elif wfun == 5: # p(x)=1/sqrt((x-a)*(b-x)), a=-1 and b=1 (default)
jj = np.arange(1, n + 1)
wf = ones(n) * np.pi / n
bp = np.cos((2 * jj - 1) * np.pi / (2 * n))
elif wfun == 6: # p(x)=sqrt((x-a)*(b-x)), a=-1 and b=1
jj = np.arange(1, n + 1)
xj = jj * np.pi / (n + 1)
wf = np.pi / (n + 1) * np.sin(xj) ** 2
bp = np.cos(xj)
elif wfun == 7: # p(x)=sqrt((x-a)/(b-x)), a=0 and b=1
jj = np.arange(1, n + 1)
xj = (jj - 0.5) * pi / (2 * n + 1)
bp = np.cos(xj) ** 2
wf = 2 * np.pi * bp / (2 * n + 1)
elif wfun == 8: # p(x)=1/sqrt(b-x), a=0 and b=1
[bp1, wf1] = p_roots(2 * n)
k, = np.where(0 <= bp1)
wf = 2 * wf1[k]
bp = 1 - bp1[k] ** 2
elif wfun == 9: # p(x)=np.sqrt(b-x), a=0 and b=1
[bp1, wf1] = p_roots(2 * n + 1)
k, = np.where(0 < bp1)
wf = 2 * bp1[k] ** 2 * wf1[k]
bp = 1 - bp1[k] ** 2
else:
raise ValueError('unknown weight function')
return bp, wf
class _Gaussq(object): class _Gaussq(object):
''' """
Numerically evaluate integral, Gauss quadrature. Numerically evaluate integral, Gauss quadrature.
Parameters Parameters
@ -887,7 +918,7 @@ class _Gaussq(object):
-------- --------
qrule qrule
gaussq2d gaussq2d
''' """
@staticmethod @staticmethod
def _get_dx(wfun, jacob, alpha, beta): def _get_dx(wfun, jacob, alpha, beta):
if wfun in [1, 2, 3, 7]: if wfun in [1, 2, 3, 7]:
@ -945,7 +976,7 @@ class _Gaussq(object):
return jacob return jacob
@staticmethod @staticmethod
def _warn(self, k, a_shape): def _warn(k, a_shape):
nk = len(k) nk = len(k)
if nk > 1: if nk > 1:
if (nk == np.prod(a_shape)): if (nk == np.prod(a_shape)):
@ -1031,8 +1062,115 @@ def richardson(Q, k):
class _Quadgr(object): class _Quadgr(object):
def _change_variable_and_integrate(self, fun, a, b, abseps, max_iter):
isreal = np.isreal(a) & np.isreal(b) & ~np.isnan(a) & ~np.isnan(b)
_assert(isreal, 'Infinite intervals must be real.')
integrate = self._integrate
# Change of variable
if np.isfinite(a) & np.isinf(b): # a to inf
Q, err = integrate(lambda t: fun(a + t / (1 - t)) / (1 - t) ** 2,
0, 1, abseps, max_iter)
elif np.isinf(a) & np.isfinite(b): # -inf to b
Q, err = integrate(lambda t: fun(b + t / (1 + t)) / (1 + t) ** 2,
-1, 0, abseps, max_iter)
else: # -inf to inf
Q1, err1 = integrate(lambda t: fun(t / (1 - t)) / (1 - t) ** 2,
0, 1, abseps / 2, max_iter)
Q2, err2 = integrate(lambda t: fun(t / (1 + t)) / (1 + t) ** 2,
-1, 0, abseps / 2, max_iter)
Q = Q1 + Q2
err = err1 + err2
return Q, err
def _nodes_and_weights(self):
# Gauss-Legendre quadrature (12-point)
xq = np.asarray(
[0.12523340851146894, 0.36783149899818018, 0.58731795428661748,
0.76990267419430469, 0.9041172563704748, 0.98156063424671924])
wq = np.asarray(
[0.24914704581340288, 0.23349253653835478, 0.20316742672306584,
0.16007832854334636, 0.10693932599531818, 0.047175336386511842])
xq = np.hstack((xq, -xq))
wq = np.hstack((wq, wq))
return xq, wq
def _get_best_estimate(self, Q0, Q1, Q2, k):
if k >= 6:
Qv = np.hstack((Q0[k], Q1[k], Q2[k]))
Qw = np.hstack((Q0[k - 1], Q1[k - 1], Q2[k - 1]))
elif k >= 4:
Qv = np.hstack((Q0[k], Q1[k]))
Qw = np.hstack((Q0[k - 1], Q1[k - 1]))
else:
Qv = np.atleast_1d(Q0[k])
Qw = Q0[k - 1]
# Estimate absolute error
errors = np.atleast_1d(abs(Qv - Qw))
j = errors.argmin()
err = errors[j]
Q = Qv[j]
# if k >= 2: # and not iscomplex:
# _val, err1 = dea3(Q0[k - 2], Q0[k - 1], Q0[k])
return Q, err
def _integrate(self, fun, a, b, abseps, max_iter):
dtype = np.result_type(fun(a), fun(b))
# Initiate vectors
Q0 = zeros(max_iter, dtype=dtype) # Quadrature
Q1 = zeros(max_iter, dtype=dtype) # First Richardson extrapolation
Q2 = zeros(max_iter, dtype=dtype) # Second Richardson extrapolation
xq, wq = self._nodes_and_weights()
nq = len(xq)
# One interval
hh = (b - a) / 2 # Half interval length
x = (a + b) / 2 + hh * xq # Nodes
# Quadrature
Q0[0] = hh * np.sum(wq * fun(x), axis=0)
# Successive bisection of intervals
for k in range(1, max_iter):
# Interval bisection
hh = hh / 2
x = np.hstack([x + a, x + b]) / 2
# Quadrature
Q0[k] = hh * np.sum(wq * np.sum(np.reshape(fun(x), (-1, nq)),
axis=0),
axis=0)
# Richardson extrapolation
if k >= 5:
Q1[k] = richardson(Q0, k)
Q2[k] = richardson(Q1, k)
elif k >= 3:
Q1[k] = richardson(Q0, k)
Q, err = self._get_best_estimate(Q0, Q1, Q2, k)
# Convergence
converged = (err < abseps) | ~np.isfinite(Q)
if converged:
break
_assert_warn(converged, 'Max number of iterations reached without ' +
'convergence.')
_assert_warn(np.isfinite(Q),
'Integral approximation is Infinite or NaN.')
# The error estimate should not be zero
err = err + 2 * np.finfo(Q).eps
return Q, err
def _order_limits(self, a, b):
if np.real(a) > np.real(b):
return b, a, True
return a, b, False
def __call__(self, fun, a, b, abseps=1e-5, max_iter=17): def __call__(self, fun, a, b, abseps=1e-5, max_iter=17):
''' """
Gauss-Legendre quadrature with Richardson extrapolation. Gauss-Legendre quadrature with Richardson extrapolation.
[Q,ERR] = QUADGR(FUN,A,B,TOL) approximates the integral of a function [Q,ERR] = QUADGR(FUN,A,B,TOL) approximates the integral of a function
@ -1078,7 +1216,7 @@ class _Quadgr(object):
-------- --------
QUAD, QUAD,
QUADGK QUADGK
''' """
# Author: jonas.lundgren@saabgroup.com, 2009. license BSD # Author: jonas.lundgren@saabgroup.com, 2009. license BSD
# Order limits (required if infinite limits) # Order limits (required if infinite limits)
a = np.asarray(a) a = np.asarray(a)
@ -1087,111 +1225,17 @@ class _Quadgr(object):
Q = b - a Q = b - a
err = b - a err = b - a
return Q, err return Q, err
elif np.real(a) > np.real(b):
reverse = True
a, b = b, a
else:
reverse = False
# Infinite limits
if np.isinf(a) | np.isinf(b):
# Check real limits
if ~ np.isreal(a) | ~np.isreal(b) | np.isnan(a) | np.isnan(b):
raise ValueError('Infinite intervals must be real.')
# Change of variable
if np.isfinite(a) & np.isinf(b):
# a to inf
Q, err = quadgr(lambda t: fun(a + t / (1 - t)) / (1 - t) ** 2,
0, 1, abseps)
elif np.isinf(a) & np.isfinite(b):
# -inf to b
Q, err = quadgr(lambda t: fun(b + t / (1 + t)) / (1 + t) ** 2,
-1, 0, abseps)
else: # -inf to inf
Q1, err1 = quadgr(lambda t: fun(t / (1 - t)) / (1 - t) ** 2,
0, 1, abseps / 2)
Q2, err2 = quadgr(lambda t: fun(t / (1 + t)) / (1 + t) ** 2,
-1, 0, abseps / 2)
Q = Q1 + Q2
err = err1 + err2
# Reverse direction
if reverse:
Q = -Q
return Q, err
# Gauss-Legendre quadrature (12-point)
xq = np.asarray(
[0.12523340851146894, 0.36783149899818018, 0.58731795428661748,
0.76990267419430469, 0.9041172563704748, 0.98156063424671924])
wq = np.asarray(
[0.24914704581340288, 0.23349253653835478, 0.20316742672306584,
0.16007832854334636, 0.10693932599531818, 0.047175336386511842])
xq = np.hstack((xq, -xq))
wq = np.hstack((wq, wq))
nq = len(xq)
dtype = np.result_type(fun(a), fun(b))
# Initiate vectors
Q0 = zeros(max_iter, dtype=dtype) # Quadrature
Q1 = zeros(max_iter, dtype=dtype) # First Richardson extrapolation
Q2 = zeros(max_iter, dtype=dtype) # Second Richardson extrapolation
# One interval
hh = (b - a) / 2 # Half interval length
x = (a + b) / 2 + hh * xq # Nodes
# Quadrature
Q0[0] = hh * np.sum(wq * fun(x), axis=0)
# Successive bisection of intervals
for k in range(1, max_iter):
# Interval bisection
hh = hh / 2
x = np.hstack([x + a, x + b]) / 2
# Quadrature
Q0[k] = hh * np.sum(wq * np.sum(np.reshape(fun(x), (-1, nq)),
axis=0),
axis=0)
# Richardson extrapolation
if k >= 5:
Q1[k] = richardson(Q0, k)
Q2[k] = richardson(Q1, k)
elif k >= 3:
Q1[k] = richardson(Q0, k)
# Estimate absolute error
if k >= 6:
Qv = np.hstack((Q0[k], Q1[k], Q2[k]))
Qw = np.hstack((Q0[k - 1], Q1[k - 1], Q2[k - 1]))
elif k >= 4:
Qv = np.hstack((Q0[k], Q1[k]))
Qw = np.hstack((Q0[k - 1], Q1[k - 1]))
else:
Qv = np.atleast_1d(Q0[k])
Qw = Q0[k - 1]
errors = np.atleast_1d(abs(Qv - Qw)) a, b, reverse = self._order_limits(a, b)
j = errors.argmin()
err = errors[j]
Q = Qv[j]
if k >= 2: # and not iscomplex:
_val, err1 = dea3(Q0[k - 2], Q0[k - 1], Q0[k])
# Convergence # Infinite limits
if (err < abseps) | ~np.isfinite(Q): improper_integral = np.isinf(a) | np.isinf(b)
break if improper_integral:
Q, err = self._change_variable_and_integrate(fun, a, b, abseps,
max_iter)
else: else:
warnings.warn('Max number of iterations reached without ' + Q, err = self._integrate(fun, a, b, abseps, max_iter)
'convergence.')
if ~ np.isfinite(Q):
warnings.warn('Integral approximation is Infinite or NaN.')
# The error estimate should not be zero
err = err + 2 * np.finfo(Q).eps
# Reverse direction # Reverse direction
if reverse: if reverse:
Q = -Q Q = -Q
@ -1211,7 +1255,7 @@ def boole(y, x):
def qdemo(f, a, b, kmax=9, plot_error=False): def qdemo(f, a, b, kmax=9, plot_error=False):
''' """
Compares different quadrature rules. Compares different quadrature rules.
Parameters Parameters
@ -1270,7 +1314,7 @@ def qdemo(f, a, b, kmax=9, plot_error=False):
129, 19.0855369552, 0.0000000320, 19.0864105817, 0.0008736585 129, 19.0855369552, 0.0000000320, 19.0864105817, 0.0008736585
257, 19.0855369252, 0.0000000020, 19.0857553393, 0.0002184161 257, 19.0855369252, 0.0000000020, 19.0857553393, 0.0002184161
513, 19.0855369233, 0.0000000001, 19.0855915273, 0.0000546041 513, 19.0855369233, 0.0000000001, 19.0855915273, 0.0000546041
''' """
true_val, _tol = intg.quad(f, a, b) true_val, _tol = intg.quad(f, a, b)
print('true value = %12.8f' % (true_val,)) print('true value = %12.8f' % (true_val,))
neval = zeros(kmax, dtype=int) neval = zeros(kmax, dtype=int)
@ -1386,14 +1430,9 @@ def main():
print(q, err) print(q, err)
def test_docstrings():
np.set_printoptions(precision=7)
import doctest
doctest.testmod()
if __name__ == '__main__': if __name__ == '__main__':
test_docstrings() from wafo.testing import test_docstrings
test_docstrings(__file__)
# qdemo(np.exp, 0, 3, plot_error=True) # qdemo(np.exp, 0, 3, plot_error=True)
# plt.show('hold') # plt.show('hold')
# main() # main()

Loading…
Cancel
Save