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
405//---------------------------------------------------------------------------
406namespace impl
407{
437std::tuple<std::vector<int>, std::vector<std::int32_t>,
438 std::vector<std::int32_t>>
439postoffice_plan(int size, int rank, std::int32_t shape0_local,
440 std::int64_t shape0, std::int64_t rank_offset);
441
461template <std::ranges::contiguous_range U>
462std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
463postoffice_exchange(MPI_Comm comm, const U& x,
464 std::array<std::int64_t, 2> shape, std::int64_t rank_offset,
465 std::span<const int> dest,
466 std::span<const std::int32_t> num_items_per_dest0,
467 std::span<const std::int32_t> pos_to_neigh_rank,
468 std::span<const int> src)
469{
470 using T = std::ranges::range_value_t<U>;
471
472 const int size = dolfinx::MPI::size(comm);
473 const int rank = dolfinx::MPI::rank(comm);
474 assert(x.size() % shape[1] == 0);
475 const std::int32_t shape0_local = x.size() / shape[1];
476
477 // Create neighbourhood communicator for sending data to post offices
478 MPI_Comm neigh_comm;
479 int err = MPI_Dist_graph_create_adjacent(
480 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
481 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm);
482 dolfinx::MPI::check_error(comm, err);
483
484 // Compute send displacements
485 std::vector<std::int32_t> num_items_per_dest(num_items_per_dest0.begin(),
486 num_items_per_dest0.end());
487 std::vector<std::int32_t> send_disp{0};
488 std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(),
489 std::back_inserter(send_disp));
490
491 // Pack send buffers
492 std::vector<T> send_buffer_data(shape[1] * send_disp.back());
493 std::vector<std::int64_t> send_buffer_index(send_disp.back());
494 {
495 std::vector<std::int32_t> send_offsets = send_disp;
496 for (std::int32_t i = 0; i < shape0_local; ++i)
497 {
498 if (int neigh_dest = pos_to_neigh_rank[i]; neigh_dest != -1)
499 {
500 std::size_t pos = send_offsets[neigh_dest];
501 send_buffer_index[pos] = i + rank_offset;
502 std::copy_n(std::next(x.begin(), i * shape[1]), shape[1],
503 std::next(send_buffer_data.begin(), shape[1] * pos));
504 ++send_offsets[neigh_dest];
505 }
506 }
507 }
508
509 // Send number of items to post offices (destination) that I will be
510 // sending
511 std::vector<int> num_items_recv(src.size());
512 num_items_per_dest.reserve(1);
513 num_items_recv.reserve(1);
514 err = MPI_Neighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT,
515 num_items_recv.data(), 1, MPI_INT, neigh_comm);
516 dolfinx::MPI::check_error(comm, err);
517
518 // Prepare receive displacement and buffers
519 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
520 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
521 std::next(recv_disp.begin()));
522
523 // Send/receive global indices
524 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
525 err = MPI_Neighbor_alltoallv(
526 send_buffer_index.data(), num_items_per_dest.data(), send_disp.data(),
527 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
528 recv_disp.data(), MPI_INT64_T, neigh_comm);
529 dolfinx::MPI::check_error(comm, err);
530
531 // Send/receive data (x)
532 MPI_Datatype compound_type;
533 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type);
534 MPI_Type_commit(&compound_type);
535 std::vector<T> recv_buffer_data(shape[1] * recv_disp.back());
536 err = MPI_Neighbor_alltoallv(
537 send_buffer_data.data(), num_items_per_dest.data(), send_disp.data(),
538 compound_type, recv_buffer_data.data(), num_items_recv.data(),
539 recv_disp.data(), compound_type, neigh_comm);
540 dolfinx::MPI::check_error(comm, err);
541 err = MPI_Type_free(&compound_type);
542 dolfinx::MPI::check_error(comm, err);
543 err = MPI_Comm_free(&neigh_comm);
544 dolfinx::MPI::check_error(comm, err);
545
546 // Convert to local indices
547 const std::int64_t r0 = common::local_range(rank, shape[0], size)[0];
548 std::vector<std::int32_t> index_local(recv_buffer_index.size());
549 std::ranges::transform(recv_buffer_index, index_local.begin(),
550 [r0](std::int64_t idx) { return idx - r0; });
551
552 return {index_local, recv_buffer_data};
553}
554} // namespace impl
555
556template <std::ranges::contiguous_range U>
557std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
558distribute_to_postoffice(MPI_Comm comm, const U& x,
559 std::array<std::int64_t, 2> shape,
560 std::int64_t rank_offset)
561{
562 assert(rank_offset >= 0 or x.empty());
563 assert(x.size() % shape[1] == 0);
564 const std::int32_t shape0_local = x.size() / shape[1];
565
566 spdlog::debug("Sending data to post offices (distribute_to_postoffice)");
567
568 const int size = dolfinx::MPI::size(comm);
569 const int rank = dolfinx::MPI::rank(comm);
570 auto [dest, num_items_per_dest, pos_to_neigh_rank]
571 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
572
573 // Determine source ranks
574 const std::vector<int> src = MPI::compute_graph_edges_nbx(comm, dest);
575 spdlog::info(
576 "Number of neighbourhood source ranks in distribute_to_postoffice: {}",
577 src.size());
578
579 auto result
580 = impl::postoffice_exchange(comm, x, shape, rank_offset, dest,
581 num_items_per_dest, pos_to_neigh_rank, src);
582 spdlog::debug("Completed send data to post offices.");
583 return result;
584}
585//---------------------------------------------------------------------------
586template <std::ranges::contiguous_range U>
587std::vector<std::ranges::range_value_t<U>>
588distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
589 const U& x, std::array<std::int64_t, 2> shape,
590 std::int64_t rank_offset)
591{
592 assert(rank_offset >= 0 or x.empty());
593 using T = std::ranges::range_value_t<U>;
594
595 common::Timer timer("Distribute row-wise data (scalable)");
596 assert(shape[1] > 0);
597
598 const int size = dolfinx::MPI::size(comm);
599 const int rank = dolfinx::MPI::rank(comm);
600 assert(x.size() % shape[1] == 0);
601 const std::int64_t shape0_local = x.size() / shape[1];
602
603 // 0. Send x data to/from post offices, and 1. determine which post
604 // office ranks hold the data I need (indices) -- these are
605 // independent local computations, so the two NBX consensus
606 // rounds they each need (below) are run concurrently in a single
607 // overlapped round rather than back-to-back.
608
609 auto [send_dest, num_items_per_send_dest, pos_to_neigh_rank]
610 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
611
612 // Build (src, global index, position) for each entry in 'indices'
613 // not held locally, then sort. Locally-held entries are read
614 // directly below -- skipping them here avoids a wasted round trip.
615 std::vector<std::tuple<int, std::int64_t, std::int32_t>> src_to_index;
616 for (std::size_t i = 0; i < indices.size(); ++i)
617 {
618 std::int64_t idx = indices[i];
619 if (idx >= rank_offset and idx < rank_offset + shape0_local)
620 continue;
621 if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank)
622 src_to_index.push_back({src, idx, i});
623 }
624
625 // Radix sort on the rank alone (not the full tuple) -- only
626 // grouping by rank matters below; order within a group doesn't.
627 {
628 std::vector<std::int32_t> perm(src_to_index.size());
629 std::iota(perm.begin(), perm.end(), 0);
630 dolfinx::radix_sort(perm, [&src_to_index](std::int32_t i)
631 { return std::get<0>(src_to_index[i]); });
632 std::vector<std::tuple<int, std::int64_t, std::int32_t>> sorted(
633 src_to_index.size());
634 for (std::size_t i = 0; i < perm.size(); ++i)
635 sorted[i] = src_to_index[perm[i]];
636 src_to_index = std::move(sorted);
637 }
638
639 // Build list of neighbour src ranks and count number of items (rows
640 // of x) to receive from each src post office (by neighbourhood rank)
641 std::vector<std::int32_t> num_items_per_src;
642 std::vector<int> src;
643 {
644 auto it = src_to_index.begin();
645 while (it != src_to_index.end())
646 {
647 src.push_back(std::get<0>(*it));
648 auto it1 = std::ranges::find_if(it, src_to_index.end(),
649 [r = src.back()](auto& idx)
650 { return std::get<0>(idx) != r; });
651 num_items_per_src.push_back(std::ranges::distance(it, it1));
652 it = it1;
653 }
654 }
655
656 // Determine, in one overlapped NBX round, (0) the post office ranks
657 // that hold data for me to receive (post office send round) and (1)
658 // the 'delivery' destination ranks that want data from me (my
659 // request round)
660 auto [post_src, dest] = dolfinx::MPI::compute_graph_edges_nbx(
661 comm, send_dest, static_cast<int>(tag::consensus_nbx), src,
662 static_cast<int>(tag::consensus_nbx) + 1);
663 spdlog::info(
664 "Neighbourhood destination ranks from post office in "
665 "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}",
666 rank, dest.size(), static_cast<double>(dest.size()) / size);
667
668 // Send receive x data to post office (only for rows that need to be
669 // communicated)
670 auto [post_indices, post_x] = impl::postoffice_exchange(
671 comm, x, {shape[0], shape[1]}, rank_offset, send_dest,
672 num_items_per_send_dest, pos_to_neigh_rank, post_src);
673 assert(post_indices.size() == post_x.size() / shape[1]);
674
675 // Create neighbourhood communicator for sending data to post offices
676 // (src), and receiving data form my send my post office
677 MPI_Comm neigh_comm0;
678 int err = MPI_Dist_graph_create_adjacent(
679 comm, dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(), src.data(),
680 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
681 dolfinx::MPI::check_error(comm, err);
682
683 // Communicate number of requests to each source
684 std::vector<int> num_items_recv(dest.size());
685 num_items_per_src.reserve(1);
686 num_items_recv.reserve(1);
687 err = MPI_Neighbor_alltoall(num_items_per_src.data(), 1, MPI_INT,
688 num_items_recv.data(), 1, MPI_INT, neigh_comm0);
689 dolfinx::MPI::check_error(comm, err);
690
691 // Prepare send/receive displacements
692 std::vector<std::int32_t> send_disp{0};
693 std::partial_sum(num_items_per_src.begin(), num_items_per_src.end(),
694 std::back_inserter(send_disp));
695 std::vector<std::int32_t> recv_disp = {0};
696 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
697 std::back_inserter(recv_disp));
698
699 // Pack my requested indices (global) in send buffer ready to send to
700 // post offices
701 assert(send_disp.back() == static_cast<int>(src_to_index.size()));
702 std::vector<std::int64_t> send_buffer_index(src_to_index.size());
703 std::ranges::transform(src_to_index, send_buffer_index.begin(),
704 [](auto x) { return std::get<1>(x); });
705
706 // Prepare the receive buffer
707 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
708 err = MPI_Neighbor_alltoallv(
709 send_buffer_index.data(), num_items_per_src.data(), send_disp.data(),
710 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
711 recv_disp.data(), MPI_INT64_T, neigh_comm0);
712 dolfinx::MPI::check_error(comm, err);
713
714 err = MPI_Comm_free(&neigh_comm0);
715 dolfinx::MPI::check_error(comm, err);
716
717 // 2. Send data (rows of x) from post office back to requesting ranks
718 // (transpose of the preceding communication pattern operation)
719
720 // Build map from local index to post_indices position. Set to -1 for
721 // data that was already on this rank and was therefore was not
722 // sent/received via a postoffice.
723 const std::array<std::int64_t, 2> postoffice_range
724 = common::local_range(rank, shape[0], size);
725 std::vector<std::int32_t> post_indices_map(
726 postoffice_range[1] - postoffice_range[0], -1);
727 for (std::size_t i = 0; i < post_indices.size(); ++i)
728 {
729 assert(post_indices[i] < static_cast<int>(post_indices_map.size()));
730 post_indices_map[post_indices[i]] = i;
731 }
732
733 // Build send buffer
734 std::vector<T> send_buffer_data(shape[1] * recv_disp.back());
735 for (std::int32_t i = 0; i < recv_disp.back(); ++i)
736 {
737 std::int64_t index = recv_buffer_index[i];
738 if (index >= rank_offset and index < (rank_offset + shape0_local))
739 {
740 // I already had this index before any communication
741 std::int32_t local_index = index - rank_offset;
742 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
743 std::next(send_buffer_data.begin(), shape[1] * i));
744 }
745 else
746 {
747 // Take from my 'post bag'
748 std::int64_t local_index = index - postoffice_range[0];
749 std::int32_t pos = post_indices_map[local_index];
750 assert(pos != -1);
751 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
752 std::next(send_buffer_data.begin(), shape[1] * i));
753 }
754 }
755
756 err = MPI_Dist_graph_create_adjacent(
757 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
758 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
759 dolfinx::MPI::check_error(comm, err);
760
761 MPI_Datatype compound_type0;
762 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type0);
763 MPI_Type_commit(&compound_type0);
764
765 std::vector<T> recv_buffer_data(shape[1] * send_disp.back());
766 err = MPI_Neighbor_alltoallv(
767 send_buffer_data.data(), num_items_recv.data(), recv_disp.data(),
768 compound_type0, recv_buffer_data.data(), num_items_per_src.data(),
769 send_disp.data(), compound_type0, neigh_comm0);
770 dolfinx::MPI::check_error(comm, err);
771
772 err = MPI_Type_free(&compound_type0);
773 dolfinx::MPI::check_error(comm, err);
774 err = MPI_Comm_free(&neigh_comm0);
775 dolfinx::MPI::check_error(comm, err);
776
777 std::vector<std::int32_t> index_pos_to_buffer(indices.size(), -1);
778 for (std::size_t i = 0; i < src_to_index.size(); ++i)
779 index_pos_to_buffer[std::get<2>(src_to_index[i])] = i;
780
781 // Extra data to return
782 std::vector<T> x_new(shape[1] * indices.size());
783 for (std::size_t i = 0; i < indices.size(); ++i)
784 {
785 const std::int64_t index = indices[i];
786 if (index >= rank_offset and index < (rank_offset + shape0_local))
787 {
788 // Had data from the start in x
789 std::int64_t local_index = index - rank_offset;
790 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
791 std::next(x_new.begin(), shape[1] * i));
792 }
793 else if (std::int32_t pos = index_pos_to_buffer[i]; pos != -1)
794 {
795 // In my received post: index_pos_to_buffer[i] != -1 iff
796 // index_owner would say this rank isn't the owner -- avoids
797 // recomputing it.
798 std::copy_n(std::next(recv_buffer_data.begin(), shape[1] * pos), shape[1],
799 std::next(x_new.begin(), shape[1] * i));
800 }
801 else
802 {
803 // In my post office bag
804 std::int64_t local_index = index - postoffice_range[0];
805 std::int32_t bag_pos = post_indices_map[local_index];
806 assert(bag_pos != -1);
807 std::copy_n(std::next(post_x.begin(), shape[1] * bag_pos), shape[1],
808 std::next(x_new.begin(), shape[1] * i));
809 }
810 }
811
812 return x_new;
813}
814//---------------------------------------------------------------------------
815template <std::ranges::contiguous_range U>
816std::vector<std::ranges::range_value_t<U>>
817distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
818 MPI_Comm comm1, const U& x, int shape1)
819{
820 if (shape1 <= 0)
821 throw std::invalid_argument("distribute_data: shape1 must be positive");
822 if (x.size() % shape1 != 0)
823 {
824 throw std::invalid_argument(
825 "distribute_data: x.size() must be a multiple of shape1");
826 }
827 const std::int64_t shape0_local = x.size() / shape1;
828
829 // A rank outside comm1 must hold no data. Check this collectively before
830 // the later comm0/comm1 collectives to avoid leaving other ranks blocked.
831 {
832 int invalid_local = (comm1 == MPI_COMM_NULL and !x.empty()) ? 1 : 0;
833 int invalid = 0;
834 int err
835 = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
836 dolfinx::MPI::check_error(comm0, err);
837 if (invalid)
838 throw std::invalid_argument("Non-empty data on null MPI communicator");
839 }
840
841 std::int64_t shape0 = 0;
842 int err
843 = MPI_Allreduce(&shape0_local, &shape0, 1, MPI_INT64_T, MPI_SUM, comm0);
844 dolfinx::MPI::check_error(comm0, err);
845
846#ifndef NDEBUG
847 {
848 int invalid_local = !std::ranges::all_of(indices, [shape0](std::int64_t i)
849 { return i >= 0 and i < shape0; });
850 int invalid = 0;
851 err = MPI_Allreduce(&invalid_local, &invalid, 1, MPI_INT, MPI_MAX, comm0);
852 dolfinx::MPI::check_error(comm0, err);
853 if (invalid)
854 {
855 throw std::out_of_range(
856 std::format("distribute_data: index outside the global row range "
857 "[0, {}) of the distributed data.",
858 shape0));
859 }
860 }
861#endif
862
863 std::int64_t rank_offset = -1;
864 if (comm1 != MPI_COMM_NULL)
865 {
866 rank_offset = 0;
867 err = MPI_Exscan(&shape0_local, &rank_offset, 1,
868 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm1);
869 dolfinx::MPI::check_error(comm1, err);
870 }
871
872 return distribute_from_postoffice(comm0, indices, x, {shape0, shape1},
873 rank_offset);
874}
875//---------------------------------------------------------------------------
876
877} // 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
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:588
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:558
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:817
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