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