Loading...
Searching...
No Matches
fv_communication.hpp
Go to the documentation of this file.
1
2#pragma once
3
4/// @file fv_communication.hpp
5/// @brief Ghost layer communication for scalar finite volume (FV) fields on the shell grid.
6///
7/// FV cells are owned exclusively by one subdomain. Each cell array has a one-cell-wide ghost
8/// layer on every side so that operator stencils can read neighbour values without extra index
9/// checks. Unlike the FE communication in communication.hpp — which *adds* contributions from
10/// shared nodes — here we simply *copy* the innermost real cells of the neighbour into our ghost
11/// cells (no reduction required).
12///
13/// Layout reminder (Grid4DDataScalar, shape [n_subdomains, N_lat+1, N_lat+1, N_rad+1]):
14/// - Real cells: indices [1, N_lat-1] × [1, N_lat-1] × [1, N_rad-1]
15/// - Ghost cells: index 0 and index N (for each axis, N = last extent index)
16///
17/// Only face-to-face communication is implemented here. A 6-neighbour cell stencil (±x, ±y, ±r)
18/// never touches edge or vertex ghost cells, so face communication is sufficient.
19///
20/// Typical usage (operator that is applied repeatedly):
21/// @code
22/// // Construct once (allocates Kokkos views for send/recv buffers):
23/// FVGhostLayerBuffers<double> ghost_bufs(domain);
24///
25/// // Call every time the field changes (no allocation, just pack/MPI/unpack):
26/// update_fv_ghost_layers(domain, field, ghost_bufs);
27/// @endcode
28
29#include <algorithm>
30#include <tuple>
31#include <vector>
32
33#include <mpi.h>
34
35#include "grid/grid_types.hpp"
37#include "mpi/mpi.hpp"
38
40
41/// MPI tag for FV ghost layer messages (distinct from MPI_TAG_BOUNDARY_DATA = 100).
42constexpr int MPI_TAG_FV_GHOST_LAYERS = 200;
43
44// ============================================================================
45// Internal pack / unpack kernels
46// ============================================================================
47
48namespace fv_detail {
49
50/// @brief Packs the innermost real-cell layer adjacent to `face` into `buffer`.
51///
52/// For a face whose normal is in direction d:
53/// - Ghost layer lives at data index 0 (P0 side) or extent-1 (P1 side) in d.
54/// - The innermost *real* cells are at index 1 (P0) or extent-2 (P1).
55///
56/// The two varying dimensions are packed in *forward* order:
57/// buffer(i, j) = data(id, ..., i+1, j+1, ...)
58///
59/// The receiver applies BoundaryDirection reversal during unpack if needed.
60template < typename ScalarType >
64 const int local_subdomain_id,
65 const grid::BoundaryFace face )
66{
67 using namespace grid;
68
69 const auto pos_x = boundary_position_from_boundary_type_x( face );
70 const auto pos_y = boundary_position_from_boundary_type_y( face );
71 const auto pos_r = boundary_position_from_boundary_type_r( face );
72
73 const int sx = static_cast< int >( data.extent( 1 ) );
74 const int sy = static_cast< int >( data.extent( 2 ) );
75 const int sr = static_cast< int >( data.extent( 3 ) );
76 const int ni = static_cast< int >( buffer.extent( 0 ) );
77 const int nj = static_cast< int >( buffer.extent( 1 ) );
78 const int id = local_subdomain_id;
79
80 Kokkos::parallel_for(
81 "fv_pack_inner_cells",
82 Kokkos::MDRangePolicy< Kokkos::Rank< 2 > >( { 0, 0 }, { ni, nj } ),
83 KOKKOS_LAMBDA( const int i, const int j ) {
84 int x = 0, y = 0, r = 0;
85
86 if ( pos_x != BoundaryPosition::PV )
87 {
88 x = ( pos_x == BoundaryPosition::P0 ) ? 1 : ( sx - 2 );
89 y = i + 1;
90 r = j + 1;
91 }
92 else if ( pos_y != BoundaryPosition::PV )
93 {
94 x = i + 1;
95 y = ( pos_y == BoundaryPosition::P0 ) ? 1 : ( sy - 2 );
96 r = j + 1;
97 }
98 else
99 {
100 x = i + 1;
101 y = j + 1;
102 r = ( pos_r == BoundaryPosition::P0 ) ? 1 : ( sr - 2 );
103 }
104
105 buffer( i, j ) = data( id, x, y, r );
106 } );
107
108 Kokkos::fence();
109}
110
111/// @brief Writes `buffer` into the ghost cell layer at `face`.
112///
113/// @param dir0 Iteration direction for the first varying dimension.
114/// @param dir1 Iteration direction for the second varying dimension.
115///
116/// Direction mapping (from SubdomainNeighborhood::neighborhood_face()):
117/// FORWARD: buffer index i → data index i+1
118/// BACKWARD: buffer index i → data index (extent - 2 - i)
119template < typename ScalarType >
123 const int local_subdomain_id,
124 const grid::BoundaryFace face,
125 const grid::BoundaryDirection dir0,
126 const grid::BoundaryDirection dir1 )
127{
128 using namespace grid;
129
130 const auto pos_x = boundary_position_from_boundary_type_x( face );
131 const auto pos_y = boundary_position_from_boundary_type_y( face );
132 const auto pos_r = boundary_position_from_boundary_type_r( face );
133
134 const int sx = static_cast< int >( data.extent( 1 ) );
135 const int sy = static_cast< int >( data.extent( 2 ) );
136 const int sr = static_cast< int >( data.extent( 3 ) );
137 const int ni = static_cast< int >( buffer.extent( 0 ) );
138 const int nj = static_cast< int >( buffer.extent( 1 ) );
139 const int id = local_subdomain_id;
140
141 Kokkos::parallel_for(
142 "fv_unpack_to_ghost",
143 Kokkos::MDRangePolicy< Kokkos::Rank< 2 > >( { 0, 0 }, { ni, nj } ),
144 KOKKOS_LAMBDA( const int i, const int j ) {
145 int x = 0, y = 0, r = 0;
146
147 if ( pos_x != BoundaryPosition::PV )
148 {
149 x = ( pos_x == BoundaryPosition::P0 ) ? 0 : ( sx - 1 );
150 y = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sy - 2 - i );
151 r = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sr - 2 - j );
152 }
153 else if ( pos_y != BoundaryPosition::PV )
154 {
155 x = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sx - 2 - i );
156 y = ( pos_y == BoundaryPosition::P0 ) ? 0 : ( sy - 1 );
157 r = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sr - 2 - j );
158 }
159 else
160 {
161 x = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sx - 2 - i );
162 y = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sy - 2 - j );
163 r = ( pos_r == BoundaryPosition::P0 ) ? 0 : ( sr - 1 );
164 }
165
166 data( id, x, y, r ) = buffer( i, j );
167 } );
168
169 Kokkos::fence();
170}
171
172} // namespace fv_detail
173
174// ============================================================================
175// FVGhostLayerBuffers — pre-allocated communication state
176// ============================================================================
177
178/// @brief Pre-allocated send/recv buffers and sorted face-pair lists for FV ghost layer comm.
179///
180/// Construct once per operator (or wherever ghost layer updates are needed), then pass to
181/// `update_fv_ghost_layers` on every call. No heap allocation happens after construction.
182template < typename ScalarType >
184{
185 public:
187
188 /// @brief All metadata needed to drive communication for one subdomain face.
189 struct FacePair
190 {
191 int buf_idx; ///< Index into send_bufs_ / recv_bufs_.
198 int neighbor_subdomain_id; ///< Local array index; -1 if on remote rank.
200 grid::BoundaryDirection dir0; ///< Unpack direction for first varying dimension.
201 grid::BoundaryDirection dir1; ///< Unpack direction for second varying dimension.
202 };
203
204 /// @brief Allocates all send/recv buffers and builds the sorted face pair lists.
205 explicit FVGhostLayerBuffers( const grid::shell::DistributedDomain& domain ) { setup( domain ); }
206
207 /// @brief Face pairs sorted for consistent MPI_Irecv posting order (by sending subdomain).
208 const std::vector< FacePair >& recv_ordered() const { return recv_ordered_; }
209
210 /// @brief Face pairs sorted for consistent MPI_Isend / local-comm packing order.
211 const std::vector< FacePair >& send_ordered() const { return send_ordered_; }
212
213 Buffer& send_buf( const FacePair& fp ) { return send_bufs_[fp.buf_idx]; }
214 Buffer& recv_buf( const FacePair& fp ) { return recv_bufs_[fp.buf_idx]; }
215
216 private:
217 std::vector< Buffer > send_bufs_; ///< One entry per face pair (indexed by FacePair::buf_idx).
218 std::vector< Buffer > recv_bufs_;
219 std::vector< FacePair > recv_ordered_;
220 std::vector< FacePair > send_ordered_;
221
222 void setup( const grid::shell::DistributedDomain& domain )
223 {
224 using namespace grid;
225
226 const int N_lat = domain.domain_info().subdomain_num_nodes_per_side_laterally();
227 const int N_rad = domain.domain_info().subdomain_num_nodes_radially();
228 const int n_lat_cells = N_lat - 1;
229 const int n_rad_cells = N_rad - 1;
230
231 const mpi::MPIRank my_rank = mpi::rank();
232
233 // Build the unsorted master list of face pairs, allocating one buffer per entry.
234 std::vector< FacePair > all_pairs;
235 all_pairs.reserve( domain.subdomains().size() * 6 );
236
237 for ( const auto& [subdomain_info, idx_and_neighborhood] : domain.subdomains() )
238 {
239 const auto& [local_id, neighborhood] = idx_and_neighborhood;
240
241 for ( const auto& [local_face, neighbor_tuple] : neighborhood.neighborhood_face() )
242 {
243 const auto& [neighbor_info, neighbor_face, unpack_dirs, neighbor_rank] = neighbor_tuple;
244
245 // Resolve the neighbour's local array index (only valid for on-rank neighbours).
246 int neighbor_local_id = -1;
247 if ( neighbor_rank == my_rank && domain.subdomains().contains( neighbor_info ) )
248 neighbor_local_id = std::get< 0 >( domain.subdomains().at( neighbor_info ) );
249
250 // Buffer dimensions: lateral×radial for x/y-normal faces, lateral×lateral for r-normal.
251 const auto px = boundary_position_from_boundary_type_x( local_face );
252 const auto py = boundary_position_from_boundary_type_y( local_face );
253 const bool r_normal = ( px == BoundaryPosition::PV && py == BoundaryPosition::PV );
254 const int ni = n_lat_cells;
255 const int nj = r_normal ? n_lat_cells : n_rad_cells;
256
257 const int buf_idx = static_cast< int >( all_pairs.size() );
258 send_bufs_.emplace_back( "fv_ghost_send", ni, nj );
259 recv_bufs_.emplace_back( "fv_ghost_recv", ni, nj );
260
261 all_pairs.push_back( FacePair{
262 .buf_idx = buf_idx,
263 .local_rank = my_rank,
264 .local_subdomain = subdomain_info,
265 .local_subdomain_id = local_id,
266 .local_face = local_face,
267 .neighbor_rank = neighbor_rank,
268 .neighbor_subdomain = neighbor_info,
269 .neighbor_subdomain_id = neighbor_local_id,
270 .neighbor_face = neighbor_face,
271 .dir0 = std::get< 0 >( unpack_dirs ),
272 .dir1 = std::get< 1 >( unpack_dirs ),
273 } );
274 }
275 }
276
277 // Sort for MPI_Irecv: order by sending (neighbour) subdomain so all ranks post recvs
278 // from the same sender in the same order.
279 recv_ordered_ = all_pairs;
280 std::sort( recv_ordered_.begin(), recv_ordered_.end(), []( const FacePair& a, const FacePair& b ) {
281 if ( a.neighbor_subdomain != b.neighbor_subdomain )
282 return a.neighbor_subdomain < b.neighbor_subdomain;
283 if ( a.neighbor_face != b.neighbor_face )
284 return static_cast< int >( a.neighbor_face ) < static_cast< int >( b.neighbor_face );
285 if ( a.local_subdomain != b.local_subdomain )
286 return a.local_subdomain < b.local_subdomain;
287 return static_cast< int >( a.local_face ) < static_cast< int >( b.local_face );
288 } );
289
290 // Sort for MPI_Isend: order by local subdomain.
291 send_ordered_ = all_pairs;
292 std::sort( send_ordered_.begin(), send_ordered_.end(), []( const FacePair& a, const FacePair& b ) {
293 if ( a.local_subdomain != b.local_subdomain )
294 return a.local_subdomain < b.local_subdomain;
295 if ( a.local_face != b.local_face )
296 return static_cast< int >( a.local_face ) < static_cast< int >( b.local_face );
297 if ( a.neighbor_subdomain != b.neighbor_subdomain )
298 return a.neighbor_subdomain < b.neighbor_subdomain;
299 return static_cast< int >( a.neighbor_face ) < static_cast< int >( b.neighbor_face );
300 } );
301 }
302};
303
304// ============================================================================
305// update_fv_ghost_layers — the actual communication call
306// ============================================================================
307
308/// @brief Fills all ghost layers of a scalar FV field from neighbouring subdomains.
309///
310/// Pre-allocated buffers and sorted face-pair lists from `bufs` are used; no allocation occurs.
311///
312/// @param domain The distributed domain.
313/// @param data FV scalar field — ghost cells are written in-place.
314/// @param bufs Pre-allocated buffers constructed for this domain (constructed once, reused).
315template < typename ScalarType >
317 const grid::shell::DistributedDomain& domain,
320{
321 using namespace fv_detail;
322
323 std::vector< MPI_Request > recv_requests;
324 std::vector< MPI_Request > send_requests;
325
326 // Post Irecvs first (recv_ordered guarantees consistent ordering across ranks).
327 for ( const auto& fp : bufs.recv_ordered() )
328 {
329 if ( fp.local_rank == fp.neighbor_rank )
330 continue; // on-rank comm handled below
331
332 auto& recv_buf = bufs.recv_buf( fp );
333 MPI_Request req;
334 MPI_Irecv(
335 recv_buf.data(),
336 static_cast< int >( recv_buf.span() ),
337 mpi::mpi_datatype< ScalarType >(),
338 fp.neighbor_rank,
340 MPI_COMM_WORLD,
341 &req );
342 recv_requests.push_back( req );
343 }
344
345 // Pack and Isend (send_ordered guarantees consistent ordering).
346 for ( const auto& fp : bufs.send_ordered() )
347 {
348 if ( fp.local_rank == fp.neighbor_rank )
349 {
350 // On-rank: read the neighbour's inner cells directly into our recv buffer.
351 pack_inner_cells( bufs.recv_buf( fp ), data, fp.neighbor_subdomain_id, fp.neighbor_face );
352 }
353 else
354 {
355 // Remote: pack our inner cells and send.
356 auto& send_buf = bufs.send_buf( fp );
357 pack_inner_cells( send_buf, data, fp.local_subdomain_id, fp.local_face );
358 // pack_inner_cells() calls Kokkos::fence(), so data is ready for MPI.
359
360 MPI_Request req;
361 MPI_Isend(
362 send_buf.data(),
363 static_cast< int >( send_buf.span() ),
364 mpi::mpi_datatype< ScalarType >(),
365 fp.neighbor_rank,
367 MPI_COMM_WORLD,
368 &req );
369 send_requests.push_back( req );
370 }
371 }
372
373 MPI_Waitall( static_cast< int >( send_requests.size() ), send_requests.data(), MPI_STATUSES_IGNORE );
374 MPI_Waitall( static_cast< int >( recv_requests.size() ), recv_requests.data(), MPI_STATUSES_IGNORE );
375
376 // Unpack all recv buffers into ghost layers (order does not matter here).
377 for ( const auto& fp : bufs.recv_ordered() )
378 {
379 unpack_to_ghost( bufs.recv_buf( fp ), data, fp.local_subdomain_id, fp.local_face, fp.dir0, fp.dir1 );
380 }
381}
382
383/// @brief Convenience overload — allocates temporary buffers internally.
384///
385/// @note THIS ALLOCATES ON EVERY CALL. Use the overload with FVGhostLayerBuffers for any
386/// code that runs repeatedly (iterative solvers, time-stepping loops).
387template < typename ScalarType >
389 const grid::shell::DistributedDomain& domain,
391{
392 FVGhostLayerBuffers< ScalarType > tmp_bufs( domain );
393 update_fv_ghost_layers( domain, data, tmp_bufs );
394}
395
396// ============================================================================
397// Vector-valued FV ghost layer communication
398// ============================================================================
399
400/// MPI tag for vector-valued FV ghost layer messages.
401constexpr int MPI_TAG_FV_VEC_GHOST_LAYERS = 201;
402
403namespace fv_detail {
404
405/// @brief Packs the innermost real-cell layer adjacent to `face` into `buffer` for a vector field.
406///
407/// buffer has shape [ni, nj, VecDim] and packs all VecDim components together so a single
408/// MPI message per face suffices.
409template < typename ScalarType, int VecDim >
413 const int local_subdomain_id,
414 const grid::BoundaryFace face )
415{
416 using namespace grid;
417
418 const auto pos_x = boundary_position_from_boundary_type_x( face );
419 const auto pos_y = boundary_position_from_boundary_type_y( face );
420 const auto pos_r = boundary_position_from_boundary_type_r( face );
421
422 const int sx = static_cast< int >( data.extent( 1 ) );
423 const int sy = static_cast< int >( data.extent( 2 ) );
424 const int sr = static_cast< int >( data.extent( 3 ) );
425 const int ni = static_cast< int >( buffer.extent( 0 ) );
426 const int nj = static_cast< int >( buffer.extent( 1 ) );
427 const int id = local_subdomain_id;
428
429 Kokkos::parallel_for(
430 "fv_pack_inner_cells_vec",
431 Kokkos::MDRangePolicy< Kokkos::Rank< 2 > >( { 0, 0 }, { ni, nj } ),
432 KOKKOS_LAMBDA( const int i, const int j ) {
433 int x = 0, y = 0, r = 0;
434
435 if ( pos_x != BoundaryPosition::PV )
436 {
437 x = ( pos_x == BoundaryPosition::P0 ) ? 1 : ( sx - 2 );
438 y = i + 1;
439 r = j + 1;
440 }
441 else if ( pos_y != BoundaryPosition::PV )
442 {
443 x = i + 1;
444 y = ( pos_y == BoundaryPosition::P0 ) ? 1 : ( sy - 2 );
445 r = j + 1;
446 }
447 else
448 {
449 x = i + 1;
450 y = j + 1;
451 r = ( pos_r == BoundaryPosition::P0 ) ? 1 : ( sr - 2 );
452 }
453
454 for ( int d = 0; d < VecDim; ++d )
455 buffer( i, j, d ) = data( id, x, y, r, d );
456 } );
457
458 Kokkos::fence();
459}
460
461/// @brief Writes `buffer` into the ghost cell layer at `face` for a vector field.
462template < typename ScalarType, int VecDim >
466 const int local_subdomain_id,
467 const grid::BoundaryFace face,
468 const grid::BoundaryDirection dir0,
469 const grid::BoundaryDirection dir1 )
470{
471 using namespace grid;
472
473 const auto pos_x = boundary_position_from_boundary_type_x( face );
474 const auto pos_y = boundary_position_from_boundary_type_y( face );
475 const auto pos_r = boundary_position_from_boundary_type_r( face );
476
477 const int sx = static_cast< int >( data.extent( 1 ) );
478 const int sy = static_cast< int >( data.extent( 2 ) );
479 const int sr = static_cast< int >( data.extent( 3 ) );
480 const int ni = static_cast< int >( buffer.extent( 0 ) );
481 const int nj = static_cast< int >( buffer.extent( 1 ) );
482 const int id = local_subdomain_id;
483
484 Kokkos::parallel_for(
485 "fv_unpack_to_ghost_vec",
486 Kokkos::MDRangePolicy< Kokkos::Rank< 2 > >( { 0, 0 }, { ni, nj } ),
487 KOKKOS_LAMBDA( const int i, const int j ) {
488 int x = 0, y = 0, r = 0;
489
490 if ( pos_x != BoundaryPosition::PV )
491 {
492 x = ( pos_x == BoundaryPosition::P0 ) ? 0 : ( sx - 1 );
493 y = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sy - 2 - i );
494 r = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sr - 2 - j );
495 }
496 else if ( pos_y != BoundaryPosition::PV )
497 {
498 x = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sx - 2 - i );
499 y = ( pos_y == BoundaryPosition::P0 ) ? 0 : ( sy - 1 );
500 r = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sr - 2 - j );
501 }
502 else
503 {
504 x = ( dir0 == BoundaryDirection::FORWARD ) ? ( i + 1 ) : ( sx - 2 - i );
505 y = ( dir1 == BoundaryDirection::FORWARD ) ? ( j + 1 ) : ( sy - 2 - j );
506 r = ( pos_r == BoundaryPosition::P0 ) ? 0 : ( sr - 1 );
507 }
508
509 for ( int d = 0; d < VecDim; ++d )
510 data( id, x, y, r, d ) = buffer( i, j, d );
511 } );
512
513 Kokkos::fence();
514}
515
516} // namespace fv_detail
517
518/// @brief Pre-allocated send/recv buffers for vector-valued FV ghost layer communication.
519///
520/// Mirrors FVGhostLayerBuffers but uses 3D buffers [ni, nj, VecDim] so all VecDim components
521/// are packed and sent in a single MPI message per face.
522template < typename ScalarType, int VecDim >
524{
525 public:
527
542
543 explicit FVGhostLayerVecBuffers( const grid::shell::DistributedDomain& domain ) { setup( domain ); }
544
545 const std::vector< FacePair >& recv_ordered() const { return recv_ordered_; }
546 const std::vector< FacePair >& send_ordered() const { return send_ordered_; }
547
548 Buffer& send_buf( const FacePair& fp ) { return send_bufs_[fp.buf_idx]; }
549 Buffer& recv_buf( const FacePair& fp ) { return recv_bufs_[fp.buf_idx]; }
550
551 private:
552 std::vector< Buffer > send_bufs_;
553 std::vector< Buffer > recv_bufs_;
554 std::vector< FacePair > recv_ordered_;
555 std::vector< FacePair > send_ordered_;
556
557 void setup( const grid::shell::DistributedDomain& domain )
558 {
559 using namespace grid;
560
561 const int N_lat = domain.domain_info().subdomain_num_nodes_per_side_laterally();
562 const int N_rad = domain.domain_info().subdomain_num_nodes_radially();
563 const int n_lat_cells = N_lat - 1;
564 const int n_rad_cells = N_rad - 1;
565
566 const mpi::MPIRank my_rank = mpi::rank();
567
568 std::vector< FacePair > all_pairs;
569 all_pairs.reserve( domain.subdomains().size() * 6 );
570
571 for ( const auto& [subdomain_info, idx_and_neighborhood] : domain.subdomains() )
572 {
573 const auto& [local_id, neighborhood] = idx_and_neighborhood;
574
575 for ( const auto& [local_face, neighbor_tuple] : neighborhood.neighborhood_face() )
576 {
577 const auto& [neighbor_info, neighbor_face, unpack_dirs, neighbor_rank] = neighbor_tuple;
578
579 int neighbor_local_id = -1;
580 if ( neighbor_rank == my_rank && domain.subdomains().contains( neighbor_info ) )
581 neighbor_local_id = std::get< 0 >( domain.subdomains().at( neighbor_info ) );
582
583 const auto px = boundary_position_from_boundary_type_x( local_face );
584 const auto py = boundary_position_from_boundary_type_y( local_face );
585 const bool r_normal = ( px == BoundaryPosition::PV && py == BoundaryPosition::PV );
586 const int ni = n_lat_cells;
587 const int nj = r_normal ? n_lat_cells : n_rad_cells;
588
589 const int buf_idx = static_cast< int >( all_pairs.size() );
590 send_bufs_.emplace_back( "fv_ghost_vec_send", ni, nj, VecDim );
591 recv_bufs_.emplace_back( "fv_ghost_vec_recv", ni, nj, VecDim );
592
593 all_pairs.push_back( FacePair{
594 .buf_idx = buf_idx,
595 .local_rank = my_rank,
596 .local_subdomain = subdomain_info,
597 .local_subdomain_id = local_id,
598 .local_face = local_face,
599 .neighbor_rank = neighbor_rank,
600 .neighbor_subdomain = neighbor_info,
601 .neighbor_subdomain_id = neighbor_local_id,
602 .neighbor_face = neighbor_face,
603 .dir0 = std::get< 0 >( unpack_dirs ),
604 .dir1 = std::get< 1 >( unpack_dirs ),
605 } );
606 }
607 }
608
609 recv_ordered_ = all_pairs;
610 std::sort( recv_ordered_.begin(), recv_ordered_.end(), []( const FacePair& a, const FacePair& b ) {
611 if ( a.neighbor_subdomain != b.neighbor_subdomain )
612 return a.neighbor_subdomain < b.neighbor_subdomain;
613 if ( a.neighbor_face != b.neighbor_face )
614 return static_cast< int >( a.neighbor_face ) < static_cast< int >( b.neighbor_face );
615 if ( a.local_subdomain != b.local_subdomain )
616 return a.local_subdomain < b.local_subdomain;
617 return static_cast< int >( a.local_face ) < static_cast< int >( b.local_face );
618 } );
619
620 send_ordered_ = all_pairs;
621 std::sort( send_ordered_.begin(), send_ordered_.end(), []( const FacePair& a, const FacePair& b ) {
622 if ( a.local_subdomain != b.local_subdomain )
623 return a.local_subdomain < b.local_subdomain;
624 if ( a.local_face != b.local_face )
625 return static_cast< int >( a.local_face ) < static_cast< int >( b.local_face );
626 if ( a.neighbor_subdomain != b.neighbor_subdomain )
627 return a.neighbor_subdomain < b.neighbor_subdomain;
628 return static_cast< int >( a.neighbor_face ) < static_cast< int >( b.neighbor_face );
629 } );
630 }
631};
632
633/// @brief Fills all ghost layers of a vector-valued FV field from neighbouring subdomains.
634///
635/// @param domain The distributed domain.
636/// @param data FV vector field — ghost cells are written in-place.
637/// @param bufs Pre-allocated buffers (constructed once, reused).
638template < typename ScalarType, int VecDim >
640 const grid::shell::DistributedDomain& domain,
643{
644 using namespace fv_detail;
645
646 std::vector< MPI_Request > recv_requests;
647 std::vector< MPI_Request > send_requests;
648
649 for ( const auto& fp : bufs.recv_ordered() )
650 {
651 if ( fp.local_rank == fp.neighbor_rank )
652 continue;
653
654 auto& recv_buf = bufs.recv_buf( fp );
655 MPI_Request req;
656 MPI_Irecv(
657 recv_buf.data(),
658 static_cast< int >( recv_buf.span() ),
659 mpi::mpi_datatype< ScalarType >(),
660 fp.neighbor_rank,
662 MPI_COMM_WORLD,
663 &req );
664 recv_requests.push_back( req );
665 }
666
667 for ( const auto& fp : bufs.send_ordered() )
668 {
669 if ( fp.local_rank == fp.neighbor_rank )
670 {
671 pack_inner_cells_vec( bufs.recv_buf( fp ), data, fp.neighbor_subdomain_id, fp.neighbor_face );
672 }
673 else
674 {
675 auto& send_buf = bufs.send_buf( fp );
676 pack_inner_cells_vec( send_buf, data, fp.local_subdomain_id, fp.local_face );
677
678 MPI_Request req;
679 MPI_Isend(
680 send_buf.data(),
681 static_cast< int >( send_buf.span() ),
682 mpi::mpi_datatype< ScalarType >(),
683 fp.neighbor_rank,
685 MPI_COMM_WORLD,
686 &req );
687 send_requests.push_back( req );
688 }
689 }
690
691 MPI_Waitall( static_cast< int >( send_requests.size() ), send_requests.data(), MPI_STATUSES_IGNORE );
692 MPI_Waitall( static_cast< int >( recv_requests.size() ), recv_requests.data(), MPI_STATUSES_IGNORE );
693
694 for ( const auto& fp : bufs.recv_ordered() )
695 {
696 unpack_to_ghost_vec( bufs.recv_buf( fp ), data, fp.local_subdomain_id, fp.local_face, fp.dir0, fp.dir1 );
697 }
698}
699
700/// @brief Convenience overload for vector fields — allocates temporary buffers internally.
701///
702/// @note THIS ALLOCATES ON EVERY CALL. Prefer the overload with FVGhostLayerVecBuffers for
703/// any code that runs repeatedly.
704template < typename ScalarType, int VecDim >
706 const grid::shell::DistributedDomain& domain,
708{
710 update_fv_ghost_layers( domain, data, tmp_bufs );
711}
712
713} // namespace terra::communication::shell
Pre-allocated send/recv buffers and sorted face-pair lists for FV ghost layer comm.
Definition fv_communication.hpp:184
Buffer & send_buf(const FacePair &fp)
Definition fv_communication.hpp:213
grid::Grid2DDataScalar< ScalarType > Buffer
Definition fv_communication.hpp:186
Buffer & recv_buf(const FacePair &fp)
Definition fv_communication.hpp:214
const std::vector< FacePair > & send_ordered() const
Face pairs sorted for consistent MPI_Isend / local-comm packing order.
Definition fv_communication.hpp:211
const std::vector< FacePair > & recv_ordered() const
Face pairs sorted for consistent MPI_Irecv posting order (by sending subdomain).
Definition fv_communication.hpp:208
FVGhostLayerBuffers(const grid::shell::DistributedDomain &domain)
Allocates all send/recv buffers and builds the sorted face pair lists.
Definition fv_communication.hpp:205
Pre-allocated send/recv buffers for vector-valued FV ghost layer communication.
Definition fv_communication.hpp:524
const std::vector< FacePair > & send_ordered() const
Definition fv_communication.hpp:546
const std::vector< FacePair > & recv_ordered() const
Definition fv_communication.hpp:545
Buffer & send_buf(const FacePair &fp)
Definition fv_communication.hpp:548
grid::Grid3DDataScalar< ScalarType > Buffer
Definition fv_communication.hpp:526
Buffer & recv_buf(const FacePair &fp)
Definition fv_communication.hpp:549
FVGhostLayerVecBuffers(const grid::shell::DistributedDomain &domain)
Definition fv_communication.hpp:543
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2518
const std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > > & subdomains() const
Definition spherical_shell.hpp:2650
const DomainInfo & domain_info() const
Returns a const reference.
Definition spherical_shell.hpp:2647
int subdomain_num_nodes_radially() const
Equivalent to calling subdomain_num_nodes_radially( subdomain_refinement_level() )
Definition spherical_shell.hpp:861
int subdomain_num_nodes_per_side_laterally() const
Equivalent to calling subdomain_num_nodes_per_side_laterally( subdomain_refinement_level() )
Definition spherical_shell.hpp:852
(Sortable) Globally unique identifier for a single subdomain of a diamond.
Definition spherical_shell.hpp:595
void unpack_to_ghost_vec(const grid::Grid3DDataScalar< ScalarType > &buffer, const grid::Grid4DDataVec< ScalarType, VecDim > &data, const int local_subdomain_id, const grid::BoundaryFace face, const grid::BoundaryDirection dir0, const grid::BoundaryDirection dir1)
Writes buffer into the ghost cell layer at face for a vector field.
Definition fv_communication.hpp:463
void pack_inner_cells(const grid::Grid2DDataScalar< ScalarType > &buffer, const grid::Grid4DDataScalar< ScalarType > &data, const int local_subdomain_id, const grid::BoundaryFace face)
Packs the innermost real-cell layer adjacent to face into buffer.
Definition fv_communication.hpp:61
void unpack_to_ghost(const grid::Grid2DDataScalar< ScalarType > &buffer, const grid::Grid4DDataScalar< ScalarType > &data, const int local_subdomain_id, const grid::BoundaryFace face, const grid::BoundaryDirection dir0, const grid::BoundaryDirection dir1)
Writes buffer into the ghost cell layer at face.
Definition fv_communication.hpp:120
void pack_inner_cells_vec(const grid::Grid3DDataScalar< ScalarType > &buffer, const grid::Grid4DDataVec< ScalarType, VecDim > &data, const int local_subdomain_id, const grid::BoundaryFace face)
Packs the innermost real-cell layer adjacent to face into buffer for a vector field.
Definition fv_communication.hpp:410
Definition communication.hpp:14
constexpr int MPI_TAG_FV_GHOST_LAYERS
MPI tag for FV ghost layer messages (distinct from MPI_TAG_BOUNDARY_DATA = 100).
Definition fv_communication.hpp:42
constexpr int MPI_TAG_FV_VEC_GHOST_LAYERS
MPI tag for vector-valued FV ghost layer messages.
Definition fv_communication.hpp:401
void update_fv_ghost_layers(const grid::shell::DistributedDomain &domain, const grid::Grid4DDataScalar< ScalarType > &data, FVGhostLayerBuffers< ScalarType > &bufs)
Fills all ghost layers of a scalar FV field from neighbouring subdomains.
Definition fv_communication.hpp:316
Kokkos::View< ScalarType ***, Layout > Grid3DDataScalar
Definition grid_types.hpp:24
BoundaryDirection
Enum for the iteration direction at a boundary.
Definition grid_types.hpp:311
BoundaryFace
Enum for identification of the 6 boundary faces of a subdomain.
Definition grid_types.hpp:297
Kokkos::View< ScalarType ****, Layout > Grid4DDataScalar
Definition grid_types.hpp:27
Kokkos::View< ScalarType **, Layout > Grid2DDataScalar
Definition grid_types.hpp:21
int MPIRank
Definition mpi.hpp:11
MPIRank rank()
Definition mpi.hpp:13
All metadata needed to drive communication for one subdomain face.
Definition fv_communication.hpp:190
mpi::MPIRank neighbor_rank
Definition fv_communication.hpp:196
grid::shell::SubdomainInfo neighbor_subdomain
Definition fv_communication.hpp:197
grid::BoundaryFace local_face
Definition fv_communication.hpp:195
int buf_idx
Index into send_bufs_ / recv_bufs_.
Definition fv_communication.hpp:191
grid::BoundaryDirection dir0
Unpack direction for first varying dimension.
Definition fv_communication.hpp:200
int local_subdomain_id
Definition fv_communication.hpp:194
mpi::MPIRank local_rank
Definition fv_communication.hpp:192
int neighbor_subdomain_id
Local array index; -1 if on remote rank.
Definition fv_communication.hpp:198
grid::shell::SubdomainInfo local_subdomain
Definition fv_communication.hpp:193
grid::BoundaryDirection dir1
Unpack direction for second varying dimension.
Definition fv_communication.hpp:201
grid::BoundaryFace neighbor_face
Definition fv_communication.hpp:199
grid::shell::SubdomainInfo neighbor_subdomain
Definition fv_communication.hpp:536
mpi::MPIRank neighbor_rank
Definition fv_communication.hpp:535
grid::shell::SubdomainInfo local_subdomain
Definition fv_communication.hpp:532
int neighbor_subdomain_id
Definition fv_communication.hpp:537
grid::BoundaryDirection dir1
Definition fv_communication.hpp:540
grid::BoundaryFace local_face
Definition fv_communication.hpp:534
grid::BoundaryDirection dir0
Definition fv_communication.hpp:539
mpi::MPIRank local_rank
Definition fv_communication.hpp:531
int local_subdomain_id
Definition fv_communication.hpp:533
grid::BoundaryFace neighbor_face
Definition fv_communication.hpp:538
SoA (Structure-of-Arrays) 4D vector grid data.
Definition grid_types.hpp:51
auto extent(int i) const
Definition grid_types.hpp:75