DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
Scatterer.h
1// Copyright (C) 2022-2026 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 <array>
14#include <cassert>
15#include <cstdint>
16#include <mpi.h>
17#include <numeric>
18#include <span>
19#include <type_traits>
20#include <vector>
21
22namespace dolfinx::common
23{
82template <class Container = std::vector<std::int32_t>>
83class Scatterer
84{
85 static_assert(std::is_integral_v<typename Container::value_type>);
86
87 template <class>
88 friend class Scatterer;
89
90public:
92 using container_type = Container;
93
101 explicit Scatterer(const IndexMap& map)
102 : _sizes_remote(map.src().size(), 0),
103 _displs_remote(map.src().size() + 1), _sizes_local(map.dest().size()),
104 _displs_local(map.dest().size() + 1)
105 {
106 if (dolfinx::MPI::size(map.comm()) == 1)
107 return;
108
109 int ierr;
110 const std::span<const int> src = map.src();
111 const std::span<const int> dest = map.dest();
112
113 // Check that src and dest ranks are unique and sorted
114 assert(std::ranges::is_sorted(src));
115 assert(std::ranges::is_sorted(dest));
116
117 // Create communicators with directed edges:
118 // (0) owner -> ghost,
119 // (1) ghost -> owner
120 MPI_Comm comm0;
121 ierr = MPI_Dist_graph_create_adjacent(
122 map.comm(), src.size(), src.data(), MPI_UNWEIGHTED, dest.size(),
123 dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
124 _comm0 = dolfinx::MPI::Comm(comm0, false);
125 dolfinx::MPI::check_error(map.comm(), ierr);
126
127 MPI_Comm comm1;
128 ierr = MPI_Dist_graph_create_adjacent(
129 map.comm(), dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(),
130 src.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm1);
131 _comm1 = dolfinx::MPI::Comm(comm1, false);
132 dolfinx::MPI::check_error(map.comm(), ierr);
133
134 // Build permutation array that sorts ghost indices by owning rank
135 std::span owners = map.owners();
136 std::vector<std::int32_t> perm(owners.size());
137 std::iota(perm.begin(), perm.end(), 0);
138 dolfinx::radix_sort(perm, [&owners](auto index) { return owners[index]; });
139
140 // Sort (i) ghost indices and (ii) ghost index owners by rank
141 // (using perm array)
142 std::span ghosts = map.ghosts();
143 std::vector<int> owners_sorted(owners.size());
144 std::vector<std::int64_t> ghosts_sorted(owners.size());
145 std::ranges::transform(perm, owners_sorted.begin(),
146 [&owners](auto idx) { return owners[idx]; });
147 std::ranges::transform(perm, ghosts_sorted.begin(),
148 [&ghosts](auto idx) { return ghosts[idx]; });
149
150 // For data associated with ghost indices, packed by owning
151 // (neighbourhood) rank, compute sizes and displacements. I.e., when
152 // sending ghost index data from this rank to the owning ranks,
153 // disp[i] is the first entry in the buffer sent to neighbourhood
154 // rank i, and disp[i + 1] - disp[i] is the number of values sent to
155 // rank i.
156 assert(_sizes_remote.size() == src.size());
157 assert(_displs_remote.size() == src.size() + 1);
158 auto begin = owners_sorted.begin();
159 for (std::size_t i = 0; i < src.size(); i++)
160 {
161 auto upper = std::ranges::upper_bound(begin, owners_sorted.end(), src[i]);
162 std::size_t num_ind = std::ranges::distance(begin, upper);
163 _displs_remote[i + 1] = _displs_remote[i] + num_ind;
164 _sizes_remote[i] = num_ind;
165 begin = upper;
166 }
167
168 // For data associated with owned indices that are ghosted by other
169 // ranks, compute the size and displacement arrays. When sending
170 // data associated with ghost indices to the owner, these size and
171 // displacement arrays are for the receive buffer.
172
173 // Compute sizes and displacements of local data (how many local
174 // elements to be sent/received grouped by neighbors)
175 assert(_sizes_local.size() == dest.size());
176 assert(_displs_local.size() == dest.size() + 1);
177 _sizes_remote.reserve(1); // ensure data is not a nullptr
178 _sizes_local.reserve(1); // ensure data is not a nullptr
179 ierr
180 = MPI_Neighbor_alltoall(_sizes_remote.data(), 1, MPI_INT,
181 _sizes_local.data(), 1, MPI_INT, _comm1.comm());
182 dolfinx::MPI::check_error(_comm1.comm(), ierr);
183
184 std::inclusive_scan(_sizes_local.begin(), _sizes_local.end(),
185 std::next(_displs_local.begin()));
186
187 assert(static_cast<int>(ghosts_sorted.size()) == _displs_remote.back());
188
189 // Send ghost global indices to owning rank, and receive owned
190 // indices that are ghosts on other ranks
191 std::vector<std::int64_t> recv_buffer(_displs_local.back(), 0);
192 ierr = MPI_Neighbor_alltoallv(
193 ghosts_sorted.data(), _sizes_remote.data(), _displs_remote.data(),
194 MPI_INT64_T, recv_buffer.data(), _sizes_local.data(),
195 _displs_local.data(), MPI_INT64_T, _comm1.comm());
196 dolfinx::MPI::check_error(_comm1.comm(), ierr);
197
198 const std::array<std::int64_t, 2> range = map.local_range();
199#ifndef NDEBUG
200 // Check that all received indices are within the owned range
201 std::ranges::for_each(recv_buffer, [range](auto idx)
202 { assert(idx >= range[0] and idx < range[1]); });
203#endif
204
205 // Sizes, displacements and indices are all in blocks. The block
206 // size enters only through the MPI datatype used to send them, and
207 // through the caller's pack/unpack.
208 {
209 // Convert the received indices from global to local numbering
210 std::vector<typename container_type::value_type> idx(recv_buffer.size());
211 std::ranges::transform(recv_buffer, idx.begin(),
212 [offset = range[0]](auto i) ->
213 typename container_type::value_type
214 { return i - offset; });
215 _local_inds = std::move(idx);
216 }
217
218 _remote_inds = container_type(perm.begin(), perm.end());
219 }
220
237 template <class U>
238 Scatterer(const Scatterer<U>& s)
239 : _comm0(s._comm0), _comm1(s._comm1),
240 _remote_inds(s._remote_inds.begin(), s._remote_inds.end()),
241 _sizes_remote(s._sizes_remote), _displs_remote(s._displs_remote),
242 _local_inds(s._local_inds.begin(), s._local_inds.end()),
243 _sizes_local(s._sizes_local), _displs_local(s._displs_local)
244 {
245 }
246
251 Scatterer(const Scatterer& scatterer) = default;
252
257 Scatterer(Scatterer&& scatterer) = default;
258
262 ~Scatterer() = default;
263
264 // Copy assignment (deleted). dolfinx::MPI::Comm cannot be copied into
265 // an existing object.
266 Scatterer& operator=(const Scatterer& scatterer) = delete;
267
272 Scatterer& operator=(Scatterer&& scatterer) = default;
273
317 template <typename T>
318 void scatter_fwd_begin_dtype(const T* send_buffer, T* recv_buffer,
319 MPI_Datatype type, MPI_Request& request) const
320 {
321 if (!has_neighbours())
322 {
323 request = MPI_REQUEST_NULL;
324 return;
325 }
326
327 int ierr = MPI_Ineighbor_alltoallv(
328 send_buffer, _sizes_local.data(), _displs_local.data(), type,
329 recv_buffer, _sizes_remote.data(), _displs_remote.data(), type,
330 _comm0.comm(), &request);
331 dolfinx::MPI::check_error(_comm0.comm(), ierr);
332 }
333
355 template <typename T>
356 void scatter_fwd_begin(const T* send_buffer, T* recv_buffer, int bs,
357 MPI_Request& request) const
358 {
359 // Checked here too, to avoid building a datatype that will not be
360 // used
361 if (!has_neighbours())
362 {
363 request = MPI_REQUEST_NULL;
364 return;
365 }
366
368 scatter_fwd_begin_dtype(send_buffer, recv_buffer, type.type(), request);
369 }
370
383 void scatter_fwd_end(MPI_Request& request) const
384 {
385 if (!has_neighbours())
386 return;
387
388 wait(_comm0, request);
389 }
390
434 template <typename T>
435 void scatter_rev_begin_dtype(const T* send_buffer, T* recv_buffer,
436 MPI_Datatype type, MPI_Request& request) const
437 {
438 if (!has_neighbours())
439 {
440 request = MPI_REQUEST_NULL;
441 return;
442 }
443
444 int ierr = MPI_Ineighbor_alltoallv(
445 send_buffer, _sizes_remote.data(), _displs_remote.data(), type,
446 recv_buffer, _sizes_local.data(), _displs_local.data(), type,
447 _comm1.comm(), &request);
448 dolfinx::MPI::check_error(_comm1.comm(), ierr);
449 }
450
472 template <typename T>
473 void scatter_rev_begin(const T* send_buffer, T* recv_buffer, int bs,
474 MPI_Request& request) const
475 {
476 // Checked here too, to avoid building a datatype that will not be
477 // used
478 if (!has_neighbours())
479 {
480 request = MPI_REQUEST_NULL;
481 return;
482 }
483
485 scatter_rev_begin_dtype(send_buffer, recv_buffer, type.type(), request);
486 }
487
500 void scatter_rev_end(MPI_Request& request) const
501 {
502 if (!has_neighbours())
503 return;
504
505 wait(_comm1, request);
506 }
507
545 const container_type& local_indices_block() const noexcept
546 {
547 return _local_inds;
548 }
549
580 const container_type& remote_indices_block() const noexcept
581 {
582 return _remote_inds;
583 }
584
585private:
586 // False only on a single rank, where _comm0/_comm1 stay MPI_COMM_NULL
587 bool has_neighbours() const noexcept
588 {
589 return _comm0.comm() != MPI_COMM_NULL;
590 }
591
592 // Complete a non-blocking request, checking errors against `comm`
593 static void wait(const dolfinx::MPI::Comm& comm, MPI_Request& request)
594 {
595 int ierr = MPI_Wait(&request, MPI_STATUS_IGNORE);
596 dolfinx::MPI::check_error(comm.comm(), ierr);
597 }
598
599 // Communicator where the source ranks own the indices in the callers
600 // halo, and the destination ranks 'ghost' indices owned by the
601 // caller. I.e.,
602 // - in-edges (src) are from ranks that own my ghosts
603 // - out-edges (dest) go to ranks that 'ghost' my owned indices
604 dolfinx::MPI::Comm _comm0{MPI_COMM_NULL};
605
606 // Communicator where the source ranks have ghost indices that are
607 // owned by the caller, and the destination ranks are the owners of
608 // indices in the callers halo region. I.e.,
609 // - in-edges (src) are from ranks that 'ghost' my owned indices
610 // - out-edges (dest) are to the owning ranks of my ghost indices
611 dolfinx::MPI::Comm _comm1{MPI_COMM_NULL};
612
613 // Permutation indices used to pack and unpack ghost data (remote)
614 container_type _remote_inds;
615
616 // Number of remote indices (ghosts) for each neighbor process
617 std::vector<int> _sizes_remote;
618
619 // Displacements of remote data for mpi scatter and gather
620 std::vector<int> _displs_remote;
621
622 // Permutation indices used to pack and unpack local shared data
623 // (owned indices that are shared with other processes). Indices are
624 // grouped by neighbor process.
625 container_type _local_inds;
626
627 // Number of local shared indices per neighbor process
628 std::vector<int> _sizes_local;
629
630 // Displacements of local data for mpi scatter and gather
631 std::vector<int> _displs_local;
632};
633} // namespace dolfinx::common
A duplicate MPI communicator and manage lifetime of the communicator.
Definition MPI.h:47
MPI_Comm comm() const noexcept
Return the underlying MPI_Comm object.
Definition MPI.cpp:71
An MPI datatype for count contiguous values of type T, and manage its lifetime.
Definition MPI.h:346
MPI_Datatype type() const noexcept
The datatype to pass to MPI.
Definition MPI.h:395
Distribution of a global index range [0, N) across MPI ranks.
Definition IndexMap.h:114
std::span< const int > dest() const noexcept
Return sorted unique ranks that ghost entries owned by the caller.
Definition IndexMap.cpp:1448
std::array< std::int64_t, 2 > local_range() const noexcept
Return the global range of owned indices.
Definition IndexMap.cpp:1035
std::span< const int > owners() const noexcept
Return ranks that own ghost entries.
Definition IndexMap.h:287
std::span< const int > src() const noexcept
Return sorted unique ranks that own the caller's ghosts.
Definition IndexMap.cpp:1446
std::span< const std::int64_t > ghosts() const noexcept
Return global indices of ghosts in local ghost-index order.
Definition IndexMap.cpp:1052
MPI_Comm comm() const
Return the communicator that the map is defined on.
Definition IndexMap.cpp:1134
Scatterer(const IndexMap &map)
Create a scatterer for data with a layout described by an IndexMap.
Definition Scatterer.h:101
void scatter_rev_begin_dtype(const T *send_buffer, T *recv_buffer, MPI_Datatype type, MPI_Request &request) const
Start a non-blocking neighbourhood collective exchange of ghost data with the owning ranks.
Definition Scatterer.h:435
void scatter_fwd_begin(const T *send_buffer, T *recv_buffer, int bs, MPI_Request &request) const
Start a non-blocking neighbourhood collective exchange of owned data with the ranks that ghost it.
Definition Scatterer.h:356
Scatterer(Scatterer &&scatterer)=default
void scatter_rev_end(MPI_Request &request) const
Complete a non-blocking MPI neighbourhood collective send.
Definition Scatterer.h:500
const container_type & local_indices_block() const noexcept
Array of indices for packing/unpacking owned data to/from a send/receive buffer.
Definition Scatterer.h:545
void scatter_fwd_begin_dtype(const T *send_buffer, T *recv_buffer, MPI_Datatype type, MPI_Request &request) const
Start a non-blocking neighbourhood collective exchange of owned data with the ranks that ghost it.
Definition Scatterer.h:318
Scatterer & operator=(Scatterer &&scatterer)=default
void scatter_rev_begin(const T *send_buffer, T *recv_buffer, int bs, MPI_Request &request) const
Start a non-blocking neighbourhood collective exchange of ghost data with the owning ranks.
Definition Scatterer.h:473
Container container_type
Container type used to store local and remote indices.
Definition Scatterer.h:92
void scatter_fwd_end(MPI_Request &request) const
Complete a non-blocking MPI neighbourhood collective send.
Definition Scatterer.h:383
const container_type & remote_indices_block() const noexcept
Array of indices for packing/unpacking ghost data to/from a send/receive buffer.
Definition Scatterer.h:580
Scatterer(const Scatterer< U > &s)
Cast-copy constructor.
Definition Scatterer.h:238
Scatterer(const Scatterer &scatterer)=default
void check_error(MPI_Comm comm, int code) noexcept
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