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 : /*! 107 : * \brief Returns a compact ID containing only the segment ID bits, 108 : * with block_id, grid_index, and direction bits stripped. 109 : * 110 : * This is useful for identifying elements in visualization tools 111 : * like ParaView. 112 : */ 113 1 : size_t to_short_id() const; 114 : 115 : /// Returns the number of block boundaries the element has. 116 1 : size_t number_of_block_boundaries() const; 117 : 118 : protected: 119 : /// Create an `ElementId` in a specified direction. 120 1 : ElementId(const Direction<VolumeDim>& direction, 121 : const ElementId<VolumeDim>& element_id); 122 : 123 0 : Direction<VolumeDim> direction() const; 124 : 125 0 : ElementId without_direction() const; 126 : 127 : private: 128 : template <size_t Dim> 129 : // NOLINTNEXTLINE(readability-redundant-declaration) 130 0 : friend bool operator==(const ElementId<Dim>& lhs, const ElementId<Dim>& rhs); 131 : 132 : template <size_t Dim> 133 : // NOLINTNEXTLINE(readability-redundant-declaration) 134 0 : friend bool operator<(const ElementId<Dim>& lhs, const ElementId<Dim>& rhs); 135 : 136 : template <size_t Dim> 137 : // NOLINTNEXTLINE(readability-redundant-declaration) 138 1 : friend bool is_zeroth_element(const ElementId<Dim>& id, 139 : const std::optional<size_t>& grid_index); 140 : 141 0 : ElementId(uint8_t block_id, uint8_t grid_index, uint8_t direction, 142 : uint16_t compact_segment_id_xi, uint16_t compact_segment_id_eta, 143 : uint16_t compact_segment_id_zeta); 144 : 145 0 : uint8_t block_id_ : block_id_bits; 146 0 : uint8_t grid_index_ : grid_index_bits; 147 0 : uint8_t direction_ : direction_bits; // end first 16 bits 148 : // each of the following is 16 bits in length 149 0 : uint16_t compact_segment_id_xi_ : max_refinement_level + 1; 150 0 : uint16_t compact_segment_id_eta_ : max_refinement_level + 1; 151 0 : uint16_t compact_segment_id_zeta_ : max_refinement_level + 1; 152 : }; 153 : 154 : /// \cond 155 : // clang-format off 156 : // macro that generate the pup operator for SegmentId 157 : PUPbytes(ElementId<1>) // NOLINT 158 : PUPbytes(ElementId<2>) // NOLINT 159 : PUPbytes(ElementId<3>) // NOLINT 160 : /// \endcond 161 : 162 : /// Output operator for ElementId. 163 : template <size_t VolumeDim> 164 1 : std::ostream& operator<<(std::ostream& os, const ElementId<VolumeDim>& id); 165 : // clang-format on 166 : 167 : /// Equivalence operator for ElementId. 168 : template <size_t VolumeDim> 169 : bool operator==(const ElementId<VolumeDim>& lhs, 170 : const ElementId<VolumeDim>& rhs); 171 : 172 : /// Inequivalence operator for ElementId. 173 : template <size_t VolumeDim> 174 : bool operator!=(const ElementId<VolumeDim>& lhs, 175 : const ElementId<VolumeDim>& rhs); 176 : 177 : /// Define an ordering of element IDs first by grid index, then by block ID, 178 : /// then by segment ID in each dimension in turn. In each dimension, segment IDs 179 : /// are ordered first by refinement level (which will typically be the same when 180 : /// comparing two element IDs), and second by index. There's no particular 181 : /// reason for this choice of ordering. For applications such as distributing 182 : /// elements among cores, orderings such as defined by 183 : /// `domain::BlockZCurveProcDistribution` may be more appropriate. 184 : template <size_t VolumeDim> 185 1 : bool operator<(const ElementId<VolumeDim>& lhs, 186 : const ElementId<VolumeDim>& rhs); 187 : 188 : template <size_t VolumeDim> 189 0 : bool operator>(const ElementId<VolumeDim>& lhs, 190 : const ElementId<VolumeDim>& rhs) { 191 : return rhs < lhs; 192 : } 193 : template <size_t VolumeDim> 194 0 : bool operator<=(const ElementId<VolumeDim>& lhs, 195 : const ElementId<VolumeDim>& rhs) { 196 : return !(lhs > rhs); 197 : } 198 : template <size_t VolumeDim> 199 0 : bool operator>=(const ElementId<VolumeDim>& lhs, 200 : const ElementId<VolumeDim>& rhs) { 201 : return !(lhs < rhs); 202 : } 203 : 204 : /// \ingroup ComputationalDomainGroup 205 : /// Check if two elements overlap, i.e., they are in the same block 206 : /// and all their segments overlap. 207 : template <size_t VolumeDim> 208 1 : bool overlapping(const ElementId<VolumeDim>& a, const ElementId<VolumeDim>& b); 209 : 210 : /// @{ 211 : /// \brief Returns a bool if the element is the zeroth element in the domain. 212 : /// 213 : /// \details An element is considered to be the zeroth element if its ElementId 214 : /// `id` has 215 : /// 1. id.block_id() == 0 216 : /// 2. All id.segment_ids() have SegmentId.index() == 0 217 : /// 3. If the argument `grid_index` is specified, id.grid_index() == grid_index. 218 : /// 219 : /// This means that the only element in a domain that this function will return 220 : /// `true` for is the element in the lower corner of Block0 of that domain. The 221 : /// `grid_index` will determine which domain is used for the comparison. During 222 : /// evolutions, only one domain will be active at a time so it doesn't make 223 : /// sense to compare the `grid_index`. However, during an elliptic solve 224 : /// when there are multiple grids, this `grid_index` is useful for specifying 225 : /// only one element over all domains. 226 : /// 227 : /// This function is useful if you need a unique element in the domain because 228 : /// only one element in the whole domain can be the zeroth element. 229 : /// 230 : /// \parblock 231 : /// \warning If you have multiple grids and you don't specify the `grid_index` 232 : /// argument, this function will return `true` for one element in every grid 233 : /// and thus can't be used to determine a unique element in a simulation; only a 234 : /// unique element in each grid. 235 : /// \endparblock 236 : /// \parblock 237 : /// \warning If the domain is re-gridded, a different ElementId may represent 238 : /// the zeroth element. 239 : /// \endparblock 240 : template <size_t Dim> 241 1 : bool is_zeroth_element(const ElementId<Dim>& id, 242 : const std::optional<size_t>& grid_index); 243 : 244 : // This overload is added (instead of adding a default value for grid_index) 245 : // in order to avoid adding DomainStructures as a dependency of Parallel 246 : // by using a forward declaration in Parallel/DistributedObject.hpp 247 : template <size_t Dim> 248 1 : bool is_zeroth_element(const ElementId<Dim>& id); 249 : /// @} 250 : // ###################################################################### 251 : // INLINE DEFINITIONS 252 : // ###################################################################### 253 : 254 : template <size_t VolumeDim> 255 0 : size_t hash_value(const ElementId<VolumeDim>& id); 256 : 257 : // NOLINTNEXTLINE(cert-dcl58-cpp) 258 : namespace std { 259 : template <size_t VolumeDim> 260 : struct hash<ElementId<VolumeDim>> { 261 : size_t operator()(const ElementId<VolumeDim>& id) const; 262 : }; 263 : } // namespace std 264 : 265 : template <size_t VolumeDim> 266 1 : inline bool operator==(const ElementId<VolumeDim>& lhs, 267 : const ElementId<VolumeDim>& rhs) { 268 : // Note: Direction is intentionally skipped. 269 : return lhs.block_id_ == rhs.block_id_ and 270 : lhs.grid_index_ == rhs.grid_index_ and 271 : lhs.compact_segment_id_xi_ == rhs.compact_segment_id_xi_ and 272 : lhs.compact_segment_id_eta_ == rhs.compact_segment_id_eta_ and 273 : lhs.compact_segment_id_zeta_ == rhs.compact_segment_id_zeta_; 274 : } 275 : 276 : template <size_t VolumeDim> 277 1 : inline bool operator!=(const ElementId<VolumeDim>& lhs, 278 : const ElementId<VolumeDim>& rhs) { 279 : return not(lhs == rhs); 280 : }