DOLFINx 0.11.0.0
DOLFINx C++
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(static_cast<int>(ghosts_sorted.size()) == _displs_remote.back());
152
153 // Send ghost global indices to owning rank, and receive owned
154 // indices that are ghosts on other ranks
155 std::vector<std::int64_t> recv_buffer(_displs_local.back(), 0);
156 ierr = MPI_Neighbor_alltoallv(
157 ghosts_sorted.data(), _sizes_remote.data(), _displs_remote.data(),
158 MPI_INT64_T, recv_buffer.data(), _sizes_local.data(),
159 _displs_local.data(), MPI_INT64_T, _comm1.comm());
160 dolfinx::MPI::check_error(_comm1.comm(), ierr);
161
162 const std::array<std::int64_t, 2> range = map.local_range();
163#ifndef NDEBUG
164 // Check that all received indice are within the owned range
165 std::ranges::for_each(recv_buffer, [range](auto idx)
166 { assert(idx >= range[0] and idx < range[1]); });
167#endif
168
169 {
170 // Scale sizes and displacements by block size
171 for (auto& x : {std::ref(_sizes_local), std::ref(_displs_local),
172 std::ref(_sizes_remote), std::ref(_displs_remote)})
173 {
174 std::ranges::transform(x.get(), x.get().begin(),
175 [bs](auto e) { return e * bs; });
176 }
177 }
178
179 {
180 // Expand local indices using block size and convert it from
181 // global to local numbering
182 std::vector<typename container_type::value_type> idx(recv_buffer.size()
183 * bs);
184 std::int64_t offset = range[0] * bs;
185 for (std::size_t i = 0; i < recv_buffer.size(); i++)
186 for (int j = 0; j < bs; j++)
187 idx[i * bs + j] = (recv_buffer[i] * bs + j) - offset;
188 _local_inds = std::move(idx);
189 }
190
191 {
192 // Expand remote indices using block size
193 std::vector<typename container_type::value_type> idx(perm.size() * bs);
194 for (std::size_t i = 0; i < perm.size(); i++)
195 for (int j = 0; j < bs; j++)
196 idx[i * bs + j] = perm[i] * bs + j;
197 _remote_inds = std::move(idx);
198 }
199 }
200
202 Scatterer(const Scatterer& scatterer) = default;
203
217 template <class U>
218 Scatterer(const Scatterer<U>& s)
219 : _comm0(s._comm0), _comm1(s._comm1), _src(s._src), _dest(s._dest),
220 _remote_inds(s._remote_inds.begin(), s._remote_inds.end()),
221 _sizes_remote(s._sizes_remote), _displs_remote(s._displs_remote),
222 _local_inds(s._local_inds.begin(), s._local_inds.end()),
223 _sizes_local(s._sizes_local), _displs_local(s._displs_local)
224 {
225 }
226
252 template <typename T>
253 void scatter_fwd_begin(const T* send_buffer, T* recv_buffer,
254 MPI_Request& request) const
255 {
256 // Return early if there are no incoming or outgoing edges
257 if (_sizes_local.empty() and _sizes_remote.empty())
258 return;
259
260 int ierr = MPI_Ineighbor_alltoallv(
261 send_buffer, _sizes_local.data(), _displs_local.data(),
262 dolfinx::MPI::mpi_t<T>, recv_buffer, _sizes_remote.data(),
263 _displs_remote.data(), dolfinx::MPI::mpi_t<T>, _comm0.comm(), &request);
264 dolfinx::MPI::check_error(_comm0.comm(), ierr);
265 }
266
280 template <typename T>
281 void scatter_fwd_begin(const T* send_buffer, T* recv_buffer,
282 std::span<MPI_Request> requests) const
283 {
284 if (requests.size() != _dest.size() + _src.size())
285 {
286 throw std::runtime_error(
287 "Point-to-point scatterer has wrong number of MPI_Requests.");
288 }
289
290 // Return early if there are no incoming or outgoing edges
291 if (_sizes_local.empty() and _sizes_remote.empty())
292 return;
293
294 for (std::size_t i = 0; i < _src.size(); ++i)
295 {
296 int ierr = MPI_Irecv(recv_buffer + _displs_remote[i], _sizes_remote[i],
297 dolfinx::MPI::mpi_t<T>, _src[i], MPI_ANY_TAG,
298 _comm0.comm(), &requests[i]);
299 dolfinx::MPI::check_error(_comm0.comm(), ierr);
300 }
301
302 for (std::size_t i = 0; i < _dest.size(); ++i)
303 {
304 int ierr = MPI_Isend(send_buffer + _displs_local[i], _sizes_local[i],
305 dolfinx::MPI::mpi_t<T>, _dest[i], 0, _comm0.comm(),
306 &requests[i + _src.size()]);
307 dolfinx::MPI::check_error(_comm0.comm(), ierr);
308 }
309 }
310
337 template <typename T>
338 void scatter_rev_begin(const T* send_buffer, T* recv_buffer,
339 MPI_Request& request) const
340 {
341 // Return early if there are no incoming or outgoing edges
342 if (_sizes_local.empty() and _sizes_remote.empty())
343 return;
344
345 int ierr = MPI_Ineighbor_alltoallv(
346 send_buffer, _sizes_remote.data(), _displs_remote.data(), MPI::mpi_t<T>,
347 recv_buffer, _sizes_local.data(), _displs_local.data(), MPI::mpi_t<T>,
348 _comm1.comm(), &request);
349 dolfinx::MPI::check_error(_comm1.comm(), ierr);
350 }
351
365 template <typename T>
366 void scatter_rev_begin(const T* send_buffer, T* recv_buffer,
367 std::span<MPI_Request> requests) const
368 {
369 if (requests.size() != _dest.size() + _src.size())
370 {
371 throw std::runtime_error(
372 "Point-to-point scatterer has wrong number of MPI_Requests.");
373 }
374
375 // Return early if there are no incoming or outgoing edges
376 if (_sizes_local.empty() and _sizes_remote.empty())
377 return;
378
379 // Start non-blocking send from this process to ghost owners
380 for (std::size_t i = 0; i < _dest.size(); i++)
381 {
382 int ierr = MPI_Irecv(recv_buffer + _displs_local[i], _sizes_local[i],
383 dolfinx::MPI::mpi_t<T>, _dest[i], MPI_ANY_TAG,
384 _comm0.comm(), &requests[i]);
385 dolfinx::MPI::check_error(_comm0.comm(), ierr);
386 }
387
388 // Start non-blocking receive from neighbor process for which an
389 // owned index is a ghost
390 for (std::size_t i = 0; i < _src.size(); i++)
391 {
392 int ierr = MPI_Isend(send_buffer + _displs_remote[i], _sizes_remote[i],
393 dolfinx::MPI::mpi_t<T>, _src[i], 0, _comm0.comm(),
394 &requests[i + _dest.size()]);
395 dolfinx::MPI::check_error(_comm0.comm(), ierr);
396 }
397 }
398
406 void scatter_end(std::span<MPI_Request> requests) const
407 {
408 // Return early if there are no incoming or outgoing edges
409 if (_sizes_local.empty() and _sizes_remote.empty())
410 return;
411
412 // Wait for communication to complete
413 MPI_Waitall(requests.size(), requests.data(), MPI_STATUS_IGNORE);
414 }
415
423 void scatter_end(MPI_Request& request) const
424 {
425 scatter_end(std::span<MPI_Request>(&request, 1));
426 }
427
458 const container_type& local_indices() const noexcept { return _local_inds; }
459
486 const container_type& remote_indices() const noexcept { return _remote_inds; }
487
492 std::size_t num_p2p_requests() { return _dest.size() + _src.size(); }
493
494private:
495 // Communicator where the source ranks own the indices in the callers
496 // halo, and the destination ranks 'ghost' indices owned by the
497 // caller. I.e.,
498 // - in-edges (src) are from ranks that own my ghosts
499 // - out-edges (dest) go to ranks that 'ghost' my owned indices
500 dolfinx::MPI::Comm _comm0{MPI_COMM_NULL};
501
502 // Communicator where the source ranks have ghost indices that are
503 // owned by the caller, and the destination ranks are the owners of
504 // indices in the callers halo region. I.e.,
505 // - in-edges (src) are from ranks that 'ghost' my owned indices
506 // - out-edges (dest) are to the owning ranks of my ghost indices
507 dolfinx::MPI::Comm _comm1{MPI_COMM_NULL};
508
509 // Set of ranks that own ghosts
510 // FIXME: Should we store the index map instead?
511 std::vector<int> _src;
512
513 // Set of ranks ghost owned indices
514 // FIXME: Should we store the index map instead?
515 std::vector<int> _dest;
516
517 // Permutation indices used to pack and unpack ghost data (remote)
518 container_type _remote_inds;
519
520 // Number of remote indices (ghosts) for each neighbor process
521 std::vector<int> _sizes_remote;
522
523 // Displacements of remote data for mpi scatter and gather
524 std::vector<int> _displs_remote;
525
526 // Permutation indices used to pack and unpack local shared data
527 // (owned indices that are shared with other processes). Indices are
528 // grouped by neighbor process.
529 container_type _local_inds;
530
531 // Number of local shared indices per neighbor process
532 std::vector<int> _sizes_local;
533
534 // Displacements of local data for mpi scatter and gather
535 std::vector<int> _displs_local;
536};
537} // 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:1008
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:281
void scatter_end(std::span< MPI_Request > requests) const
Complete non-blocking MPI point-to-point sends.
Definition Scatterer.h:406
std::size_t num_p2p_requests()
Number of required MPI_Requests for point-to-point communication.
Definition Scatterer.h:492
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:338
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:253
const container_type & local_indices() const noexcept
Array of indices for packing/unpacking owned data to/from a send/receive buffer.
Definition Scatterer.h:458
const container_type & remote_indices() const noexcept
Array of indices for packing/unpacking ghost data to/from a send/receive buffer.
Definition Scatterer.h:486
void scatter_end(MPI_Request &request) const
Complete a non-blocking MPI neighbourhood collective send.
Definition Scatterer.h:423
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:366
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:218
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 void radix_sort(R &&range, P proj={})
Sort a range with radix sorting algorithm. The bucket size is determined by the number of bits to sor...
Definition sort.h:78