IPPL API Reference
Independent Parallel Particle Layer C++ API
Loading...
Searching...
No Matches
ParticleSpatialLayout.hpp
1//
2// Class ParticleSpatialLayout
3// Particle layout based on spatial decomposition.
4//
5// This is a specialized version of ParticleLayout, which places particles
6// on processors based on their spatial location relative to a fixed grid.
7// In particular, this can maintain particles on processors based on a
8// specified FieldLayout or RegionLayout, so that particles are always on
9// the same node as the node containing the Field region to which they are
10// local. This may also be used if there is no associated Field at all,
11// in which case a grid is selected based on an even distribution of
12// particles among processors.
13//
14// After each 'time step' in a calculation, which is defined as a period
15// in which the particle positions may change enough to affect the global
16// layout, the user must call the 'update' routine, which will move
17// particles between processors, etc. After the Nth call to update, a
18// load balancing routine will be called instead. The user may set the
19// frequency of load balancing (N), or may supply a function to
20// determine if load balancing should be done or not.
21//
22#include <memory>
23#include <numeric>
24#include <vector>
25
26#include "Utility/IpplTimings.h"
27
28#include "Communicate/Window.h"
29
30namespace ippl {
31
39 size_t count[2];
40
41 KOKKOS_FUNCTION void init() {
42 count[0] = 0;
43 count[1] = 0;
44 }
45
46 KOKKOS_INLINE_FUNCTION increment_type& operator+=(bool* values) {
47 count[0] += values[0];
48 count[1] += values[1];
49 return *this;
50 }
51
52 KOKKOS_INLINE_FUNCTION increment_type& operator+=(increment_type values) {
53 count[0] += values.count[0];
54 count[1] += values.count[1];
55 return *this;
56 }
57 };
58
59 template <typename T, unsigned Dim, class Mesh, typename... Properties>
61 Mesh& mesh, bool fem)
62 : rlayout_m(fl, mesh, fem)
63 , flayout_m(fl)
64 {
65 nRecvs_m.resize(Comm->size());
66 if (Comm->size() > 1) {
67 window_m.create(*Comm, nRecvs_m.begin(), nRecvs_m.end());
68 }
69 }
70
71 template <typename T, unsigned Dim, class Mesh, typename... Properties>
72 void ParticleSpatialLayout<T, Dim, Mesh, Properties...>::updateLayout(FieldLayout<Dim>& fl,
73 Mesh& mesh) {
74 // flayout_m = fl;
75 rlayout_m.changeDomain(fl, mesh);
76 }
77
78 template <typename T, unsigned Dim, class Mesh, typename... Properties>
79 template <class ParticleContainer>
80 void ParticleSpatialLayout<T, Dim, Mesh, Properties...>::update(ParticleContainer& pc) {
81 /* Apply Boundary Conditions */
82 static IpplTimings::TimerRef ParticleBCTimer = IpplTimings::getTimer("particleBC");
83 IpplTimings::startTimer(ParticleBCTimer);
84 this->applyBC(pc.R, rlayout_m.getDomain());
85 IpplTimings::stopTimer(ParticleBCTimer);
86
87 /* Update Timer for the rest of the function */
88 static IpplTimings::TimerRef ParticleUpdateTimer = IpplTimings::getTimer("updateParticle");
89 IpplTimings::startTimer(ParticleUpdateTimer);
90
91 int nRanks = Comm->size();
92 if (nRanks < 2) {
93 return;
94 }
95
96 /* particle MPI exchange:
97 * 1. figure out which particles need to go where -> locateParticles(...)
98 * 2. fill send buffer and send particles
99 * 3. delete invalidated particles
100 * 4. receive particles
101 */
102
103 // 1. figure out which particles need to go where -> locateParticles(...) ============= //
104
105 static IpplTimings::TimerRef locateTimer = IpplTimings::getTimer("locateParticles");
106 IpplTimings::startTimer(locateTimer);
107
108 /* Rank-local number of particles */
109 size_type localnum = pc.getLocalNum();
110
111 /* The indices correspond to the indices of the local particles,
112 * the values correspond to the ranks to which the particles need to be sent
113 */
114 locate_type particleRanks("particles' MPI ranks", localnum);
115
116 /* The indices are the indices of the particles,
117 * the boolean values describe whether the particle has left the current rank
118 * 0 --> particle valid (inside current rank)
119 * 1 --> particle invalid (left rank)
120 */
121 bool_type invalidParticles("validity of particles", localnum);
122
123 /* The indices are the MPI ranks,
124 * the values are the number of particles are sent to that rank from myrank
125 */
126 locate_type rankSendCount_dview("rankSendCount Device", nRanks);
127
128 /* The indices have no particluar meaning,
129 * the values are the MPI ranks to which we need to send
130 */
131 locate_type destinationRanks_dview("destinationRanks Device", nRanks);
132
133 /* nInvalid is the number of invalid particles
134 * nDestinationRanks is the number of MPI ranks we need to send to
135 */
136 auto [nInvalid, nDestinationRanks] = locateParticles(
137 pc, particleRanks, invalidParticles, rankSendCount_dview, destinationRanks_dview);
138
139 /* Host space copy of rankSendCount_dview */
140 auto rankSendCount_hview =
141 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), rankSendCount_dview);
142
143 /* Host Space copy of destinationRanks_dview */
144 auto destinationRanks_hview =
145 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), destinationRanks_dview);
146
147 IpplTimings::stopTimer(locateTimer);
148
149 // 2. fill send buffer and send particles =============================================== //
150
151 // 2.1 Remote Memory Access window for one-sided communication
152
153 static IpplTimings::TimerRef preprocTimer = IpplTimings::getTimer("sendPreprocess");
154 IpplTimings::startTimer(preprocTimer);
155
156 std::fill(nRecvs_m.begin(), nRecvs_m.end(), 0);
157
158 window_m.fence(0);
159
160 // Prepare RMA window for the ranks we need to send to
161 for (size_t ridx = 0; ridx < nDestinationRanks; ridx++) {
162 int rank = destinationRanks_hview[ridx];
163 if (rank == Comm->rank()) {
164 // we do not need to send to ourselves
165 continue;
166 }
167 const int* src_ptr = &rankSendCount_hview(rank);
168 window_m.put<int>(src_ptr, rank, Comm->rank());
169 }
170 window_m.fence(0);
171
172 IpplTimings::stopTimer(preprocTimer);
173
174 // 2.2 Particle Sends
175
176 static IpplTimings::TimerRef sendTimer = IpplTimings::getTimer("particleSend");
177 IpplTimings::startTimer(sendTimer);
178
179 std::vector<MPI_Request> requests(0);
180
181 int tag = Comm->next_tag(mpi::tag::P_SPATIAL_LAYOUT, mpi::tag::P_LAYOUT_CYCLE);
182
183 for (size_t ridx = 0; ridx < nDestinationRanks; ridx++) {
184 int rank = destinationRanks_hview[ridx];
185 if (rank == Comm->rank()) {
186 continue;
187 }
188 hash_type hash("hash", rankSendCount_hview(rank));
189 fillHash(rank, particleRanks, hash);
190 pc.sendToRank(rank, tag, requests, hash);
191 }
192
193 IpplTimings::stopTimer(sendTimer);
194
195 // 3. Internal destruction of invalid particles ======================================= //
196
197 static IpplTimings::TimerRef destroyTimer = IpplTimings::getTimer("particleDestroy");
198 IpplTimings::startTimer(destroyTimer);
199
200 pc.internalDestroy(invalidParticles, nInvalid);
201 Kokkos::fence();
202
203 IpplTimings::stopTimer(destroyTimer);
204
205 // 4. Receive Particles ================================================================ //
206
207 static IpplTimings::TimerRef recvTimer = IpplTimings::getTimer("particleRecv");
208 IpplTimings::startTimer(recvTimer);
209
210 for (int rank = 0; rank < nRanks; ++rank) {
211 if (nRecvs_m[rank] > 0) {
212 pc.recvFromRank(rank, tag, nRecvs_m[rank]);
213 }
214 }
215 IpplTimings::stopTimer(recvTimer);
216
217 IpplTimings::startTimer(sendTimer);
218
219 if (requests.size() > 0) {
220 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
221 }
222 IpplTimings::stopTimer(sendTimer);
223 Comm->freeAllBuffers();
224
225 IpplTimings::stopTimer(ParticleUpdateTimer);
226 }
227
228 template <typename T, unsigned Dim, class Mesh, typename... Properties>
229 template <size_t... Idx>
230 KOKKOS_INLINE_FUNCTION constexpr bool
231 ParticleSpatialLayout<T, Dim, Mesh, Properties...>::positionInRegion(
232 const std::index_sequence<Idx...>&, const vector_type& pos, const region_type& region) {
233 return ((pos[Idx] > region[Idx].min()) && ...) && ((pos[Idx] <= region[Idx].max()) && ...);
234 };
235
236 /* Helper function that evaluates the total number of neighbors for the current rank in Dim
237 * dimensions.
238 */
239 template <typename T, unsigned Dim, class Mesh, typename... Properties>
241 const neighbor_list& neighbors) const {
242 size_type totalSize = 0;
243
244 for (const auto& componentNeighbors : neighbors) {
245 totalSize += componentNeighbors.size();
246 }
247
248 return totalSize;
249 }
250
268 template <typename T, unsigned Dim, class Mesh, typename... Properties>
269 template <typename ParticleContainer>
270 std::pair<detail::size_type, detail::size_type>
272 const ParticleContainer& pc, locate_type& ranks, bool_type& invalid,
273 locate_type& nSends_dview, locate_type& sends_dview) const {
274 auto positions = pc.R.getView();
275 region_view_type Regions = rlayout_m.getdLocalRegions();
276
277 using policy_type = Kokkos::RangePolicy<position_execution_space>;
278 using mdrange_type = Kokkos::MDRangePolicy<Kokkos::Rank<2>, position_execution_space>;
279
280 size_type myRank = Comm->rank();
281
282 const auto is = std::make_index_sequence<Dim>{};
283
284 const neighbor_list& neighbors = flayout_m.getNeighbors();
285
287 locate_type outsideIds("Particles outside of neighborhood", size_type(pc.getLocalNum()));
288
290 size_type outsideCount = 0;
292 size_type invalidCount = 0;
293
295 const size_type neighborSize = getNeighborSize(neighbors);
296
298 locate_type neighbors_view("Nearest neighbors IDs", neighborSize);
299
300 /* red_val: Used to reduce both the number of invalid particles and the number of particles
301 * outside of the neighborhood (Kokkos::parallel_scan doesn't allow multiple reduction
302 * values, so we use the helper class increment_type). First element updates InvalidCount,
303 * second one updates outsideCount.
304 */
305 increment_type red_val;
306 red_val.init();
307
308 auto neighbors_mirror =
309 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), neighbors_view);
310
311 size_t k = 0;
312
313 for (const auto& componentNeighbors : neighbors) {
314 for (size_t j = 0; j < componentNeighbors.size(); ++j) {
315 neighbors_mirror(k) = componentNeighbors[j];
316 // std::cout << "Neighbor: " << neighbors_mirror(k) << std::endl;
317 k++;
318 }
319 }
320
321 Kokkos::deep_copy(neighbors_view, neighbors_mirror);
322
329 static IpplTimings::TimerRef neighborSearch = IpplTimings::getTimer("neighborSearch");
330 IpplTimings::startTimer(neighborSearch);
331
332 Kokkos::parallel_scan(
333 "ParticleSpatialLayout::locateParticles()",
334 policy_type(0, ranks.extent(0)),
335 KOKKOS_LAMBDA(const size_type i, increment_type& val, const bool final) {
336 /* Step 1
337 * inCurr: True if the particle hasn't left the current MPI rank.
338 * inNeighbor: True if the particle is found in a neighboring rank.
339 * found: True either if inCurr = True or inNeighbor = True.
340 * increment: Helper variable to update red_val.
341 */
342 bool inCurr = false;
343 bool inNeighbor = false;
344 bool found = false;
345 bool increment[2];
346
347 inCurr = positionInRegion(is, positions(i), Regions(myRank));
348
349 ranks(i) = inCurr * myRank;
350 invalid(i) = !inCurr;
351 found = inCurr || found;
352
354 for (size_t j = 0; j < neighbors_view.extent(0); ++j) {
355 size_type rank = neighbors_view(j);
356
357 inNeighbor = positionInRegion(is, positions(i), Regions(rank));
358
359 ranks(i) = !(inNeighbor)*ranks(i) + inNeighbor * rank;
360 found = inNeighbor || found;
361 }
363 /* isOut: When the last thread has finished the search, checks whether the particle
364 * has been found either in the current rank or in a neighboring one. Used to avoid
365 * race conditions when updating outsideIds.
366 */
367 if (final && !found) {
368 outsideIds(val.count[1]) = i;
369 }
370 // outsideIds(val.count[1]) = i * isOut;
371 increment[0] = invalid(i);
372 increment[1] = !found;
373 val += increment;
374 },
375 red_val);
376
377 Kokkos::fence();
378
379 invalidCount = red_val.count[0];
380 outsideCount = red_val.count[1];
381
382 IpplTimings::stopTimer(neighborSearch);
383
385 static IpplTimings::TimerRef nonNeighboringParticles =
386 IpplTimings::getTimer("nonNeighboringParticles");
387 IpplTimings::startTimer(nonNeighboringParticles);
388 if (outsideCount > 0) {
389 Kokkos::parallel_for(
390 "ParticleSpatialLayout::leftParticles()",
391 mdrange_type({0, 0}, {outsideCount, Regions.extent(0)}),
392 KOKKOS_LAMBDA(const size_t i, const size_type j) {
394 size_type pId = outsideIds(i);
395
397 bool inRegion = positionInRegion(is, positions(pId), Regions(j));
398 if (inRegion) {
399 ranks(pId) = j;
400 }
401 });
402 Kokkos::fence();
403 }
404 IpplTimings::stopTimer(nonNeighboringParticles);
405
406 Kokkos::parallel_for(
407 "Calculate nSends", policy_type(0, ranks.extent(0)),
408 KOKKOS_LAMBDA(const size_t i) {
409 size_type rank = ranks(i);
410 Kokkos::atomic_fetch_add(&nSends_dview(rank), 1);
411 });
412
413 // Number of Ranks we need to send to
414 Kokkos::View<size_type, position_memory_space> rankSends(
415 "Number of Ranks we need to send to");
416
417 Kokkos::parallel_for(
418 "Calculate sends", policy_type(0, nSends_dview.extent(0)),
419 KOKKOS_LAMBDA(const size_t rank) {
420 if (nSends_dview(rank) != 0) {
421 size_type index = Kokkos::atomic_fetch_add(&rankSends(), 1);
422 sends_dview(index) = rank;
423 }
424 });
425 size_type temp;
426 Kokkos::deep_copy(temp, rankSends);
427
428 return {invalidCount, temp};
429 }
430
431 template <typename T, unsigned Dim, class Mesh, typename... Properties>
433 const locate_type& ranks,
434 hash_type& hash) {
435 /* Compute the prefix sum and fill the hash
436 */
437 using policy_type = Kokkos::RangePolicy<position_execution_space>;
438 Kokkos::parallel_scan(
439 "ParticleSpatialLayout::fillHash()", policy_type(0, ranks.extent(0)),
440 KOKKOS_LAMBDA(const size_t i, int& idx, const bool final) {
441 if (final) {
442 if (rank == ranks(i)) {
443 hash(idx) = i;
444 }
445 }
446
447 if (rank == ranks(i)) {
448 idx += 1;
449 }
450 });
451 Kokkos::fence();
452 }
453
454 template <typename T, unsigned Dim, class Mesh, typename... Properties>
456 int rank, const locate_type& ranks) {
457 size_t nSends = 0;
458 using policy_type = Kokkos::RangePolicy<position_execution_space>;
459 Kokkos::parallel_reduce(
460 "ParticleSpatialLayout::numberOfSends()", policy_type(0, ranks.extent(0)),
461 KOKKOS_LAMBDA(const size_t i, size_t& num) { num += size_t(rank == ranks(i)); },
462 nSends);
463 Kokkos::fence();
464 return nSends;
465 }
466
467} // namespace ippl
Definition FieldLayout.h:166
Definition Mesh.h:15
Definition ParticleSpatialLayout.h:46
size_t numberOfSends(int rank, const locate_type &ranks)
Definition ParticleSpatialLayout.hpp:455
size_type getNeighborSize(const neighbor_list &neighbors) const
Definition ParticleSpatialLayout.hpp:240
void fillHash(int rank, const locate_type &ranks, hash_type &hash)
Definition ParticleSpatialLayout.hpp:432
typename RegionLayout_t::view_type region_view_type
Type of the Kokkos view containing the local regions.
Definition ParticleSpatialLayout.h:92
std::pair< size_type, size_type > locateParticles(const ParticleContainer &pc, locate_type &ranks, bool_type &invalid, locate_type &nSends_dview, locate_type &sends_dview) const
typename FieldLayout_t::neighbor_list neighbor_list
Array of N rank lists, where N = number of hypercubes for the dimension Dim.
Definition ParticleSpatialLayout.h:96
Definition Archive.h:20
Definition ParticleSpatialLayout.hpp:38