/*************************************************************************** * GIntegral.cpp - Integration class * * ----------------------------------------------------------------------- * * copyright (C) 2010-2015 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 // std::abs() #include #include // std::sort #include "GIntegral.hpp" #include "GException.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ #define G_ROMBERG "GIntegral::romberg(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 __________________________________________________ */ /* __ Constants __________________________________________________________ */ namespace gammalib { // Gauss-Kronrod abscissae, common to the 10-, 21-, 43- and 87-point rule const double gkx1[5] = { 0.973906528517171720077964012084452, 0.865063366688984510732096688423493, 0.679409568299024406234327365114874, 0.433395394129247190799265943165784, 0.148874338981631210884826001129720 }; // Gauss-Kronrod weights of the 10-point rule const double gkw10[5] = { 0.066671344308688137593568809893332, 0.149451349150580593145776339657697, 0.219086362515982043995534934228163, 0.269266719309996355091226921569469, 0.295524224714752870173892994651338 }; // Gauss-Kronrod abscissae, common to the 21-, 43- and 87-point rule const double gkx2[5] = { 0.995657163025808080735527280689003, 0.930157491355708226001207180059508, 0.780817726586416897063717578345042, 0.562757134668604683339000099272694, 0.294392862701460198131126603103866 }; // Gauss-Kronrod weights of the 21-point rule for abscissae gkx1 const double gkw21a[5] = { 0.032558162307964727478818972459390, 0.075039674810919952767043140916190, 0.109387158802297641899210590325805, 0.134709217311473325928054001771707, 0.147739104901338491374841515972068 }; // Gauss-Kronrod weights of the 21-point rule for abscissae gkx2 const double gkw21b[6] = { 0.011694638867371874278064396062192, 0.054755896574351996031381300244580, 0.093125454583697605535065465083366, 0.123491976262065851077958109831074, 0.142775938577060080797094273138717, 0.149445554002916905664936468389821 }; // Gauss-Kronrod abscissae, common to the 43- and 87-point rule const double gkx3[11] = { 0.999333360901932081394099323919911, 0.987433402908088869795961478381209, 0.954807934814266299257919200290473, 0.900148695748328293625099494069092, 0.825198314983114150847066732588520, 0.732148388989304982612354848755461, 0.622847970537725238641159120344323, 0.499479574071056499952214885499755, 0.364901661346580768043989548502644, 0.222254919776601296498260928066212, 0.074650617461383322043914435796506 }; // Gauss-Kronrod weights of the 43-point rule for abscissae gkx1, gkx3 const double gkw43a[10] = { 0.016296734289666564924281974617663, 0.037522876120869501461613795898115, 0.054694902058255442147212685465005, 0.067355414609478086075553166302174, 0.073870199632393953432140695251367, 0.005768556059769796184184327908655, 0.027371890593248842081276069289151, 0.046560826910428830743339154433824, 0.061744995201442564496240336030883, 0.071387267268693397768559114425516 }; // Gauss-Kronrod weights of the 43-point formula for abscissae gkx3 const double gkw43b[12] = { 0.001844477640212414100389106552965, 0.010798689585891651740465406741293, 0.021895363867795428102523123075149, 0.032597463975345689443882222526137, 0.042163137935191811847627924327955, 0.050741939600184577780189020092084, 0.058379395542619248375475369330206, 0.064746404951445885544689259517511, 0.069566197912356484528633315038405, 0.072824441471833208150939535192842, 0.074507751014175118273571813842889, 0.074722147517403005594425168280423 }; // Gauss-Kronrod abscissae, of the 87-point rule const double gkx4[22] = { 0.999902977262729234490529830591582, 0.997989895986678745427496322365960, 0.992175497860687222808523352251425, 0.981358163572712773571916941623894, 0.965057623858384619128284110607926, 0.943167613133670596816416634507426, 0.915806414685507209591826430720050, 0.883221657771316501372117548744163, 0.845710748462415666605902011504855, 0.803557658035230982788739474980964, 0.757005730685495558328942793432020, 0.706273209787321819824094274740840, 0.651589466501177922534422205016736, 0.593223374057961088875273770349144, 0.531493605970831932285268948562671, 0.466763623042022844871966781659270, 0.399424847859218804732101665817923, 0.329874877106188288265053371824597, 0.258503559202161551802280975429025, 0.185695396568346652015917141167606, 0.111842213179907468172398359241362, 0.037352123394619870814998165437704 }; // Gauss-Kronrod weights of the 87-point rule for abscissae gkx1, gkx2, gkx3 const double gkw87a[21] = { 0.008148377384149172900002878448190, 0.018761438201562822243935059003794, 0.027347451050052286161582829741283, 0.033677707311637930046581056957588, 0.036935099820427907614589586742499, 0.002884872430211530501334156248695, 0.013685946022712701888950035273128, 0.023280413502888311123409291030404, 0.030872497611713358675466394126442, 0.035693633639418770719351355457044, 0.000915283345202241360843392549948, 0.005399280219300471367738743391053, 0.010947679601118931134327826856808, 0.016298731696787335262665703223280, 0.021081568889203835112433060188190, 0.025370969769253827243467999831710, 0.029189697756475752501446154084920, 0.032373202467202789685788194889595, 0.034783098950365142750781997949596, 0.036412220731351787562801163687577, 0.037253875503047708539592001191226 }; // Gauss-Kronrod weights of the 87-point formula for abscissae gkx4 const double gkw87b[23] = { 0.000274145563762072350016527092881, 0.001807124155057942948341311753254, 0.004096869282759164864458070683480, 0.006758290051847378699816577897424, 0.009549957672201646536053581325377, 0.012329447652244853694626639963780, 0.015010447346388952376697286041943, 0.017548967986243191099665352925900, 0.019938037786440888202278192730714, 0.022194935961012286796332102959499, 0.024339147126000805470360647041454, 0.026374505414839207241503786552615, 0.028286910788771200659968002987960, 0.030052581128092695322521110347341, 0.031646751371439929404586051078883, 0.033050413419978503290785944862689, 0.034255099704226061787082821046821, 0.035262412660156681033782717998428, 0.036076989622888701185500318003895, 0.036698604498456094498018047441094, 0.037120549269832576114119958413599, 0.037334228751935040321235449094698, 0.037361073762679023410321241766599 }; } // end gammalib namespace /*========================================================================== = = = 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] bounds Integration boundaries. * @param[in] order Integration order (default: 5) * * @exception GException::invalid_argument * Integration order incompatible with number of iterations. * * Returns the integral of the integrand, computed over a number of * intervals [a0,a1], [a1,a2], ... that are given as an unordered vector * by the @p bounds argument. * * Integration is performed by Romberg's method of order 2*order, where * * order=1 is equivalent to the trapezoidal rule, * order=2 is equivalent to Simpson's rule, and * order=3 is equivalent to Boole'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. ***************************************************************************/ double GIntegral::romberg(std::vector bounds, const int& order) { // Sort integration boundaries in ascending order std::sort(bounds.begin(), bounds.end()); // Initialise integral double value = 0.0; // Add integral of all intervals for (int i = 0; i < bounds.size()-1; ++i) { value += romberg(bounds[i], bounds[i+1], order); } // Return value return value; } /***********************************************************************//** * @brief Perform Romberg integration * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * @param[in] order Integration order (default: 5) * * @exception GException::invalid_argument * Integration order incompatible with number of iterations. * * Returns the integral of the integrand from a to b. Integration is * performed by Romberg's method of order 2*order, where * * order=1 is equivalent to the trapezoidal rule, * order=2 is equivalent to Simpson's rule, and * order=3 is equivalent to Boole'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. ***************************************************************************/ double GIntegral::romberg(const double& a, const double& b, const int& order) { // Initialise result and status double result = 0.0; // Initialise integration status information m_isvalid = true; m_calls = 0; m_has_abserr = false; m_relerr = false; // Continue only if integration range is valid if (b > a) { // Initialise variables bool converged = false; double dss = 0.0; // Determine (maximum) number of iterations int max_iter = (m_fix_iter > 0) ? m_fix_iter : m_max_iter; // Check whether maximum number of iterations is compliant with // order if (order > max_iter) { std::string msg = "Requested integration order "+ gammalib::str(order)+" is larger than the " "maximum number of iterations "+ gammalib::str(max_iter)+". Either reduce the " "integration order or increase the (maximum) " "number of iterations."; throw GException::invalid_argument(G_ROMBERG, msg); } // Allocate temporal storage double* s = new double[max_iter+2]; double* h = new double[max_iter+2]; // Initialise step size h[1] = 1.0; s[0] = 0.0; // Iterative loop for (m_iter = 1; m_iter <= 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])) { m_message = "*** ERROR: GIntegral::romberg" "(a="+gammalib::str(a)+", b="+gammalib::str(b)+"" ", k="+gammalib::str(k)+"): NaN/Inf encountered" " (s["+gammalib::str(m_iter)+"]=" ""+gammalib::str(s[m_iter])+")"; std::cout << m_message << std::endl; m_isvalid = false; } #endif // Starting from iteration order on, use polynomial interpolation if (m_iter >= order) { // Compute result using polynom interpolation result = polint(&h[m_iter-order], &s[m_iter-order], order, 0.0, &dss); // If a fixed number of iterations has been requested then // check whether we reached the final one; otherwise check // whether we reached the requested precision. if (m_fix_iter > 0) { if (m_iter == max_iter) { converged = true; } } else if (std::abs(dss) <= m_eps * std::abs(result)) { converged = true; } if (converged) { m_has_abserr = true; m_abserr = std::abs(dss); if (std::abs(result) > 0) { m_has_relerr = true; m_relerr = m_abserr / std::abs(result); } break; } } // endif: polynomial interpolation performed // Reduce step size h[m_iter+1]= 0.25 * h[m_iter]; } // endfor: iterative loop // Free temporal storage delete [] s; delete [] h; // Set status and optionally dump warning if (!converged) { m_isvalid = false; m_message = "Integration uncertainty "+ gammalib::str(std::abs(dss))+ " exceeds absolute tolerance of "+ gammalib::str(m_eps * std::abs(result))+ " after "+gammalib::str(m_iter)+ " iterations. Result "+ gammalib::str(result)+ " is inaccurate."; if (!m_silent) { std::string origin = "GIntegral::romberg("+ gammalib::str(a)+", "+ gammalib::str(b)+", "+ gammalib::str(order)+")"; gammalib::warning(origin, m_message); } } } // 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); m_calls += 2; // 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) { m_isvalid = false; m_message = "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, m_message); } // Set step size double tnm = double(it); double del = (b-a)/tnm; // Verify that step is >0 if (del == 0) { m_isvalid = false; m_message = "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, m_message); } // 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); m_calls++; // 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 Adaptive Simpson's integration * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * * Integrates the function using an adaptive Simpson's rule. The initial * interval [a,b] is split into two sub-intervals [a,c] and [c,b] for which * the integral is computed using * * \f[ * \frac{b-a}{6} f(a) + 4f(c) + f(b) * \f] * * where \f$c=(a+b)/2\f$ is the mid-point of interval [a,b]. Each * sub-interval is then recursively divided into sub-interval and the process * is repeated. Dividing of sub-intervals is stopped when the difference * between subsequent intervals falls below the relative tolerance specified * by eps(). The maximum recursion depth is set by the max_iter() method. * * I almost do not dare to confess, but the code has been taken from * http://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method * It's really pretty simple ... ***************************************************************************/ double GIntegral::adaptive_simpson(const double& a, const double& b) const { // Initialise integration status information m_isvalid = true; m_calls = 0; m_iter = m_max_iter; m_has_abserr = false; m_relerr = false; // Compute mid-point c double c = 0.5*(a + b); //!< Mid-point of interval [a,b] double h = b - a; //!< Length of interval [a,b] // Evaluate function at boundaries and mid-point c double fa = m_kernel->eval(a); double fb = m_kernel->eval(b); double fc = m_kernel->eval(c); m_calls += 3; // Compute integral using Simpson's rule double S = (h/6.0) * (fa + 4.0*fc + fb); // Initialise absolute precision double epsilon = (std::abs(S) > 0) ? m_eps * S : m_eps; // Call recursive auxiliary function double value = adaptive_simpson_aux(a, b, epsilon, S, fa, fb, fc, m_max_iter); // Deduce the number of iterations from the iteration counter m_iter = m_max_iter - m_iter; // If result is not valid, set and output status message if (!m_isvalid) { m_message = "Integration uncertainty exceeds relative tolerance " "of "+gammalib::str(m_eps)+" and absolute tolerance of " ""+gammalib::str(epsilon)+" after "+gammalib::str(m_iter)+ " iterations and "+gammalib::str(m_calls)+" function " "calls. Result "+gammalib::str(value)+" inaccurate."; if (!m_silent) { std::string origin = "GIntegral::adaptive_simpson("+ gammalib::str(a)+", "+ gammalib::str(b)+")"; gammalib::warning(origin, m_message); } } // Return result return value; } /***********************************************************************//** * @brief Gauss-Kronrod integration * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * ***************************************************************************/ double GIntegral::gauss_kronrod(const double& a, const double& b) const { // Initialise integration status information m_isvalid = true; m_iter = 0; m_calls = 0; m_has_abserr = false; m_relerr = false; // Initialise integration result double result = 0.0; double error = 0.0; // Allocate some arrays double fv1[5]; double fv2[5]; double fv3[5]; double fv4[5]; double savfun[21]; // Main code loop (so that we can exit using break) do { // Tolerance check if (m_eps < 1.12e-14) { m_isvalid = false; m_message = "Requested relative tolerance of "+gammalib::str(m_eps)+ " cannot be acheived. Please relax the integration " "precision."; if (!m_silent) { std::string origin = "GIntegral::gauss_kronrod("+ gammalib::str(a)+", "+ gammalib::str(b)+")"; gammalib::warning(origin, m_message); } } // Compute function at mid-point double h = 0.5 * (b - a); double abs_h = std::abs(h); double c = 0.5 * (b + a); double f_c = m_kernel->eval(c); m_calls++; // Compute the integral using the 10- and 21-point formulae m_iter++; double res10 = 0; double res21 = gammalib::gkw21b[5] * f_c; double resabs = gammalib::gkw21b[5] * std::abs(f_c); for (int k = 0; k < 5; ++k) { double x = h * gammalib::gkx1[k]; double fval1 = m_kernel->eval(c+x); double fval2 = m_kernel->eval(c-x); double fval = fval1 + fval2; m_calls += 2; res10 += gammalib::gkw10[k] * fval; res21 += gammalib::gkw21a[k] * fval; resabs += gammalib::gkw21a[k] * (std::abs(fval1) + std::abs(fval2)); savfun[k] = fval; fv1[k] = fval1; fv2[k] = fval2; } for (int k = 0; k < 5; ++k) { double x = h * gammalib::gkx2[k]; double fval1 = m_kernel->eval(c+x); double fval2 = m_kernel->eval(c-x); double fval = fval1 + fval2; m_calls += 2; res21 += gammalib::gkw21b[k] * fval; resabs += gammalib::gkw21b[k] * (std::abs(fval1) + std::abs(fval2)); savfun[k+5] = fval; fv3[k] = fval1; fv4[k] = fval2; } resabs *= abs_h; double mean = 0.5 * res21; double resasc = gammalib::gkw21b[5] * std::abs(f_c - mean); for (int k = 0; k < 5; ++k) { resasc += (gammalib::gkw21a[k] * (std::abs(fv1[k] - mean) + std::abs(fv2[k] - mean)) + gammalib::gkw21b[k] * (std::abs(fv3[k] - mean) + std::abs(fv4[k] - mean))); } resasc *= abs_h ; result = res21 * h; error = rescale_error((res21 - res10) * h, resabs, resasc); // Test for convergence */ //if (err < epsabs || err < epsrel * fabs (result)) if (error < m_eps * std::abs(result)) { m_has_abserr = true; m_abserr = error; if (std::abs(result) > 0) { m_has_relerr = true; m_relerr = error / std::abs(result); } break; } // Compute the integral using the 43-point formula m_iter++; double res43 = gammalib::gkw43b[11] * f_c; for (int k = 0; k < 10; ++k) { res43 += savfun[k] * gammalib::gkw43a[k]; } for (int k = 0; k < 11; ++k) { double x = h * gammalib::gkx3[k]; double fval = (m_kernel->eval(c+x) + m_kernel->eval(c-x)); m_calls += 2; res43 += fval * gammalib::gkw43b[k]; savfun[k+10] = fval; } result = res43 * h; // Test for convergence */ error = rescale_error((res43 - res21) * h, resabs, resasc); //if (err < epsabs || err < epsrel * fabs (result)) if (error < m_eps * std::abs(result)) { m_has_abserr = true; m_abserr = error; if (std::abs(result) > 0) { m_has_relerr = true; m_relerr = error / std::abs(result); } break; } // Compute the integral using the 87-point formula m_iter++; double res87 = gammalib::gkw87b[22] * f_c; for (int k = 0; k < 21; ++k) { res87 += savfun[k] * gammalib::gkw87a[k]; } for (int k = 0; k < 22; ++k) { double x = h * gammalib::gkx4[k]; res87 += gammalib::gkw87b[k] * (m_kernel->eval(c+x) + m_kernel->eval(c-x)); m_calls += 2; } result = res87 * h ; // Test for convergence */ error = rescale_error ((res87 - res43) * h, resabs, resasc); //if (err < epsabs || err < epsrel * fabs (result)) if (error < m_eps * std::abs(result)) { m_has_abserr = true; m_abserr = error; if (std::abs(result) > 0) { m_has_relerr = true; m_relerr = error / std::abs(result); } break; } // Failed to converge m_isvalid = false; m_message = "Integration uncertainty "+gammalib::str(error)+" exceeds " "absolute tolerance of "+ gammalib::str(m_eps * std::abs(result))+" after "+ gammalib::str(m_iter)+" iterations and "+ gammalib::str(m_calls)+" function calls. Result "+ gammalib::str(result)+" inaccurate."; if (!m_silent) { std::string origin = "GIntegral::gauss_kronrod("+ gammalib::str(a)+", "+ gammalib::str(b)+")"; gammalib::warning(origin, m_message); } } while (false); // end of main loop // 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())); if (m_has_abserr) { result.append("\n"+gammalib::parformat("Absolute error")); result.append(gammalib::str(m_abserr)); } if (m_has_relerr) { result.append("\n"+gammalib::parformat("Relative error")); result.append(gammalib::str(m_relerr)); } result.append("\n"+gammalib::parformat("Function calls")); result.append(gammalib::str(calls())); result.append("\n"+gammalib::parformat("Iterations")); result.append(gammalib::str(iter())); if (m_fix_iter > 0) { result.append(" (fixed: "); result.append(gammalib::str(fixed_iter())); result.append(")"); } else { result.append(" (maximum: "); result.append(gammalib::str(max_iter())); result.append(")"); } // Append status information result.append("\n"+gammalib::parformat("Status")); if (is_valid()) { result.append("Result accurate."); } else { result.append(message()); } 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_fix_iter = 0; m_message.clear(); m_silent = false; // Initialise results m_iter = 0; m_calls = 0; m_isvalid = true; m_has_abserr = false; m_has_relerr = false; m_abserr = 0.0; m_relerr = 0.0; // 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_fix_iter = integral.m_fix_iter; m_message = integral.m_message; m_silent = integral.m_silent; // Copy results m_iter = integral.m_iter; m_calls = integral.m_calls; m_isvalid = integral.m_isvalid; m_has_abserr = integral.m_has_abserr; m_has_relerr = integral.m_has_relerr; m_abserr = integral.m_abserr; m_relerr = integral.m_relerr; // 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) { m_isvalid = false; m_message = "Invalid step size "+gammalib::str(den)+ " encountered. Two values in xa array are" " identical."; gammalib::warning(G_POLINT, m_message); } 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; } /***********************************************************************//** * @brief Auxiliary function for adaptive Simpson's method * * @param[in] a Left integration boundary. * @param[in] b Right integration boundary. * @param[in] eps Precision. * @param[in] S Integral of last computation. * @param[in] fa Function value at left integration boundary. * @param[in] fb Function value at right integration boundary. * @param[in] fc Function value at mid-point of interval [a,b] * @param[in] bottom Iteration counter (stop when 0) * * Implements a recursive auxiliary method for the adative_simpson() * integrator. ***************************************************************************/ double GIntegral::adaptive_simpson_aux(const double& a, const double& b, const double& eps, const double& S, const double& fa, const double& fb, const double& fc, const int& bottom) const { // Store the iteration counter if (bottom < m_iter) { m_iter = bottom; } // Compute mid-point c bet double c = 0.5*(a + b); //!< Mid-point of interval [a,b] double h = b - a; //!< Length of interval [a,b] double d = 0.5*(a + c); //!< Mid-point of interval [a,c] double e = 0.5*(c + b); //!< Mid-point of interval [c,b] // Evaluate function at mid-points d and e double fd = m_kernel->eval(d); double fe = m_kernel->eval(e); m_calls += 2; // Compute integral using Simpson's rule for the left and right interval double h12 = h / 12.0; double Sleft = h12 * (fa + 4.0*fd + fc); double Sright = h12 * (fc + 4.0*fe + fb); double S2 = Sleft + Sright; // Allocate result double value; // If converged then compute the result ... if (std::abs(S2 - S) <= 15.0 * eps) { // if (std::abs(S2 - S) <= 15.0 * m_eps * std::abs(S2)) { value = S2 + (S2 - S)/15.0; } // ... else if the maximum recursion depth was reached then compute the // result and signal result invalidity else if (bottom <= 0) { value = S2 + (S2 - S)/15.0; m_isvalid = false; } // ... otherwise call this method recursively else { value = adaptive_simpson_aux(a, c, 0.5*eps, Sleft, fa, fc, fd, bottom-1) + adaptive_simpson_aux(c, b, 0.5*eps, Sright, fc, fb, fe, bottom-1); } // Return result return value; } //#define GSL_DBL_EPSILON 2.2204460492503131e-16 //#define GSL_DBL_MIN 2.2250738585072014e-308 /***********************************************************************//** * @brief Rescale errors for Gauss-Kronrod integration * * @param[in] err Error estimate. * @param[in] result_abs ???. * @param[in] result_asc ???. * @return Rescaled error estimate. ***************************************************************************/ double GIntegral::rescale_error(double err, const double& result_abs, const double& result_asc) const { // Take absolute value of error err = std::abs(err); // ... if (result_asc != 0.0 && err != 0.0) { double scale = std::pow((200.0 * err / result_asc), 1.5); if (scale < 1.0) { err = result_asc * scale ; } else { err = result_asc ; } } if (result_abs > 2.2250738585072014e-308 / (50.0 * 2.2204460492503131e-16)) { double min_err = 50.0 * 2.2204460492503131e-16 * result_abs; if (min_err > err) { err = min_err ; } } // Return error return err; }