SpECTRE Documentation Coverage Report
Current view: top level - Domain/Structure - ElementId.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 18 51 35.3 %
Date: 2025-12-05 05:03:31
Legend: Lines: hit not hit

          Line data    Source code
       1           1 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : /// \file
       5             : /// Defines class ElementId.
       6             : 
       7             : #pragma once
       8             : 
       9             : #include <array>
      10             : #include <cstddef>
      11             : #include <cstdint>
      12             : #include <functional>
      13             : #include <iosfwd>
      14             : #include <optional>
      15             : #include <string>
      16             : 
      17             : #include "Domain/Structure/Direction.hpp"
      18             : #include "Domain/Structure/SegmentId.hpp"
      19             : #include "Domain/Structure/Side.hpp"
      20             : 
      21             : /// \cond
      22             : namespace PUP {
      23             : class er;
      24             : }  // namespace PUP
      25             : /// \endcond
      26             : 
      27             : /*!
      28             :  * \ingroup ComputationalDomainGroup
      29             :  * \brief An ElementId uniquely labels an Element.
      30             :  *
      31             :  * It is constructed from the BlockId of the Block to which the Element belongs
      32             :  * and the VolumeDim SegmentIds that label the segments of the Block that the
      33             :  * Element covers. An optional `grid_index` identifies elements with the same
      34             :  * BlockId and SegmentIds across multiple grids.
      35             :  *
      36             :  * \details
      37             :  * The `ElementId` serves as an index that is compatible with Charm++ and
      38             :  * therefore must adhere to the restrictions imposed by Charm++. These are:
      39             :  * - `ElementId` must satisfy `std::is_pod`
      40             :  * - `ElementId` must not be larger than the size of three `int`s, i.e.
      41             :  *   `sizeof(ElementId) <= 3 * sizeof(int)`
      42             :  */
      43             : template <size_t VolumeDim>
      44           1 : class alignas(int[2]) ElementId {  // NOLINT(modernize-avoid-c-arrays)
      45             :  public:
      46             :   // We restrict the ElementId size to 64 bits for easy hashing into
      47             :   // size_t. This still allows us to have over 9 quadrillion elements, which is
      48             :   // probably enough. 2^45 * 2^8 9 quadrillion elements per grid index, with
      49             :   // up to 16 grid indices.
      50             :   //
      51             :   // Note: C++ populates bits from right to left in order of the
      52             :   // variables. This gives us the direction_mask we use below.
      53           0 :   static constexpr size_t block_id_bits = 8;
      54           0 :   static constexpr size_t grid_index_bits = 4;
      55           0 :   static constexpr size_t direction_bits = 4;
      56             :   /// The maximum allowed refinement level
      57           1 :   static constexpr size_t max_refinement_level = 15;
      58           0 :   static constexpr uint64_t direction_shift =
      59             :       static_cast<uint64_t>(block_id_bits + grid_index_bits);
      60           0 :   static constexpr uint64_t direction_mask = static_cast<uint64_t>(0b1111)
      61             :                                              << direction_shift;
      62             :   static_assert(block_id_bits + 3 * (1 + max_refinement_level) +
      63             :                         grid_index_bits + direction_bits ==
      64             :                     static_cast<size_t>(2 * 8) * sizeof(int),
      65             :                 "Bit representation requires padding or is too large");
      66             : 
      67           0 :   static constexpr size_t volume_dim = VolumeDim;
      68             : 
      69             :   /// Default constructor needed for Charm++ serialization.
      70           1 :   ElementId() = default;
      71           0 :   ElementId(const ElementId&) = default;
      72           0 :   ElementId& operator=(const ElementId&) = default;
      73           0 :   ElementId(ElementId&&) = default;
      74           0 :   ElementId& operator=(ElementId&&) = default;
      75           0 :   ~ElementId() = default;
      76             : 
      77             :   /// Create the ElementId of the root Element of a Block.
      78           1 :   explicit ElementId(size_t block_id, size_t grid_index = 0);
      79             : 
      80             :   /// Create an arbitrary ElementId.
      81           1 :   ElementId(size_t block_id,
      82             :             const std::array<SegmentId, VolumeDim>& segment_ids,
      83             :             size_t grid_index = 0);
      84             : 
      85             :   /// Create an ElementId from its string representation (see `operator<<`).
      86           1 :   explicit ElementId(const std::string& grid_name);
      87             : 
      88           0 :   ElementId<VolumeDim> id_of_child(size_t dim, Side side) const;
      89             : 
      90           0 :   ElementId<VolumeDim> id_of_parent(size_t dim) const;
      91             : 
      92           0 :   size_t block_id() const { return block_id_; }
      93             : 
      94           0 :   size_t grid_index() const { return grid_index_; }
      95             : 
      96           0 :   std::array<size_t, VolumeDim> refinement_levels() const;
      97             : 
      98           0 :   std::array<SegmentId, VolumeDim> segment_ids() const;
      99             : 
     100           0 :   SegmentId segment_id(size_t dim) const;
     101             : 
     102             :   /// Returns an ElementId meant for identifying data on external boundaries,
     103             :   /// which does not correspond to the Id of an actual element.
     104           1 :   static ElementId<VolumeDim> external_boundary_id();
     105             : 
     106             :   /// Returns the number of block boundaries the element has.
     107           1 :   size_t number_of_block_boundaries() const;
     108             : 
     109             :  protected:
     110             :   /// Create an `ElementId` in a specified direction.
     111           1 :   ElementId(const Direction<VolumeDim>& direction,
     112             :             const ElementId<VolumeDim>& element_id);
     113             : 
     114           0 :   Direction<VolumeDim> direction() const;
     115             : 
     116           0 :   ElementId without_direction() const;
     117             : 
     118             :  private:
     119             :   template <size_t Dim>
     120             :   // NOLINTNEXTLINE(readability-redundant-declaration)
     121           0 :   friend bool operator==(const ElementId<Dim>& lhs, const ElementId<Dim>& rhs);
     122             : 
     123             :   template <size_t Dim>
     124             :   // NOLINTNEXTLINE(readability-redundant-declaration)
     125           0 :   friend bool operator<(const ElementId<Dim>& lhs, const ElementId<Dim>& rhs);
     126             : 
     127             :   template <size_t Dim>
     128             :   // NOLINTNEXTLINE(readability-redundant-declaration)
     129           1 :   friend bool is_zeroth_element(const ElementId<Dim>& id,
     130             :                                 const std::optional<size_t>& grid_index);
     131             : 
     132           0 :   ElementId(uint8_t block_id, uint8_t grid_index, uint8_t direction,
     133             :             uint16_t compact_segment_id_xi, uint16_t compact_segment_id_eta,
     134             :             uint16_t compact_segment_id_zeta);
     135             : 
     136           0 :   uint8_t block_id_ : block_id_bits;
     137           0 :   uint8_t grid_index_ : grid_index_bits;
     138           0 :   uint8_t direction_ : direction_bits;  // end first 16 bits
     139             :   // each of the following is 16 bits in length
     140           0 :   uint16_t compact_segment_id_xi_ : max_refinement_level + 1;
     141           0 :   uint16_t compact_segment_id_eta_ : max_refinement_level + 1;
     142           0 :   uint16_t compact_segment_id_zeta_ : max_refinement_level + 1;
     143             : };
     144             : 
     145             : /// \cond
     146             : // clang-format off
     147             : // macro that generate the pup operator for SegmentId
     148             : PUPbytes(ElementId<1>)      // NOLINT
     149             : PUPbytes(ElementId<2>)  // NOLINT
     150             : PUPbytes(ElementId<3>)  // NOLINT
     151             : /// \endcond
     152             : 
     153             : /// Output operator for ElementId.
     154             : template <size_t VolumeDim>
     155           1 : std::ostream& operator<<(std::ostream& os, const ElementId<VolumeDim>& id);
     156             : // clang-format on
     157             : 
     158             : /// Equivalence operator for ElementId.
     159             : template <size_t VolumeDim>
     160             : bool operator==(const ElementId<VolumeDim>& lhs,
     161             :                 const ElementId<VolumeDim>& rhs);
     162             : 
     163             : /// Inequivalence operator for ElementId.
     164             : template <size_t VolumeDim>
     165             : bool operator!=(const ElementId<VolumeDim>& lhs,
     166             :                 const ElementId<VolumeDim>& rhs);
     167             : 
     168             : /// Define an ordering of element IDs first by grid index, then by block ID,
     169             : /// then by segment ID in each dimension in turn. In each dimension, segment IDs
     170             : /// are ordered first by refinement level (which will typically be the same when
     171             : /// comparing two element IDs), and second by index. There's no particular
     172             : /// reason for this choice of ordering. For applications such as distributing
     173             : /// elements among cores, orderings such as defined by
     174             : /// `domain::BlockZCurveProcDistribution` may be more appropriate.
     175             : template <size_t VolumeDim>
     176           1 : bool operator<(const ElementId<VolumeDim>& lhs,
     177             :                const ElementId<VolumeDim>& rhs);
     178             : 
     179             : template <size_t VolumeDim>
     180           0 : bool operator>(const ElementId<VolumeDim>& lhs,
     181             :                const ElementId<VolumeDim>& rhs) {
     182             :   return rhs < lhs;
     183             : }
     184             : template <size_t VolumeDim>
     185           0 : bool operator<=(const ElementId<VolumeDim>& lhs,
     186             :                 const ElementId<VolumeDim>& rhs) {
     187             :   return !(lhs > rhs);
     188             : }
     189             : template <size_t VolumeDim>
     190           0 : bool operator>=(const ElementId<VolumeDim>& lhs,
     191             :                 const ElementId<VolumeDim>& rhs) {
     192             :   return !(lhs < rhs);
     193             : }
     194             : 
     195             : /// \ingroup ComputationalDomainGroup
     196             : /// Check if two elements overlap, i.e., they are in the same block
     197             : /// and all their segments overlap.
     198             : template <size_t VolumeDim>
     199           1 : bool overlapping(const ElementId<VolumeDim>& a, const ElementId<VolumeDim>& b);
     200             : 
     201             : /// @{
     202             : /// \brief Returns a bool if the element is the zeroth element in the domain.
     203             : ///
     204             : /// \details An element is considered to be the zeroth element if its ElementId
     205             : /// `id` has
     206             : /// 1. id.block_id() == 0
     207             : /// 2. All id.segment_ids() have SegmentId.index() == 0
     208             : /// 3. If the argument `grid_index` is specified, id.grid_index() == grid_index.
     209             : ///
     210             : /// This means that the only element in a domain that this function will return
     211             : /// `true` for is the element in the lower corner of Block0 of that domain. The
     212             : /// `grid_index` will determine which domain is used for the comparison. During
     213             : /// evolutions, only one domain will be active at a time so it doesn't make
     214             : /// sense to compare the `grid_index`. However, during an elliptic solve
     215             : /// when there are multiple grids, this `grid_index` is useful for specifying
     216             : /// only one element over all domains.
     217             : ///
     218             : /// This function is useful if you need a unique element in the domain because
     219             : /// only one element in the whole domain can be the zeroth element.
     220             : ///
     221             : /// \parblock
     222             : /// \warning If you have multiple grids and you don't specify the `grid_index`
     223             : /// argument, this function will return `true` for one element in every grid
     224             : /// and thus can't be used to determine a unique element in a simulation; only a
     225             : /// unique element in each grid.
     226             : /// \endparblock
     227             : /// \parblock
     228             : /// \warning If the domain is re-gridded, a different ElementId may represent
     229             : /// the zeroth element.
     230             : /// \endparblock
     231             : template <size_t Dim>
     232           1 : bool is_zeroth_element(const ElementId<Dim>& id,
     233             :                        const std::optional<size_t>& grid_index);
     234             : 
     235             : // This overload is added (instead of adding a default value for grid_index)
     236             : // in order to avoid adding DomainStructures as a dependency of Parallel
     237             : // by using a forward declaration in Parallel/DistributedObject.hpp
     238             : template <size_t Dim>
     239           1 : bool is_zeroth_element(const ElementId<Dim>& id);
     240             : /// @}
     241             : // ######################################################################
     242             : // INLINE DEFINITIONS
     243             : // ######################################################################
     244             : 
     245             : template <size_t VolumeDim>
     246           0 : size_t hash_value(const ElementId<VolumeDim>& id);
     247             : 
     248             : // NOLINTNEXTLINE(cert-dcl58-cpp)
     249             : namespace std {
     250             : template <size_t VolumeDim>
     251             : struct hash<ElementId<VolumeDim>> {
     252             :   size_t operator()(const ElementId<VolumeDim>& id) const;
     253             : };
     254             : }  // namespace std
     255             : 
     256             : template <size_t VolumeDim>
     257           1 : inline bool operator==(const ElementId<VolumeDim>& lhs,
     258             :                        const ElementId<VolumeDim>& rhs) {
     259             :   // Note: Direction is intentionally skipped.
     260             :   return lhs.block_id_ == rhs.block_id_ and
     261             :          lhs.grid_index_ == rhs.grid_index_ and
     262             :          lhs.compact_segment_id_xi_ == rhs.compact_segment_id_xi_ and
     263             :          lhs.compact_segment_id_eta_ == rhs.compact_segment_id_eta_ and
     264             :          lhs.compact_segment_id_zeta_ == rhs.compact_segment_id_zeta_;
     265             : }
     266             : 
     267             : template <size_t VolumeDim>
     268           1 : inline bool operator!=(const ElementId<VolumeDim>& lhs,
     269             :                        const ElementId<VolumeDim>& rhs) {
     270             :   return not(lhs == rhs);
     271             : }

Generated by: LCOV version 1.14