2 Routines for arbitrary-precision numerical quadrature
7 #----------------------------------------------------------------------------#
8 # General classes / utilities #
9 #----------------------------------------------------------------------------#
11 from float_
import Float
12 from constants
import *
13 from functions
import *
14 from evalf_
import polyfunc
21 This class provides a standard interface for quadrature rules. A
22 rule is applied through a function call (e.g. to integrate sin(x)
23 from x=3 to x=8, SomeQuadRule(<options>)(lambda x: sin(x), 3, 8))
25 Each Quadrature subclass should define an '_eval' method that
26 returns a tuple containing two values: (y, err) where y is the
27 calculated value of the integral, and err is an estimate of
28 the approximation error (or None if the error cannot be estimated
31 By default, the quadrature rule is assumed to be defined on
32 [-1, 1] or (-1, 1). __call__ automatically transforms other
33 (finite) intervals to this default interval. This behavior can
34 be modified by overriding the 'transform' method.
37 def transform(self
, f
, a
, b
):
38 """Given an integrand function f and boundaries a, b,
39 return an equivalent integrand defined on [-1, 1]"""
51 def __call__(self
, f
, a
, b
, extraprec
=3, verbose
=False):
53 Float
.extraprec(extraprec
)
54 f
= self
.transform(f
, a
, b
)
56 s
, err
= self
._eval
(f
, verbose
)
63 def adaptive(self
, f
, a
, b
, eps
, steps
=0, maxsteps
=5000, verbose
=False):
64 """Apply rule adaptively (must support error estimation)"""
65 s
, err
= self(f
, a
, b
, verbose
=verbose
)
66 if err
<= eps
or steps
>= maxsteps
:
67 return s
, err
, steps
+1
71 s1
, e1
, steps
= self
.adaptive(f
, a
, mid
, eps
, steps
+1, maxsteps
, verbose
)
72 s2
, e2
, steps
= self
.adaptive(f
, mid
, b
, eps
, steps
, maxsteps
, verbose
)
73 return s1
+ s2
, e1
+ e2
, steps
76 #----------------------------------------------------------------------------#
77 # Trivial quadrature rules #
78 #----------------------------------------------------------------------------#
80 # Mainly for demonstration purposes...
82 class Midpoint(Quadrature
):
83 def _eval(self
, f
, verbose
=False):
84 return 2*f(Float(0)), None
86 class Trapezoid(Quadrature
):
87 def _eval(self
, f
, verbose
=False):
88 return f(Float(-1)) + f(Float(1)), None
91 #----------------------------------------------------------------------------#
92 # Gaussian quadrature #
93 #----------------------------------------------------------------------------#
95 def _iterfixpoint(h
, x
, eps
):
102 def _gaussnode(pf
, k
, n
):
104 x
= Float(-math
.cos(math
.pi
*(k
+1-0.25)/(n
+0.5)))
108 eps
= Float((1, -Float
._prec
//2))
109 x
= _iterfixpoint(h
, x
, eps
)
110 w
= 2 / (1-x
**2) / (pf(x
)[1])**2
116 class GaussLegendre(Quadrature
):
118 Gauss-Legendre quadrature is highly efficient for smooth integrands
122 def __init__(self
, n
=None, verbose
=False):
124 prec
= dps
= Float
.getdps()
129 cacheddps
, xs
, ws
= _gausscache
[n
]
131 self
.x
= [x
for x
in xs
]
132 self
.w
= [w
for w
in ws
]
136 print ("calculating nodes for degree-%i Gauss-Legendre "
140 Float
.setdps(2*prec
+ 5)
145 from sympy
.specfun
import legendre
146 pf
= polyfunc(legendre(n
, 'x'), True)
148 for k
in xrange(n
//2 + 1):
149 if verbose
and k
% 4 == 0:
150 print " node", k
, "of", n
//2
151 x
, w
= _gaussnode(pf
, k
, n
)
154 self
.w
[k
] = self
.w
[n
-k
-1] = w
156 _gausscache
[n
] = (dps
, self
.x
, self
.w
)
160 def _eval(self
, f
, verbose
=False):
162 for i
in xrange(self
.n
):
163 s
+= self
.w
[i
] * f(self
.x
[i
])
167 class CompositeGaussLegendre(Quadrature
):
169 Gauss-Legendre quadrature with error estimation. Two Gauss-Legendre
170 rules of different degree are used, and the error is estimated as
171 the difference between the two results.
174 def __init__(self
, deg1
, deg2
, verbose
=False):
175 self
.rule1
= GaussLegendre(deg1
, verbose
)
176 self
.rule2
= GaussLegendre(deg2
, verbose
)
178 def _eval(self
, f
, verbose
=False):
179 s
= self
.rule1
._eval(f
, verbose
)[0]
180 t
= self
.rule2
._eval(f
, verbose
)[0]
184 #----------------------------------------------------------------------------#
185 # Tanh-sinh quadrature #
186 #----------------------------------------------------------------------------#
189 # x[k] = tanh(pi/2 * sinh(k*h))
190 # w[k] = pi/2 * cosh(k*h) / cosh(pi/2 sinh(k*h))**2
197 b
= exp((pi
* sinht
) / 2)
201 return sinhb
/coshb
, (pi
/2)*cosht
/coshb
**2
205 class TanhSinh(Quadrature
):
207 The tanh-sinh quadrature rule (also known as the doubly-exponential
208 quadrature rule) is well suited for integrals with singularities
209 and/or extremely high precision levels (tens or hundreds of digits).
211 The level m is a small integer between, say, 5 for low precision
212 levels and 10 for extremely high precision (it must be set higher
213 if the integrand is ill-behaved).
215 This implementation of the tanh-sinh algorithm is based on the
216 description given in Borwein, Bailey & Girgensohn, "Experimentation
217 in Mathematics - Computational Paths to Discovery", A K Peters,
218 2003, pages 312-313. It also described in various documents
222 def __init__(self
, eps
, m
, verbose
=False):
223 # Compute abscissas and weights
225 prec
= Float
.getdps()
230 self
.h
= h
= Float(1) / 2**m
234 if (eps
, m
) in _tscache
:
235 self
.x
, self
.w
= _tscache
[(eps
, m
)]
239 print ("calculating nodes for tanh-sinh quadrature with "
240 "epsilon %s and degree %i" % (eps
, m
))
243 Float
.setdps(prec
+ 10)
245 for k
in xrange(20 * 2**m
+ 1):
249 diff
= abs(self
.x
[-1] - Float(1))
252 if verbose
and m
> 6 and k
% 300 == 299:
253 Float
.store(); Float
.setdps(5)
254 print " progress", -log(diff
, 10) / prec
257 _tscache
[(eps
, m
)] = self
.x
, self
.w
261 def _estimate_error(self
, res
):
263 D1
= log(abs(res
[-1]-res
[-2]), 10)
264 D2
= log(abs(res
[-1]-res
[-3]), 10)
268 D4
= min(0, max(D1
**2/D2
, 2*D1
, D3
))
269 return Float('0.1') ** -int(D4
)
271 def _eval(self
, f
, verbose
=False):
276 for k
in xrange(1, m
+1):
278 for i
in xrange(0, len(self
.x
), 2**(m
-k
)):
279 if i
% (2**(m
-k
+1)) != 0 or k
== 1:
281 S
= S
+ self
.w
[0]*f(Float(0))
283 S
= S
+ (self
.w
[i
])*(f(-self
.x
[i
]) + f(self
.x
[i
]))
286 err
= self
._estimate
_error
(res
)
289 return +res
[-1], self
._estimate
_error
(res
)
292 class AdaptiveTanhSinh(Quadrature
):
294 Adaptive tanh-sinh quadrature algorithm.
297 def __init__(self
, initial
):
298 self
.initial
= initial
300 def adaptive(self
, f
, a
, b
, eps
, steps
=0, maxsteps
=5000, verbose
=False):
301 prec
= Float
.getprec()
302 for m
in xrange(self
.initial
, 50):
304 print "using tanh-sinh rule with m =", m
305 ts
= TanhSinh(eps
, m
, verbose
=verbose
)
306 s
, err
= ts(f
, a
, b
, verbose
=verbose
)
308 if err
<= eps
or steps
>= maxsteps
:
312 #----------------------------------------------------------------------------#
313 # User-friendly functions #
314 #----------------------------------------------------------------------------#
317 def nintegrate(f
, a
, b
, method
=0, maxsteps
=5000, verbose
=False):
322 nintegrate(f, a, b) numerically evaluates the integral
329 The integrand f should be a callable that accepts a Float as input
330 and outputs a Float or ComplexFloat; a and b should be Floats,
331 numbers that can be converted to Floats, or +/- infinity.
336 >>> print nintegrate(lambda x: 2*x**2 - 4*x, 2, 3)
339 Calculating the area of a unit circle, with 30 digits:
342 >>> print nintegrate(lambda x: 4*sqrt(1-x**2), 0, 1)
343 3.14159265358979323846264338328
345 The integration interval can be infinite or semi-infinite:
348 >>> print nintegrate(lambda x: exp(-x)*sin(x), 0, oo)
351 Integration methods and accuracy
352 ================================
354 Nintegrate attempts to obtain a value that is fully accurate within
355 the current working precision (i.e., correct to 15 decimal places
356 at the default precision level). If nintegrate fails to reach full
357 accuracy after a certain number of steps, it prints a warning
360 This message signifies either that the integral is either divergent
361 or, if convergent, ill-behaved. It may still be possible to
362 evaluate an ill-behaved integral by increasing the 'maxsteps'
363 setting, changing the integration method, and/or manually
364 transforming the integrand.
366 Nintegrate currently supports the following integration methods:
368 method = 0 : Gaussian quadrature (default)
369 method = 1 : tanh-sinh quadrature
371 Gaussian quadrature is generally very efficient if the integration
372 interval is finite and the integrand is smooth on the entire range
373 (including the endpoints). It may fail if the integrand has many
374 discontinuities, is highly oscillatory, or possesses integrable
377 The tanh-sinh algorithm is often better if the integration interval
378 is infinite or if singularities are present at the endpoints;
379 especially at very high precision levels. It does not perform well
380 if there are singularities between the endpoints or the integrand
381 is bumpy or oscillatory.
383 It may help to manually transform the integrand, e.g. changing
384 variables to remove singularities or breaking up the integration
385 interval so that singularities appear only at the endpoints.
387 The 'verbose' flag can be set to track the computation's progress.
391 Float
.setdps(dps
+ 3)
392 prec
= Float
.getprec()
394 # Transform infinite or semi-infinite interval to a finite interval
395 if a
== -oo
or b
== oo
:
397 if a
== -oo
and b
== oo
:
399 # make adaptive quadrature work from the left
401 return g(x
) + g(Float(1)/x
)/x
**2 + g(-x
) + g(Float(-1)/x
)/(-x
)**2
406 return g(x
+ aa
) + g(Float(1)/x
+ aa
)/x
**2
411 return g(-x
+ bb
) + g(Float(-1)/x
+ bb
)/(-x
)**2
412 a
, b
= Float(0), Float(1)
414 a
, b
= Float(a
), Float(b
)
416 eps
= Float((1, -prec
+4))
419 degree
= int(5 + dps
**0.8)
420 rule
= CompositeGaussLegendre(degree
, degree
//2, verbose
)
422 rule
= AdaptiveTanhSinh(initial
= int(3 + max(0, math
.log(prec
/30.0, 2))))
426 raise ValueError("unknown method")
428 s
, err
, steps
= rule
.adaptive(f
, Float(a
), Float(b
), eps
, steps
=0, maxsteps
=maxsteps
, verbose
=verbose
)
435 print "Warning: failed to reach full accuracy.", \
436 "Estimated magnitude of error: ", str(err
)