Cabana 0.8.0-dev
 
Loading...
Searching...
No Matches
Cabana_Grid_ParticleDistributor.hpp
Go to the documentation of this file.
1/****************************************************************************
2 * Copyright (c) 2018-2023 by the Cabana authors *
3 * All rights reserved. *
4 * *
5 * This file is part of the Cabana library. Cabana is distributed under a *
6 * BSD 3-clause license. For the licensing terms see the LICENSE file in *
7 * the top-level directory. *
8 * *
9 * SPDX-License-Identifier: BSD-3-Clause *
10 ****************************************************************************/
11
16#ifndef CABANA_PARTICLEGRIDDISTRIBUTOR_HPP
17#define CABANA_PARTICLEGRIDDISTRIBUTOR_HPP
18
19#include <Cabana_DeepCopy.hpp>
21
26
27#include <Kokkos_Core.hpp>
28
29#include <vector>
30
31namespace Cabana
32{
33namespace Grid
34{
35
36//---------------------------------------------------------------------------//
37// Particle Grid Distributor
38//---------------------------------------------------------------------------//
39
46template <class LocalGridType, std::size_t NSD = LocalGridType::num_space_dim>
47std::enable_if_t<3 == NSD, std::vector<int>>
48getTopology( const LocalGridType& local_grid )
49{
50 std::vector<int> topology( 27, -1 );
51 int nr = 0;
52 for ( int k = -1; k < 2; ++k )
53 for ( int j = -1; j < 2; ++j )
54 for ( int i = -1; i < 2; ++i, ++nr )
55 topology[nr] = local_grid.neighborRank( i, j, k );
56 return topology;
57}
58
65template <class LocalGridType, std::size_t NSD = LocalGridType::num_space_dim>
66std::enable_if_t<2 == NSD, std::vector<int>>
67getTopology( const LocalGridType& local_grid )
68{
69 std::vector<int> topology( 9, -1 );
70 int nr = 0;
71 for ( int j = -1; j < 2; ++j )
72 for ( int i = -1; i < 2; ++i, ++nr )
73 topology[nr] = local_grid.neighborRank( i, j );
74 return topology;
75}
76
77namespace Impl
78{
80
81// Locate the particles in the local grid and get their destination rank.
82// Particles are assumed to only migrate to a location in the nearest
83// neighbor halo or stay on this rank. If the particle crosses a global
84// periodic boundary, wrap it's coordinates back into the domain.
85template <class LocalGridType, class PositionSliceType, class NeighborRankView,
86 class DestinationRankView>
87void getMigrateDestinations( const LocalGridType& local_grid,
88 const NeighborRankView& neighbor_ranks,
89 DestinationRankView& destinations,
90 PositionSliceType& positions )
91{
92 static constexpr std::size_t num_space_dim = LocalGridType::num_space_dim;
93 using execution_space = typename PositionSliceType::execution_space;
94
95 // Check within the local domain.
96 const auto& local_mesh = createLocalMesh<Kokkos::HostSpace>( local_grid );
97
98 // Use global domain for periodicity.
99 const auto& global_grid = local_grid.globalGrid();
100 const auto& global_mesh = global_grid.globalMesh();
101
102 Kokkos::Array<double, num_space_dim> local_low{};
103 Kokkos::Array<double, num_space_dim> local_high{};
104 Kokkos::Array<bool, num_space_dim> periodic{};
105 Kokkos::Array<double, num_space_dim> global_low{};
106 Kokkos::Array<double, num_space_dim> global_high{};
107 Kokkos::Array<double, num_space_dim> global_extent{};
108
109 for ( std::size_t d = 0; d < num_space_dim; ++d )
110 {
111 local_low[d] = local_mesh.lowCorner( Own(), d );
112 local_high[d] = local_mesh.highCorner( Own(), d );
113 periodic[d] = global_grid.isPeriodic( d );
114 global_low[d] = global_mesh.lowCorner( d );
115 global_high[d] = global_mesh.highCorner( d );
116 global_extent[d] = global_mesh.extent( d );
117 }
118
119 Kokkos::parallel_for(
120 "Cabana::Grid::ParticleGridMigrate::get_destinations",
121 Kokkos::RangePolicy<execution_space>( 0, positions.size() ),
122 KOKKOS_LAMBDA( const int p ) {
123 // Compute the logical index of the neighbor we are sending to.
124 int nid[num_space_dim];
125 for ( std::size_t d = 0; d < num_space_dim; ++d )
126 {
127 nid[d] = 1;
128 if ( positions( p, d ) < local_low[d] )
129 nid[d] = 0;
130 else if ( positions( p, d ) > local_high[d] )
131 nid[d] = 2;
132 }
133
134 // Compute the destination MPI rank [ni + 3*(nj + 3*nk) in 3d].
135 int neighbor_index = nid[0];
136 for ( std::size_t d = 1; d < num_space_dim; ++d )
137 {
138 int npower = 1;
139 for ( std::size_t dp = 1; dp <= d; ++dp )
140 npower *= 3;
141 neighbor_index += npower * nid[d];
142 }
143 destinations( p ) = neighbor_ranks( neighbor_index );
144
145 // Shift particles through periodic boundaries.
146 for ( std::size_t d = 0; d < num_space_dim; ++d )
147 {
148 if ( periodic[d] )
149 {
150 if ( positions( p, d ) > global_high[d] )
151 positions( p, d ) -= global_extent[d];
152 else if ( positions( p, d ) < global_low[d] )
153 positions( p, d ) += global_extent[d];
154 }
155 }
156 } );
157}
159} // namespace Impl
160
161//-----------------------------------------------------------------------//
174template <class LocalGridType, class PositionSliceType>
175int migrateCount( const LocalGridType& local_grid,
176 const PositionSliceType& positions,
177 const int minimum_halo_width )
178{
179 using grid_type = LocalGridType;
180 static constexpr std::size_t num_space_dim = grid_type::num_space_dim;
181 using mesh_type = typename grid_type::mesh_type;
182 using scalar_type = typename mesh_type::scalar_type;
183 using uniform_type = UniformMesh<scalar_type, num_space_dim>;
184 static_assert( std::is_same<mesh_type, uniform_type>::value,
185 "Migrate count requires a uniform mesh." );
186
187 using execution_space = typename PositionSliceType::execution_space;
188
189 // Check within the halo width, within the ghosted domain.
190 const auto& local_mesh = createLocalMesh<Kokkos::HostSpace>( local_grid );
191
192 Kokkos::Array<double, num_space_dim> local_low{};
193 Kokkos::Array<double, num_space_dim> local_high{};
194 for ( std::size_t d = 0; d < num_space_dim; ++d )
195 {
196 auto dx = local_grid.globalGrid().globalMesh().cellSize( d );
197 local_low[d] =
198 local_mesh.lowCorner( Ghost(), d ) + minimum_halo_width * dx;
199 local_high[d] =
200 local_mesh.highCorner( Ghost(), d ) - minimum_halo_width * dx;
201 }
202
203 int comm_count = 0;
204 Kokkos::parallel_reduce(
205 "Cabana::Grid::ParticleGridMigrate::count",
206 Kokkos::RangePolicy<execution_space>( 0, positions.size() ),
207 KOKKOS_LAMBDA( const int p, int& result ) {
208 for ( std::size_t d = 0; d < num_space_dim; ++d )
209 if ( positions( p, d ) < local_low[d] ||
210 positions( p, d ) > local_high[d] )
211 {
212 result += 1;
213 break;
214 }
215 },
216 comm_count );
217
218 MPI_Allreduce( MPI_IN_PLACE, &comm_count, 1, MPI_INT, MPI_SUM,
219 local_grid.globalGrid().comm() );
220
221 return comm_count;
222}
223
224//---------------------------------------------------------------------------//
239template <class LocalGridType, class PositionSliceType>
241createParticleDistributor( const LocalGridType& local_grid,
242 PositionSliceType& positions )
243{
244 using memory_space = typename PositionSliceType::memory_space;
245
246 // Get all 26 neighbor ranks.
247 auto topology = getTopology( local_grid );
248
249 Kokkos::View<int*, Kokkos::HostSpace, Kokkos::MemoryUnmanaged>
250 topology_host( topology.data(), topology.size() );
251 auto topology_mirror =
252 Kokkos::create_mirror_view_and_copy( memory_space(), topology_host );
253 Kokkos::View<int*, memory_space> destinations(
254 Kokkos::ViewAllocateWithoutInitializing( "destinations" ),
255 positions.size() );
256
257 // Determine destination ranks for all particles and wrap positions across
258 // periodic boundaries.
259 Impl::getMigrateDestinations( local_grid, topology_mirror, destinations,
260 positions );
261
262 // Create the Cabana distributor.
264 local_grid.globalGrid().comm(), destinations, topology );
265 return distributor;
266}
267
268//---------------------------------------------------------------------------//
287template <class LocalGridType, class ParticlePositions, class ParticleContainer>
288bool particleMigrate( const LocalGridType& local_grid,
289 const ParticlePositions& positions,
290 ParticleContainer& particles, const int min_halo_width,
291 const bool force_migrate = false )
292{
293 // When false, this option checks that any particles are nearly outside the
294 // ghosted halo region (outside the min_halo_width) before initiating
295 // migration. Otherwise, anything outside the local domain is migrated
296 // regardless of position in the halo.
297 if ( !force_migrate )
298 {
299 // Check to see if we need to communicate.
300 auto comm_count = migrateCount( local_grid, positions, min_halo_width );
301
302 // If we have no particles near the ghosted boundary, then exit.
303 if ( 0 == comm_count )
304 return false;
305 }
306
307 auto distributor = createParticleDistributor( local_grid, positions );
308
309 // Redistribute the particles.
310 migrate( distributor, particles );
311 return true;
312}
313
314//---------------------------------------------------------------------------//
335template <class LocalGridType, class ParticlePositions, class ParticleContainer>
336bool particleMigrate( const LocalGridType& local_grid,
337 const ParticlePositions& positions,
338 const ParticleContainer& src_particles,
339 ParticleContainer& dst_particles,
340 const int min_halo_width,
341 const bool force_migrate = false )
342{
343 // When false, this option checks that any particles are nearly outside the
344 // ghosted halo region (outside the min_halo_width) before initiating
345 // migration. Otherwise, anything outside the local domain is migrated
346 // regardless of position in the halo.
347 if ( !force_migrate )
348 {
349 // Check to see if we need to communicate.
350 auto comm_count = migrateCount( local_grid, positions, min_halo_width );
351
352 // If we have no particles near the ghosted boundary, copy, then exit.
353 if ( 0 == comm_count )
354 {
355 Cabana::deep_copy( dst_particles, src_particles );
356 return false;
357 }
358 }
359
360 auto distributor = createParticleDistributor( local_grid, positions );
361
362 // Resize as needed.
363 dst_particles.resize( distributor.totalNumImport() );
364
365 // Redistribute the particles.
366 migrate( distributor, src_particles, dst_particles );
367 return true;
368}
369
371template <class... Args>
372[[deprecated( "Cabana::Grid::particleGridMigrate is now "
373 "Cabana::Grid::particleMigrate. This function wrapper will be "
374 "removed in a future release." )]] auto
375particleGridMigrate( Args&&... args )
376{
377 return Cabana::Grid::particleMigrate( std::forward<Args>( args )... );
378}
379
380template <class... Args>
381[[deprecated(
382 "Cabana::Grid::createParticleGridDistributor is now "
383 "Cabana::Grid::createParticleDistributor. This function wrapper will be "
384 "removed in a future release." )]] auto
385createParticleGridDistributor( Args&&... args )
386{
388 std::forward<Args>( args )... );
389}
391
392} // namespace Grid
393} // namespace Cabana
394
395#endif // end CABANA_PARTICLEGRIDDISTRIBUTOR_HPP
AoSoA and slice extensions for Kokkos deep copy and mirrors.
Multi-node particle redistribution.
LocalMesh< MemorySpace, MeshType > createLocalMesh(const LocalGrid< MeshType > &local_grid)
Creation function for local mesh.
Definition Cabana_Grid_LocalMesh.hpp:791
Cabana::Distributor< typename PositionSliceType::memory_space > createParticleDistributor(const LocalGridType &local_grid, PositionSliceType &positions)
Determine which data should be migrated from one uniquely-owned decomposition to another uniquely-own...
Definition Cabana_Grid_ParticleDistributor.hpp:241
std::enable_if_t< 3==NSD, std::vector< int > > getTopology(const LocalGridType &local_grid)
Build neighbor topology of 27 nearest 3D neighbors. Some of the ranks in this list may be invalid.
Definition Cabana_Grid_ParticleDistributor.hpp:48
int migrateCount(const LocalGridType &local_grid, const PositionSliceType &positions, const int minimum_halo_width)
Check for the number of particles that must be communicated.
Definition Cabana_Grid_ParticleDistributor.hpp:175
bool particleMigrate(const LocalGridType &local_grid, const ParticlePositions &positions, ParticleContainer &particles, const int min_halo_width, const bool force_migrate=false)
Migrate data from one uniquely-owned decomposition to another uniquely-owned decomposition,...
Definition Cabana_Grid_ParticleDistributor.hpp:288
A communication plan for migrating data from one uniquely-owned decomposition to another uniquely own...
Definition Cabana_Distributor.hpp:63
Core: particle data structures and algorithms.
Definition Cabana_AoSoA.hpp:36
void deep_copy(DstAoSoA &dst, const SrcAoSoA &src, typename std::enable_if<(is_aosoa< DstAoSoA >::value &&is_aosoa< SrcAoSoA >::value)>::type *=0)
Deep copy data between compatible AoSoA objects.
Definition Cabana_DeepCopy.hpp:158
void migrate(ExecutionSpace exec_space, const Distributor_t &distributor, const AoSoA_t &src, AoSoA_t &dst, typename std::enable_if<(is_distributor< Distributor_t >::value &&is_aosoa< AoSoA_t >::value), int >::type *=0)
Synchronously migrate data between two different decompositions using the distributor forward communi...
Definition Cabana_Distributor.hpp:335
Ghosted decomposition tag.
Definition Cabana_Grid_Types.hpp:197
Uniform mesh tag.
Definition Cabana_Grid_Types.hpp:227