DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
MPI.h
1// Copyright (C) 2007-2023 Magnus Vikstrøm, Garth N. Wells and Paul T. Kühner
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 "Timer.h"
10#include "local_range.h"
11#include "log.h"
12#include "sort.h"
13#include "types.h"
14#include <algorithm>
15#include <array>
16#include <cassert>
17#include <complex>
18#include <concepts>
19#include <cstdint>
20#include <format>
21#include <iterator>
22#include <numeric>
23#include <ranges>
24#include <span>
25#include <stdexcept>
26#include <tuple>
27#include <type_traits>
28#include <utility>
29#include <vector>
30
31#define MPICH_IGNORE_CXX_SEEK 1
32#include <mpi.h>
33
35namespace dolfinx::MPI
36{
38enum class tag : int
39{
40 consensus_pcx = 1200,
41 consensus_nbx = 1202,
42};
43
46class Comm
47{
48public:
50 explicit Comm(MPI_Comm comm, bool duplicate = true);
51
53 Comm(const Comm& comm) noexcept;
54
56 Comm(Comm&& comm) noexcept;
57
59 ~Comm();
60
61 // Copy assignment (deleted). MPI_Comm_dup is collective; assigning into
62 // a live Comm would hide a collective dup and free behind `=`.
63 Comm& operator=(const Comm& comm) = delete;
64
66 Comm& operator=(Comm&& comm) noexcept;
67
69 MPI_Comm comm() const noexcept;
70
71private:
72 // MPI communicator
73 MPI_Comm _comm;
74};
75
77int rank(MPI_Comm comm);
78
81int size(MPI_Comm comm);
82
90void check_error(MPI_Comm comm, int code) noexcept;
91
98constexpr int index_owner(int size, std::size_t index, std::size_t N)
99{
100 assert(index < N);
101
102 // Compute number of items per rank and remainder
103 const std::size_t n = N / size;
104 const std::size_t r = N % size;
105
106 if (index < r * (n + 1))
107 {
108 // First r ranks own n + 1 indices
109 return index / (n + 1);
110 }
111 else
112 {
113 // Remaining ranks own n indices
114 return r + (index - r * (n + 1)) / n;
115 }
116}
117
144std::vector<int> compute_graph_edges_pcx(MPI_Comm comm,
145 std::span<const int> edges);
146
174std::vector<int>
175compute_graph_edges_nbx(MPI_Comm comm, std::span<const int> edges,
176 int tag = static_cast<int>(tag::consensus_nbx));
177
202std::pair<std::vector<int>, std::vector<int>>
203compute_graph_edges_nbx(MPI_Comm comm, std::span<const int> edges0, int tag0,
204 std::span<const int> edges1, int tag1);
205
224template <std::ranges::contiguous_range U>
225std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
226distribute_to_postoffice(MPI_Comm comm, const U& x,
227 std::array<std::int64_t, 2> shape,
228 std::int64_t rank_offset);
229
251template <std::ranges::contiguous_range U>
252std::vector<std::ranges::range_value_t<U>>
253distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
254 const U& x, std::array<std::int64_t, 2> shape,
255 std::int64_t rank_offset);
256
276template <std::ranges::contiguous_range U>
277std::vector<std::ranges::range_value_t<U>>
278distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
279 MPI_Comm comm1, const U& x, int shape1);
280
285template <typename T>
286struct dependent_false : std::false_type
287{
288};
289
292template <typename T>
293MPI_Datatype mpi_datatype()
294{
295 if constexpr (std::same_as<T, float>)
296 return MPI_FLOAT;
297 else if constexpr (std::same_as<T, double>)
298 return MPI_DOUBLE;
299 else if constexpr (std::same_as<T, std::complex<float>>)
300 return MPI_C_FLOAT_COMPLEX;
301 else if constexpr (std::same_as<T, std::complex<double>>)
302 return MPI_C_DOUBLE_COMPLEX;
303 else if constexpr (std::same_as<T, std::int8_t>)
304 return MPI_INT8_T;
305 else if constexpr (std::same_as<T, std::int16_t>)
306 return MPI_INT16_T;
307 else if constexpr (std::same_as<T, std::int32_t>)
308 return MPI_INT32_T;
309 else if constexpr (std::same_as<T, std::int64_t>)
310 return MPI_INT64_T;
311 else if constexpr (std::same_as<T, std::uint8_t>)
312 return MPI_UINT8_T;
313 else if constexpr (std::same_as<T, std::uint16_t>)
314 return MPI_UINT16_T;
315 else if constexpr (std::same_as<T, std::uint32_t>)
316 return MPI_UINT32_T;
317 else if constexpr (std::same_as<T, std::uint64_t>)
318 return MPI_UINT64_T;
319 else
320 static_assert(dependent_false<T>::value,
321 "No MPI datatype registered for this type.");
322}
324
327template <typename T>
328MPI_Datatype mpi_t = mpi_datatype<T>();
329
346template <typename T>
348{
349public:
352 explicit Datatype(int count)
353 {
354 // Not error checked, matching the other datatype creation sites in
355 // the library: these are local calls, and under the default
356 // MPI_ERRORS_ARE_FATAL handler a failure aborts before a return code
357 // is visible. There is also no communicator here to abort on --
358 // MPI_COMM_SELF would abort this rank alone and hang the rest.
359 if (count > 1)
360 {
361 MPI_Type_contiguous(count, mpi_t<T>, &_type);
362 MPI_Type_commit(&_type);
363 }
364 }
365
366 // Copy constructor (deleted)
367 Datatype(const Datatype& type) = delete;
368
370 Datatype(Datatype&& type) noexcept : _type(type._type)
371 {
372 type._type = MPI_DATATYPE_NULL;
373 }
374
377 {
378 if (_type != MPI_DATATYPE_NULL)
379 MPI_Type_free(&_type);
380 }
381
382 // Copy assignment (deleted)
383 Datatype& operator=(const Datatype& type) = delete;
384
387 {
388 if (_type != MPI_DATATYPE_NULL)
389 MPI_Type_free(&_type);
390 _type = type._type;
391 type._type = MPI_DATATYPE_NULL;
392 return *this;
393 }
394
397 MPI_Datatype type() const noexcept
398 {
399 return _type == MPI_DATATYPE_NULL ? mpi_t<T> : _type;
400 }
401
402private:
403 // Created contiguous type, or MPI_DATATYPE_NULL if none was created
404 MPI_Datatype _type = MPI_DATATYPE_NULL;
405};
406
423{
424public:
426 Request() = default;
427
429 Request(const Request&) noexcept {}
430
433 : _request(std::exchange(request._request, MPI_REQUEST_NULL))
434 {
435 }
436
438 ~Request() = default;
439
441 Request& operator=(const Request&) noexcept
442 {
443 _request = MPI_REQUEST_NULL;
444 return *this;
445 }
446
449 {
450 _request = std::exchange(request._request, MPI_REQUEST_NULL);
451 return *this;
452 }
453
456 MPI_Request& request() noexcept { return _request; }
457
460 const MPI_Request& request() const noexcept { return _request; }
461
462private:
463 MPI_Request _request = MPI_REQUEST_NULL;
464};
465
466//---------------------------------------------------------------------------
467namespace impl
468{
498std::tuple<std::vector<int>, std::vector<std::int32_t>,
499 std::vector<std::int32_t>>
500postoffice_plan(int size, int rank, std::int32_t shape0_local,
501 std::int64_t shape0, std::int64_t rank_offset);
502
522template <std::ranges::contiguous_range U>
523std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
524postoffice_exchange(MPI_Comm comm, const U& x,
525 std::array<std::int64_t, 2> shape, std::int64_t rank_offset,
526 std::span<const int> dest,
527 std::span<const std::int32_t> num_items_per_dest0,
528 std::span<const std::int32_t> pos_to_neigh_rank,
529 std::span<const int> src)
530{
531 using T = std::ranges::range_value_t<U>;
532
533 const int size = dolfinx::MPI::size(comm);
534 const int rank = dolfinx::MPI::rank(comm);
535 assert(x.size() % shape[1] == 0);
536 const std::int32_t shape0_local = x.size() / shape[1];
537
538 // Create neighbourhood communicator for sending data to post offices
539 MPI_Comm neigh_comm;
540 int err = MPI_Dist_graph_create_adjacent(
541 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
542 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm);
543 dolfinx::MPI::check_error(comm, err);
544
545 // Compute send displacements
546 std::vector<std::int32_t> num_items_per_dest(num_items_per_dest0.begin(),
547 num_items_per_dest0.end());
548 std::vector<std::int32_t> send_disp{0};
549 std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(),
550 std::back_inserter(send_disp));
551
552 // Pack send buffers
553 std::vector<T> send_buffer_data(shape[1] * send_disp.back());
554 std::vector<std::int64_t> send_buffer_index(send_disp.back());
555 {
556 std::vector<std::int32_t> send_offsets = send_disp;
557 for (std::int32_t i = 0; i < shape0_local; ++i)
558 {
559 if (int neigh_dest = pos_to_neigh_rank[i]; neigh_dest != -1)
560 {
561 std::size_t pos = send_offsets[neigh_dest];
562 send_buffer_index[pos] = i + rank_offset;
563 std::copy_n(std::next(x.begin(), i * shape[1]), shape[1],
564 std::next(send_buffer_data.begin(), shape[1] * pos));
565 ++send_offsets[neigh_dest];
566 }
567 }
568 }
569
570 // Send number of items to post offices (destination) that I will be
571 // sending
572 std::vector<int> num_items_recv(src.size());
573 num_items_per_dest.reserve(1);
574 num_items_recv.reserve(1);
575 err = MPI_Neighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT,
576 num_items_recv.data(), 1, MPI_INT, neigh_comm);
577 dolfinx::MPI::check_error(comm, err);
578
579 // Prepare receive displacement and buffers
580 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
581 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
582 std::next(recv_disp.begin()));
583
584 // Send/receive global indices
585 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
586 err = MPI_Neighbor_alltoallv(
587 send_buffer_index.data(), num_items_per_dest.data(), send_disp.data(),
588 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
589 recv_disp.data(), MPI_INT64_T, neigh_comm);
590 dolfinx::MPI::check_error(comm, err);
591
592 // Send/receive data (x)
593 MPI_Datatype compound_type;
594 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type);
595 MPI_Type_commit(&compound_type);
596 std::vector<T> recv_buffer_data(shape[1] * recv_disp.back());
597 err = MPI_Neighbor_alltoallv(
598 send_buffer_data.data(), num_items_per_dest.data(), send_disp.data(),
599 compound_type, recv_buffer_data.data(), num_items_recv.data(),
600 recv_disp.data(), compound_type, neigh_comm);
601 dolfinx::MPI::check_error(comm, err);
602 err = MPI_Type_free(&compound_type);
603 dolfinx::MPI::check_error(comm, err);
604 err = MPI_Comm_free(&neigh_comm);
605 dolfinx::MPI::check_error(comm, err);
606
607 // Convert to local indices
608 const std::int64_t r0 = common::local_range(rank, shape[0], size)[0];
609 std::vector<std::int32_t> index_local(recv_buffer_index.size());
610 std::ranges::transform(recv_buffer_index, index_local.begin(),
611 [r0](std::int64_t idx) { return idx - r0; });
612
613 return {index_local, recv_buffer_data};
614}
615} // namespace impl
616
617template <std::ranges::contiguous_range U>
618std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
619distribute_to_postoffice(MPI_Comm comm, const U& x,
620 std::array<std::int64_t, 2> shape,
621 std::int64_t rank_offset)
622{
623 assert(rank_offset >= 0 or x.empty());
624 assert(x.size() % shape[1] == 0);
625 const std::int32_t shape0_local = x.size() / shape[1];
626
627 logger().debug("Sending data to post offices (distribute_to_postoffice)");
628
629 const int size = dolfinx::MPI::size(comm);
630 const int rank = dolfinx::MPI::rank(comm);
631 auto [dest, num_items_per_dest, pos_to_neigh_rank]
632 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
633
634 // Determine source ranks
635 const std::vector<int> src = MPI::compute_graph_edges_nbx(comm, dest);
636 logger().info(
637 "Number of neighbourhood source ranks in distribute_to_postoffice: {}",
638 src.size());
639
640 auto result
641 = impl::postoffice_exchange(comm, x, shape, rank_offset, dest,
642 num_items_per_dest, pos_to_neigh_rank, src);
643 logger().debug("Completed send data to post offices.");
644 return result;
645}
646//---------------------------------------------------------------------------
647template <std::ranges::contiguous_range U>
648std::vector<std::ranges::range_value_t<U>>
649distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
650 const U& x, std::array<std::int64_t, 2> shape,
651 std::int64_t rank_offset)
652{
653 assert(rank_offset >= 0 or x.empty());
654 using T = std::ranges::range_value_t<U>;
655
656 common::Timer timer("Distribute row-wise data (scalable)");
657 assert(shape[1] > 0);
658
659 const int size = dolfinx::MPI::size(comm);
660 const int rank = dolfinx::MPI::rank(comm);
661 assert(x.size() % shape[1] == 0);
662 const std::int64_t shape0_local = x.size() / shape[1];
663
664 // 0. Send x data to/from post offices, and 1. determine which post
665 // office ranks hold the data I need (indices) -- these are
666 // independent local computations, so the two NBX consensus
667 // rounds they each need (below) are run concurrently in a single
668 // overlapped round rather than back-to-back.
669
670 auto [send_dest, num_items_per_send_dest, pos_to_neigh_rank]
671 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
672
673 // Build (src, global index, position) for each entry in 'indices'
674 // not held locally, then sort. Locally-held entries are read
675 // directly below -- skipping them here avoids a wasted round trip.
676 std::vector<std::tuple<int, std::int64_t, std::int32_t>> src_to_index;
677 for (std::size_t i = 0; i < indices.size(); ++i)
678 {
679 std::int64_t idx = indices[i];
680 if (idx >= rank_offset and idx < rank_offset + shape0_local)
681 continue;
682 if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank)
683 src_to_index.push_back({src, idx, i});
684 }
685
686 // Radix sort on the rank alone (not the full tuple) -- only
687 // grouping by rank matters below; order within a group doesn't.
688 {
689 std::vector<std::int32_t> perm(src_to_index.size());
690 std::iota(perm.begin(), perm.end(), 0);
691 dolfinx::radix_sort(perm, [&src_to_index](std::int32_t i)
692 { return std::get<0>(src_to_index[i]); });
693 std::vector<std::tuple<int, std::int64_t, std::int32_t>> sorted(
694 src_to_index.size());
695 for (std::size_t i = 0; i < perm.size(); ++i)
696 sorted[i] = src_to_index[perm[i]];
697 src_to_index = std::move(sorted);
698 }
699
700 // Build list of neighbour src ranks and count number of items (rows
701 // of x) to receive from each src post office (by neighbourhood rank)
702 std::vector<std::int32_t> num_items_per_src;
703 std::vector<int> src;
704 {
705 auto it = src_to_index.begin();
706 while (it != src_to_index.end())
707 {
708 src.push_back(std::get<0>(*it));
709 auto it1 = std::ranges::find_if(it, src_to_index.end(),
710 [r = src.back()](auto& idx)
711 { return std::get<0>(idx) != r; });
712 num_items_per_src.push_back(std::ranges::distance(it, it1));
713 it = it1;
714 }
715 }
716
717 // Determine, in one overlapped NBX round, (0) the post office ranks
718 // that hold data for me to receive (post office send round) and (1)
719 // the 'delivery' destination ranks that want data from me (my
720 // request round)
721 auto [post_src, dest] = dolfinx::MPI::compute_graph_edges_nbx(
722 comm, send_dest, static_cast<int>(tag::consensus_nbx), src,
723 static_cast<int>(tag::consensus_nbx) + 1);
724 logger().info(
725 "Neighbourhood destination ranks from post office in "
726 "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}",
727 rank, dest.size(), static_cast<double>(dest.size()) / size);
728
729 // Send receive x data to post office (only for rows that need to be
730 // communicated)
731 auto [post_indices, post_x] = impl::postoffice_exchange(
732 comm, x, {shape[0], shape[1]}, rank_offset, send_dest,
733 num_items_per_send_dest, pos_to_neigh_rank, post_src);
734 assert(post_indices.size() == post_x.size() / shape[1]);
735
736 // Create neighbourhood communicator for sending data to post offices
737 // (src), and receiving data form my send my post office
738 MPI_Comm neigh_comm0;
739 int err = MPI_Dist_graph_create_adjacent(
740 comm, dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(), src.data(),
741 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
742 dolfinx::MPI::check_error(comm, err);
743
744 // Communicate number of requests to each source
745 std::vector<int> num_items_recv(dest.size());
746 num_items_per_src.reserve(1);
747 num_items_recv.reserve(1);
748 err = MPI_Neighbor_alltoall(num_items_per_src.data(), 1, MPI_INT,
749 num_items_recv.data(), 1, MPI_INT, neigh_comm0);
750 dolfinx::MPI::check_error(comm, err);
751
752 // Prepare send/receive displacements
753 std::vector<std::int32_t> send_disp{0};
754 std::partial_sum(num_items_per_src.begin(), num_items_per_src.end(),
755 std::back_inserter(send_disp));
756 std::vector<std::int32_t> recv_disp = {0};
757 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
758 std::back_inserter(recv_disp));
759
760 // Pack my requested indices (global) in send buffer ready to send to
761 // post offices
762 assert(send_disp.back() == static_cast<int>(src_to_index.size()));
763 std::vector<std::int64_t> send_buffer_index(src_to_index.size());
764 std::ranges::transform(src_to_index, send_buffer_index.begin(),
765 [](auto x) { return std::get<1>(x); });
766
767 // Prepare the receive buffer
768 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
769 err = MPI_Neighbor_alltoallv(
770 send_buffer_index.data(), num_items_per_src.data(), send_disp.data(),
771 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
772 recv_disp.data(), MPI_INT64_T, neigh_comm0);
773 dolfinx::MPI::check_error(comm, err);
774
775 err = MPI_Comm_free(&neigh_comm0);
776 dolfinx::MPI::check_error(comm, err);
777
778 // 2. Send data (rows of x) from post office back to requesting ranks
779 // (transpose of the preceding communication pattern operation)
780
781 // Build map from local index to post_indices position. Set to -1 for
782 // data that was already on this rank and was therefore was not
783 // sent/received via a postoffice.
784 const std::array<std::int64_t, 2> postoffice_range
785 = common::local_range(rank, shape[0], size);
786 std::vector<std::int32_t> post_indices_map(
787 postoffice_range[1] - postoffice_range[0], -1);
788 for (std::size_t i = 0; i < post_indices.size(); ++i)
789 {
790 assert(post_indices[i] < static_cast<int>(post_indices_map.size()));
791 post_indices_map[post_indices[i]] = i;
792 }
793
794 // Build send buffer
795 std::vector<T> send_buffer_data(shape[1] * recv_disp.back());
796 for (std::int32_t i = 0; i < recv_disp.back(); ++i)
797 {
798 std::int64_t index = recv_buffer_index[i];
799 if (index >= rank_offset and index < (rank_offset + shape0_local))
800 {
801 // I already had this index before any communication
802 std::int32_t local_index = index - rank_offset;
803 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
804 std::next(send_buffer_data.begin(), shape[1] * i));
805 }
806 else
807 {
808 // Take from my 'post bag'
809 std::int64_t local_index = index - postoffice_range[0];
810 std::int32_t pos = post_indices_map[local_index];
811 assert(pos != -1);
812 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
813 std::next(send_buffer_data.begin(), shape[1] * i));
814 }
815 }
816
817 err = MPI_Dist_graph_create_adjacent(
818 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
819 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
820 dolfinx::MPI::check_error(comm, err);
821
822 MPI_Datatype compound_type0;
823 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type0);
824 MPI_Type_commit(&compound_type0);
825
826 std::vector<T> recv_buffer_data(shape[1] * send_disp.back());
827 err = MPI_Neighbor_alltoallv(
828 send_buffer_data.data(), num_items_recv.data(), recv_disp.data(),
829 compound_type0, recv_buffer_data.data(), num_items_per_src.data(),
830 send_disp.data(), compound_type0, neigh_comm0);
831 dolfinx::MPI::check_error(comm, err);
832
833 err = MPI_Type_free(&compound_type0);
834 dolfinx::MPI::check_error(comm, err);
835 err = MPI_Comm_free(&neigh_comm0);
836 dolfinx::MPI::check_error(comm, err);
837
838 std::vector<std::int32_t> index_pos_to_buffer(indices.size(), -1);
839 for (std::size_t i = 0; i < src_to_index.size(); ++i)
840 index_pos_to_buffer[std::get<2>(src_to_index[i])] = i;
841
842 // Extra data to return
843 std::vector<T> x_new(shape[1] * indices.size());
844 for (std::size_t i = 0; i < indices.size(); ++i)
845 {
846 const std::int64_t index = indices[i];
847 if (index >= rank_offset and index < (rank_offset + shape0_local))
848 {
849 // Had data from the start in x
850 std::int64_t local_index = index - rank_offset;
851 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
852 std::next(x_new.begin(), shape[1] * i));
853 }
854 else if (std::int32_t pos = index_pos_to_buffer[i]; pos != -1)
855 {
856 // In my received post: index_pos_to_buffer[i] != -1 iff
857 // index_owner would say this rank isn't the owner -- avoids
858 // recomputing it.
859 std::copy_n(std::next(recv_buffer_data.begin(), shape[1] * pos), shape[1],
860 std::next(x_new.begin(), shape[1] * i));
861 }
862 else
863 {
864 // In my post office bag
865 std::int64_t local_index = index - postoffice_range[0];
866 std::int32_t bag_pos = post_indices_map[local_index];
867 assert(bag_pos != -1);
868 std::copy_n(std::next(post_x.begin(), shape[1] * bag_pos), shape[1],
869 std::next(x_new.begin(), shape[1] * i));
870 }
871 }
872
873 return x_new;
874}
875//---------------------------------------------------------------------------
876template <std::ranges::contiguous_range U>
877std::vector<std::ranges::range_value_t<U>>
878distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
879 MPI_Comm comm1, const U& x, int shape1)
880{
881 if (shape1 <= 0)
882 throw std::invalid_argument("distribute_data: shape1 must be positive");
883 if (x.size() % shape1 != 0)
884 {
885 throw std::invalid_argument(
886 "distribute_data: x.size() must be a multiple of shape1");
887 }
888 const std::int64_t shape0_local = x.size() / shape1;
889
890 // A rank outside comm1 must hold no data. Check this collectively before
891 // the later comm0/comm1 collectives to avoid leaving other ranks blocked.
892 {
893 int invalid_local = (comm1 == MPI_COMM_NULL and !x.empty()) ? 1 : 0;
894 int invalid = 0;
895 int err
896 = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
897 dolfinx::MPI::check_error(comm0, err);
898 if (invalid)
899 throw std::invalid_argument("Non-empty data on null MPI communicator");
900 }
901
902 std::int64_t shape0 = 0;
903 int err
904 = MPI_Allreduce(&shape0_local, &shape0, 1, MPI_INT64_T, MPI_SUM, comm0);
905 dolfinx::MPI::check_error(comm0, err);
906
907#ifndef NDEBUG
908 {
909 int invalid_local = !std::ranges::all_of(indices, [shape0](std::int64_t i)
910 { return i >= 0 and i < shape0; });
911 int invalid = 0;
912 err = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
913 dolfinx::MPI::check_error(comm0, err);
914 if (invalid)
915 {
916 throw std::out_of_range(
917 std::format("distribute_data: index outside the global row range "
918 "[0, {}) of the distributed data.",
919 shape0));
920 }
921 }
922#endif
923
924 std::int64_t rank_offset = -1;
925 if (comm1 != MPI_COMM_NULL)
926 {
927 rank_offset = 0;
928 err = MPI_Exscan(&shape0_local, &rank_offset, 1,
929 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm1);
930 dolfinx::MPI::check_error(comm1, err);
931 }
932
933 return distribute_from_postoffice(comm0, indices, x, {shape0, shape1},
934 rank_offset);
935}
936//---------------------------------------------------------------------------
937
938} // namespace dolfinx::MPI
Comm(MPI_Comm comm, bool duplicate=true)
Duplicate communicator and wrap duplicate.
Definition MPI.cpp:21
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:348
Datatype & operator=(Datatype &&type) noexcept
Move assignment.
Definition MPI.h:386
MPI_Datatype type() const noexcept
The datatype to pass to MPI.
Definition MPI.h:397
Datatype(Datatype &&type) noexcept
Move constructor.
Definition MPI.h:370
Datatype(int count)
Create a datatype for count contiguous values.
Definition MPI.h:352
~Datatype()
Destructor (frees the datatype, if one was created).
Definition MPI.h:376
Request()=default
Create a null request.
Request & operator=(Request &&request) noexcept
Move assignment.
Definition MPI.h:448
Request & operator=(const Request &) noexcept
Copy assignment. Leaves this with no request in flight.
Definition MPI.h:441
MPI_Request & request() noexcept
The handle to pass to MPI.
Definition MPI.h:456
Request(Request &&request) noexcept
Move constructor.
Definition MPI.h:432
Request(const Request &) noexcept
Copy constructor. The copy has no request in flight.
Definition MPI.h:429
~Request()=default
Destructor.
const MPI_Request & request() const noexcept
The handle.
Definition MPI.h:460
Timer for measuring and logging elapsed time durations.
Definition Timer.h:41
MPI support functionality.
Definition MPI.h:36
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:328
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
std::vector< std::ranges::range_value_t< U > > distribute_from_postoffice(MPI_Comm comm, std::span< const std::int64_t > indices, const U &x, std::array< std::int64_t, 2 > shape, std::int64_t rank_offset)
Fetch rows of a distributed row-major array via their post office ranks.
Definition MPI.h:649
std::vector< int > compute_graph_edges_nbx(MPI_Comm comm, std::span< const int > edges, int tag=static_cast< int >(tag::consensus_nbx))
Determine incoming graph edges using the NBX consensus algorithm.
Definition MPI.cpp:294
constexpr int index_owner(int size, std::size_t index, std::size_t N)
Return which rank owns index in global range [0, N - 1] (inverse of MPI::local_range).
Definition MPI.h:98
std::pair< std::vector< std::int32_t >, std::vector< std::ranges::range_value_t< U > > > distribute_to_postoffice(MPI_Comm comm, const U &x, std::array< std::int64_t, 2 > shape, std::int64_t rank_offset)
Send row data to its 'post office' rank.
Definition MPI.h:619
std::vector< int > compute_graph_edges_pcx(MPI_Comm comm, std::span< const int > edges)
Determine incoming graph edges using the PCX consensus algorithm.
Definition MPI.cpp:104
int size(MPI_Comm comm)
Definition MPI.cpp:81
std::vector< std::ranges::range_value_t< U > > distribute_data(MPI_Comm comm0, std::span< const std::int64_t > indices, MPI_Comm comm1, const U &x, int shape1)
Distribute rows of a row-major array to the ranks that require them, via the post office pattern.
Definition MPI.h:878
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
tag
MPI communication tags.
Definition MPI.h:39
constexpr std::array< std::int64_t, 2 > local_range(int index, std::int64_t N, int size)
Partition a global range [0, N - 1] across callers into non-overlapping sub-partitions of almost equa...
Definition local_range.h:26
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
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24