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 : }