Skip to content

Commit

Permalink
Merge pull request #3140 from stevengj/quadgk
Browse files Browse the repository at this point in the history
RFC: quadgk integration routine
  • Loading branch information
stevengj committed May 18, 2013
2 parents 186d38f + 949421f commit 617840e
Show file tree
Hide file tree
Showing 6 changed files with 435 additions and 0 deletions.
4 changes: 4 additions & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export
Math,
MPFR,
GMP,
QuadGK,
Sort,
Test,
Pkg,
Expand Down Expand Up @@ -862,6 +863,9 @@ export
plan_dct!,
plan_idct!,

# numerical integration
quadgk,

# iteration
start,
done,
Expand Down
344 changes: 344 additions & 0 deletions base/quadgk.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,344 @@
module QuadGK
export gauss, kronrod, quadgk
using Base.Collections
import Base.isless, Base.Sort.Reverse

# Adaptive Gauss-Kronrod quadrature routines (arbitrary precision),
# written and contributed to Julia by Steven G. Johnson, 2013.

###########################################################################

# cache of (T,n) -> (x,w,gw) Kronrod rules, to avoid recomputing them
# unnecessarily for repeated integration. We initialize it with the
# default n=7 rule for double-precision calculations.
const rulecache = (Any=>Any)[ (Float64,7) => # precomputed in 100-bit arith.
([-9.9145537112081263920685469752598e-01,
-9.4910791234275852452618968404809e-01,
-8.6486442335976907278971278864098e-01,
-7.415311855993944398638647732811e-01,
-5.8608723546769113029414483825842e-01,
-4.0584515137739716690660641207707e-01,
-2.0778495500789846760068940377309e-01,
0.0],
[2.2935322010529224963732008059913e-02,
6.3092092629978553290700663189093e-02,
1.0479001032225018383987632254189e-01,
1.4065325971552591874518959051021e-01,
1.6900472663926790282658342659795e-01,
1.9035057806478540991325640242055e-01,
2.0443294007529889241416199923466e-01,
2.0948214108472782801299917489173e-01],
[1.2948496616886969327061143267787e-01,
2.797053914892766679014677714229e-01,
3.8183005050511894495036977548818e-01,
4.1795918367346938775510204081658e-01]) ]

# integration segment (a,b), estimated integral I, and estimated error E
immutable Segment
a::Number
b::Number
I::Number
E::Number
end
isless(i::Segment, j::Segment) = isless(i.E, j.E)

# Internal routine: integrate f(x) over a sequence of line segments
# evaluate the integration rule and return a Segment.
function evalrule(f, a,b, x,w,gw)
# Ik and Ig are integrals via Kronrod and Gauss rules, respectively
Ik = Ig = zero(promote_type(typeof(a),eltype(w)))
s = convert(eltype(x), 0.5) * (b-a)
n1 = 1 - (length(x) & 1) # 0 if even order, 1 if odd order
for i = 1:length(gw)-n1
fg = f(a + (1+x[2i])*s) + f(a + (1-x[2i])*s)
fk = f(a + (1+x[2i-1])*s) + f(a + (1-x[2i-1])*s)
Ig += fg * gw[i]
Ik += fg * w[2i] + fk * w[2i-1]
end
if n1 == 0 # even: Gauss rule does not include x == 0
Ik += f(a + s) * w[end]
else # odd: don't count x==0 twice in Gauss rule
f0 = f(a + s)
Ig += f0 * gw[end]
Ik += f0 * w[end] +
(f(a + (1+x[end-1])*s) + f(a + (1-x[end-1])*s)) * w[end-1]
end
Ik *= s
Ig *= s
E = abs(Ik - Ig)
if isnan(E) || isinf(E)
throw(DomainError())
end
return Segment(a, b, Ik, E)
end

rulekey(::Type{BigFloat}, n) = (BigFloat, get_bigfloat_precision(), n)
rulekey(T,n) = (T,n)

# Internal routine: integrate f over the union of the open intervals
# (s[1],s[2]), (s[2],s[3]), ..., (s[end-1],s[end]), using h-adaptive
# integration with the order-n Kronrod rule and weights of type Tw,
# with absolute tolerance abstol and relative tolerance reltol,
# with maxevals an approximate maximum number of f evaluations.
function do_quadgk{Tw}(f, s, n, ::Type{Tw}, abstol, reltol, maxevals)

if eltype(s) <: Real # check for infinite or semi-infinite intervals
s1 = s[1]; s2 = s[end]; inf1 = isinf(s1); inf2 = isinf(s2)
if inf1 || inf2
if inf1 && inf2 # x = t/(1-t^2) coordinate transformation
return do_quadgk(t -> begin t2 = t*t; den = 1 / (1 - t2);
f(t*den) * (1+t2)*den*den; end,
map(x -> isinf(x) ? copysign(one(x), x) : 2x / (1+hypot(1,2x)), s),
n, Tw, abstol, reltol, maxevals)
end
s0,si = inf1 ? (s2,s1) : (s1,s2)
if si < 0 # x = s0 - t/(1-t)
return do_quadgk(t -> begin den = 1 / (1 - t);
f(s0 - t*den) * den*den; end,
reverse!(map(x -> 1 / (1 + 1 / (s0 - x)), s)),
n, Tw, abstol, reltol, maxevals)
else # x = s0 + t/(1-t)
return do_quadgk(t -> begin den = 1 / (1 - t);
f(s0 + t*den) * den*den; end,
map(x -> 1 / (1 + 1 / (x - s0)), s),
n, Tw, abstol, reltol, maxevals)
end
end
end

key = rulekey(Tw,n)
x,w,gw = haskey(rulecache, key) ? rulecache[key] :
(rulecache[key] = kronrod(Tw, n))
segs = Segment[]
for i in 1:length(s) - 1
heappush!(segs, evalrule(f, s[i],s[i+1], x,w,gw), Reverse)
end
numevals = (2n+1) * length(segs)
I = segs[1].I
E = segs[1].E
for i in 2:length(segs)
I += segs[i].I
E += segs[i].E
end
# Pop the biggest-error segment and subdivide (h-adaptation)
# until convergence is achieved or maxevals is exceeded.
while E > abstol && E > reltol * abs(I) && numevals < maxevals
s = heappop!(segs, Reverse)
mid = (s.a + s.b) * 0.5
s1 = evalrule(f, s.a, mid, x,w,gw)
s2 = evalrule(f, mid, s.b, x,w,gw)
heappush!(segs, s1, Reverse)
heappush!(segs, s2, Reverse)
I = (I - s.I) + s1.I + s2.I
E = (E - s.E) + s1.E + s2.E
numevals += 4n+2
end
# re-sum (paranoia about accumulated roundoff)
I = segs[1].I
E = segs[1].E
for i in 2:length(segs)
I += segs[i].I
E += segs[i].E
end
return (I, E)
end

# Gauss-Kronrod quadrature of f from a to b to c...

function quadgk{T<:FloatingPoint}(f, a::T,b::T,c::T...;
abstol=zero(T), reltol=eps(T)*100,
maxevals=10^7, order=7)
do_quadgk(f, [a, b, c...], order, T, abstol, reltol, maxevals)
end

function quadgk{T<:FloatingPoint}(f, a::Complex{T},
b::Complex{T},c::Complex{T}...;
abstol=zero(T), reltol=eps(T)*100,
maxevals=10^7, order=7)
do_quadgk(f, [a, b, c...], order, T, abstol, reltol, maxevals)
end

# generic version: determine precision from a combination of
# all the integration-segment endpoints
function quadgk(f, a, b, c...; kws...)
T = promote_type(typeof(float(a)), typeof(b))
for i in 1:length(c)
T = promote_type(T, typeof(c[i]))
end
cT = T[ c[i] for i in 1:length(c) ]
quadgk(f, convert(T, a), convert(T, b), cT...; kws...)
end

###########################################################################
# Gauss-Kronrod integration-weight computation for arbitrary floating-point
# types and precision, implemented based on the description in:
#
# Dirk P. Laurie, "Calculation of Gauss-Kronrod quadrature rules,"
# Mathematics of Computation, vol. 66, no. 219, pp. 1133-1145 (1997).
#
# for the Kronrod rule, and for the Gauss rule from the description in
#
# Lloyd N. Trefethen and David Bau, Numerical Linear Algebra (SIAM, 1997).
#
# Arbitrary-precision eigenvalue (eignewt & eigpoly) and eigenvector
# (eigvec1) routines are written by SGJ, independent of the above sources.
#
# Since we only implement Gauss-Kronrod rules for the unit weight function,
# the diagonals of the Jacobi matrices are zero and certain things simplify
# compared to the general case of an arbitrary weight function.

# Given a symmetric tridiagonal matrix H with H[i,i] = 0 and
# H[i-1,i] = H[i,i-1] = b[i-1], compute p(z) = det(z I - H) and its
# derivative p'(z), returning (p,p').
function eigpoly(b,z,m=length(b)+1)
d1 = z
d1deriv = d2 = one(z);
d2deriv = zero(z);
for i = 2:m
b2 = b[i-1]^2
d = z * d1 - b2 * d2;
dderiv = d1 + z * d1deriv - b2 * d2deriv;
d2 = d1;
d1 = d;
d2deriv = d1deriv;
d1deriv = dderiv;
end
return (d1, d1deriv)
end

# compute the n smallest eigenvalues of the symmetric tridiagonal matrix H
# (defined from b as in eigpoly) using a Newton iteration
# on det(H - lambda I). Unlike eig, handles BigFloat.
function eignewt(b,m,n)
# get initial guess from eig on Float64 matrix
H = zeros(m, m)
for i in 2:m
H[i-1,i] = H[i,i-1] = b[i-1]
end
# TODO: use LAPACK routine for eigenvalues of tridiagonal matrix
# rather than generic O(n^3) routine
lambda0 = sort(eigvals(H))

lambda = Array(eltype(b), n)
for i = 1:n
lambda[i] = lambda0[i]
for k = 1:1000
(p,pderiv) = eigpoly(b,lambda[i],m)
lambda[i] = (lamold = lambda[i]) - p / pderiv
if abs(lambda[i] - lamold) < 10 * eps(lambda[i]) * abs(lambda[i])
break
end
end
# do one final Newton iteration for luck and profit:
(p,pderiv) = eigpoly(b,lambda[i],m)
lambda[i] = lambda[i] - p / pderiv
end
return lambda
end

# given an eigenvalue z and the matrix H(b) from above, return
# the corresponding eigenvector, normalized to 1.
function eigvec1(b,z::Number,m=length(b)+1)
# "cheat" and use the fact that our eigenvector v must have a
# nonzero first entries (since it is a quadrature weight), so we
# can set v[1] = 1 to solve for the rest of the components:.
v = Array(eltype(b), m)
v[1] = 1
if m > 1
s = v[1]
v[2] = z * v[1] / b[1]
s += v[2]^2
for i = 3:m
v[i] = - (b[i-2]*v[i-2] - z*v[i-1]) / b[i-1]
s += v[i]^2
end
scale!(v, 1 / sqrt(s))
end
return v
end

# return (x, w) pair of N quadrature points x[i] and weights w[i] to
# integrate functions on the interval (-1, 1). i.e. dot(f(x), w)
# approximates the integral. Uses the method described in Trefethen &
# Bau, Numerical Linear Algebra, to find the N-point Gaussian quadrature
function gauss{T<:FloatingPoint}(::Type{T}, N::Integer)
if N < 1
throw(ArgumentError("Gauss rules require positive order"))
end
o = one(T)
b = T[ n / sqrt(4n^2 - o) for n = 1:N-1 ]
x = eignewt(b,N,N)
w = T[ 2*eigvec1(b,x[i])[1]^2 for i = 1:N ]
return (x, w)
end

# Compute 2n+1 Kronrod points x and weights w based on the description in
# Laurie (1997), appendix A, simplified for a=0, for integrating on [-1,1].
# Since the rule is symmetric only returns the n+1 points with x <= 0.
# Also computes the embedded n-point Gauss quadrature weights gw (again
# for x <= 0), corresponding to the points x[2:2:end]. Returns (x,w,wg).
function kronrod{T<:FloatingPoint}(::Type{T}, n::Integer)
if n < 1
throw(ArgumentError("Kronrod rules require positive order"))
end
o = one(T)
b = zeros(T, 2n+1)
b[1] = 2*o
for j = 1:div(3n+1,2)
b[j+1] = j^2 / (4j^2 - o)
end
s = zeros(T, div(n,2) + 2)
t = zeros(T, div(n,2) + 2)
t[2] = b[n+2]
for m = 0:n-2
u = zero(T)
for k = div(m+1,2):-1:0
l = m - k + 1
k1 = k + n + 2
u += b[k1]*s[k+1] - b[l]*s[k+2]
s[k+2] = u
end
s,t = t,s
end
for j = div(n,2):-1:0
s[j+2] = s[j+1]
end
for m = n-1:2n-3
u = zero(T)
for k = m+1-n:div(m-1,2)
l = m - k + 1
j = n - l
k1 = k + n + 2
u -= b[k1]*s[j+2] - b[l]*s[j+3]
s[j+2] = u
end
k = div(m+1,2)
if 2k != m
j = n - (m - k + 2)
b[k+n+2] = s[j+2] / s[j+3]
end
s,t = t,s
end
for j = 1:2n
b[j] = sqrt(b[j+1])
end

# get negative quadrature points x
x = eignewt(b,2n+1,n+1) # x <= 0

# get quadrature weights
w = T[ 2*eigvec1(b,x[i],2n+1)[1]^2 for i in 1:n+1 ]

# Get embedded Gauss rule from even-indexed points, using
# the method described in Trefethen and Bau.
for j = 1:n-1
b[j] = j / sqrt(4j^2 - o)
end
gw = T[ 2*eigvec1(b,x[i],n)[1]^2 for i = 2:2:n+1 ]

return (x, w, gw)
end

###########################################################################

end # module QuadGK
4 changes: 4 additions & 0 deletions base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ importall .GMP
include("mpfr.jl")
importall .MPFR

# Numerical integration
include("quadgk.jl")
importall .QuadGK

# deprecated functions
include("deprecated.jl")

Expand Down
Loading

0 comments on commit 617840e

Please sign in to comment.