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 <iterator>
21#include <numeric>
22#include <ranges>
23#include <span>
24#include <tuple>
25#include <type_traits>
26#include <utility>
27#include <vector>
28
29#define MPICH_IGNORE_CXX_SEEK 1
30#include <mpi.h>
31
33namespace dolfinx::MPI
34{
36enum class tag : int
37{
38 consensus_pcx = 1200,
39 consensus_nbx = 1202,
40};
41
44class Comm
45{
46public:
48 explicit Comm(MPI_Comm comm, bool duplicate = true);
49
51 Comm(const Comm& comm) noexcept;
52
54 Comm(Comm&& comm) noexcept;
55
56 // Disable copy assignment operator
57 Comm& operator=(const Comm& comm) = delete;
58
60 Comm& operator=(Comm&& comm) noexcept;
61
63 ~Comm();
64
66 MPI_Comm comm() const noexcept;
67
68private:
69 // MPI communicator
70 MPI_Comm _comm;
71};
72
74int rank(MPI_Comm comm);
75
78int size(MPI_Comm comm);
79
84void check_error(MPI_Comm comm, int code);
85
92constexpr int index_owner(int size, std::size_t index, std::size_t N)
93{
94 assert(index < N);
95
96 // Compute number of items per rank and remainder
97 const std::size_t n = N / size;
98 const std::size_t r = N % size;
99
100 if (index < r * (n + 1))
101 {
102 // First r ranks own n + 1 indices
103 return index / (n + 1);
104 }
105 else
106 {
107 // Remaining ranks own n indices
108 return r + (index - r * (n + 1)) / n;
109 }
110}
111
138std::vector<int> compute_graph_edges_pcx(MPI_Comm comm,
139 std::span<const int> edges);
140
168std::vector<int>
169compute_graph_edges_nbx(MPI_Comm comm, std::span<const int> edges,
170 int tag = static_cast<int>(tag::consensus_nbx));
171
196std::pair<std::vector<int>, std::vector<int>>
197compute_graph_edges_nbx(MPI_Comm comm, std::span<const int> edges0, int tag0,
198 std::span<const int> edges1, int tag1);
199
218template <std::ranges::contiguous_range U>
219std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
220distribute_to_postoffice(MPI_Comm comm, const U& x,
221 std::array<std::int64_t, 2> shape,
222 std::int64_t rank_offset);
223
245template <std::ranges::contiguous_range U>
246std::vector<std::ranges::range_value_t<U>>
247distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
248 const U& x, std::array<std::int64_t, 2> shape,
249 std::int64_t rank_offset);
250
270template <std::ranges::contiguous_range U>
271std::vector<std::ranges::range_value_t<U>>
272distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
273 MPI_Comm comm1, const U& x, int shape1);
274
278template <typename T>
279struct dependent_false : std::false_type
280{
281};
282
285template <typename T>
286MPI_Datatype mpi_datatype()
287{
288 if constexpr (std::same_as<T, float>)
289 return MPI_FLOAT;
290 else if constexpr (std::same_as<T, double>)
291 return MPI_DOUBLE;
292 else if constexpr (std::same_as<T, std::complex<float>>)
293 return MPI_C_FLOAT_COMPLEX;
294 else if constexpr (std::same_as<T, std::complex<double>>)
295 return MPI_C_DOUBLE_COMPLEX;
296 else if constexpr (std::same_as<T, std::int8_t>)
297 return MPI_INT8_T;
298 else if constexpr (std::same_as<T, std::int16_t>)
299 return MPI_INT16_T;
300 else if constexpr (std::same_as<T, std::int32_t>)
301 return MPI_INT32_T;
302 else if constexpr (std::same_as<T, std::int64_t>)
303 return MPI_INT64_T;
304 else if constexpr (std::same_as<T, std::uint8_t>)
305 return MPI_UINT8_T;
306 else if constexpr (std::same_as<T, std::uint16_t>)
307 return MPI_UINT16_T;
308 else if constexpr (std::same_as<T, std::uint32_t>)
309 return MPI_UINT32_T;
310 else if constexpr (std::same_as<T, std::uint64_t>)
311 return MPI_UINT64_T;
312 else
313 static_assert(dependent_false<T>::value,
314 "No MPI datatype registered for this type.");
315}
316
319template <typename T>
320MPI_Datatype mpi_t = mpi_datatype<T>();
321
322//---------------------------------------------------------------------------
323namespace impl
324{
354std::tuple<std::vector<int>, std::vector<std::int32_t>,
355 std::vector<std::int32_t>>
356postoffice_plan(int size, int rank, std::int32_t shape0_local,
357 std::int64_t shape0, std::int64_t rank_offset);
358
378template <std::ranges::contiguous_range U>
379std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
380postoffice_exchange(MPI_Comm comm, const U& x,
381 std::array<std::int64_t, 2> shape, std::int64_t rank_offset,
382 std::span<const int> dest,
383 std::span<const std::int32_t> num_items_per_dest0,
384 std::span<const std::int32_t> pos_to_neigh_rank,
385 std::span<const int> src)
386{
387 using T = std::ranges::range_value_t<U>;
388
389 const int size = dolfinx::MPI::size(comm);
390 const int rank = dolfinx::MPI::rank(comm);
391 assert(x.size() % shape[1] == 0);
392 const std::int32_t shape0_local = x.size() / shape[1];
393
394 // Create neighbourhood communicator for sending data to post offices
395 MPI_Comm neigh_comm;
396 int err = MPI_Dist_graph_create_adjacent(
397 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
398 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm);
399 dolfinx::MPI::check_error(comm, err);
400
401 // Compute send displacements
402 std::vector<std::int32_t> num_items_per_dest(num_items_per_dest0.begin(),
403 num_items_per_dest0.end());
404 std::vector<std::int32_t> send_disp{0};
405 std::partial_sum(num_items_per_dest.begin(), num_items_per_dest.end(),
406 std::back_inserter(send_disp));
407
408 // Pack send buffers
409 std::vector<T> send_buffer_data(shape[1] * send_disp.back());
410 std::vector<std::int64_t> send_buffer_index(send_disp.back());
411 {
412 std::vector<std::int32_t> send_offsets = send_disp;
413 for (std::int32_t i = 0; i < shape0_local; ++i)
414 {
415 if (int neigh_dest = pos_to_neigh_rank[i]; neigh_dest != -1)
416 {
417 std::size_t pos = send_offsets[neigh_dest];
418 send_buffer_index[pos] = i + rank_offset;
419 std::copy_n(std::next(x.begin(), i * shape[1]), shape[1],
420 std::next(send_buffer_data.begin(), shape[1] * pos));
421 ++send_offsets[neigh_dest];
422 }
423 }
424 }
425
426 // Send number of items to post offices (destination) that I will be
427 // sending
428 std::vector<int> num_items_recv(src.size());
429 num_items_per_dest.reserve(1);
430 num_items_recv.reserve(1);
431 err = MPI_Neighbor_alltoall(num_items_per_dest.data(), 1, MPI_INT,
432 num_items_recv.data(), 1, MPI_INT, neigh_comm);
433 dolfinx::MPI::check_error(comm, err);
434
435 // Prepare receive displacement and buffers
436 std::vector<std::int32_t> recv_disp(num_items_recv.size() + 1, 0);
437 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
438 std::next(recv_disp.begin()));
439
440 // Send/receive global indices
441 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
442 err = MPI_Neighbor_alltoallv(
443 send_buffer_index.data(), num_items_per_dest.data(), send_disp.data(),
444 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
445 recv_disp.data(), MPI_INT64_T, neigh_comm);
446 dolfinx::MPI::check_error(comm, err);
447
448 // Send/receive data (x)
449 MPI_Datatype compound_type;
450 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type);
451 MPI_Type_commit(&compound_type);
452 std::vector<T> recv_buffer_data(shape[1] * recv_disp.back());
453 err = MPI_Neighbor_alltoallv(
454 send_buffer_data.data(), num_items_per_dest.data(), send_disp.data(),
455 compound_type, recv_buffer_data.data(), num_items_recv.data(),
456 recv_disp.data(), compound_type, neigh_comm);
457 dolfinx::MPI::check_error(comm, err);
458 err = MPI_Type_free(&compound_type);
459 dolfinx::MPI::check_error(comm, err);
460 err = MPI_Comm_free(&neigh_comm);
461 dolfinx::MPI::check_error(comm, err);
462
463 // Convert to local indices
464 const std::int64_t r0 = common::local_range(rank, shape[0], size)[0];
465 std::vector<std::int32_t> index_local(recv_buffer_index.size());
466 std::ranges::transform(recv_buffer_index, index_local.begin(),
467 [r0](std::int64_t idx) { return idx - r0; });
468
469 return {index_local, recv_buffer_data};
470}
471} // namespace impl
472
473template <std::ranges::contiguous_range U>
474std::pair<std::vector<std::int32_t>, std::vector<std::ranges::range_value_t<U>>>
475distribute_to_postoffice(MPI_Comm comm, const U& x,
476 std::array<std::int64_t, 2> shape,
477 std::int64_t rank_offset)
478{
479 assert(rank_offset >= 0 or x.empty());
480 assert(x.size() % shape[1] == 0);
481 const std::int32_t shape0_local = x.size() / shape[1];
482
483 spdlog::debug("Sending data to post offices (distribute_to_postoffice)");
484
485 const int size = dolfinx::MPI::size(comm);
486 const int rank = dolfinx::MPI::rank(comm);
487 auto [dest, num_items_per_dest, pos_to_neigh_rank]
488 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
489
490 // Determine source ranks
491 const std::vector<int> src = MPI::compute_graph_edges_nbx(comm, dest);
492 spdlog::info(
493 "Number of neighbourhood source ranks in distribute_to_postoffice: {}",
494 src.size());
495
496 auto result
497 = impl::postoffice_exchange(comm, x, shape, rank_offset, dest,
498 num_items_per_dest, pos_to_neigh_rank, src);
499 spdlog::debug("Completed send data to post offices.");
500 return result;
501}
502//---------------------------------------------------------------------------
503template <std::ranges::contiguous_range U>
504std::vector<std::ranges::range_value_t<U>>
505distribute_from_postoffice(MPI_Comm comm, std::span<const std::int64_t> indices,
506 const U& x, std::array<std::int64_t, 2> shape,
507 std::int64_t rank_offset)
508{
509 assert(rank_offset >= 0 or x.empty());
510 using T = std::ranges::range_value_t<U>;
511
512 common::Timer timer("Distribute row-wise data (scalable)");
513 assert(shape[1] > 0);
514
515 const int size = dolfinx::MPI::size(comm);
516 const int rank = dolfinx::MPI::rank(comm);
517 assert(x.size() % shape[1] == 0);
518 const std::int64_t shape0_local = x.size() / shape[1];
519
520 // 0. Send x data to/from post offices, and 1. determine which post
521 // office ranks hold the data I need (indices) -- these are
522 // independent local computations, so the two NBX consensus
523 // rounds they each need (below) are run concurrently in a single
524 // overlapped round rather than back-to-back.
525
526 auto [send_dest, num_items_per_send_dest, pos_to_neigh_rank]
527 = impl::postoffice_plan(size, rank, shape0_local, shape[0], rank_offset);
528
529 // Build (src, global index, position) for each entry in 'indices'
530 // not held locally, then sort. Locally-held entries are read
531 // directly below -- skipping them here avoids a wasted round trip.
532 std::vector<std::tuple<int, std::int64_t, std::int32_t>> src_to_index;
533 for (std::size_t i = 0; i < indices.size(); ++i)
534 {
535 std::int64_t idx = indices[i];
536 if (idx >= rank_offset and idx < rank_offset + shape0_local)
537 continue;
538 if (int src = dolfinx::MPI::index_owner(size, idx, shape[0]); src != rank)
539 src_to_index.push_back({src, idx, i});
540 }
541
542 // Radix sort on the rank alone (not the full tuple) -- only
543 // grouping by rank matters below; order within a group doesn't.
544 {
545 std::vector<std::int32_t> perm(src_to_index.size());
546 std::iota(perm.begin(), perm.end(), 0);
547 dolfinx::radix_sort(perm, [&src_to_index](std::int32_t i)
548 { return std::get<0>(src_to_index[i]); });
549 std::vector<std::tuple<int, std::int64_t, std::int32_t>> sorted(
550 src_to_index.size());
551 for (std::size_t i = 0; i < perm.size(); ++i)
552 sorted[i] = src_to_index[perm[i]];
553 src_to_index = std::move(sorted);
554 }
555
556 // Build list of neighbour src ranks and count number of items (rows
557 // of x) to receive from each src post office (by neighbourhood rank)
558 std::vector<std::int32_t> num_items_per_src;
559 std::vector<int> src;
560 {
561 auto it = src_to_index.begin();
562 while (it != src_to_index.end())
563 {
564 src.push_back(std::get<0>(*it));
565 auto it1 = std::ranges::find_if(it, src_to_index.end(),
566 [r = src.back()](auto& idx)
567 { return std::get<0>(idx) != r; });
568 num_items_per_src.push_back(std::ranges::distance(it, it1));
569 it = it1;
570 }
571 }
572
573 // Determine, in one overlapped NBX round, (0) the post office ranks
574 // that hold data for me to receive (post office send round) and (1)
575 // the 'delivery' destination ranks that want data from me (my
576 // request round)
577 auto [post_src, dest] = dolfinx::MPI::compute_graph_edges_nbx(
578 comm, send_dest, static_cast<int>(tag::consensus_nbx), src,
579 static_cast<int>(tag::consensus_nbx) + 1);
580 spdlog::info(
581 "Neighbourhood destination ranks from post office in "
582 "distribute_data (rank, num dests, num dests/mpi_size): {}, {}, {}",
583 rank, dest.size(), static_cast<double>(dest.size()) / size);
584
585 // Send receive x data to post office (only for rows that need to be
586 // communicated)
587 auto [post_indices, post_x] = impl::postoffice_exchange(
588 comm, x, {shape[0], shape[1]}, rank_offset, send_dest,
589 num_items_per_send_dest, pos_to_neigh_rank, post_src);
590 assert(post_indices.size() == post_x.size() / shape[1]);
591
592 // Create neighbourhood communicator for sending data to post offices
593 // (src), and receiving data form my send my post office
594 MPI_Comm neigh_comm0;
595 int err = MPI_Dist_graph_create_adjacent(
596 comm, dest.size(), dest.data(), MPI_UNWEIGHTED, src.size(), src.data(),
597 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
598 dolfinx::MPI::check_error(comm, err);
599
600 // Communicate number of requests to each source
601 std::vector<int> num_items_recv(dest.size());
602 num_items_per_src.reserve(1);
603 num_items_recv.reserve(1);
604 err = MPI_Neighbor_alltoall(num_items_per_src.data(), 1, MPI_INT,
605 num_items_recv.data(), 1, MPI_INT, neigh_comm0);
606 dolfinx::MPI::check_error(comm, err);
607
608 // Prepare send/receive displacements
609 std::vector<std::int32_t> send_disp{0};
610 std::partial_sum(num_items_per_src.begin(), num_items_per_src.end(),
611 std::back_inserter(send_disp));
612 std::vector<std::int32_t> recv_disp = {0};
613 std::partial_sum(num_items_recv.begin(), num_items_recv.end(),
614 std::back_inserter(recv_disp));
615
616 // Pack my requested indices (global) in send buffer ready to send to
617 // post offices
618 assert(send_disp.back() == static_cast<int>(src_to_index.size()));
619 std::vector<std::int64_t> send_buffer_index(src_to_index.size());
620 std::ranges::transform(src_to_index, send_buffer_index.begin(),
621 [](auto x) { return std::get<1>(x); });
622
623 // Prepare the receive buffer
624 std::vector<std::int64_t> recv_buffer_index(recv_disp.back());
625 err = MPI_Neighbor_alltoallv(
626 send_buffer_index.data(), num_items_per_src.data(), send_disp.data(),
627 MPI_INT64_T, recv_buffer_index.data(), num_items_recv.data(),
628 recv_disp.data(), MPI_INT64_T, neigh_comm0);
629 dolfinx::MPI::check_error(comm, err);
630
631 err = MPI_Comm_free(&neigh_comm0);
632 dolfinx::MPI::check_error(comm, err);
633
634 // 2. Send data (rows of x) from post office back to requesting ranks
635 // (transpose of the preceding communication pattern operation)
636
637 // Build map from local index to post_indices position. Set to -1 for
638 // data that was already on this rank and was therefore was not
639 // sent/received via a postoffice.
640 const std::array<std::int64_t, 2> postoffice_range
641 = common::local_range(rank, shape[0], size);
642 std::vector<std::int32_t> post_indices_map(
643 postoffice_range[1] - postoffice_range[0], -1);
644 for (std::size_t i = 0; i < post_indices.size(); ++i)
645 {
646 assert(post_indices[i] < static_cast<int>(post_indices_map.size()));
647 post_indices_map[post_indices[i]] = i;
648 }
649
650 // Build send buffer
651 std::vector<T> send_buffer_data(shape[1] * recv_disp.back());
652 for (std::int32_t i = 0; i < recv_disp.back(); ++i)
653 {
654 std::int64_t index = recv_buffer_index[i];
655 if (index >= rank_offset and index < (rank_offset + shape0_local))
656 {
657 // I already had this index before any communication
658 std::int32_t local_index = index - rank_offset;
659 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
660 std::next(send_buffer_data.begin(), shape[1] * i));
661 }
662 else
663 {
664 // Take from my 'post bag'
665 std::int64_t local_index = index - postoffice_range[0];
666 std::int32_t pos = post_indices_map[local_index];
667 assert(pos != -1);
668 std::copy_n(std::next(post_x.begin(), shape[1] * pos), shape[1],
669 std::next(send_buffer_data.begin(), shape[1] * i));
670 }
671 }
672
673 err = MPI_Dist_graph_create_adjacent(
674 comm, src.size(), src.data(), MPI_UNWEIGHTED, dest.size(), dest.data(),
675 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &neigh_comm0);
676 dolfinx::MPI::check_error(comm, err);
677
678 MPI_Datatype compound_type0;
679 MPI_Type_contiguous(shape[1], dolfinx::MPI::mpi_t<T>, &compound_type0);
680 MPI_Type_commit(&compound_type0);
681
682 std::vector<T> recv_buffer_data(shape[1] * send_disp.back());
683 err = MPI_Neighbor_alltoallv(
684 send_buffer_data.data(), num_items_recv.data(), recv_disp.data(),
685 compound_type0, recv_buffer_data.data(), num_items_per_src.data(),
686 send_disp.data(), compound_type0, neigh_comm0);
687 dolfinx::MPI::check_error(comm, err);
688
689 err = MPI_Type_free(&compound_type0);
690 dolfinx::MPI::check_error(comm, err);
691 err = MPI_Comm_free(&neigh_comm0);
692 dolfinx::MPI::check_error(comm, err);
693
694 std::vector<std::int32_t> index_pos_to_buffer(indices.size(), -1);
695 for (std::size_t i = 0; i < src_to_index.size(); ++i)
696 index_pos_to_buffer[std::get<2>(src_to_index[i])] = i;
697
698 // Extra data to return
699 std::vector<T> x_new(shape[1] * indices.size());
700 for (std::size_t i = 0; i < indices.size(); ++i)
701 {
702 const std::int64_t index = indices[i];
703 if (index >= rank_offset and index < (rank_offset + shape0_local))
704 {
705 // Had data from the start in x
706 std::int64_t local_index = index - rank_offset;
707 std::copy_n(std::next(x.begin(), shape[1] * local_index), shape[1],
708 std::next(x_new.begin(), shape[1] * i));
709 }
710 else if (std::int32_t pos = index_pos_to_buffer[i]; pos != -1)
711 {
712 // In my received post: index_pos_to_buffer[i] != -1 iff
713 // index_owner would say this rank isn't the owner -- avoids
714 // recomputing it.
715 std::copy_n(std::next(recv_buffer_data.begin(), shape[1] * pos), shape[1],
716 std::next(x_new.begin(), shape[1] * i));
717 }
718 else
719 {
720 // In my post office bag
721 std::int64_t local_index = index - postoffice_range[0];
722 std::int32_t bag_pos = post_indices_map[local_index];
723 assert(bag_pos != -1);
724 std::copy_n(std::next(post_x.begin(), shape[1] * bag_pos), shape[1],
725 std::next(x_new.begin(), shape[1] * i));
726 }
727 }
728
729 return x_new;
730}
731//---------------------------------------------------------------------------
732template <std::ranges::contiguous_range U>
733std::vector<std::ranges::range_value_t<U>>
734distribute_data(MPI_Comm comm0, std::span<const std::int64_t> indices,
735 MPI_Comm comm1, const U& x, int shape1)
736{
737 assert(shape1 > 0);
738 assert(x.size() % shape1 == 0);
739 const std::int64_t shape0_local = x.size() / shape1;
740
741 std::int64_t shape0 = 0;
742 int err
743 = MPI_Allreduce(&shape0_local, &shape0, 1, MPI_INT64_T, MPI_SUM, comm0);
744 dolfinx::MPI::check_error(comm0, err);
745
746 std::int64_t rank_offset = -1;
747 if (comm1 != MPI_COMM_NULL)
748 {
749 rank_offset = 0;
750 err = MPI_Exscan(&shape0_local, &rank_offset, 1,
751 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm1);
752 dolfinx::MPI::check_error(comm1, err);
753 }
754 else if (!x.empty())
755 throw std::runtime_error("Non-empty data on null MPI communicator");
756
757 return distribute_from_postoffice(comm0, indices, x, {shape0, shape1},
758 rank_offset);
759}
760//---------------------------------------------------------------------------
761
762} // 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
Timer for measuring and logging elapsed time durations.
Definition Timer.h:40
MPI support functionality.
Definition MPI.h:34
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:320
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:505
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:291
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:92
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:475
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
void check_error(MPI_Comm comm, int code)
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:89
int size(MPI_Comm comm)
Definition MPI.cpp:81
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:734
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
tag
MPI communication tags.
Definition MPI.h:37
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