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 CommSpaceType = Mpi, class LocalGridType,
240 class PositionSliceType>
242createParticleDistributor( const LocalGridType& local_grid,
243 PositionSliceType& positions )
244{
245 using memory_space = typename PositionSliceType::memory_space;
246
247 // Get all 26 neighbor ranks.
248 auto topology = getTopology( local_grid );
249
250 Kokkos::View<int*, Kokkos::HostSpace, Kokkos::MemoryUnmanaged>
251 topology_host( topology.data(), topology.size() );
252 auto topology_mirror =
253 Kokkos::create_mirror_view_and_copy( memory_space(), topology_host );
254 Kokkos::View<int*, memory_space> destinations(
255 Kokkos::ViewAllocateWithoutInitializing( "destinations" ),
256 positions.size() );
257
258 // Determine destination ranks for all particles and wrap positions across
259 // periodic boundaries.
260 Impl::getMigrateDestinations( local_grid, topology_mirror, destinations,
261 positions );
262
263 // Create the Cabana distributor.
265 local_grid.globalGrid().comm(), destinations, topology );
266 return distributor;
267}
268
269//---------------------------------------------------------------------------//
288template <class CommSpaceType = Mpi, class LocalGridType,
289 class ParticlePositions, class ParticleContainer>
290bool particleMigrate( const LocalGridType& local_grid,
291 const ParticlePositions& positions,
292 ParticleContainer& particles, const int min_halo_width,
293 const bool force_migrate = false )
294{
295 // When false, this option checks that any particles are nearly outside the
296 // ghosted halo region (outside the min_halo_width) before initiating
297 // migration. Otherwise, anything outside the local domain is migrated
298 // regardless of position in the halo.
299 if ( !force_migrate )
300 {
301 // Check to see if we need to communicate.
302 auto comm_count = migrateCount( local_grid, positions, min_halo_width );
303
304 // If we have no particles near the ghosted boundary, then exit.
305 if ( 0 == comm_count )
306 return false;
307 }
308
309 auto distributor =
310 createParticleDistributor<CommSpaceType>( local_grid, positions );
311
312 // Redistribute the particles.
313 migrate( distributor, particles );
314 return true;
315}
316
317//---------------------------------------------------------------------------//
338template <class CommSpaceType = Mpi, class LocalGridType,
339 class ParticlePositions, class ParticleContainer>
340bool particleMigrate( const LocalGridType& local_grid,
341 const ParticlePositions& positions,
342 const ParticleContainer& src_particles,
343 ParticleContainer& dst_particles,
344 const int min_halo_width,
345 const bool force_migrate = false )
346{
347 // When false, this option checks that any particles are nearly outside the
348 // ghosted halo region (outside the min_halo_width) before initiating
349 // migration. Otherwise, anything outside the local domain is migrated
350 // regardless of position in the halo.
351 if ( !force_migrate )
352 {
353 // Check to see if we need to communicate.
354 auto comm_count = migrateCount( local_grid, positions, min_halo_width );
355
356 // If we have no particles near the ghosted boundary, copy, then exit.
357 if ( 0 == comm_count )
358 {
359 Cabana::deep_copy( dst_particles, src_particles );
360 return false;
361 }
362 }
363
364 auto distributor =
365 createParticleDistributor<CommSpaceType>( local_grid, positions );
366
367 // Resize as needed.
368 dst_particles.resize( distributor.totalNumImport() );
369
370 // Redistribute the particles.
371 migrate( distributor, src_particles, dst_particles );
372 return true;
373}
374
376template <class... Args>
377[[deprecated( "Cabana::Grid::particleGridMigrate is now "
378 "Cabana::Grid::particleMigrate. This function wrapper will be "
379 "removed in a future release." )]] auto
380particleGridMigrate( Args&&... args )
381{
382 return Cabana::Grid::particleMigrate( std::forward<Args>( args )... );
383}
384
385template <class... Args>
386[[deprecated(
387 "Cabana::Grid::createParticleGridDistributor is now "
388 "Cabana::Grid::createParticleDistributor. This function wrapper will be "
389 "removed in a future release." )]] auto
390createParticleGridDistributor( Args&&... args )
391{
393 std::forward<Args>( args )... );
394}
396
397} // namespace Grid
398} // namespace Cabana
399
400#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:779
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:290
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
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:242
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
A communication plan for migrating data from one uniquely-owned decomposition to another uniquely own...
Definition Cabana_Distributor.hpp:64
Core: particle data structures and algorithms.
Definition Cabana_AoSoA.hpp:36
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:203
void deep_copy(DstAoSoA &dst, const SrcAoSoA &src, std::enable_if_t<(is_aosoa< DstAoSoA >::value &&is_aosoa< SrcAoSoA >::value)> *=nullptr)
Deep copy data between compatible AoSoA objects.
Definition Cabana_DeepCopy.hpp:156
Ghosted decomposition tag.
Definition Cabana_Grid_Types.hpp:197
Uniform mesh tag.
Definition Cabana_Grid_Types.hpp:227
Vanilla MPI backend tag - default.
Definition Cabana_Tags.hpp:28