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
284template <typename T>
285struct dependent_false : std::false_type
286{
287};
288
291template <typename T>
292MPI_Datatype mpi_datatype()
293{
294 if constexpr (std::same_as<T, float>)
295 return MPI_FLOAT;
296 else if constexpr (std::same_as<T, double>)
297 return MPI_DOUBLE;
298 else if constexpr (std::same_as<T, std::complex<float>>)
299 return MPI_C_FLOAT_COMPLEX;
300 else if constexpr (std::same_as<T, std::complex<double>>)
301 return MPI_C_DOUBLE_COMPLEX;
302 else if constexpr (std::same_as<T, std::int8_t>)
303 return MPI_INT8_T;
304 else if constexpr (std::same_as<T, std::int16_t>)
305 return MPI_INT16_T;
306 else if constexpr (std::same_as<T, std::int32_t>)
307 return MPI_INT32_T;
308 else if constexpr (std::same_as<T, std::int64_t>)
309 return MPI_INT64_T;
310 else if constexpr (std::same_as<T, std::uint8_t>)
311 return MPI_UINT8_T;
312 else if constexpr (std::same_as<T, std::uint16_t>)
313 return MPI_UINT16_T;
314 else if constexpr (std::same_as<T, std::uint32_t>)
315 return MPI_UINT32_T;
316 else if constexpr (std::same_as<T, std::uint64_t>)
317 return MPI_UINT64_T;
318 else
319 static_assert(dependent_false<T>::value,
320 "No MPI datatype registered for this type.");
321}
322
325template <typename T>
326MPI_Datatype mpi_t = mpi_datatype<T>();
327
344template <typename T>
346{
347public:
350 explicit Datatype(int count)
351 {
352 // Not error checked, matching the other datatype creation sites in
353 // the library: these are local calls, and under the default
354 // MPI_ERRORS_ARE_FATAL handler a failure aborts before a return code
355 // is visible. There is also no communicator here to abort on --
356 // MPI_COMM_SELF would abort this rank alone and hang the rest.
357 if (count > 1)
358 {
359 MPI_Type_contiguous(count, mpi_t<T>, &_type);
360 MPI_Type_commit(&_type);
361 }
362 }
363
364 // Copy constructor (deleted)
365 Datatype(const Datatype& type) = delete;
366
368 Datatype(Datatype&& type) noexcept : _type(type._type)
369 {
370 type._type = MPI_DATATYPE_NULL;
371 }
372
375 {
376 if (_type != MPI_DATATYPE_NULL)
377 MPI_Type_free(&_type);
378 }
379
380 // Copy assignment (deleted)
381 Datatype& operator=(const Datatype& type) = delete;
382
385 {
386 if (_type != MPI_DATATYPE_NULL)
387 MPI_Type_free(&_type);
388 _type = type._type;
389 type._type = MPI_DATATYPE_NULL;
390 return *this;
391 }
392
395 MPI_Datatype type() const noexcept
396 {
397 return _type == MPI_DATATYPE_NULL ? mpi_t<T> : _type;
398 }
399
400private:
401 // Created contiguous type, or MPI_DATATYPE_NULL if none was created
402 MPI_Datatype _type = MPI_DATATYPE_NULL;
403};
404
421{
422public:
424 Request() = default;
425
427 Request(const Request&) noexcept {}
428
431 : _request(std::exchange(request._request, MPI_REQUEST_NULL))
432 {
433 }
434
436 ~Request() = default;
437
439 Request& operator=(const Request&) noexcept
440 {
441 _request = MPI_REQUEST_NULL;
442 return *this;
443 }
444
447 {
448 _request = std::exchange(request._request, MPI_REQUEST_NULL);
449 return *this;
450 }
451
454 MPI_Request& request() noexcept { return _request; }
455
458 const MPI_Request& request() const noexcept { return _request; }
459
460private:
461 MPI_Request _request = MPI_REQUEST_NULL;
462};
463
464//---------------------------------------------------------------------------
465namespace impl
466{
496std::tuple<std::vector<int>, std::vector<std::int32_t>,
497 std::vector<std::int32_t>>
498postoffice_plan(int size, int rank, std::int32_t shape0_local,
499 std::int64_t shape0, std::int64_t rank_offset);
500
520template <std::ranges::contiguous_range U>
521std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
522postoffice_exchange(MPI_Comm comm, const U& x,
523 std::array<std::int64_t, 2> shape, std::int64_t rank_offset,
524 std::span<const int> dest,
525 std::span<const std::int32_t> num_items_per_dest0,
526 std::span<const std::int32_t> pos_to_neigh_rank,
527 std::span<const int> src)
528{
529 using T = std::ranges::range_value_t<U>;
530
531 const int size = dolfinx::MPI::size(comm);
532 const int rank = dolfinx::MPI::rank(comm);
533 assert(x.size() % shape[1] == 0);
534 const std::int32_t shape0_local = x.size() / shape[1];
535
536 // Create neighbourhood communicator for sending data to post offices
537 MPI_Comm neigh_comm;
538 int err = MPI_Dist_graph_create_adjacent(
539 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
540 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm);
541 dolfinx::MPI::check_error(comm, err);
542
543 // Compute send displacements
544 std::vector<std::int32_t> num_items_per_dest(num_items_per_dest0.begin(),
545 num_items_per_dest0.end());
546 std::vector<std::int32_t> send_disp{0};
547 std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(),
548 std::back_inserter(send_disp));
549
550 // Pack send buffers
551 std::vector<T> send_buffer_data(shape[1] * send_disp.back());
552 std::vector<std::int64_t> send_buffer_index(send_disp.back());
553 {
554 std::vector<std::int32_t> send_offsets = send_disp;
555 for (std::int32_t i = 0; i < shape0_local; ++i)
556 {
557 if (int neigh_dest = pos_to_neigh_rank[i]; neigh_dest != -1)
558 {
559 std::size_t pos = send_offsets[neigh_dest];
560 send_buffer_index[pos] = i + rank_offset;
561 std::copy_n(std::next(x.begin(), i * shape[1]), shape[1],
562 std::next(send_buffer_data.begin(), shape[1] * pos));
563 ++send_offsets[neigh_dest];
564 }
565 }
566 }
567
568 // Send number of items to post offices (destination) that I will be
569 // sending
570 std::vector<int> num_items_recv(src.size());
571 num_items_per_dest.reserve(1);
572 num_items_recv.reserve(1);
573 err = MPI_Neighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT,
574 num_items_recv.data(), 1, MPI_INT, neigh_comm);
575 dolfinx::MPI::check_error(comm, err);
576
577 // Prepare receive displacement and buffers
578 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
579 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
580 std::next(recv_disp.begin()));
581
582 // Send/receive global indices
583 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
584 err = MPI_Neighbor_alltoallv(
585 send_buffer_index.data(), num_items_per_dest.data(), send_disp.data(),
586 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
587 recv_disp.data(), MPI_INT64_T, neigh_comm);
588 dolfinx::MPI::check_error(comm, err);
589
590 // Send/receive data (x)
591 MPI_Datatype compound_type;
592 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type);
593 MPI_Type_commit(&compound_type);
594 std::vector<T> recv_buffer_data(shape[1] * recv_disp.back());
595 err = MPI_Neighbor_alltoallv(
596 send_buffer_data.data(), num_items_per_dest.data(), send_disp.data(),
597 compound_type, recv_buffer_data.data(), num_items_recv.data(),
598 recv_disp.data(), compound_type, neigh_comm);
599 dolfinx::MPI::check_error(comm, err);
600 err = MPI_Type_free(&compound_type);
601 dolfinx::MPI::check_error(comm, err);
602 err = MPI_Comm_free(&neigh_comm);
603 dolfinx::MPI::check_error(comm, err);
604
605 // Convert to local indices
606 const std::int64_t r0 = common::local_range(rank, shape[0], size)[0];
607 std::vector<std::int32_t> index_local(recv_buffer_index.size());
608 std::ranges::transform(recv_buffer_index, index_local.begin(),
609 [r0](std::int64_t idx) { return idx - r0; });
610
611 return {index_local, recv_buffer_data};
612}
613} // namespace impl
614
615template <std::ranges::contiguous_range U>
616std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
617distribute_to_postoffice(MPI_Comm comm, const U& x,
618 std::array<std::int64_t, 2> shape,
619 std::int64_t rank_offset)
620{
621 assert(rank_offset >= 0 or x.empty());
622 assert(x.size() % shape[1] == 0);
623 const std::int32_t shape0_local = x.size() / shape[1];
624
625 logger().debug("Sending data to post offices (distribute_to_postoffice)");
626
627 const int size = dolfinx::MPI::size(comm);
628 const int rank = dolfinx::MPI::rank(comm);
629 auto [dest, num_items_per_dest, pos_to_neigh_rank]
630 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
631
632 // Determine source ranks
633 const std::vector<int> src = MPI::compute_graph_edges_nbx(comm, dest);
634 logger().info(
635 "Number of neighbourhood source ranks in distribute_to_postoffice: {}",
636 src.size());
637
638 auto result
639 = impl::postoffice_exchange(comm, x, shape, rank_offset, dest,
640 num_items_per_dest, pos_to_neigh_rank, src);
641 logger().debug("Completed send data to post offices.");
642 return result;
643}
644//---------------------------------------------------------------------------
645template <std::ranges::contiguous_range U>
646std::vector<std::ranges::range_value_t<U>>
647distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
648 const U& x, std::array<std::int64_t, 2> shape,
649 std::int64_t rank_offset)
650{
651 assert(rank_offset >= 0 or x.empty());
652 using T = std::ranges::range_value_t<U>;
653
654 common::Timer timer("Distribute row-wise data (scalable)");
655 assert(shape[1] > 0);
656
657 const int size = dolfinx::MPI::size(comm);
658 const int rank = dolfinx::MPI::rank(comm);
659 assert(x.size() % shape[1] == 0);
660 const std::int64_t shape0_local = x.size() / shape[1];
661
662 // 0. Send x data to/from post offices, and 1. determine which post
663 // office ranks hold the data I need (indices) -- these are
664 // independent local computations, so the two NBX consensus
665 // rounds they each need (below) are run concurrently in a single
666 // overlapped round rather than back-to-back.
667
668 auto [send_dest, num_items_per_send_dest, pos_to_neigh_rank]
669 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
670
671 // Build (src, global index, position) for each entry in 'indices'
672 // not held locally, then sort. Locally-held entries are read
673 // directly below -- skipping them here avoids a wasted round trip.
674 std::vector<std::tuple<int, std::int64_t, std::int32_t>> src_to_index;
675 for (std::size_t i = 0; i < indices.size(); ++i)
676 {
677 std::int64_t idx = indices[i];
678 if (idx >= rank_offset and idx < rank_offset + shape0_local)
679 continue;
680 if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank)
681 src_to_index.push_back({src, idx, i});
682 }
683
684 // Radix sort on the rank alone (not the full tuple) -- only
685 // grouping by rank matters below; order within a group doesn't.
686 {
687 std::vector<std::int32_t> perm(src_to_index.size());
688 std::iota(perm.begin(), perm.end(), 0);
689 dolfinx::radix_sort(perm, [&src_to_index](std::int32_t i)
690 { return std::get<0>(src_to_index[i]); });
691 std::vector<std::tuple<int, std::int64_t, std::int32_t>> sorted(
692 src_to_index.size());
693 for (std::size_t i = 0; i < perm.size(); ++i)
694 sorted[i] = src_to_index[perm[i]];
695 src_to_index = std::move(sorted);
696 }
697
698 // Build list of neighbour src ranks and count number of items (rows
699 // of x) to receive from each src post office (by neighbourhood rank)
700 std::vector<std::int32_t> num_items_per_src;
701 std::vector<int> src;
702 {
703 auto it = src_to_index.begin();
704 while (it != src_to_index.end())
705 {
706 src.push_back(std::get<0>(*it));
707 auto it1 = std::ranges::find_if(it, src_to_index.end(),
708 [r = src.back()](auto& idx)
709 { return std::get<0>(idx) != r; });
710 num_items_per_src.push_back(std::ranges::distance(it, it1));
711 it = it1;
712 }
713 }
714
715 // Determine, in one overlapped NBX round, (0) the post office ranks
716 // that hold data for me to receive (post office send round) and (1)
717 // the 'delivery' destination ranks that want data from me (my
718 // request round)
719 auto [post_src, dest] = dolfinx::MPI::compute_graph_edges_nbx(
720 comm, send_dest, static_cast<int>(tag::consensus_nbx), src,
721 static_cast<int>(tag::consensus_nbx) + 1);
722 logger().info(
723 "Neighbourhood destination ranks from post office in "
724 "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}",
725 rank, dest.size(), static_cast<double>(dest.size()) / size);
726
727 // Send receive x data to post office (only for rows that need to be
728 // communicated)
729 auto [post_indices, post_x] = impl::postoffice_exchange(
730 comm, x, {shape[0], shape[1]}, rank_offset, send_dest,
731 num_items_per_send_dest, pos_to_neigh_rank, post_src);
732 assert(post_indices.size() == post_x.size() / shape[1]);
733
734 // Create neighbourhood communicator for sending data to post offices
735 // (src), and receiving data form my send my post office
736 MPI_Comm neigh_comm0;
737 int err = MPI_Dist_graph_create_adjacent(
738 comm, dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(), src.data(),
739 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
740 dolfinx::MPI::check_error(comm, err);
741
742 // Communicate number of requests to each source
743 std::vector<int> num_items_recv(dest.size());
744 num_items_per_src.reserve(1);
745 num_items_recv.reserve(1);
746 err = MPI_Neighbor_alltoall(num_items_per_src.data(), 1, MPI_INT,
747 num_items_recv.data(), 1, MPI_INT, neigh_comm0);
748 dolfinx::MPI::check_error(comm, err);
749
750 // Prepare send/receive displacements
751 std::vector<std::int32_t> send_disp{0};
752 std::partial_sum(num_items_per_src.begin(), num_items_per_src.end(),
753 std::back_inserter(send_disp));
754 std::vector<std::int32_t> recv_disp = {0};
755 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
756 std::back_inserter(recv_disp));
757
758 // Pack my requested indices (global) in send buffer ready to send to
759 // post offices
760 assert(send_disp.back() == static_cast<int>(src_to_index.size()));
761 std::vector<std::int64_t> send_buffer_index(src_to_index.size());
762 std::ranges::transform(src_to_index, send_buffer_index.begin(),
763 [](auto x) { return std::get<1>(x); });
764
765 // Prepare the receive buffer
766 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
767 err = MPI_Neighbor_alltoallv(
768 send_buffer_index.data(), num_items_per_src.data(), send_disp.data(),
769 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
770 recv_disp.data(), MPI_INT64_T, neigh_comm0);
771 dolfinx::MPI::check_error(comm, err);
772
773 err = MPI_Comm_free(&neigh_comm0);
774 dolfinx::MPI::check_error(comm, err);
775
776 // 2. Send data (rows of x) from post office back to requesting ranks
777 // (transpose of the preceding communication pattern operation)
778
779 // Build map from local index to post_indices position. Set to -1 for
780 // data that was already on this rank and was therefore was not
781 // sent/received via a postoffice.
782 const std::array<std::int64_t, 2> postoffice_range
783 = common::local_range(rank, shape[0], size);
784 std::vector<std::int32_t> post_indices_map(
785 postoffice_range[1] - postoffice_range[0], -1);
786 for (std::size_t i = 0; i < post_indices.size(); ++i)
787 {
788 assert(post_indices[i] < static_cast<int>(post_indices_map.size()));
789 post_indices_map[post_indices[i]] = i;
790 }
791
792 // Build send buffer
793 std::vector<T> send_buffer_data(shape[1] * recv_disp.back());
794 for (std::int32_t i = 0; i < recv_disp.back(); ++i)
795 {
796 std::int64_t index = recv_buffer_index[i];
797 if (index >= rank_offset and index < (rank_offset + shape0_local))
798 {
799 // I already had this index before any communication
800 std::int32_t local_index = index - rank_offset;
801 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
802 std::next(send_buffer_data.begin(), shape[1] * i));
803 }
804 else
805 {
806 // Take from my 'post bag'
807 std::int64_t local_index = index - postoffice_range[0];
808 std::int32_t pos = post_indices_map[local_index];
809 assert(pos != -1);
810 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
811 std::next(send_buffer_data.begin(), shape[1] * i));
812 }
813 }
814
815 err = MPI_Dist_graph_create_adjacent(
816 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
817 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
818 dolfinx::MPI::check_error(comm, err);
819
820 MPI_Datatype compound_type0;
821 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type0);
822 MPI_Type_commit(&compound_type0);
823
824 std::vector<T> recv_buffer_data(shape[1] * send_disp.back());
825 err = MPI_Neighbor_alltoallv(
826 send_buffer_data.data(), num_items_recv.data(), recv_disp.data(),
827 compound_type0, recv_buffer_data.data(), num_items_per_src.data(),
828 send_disp.data(), compound_type0, neigh_comm0);
829 dolfinx::MPI::check_error(comm, err);
830
831 err = MPI_Type_free(&compound_type0);
832 dolfinx::MPI::check_error(comm, err);
833 err = MPI_Comm_free(&neigh_comm0);
834 dolfinx::MPI::check_error(comm, err);
835
836 std::vector<std::int32_t> index_pos_to_buffer(indices.size(), -1);
837 for (std::size_t i = 0; i < src_to_index.size(); ++i)
838 index_pos_to_buffer[std::get<2>(src_to_index[i])] = i;
839
840 // Extra data to return
841 std::vector<T> x_new(shape[1] * indices.size());
842 for (std::size_t i = 0; i < indices.size(); ++i)
843 {
844 const std::int64_t index = indices[i];
845 if (index >= rank_offset and index < (rank_offset + shape0_local))
846 {
847 // Had data from the start in x
848 std::int64_t local_index = index - rank_offset;
849 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
850 std::next(x_new.begin(), shape[1] * i));
851 }
852 else if (std::int32_t pos = index_pos_to_buffer[i]; pos != -1)
853 {
854 // In my received post: index_pos_to_buffer[i] != -1 iff
855 // index_owner would say this rank isn't the owner -- avoids
856 // recomputing it.
857 std::copy_n(std::next(recv_buffer_data.begin(), shape[1] * pos), shape[1],
858 std::next(x_new.begin(), shape[1] * i));
859 }
860 else
861 {
862 // In my post office bag
863 std::int64_t local_index = index - postoffice_range[0];
864 std::int32_t bag_pos = post_indices_map[local_index];
865 assert(bag_pos != -1);
866 std::copy_n(std::next(post_x.begin(), shape[1] * bag_pos), shape[1],
867 std::next(x_new.begin(), shape[1] * i));
868 }
869 }
870
871 return x_new;
872}
873//---------------------------------------------------------------------------
874template <std::ranges::contiguous_range U>
875std::vector<std::ranges::range_value_t<U>>
876distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
877 MPI_Comm comm1, const U& x, int shape1)
878{
879 if (shape1 <= 0)
880 throw std::invalid_argument("distribute_data: shape1 must be positive");
881 if (x.size() % shape1 != 0)
882 {
883 throw std::invalid_argument(
884 "distribute_data: x.size() must be a multiple of shape1");
885 }
886 const std::int64_t shape0_local = x.size() / shape1;
887
888 // A rank outside comm1 must hold no data. Check this collectively before
889 // the later comm0/comm1 collectives to avoid leaving other ranks blocked.
890 {
891 int invalid_local = (comm1 == MPI_COMM_NULL and !x.empty()) ? 1 : 0;
892 int invalid = 0;
893 int err
894 = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
895 dolfinx::MPI::check_error(comm0, err);
896 if (invalid)
897 throw std::invalid_argument("Non-empty data on null MPI communicator");
898 }
899
900 std::int64_t shape0 = 0;
901 int err
902 = MPI_Allreduce(&shape0_local, &shape0, 1, MPI_INT64_T, MPI_SUM, comm0);
903 dolfinx::MPI::check_error(comm0, err);
904
905#ifndef NDEBUG
906 {
907 int invalid_local = !std::ranges::all_of(indices, [shape0](std::int64_t i)
908 { return i >= 0 and i < shape0; });
909 int invalid = 0;
910 err = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
911 dolfinx::MPI::check_error(comm0, err);
912 if (invalid)
913 {
914 throw std::out_of_range(
915 std::format("distribute_data: index outside the global row range "
916 "[0, {}) of the distributed data.",
917 shape0));
918 }
919 }
920#endif
921
922 std::int64_t rank_offset = -1;
923 if (comm1 != MPI_COMM_NULL)
924 {
925 rank_offset = 0;
926 err = MPI_Exscan(&shape0_local, &rank_offset, 1,
927 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm1);
928 dolfinx::MPI::check_error(comm1, err);
929 }
930
931 return distribute_from_postoffice(comm0, indices, x, {shape0, shape1},
932 rank_offset);
933}
934//---------------------------------------------------------------------------
935
936} // namespace dolfinx::MPI
Comm(MPI_Comm comm, bool duplicate=true)
Duplicate communicator and wrap duplicate.
Definition MPI.cpp:21
~Comm()
Destructor (frees wrapped communicator).
Definition MPI.cpp:45
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
Datatype & operator=(Datatype &&type) noexcept
Move assignment.
Definition MPI.h:384
MPI_Datatype type() const noexcept
The datatype to pass to MPI.
Definition MPI.h:395
Datatype(Datatype &&type) noexcept
Move constructor.
Definition MPI.h:368
Datatype(int count)
Create a datatype for count contiguous values.
Definition MPI.h:350
~Datatype()
Destructor (frees the datatype, if one was created).
Definition MPI.h:374
Request()=default
Create a null request.
Request & operator=(Request &&request) noexcept
Move assignment.
Definition MPI.h:446
Request & operator=(const Request &) noexcept
Copy assignment. Leaves this with no request in flight.
Definition MPI.h:439
MPI_Request & request() noexcept
The handle to pass to MPI.
Definition MPI.h:454
Request(Request &&request) noexcept
Move constructor.
Definition MPI.h:430
Request(const Request &) noexcept
Copy constructor. The copy has no request in flight.
Definition MPI.h:427
~Request()=default
Destructor.
const MPI_Request & request() const noexcept
The handle.
Definition MPI.h:458
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:326
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:647
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:617
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:876
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