/*************************************************************************** * GIntegral.cpp - Integration class * * ----------------------------------------------------------------------- * * copyright (C) 2010-2013 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * 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 . * * * ***************************************************************************/ /** * @file GIntegral.cpp * @brief Integration class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #include // For std::abs() #include #include "GIntegral.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ #define G_ROMB "GIntegral::romb(double&, double&, int&)" #define G_TRAPZD "GIntegral::trapzd(double&, double&, int&, double)" #define G_POLINT "GIntegral::polint(double*, double*, int, double, double*)" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GIntegral::GIntegral(void) { // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Function kernel constructor * * @param[in] kernel Pointer to function kernel. * * The function kernel constructor assigns the function kernel pointer in * constructing the object. ***************************************************************************/ GIntegral::GIntegral(GFunction* kernel) { // Initialise members init_members(); // Set function kernel m_kernel = kernel; // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] integral Integral. ***************************************************************************/ GIntegral::GIntegral(const GIntegral& integral) { // Initialise members init_members(); // Copy members copy_members(integral); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GIntegral::~GIntegral(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] integral Integral. * @return Integral. ***************************************************************************/ GIntegral& GIntegral::operator=(const GIntegral& integral) { // Execute only if object is not identical if (this != &integral) { // Free members free_members(); // Initialise integral init_members(); // Copy members copy_members(integral); } // endif: object was not identical // Return return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear integral ***************************************************************************/ void GIntegral::clear(void) { // Free members free_members(); // Initialise private members init_members(); // Return return; } /***********************************************************************//** * @brief Clone integral * * @return Pointer to deep copy of integral. ***************************************************************************/ GIntegral* GIntegral::clone(void) const { return new GIntegral(*this); } /***********************************************************************//** * @brief Perform Romberg integration * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * @param[in] k Integration order (default: k=5) * * Returns the integral of the integrand from a to b. Integration is * performed by Romberg's method of order 2K, where e.g. K=2 in Simpson's * rule. * The number of iterations is limited by m_max_iter. m_eps specifies the * requested fractional accuracy. By default it is set to 1e-6. * * @todo Check that k is smaller than m_max_iter * @todo Make use of std::vector class ***************************************************************************/ double GIntegral::romb(const double& a, const double& b, const int& k) { // Initialise result double result = 0.0; // Continue only if integration range is valid if (b > a) { // Initialise variables bool converged = false; double ss = 0.0; double dss = 0.0; // Allocate temporal storage double* s = new double[m_max_iter+2]; double* h = new double[m_max_iter+2]; // Initialise step size h[1] = 1.0; s[0] = 0.0; // Iterative loop for (m_iter = 1; m_iter <= m_max_iter; ++m_iter) { // Integration using Trapezoid rule s[m_iter] = trapzd(a, b, m_iter, s[m_iter-1]); // Compile option: Check for NaN/Inf #if defined(G_NAN_CHECK) if (is_notanumber(s[m_iter]) || is_infinite(s[m_iter])) { std::cout << "*** ERROR: GIntegral::romb"; std::cout << "(a=" << a << ", b=" << b << ", k=" << k << "):"; std::cout << " NaN/Inf encountered"; std::cout << " (s[" << m_iter << "]=" << s[m_iter] << ")"; std::cout << std::endl; } #endif // Starting from iteration k on, use polynomial interpolation if (m_iter >= k) { ss = polint(&h[m_iter-k], &s[m_iter-k], k, 0.0, &dss); if (std::abs(dss) <= m_eps * std::abs(ss)) { converged = true; result = ss; break; } } // Reduce step size h[m_iter+1]= 0.25 * h[m_iter]; } // endfor: iterative loop // Free temporal storage delete [] s; delete [] h; // Dump warning if (!m_silent) { if (!converged) { std::string msg = "Integration uncertainty "+ gammalib::str(std::abs(dss))+ " exceeds tolerance of "+ gammalib::str(m_eps * std::abs(ss))+ " after "+gammalib::str(m_iter)+ " iterations. Result "+ gammalib::str(result)+ " is inaccurate."; gammalib::warning(G_ROMB, msg); } } } // endif: integration range was valid // Return result return result; } /***********************************************************************//** * @brief Perform Trapezoidal integration * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * @param[in] n Number of steps. * @param[in] result Result from a previous trapezoidal integration step. * * The original Numerical Recipes function had result declared as a static * variable, yet this led to some untrackable integration problems. For this * reason, previous results are now passed using an argument. * Result initialisation is done if n=1. ***************************************************************************/ double GIntegral::trapzd(const double& a, const double& b, const int& n, double result) { // Handle case of identical boundaries if (a == b) { result = 0.0; } // ... otherwise use trapeziodal rule else { // Case A: Only a single step is requested if (n == 1) { // Evaluate integrand at boundaries double y_a = m_kernel->eval(a); double y_b = m_kernel->eval(b); // Compute result result = 0.5*(b-a)*(y_a + y_b); } // endif: only a single step was requested // Case B: More than a single step is requested else { // Compute step level 2^(n-1) int it = 1; for (int j = 1; j < n-1; ++j) { it <<= 1; } // Verify that step level is valid if (it == 0) { std::string msg = "Invalid step level "+gammalib::str(it)+ " encountered for" " a="+gammalib::str(a)+ ", b="+gammalib::str(b)+ ", n="+gammalib::str(n)+ ", result="+gammalib::str(result)+ ". Looks like n is too large."; gammalib::warning(G_TRAPZD, msg); } // Set step size double tnm = double(it); double del = (b-a)/tnm; // Verify that step is >0 if (del == 0) { std::string msg = "Invalid step size "+gammalib::str(del)+ " encountered for" " a="+gammalib::str(a)+ ", b="+gammalib::str(b)+ ", n="+gammalib::str(n)+ ", result="+gammalib::str(result)+ ". Step is too small to make sense."; gammalib::warning(G_TRAPZD, msg); } // Sum up values double x = a + 0.5*del; double sum = 0.0; for (int j = 0; j < it; ++j, x+=del) { // Evaluate integrand double y = m_kernel->eval(x); // Add integrand sum += y; } // endfor: looped over steps // Set result result = 0.5*(result + (b-a)*sum/tnm); } } // endelse: trapeziodal rule was applied // Return result return result; } /***********************************************************************//** * @brief Print integral information * * @param[in] chatter Chattiness (defaults to NORMAL). * @return String containing integral information. ***************************************************************************/ std::string GIntegral::print(const GChatter& chatter) const { // Initialise result string std::string result; // Continue only if chatter is not silent if (chatter != SILENT) { // Append header result.append("=== GIntegral ==="); // Append information result.append("\n"+gammalib::parformat("Relative precision")); result.append(gammalib::str(eps())); result.append("\n"+gammalib::parformat("Max. number of iterations")); result.append(gammalib::str(max_iter())); if (silent()) { result.append("\n"+gammalib::parformat("Warnings")+"suppressed"); } else { result.append("\n"+gammalib::parformat("Warnings")); result.append("in standard output"); } } // endif: chatter was not silent // Return result return result; } /*========================================================================== = = = Protected methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Initialise class members ***************************************************************************/ void GIntegral::init_members(void) { // Initialise members m_kernel = NULL; m_eps = 1.0e-6; m_max_iter = 20; m_iter = 0; m_silent = false; // Return return; } /***********************************************************************//** * @brief Copy class members * * @param[in] integral Integral. ***************************************************************************/ void GIntegral::copy_members(const GIntegral& integral) { // Copy attributes m_kernel = integral.m_kernel; m_eps = integral.m_eps; m_max_iter = integral.m_max_iter; m_iter = integral.m_iter; m_silent = integral.m_silent; // Return return; } /***********************************************************************//** * @brief Delete class members ***************************************************************************/ void GIntegral::free_members(void) { // Return return; } /***********************************************************************//** * @brief Perform Polynomial interpolation * * @param[in] xa Pointer to array of X values. * @param[in] ya Pointer to array of Y values. * @param[in] n Number of elements in arrays. * @param[in] x X value at which interpolations should be performed. * @param[out] dy Error estimate for interpolated values. * * Given arrays xa[1,..,n] and ya[1,..,n], and given a value x, this * method returns a value y, and an error estimate dy. If P(x) is the * polynomial of degree n-1, then the returned value y=P(x). * * @todo Implement exceptions instead of screen dump. * @todo Use std::vector for xa and ya and start at 0 ***************************************************************************/ double GIntegral::polint(double* xa, double* ya, int n, double x, double* dy) { // Initialise result double y = 0.0; // Allocate temporary memory std::vector c(n, 0.0); std::vector d(n, 0.0); // Compute initial distance to first node double dif = std::abs(x-xa[1]); // Find index ns of the closest table entry int ns = 0; for (int i = 0; i < n; ++i) { double dift = std::abs(x-xa[i+1]); if (dift < dif) { ns = i; dif = dift; } c[i] = ya[i+1]; d[i] = ya[i+1]; } // Get initial approximation to y y = ya[ns+1]; ns--; // Loop over each column of the tableau for (int m = 1; m < n; ++m) { // Update current c's and d's for (int i = 0; i < n-m; ++i) { double ho = xa[i+1] - x; double hp = xa[i+m+1] - x; double w = c[i+1] - d[i]; double den = ho - hp; if (den == 0.0) { std::string msg = "Invalid step size "+gammalib::str(den)+ " encountered. Two values in xa array are" " identical."; gammalib::warning(G_POLINT, msg); } den = w/den; d[i] = hp*den; c[i] = ho*den; } // Compute y correction *dy = (2*(ns+1) < (n-m)) ? c[ns+1] : d[ns--]; // Update y y += *dy; } // endfor: looped over columns of tableau // Return return y; }