// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
// vi: set et ts=4 sw=4 sts=4:
/*****************************************************************************
* See the file COPYING for full copying permissions. *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* 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. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
* *
* This version is modified after the original version by John D. Cook *
* see https://www.codeproject.com/ *
* Articles/31550/Fast-Numerical-Integration *
* which is licensed under BSD-2-clause, which reads as follows: *
* Copyright John D. Cook *
* *
* Redistribution and use in source and binary forms, with or without *
* modification, are permitted provided that the following *
* conditions are met: *
* 1. Redistributions of source code must retain the above *
* copyright notice, this list of conditions *
and the following disclaimer. *
* 2. Redistributions in binary form must reproduce the above *
* copyright notice, this list of conditions *
* and the following disclaimer *
* in the documentation and/or other materials *
* provided with the distribution. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT *
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS *
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE *
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, *
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL *
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE *
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, *
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE *
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, *
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
* *
*****************************************************************************/
#ifndef DUMUX_COMMON_DOUBLEEXP_INTEGRATOR_HH
#define DUMUX_COMMON_DOUBLEEXP_INTEGRATOR_HH
#include
#include
#include
#include
namespace Dumux {
/*!
* \brief Numerical integration in one dimension using the double exponential method of M. Mori.
*/
template
class DoubleExponentialIntegrator
{
public:
/*!
* \brief Integrate an analytic function over a finite interval
* \param f the integrand (invocable with a single scalar)
* \param a lower limit of integration
* \param b upper limit of integration
* \param targetAbsoluteError desired bound on error
* \param numFunctionEvaluations number of function evaluations used
* \param errorEstimate estimate for error in integration
* \return The value of the integral
*/
template::value>...>
static Scalar integrate(const Function& f,
const Scalar a,
const Scalar b,
const Scalar targetAbsoluteError,
int& numFunctionEvaluations,
Scalar& errorEstimate)
{
// Apply the linear change of variables x = ct + d
// $$\int_a^b f(x) dx = c \int_{-1}^1 f( ct + d ) dt$$
// c = (b-a)/2, d = (a+b)/2
const Scalar c = 0.5*(b - a);
const Scalar d = 0.5*(a + b);
return integrateCore_(f, c, d, targetAbsoluteError, numFunctionEvaluations, errorEstimate,
doubleExponentialIntegrationAbcissas, doubleExponentialIntegrationWeights);
}
/*!
* \brief Integrate an analytic function over a finite interval.
* \note This version overloaded to not require arguments passed in for
* function evaluation counts or error estimates.
* \param f the integrand (invocable with a single scalar)
* \param a lower integral bound
* \param b upper integral bound
* \param targetAbsoluteError desired absolute error in the result
* \return The value of the integral.
*/
template::value>...>
static Scalar integrate(const Function& f,
const Scalar a,
const Scalar b,
const Scalar targetAbsoluteError)
{
int numFunctionEvaluations;
Scalar errorEstimate;
return integrate(f, a, b, targetAbsoluteError, numFunctionEvaluations, errorEstimate);
}
private:
// Integrate f(cx + d) with the given integration constants
template
static Scalar integrateCore_(const Function& f,
const Scalar c, // slope of change of variables
const Scalar d, // intercept of change of variables
Scalar targetAbsoluteError,
int& numFunctionEvaluations,
Scalar& errorEstimate,
const double* abcissas,
const double* weights)
{
targetAbsoluteError /= c;
// Offsets to where each level's integration constants start.
// The last element is not a beginning but an end.
static const int offsets[] = {1, 4, 7, 13, 25, 49, 97, 193};
static const int numLevels = sizeof(offsets)/sizeof(int) - 1;
Scalar newContribution = 0.0;
Scalar integral = 0.0;
Scalar h = 1.0;
errorEstimate = std::numeric_limits::max();
Scalar previousDelta, currentDelta = std::numeric_limits::max();
integral = f(c*abcissas[0] + d) * weights[0];
int i;
for (i = offsets[0]; i != offsets[1]; ++i)
integral += weights[i]*(f(c*abcissas[i] + d) + f(-c*abcissas[i] + d));
for (int level = 1; level != numLevels; ++level)
{
h *= 0.5;
newContribution = 0.0;
for (i = offsets[level]; i != offsets[level+1]; ++i)
newContribution += weights[i]*(f(c*abcissas[i] + d) + f(-c*abcissas[i] + d));
newContribution *= h;
// difference in consecutive integral estimates
previousDelta = currentDelta;
using std::abs;
currentDelta = abs(0.5*integral - newContribution);
integral = 0.5*integral + newContribution;
// Once convergence kicks in, error is approximately squared at each step.
// Determine whether we're in the convergent region by looking at the trend in the error.
if (level == 1)
continue; // previousDelta meaningless, so cannot check convergence.
// Exact comparison with zero is harmless here. Could possibly be replaced with
// a small positive upper limit on the size of currentDelta, but determining
// that upper limit would be difficult. At worse, the loop is executed more
// times than necessary. But no infinite loop can result since there is
// an upper bound on the loop variable.
if (currentDelta == 0.0)
break;
using std::log;
const Scalar rate = log( currentDelta )/log( previousDelta ); // previousDelta != 0 or would have been kicked out previously
if (rate > 1.9 && rate < 2.1)
{
// If convergence theory applied perfectly, r would be 2 in the convergence region.
// r close to 2 is good enough. We expect the difference between this integral estimate
// and the next one to be roughly delta^2.
errorEstimate = currentDelta*currentDelta;
}
else
{
// Not in the convergence region. Assume only that error is decreasing.
errorEstimate = currentDelta;
}
if (errorEstimate < 0.1*targetAbsoluteError)
break;
}
numFunctionEvaluations = 2*i - 1;
errorEstimate *= c;
return c*integral;
}
};
} // end namespace Dumux
#endif