SpECTRE Documentation Coverage Report
Current view: top level - Utilities - StaticCache.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 4 23 17.4 %
Date: 2025-12-05 05:03:31
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 <array>
       7             : #include <cstddef>
       8             : #include <utility>
       9             : 
      10             : #include "Utilities/ErrorHandling/Assert.hpp"
      11             : #include "Utilities/Gsl.hpp"
      12             : #include "Utilities/Requires.hpp"
      13             : #include "Utilities/TMPL.hpp"
      14             : #include "Utilities/TypeTraits/IsInteger.hpp"
      15             : 
      16             : /// \ingroup UtilitiesGroup
      17             : /// Range of integral values for StaticCache indices.  The `Start` is inclusive
      18             : /// and the `End` is exclusive.  The range must not be empty.
      19             : template <auto Start, auto End>
      20           1 : struct CacheRange {
      21             :   static_assert(std::is_same_v<decltype(Start), decltype(End)>);
      22             :   static_assert(Start < End, "CacheRange must include at least one value");
      23           0 :   constexpr static auto start = Start;
      24           0 :   constexpr static auto end = End;
      25           0 :   constexpr static auto size = end - start;
      26           0 :   using value_type = std::remove_cv_t<decltype(start)>;
      27             : };
      28             : 
      29             : /// \ingroup UtilitiesGroup
      30             : /// Possible enumeration values for the StaticCache. Only values specified here
      31             : /// are retrievable.
      32             : ///
      33             : /// \note The `EnumerationType` must be streamable.
      34             : template <typename EnumerationType, EnumerationType... Enums>
      35           1 : struct CacheEnumeration {
      36           0 :   constexpr static size_t size = sizeof...(Enums);
      37           0 :   using value_type = EnumerationType;
      38           0 :   static constexpr std::array<value_type, size> values{Enums...};
      39           0 :   using value_list = tmpl::integral_list<EnumerationType, Enums...>;
      40             : };
      41             : 
      42             : /// \ingroup UtilitiesGroup
      43             : /// A cache of objects intended to be stored in a static variable.
      44             : ///
      45             : /// Objects can be accessed via a combination of several `size_t` and `enum`
      46             : /// arguments. The range of each integral argument is specified via a template
      47             : /// parameter of type `CacheRange<start, end>`, giving the first and
      48             : /// one-past-last values for the range. Each `enum` argument is specified by a
      49             : /// template parameter of type `CacheEnumeration<EnumerationType, Members...>`
      50             : /// giving the enumeration type and an explicit set of every enum member to be
      51             : /// cached.
      52             : ///
      53             : /// \example
      54             : /// A cache with only numeric indices:
      55             : /// \snippet Test_StaticCache.cpp static_cache
      56             : ///
      57             : /// \example
      58             : /// A cache with enumeration indices:
      59             : /// \snippet Test_StaticCache.cpp static_cache_with_enum
      60             : ///
      61             : /// \example
      62             : /// A cache with mixed numeric and enumeration indices:
      63             : /// \snippet Test_StaticCache.cpp static_cache_with_enum_and_numeric
      64             : ///
      65             : /// \example
      66             : /// A cache with no arguments at all (caching only a single object)
      67             : /// \snippet Test_StaticCache.cpp static_cache_no_args
      68             : ///
      69             : /// \see make_static_cache
      70             : ///
      71             : /// \tparam T type held in the cache
      72             : /// \tparam Ranges ranges of valid indices
      73             : template <typename Generator, typename T, typename... Ranges>
      74           1 : class StaticCache {
      75             :  public:
      76             :   template <typename Gen>
      77             :   // NOLINTNEXTLINE(bugprone-forwarding-reference-overload)
      78           0 :   explicit StaticCache(Gen&& generator)
      79             :       : generator_{std::forward<Gen>(generator)} {}
      80             : 
      81             :   template <typename... Args>
      82           0 :   const T& operator()(const Args... parameters) const {
      83             :     static_assert(sizeof...(parameters) == sizeof...(Ranges),
      84             :                   "Number of arguments must match number of ranges.");
      85             :     return unwrap_cache_combined(generate_tuple<Ranges>(parameters)...);
      86             :   }
      87             : 
      88             :  private:
      89             :   template <typename Range, typename T1>
      90           0 :   auto generate_tuple(const T1 parameter) const {
      91             :     if constexpr (std::is_enum<T1>::value) {
      92             :       static_assert(
      93             :           std::is_same<typename Range::value_type, std::remove_cv_t<T1>>::value,
      94             :           "Mismatched enum parameter type and cached type.");
      95             :       size_t array_location = std::numeric_limits<size_t>::max();
      96             :       static const std::array<typename Range::value_type, Range::size> values{
      97             :           Range::values};
      98             :       for (size_t i = 0; i < Range::size; ++i) {
      99             :         if (parameter == gsl::at(values, i)) {
     100             :           array_location = i;
     101             :           break;
     102             :         }
     103             :       }
     104             :       if (UNLIKELY(array_location == std::numeric_limits<size_t>::max())) {
     105             :         ERROR("Uncached enumeration value: " << parameter);
     106             :       }
     107             :       return std::tuple{array_location, typename Range::value_list{}};
     108             :     } else {
     109             :       static_assert(
     110             :           tt::is_integer_v<std::remove_cv_t<T1>>,
     111             :           "The parameter passed for a CacheRange must be an integer type.");
     112             : 
     113             :       // Check range here because the nested range checks in the unwrap_cache
     114             :       // function cause significant compile time overhead.
     115             :       if (UNLIKELY(Range::start >
     116             :                        static_cast<decltype(Range::start)>(parameter) or
     117             :                    static_cast<decltype(Range::start)>(parameter) >=
     118             :                        Range::start +
     119             :                            static_cast<decltype(Range::start)>(Range::size))) {
     120             :         ERROR("Index out of range: "
     121             :               << Range::start << " <= " << parameter << " < "
     122             :               << Range::start +
     123             :                      static_cast<decltype(Range::start)>(Range::size));
     124             :       }
     125             :       return std::tuple{
     126             :           // unsigned cast is safe since this is an index into an array
     127             :           static_cast<size_t>(
     128             :               static_cast<typename Range::value_type>(parameter) -
     129             :               Range::start),
     130             :           tmpl::make_sequence<
     131             :               tmpl::integral_constant<typename Range::value_type, Range::start>,
     132             :               Range::size>{}};
     133             :     }
     134             :   }
     135             : 
     136             :   // Compilation time notes:
     137             :   //
     138             :   // - The separate peeling of different number of arguments is the
     139             :   //   fastest implementation Nils Deppe has found so far.
     140             :   // - The second fastest is using a Cartesian product on the lists of
     141             :   //   possible values, followed by a for_each over that list to set the
     142             :   //   function pointers in the array. Note that having the for_each function
     143             :   //   be marked `constexpr` resulted in a 6x reduction in compilation time
     144             :   //   for clang 17 compared to the not constexpr version, but still 40%
     145             :   //   slower compilation compared to the pattern matching below.
     146             :   template <typename... IntegralConstantValues>
     147           0 :   const T& unwrap_cache_combined() const {
     148             :     static const T cached_object = generator_(IntegralConstantValues::value...);
     149             :     return cached_object;
     150             :   }
     151             : 
     152             :   // NVCC v12.6 can't handle the unwrapping code below, so we use a simpler
     153             :   // version for CUDA. Note that we don't actually need to use StaticCache on
     154             :   // device at all, so the main issue with NVCC is that it tries to compile this
     155             :   // code for CUDA at all.
     156             : #if defined(__NVCC__) && defined(__CUDA_ARCH__)
     157             :   template <typename... IntegralConstantValues, typename... IntegralConstants,
     158             :             typename... Args>
     159             :   const T& unwrap_cache_combined(
     160             :       std::tuple<size_t, tmpl::list<IntegralConstants...>> parameter0,
     161             :       Args... parameters) const {
     162             :     // note that the act of assigning to the specified function pointer type
     163             :     // fixes the template arguments that need to be inferred.
     164             :     static const std::array<
     165             :         const T& (StaticCache<Generator, T, Ranges...>::*)(Args...) const,
     166             :         sizeof...(IntegralConstants)>
     167             :         cache{{&StaticCache<Generator, T, Ranges...>::unwrap_cache_combined<
     168             :             IntegralConstantValues..., IntegralConstants>...}};
     169             :     // The array `cache` holds pointers to member functions, so we dereference
     170             :     // the pointer and invoke it on `this`.
     171             :     return (this->*gsl::at(cache, std::get<0>(parameter0)))(parameters...);
     172             :   }
     173             : #else  // defined(__NVCC__) && defined(__CUDA_ARCH__)
     174             :   template <typename... IntegralConstantValues, typename... IntegralConstants>
     175           0 :   const T& unwrap_cache_combined(
     176             :       std::tuple<size_t, tmpl::list<IntegralConstants...>> parameter0) const {
     177             :     // note that the act of assigning to the specified function pointer type
     178             :     // fixes the template arguments that need to be inferred.
     179             :     static const std::array<const T& (StaticCache::*)() const,
     180             :                             sizeof...(IntegralConstants)>
     181             :         cache{{&StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     182             :                                                    IntegralConstants>...}};
     183             :     // The array `cache` holds pointers to member functions, so we dereference
     184             :     // the pointer and invoke it on `this`.
     185             :     return (this->*gsl::at(cache, std::get<0>(parameter0)))();
     186             :   }
     187             : 
     188             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     189             :             typename... IntegralConstants1>
     190           0 :   const T& unwrap_cache_combined(
     191             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     192             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1) const {
     193             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     194             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     195             :     constexpr size_t total_size = num0 * num1;
     196             :     // note that the act of assigning to the specified function pointer type
     197             :     // fixes the template arguments that need to be inferred.
     198             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     199             :         cache = []() {
     200             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     201             :           size_t counter1 = 0;
     202             :           const auto helper1 = [&counter1,
     203             :                                 &result]<typename IntegralConstant1>() {
     204             :             size_t counter0 = 0;
     205             :             const auto helper0 = [&counter0, &counter1,
     206             :                                   &result]<typename IntegralConstant0>() {
     207             :               result[counter0 + num0 * counter1] =
     208             :                   &StaticCache::unwrap_cache_combined<IntegralConstantValues...,
     209             :                                                       IntegralConstant0,
     210             :                                                       IntegralConstant1>;
     211             :               ++counter0;
     212             :             };
     213             :             EXPAND_PACK_LEFT_TO_RIGHT(
     214             :                 helper0.template operator()<IntegralConstants0>());
     215             :             ++counter1;
     216             :           };
     217             :           EXPAND_PACK_LEFT_TO_RIGHT(
     218             :               helper1.template operator()<IntegralConstants1>());
     219             :           return result;
     220             :         }();
     221             : 
     222             :     // The array `cache` holds pointers to member functions, so we dereference
     223             :     // the pointer and invoke it on `this`.
     224             :     return (this->*gsl::at(cache, std::get<0>(parameter0) +
     225             :                                       num0 * std::get<0>(parameter1)))();
     226             :   }
     227             : 
     228             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     229             :             typename... IntegralConstants1, typename... IntegralConstants2>
     230           0 :   const T& unwrap_cache_combined(
     231             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     232             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     233             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2) const {
     234             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     235             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     236             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     237             :     constexpr size_t total_size = num0 * num1 * num2;
     238             :     // note that the act of assigning to the specified function pointer type
     239             :     // fixes the template arguments that need to be inferred.
     240             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     241             :         cache = []() {
     242             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     243             :           size_t counter2 = 0;
     244             :           const auto helper2 = [&counter2,
     245             :                                 &result]<typename IntegralConstant2>() {
     246             :             size_t counter1 = 0;
     247             :             const auto helper1 = [&counter1, &counter2,
     248             :                                   &result]<typename IntegralConstant1>() {
     249             :               size_t counter0 = 0;
     250             :               const auto helper0 = [&counter0, &counter1, &counter2,
     251             :                                     &result]<typename IntegralConstant0>() {
     252             :                 result[counter0 + num0 * (counter1 + num1 * counter2)] =
     253             :                     &StaticCache::unwrap_cache_combined<
     254             :                         IntegralConstantValues..., IntegralConstant0,
     255             :                         IntegralConstant1, IntegralConstant2>;
     256             :                 ++counter0;
     257             :               };
     258             :               EXPAND_PACK_LEFT_TO_RIGHT(
     259             :                   helper0.template operator()<IntegralConstants0>());
     260             :               ++counter1;
     261             :             };
     262             :             EXPAND_PACK_LEFT_TO_RIGHT(
     263             :                 helper1.template operator()<IntegralConstants1>());
     264             :             ++counter2;
     265             :           };
     266             :           EXPAND_PACK_LEFT_TO_RIGHT(
     267             :               helper2.template operator()<IntegralConstants2>());
     268             :           return result;
     269             :         }();
     270             : 
     271             :     // The array `cache` holds pointers to member functions, so we dereference
     272             :     // the pointer and invoke it on `this`.
     273             :     return (
     274             :         this->*gsl::at(cache, std::get<0>(parameter0) +
     275             :                                   num0 * (std::get<0>(parameter1) +
     276             :                                           num1 * std::get<0>(parameter2))))();
     277             :   }
     278             : 
     279             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     280             :             typename... IntegralConstants1, typename... IntegralConstants2,
     281             :             typename... IntegralConstants3>
     282           0 :   const T& unwrap_cache_combined(
     283             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     284             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     285             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     286             :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3) const {
     287             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     288             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     289             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     290             :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     291             :     constexpr size_t total_size = num0 * num1 * num2 * num3;
     292             :     // note that the act of assigning to the specified function pointer type
     293             :     // fixes the template arguments that need to be inferred.
     294             :     static const std::array<const T& (StaticCache::*)() const, total_size>
     295             :         cache = []() {
     296             :           std::array<const T& (StaticCache::*)() const, total_size> result;
     297             :           size_t counter3 = 0;
     298             :           const auto helper3 = [&counter3,
     299             :                                 &result]<typename IntegralConstant3>() {
     300             :             size_t counter2 = 0;
     301             :             const auto helper2 = [&counter2, &counter3,
     302             :                                   &result]<typename IntegralConstant2>() {
     303             :               size_t counter1 = 0;
     304             :               const auto helper1 = [&counter1, &counter2, &counter3,
     305             :                                     &result]<typename IntegralConstant1>() {
     306             :                 size_t counter0 = 0;
     307             :                 const auto helper0 = [&counter0, &counter1, &counter2,
     308             :                                       &counter3,
     309             :                                       &result]<typename IntegralConstant0>() {
     310             :                   result[counter0 +
     311             :                          num0 *
     312             :                              (counter1 + num1 * (counter2 + num2 * counter3))] =
     313             :                       &StaticCache::unwrap_cache_combined<
     314             :                           IntegralConstantValues..., IntegralConstant0,
     315             :                           IntegralConstant1, IntegralConstant2,
     316             :                           IntegralConstant3>;
     317             :                   ++counter0;
     318             :                 };
     319             :                 EXPAND_PACK_LEFT_TO_RIGHT(
     320             :                     helper0.template operator()<IntegralConstants0>());
     321             :                 ++counter1;
     322             :               };
     323             :               EXPAND_PACK_LEFT_TO_RIGHT(
     324             :                   helper1.template operator()<IntegralConstants1>());
     325             :               ++counter2;
     326             :             };
     327             :             EXPAND_PACK_LEFT_TO_RIGHT(
     328             :                 helper2.template operator()<IntegralConstants2>());
     329             :             ++counter3;
     330             :           };
     331             :           EXPAND_PACK_LEFT_TO_RIGHT(
     332             :               helper3.template operator()<IntegralConstants3>());
     333             :           return result;
     334             :         }();
     335             : 
     336             :     // The array `cache` holds pointers to member functions, so we
     337             :     // dereference the pointer and invoke it on `this`.
     338             :     return (
     339             :         this->*gsl::at(cache,
     340             :                        std::get<0>(parameter0) +
     341             :                            num0 * (std::get<0>(parameter1) +
     342             :                                    num1 * (std::get<0>(parameter2) +
     343             :                                            num2 * std::get<0>(parameter3)))))();
     344             :   }
     345             : 
     346             :   template <typename... IntegralConstantValues, typename... IntegralConstants0,
     347             :             typename... IntegralConstants1, typename... IntegralConstants2,
     348             :             typename... IntegralConstants3, typename... IntegralConstants4,
     349             :             typename... Args>
     350           0 :   const T& unwrap_cache_combined(
     351             :       std::tuple<size_t, tmpl::list<IntegralConstants0...>> parameter0,
     352             :       std::tuple<size_t, tmpl::list<IntegralConstants1...>> parameter1,
     353             :       std::tuple<size_t, tmpl::list<IntegralConstants2...>> parameter2,
     354             :       std::tuple<size_t, tmpl::list<IntegralConstants3...>> parameter3,
     355             :       std::tuple<size_t, tmpl::list<IntegralConstants4...>> parameter4,
     356             :       const Args&... parameters) const {
     357             :     constexpr size_t num0 = sizeof...(IntegralConstants0);
     358             :     constexpr size_t num1 = sizeof...(IntegralConstants1);
     359             :     constexpr size_t num2 = sizeof...(IntegralConstants2);
     360             :     constexpr size_t num3 = sizeof...(IntegralConstants3);
     361             :     constexpr size_t num4 = sizeof...(IntegralConstants4);
     362             :     constexpr size_t total_size = num0 * num1 * num2 * num3 * num4;
     363             :     // note that the act of assigning to the specified function pointer type
     364             :     // fixes the template arguments that need to be inferred.
     365             :     static const std::array<const T& (StaticCache::*)(Args...) const,
     366             :                             total_size>
     367             :         cache = []() {
     368             :           std::array<const T& (StaticCache::*)(Args...) const, total_size>
     369             :               result;
     370             :           size_t counter4 = 0;
     371             :           const auto helper4 = [&counter4,
     372             :                                 &result]<typename IntegralConstant4>() {
     373             :             size_t counter3 = 0;
     374             :             const auto helper3 = [&counter3, &counter4,
     375             :                                   &result]<typename IntegralConstant3>() {
     376             :               size_t counter2 = 0;
     377             :               const auto helper2 = [&counter2, &counter3, &counter4,
     378             :                                     &result]<typename IntegralConstant2>() {
     379             :                 size_t counter1 = 0;
     380             :                 const auto helper1 = [&counter1, &counter2, &counter3,
     381             :                                       &counter4,
     382             :                                       &result]<typename IntegralConstant1>() {
     383             :                   size_t counter0 = 0;
     384             :                   const auto helper0 = [&counter0, &counter1, &counter2,
     385             :                                         &counter3, &counter4,
     386             :                                         &result]<typename IntegralConstant0>() {
     387             :                     result[counter0 +
     388             :                            num0 *
     389             :                                (counter1 +
     390             :                                 num1 * (counter2 +
     391             :                                         num2 * (counter3 + num3 * counter4)))] =
     392             :                         &StaticCache::unwrap_cache_combined<
     393             :                             IntegralConstantValues..., IntegralConstant0,
     394             :                             IntegralConstant1, IntegralConstant2,
     395             :                             IntegralConstant3, IntegralConstant4>;
     396             :                     ++counter0;
     397             :                   };
     398             :                   EXPAND_PACK_LEFT_TO_RIGHT(
     399             :                       helper0.template operator()<IntegralConstants0>());
     400             :                   ++counter1;
     401             :                 };
     402             :                 EXPAND_PACK_LEFT_TO_RIGHT(
     403             :                     helper1.template operator()<IntegralConstants1>());
     404             :                 ++counter2;
     405             :               };
     406             :               EXPAND_PACK_LEFT_TO_RIGHT(
     407             :                   helper2.template operator()<IntegralConstants2>());
     408             :               ++counter3;
     409             :             };
     410             :             EXPAND_PACK_LEFT_TO_RIGHT(
     411             :                 helper3.template operator()<IntegralConstants3>());
     412             :             ++counter4;
     413             :           };
     414             :           EXPAND_PACK_LEFT_TO_RIGHT(
     415             :               helper4.template operator()<IntegralConstants4>());
     416             :           return result;
     417             :         }();
     418             : 
     419             :     // The array `cache` holds pointers to member functions, so we dereference
     420             :     // the pointer and invoke it on `this`.
     421             :     return (
     422             :         this->*gsl::at(
     423             :                    cache,
     424             :                    std::get<0>(parameter0) +
     425             :                        num0 *
     426             :                            (std::get<0>(parameter1) +
     427             :                             num1 * (std::get<0>(parameter2) +
     428             :                                     num2 * (std::get<0>(parameter3) +
     429             :                                             num3 * std::get<0>(parameter4))))))(
     430             :         parameters...);
     431             :   }
     432             : #endif  // defined(__NVCC__) && defined(__CUDA_ARCH__)
     433             : 
     434           0 :   const Generator generator_;
     435             : };
     436             : 
     437             : /// \ingroup UtilitiesGroup
     438             : /// Create a StaticCache, inferring the cached type from the generator.
     439             : template <typename... Ranges, typename Generator>
     440           1 : auto make_static_cache(Generator&& generator) {
     441             :   using CachedType = std::remove_cv_t<decltype(generator(
     442             :       std::declval<typename Ranges::value_type>()...))>;
     443             :   return StaticCache<std::remove_cv_t<Generator>, CachedType, Ranges...>(
     444             :       std::forward<Generator>(generator));
     445             : }

Generated by: LCOV version 1.14