SpECTRE Documentation Coverage Report
Current view: top level - NumericalAlgorithms/Integration - GslQuadAdaptive.hpp Hit Total Coverage
Commit: 058fd9f3a53606b32c6beec17aafdb5fcf4268be Lines: 9 16 56.2 %
Date: 2024-04-27 02:05:51
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 1.14