IPPL (Independent Parallel Particle Layer)
IPPL
Loading...
Searching...
No Matches
ParticleSpatialLayout.hpp
Go to the documentation of this file.
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
30
31namespace ippl {
32
40 size_t count[2];
41
42 KOKKOS_FUNCTION void init() {
43 count[0] = 0;
44 count[1] = 0;
45 }
46
47 KOKKOS_INLINE_FUNCTION increment_type& operator+=(bool* values) {
48 count[0] += values[0];
49 count[1] += values[1];
50 return *this;
51 }
52
53 KOKKOS_INLINE_FUNCTION increment_type& operator+=(increment_type values) {
54 count[0] += values.count[0];
55 count[1] += values.count[1];
56 return *this;
57 }
58 };
59
60 template <typename T, unsigned Dim, class Mesh, typename... Properties>
62 Mesh& mesh)
63 : rlayout_m(fl, mesh)
65 {
66 nRecvs_m.resize(Comm->size());
67 if (Comm->size() > 1) {
68 window_m.create(*Comm, nRecvs_m.begin(), nRecvs_m.end());
69 }
70 }
72 template <typename T, unsigned Dim, class Mesh, typename... Properties>
74 Mesh& mesh) {
75 //flayout_m = fl;
76 rlayout_m.changeDomain(fl, mesh);
77 }
78
79 template <typename T, unsigned Dim, class Mesh, typename... Properties>
80 template <class ParticleContainer>
82
83 /* Apply Boundary Conditions */
84 static IpplTimings::TimerRef ParticleBCTimer = IpplTimings::getTimer("particleBC");
85 IpplTimings::startTimer(ParticleBCTimer);
86 this->applyBC(pc.R, rlayout_m.getDomain());
87 IpplTimings::stopTimer(ParticleBCTimer);
88
89 /* Update Timer for the rest of the function */
90 static IpplTimings::TimerRef ParticleUpdateTimer = IpplTimings::getTimer("updateParticle");
91 IpplTimings::startTimer(ParticleUpdateTimer);
92
93 int nRanks = Comm->size();
94 if (nRanks < 2) {
95 return;
96 }
97
98 /* particle MPI exchange:
99 * 1. figure out which particles need to go where -> locateParticles(...)
100 * 2. fill send buffer and send particles
101 * 3. delete invalidated particles
102 * 4. receive particles
103 */
104
105 // 1. figure out which particles need to go where -> locateParticles(...) ============= //
106
107 static IpplTimings::TimerRef locateTimer = IpplTimings::getTimer("locateParticles");
108 IpplTimings::startTimer(locateTimer);
110 /* Rank-local number of particles */
111 size_type localnum = pc.getLocalNum();
112
113 /* The indices correspond to the indices of the local particles,
114 * the values correspond to the ranks to which the particles need to be sent
115 */
116 locate_type particleRanks("particles' MPI ranks", localnum);
117
118 /* The indices are the indices of the particles,
119 * the boolean values describe whether the particle has left the current rank
120 * 0 --> particle valid (inside current rank)
121 * 1 --> particle invalid (left rank)
122 */
123 bool_type invalidParticles("validity of particles", localnum);
124
125 /* The indices are the MPI ranks,
126 * the values are the number of particles are sent to that rank from myrank
127 */
128 locate_type rankSendCount_dview("rankSendCount Device", nRanks);
129
130 /* The indices have no particluar meaning,
131 * the values are the MPI ranks to which we need to send
132 */
133 locate_type destinationRanks_dview("destinationRanks Device", nRanks);
134
135 /* nInvalid is the number of invalid particles
136 * nDestinationRanks is the number of MPI ranks we need to send to
137 */
138 auto [nInvalid, nDestinationRanks] =
139 locateParticles(pc,
140 particleRanks, invalidParticles,
141 rankSendCount_dview, destinationRanks_dview);
142
143 /* Host space copy of rankSendCount_dview */
144 auto rankSendCount_hview =
145 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), rankSendCount_dview);
146
147 /* Host Space copy of destinationRanks_dview */
148 auto destinationRanks_hview =
149 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), destinationRanks_dview);
150
151 IpplTimings::stopTimer(locateTimer);
152
153 // 2. fill send buffer and send particles =============================================== //
154
155 // 2.1 Remote Memory Access window for one-sided communication
156
157 static IpplTimings::TimerRef preprocTimer = IpplTimings::getTimer("sendPreprocess");
158 IpplTimings::startTimer(preprocTimer);
159
160 std::fill(nRecvs_m.begin(), nRecvs_m.end(), 0);
161
162 window_m.fence(0);
163
164 // Prepare RMA window for the ranks we need to send to
165 for(size_t ridx=0; ridx < nDestinationRanks; ridx++){
166 int rank = destinationRanks_hview[ridx];
167 if (rank == Comm->rank()){
168 // we do not need to send to ourselves
169 continue;
170 }
171 const int* src_ptr = &rankSendCount_hview(rank);
172 window_m.put<int>(src_ptr, rank, Comm->rank());
173 }
174 window_m.fence(0);
175
176 IpplTimings::stopTimer(preprocTimer);
177
178 // 2.2 Particle Sends
179
180 static IpplTimings::TimerRef sendTimer = IpplTimings::getTimer("particleSend");
181 IpplTimings::startTimer(sendTimer);
182
183 std::vector<MPI_Request> requests(0);
184
186
187 for(size_t ridx=0; ridx < nDestinationRanks; ridx++){
188 int rank = destinationRanks_hview[ridx];
189 if(rank == Comm->rank()){
190 continue;
191 }
192 hash_type hash("hash", rankSendCount_hview(rank));
193 fillHash(rank, particleRanks, hash);
194 pc.sendToRank(rank, tag, requests, hash);
195 }
196
197 IpplTimings::stopTimer(sendTimer);
198
199
200 // 3. Internal destruction of invalid particles ======================================= //
201
202 static IpplTimings::TimerRef destroyTimer = IpplTimings::getTimer("particleDestroy");
203 IpplTimings::startTimer(destroyTimer);
204
205 pc.internalDestroy(invalidParticles, nInvalid);
206 Kokkos::fence();
207
208 IpplTimings::stopTimer(destroyTimer);
209
210 // 4. Receive Particles ================================================================ //
211
212 static IpplTimings::TimerRef recvTimer = IpplTimings::getTimer("particleRecv");
213 IpplTimings::startTimer(recvTimer);
214
215 for (int rank = 0; rank < nRanks; ++rank) {
216 if (nRecvs_m[rank] > 0) {
217 pc.recvFromRank(rank, tag, nRecvs_m[rank]);
218 }
219 }
220 IpplTimings::stopTimer(recvTimer);
221
222
223 IpplTimings::startTimer(sendTimer);
224
225 if (requests.size() > 0) {
226 MPI_Waitall(requests.size(), requests.data(), MPI_STATUSES_IGNORE);
227 }
228 IpplTimings::stopTimer(sendTimer);
229
230 IpplTimings::stopTimer(ParticleUpdateTimer);
231 }
232
233 template <typename T, unsigned Dim, class Mesh, typename... Properties>
234 template <size_t... Idx>
235 KOKKOS_INLINE_FUNCTION constexpr bool
237 const std::index_sequence<Idx...>&, const vector_type& pos, const region_type& region) {
238 return ((pos[Idx] > region[Idx].min()) && ...) && ((pos[Idx] <= region[Idx].max()) && ...);
239 };
240
241
242 /* Helper function that evaluates the total number of neighbors for the current rank in Dim dimensions.
243 */
244 template <typename T, unsigned Dim, class Mesh, typename... Properties>
246 const neighbor_list& neighbors) const {
247 size_type totalSize = 0;
248
249 for (const auto& componentNeighbors : neighbors) {
250 totalSize += componentNeighbors.size();
251 }
252
253 return totalSize;
254 }
255
256
273 template <typename T, unsigned Dim, class Mesh, typename... Properties>
274 template <typename ParticleContainer>
275 std::pair<detail::size_type, detail::size_type> ParticleSpatialLayout<T, Dim, Mesh, Properties...>::locateParticles(
276 const ParticleContainer& pc, locate_type& ranks, bool_type& invalid,
277 locate_type& nSends_dview, locate_type& sends_dview) const {
278
279 auto positions = pc.R.getView();
280 region_view_type Regions = rlayout_m.getdLocalRegions();
281
282 using mdrange_type = Kokkos::MDRangePolicy<Kokkos::Rank<2>, position_execution_space>;
283
284 size_type myRank = Comm->rank();
285
286 const auto is = std::make_index_sequence<Dim>{};
287
288 const neighbor_list& neighbors = flayout_m.getNeighbors();
289
291 locate_type outsideIds("Particles outside of neighborhood", size_type(pc.getLocalNum()));
292
294 size_type outsideCount = 0;
296 size_type invalidCount = 0;
297
299 const size_type neighborSize = getNeighborSize(neighbors);
300
302 locate_type neighbors_view("Nearest neighbors IDs", neighborSize);
303
304 /* red_val: Used to reduce both the number of invalid particles and the number of particles
305 * outside of the neighborhood (Kokkos::parallel_scan doesn't allow multiple reduction values, so we
306 * use the helper class increment_type). First element updates InvalidCount, second
307 * one updates outsideCount.
308 */
309 increment_type red_val;
310 red_val.init();
311
312 auto neighbors_mirror =
313 Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), neighbors_view);
314
315 size_t k = 0;
316
317 for (const auto& componentNeighbors : neighbors) {
318 for (size_t j = 0; j < componentNeighbors.size(); ++j) {
319 neighbors_mirror(k) = componentNeighbors[j];
320 //std::cout << "Neighbor: " << neighbors_mirror(k) << std::endl;
321 k++;
322 }
323 }
324
325 Kokkos::deep_copy(neighbors_view, neighbors_mirror);
326
333 static IpplTimings::TimerRef neighborSearch = IpplTimings::getTimer("neighborSearch");
334 IpplTimings::startTimer(neighborSearch);
335
336 Kokkos::parallel_scan(
337 "ParticleSpatialLayout::locateParticles()",
338 Kokkos::RangePolicy<size_t>(0, ranks.extent(0)),
339 KOKKOS_LAMBDA(const size_type i, increment_type& val, const bool final) {
340 /* Step 1
341 * inCurr: True if the particle hasn't left the current MPI rank.
342 * inNeighbor: True if the particle is found in a neighboring rank.
343 * found: True either if inCurr = True or inNeighbor = True.
344 * increment: Helper variable to update red_val.
345 */
346 bool inCurr = false;
347 bool inNeighbor = false;
348 bool found = false;
349 bool increment[2];
350
351 inCurr = positionInRegion(is, positions(i), Regions(myRank));
352
353 ranks(i) = inCurr * myRank;
354 invalid(i) = !inCurr;
355 found = inCurr || found;
356
358 for (size_t j = 0; j < neighbors_view.extent(0); ++j) {
359 size_type rank = neighbors_view(j);
360
361 inNeighbor = positionInRegion(is, positions(i), Regions(rank));
362
363 ranks(i) = !(inNeighbor) * ranks(i) + inNeighbor * rank;
364 found = inNeighbor || found;
365 }
367 /* isOut: When the last thread has finished the search, checks whether the particle has been found
368 * either in the current rank or in a neighboring one.
369 * Used to avoid race conditions when updating outsideIds.
370 */
371 if(final && !found) {
372 outsideIds(val.count[1]) = i;
373 }
374 //outsideIds(val.count[1]) = i * isOut;
375 increment[0] = invalid(i);
376 increment[1] = !found;
377 val += increment;
378
379 },
380 red_val);
381
382 Kokkos::fence();
383
384 invalidCount = red_val.count[0];
385 outsideCount = red_val.count[1];
386
387 IpplTimings::stopTimer(neighborSearch);
388
390 static IpplTimings::TimerRef nonNeighboringParticles = IpplTimings::getTimer("nonNeighboringParticles");
391 IpplTimings::startTimer(nonNeighboringParticles);
392 if (outsideCount > 0) {
393 Kokkos::parallel_for(
394 "ParticleSpatialLayout::leftParticles()",
395 mdrange_type({0, 0}, {outsideCount, Regions.extent(0)}),
396 KOKKOS_LAMBDA(const size_t i, const size_type j) {
398 size_type pId = outsideIds(i);
399
401 bool inRegion = positionInRegion(is, positions(pId), Regions(j));
402 if(inRegion){
403 ranks(pId) = j;
404 }
405 });
406 Kokkos::fence();
407 }
408 IpplTimings::stopTimer(nonNeighboringParticles);
409
410 Kokkos::parallel_for("Calculate nSends",
411 Kokkos::RangePolicy<size_t>(0, ranks.extent(0)),
412 KOKKOS_LAMBDA(const size_t i){
413 size_type rank = ranks(i);
414 Kokkos::atomic_fetch_add(&nSends_dview(rank),1);
415 }
416 );
417
418 // Number of Ranks we need to send to
419 Kokkos::View<size_type> rankSends("Number of Ranks we need to send to");
420
421 Kokkos::parallel_for("Calculate sends",
422 Kokkos::RangePolicy<size_t>(0, nSends_dview.extent(0)),
423 KOKKOS_LAMBDA(const size_t rank){
424 if(nSends_dview(rank) != 0){
425 size_type index = Kokkos::atomic_fetch_add(&rankSends(), 1);
426 sends_dview(index) = rank;
427 }
428 }
429 );
430 size_type temp;
431 Kokkos::deep_copy(temp, rankSends);
432
433 return {invalidCount, temp};
434 }
435
436 template <typename T, unsigned Dim, class Mesh, typename... Properties>
438 const locate_type& ranks,
439 hash_type& hash) {
440 /* Compute the prefix sum and fill the hash
441 */
442 using policy_type = Kokkos::RangePolicy<position_execution_space>;
443 Kokkos::parallel_scan(
444 "ParticleSpatialLayout::fillHash()", policy_type(0, ranks.extent(0)),
445 KOKKOS_LAMBDA(const size_t i, int& idx, const bool final) {
446 if (final) {
447 if (rank == ranks(i)) {
448 hash(idx) = i;
449 }
450 }
451
452 if (rank == ranks(i)) {
453 idx += 1;
454 }
455 });
456 Kokkos::fence();
457
458 }
459
460 template <typename T, unsigned Dim, class Mesh, typename... Properties>
462 int rank, const locate_type& ranks) {
463 size_t nSends = 0;
464 using policy_type = Kokkos::RangePolicy<position_execution_space>;
465 Kokkos::parallel_reduce(
466 "ParticleSpatialLayout::numberOfSends()", policy_type(0, ranks.extent(0)),
467 KOKKOS_LAMBDA(const size_t i, size_t& num) { num += size_t(rank == ranks(i)); },
468 nSends);
469 Kokkos::fence();
470 return nSends;
471 }
472
473} // namespace ippl
constexpr unsigned Dim
ippl::detail::size_type size_type
Definition datatypes.h:23
Definition Archive.h:20
KOKKOS_INLINE_FUNCTION Vector< T, Dim > min(const Vector< T, Dim > &a, const Vector< T, Dim > &b)
Definition Vector.hpp:217
std::unique_ptr< mpi::Communicator > Comm
Definition Ippl.h:22
if(myVal > valL) valL
typename detail::ViewType< int, 1, MemorySpace >::view_type hash_type
Definition ViewTypes.h:49
std::size_t size_type
Definition IpplTypes.h:13
@ P_SPATIAL_LAYOUT
Definition Tags.h:22
@ P_LAYOUT_CYCLE
Definition Tags.h:23
void internalDestroy(const Kokkos::View< bool *, Properties... > &invalid, const size_type destroyNum)
void recvFromRank(int rank, int tag, size_type nRecvs)
void sendToRank(int rank, int tag, std::vector< MPI_Request > &requests, const HashType &hash)
particle_position_type R
view of particle positions
size_type getLocalNum() const
void applyBC(const particle_position_type &R, const NDRegion< T, Dim > &nr)
typename particle_position_type::execution_space position_execution_space
mpi::rma::Window< mpi::rma::Active > window_m
RegionLayout_t rlayout_m
The RegionLayout which determines where our particles go.
size_t numberOfSends(int rank, const locate_type &ranks)
void update(ParticleContainer &pc)
std::pair< detail::size_type, detail::size_type > locateParticles(const ParticleContainer &pc, locate_type &ranks, bool_type &invalid, locate_type &nSends_dview, locate_type &sends_dview) const
typename detail::ViewType< bool, 1, position_memory_space >::view_type bool_type
KOKKOS_INLINE_FUNCTION static constexpr bool positionInRegion(const std::index_sequence< Idx... > &, const vector_type &pos, const region_type &region)
size_type getNeighborSize(const neighbor_list &neighbors) const
void fillHash(int rank, const locate_type &ranks, hash_type &hash)
typename detail::ViewType< int, 1, position_memory_space >::view_type locate_type
detail::hash_type< position_memory_space > hash_type
void updateLayout(FieldLayout< Dim > &, Mesh &)
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.
KOKKOS_INLINE_FUNCTION increment_type & operator+=(increment_type values)
KOKKOS_FUNCTION void init()
KOKKOS_INLINE_FUNCTION increment_type & operator+=(bool *values)
Timing::TimerRef TimerRef
static TimerRef getTimer(const char *nm)
static void stopTimer(TimerRef t)
static void startTimer(TimerRef t)