Branch data Line data Source code
1 : : //
2 : : // Class FieldLayout
3 : : // FieldLayout describes how a given index space (represented by an NDIndex
4 : : // object) is distributed among MPI ranks. It performs the initial
5 : : // partitioning. The user may request that a particular dimension not be
6 : : // partitioned by flagging that axis as 'SERIAL' (instead of 'PARALLEL').
7 : : //
8 : : #ifndef IPPL_FIELD_LAYOUT_H
9 : : #define IPPL_FIELD_LAYOUT_H
10 : :
11 : : #include <array>
12 : : #include <iostream>
13 : : #include <map>
14 : : #include <vector>
15 : :
16 : : #include "Types/ViewTypes.h"
17 : :
18 : : #include "Communicate/Communicator.h"
19 : : #include "Index/NDIndex.h"
20 : : #include "Partition/Partitioner.h"
21 : :
22 : : namespace ippl {
23 : :
24 : : template <unsigned Dim>
25 : : class FieldLayout;
26 : :
27 : : template <unsigned Dim>
28 : : std::ostream& operator<<(std::ostream&, const FieldLayout<Dim>&);
29 : :
30 : : // enumeration used to describe a hypercube's relation to
31 : : // a particular axis in a given bounded domain
32 : : enum e_cube_tag {
33 : : UPPER = 0,
34 : : LOWER = 1,
35 : : IS_PARALLEL = 2
36 : : };
37 : :
38 : : namespace detail {
39 : : /*!
40 : : * Counts the hypercubes in a given dimension
41 : : * @param dim the dimension
42 : : * @return 3^n
43 : : */
44 : 4368 : constexpr unsigned int countHypercubes(unsigned int dim) {
45 : 4368 : unsigned int ret = 1;
46 [ + + ]: 28428 : for (unsigned int d = 0; d < dim; d++) {
47 : 24060 : ret *= 3;
48 : : }
49 : 4368 : return ret;
50 : : }
51 : :
52 : : /*!
53 : : * Compile-time evaluation of x!
54 : : * @param x input value
55 : : * @return x factorial
56 : : */
57 : : constexpr unsigned int factorial(unsigned x) {
58 : : return x == 0 ? 1 : x * factorial(x - 1);
59 : : }
60 : :
61 : : /*!
62 : : * Compile-time evaluation of binomial coefficients, aka
63 : : * elements of Pascal's triangle, aka choices from combinatorics, etc
64 : : * @param a number of options
65 : : * @param b number of choices
66 : : * @return a choose b
67 : : */
68 : : constexpr unsigned int binomialCoefficient(unsigned a, unsigned b) {
69 : : return factorial(a) / (factorial(b) * factorial(a - b));
70 : : }
71 : :
72 : : /*!
73 : : * Compile-time evaluation of the number of hypercubes of dimension m in a hypercube
74 : : * of dimension Dim
75 : : * @tparam Dim parent hypercube dimension
76 : : * @param m sub-hypercube dimension
77 : : * @return The number of m-cubes in an n-cube
78 : : */
79 : : template <unsigned Dim>
80 : : constexpr unsigned int countCubes(unsigned m) {
81 : : return (1 << (Dim - m)) * binomialCoefficient(Dim, m);
82 : : }
83 : :
84 : : /*!
85 : : * Determines whether a facet is on the upper boundary
86 : : * of its domain. For lower dimension hypercubes, determines
87 : : * whether the component is on the upper boundary of
88 : : * the domain along at least one axis
89 : : * @param face the hypercube's index
90 : : * @return Whether it touches any upper boundary
91 : : */
92 : : bool isUpper(unsigned int face);
93 : :
94 : : /*!
95 : : * Determine the axis perpendicular to a given facet
96 : : * (throws an exception if the index does not correspond
97 : : * to a facet)
98 : : * @param face the facet's index
99 : : * @return The index of the axis perpendicular to that facet
100 : : */
101 : : unsigned int getFaceDim(unsigned int face);
102 : :
103 : : /*!
104 : : * Converts between ternary encoding and face set indexing
105 : : * @tparam Dim the number of dimensions
106 : : * @param index the ternary-encoded index of a facet in [0, 3^Dim)
107 : : * @return The index of that facet in a set of faces in [0, 2*Dim)
108 : : */
109 : : template <unsigned Dim>
110 : : unsigned int indexToFace(unsigned int index) {
111 : : // facets are group low/high by axis
112 : : unsigned int axis = index / 2;
113 : : // the digit to subtract is determined by whether the index describes an upper
114 : : // face (even index) or lower face (odd index) and that digit's position is
115 : : // determined by the axis of the face
116 : : unsigned int toRemove = (2 - index % 2) * countHypercubes(axis);
117 : : // start with all 2s (in base 3) and change the correct digit to get the encoded face
118 : : return countHypercubes(Dim) - 1 - toRemove;
119 : : }
120 : :
121 : : /*!
122 : : * Computes the ternary-encoded index of a hypercube
123 : : * @tparam Dim the number of dimensions in the full hypercube
124 : : * @tparam CubeTags... variadic argument list, must be all e_cube_tag
125 : : * @param tag(s...) the tags describing the hypercube of interest
126 : : * @return The index of the desired hypercube
127 : : */
128 : : template <
129 : : unsigned Dim, typename... CubeTags,
130 : : typename = std::enable_if_t<sizeof...(CubeTags) == Dim - 1>,
131 : : typename = std::enable_if_t<std::conjunction_v<std::is_same<e_cube_tag, CubeTags>...>>>
132 : 0 : unsigned int getCube(e_cube_tag tag, CubeTags... tags) {
133 : : if constexpr (Dim == 1) {
134 : 0 : return tag;
135 : : } else {
136 : 0 : return tag + 3 * getCube<Dim - 1>(tags...);
137 : : }
138 : : }
139 : :
140 : : /*!
141 : : * Utility function for getFace
142 : : */
143 : : template <size_t... Idx>
144 : : unsigned int getFace_impl(const std::array<e_cube_tag, sizeof...(Idx)>& args,
145 : : const std::index_sequence<Idx...>&) {
146 : : return getCube<sizeof...(Idx)>(args[Idx]...);
147 : : }
148 : :
149 : : /*!
150 : : * Convenience alias for getCube for getting facets
151 : : * @tparam Dim the number of dimensions in the parent hypercube
152 : : * @param axis the axis perpendicular to the facet
153 : : * @param side whether the facet is an upper or lower facet
154 : : * @return The index of the facet
155 : : */
156 : : template <unsigned Dim>
157 : : unsigned int getFace(unsigned int axis, e_cube_tag side) {
158 : : std::array<e_cube_tag, Dim> args;
159 : : args.fill(IS_PARALLEL);
160 : : args[axis] = side;
161 : : return getFace_impl(args, std::make_index_sequence<Dim>{});
162 : : }
163 : : } // namespace detail
164 : :
165 : : template <unsigned Dim>
166 : : class FieldLayout {
167 : : public:
168 : : using NDIndex_t = NDIndex<Dim>;
169 : : using view_type = typename detail::ViewType<NDIndex_t, 1>::view_type;
170 : : using host_mirror_type = typename view_type::host_mirror_type;
171 : :
172 : : struct bound_type {
173 : : // lower bounds (ordering: x, y, z, ...)
174 : : std::array<long, Dim> lo;
175 : : // upper bounds (ordering: x, y, z, ...)
176 : : std::array<long, Dim> hi;
177 : :
178 : : /*!
179 : : * Compute the size of the region described by the bounds
180 : : * @return Product of the axial dimensions of the region
181 : : */
182 : 0 : long size() const {
183 : 0 : long total = 1;
184 [ # # ]: 0 : for (unsigned d = 0; d < Dim; d++) {
185 : 0 : total *= hi[d] - lo[d];
186 : : }
187 : 0 : return total;
188 : : }
189 : : };
190 : :
191 : : using rank_list = std::vector<int>;
192 : : using bounds_list = std::vector<bound_type>;
193 : :
194 : : using neighbor_list = std::array<rank_list, detail::countHypercubes(Dim) - 1>;
195 : : using neighbor_range_list = std::array<bounds_list, detail::countHypercubes(Dim) - 1>;
196 : :
197 : : /*!
198 : : * Default constructor, which should only be used if you are going to
199 : : * call 'initialize' soon after (before using in any context)
200 : : */
201 : : FieldLayout(const mpi::Communicator& = MPI_COMM_WORLD);
202 : :
203 : : FieldLayout(mpi::Communicator, const NDIndex<Dim>& domain, std::array<bool, Dim> decomp,
204 : : bool isAllPeriodic = false);
205 : :
206 : : // Destructor: Everything deletes itself automatically ... the base
207 : : // class destructors inform all the FieldLayoutUser's we're going away.
208 : 792 : virtual ~FieldLayout() = default;
209 : :
210 : : // Initialization functions, only to be called by the user of FieldLayout
211 : : // objects when the FieldLayout was created using the default constructor;
212 : : // otherwise these are only called internally by the various non-default
213 : : // FieldLayout constructors:
214 : :
215 : : void initialize(const NDIndex<Dim>& domain, std::array<bool, Dim> decomp,
216 : : bool isAllPeriodic = false);
217 : :
218 : : // Return the domain.
219 : 1722 : const NDIndex<Dim>& getDomain() const { return gDomain_m; }
220 : :
221 : : // Compare FieldLayouts to see if they represent the same domain; if
222 : : // dimensionalities are different, the NDIndex operator==() will return
223 : : // false:
224 : : template <unsigned Dim2>
225 : : bool operator==(const FieldLayout<Dim2>& x) const {
226 : : return gDomain_m == x.getDomain();
227 : : }
228 : :
229 : : bool operator==(const FieldLayout<Dim>& x) const {
230 : : for (unsigned int i = 0; i < Dim; ++i) {
231 : : if (hLocalDomains_m(comm.rank())[i] != x.getLocalNDIndex()[i]) {
232 : : return false;
233 : : }
234 : : }
235 : : return true;
236 : : }
237 : :
238 : : // for the requested dimension, report if the distribution is
239 : : // SERIAL or PARALLEL
240 : : bool getDistribution(unsigned int d) const {
241 : : return minWidth_m[d] == (unsigned int)gDomain_m[d].length();
242 : : }
243 : :
244 : : // for the requested dimension, report if the distribution was requested to
245 : : // be SERIAL or PARALLEL
246 : 0 : std::array<bool, Dim> isParallel() const { return isParallelDim_m; }
247 : :
248 : : const NDIndex_t& getLocalNDIndex(int rank = -1) const;
249 : :
250 : : const host_mirror_type getHostLocalDomains() const;
251 : :
252 : : const view_type getDeviceLocalDomains() const;
253 : :
254 : : /*!
255 : : * Get a list of all the neighbors, arranged by ternary encoding
256 : : * of the hypercubes
257 : : * @return List of list of neighbor ranks touching each boundary component
258 : : */
259 : : const neighbor_list& getNeighbors() const;
260 : :
261 : : /*!
262 : : * Get the domain ranges corresponding to regions that should be sent
263 : : * to neighbor ranks
264 : : * @return Ranges to send
265 : : */
266 : : const neighbor_range_list& getNeighborsSendRange() const;
267 : :
268 : : /*!
269 : : * Get the domain ranges corresponding to regions that should be received
270 : : * from neighbor ranks
271 : : * @return Ranges to receive
272 : : */
273 : : const neighbor_range_list& getNeighborsRecvRange() const;
274 : :
275 : : /*!
276 : : * Given the index of a hypercube, find the index of the opposite hypercube,
277 : : * i.e. the component with the same codimension belonging to a neighboring domain
278 : : * that touches the hypercube with the given index, as determined by the
279 : : * ternary encoding for hypercubes.
280 : : *
281 : : * For neighbor communication, the opposite component is the one that receives
282 : : * sent data or sends us data to receive for a given component.
283 : : *
284 : : * The matching index is given by swapping alls 1s for 0s and vice versa in
285 : : * the ternary encoding, while keeping the 2s unchanged. This can be understood
286 : : * from the fact that if the local component is on the upper boundary of the local
287 : : * domain, the neighbor component must be on the lower boundary of its local domain,
288 : : * and vice versa. The 2s are unchanged because both the local component and the
289 : : * neighbor component must be parallel to the same axes, otherwise their intersection
290 : : * would have lower or higher dimension than the components themselves.
291 : : * @param index index of the known component
292 : : * @return Index of the matching component
293 : : */
294 : : static int getMatchingIndex(int index);
295 : :
296 : : /*!
297 : : * Recursively finds neighbor ranks for layouts with all periodic boundary
298 : : * conditions
299 : : * @param nghost number of ghost cells
300 : : * @param localDomain the rank's local domain
301 : : * @param grown the local domain, grown by the number of ghost cells
302 : : * @param neighborDomain a candidate neighbor rank's domain
303 : : * @param rank the candidate neighbor's rank
304 : : * @param offsets a dictionary containing offsets along different dimensions
305 : : * @param d0 the dimension from which to start checking (default 0)
306 : : * @param codim the codimension of overlapping regions to check (default 0)
307 : : */
308 : : void findPeriodicNeighbors(const int nghost, const NDIndex<Dim>& localDomain,
309 : : NDIndex<Dim>& grown, NDIndex<Dim>& neighborDomain,
310 : : const int rank, std::map<unsigned int, int>& offsets,
311 : : unsigned d0 = 0, unsigned codim = 0);
312 : :
313 : : /*!
314 : : * Finds all neighboring ranks based on the field layout
315 : : * @param nghost number of ghost cells (default 1)
316 : : */
317 : : void findNeighbors(int nghost = 1);
318 : :
319 : : /*!
320 : : * Adds a neighbor to the neighbor list
321 : : * @param gnd the local domain, including ghost cells
322 : : * @param nd the local domain
323 : : * @param ndNeighbor the neighbor rank's domain
324 : : * @param intersect the intersection of the domains
325 : : * @param nghost number of ghost cells
326 : : * @param rank the neighbor's rank
327 : : */
328 : : void addNeighbors(const NDIndex_t& gnd, const NDIndex_t& nd, const NDIndex_t& ndNeighbor,
329 : : const NDIndex_t& intersect, int nghost, int rank);
330 : :
331 : : void write(std::ostream& = std::cout) const;
332 : :
333 : : void updateLayout(const std::vector<NDIndex_t>& domains);
334 : :
335 : : bool isAllPeriodic_m;
336 : :
337 : : mpi::Communicator comm;
338 : :
339 : : private:
340 : : /*!
341 : : * Obtain the bounds to send / receive. The second domain, i.e.,
342 : : * nd2, is grown by nghost cells in each dimension in order to
343 : : * figure out the intersecting cells.
344 : : * @param nd1 either remote or owned domain
345 : : * @param nd2 either remote or owned domain
346 : : * @param offset to map global to local grid point
347 : : * @param nghost number of ghost cells per dimension
348 : : */
349 : : bound_type getBounds(const NDIndex_t& nd1, const NDIndex_t& nd2, const NDIndex_t& offset,
350 : : int nghost);
351 : :
352 : : int getPeriodicOffset(const NDIndex_t& nd, const unsigned int d, const int k);
353 : :
354 : : private:
355 : : //! Global domain
356 : : NDIndex_t gDomain_m;
357 : :
358 : : //! Local domains (device view)
359 : : view_type dLocalDomains_m;
360 : :
361 : : //! Local domains (host mirror view)
362 : : host_mirror_type hLocalDomains_m;
363 : :
364 : : std::array<bool, Dim> isParallelDim_m;
365 : :
366 : : unsigned int minWidth_m[Dim];
367 : :
368 : : neighbor_list neighbors_m;
369 : : neighbor_range_list neighborsSendRange_m, neighborsRecvRange_m;
370 : :
371 : : void calcWidths();
372 : : };
373 : :
374 : : template <unsigned Dim>
375 : 0 : inline std::ostream& operator<<(std::ostream& out, const FieldLayout<Dim>& f) {
376 : 0 : f.write(out);
377 : 0 : return out;
378 : : }
379 : : } // namespace ippl
380 : :
381 : : #include "FieldLayout/FieldLayout.hpp"
382 : :
383 : : #endif
|