Note: this is documentation for an old release. View the latest documentation at docs.fenicsproject.org/dolfinx/v0.9.0/cpp/doxygen/d4/d42/Scatterer_8h_source.html
DOLFINx 0.7.3
DOLFINx C++ interface
Loading...
Searching...
No Matches
Scatterer.h
1// Copyright (C) 2022 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 <memory>
14#include <mpi.h>
15#include <numeric>
16#include <span>
17#include <vector>
18
19using namespace dolfinx;
20
21namespace dolfinx::common
22{
29template <class Allocator = std::allocator<std::int32_t>>
31{
32public:
34 using allocator_type = Allocator;
35
37 enum class type
38 {
39 neighbor, // use MPI neighborhood collectives
40 p2p // use MPI Isend/Irecv for communication
41 };
42
49 Scatterer(const IndexMap& map, int bs, const Allocator& alloc = Allocator())
50 : _bs(bs), _remote_inds(0, alloc), _local_inds(0, alloc), _src(map.src()),
51 _dest(map.dest())
52 {
53 if (map.overlapped())
54 {
55 // Check that src and dest ranks are unique and sorted
56 assert(std::is_sorted(_src.begin(), _src.end()));
57 assert(std::is_sorted(_dest.begin(), _dest.end()));
58
59 // Create communicators with directed edges:
60 // (0) owner -> ghost,
61 // (1) ghost -> owner
62 MPI_Comm comm0;
63 MPI_Dist_graph_create_adjacent(
64 map.comm(), _src.size(), _src.data(), MPI_UNWEIGHTED, _dest.size(),
65 _dest.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm0);
66 _comm0 = dolfinx::MPI::Comm(comm0, false);
67
68 MPI_Comm comm1;
69 MPI_Dist_graph_create_adjacent(
70 map.comm(), _dest.size(), _dest.data(), MPI_UNWEIGHTED, _src.size(),
71 _src.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm1);
72 _comm1 = dolfinx::MPI::Comm(comm1, false);
73
74 // Build permutation array that sorts ghost indices by owning rank
75 const std::vector<int>& owners = map.owners();
76 std::vector<std::int32_t> perm(owners.size());
77 std::iota(perm.begin(), perm.end(), 0);
78 dolfinx::argsort_radix<std::int32_t>(owners, perm);
79
80 // Sort (i) ghost indices and (ii) ghost index owners by rank
81 // (using perm array)
82 const std::vector<std::int64_t>& ghosts = map.ghosts();
83 std::vector<int> owners_sorted(owners.size());
84 std::vector<std::int64_t> ghosts_sorted(owners.size());
85 std::transform(perm.begin(), perm.end(), owners_sorted.begin(),
86 [&owners](auto idx) { return owners[idx]; });
87 std::transform(perm.begin(), perm.end(), ghosts_sorted.begin(),
88 [&ghosts](auto idx) { return ghosts[idx]; });
89
90 // For data associated with ghost indices, packed by owning
91 // (neighbourhood) rank, compute sizes and displacements. I.e.,
92 // when sending ghost index data from this rank to the owning
93 // ranks, disp[i] is the first entry in the buffer sent to
94 // neighbourhood rank i, and disp[i + 1] - disp[i] is the number
95 // of values sent to rank i.
96 _sizes_remote.resize(_src.size(), 0);
97 _displs_remote.resize(_src.size() + 1, 0);
98 std::vector<std::int32_t>::iterator begin = owners_sorted.begin();
99 for (std::size_t i = 0; i < _src.size(); i++)
100 {
101 auto upper = std::upper_bound(begin, owners_sorted.end(), _src[i]);
102 int num_ind = std::distance(begin, upper);
103 _displs_remote[i + 1] = _displs_remote[i] + num_ind;
104 _sizes_remote[i] = num_ind;
105 begin = upper;
106 }
107
108 // For data associated with owned indices that are ghosted by
109 // other ranks, compute the size and displacement arrays. When
110 // sending data associated with ghost indices to the owner, these
111 // size and displacement arrays are for the receive buffer.
112
113 // Compute sizes and displacements of local data (how many local
114 // elements to be sent/received grouped by neighbors)
115 _sizes_local.resize(_dest.size());
116 _displs_local.resize(_sizes_local.size() + 1);
117 _sizes_remote.reserve(1);
118 _sizes_local.reserve(1);
119 MPI_Neighbor_alltoall(_sizes_remote.data(), 1, MPI_INT32_T,
120 _sizes_local.data(), 1, MPI_INT32_T, _comm1.comm());
121 std::partial_sum(_sizes_local.begin(), _sizes_local.end(),
122 std::next(_displs_local.begin()));
123
124 assert((std::int32_t)ghosts_sorted.size() == _displs_remote.back());
125 assert((std::int32_t)ghosts_sorted.size() == _displs_remote.back());
126
127 // Send ghost global indices to owning rank, and receive owned
128 // indices that are ghosts on other ranks
129 std::vector<std::int64_t> recv_buffer(_displs_local.back(), 0);
130 MPI_Neighbor_alltoallv(ghosts_sorted.data(), _sizes_remote.data(),
131 _displs_remote.data(), MPI_INT64_T,
132 recv_buffer.data(), _sizes_local.data(),
133 _displs_local.data(), MPI_INT64_T, _comm1.comm());
134
135 const std::array<std::int64_t, 2> range = map.local_range();
136#ifndef NDEBUG
137 // Check that all received indice are within the owned range
138 std::for_each(recv_buffer.begin(), recv_buffer.end(),
139 [range](auto idx)
140 { assert(idx >= range[0] and idx < range[1]); });
141#endif
142
143 // Scale sizes and displacements by block size
144 {
145 auto rescale = [](auto& x, int bs)
146 {
147 std::transform(x.begin(), x.end(), x.begin(),
148 [bs](auto e) { return e *= bs; });
149 };
150 rescale(_sizes_local, bs);
151 rescale(_displs_local, bs);
152 rescale(_sizes_remote, bs);
153 rescale(_displs_remote, bs);
154 }
155
156 // Expand local indices using block size and convert it from
157 // global to local numbering
158 _local_inds = std::vector<std::int32_t, allocator_type>(
159 recv_buffer.size() * _bs, alloc);
160 std::int64_t offset = range[0] * _bs;
161 for (std::size_t i = 0; i < recv_buffer.size(); i++)
162 for (int j = 0; j < _bs; j++)
163 _local_inds[i * _bs + j] = (recv_buffer[i] * _bs + j) - offset;
164
165 // Expand remote indices using block size
166 _remote_inds
167 = std::vector<std::int32_t, allocator_type>(perm.size() * _bs, alloc);
168 for (std::size_t i = 0; i < perm.size(); i++)
169 for (int j = 0; j < _bs; j++)
170 _remote_inds[i * _bs + j] = perm[i] * _bs + j;
171 }
172 }
173
194 template <typename T>
195 void scatter_fwd_begin(std::span<const T> send_buffer,
196 std::span<T> recv_buffer,
197 std::span<MPI_Request> requests,
198 Scatterer::type type = type::neighbor) const
199 {
200 // Return early if there are no incoming or outgoing edges
201 if (_sizes_local.empty() and _sizes_remote.empty())
202 return;
203
204 switch (type)
205 {
206 case type::neighbor:
207 {
208 assert(requests.size() == std::size_t(1));
209 MPI_Ineighbor_alltoallv(
210 send_buffer.data(), _sizes_local.data(), _displs_local.data(),
211 dolfinx::MPI::mpi_type<T>(), recv_buffer.data(), _sizes_remote.data(),
212 _displs_remote.data(), dolfinx::MPI::mpi_type<T>(), _comm0.comm(),
213 requests.data());
214 break;
215 }
216 case type::p2p:
217 {
218 assert(requests.size() == _dest.size() + _src.size());
219 for (std::size_t i = 0; i < _src.size(); i++)
220 {
221 MPI_Irecv(recv_buffer.data() + _displs_remote[i], _sizes_remote[i],
222 dolfinx::MPI::mpi_type<T>(), _src[i], MPI_ANY_TAG,
223 _comm0.comm(), &requests[i]);
224 }
225
226 for (std::size_t i = 0; i < _dest.size(); i++)
227 {
228 MPI_Isend(send_buffer.data() + _displs_local[i], _sizes_local[i],
229 dolfinx::MPI::mpi_type<T>(), _dest[i], 0, _comm0.comm(),
230 &requests[i + _src.size()]);
231 }
232 break;
233 }
234 default:
235 throw std::runtime_error("Scatter::type not recognized");
236 }
237 }
238
247 void scatter_fwd_end(std::span<MPI_Request> requests) const
248 {
249 // Return early if there are no incoming or outgoing edges
250 if (_sizes_local.empty() and _sizes_remote.empty())
251 return;
252
253 // Wait for communication to complete
254 MPI_Waitall(requests.size(), requests.data(), MPI_STATUS_IGNORE);
255 }
256
277 template <typename T, typename Functor>
278 void scatter_fwd_begin(std::span<const T> local_data,
279 std::span<T> local_buffer, std::span<T> remote_buffer,
280 Functor pack_fn, std::span<MPI_Request> requests,
281 Scatterer::type type = type::neighbor) const
282 {
283 assert(local_buffer.size() == _local_inds.size());
284 assert(remote_buffer.size() == _remote_inds.size());
285 pack_fn(local_data, _local_inds, local_buffer);
286 scatter_fwd_begin(std::span<const T>(local_buffer), remote_buffer, requests,
287 type);
288 }
289
309 template <typename T, typename Functor>
310 void scatter_fwd_end(std::span<const T> remote_buffer,
311 std::span<T> remote_data, Functor unpack_fn,
312 std::span<MPI_Request> requests) const
313 {
314 assert(remote_buffer.size() == _remote_inds.size());
315 assert(remote_data.size() == _remote_inds.size());
316 scatter_fwd_end(requests);
317 unpack_fn(remote_buffer, _remote_inds, remote_data,
318 [](T /*a*/, T b) { return b; });
319 }
320
332 template <typename T>
333 void scatter_fwd(std::span<const T> local_data,
334 std::span<T> remote_data) const
335 {
336 std::vector<MPI_Request> requests(1, MPI_REQUEST_NULL);
337 std::vector<T> local_buffer(local_buffer_size(), 0);
338 std::vector<T> remote_buffer(remote_buffer_size(), 0);
339 auto pack_fn = [](const auto& in, const auto& idx, auto& out)
340 {
341 for (std::size_t i = 0; i < idx.size(); ++i)
342 out[i] = in[idx[i]];
343 };
344 scatter_fwd_begin(local_data, std::span<T>(local_buffer),
345 std::span<T>(remote_buffer), pack_fn,
346 std::span<MPI_Request>(requests));
347
348 auto unpack_fn = [](const auto& in, const auto& idx, auto& out, auto op)
349 {
350 for (std::size_t i = 0; i < idx.size(); ++i)
351 out[idx[i]] = op(out[idx[i]], in[i]);
352 };
353
354 scatter_fwd_end(std::span<const T>(remote_buffer), remote_data, unpack_fn,
355 std::span<MPI_Request>(requests));
356 }
357
384 template <typename T>
385 void scatter_rev_begin(std::span<const T> send_buffer,
386 std::span<T> recv_buffer,
387 std::span<MPI_Request> requests,
388 Scatterer::type type = type::neighbor) const
389 {
390 // Return early if there are no incoming or outgoing edges
391 if (_sizes_local.empty() and _sizes_remote.empty())
392 return;
393
394 // // Send and receive data
395
396 switch (type)
397 {
398 case type::neighbor:
399 {
400 assert(requests.size() == 1);
401 MPI_Ineighbor_alltoallv(send_buffer.data(), _sizes_remote.data(),
402 _displs_remote.data(), MPI::mpi_type<T>(),
403 recv_buffer.data(), _sizes_local.data(),
404 _displs_local.data(), MPI::mpi_type<T>(),
405 _comm1.comm(), &requests[0]);
406 break;
407 }
408 case type::p2p:
409 {
410 assert(requests.size() == _dest.size() + _src.size());
411 // Start non-blocking send from this process to ghost owners.
412 for (std::size_t i = 0; i < _dest.size(); i++)
413 {
414 MPI_Irecv(recv_buffer.data() + _displs_local[i], _sizes_local[i],
415 dolfinx::MPI::mpi_type<T>(), _dest[i], MPI_ANY_TAG,
416 _comm0.comm(), &requests[i]);
417 }
418
419 // Start non-blocking receive from neighbor process for which an owned
420 // index is a ghost.
421 for (std::size_t i = 0; i < _src.size(); i++)
422 {
423 MPI_Isend(send_buffer.data() + _displs_remote[i], _sizes_remote[i],
424 dolfinx::MPI::mpi_type<T>(), _src[i], 0, _comm0.comm(),
425 &requests[i + _dest.size()]);
426 }
427 break;
428 }
429 default:
430 throw std::runtime_error("Scatter::type not recognized");
431 }
432 }
433
442 void scatter_rev_end(std::span<MPI_Request> request) const
443 {
444 // Return early if there are no incoming or outgoing edges
445 if (_sizes_local.empty() and _sizes_remote.empty())
446 return;
447
448 // Wait for communication to complete
449 MPI_Waitall(request.size(), request.data(), MPI_STATUS_IGNORE);
450 }
451
480 template <typename T, typename Functor>
481 void scatter_rev_begin(std::span<const T> remote_data,
482 std::span<T> remote_buffer, std::span<T> local_buffer,
483 Functor pack_fn, std::span<MPI_Request> request,
484 Scatterer::type type = type::neighbor) const
485 {
486 assert(local_buffer.size() == _local_inds.size());
487 assert(remote_buffer.size() == _remote_inds.size());
488 pack_fn(remote_data, _remote_inds, remote_buffer);
489 scatter_rev_begin(std::span<const T>(remote_buffer), local_buffer, request,
490 type);
491 }
492
512 template <typename T, typename Functor, typename BinaryOp>
513 void scatter_rev_end(std::span<const T> local_buffer, std::span<T> local_data,
514 Functor unpack_fn, BinaryOp op,
515 std::span<MPI_Request> request)
516 {
517 assert(local_buffer.size() == _local_inds.size());
518 if (_local_inds.size() > 0)
519 assert(*std::max_element(_local_inds.begin(), _local_inds.end())
520 < std::int32_t(local_data.size()));
521 scatter_rev_end(request);
522 unpack_fn(local_buffer, _local_inds, local_data, op);
523 }
524
527 template <typename T, typename BinaryOp>
528 void scatter_rev(std::span<T> local_data, std::span<const T> remote_data,
529 BinaryOp op)
530 {
531 std::vector<T> local_buffer(local_buffer_size(), 0);
532 std::vector<T> remote_buffer(remote_buffer_size(), 0);
533 auto pack_fn = [](const auto& in, const auto& idx, auto& out)
534 {
535 for (std::size_t i = 0; i < idx.size(); ++i)
536 out[i] = in[idx[i]];
537 };
538 auto unpack_fn = [](const auto& in, const auto& idx, auto& out, auto op)
539 {
540 for (std::size_t i = 0; i < idx.size(); ++i)
541 out[idx[i]] = op(out[idx[i]], in[i]);
542 };
543 std::vector<MPI_Request> request(1, MPI_REQUEST_NULL);
544 scatter_rev_begin(remote_data, std::span<T>(remote_buffer),
545 std::span<T>(local_buffer), pack_fn,
546 std::span<MPI_Request>(request));
547 scatter_rev_end(std::span<const T>(local_buffer), local_data, unpack_fn, op,
548 std::span<MPI_Request>(request));
549 }
550
554 std::int32_t local_buffer_size() const noexcept { return _local_inds.size(); }
555
559 std::int32_t remote_buffer_size() const noexcept
560 {
561 return _remote_inds.size();
562 }
563
567 const std::vector<std::int32_t>& local_indices() const noexcept
568 {
569 return _local_inds;
570 }
571
574 const std::vector<std::int32_t>& remote_indices() const noexcept
575 {
576 return _remote_inds;
577 }
578
582 int bs() const noexcept { return _bs; }
583
587 = type::neighbor)
588 {
589 std::vector<MPI_Request> requests;
590 switch (type)
591 {
592 case type::neighbor:
593 requests = {MPI_REQUEST_NULL};
594 break;
595 case type::p2p:
596 requests.resize(_dest.size() + _src.size(), MPI_REQUEST_NULL);
597 break;
598 default:
599 throw std::runtime_error("Scatter::type not recognized");
600 }
601 return requests;
602 }
603
604private:
605 // Block size
606 int _bs;
607
608 // Communicator where the source ranks own the indices in the callers
609 // halo, and the destination ranks 'ghost' indices owned by the
610 // caller. I.e.,
611 // - in-edges (src) are from ranks that own my ghosts
612 // - out-edges (dest) go to ranks that 'ghost' my owned indices
613 dolfinx::MPI::Comm _comm0{MPI_COMM_NULL};
614
615 // Communicator where the source ranks have ghost indices that are
616 // owned by the caller, and the destination ranks are the owners of
617 // indices in the callers halo region. I.e.,
618 // - in-edges (src) are from ranks that 'ghost' my owned indices
619 // - out-edges (dest) are to the owning ranks of my ghost indices
620 dolfinx::MPI::Comm _comm1{MPI_COMM_NULL};
621
622 // Permutation indices used to pack and unpack ghost data (remote)
623 std::vector<std::int32_t, allocator_type> _remote_inds;
624
625 // Number of remote indices (ghosts) for each neighbor process
626 std::vector<int> _sizes_remote;
627
628 // Displacements of remote data for mpi scatter and gather
629 std::vector<int> _displs_remote;
630
631 // Permutation indices used to pack and unpack local shared data
632 // (owned indices that are shared with other processes). Indices are
633 // grouped by neighbor process.
634 std::vector<std::int32_t, allocator_type> _local_inds;
635
636 // Number of local shared indices per neighbor process
637 std::vector<int> _sizes_local;
638
639 // Displacements of local data for mpi scatter and gather
640 std::vector<int> _displs_local;
641
642 // Set of ranks that own ghosts
643 // FIXME: Should we store the index map instead?
644 std::vector<int> _src;
645
646 // Set of ranks ghost owned indices
647 // FIXME: Should we store the index map instead?
648 std::vector<int> _dest;
649};
650} // namespace dolfinx::common
A duplicate MPI communicator and manage lifetime of the communicator.
Definition MPI.h:43
This class represents the distribution index arrays across processes. An index array is a contiguous ...
Definition IndexMap.h:64
bool overlapped() const noexcept
Check if index map has overlaps (ghosts on any rank).
Definition IndexMap.cpp:948
const std::vector< int > & owners() const
The ranks that own each ghost index.
Definition IndexMap.h:174
std::array< std::int64_t, 2 > local_range() const noexcept
Range of indices (global) owned by this process.
Definition IndexMap.cpp:384
const std::vector< std::int64_t > & ghosts() const noexcept
Local-to-global map for ghosts (local indexing beyond end of local range)
Definition IndexMap.cpp:398
MPI_Comm comm() const
Return the MPI communicator that the map is defined on.
Definition IndexMap.cpp:461
A Scatterer supports the MPI scattering and gathering of data that is associated with a common::Index...
Definition Scatterer.h:31
std::vector< MPI_Request > create_request_vector(Scatterer::type type=type::neighbor)
Create a vector of MPI_Requests for a given Scatterer::type.
Definition Scatterer.h:586
Allocator allocator_type
The allocator type.
Definition Scatterer.h:34
void scatter_fwd_end(std::span< MPI_Request > requests) const
Complete a non-blocking send from the local owner to process ranks that have the index as a ghost.
Definition Scatterer.h:247
void scatter_fwd(std::span< const T > local_data, std::span< T > remote_data) const
Scatter data associated with owned indices to ghosting ranks.
Definition Scatterer.h:333
Scatterer(const IndexMap &map, int bs, const Allocator &alloc=Allocator())
Create a scatterer.
Definition Scatterer.h:49
std::int32_t remote_buffer_size() const noexcept
Buffer size for remote data (ghosts) used in forward and reverse communication.
Definition Scatterer.h:559
std::int32_t local_buffer_size() const noexcept
Size of buffer for local data (owned and shared) used in forward and reverse communication.
Definition Scatterer.h:554
void scatter_fwd_end(std::span< const T > remote_buffer, std::span< T > remote_data, Functor unpack_fn, std::span< MPI_Request > requests) const
Complete a non-blocking send from the local owner to process ranks that have the index as a ghost,...
Definition Scatterer.h:310
type
Types of MPI communication pattern used by the Scatterer.
Definition Scatterer.h:38
void scatter_rev_end(std::span< MPI_Request > request) const
End the reverse scatter communication.
Definition Scatterer.h:442
void scatter_fwd_begin(std::span< const T > send_buffer, std::span< T > recv_buffer, std::span< MPI_Request > requests, Scatterer::type type=type::neighbor) const
Start a non-blocking send of owned data to ranks that ghost the data.
Definition Scatterer.h:195
void scatter_rev_begin(std::span< const T > remote_data, std::span< T > remote_buffer, std::span< T > local_buffer, Functor pack_fn, std::span< MPI_Request > request, Scatterer::type type=type::neighbor) const
Scatter data associated with ghost indices to owning ranks.
Definition Scatterer.h:481
const std::vector< std::int32_t > & local_indices() const noexcept
Return a vector of local indices (owned) used to pack/unpack local data. These indices are grouped by...
Definition Scatterer.h:567
void scatter_rev_end(std::span< const T > local_buffer, std::span< T > local_data, Functor unpack_fn, BinaryOp op, std::span< MPI_Request > request)
End the reverse scatter communication, and unpack the received local buffer into local data.
Definition Scatterer.h:513
void scatter_fwd_begin(std::span< const T > local_data, std::span< T > local_buffer, std::span< T > remote_buffer, Functor pack_fn, std::span< MPI_Request > requests, Scatterer::type type=type::neighbor) const
Scatter data associated with owned indices to ghosting ranks.
Definition Scatterer.h:278
const std::vector< std::int32_t > & remote_indices() const noexcept
Return a vector of remote indices (ghosts) used to pack/unpack ghost data. These indices are grouped ...
Definition Scatterer.h:574
void scatter_rev(std::span< T > local_data, std::span< const T > remote_data, BinaryOp op)
Scatter data associated with ghost indices to ranks that own the indices.
Definition Scatterer.h:528
int bs() const noexcept
The number values (block size) to send per index in the common::IndexMap use to create the scatterer.
Definition Scatterer.h:582
void scatter_rev_begin(std::span< const T > send_buffer, std::span< T > recv_buffer, std::span< MPI_Request > requests, Scatterer::type type=type::neighbor) const
Start a non-blocking send of ghost data to ranks that own the data.
Definition Scatterer.h:385
Miscellaneous classes, functions and types.
Definition dolfinx_common.h:8
Top-level namespace.
Definition defines.h:12