The Gauss-Lobatto quadrature is an alternative to the widely used Gaussian quadrature that is exact for polynomials of order where is the number of points used.
Compared to the standard Gaussian quadrature, the Lobatto rules have the advantage that the end-points of the interval to be integrated are included in the points to be evaluated. This means adaptive refinement is easy to implement and understand in this scheme.
The algorithm broadly consists of recursively evaluating a 7-point Lobatto quadrature rule.
At each recursion level, the 7- point and 4-point Lobatto quadratures are estimated. If these estimates are close enough, it is assumed that the required tolerance has been met and the this recursion level returns the value of the 7-point estimate and does not cause further processing.
If the two estimates are sufficiently different, the interval to be integrated is split into six sub-intervals, with the boundaries of these intervals defined by the 7 points used in the quadrature (therefore re-using all of the function evaluations). The same procedure is then applied to these six sub-intervals and the result of the current recursion level is the sum of results of the six sub-intervals.
The implementation shown below is closely based on the open source implementation in the QuantLib library. For the purposes of this article I have made the implementation entirely free-standing and simplified it slightly by removing the relative tolerance stopping criterion.
The implementation is quite straightforward, with only a few points to note:
/*
Copyright (C) 2008 Klaus Spanderen
Copyright (C) 2010 Bojan Nikolic <bojan@bnikolic.co.uk>
Portions originaly part of QuantLib http://quantlib.org/
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.
*/
#include <cmath>
#include <boost/function.hpp>
#include <iostream>
#include <boost/format.hpp>
/** \brief Perform a single step of the Gauss-Lobatto integration
\param f Function to integrate
\param a Lower integration limit
\param b Upper integration limit
\param fa Value of function at the lower limit (used to save an
evaluation when refinement is used)
\param fa Value of function at the upper limit (used to save an
evaluation when refinement is used)
\param neval Number of evaluations made so far
\param maxeval Maximum number of evalutions which should not be
exceeded
\param acc Required accuracy expressed in units of
std::numeric_limits<double>::epsilon(). This allows less-than
comparison by using addition and equality.
*/
double GaussLobattoIntStep(const boost::function<double (double)>& f,
double a, double b,
double fa, double fb,
size_t &neval,
size_t maxeval,
double acc)
{
// Constants used in the algorithm
const double alpha = std::sqrt(2.0/3.0);
const double beta = 1.0/std::sqrt(5.0);
if (neval >= maxeval)
{
throw std::runtime_error("Maximum number of evaluations reached in GaussLobatto");
}
// Here the abcissa points and function values for both the 4-point
// and the 7-point rule are calculated (the points at the end of
// interval come from the function call, i.e., fa and fb. Also note
// the 7-point rule re-uses all the points of the 4-point rule.)
const double h=(b-a)/2;
const double m=(a+b)/2;
const double mll=m-alpha*h;
const double ml =m-beta*h;
const double mr =m+beta*h;
const double mrr=m+alpha*h;
const double fmll= f(mll);
const double fml = f(ml);
const double fm = f(m);
const double fmr = f(mr);
const double fmrr= f(mrr);
neval+=5;
// Both the 4-point and 7-point rule integrals are evaluted
const double integral2=(h/6)*(fa+fb+5*(fml+fmr));
const double integral1=(h/1470)*(77*(fa+fb)
+432*(fmll+fmrr)+625*(fml+fmr)+672*fm);
// The difference betwen the 4-point and 7-point integrals is the
// estimate of the accuracy
const double estacc=(integral1-integral2);
// The volatile keyword should prevent the floating point
// destination value from being stored in extended precision
// registers which actually have a very different
// std::numeric_limits<double>::epsilon().
volatile double dist = acc + estacc;
if(dist==acc || mll<=a || b<=mrr)
{
if (not (m>a && b>m))
{
throw std::runtime_error("Integration reached an interval with no more machine numbers");
}
return integral1;
}
else {
return GaussLobattoIntStep(f, a, mll, fa, fmll, neval, maxeval, acc)
+ GaussLobattoIntStep(f, mll, ml, fmll, fml, neval, maxeval, acc)
+ GaussLobattoIntStep(f, ml, m, fml, fm, neval, maxeval, acc)
+ GaussLobattoIntStep(f, m, mr, fm, fmr, neval, maxeval, acc)
+ GaussLobattoIntStep(f, mr, mrr, fmr, fmrr, neval, maxeval, acc)
+ GaussLobattoIntStep(f, mrr, b, fmrr, fb, neval, maxeval, acc);
}
}
/** \brief Compute the Gauss-Lobatto integral
\param f The function to be integrated
\param a The lower integration limit
\param b The upper integration limit
\param abstol Absolute tolerance -- integration stops when the
error estimate is smaller than this
\param maxeval Maxium of evaluations to make. If this number of
evalution is made without reaching the requied accuracy, an
exception of type std::runtime_error is thrown.
*/
double GaussLobattoInt(const boost::function<double (double)>& f,
double a, double b,
double abstol,
size_t maxeval)
{
const double tol_epsunit=abstol/std::numeric_limits<double>::epsilon();
size_t neval=0;
return GaussLobattoIntStep(f, a, b,
f(a), f(b),
neval,
maxeval,
tol_epsunit);
}
// Below is a very simple illustration of the code
double samplef(double x)
{
return std::sin(x);
}
int main(void)
{
double res=GaussLobattoInt(&samplef,
0, 10,
1e-10,
1000);
std::cout<<boost::format("Result is %g ") % res
<<std::endl;
}