SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/Integration - GslQuadAdaptive.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 56.2 % 16 9
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            Line data    Source code
       1            0 : // Distributed under the MIT License.
       2              : // See LICENSE.txt for details.
       3              : 
       4              : #pragma once
       5              : 
       6              : #include <gsl/gsl_integration.h>
       7              : #include <limits>
       8              : #include <memory>
       9              : #include <vector>
      10              : 
      11              : /// \cond
      12              : namespace PUP {
      13              : class er;
      14              : }  // namespace PUP
      15              : /// \endcond
      16              : 
      17              : /// \ingroup NumericalAlgorithmsGroup
      18              : /// Numerical integration algorithms
      19            1 : namespace integration {
      20              : 
      21              : namespace detail {
      22              : 
      23              : // The GSL functions require the integrand to have this particular function
      24              : // signature. In particular, any extra parameters to the functions must be
      25              : // passed as a void* and re-interpreted appropriately.
      26              : template <typename IntegrandType>
      27              : double integrand(const double x, void* const params) {
      28              :   // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
      29              :   auto* const function = reinterpret_cast<IntegrandType*>(params);
      30              :   return (*function)(x);
      31              : }
      32              : 
      33              : class GslQuadAdaptiveImpl {
      34              :  public:
      35              :   explicit GslQuadAdaptiveImpl(size_t max_intervals);
      36              : 
      37              :   GslQuadAdaptiveImpl() = default;
      38              :   GslQuadAdaptiveImpl(const GslQuadAdaptiveImpl&) = delete;
      39              :   GslQuadAdaptiveImpl& operator=(const GslQuadAdaptiveImpl&) = delete;
      40              :   GslQuadAdaptiveImpl(GslQuadAdaptiveImpl&&) = default;
      41              :   GslQuadAdaptiveImpl& operator=(GslQuadAdaptiveImpl&& rhs) = default;
      42              :   ~GslQuadAdaptiveImpl() = default;
      43              : 
      44              :   // NOLINTNEXTLINE(google-runtime-references)
      45              :   void pup(PUP::er& p);
      46              : 
      47              :  protected:
      48              :   template <typename IntegrandType>
      49              :   gsl_function make_gsl_integrand(IntegrandType& integrand) const {
      50              :     gsl_function gsl_integrand;
      51              :     gsl_integrand.function = &detail::integrand<IntegrandType>;
      52              :     gsl_integrand.params = &integrand;
      53              :     return gsl_integrand;
      54              :   }
      55              : 
      56              :   struct GslIntegrationWorkspaceDeleter {
      57              :     void operator()(gsl_integration_workspace* workspace) const;
      58              :   };
      59              : 
      60              :   size_t max_intervals_ = 0;
      61              :   std::unique_ptr<gsl_integration_workspace, GslIntegrationWorkspaceDeleter>
      62              :       workspace_{};
      63              : 
      64              :  private:
      65              :   void initialize();
      66              : };
      67              : 
      68              : void check_status_code(int status_code);
      69              : 
      70              : void disable_gsl_error_handling();
      71              : 
      72              : }  // namespace detail
      73              : 
      74              : /// Each type specifies which algorithm from GSL should be used. It should be
      75              : /// chosen according to the problem.
      76            1 : enum class GslIntegralType {
      77              :   /// gsl_integration_qag()
      78              :   StandardGaussKronrod,
      79              :   /// gsl_integration_qags()
      80              :   IntegrableSingularitiesPresent,
      81              :   /// gsl_integration_qagp()
      82              :   IntegrableSingularitiesKnown,
      83              :   /// gsl_integration_qagi()
      84              :   InfiniteInterval,
      85              :   /// gsl_integration_qagiu()
      86              :   UpperBoundaryInfinite,
      87              :   /// gsl_integration_qagil()
      88              :   LowerBoundaryInfinite
      89              : };
      90              : 
      91              : /*!
      92              :  * \brief A wrapper around the GSL adaptive integration procedures
      93              :  *
      94              :  * All templates take upper bounds to the absolute and relative error;
      95              :  * `tolerance_abs` and `tolerance_rel(default = 0.0)`, respectively. To compute
      96              :  * to a specified absolute error, set `tolerance_rel` to zero. To compute to a
      97              :  * specified relative error, set `tolerance_abs` to zero. For details on the
      98              :  * algorithm see the GSL documentation on `gsl_integration`.
      99              :  *
     100              :  * Here is an example how to use this class. For the function:
     101              :  *
     102              :  * \snippet Test_GslQuadAdaptive.cpp integrated_function
     103              :  *
     104              :  * the integration should look like:
     105              :  *
     106              :  * \snippet Test_GslQuadAdaptive.cpp integration_example
     107              :  */
     108              : template <GslIntegralType TheIntegralType>
     109            1 : class GslQuadAdaptive;
     110              : 
     111              : /*!
     112              :  * \brief Use Gauss-Kronrod rule to integrate a 1D-function
     113              :  *
     114              :  * The algorithm for "StandardGaussKronrod" uses the QAG algorithm to employ an
     115              :  * adaptive Gauss-Kronrod n-points integration rule. Its function takes a
     116              :  * `lower_boundary` and `upper_boundary` to the integration region, an upper
     117              :  * bound for the absolute error `tolerance_abs` and a `key`. The latter is an
     118              :  * index to the array [15, 21, 31, 41, 51, 61], where each element denotes how
     119              :  * many function evaluations take place in each subinterval. A higher-order rule
     120              :  * serves better for smooth functions, whereas a lower-order rule saves time for
     121              :  * functions with local difficulties, such as discontinuities.
     122              :  */
     123              : template <>
     124            1 : class GslQuadAdaptive<GslIntegralType::StandardGaussKronrod>
     125              :     : public detail::GslQuadAdaptiveImpl {
     126              :  public:
     127              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     128              :   template <typename IntegrandType>
     129            0 :   double operator()(IntegrandType&& integrand, const double lower_boundary,
     130              :                     const double upper_boundary, const double tolerance_abs,
     131              :                     const int key, const double tolerance_rel = 0.) const {
     132              :     double result = std::numeric_limits<double>::signaling_NaN();
     133              :     double error = std::numeric_limits<double>::signaling_NaN();
     134              :     auto gsl_integrand = make_gsl_integrand(integrand);
     135              :     detail::disable_gsl_error_handling();
     136              :     const int status_code =
     137              :         gsl_integration_qag(&gsl_integrand, lower_boundary, upper_boundary,
     138              :                             tolerance_abs, tolerance_rel, this->max_intervals_,
     139              :                             key, this->workspace_.get(), &result, &error);
     140              :     detail::check_status_code(status_code);
     141              :     return result;
     142              :   }
     143              : };
     144              : 
     145              : /*!
     146              :  * \brief Integrates a 1D-function with singularities
     147              :  *
     148              :  * The algorithm for "IntegrableSingularitiesPresent" concentrates new,
     149              :  * increasingly smaller subintervals around an unknown singularity and makes
     150              :  * successive approximations to the integral which should converge towards a
     151              :  * limit. The integration region is defined by `lower_boundary` and
     152              :  * `upper_boundary`.
     153              :  */
     154              : template <>
     155            1 : class GslQuadAdaptive<GslIntegralType::IntegrableSingularitiesPresent>
     156              :     : public detail::GslQuadAdaptiveImpl {
     157              :  public:
     158              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     159              :   template <typename IntegrandType>
     160            0 :   double operator()(IntegrandType&& integrand, const double lower_boundary,
     161              :                     const double upper_boundary, const double tolerance_abs,
     162              :                     const double tolerance_rel = 0.) const {
     163              :     double result = std::numeric_limits<double>::signaling_NaN();
     164              :     double error = std::numeric_limits<double>::signaling_NaN();
     165              :     auto gsl_integrand = make_gsl_integrand(integrand);
     166              :     detail::disable_gsl_error_handling();
     167              :     const int status_code =
     168              :         gsl_integration_qags(&gsl_integrand, lower_boundary, upper_boundary,
     169              :                              tolerance_abs, tolerance_rel, this->max_intervals_,
     170              :                              this->workspace_.get(), &result, &error);
     171              :     detail::check_status_code(status_code);
     172              :     return result;
     173              :   }
     174              : };
     175              : 
     176              : /*!
     177              :  * \brief Integrates a 1D-function where singularities are known
     178              :  *
     179              :  * The algorithm for "IntegrableSingularitiesKnown" uses user-defined
     180              :  * subintervals given by a vector of doubles `points`, where each element
     181              :  * denotes an interval boundary.
     182              :  */
     183              : template <>
     184            1 : class GslQuadAdaptive<GslIntegralType::IntegrableSingularitiesKnown>
     185              :     : public detail::GslQuadAdaptiveImpl {
     186              :  public:
     187              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     188              :   template <typename IntegrandType>
     189            0 :   double operator()(IntegrandType&& integrand,
     190              :                     const std::vector<double>& points,
     191              :                     const double tolerance_abs,
     192              :                     const double tolerance_rel = 0.) const {
     193              :     double result = std::numeric_limits<double>::signaling_NaN();
     194              :     double error = std::numeric_limits<double>::signaling_NaN();
     195              :     auto gsl_integrand = make_gsl_integrand(integrand);
     196              :     detail::disable_gsl_error_handling();
     197              :     // The const_cast is necessary because GSL has a weird interface where
     198              :     // the number of singularities does not change, but it doesn't take
     199              :     // the argument as a `const double*`. However, the first thing
     200              :     // `gsl_integration_qagp` does internally is forward to another function
     201              :     // that does take `points` by `const double*`. If `gsl_integration_qagp`
     202              :     // were to change the size of `points` this code would be severely broken
     203              :     // because `std::vector` allocates with `new`, while GSL would likely use
     204              :     // `malloc` (or some other C allocator). Mixing (de)allocators in such a way
     205              :     // is undefined behavior.
     206              :     const int status_code = gsl_integration_qagp(
     207              :         &gsl_integrand, const_cast<double*>(points.data()),  // NOLINT
     208              :         points.size(), tolerance_abs, tolerance_rel, this->max_intervals_,
     209              :         this->workspace_.get(), &result, &error);
     210              :     detail::check_status_code(status_code);
     211              :     return result;
     212              :   }
     213              : };
     214              : 
     215              : /*!
     216              :  * \brief Integrates a 1D-function over the interval \f$ (-\infty, +\infty) \f$
     217              :  *
     218              :  * The algorithm for "InfiniteInterval" uses the semi-open interval
     219              :  * \f$ (0, 1] \f$ to map to an infinite interval \f$ (-\infty, +\infty) \f$. Its
     220              :  * function takes no parameters other than limits `tolerance_abs` and optional
     221              :  * `tolerance_rel` for the absolute error and the relative error.
     222              :  */
     223              : template <>
     224            1 : class GslQuadAdaptive<GslIntegralType::InfiniteInterval>
     225              :     : public detail::GslQuadAdaptiveImpl {
     226              :  public:
     227              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     228              :   template <typename IntegrandType>
     229            0 :   double operator()(IntegrandType&& integrand, const double tolerance_abs,
     230              :                     const double tolerance_rel = 0.) const {
     231              :     double result = std::numeric_limits<double>::signaling_NaN();
     232              :     double error = std::numeric_limits<double>::signaling_NaN();
     233              :     auto gsl_integrand = make_gsl_integrand(integrand);
     234              :     detail::disable_gsl_error_handling();
     235              :     const int status_code = gsl_integration_qagi(
     236              :         &gsl_integrand, tolerance_abs, tolerance_rel, this->max_intervals_,
     237              :         this->workspace_.get(), &result, &error);
     238              :     detail::check_status_code(status_code);
     239              :     return result;
     240              :   }
     241              : };
     242              : 
     243              : /*!
     244              :  * \brief Integrates a 1D-function over the interval \f$ [a, +\infty) \f$
     245              :  *
     246              :  * The algorithm for "UpperBoundaryInfinite" maps the semi-open interval
     247              :  * \f$ (0, 1] \f$ to a semi-infinite interval \f$ [a, +\infty) \f$, where
     248              :  * \f$ a \f$ is given by `lower_boundary`.
     249              :  */
     250              : template <>
     251            1 : class GslQuadAdaptive<GslIntegralType::UpperBoundaryInfinite>
     252              :     : public detail::GslQuadAdaptiveImpl {
     253              :  public:
     254              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     255              :   template <typename IntegrandType>
     256            0 :   double operator()(IntegrandType&& integrand, const double lower_boundary,
     257              :                     const double tolerance_abs,
     258              :                     const double tolerance_rel = 0.) const {
     259              :     double result = std::numeric_limits<double>::signaling_NaN();
     260              :     double error = std::numeric_limits<double>::signaling_NaN();
     261              :     auto gsl_integrand = make_gsl_integrand(integrand);
     262              :     detail::disable_gsl_error_handling();
     263              :     const int status_code = gsl_integration_qagiu(
     264              :         &gsl_integrand, lower_boundary, tolerance_abs, tolerance_rel,
     265              :         this->max_intervals_, this->workspace_.get(), &result, &error);
     266              :     detail::check_status_code(status_code);
     267              :     return result;
     268              :   }
     269              : };
     270              : 
     271              : /*!
     272              :  * \brief Integrates a 1D-function over the interval \f$ (-\infty, b] \f$
     273              :  *
     274              :  * The algorithm for "LowerBoundaryInfinite" maps the semi-open interval
     275              :  * \f$ (0, 1] \f$ to a semi-infinite interval \f$ (-\infty, b] \f$, where
     276              :  * \f$ b \f$ is given by `upper_boundary`.
     277              :  */
     278              : template <>
     279            1 : class GslQuadAdaptive<GslIntegralType::LowerBoundaryInfinite>
     280              :     : public detail::GslQuadAdaptiveImpl {
     281              :  public:
     282              :   using detail::GslQuadAdaptiveImpl::GslQuadAdaptiveImpl;
     283              :   template <typename IntegrandType>
     284            0 :   double operator()(IntegrandType&& integrand, const double upper_boundary,
     285              :                     const double tolerance_abs,
     286              :                     const double tolerance_rel = 0.) const {
     287              :     double result = std::numeric_limits<double>::signaling_NaN();
     288              :     double error = std::numeric_limits<double>::signaling_NaN();
     289              :     auto gsl_integrand = make_gsl_integrand(integrand);
     290              :     detail::disable_gsl_error_handling();
     291              :     const int status_code = gsl_integration_qagil(
     292              :         &gsl_integrand, upper_boundary, tolerance_abs, tolerance_rel,
     293              :         this->max_intervals_, this->workspace_.get(), &result, &error);
     294              :     detail::check_status_code(status_code);
     295              :     return result;
     296              :   }
     297              : };
     298              : }  // namespace integration
        

Generated by: LCOV version 2.0-1