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