DOLFINx 0.10.0.0
DOLFINx C++ interface
Loading...
Searching...
No Matches
Scatterer.h
1// Copyright (C) 2022-2025 Igor Baratta and Garth N. Wells
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "IndexMap.h"
10#include "MPI.h"
11#include "sort.h"
12#include <algorithm>
13#include <concepts>
14#include <functional>
15#include <memory>
16#include <mpi.h>
17#include <numeric>
18#include <span>
19#include <type_traits>
20#include <vector>
21
22namespace dolfinx::common
23{
24
47template <class Container = std::vector<std::int32_t>>
48class Scatterer
49{
50 static_assert(std::is_integral_v<typename Container::value_type>);
51
52 template <class>
53 friend class Scatterer;
54
55public:
57 using container_type = Container;
58
66 Scatterer(const IndexMap& map, int bs)
67 : _src(map.src().begin(), map.src().end()),
68 _dest(map.dest().begin(), map.dest().end()),
69 _sizes_remote(_src.size(), 0), _displs_remote(_src.size() + 1),
70 _sizes_local(_dest.size()), _displs_local(_dest.size() + 1)
71 {
72 if (dolfinx::MPI::size(map.comm()) == 1)
73 return;
74
75 int ierr;
76
77 // Check that src and dest ranks are unique and sorted
78 assert(std::ranges::is_sorted(_src));
79 assert(std::ranges::is_sorted(_dest));
80
81 // Create communicators with directed edges:
82 // (0) owner -> ghost,
83 // (1) ghost -> owner
84 MPI_Comm comm0;
85 ierr = MPI_Dist_graph_create_adjacent(
86 map.comm(), _src.size(), _src.data(), MPI_UNWEIGHTED, _dest.size(),
87 _dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
88 _comm0 = dolfinx::MPI::Comm(comm0, false);
90
91 MPI_Comm comm1;
92 ierr = MPI_Dist_graph_create_adjacent(
93 map.comm(), _dest.size(), _dest.data(), MPI_UNWEIGHTED, _src.size(),
94 _src.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm1);
95 _comm1 = dolfinx::MPI::Comm(comm1, false);
97
98 // Build permutation array that sorts ghost indices by owning rank
99 std::span owners = map.owners();
100 std::vector<std::int32_t> perm(owners.size());
101 std::iota(perm.begin(), perm.end(), 0);
102 dolfinx::radix_sort(perm, [&owners](auto index) { return owners[index]; });
103
104 // Sort (i) ghost indices and (ii) ghost index owners by rank
105 // (using perm array)
106 std::span ghosts = map.ghosts();
107 std::vector<int> owners_sorted(owners.size());
108 std::vector<std::int64_t> ghosts_sorted(owners.size());
109 std::ranges::transform(perm, owners_sorted.begin(),
110 [&owners](auto idx) { return owners[idx]; });
111 std::ranges::transform(perm, ghosts_sorted.begin(),
112 [&ghosts](auto idx) { return ghosts[idx]; });
113
114 // For data associated with ghost indices, packed by owning
115 // (neighbourhood) rank, compute sizes and displacements. I.e., when
116 // sending ghost index data from this rank to the owning ranks,
117 // disp[i] is the first entry in the buffer sent to neighbourhood
118 // rank i, and disp[i + 1] - disp[i] is the number of values sent to
119 // rank i.
120 assert(_sizes_remote.size() == _src.size());
121 assert(_displs_remote.size() == _src.size() + 1);
122 auto begin = owners_sorted.begin();
123 for (std::size_t i = 0; i < _src.size(); i++)
124 {
125 auto upper = std::upper_bound(begin, owners_sorted.end(), _src[i]);
126 std::size_t num_ind = std::distance(begin, upper);
127 _displs_remote[i + 1] = _displs_remote[i] + num_ind;
128 _sizes_remote[i] = num_ind;
129 begin = upper;
130 }
131
132 // For data associated with owned indices that are ghosted by other
133 // ranks, compute the size and displacement arrays. When sending
134 // data associated with ghost indices to the owner, these size and
135 // displacement arrays are for the receive buffer.
136
137 // Compute sizes and displacements of local data (how many local
138 // elements to be sent/received grouped by neighbors)
139 assert(_sizes_local.size() == _dest.size());
140 assert(_displs_local.size() == _dest.size() + 1);
141 _sizes_remote.reserve(1);
142 _sizes_local.reserve(1);
143 ierr = MPI_Neighbor_alltoall(_sizes_remote.data(), 1, MPI_INT32_T,
144 _sizes_local.data(), 1, MPI_INT32_T,
145 _comm1.comm());
146 dolfinx::MPI::check_error(_comm1.comm(), ierr);
147
148 std::partial_sum(_sizes_local.begin(), _sizes_local.end(),
149 std::next(_displs_local.begin()));
150
151 assert((int)ghosts_sorted.size() == _displs_remote.back());
152 assert((int)ghosts_sorted.size() == _displs_remote.back());
153
154 // Send ghost global indices to owning rank, and receive owned
155 // indices that are ghosts on other ranks
156 std::vector<std::int64_t> recv_buffer(_displs_local.back(), 0);
157 ierr = MPI_Neighbor_alltoallv(
158 ghosts_sorted.data(), _sizes_remote.data(), _displs_remote.data(),
159 MPI_INT64_T, recv_buffer.data(), _sizes_local.data(),
160 _displs_local.data(), MPI_INT64_T, _comm1.comm());
161 dolfinx::MPI::check_error(_comm1.comm(), ierr);
162
163 const std::array<std::int64_t, 2> range = map.local_range();
164#ifndef NDEBUG
165 // Check that all received indice are within the owned range
166 std::ranges::for_each(recv_buffer, [range](auto idx)
167 { assert(idx >= range[0] and idx < range[1]); });
168#endif
169
170 {
171 // Scale sizes and displacements by block size
172 for (auto& x : {std::ref(_sizes_local), std::ref(_displs_local),
173 std::ref(_sizes_remote), std::ref(_displs_remote)})
174 {
175 std::ranges::transform(x.get(), x.get().begin(),
176 [bs](auto e) { return e *= bs; });
177 }
178 }
179
180 {
181 // Expand local indices using block size and convert it from
182 // global to local numbering
183 std::vector<typename container_type::value_type> idx(recv_buffer.size()
184 * bs);
185 std::int64_t offset = range[0] * bs;
186 for (std::size_t i = 0; i < recv_buffer.size(); i++)
187 for (int j = 0; j < bs; j++)
188 idx[i * bs + j] = (recv_buffer[i] * bs + j) - offset;
189 _local_inds = std::move(idx);
190 }
191
192 {
193 // Expand remote indices using block size
194 std::vector<typename container_type::value_type> idx(perm.size() * bs);
195 for (std::size_t i = 0; i < perm.size(); i++)
196 for (int j = 0; j < bs; j++)
197 idx[i * bs + j] = perm[i] * bs + j;
198 _remote_inds = std::move(idx);
199 }
200 }
201
203 Scatterer(const Scatterer& scatterer) = default;
204
218 template <class U>
219 Scatterer(const Scatterer<U>& s)
220 : _comm0(s._comm0), _comm1(s._comm1), _src(s._src), _dest(s._dest),
221 _remote_inds(s._remote_inds.begin(), s._remote_inds.end()),
222 _sizes_remote(s._sizes_remote), _displs_remote(s._displs_remote),
223 _local_inds(s._local_inds.begin(), s._local_inds.end()),
224 _sizes_local(s._sizes_local), _displs_local(s._displs_local)
225 {
226 }
227
253 template <typename T>
254 void scatter_fwd_begin(const T* send_buffer, T* recv_buffer,
255 MPI_Request& request) const
256 {
257 // Return early if there are no incoming or outgoing edges
258 if (_sizes_local.empty() and _sizes_remote.empty())
259 return;
260
261 int ierr = MPI_Ineighbor_alltoallv(
262 send_buffer, _sizes_local.data(), _displs_local.data(),
263 dolfinx::MPI::mpi_t<T>, recv_buffer, _sizes_remote.data(),
264 _displs_remote.data(), dolfinx::MPI::mpi_t<T>, _comm0.comm(), &request);
265 dolfinx::MPI::check_error(_comm0.comm(), ierr);
266 }
267
281 template <typename T>
282 void scatter_fwd_begin(const T* send_buffer, T* recv_buffer,
283 std::span<MPI_Request> requests) const
284 {
285 if (requests.size() != _dest.size() + _src.size())
286 {
287 throw std::runtime_error(
288 "Point-to-point scatterer has wrong number of MPI_Requests.");
289 }
290
291 // Return early if there are no incoming or outgoing edges
292 if (_sizes_local.empty() and _sizes_remote.empty())
293 return;
294
295 for (std::size_t i = 0; i < _src.size(); ++i)
296 {
297 int ierr = MPI_Irecv(recv_buffer + _displs_remote[i], _sizes_remote[i],
298 dolfinx::MPI::mpi_t<T>, _src[i], MPI_ANY_TAG,
299 _comm0.comm(), &requests[i]);
300 dolfinx::MPI::check_error(_comm0.comm(), ierr);
301 }
302
303 for (std::size_t i = 0; i < _dest.size(); ++i)
304 {
305 int ierr = MPI_Isend(send_buffer + _displs_local[i], _sizes_local[i],
306 dolfinx::MPI::mpi_t<T>, _dest[i], 0, _comm0.comm(),
307 &requests[i + _src.size()]);
308 dolfinx::MPI::check_error(_comm0.comm(), ierr);
309 }
310 }
311
338 template <typename T>
339 void scatter_rev_begin(const T* send_buffer, T* recv_buffer,
340 MPI_Request& request) const
341 {
342 // Return early if there are no incoming or outgoing edges
343 if (_sizes_local.empty() and _sizes_remote.empty())
344 return;
345
346 int ierr = MPI_Ineighbor_alltoallv(
347 send_buffer, _sizes_remote.data(), _displs_remote.data(), MPI::mpi_t<T>,
348 recv_buffer, _sizes_local.data(), _displs_local.data(), MPI::mpi_t<T>,
349 _comm1.comm(), &request);
350 dolfinx::MPI::check_error(_comm1.comm(), ierr);
351 }
352
366 template <typename T>
367 void scatter_rev_begin(const T* send_buffer, T* recv_buffer,
368 std::span<MPI_Request> requests) const
369 {
370 if (requests.size() != _dest.size() + _src.size())
371 {
372 throw std::runtime_error(
373 "Point-to-point scatterer has wrong number of MPI_Requests.");
374 }
375
376 // Return early if there are no incoming or outgoing edges
377 if (_sizes_local.empty() and _sizes_remote.empty())
378 return;
379
380 // Start non-blocking send from this process to ghost owners
381 for (std::size_t i = 0; i < _dest.size(); i++)
382 {
383 int ierr = MPI_Irecv(recv_buffer + _displs_local[i], _sizes_local[i],
384 dolfinx::MPI::mpi_t<T>, _dest[i], MPI_ANY_TAG,
385 _comm0.comm(), &requests[i]);
386 dolfinx::MPI::check_error(_comm0.comm(), ierr);
387 }
388
389 // Start non-blocking receive from neighbor process for which an
390 // owned index is a ghost
391 for (std::size_t i = 0; i < _src.size(); i++)
392 {
393 int ierr = MPI_Isend(send_buffer + _displs_remote[i], _sizes_remote[i],
394 dolfinx::MPI::mpi_t<T>, _src[i], 0, _comm0.comm(),
395 &requests[i + _dest.size()]);
396 dolfinx::MPI::check_error(_comm0.comm(), ierr);
397 }
398 }
399
407 void scatter_end(std::span<MPI_Request> requests) const
408 {
409 // Return early if there are no incoming or outgoing edges
410 if (_sizes_local.empty() and _sizes_remote.empty())
411 return;
412
413 // Wait for communication to complete
414 MPI_Waitall(requests.size(), requests.data(), MPI_STATUS_IGNORE);
415 }
416
424 void scatter_end(MPI_Request& request) const
425 {
426 scatter_end(std::span<MPI_Request>(&request, 1));
427 }
428
459 const container_type& local_indices() const noexcept { return _local_inds; }
460
487 const container_type& remote_indices() const noexcept { return _remote_inds; }
488
493 std::size_t num_p2p_requests() { return _dest.size() + _src.size(); }
494
495private:
496 // Communicator where the source ranks own the indices in the callers
497 // halo, and the destination ranks 'ghost' indices owned by the
498 // caller. I.e.,
499 // - in-edges (src) are from ranks that own my ghosts
500 // - out-edges (dest) go to ranks that 'ghost' my owned indices
501 dolfinx::MPI::Comm _comm0{MPI_COMM_NULL};
502
503 // Communicator where the source ranks have ghost indices that are
504 // owned by the caller, and the destination ranks are the owners of
505 // indices in the callers halo region. I.e.,
506 // - in-edges (src) are from ranks that 'ghost' my owned indices
507 // - out-edges (dest) are to the owning ranks of my ghost indices
508 dolfinx::MPI::Comm _comm1{MPI_COMM_NULL};
509
510 // Set of ranks that own ghosts
511 // FIXME: Should we store the index map instead?
512 std::vector<int> _src;
513
514 // Set of ranks ghost owned indices
515 // FIXME: Should we store the index map instead?
516 std::vector<int> _dest;
517
518 // Permutation indices used to pack and unpack ghost data (remote)
519 container_type _remote_inds;
520
521 // Number of remote indices (ghosts) for each neighbor process
522 std::vector<int> _sizes_remote;
523
524 // Displacements of remote data for mpi scatter and gather
525 std::vector<int> _displs_remote;
526
527 // Permutation indices used to pack and unpack local shared data
528 // (owned indices that are shared with other processes). Indices are
529 // grouped by neighbor process.
530 container_type _local_inds;
531
532 // Number of local shared indices per neighbor process
533 std::vector<int> _sizes_local;
534
535 // Displacements of local data for mpi scatter and gather
536 std::vector<int> _displs_local;
537};
538} // namespace dolfinx::common
A duplicate MPI communicator and manage lifetime of the communicator.
Definition MPI.h:42
Definition IndexMap.h:97
std::span< const int > owners() const
The ranks that own each ghost index.
Definition IndexMap.h:221
std::array< std::int64_t, 2 > local_range() const noexcept
Range of indices (global) owned by this process.
Definition IndexMap.cpp:926
std::span< const std::int64_t > ghosts() const noexcept
Definition IndexMap.cpp:940
MPI_Comm comm() const
Return the MPI communicator that the map is defined on.
Definition IndexMap.cpp:1005
void scatter_fwd_begin(const T *send_buffer, T *recv_buffer, std::span< MPI_Request > requests) const
Start a non-blocking send of owned data to ranks that ghost the data using point-to-point MPI communi...
Definition Scatterer.h:282
void scatter_end(std::span< MPI_Request > requests) const
Complete non-blocking MPI point-to-point sends.
Definition Scatterer.h:407
std::size_t num_p2p_requests()
Number of required MPI_Requests for point-to-point communication.
Definition Scatterer.h:493
void scatter_rev_begin(const T *send_buffer, T *recv_buffer, MPI_Request &request) const
Start a non-blocking send of ghost data to ranks that own the data using MPI neighbourhood collective...
Definition Scatterer.h:339
void scatter_fwd_begin(const T *send_buffer, T *recv_buffer, MPI_Request &request) const
Start a non-blocking send of owned data to ranks that ghost the data using MPI neighbourhood collecti...
Definition Scatterer.h:254
const container_type & local_indices() const noexcept
Array of indices for packing/unpacking owned data to/from a send/receive buffer.
Definition Scatterer.h:459
const container_type & remote_indices() const noexcept
Array of indices for packing/unpacking ghost data to/from a send/receive buffer.
Definition Scatterer.h:487
void scatter_end(MPI_Request &request) const
Complete a non-blocking MPI neighbourhood collective send.
Definition Scatterer.h:424
Container container_type
Container type used to store local and remote indices.
Definition Scatterer.h:57
void scatter_rev_begin(const T *send_buffer, T *recv_buffer, std::span< MPI_Request > requests) const
Start a non-blocking send of ghost data to ranks that own the data using point-to-point MPI communica...
Definition Scatterer.h:367
Scatterer(const IndexMap &map, int bs)
Create a scatterer for data with a layout described by an IndexMap and a block size.
Definition Scatterer.h:66
Scatterer(const Scatterer< U > &s)
Cast-copy constructor.
Definition Scatterer.h:219
Scatterer(const Scatterer &scatterer)=default
Copy constructor.
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:280
void check_error(MPI_Comm comm, int code)
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:80
int size(MPI_Comm comm)
Definition MPI.cpp:72
Miscellaneous classes, functions and types.
Definition dolfinx_common.h:8
constexpr __radix_sort radix_sort
Radix sort.
Definition sort.h:170