From a2ab6370fd46441304d888657f5305aac658e911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Wed, 6 Mar 2024 14:09:12 +0100 Subject: [PATCH 1/6] Add a basic wrapper around MPI_Group --- include/kamping/communicator.hpp | 7 ++ include/kamping/group.hpp | 169 +++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 5 + tests/mpi_group_test.cpp | 94 +++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 include/kamping/group.hpp create mode 100644 tests/mpi_group_test.cpp diff --git a/include/kamping/communicator.hpp b/include/kamping/communicator.hpp index 3e153f174..07f9452f5 100644 --- a/include/kamping/communicator.hpp +++ b/include/kamping/communicator.hpp @@ -23,6 +23,7 @@ #include "error_handling.hpp" #include "kamping/checking_casts.hpp" #include "kamping/environment.hpp" +#include "kamping/group.hpp" #include "kamping/mpi_constants.hpp" #include "kamping/mpi_datatype.hpp" #include "kamping/mpi_ops.hpp" @@ -300,6 +301,12 @@ class Communicator : public Plugins +#include + +#include "kamping/checking_casts.hpp" + +namespace kamping { + +/// @brief Describes the equality of two groups. +/// - Identical :: The order and members of the two groups are the same. +/// - Similar :: Only the members are the same, the order is different. +/// - Unequal :: otherwise +enum class GroupEquality { Identical, Similar, Unequal, Invalid }; + +/// @brief A group of MPI processes. +class Group { +public: + /// @brief Constructs a new group from an existing MPI group. + Group(MPI_Group group, bool owning = false) : _group(group), _owns_group(owning) {} + + Group(Group const&) = delete; + Group operator=(Group const&) = delete; + + /// @brief Move constructor. + Group(Group&& other) { + this->_group = other._group; + other._owns_group = false; + this->_owns_group = true; + } + + /// @brief Move assignment. + Group& operator=(Group&& other) { + this->_group = other._group; + other._owns_group = false; + this->_owns_group = true; + return *this; + } + + /// @brief Constructs the group associated with a communicator. + Group(MPI_Comm comm) : _owns_group(true) { + MPI_Comm_group(comm, &_group); + } + + /// @brief Constructs the group associated with a communicator. + template + Group(Comm const& comm) : Group(comm.mpi_communicator()) {} + + /// @brief Constructs an empty group. + /// @return An empty group. + [[nodiscard]] static Group empty() { + return Group(MPI_GROUP_EMPTY); + } + + /// @brief Constructs the group associated with the world communicator. + /// @return The group associated with the world communicator. + [[nodiscard]] static Group world() { + return Group(MPI_COMM_WORLD); + } + + /// @brief Default destructor, freeing the encapsulated group if owned. + ~Group() { + if (_owns_group) { + MPI_Group_free(&_group); + } + } + + // TODO We need a safe tag for this -> move to another PR + // template + // Comm create_comm(Comm const& comm) const { + // constexpr int safe_tag = 1337; // TODO + // MPI_Comm new_comm; + // MPI_Comm_group_create(comm, _group, safe_tag, &new_comm); + // return Comm(new_comm); + // } + // TODO _excl, _incl, _range_incl, _range_excl, and translate_ranks + + /// @brief Compare two groups. + /// @param other The group to compare with. + /// @return The equality of the two groups (see \ref GroupEquality). + GroupEquality compare(Group const& other) const { + int result; + MPI_Group_compare(_group, other._group, &result); + + switch (result) { + case MPI_IDENT: + return GroupEquality::Identical; + case MPI_SIMILAR: + return GroupEquality::Similar; + case MPI_UNEQUAL: + return GroupEquality::Unequal; + default: + KASSERT(false, "MPI_Group_compare returned an unknown value"); + return GroupEquality::Invalid; + } + } + + /// @Compare two groups. + /// @param other The group to compare with. + /// @return True if the groups are identical; see \ref GroupEquality. False otherwise. + [[nodiscard]] bool is_identical(Group const& other) const { + return compare(other) == GroupEquality::Identical; + } + + /// @Compare two groups. + /// @param other The group to compare with. + /// @return True if the groups are similar; see \ref GroupEquality. False otherwise. + [[nodiscard]] bool is_similar(Group const& other) const { + return compare(other) == GroupEquality::Similar; + } + + /// @Compare two groups. + /// @param other The group to compare with. + /// @return True if the groups are identical; see \ref GroupEquality. False otherwise. + [[nodiscard]] bool has_same_ranks(Group const& other) const { + auto const result = compare(other); + return (result == GroupEquality::Identical) || (result == GroupEquality::Similar); + } + + /// @brief Makes a group from the difference of two groups. + /// @param other The other group. + /// @return A group containing all the ranks of the first group that are not in the second group. + Group difference(Group const& other) const { + MPI_Group diff; + MPI_Group_difference(_group, other._group, &diff); + return Group(diff); + } + + /// @brief Makes a group from the intersection of two groups. + /// @param other The other group. + /// @return A group containing only the ranks present in both groups. + Group intersection(Group const& other) const { + MPI_Group inter; + MPI_Group_intersection(_group, other._group, &inter); + return Group(inter); + } + + /// @brief Makes a group from the union of two groups. + /// @param other The other group. + /// @return A group containing all ranks present in either of the two groups. + /// @note The suffixing underscore is to avoid a name clash with the C++ keyword `union`. + Group union_(Group const& other) const { + MPI_Group un; + MPI_Group_union(_group, other._group, &un); + return Group(un); + } + + /// @brief Get the number of ranks in the group. + /// @return The number of ranks in the group. + size_t size() const { + int size; + MPI_Group_size(_group, &size); + return asserting_cast(size); + } + + /// @brief Get the rank of the calling process in the group. + /// @return The rank of the calling process in the group. + size_t rank() const { + int rank; + MPI_Group_rank(_group, &rank); + return asserting_cast(rank); + } + +private: + MPI_Group _group; + bool _owns_group; +}; + +}; // namespace kamping diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6d040d0ca..1ff335dfd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -80,6 +80,11 @@ kamping_register_mpi_test( FILES mpi_communicator_test.cpp CORES 1 4 ) +kamping_register_mpi_test( + test_mpi_group + FILES mpi_group_test.cpp + CORES 1 4 +) kamping_register_mpi_test( test_mpi_datatype FILES mpi_datatype_test.cpp diff --git a/tests/mpi_group_test.cpp b/tests/mpi_group_test.cpp new file mode 100644 index 000000000..0a7a08e2f --- /dev/null +++ b/tests/mpi_group_test.cpp @@ -0,0 +1,94 @@ +// This file is part of KaMPIng. +// +// Copyright 2024 The KaMPIng Authors +// +// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later +// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see +// . + +#include + +#include +#include +#include +#include + +#include "kamping/assertion_levels.hpp" +#include "kamping/communicator.hpp" +#include "kamping/group.hpp" + +TEST(GroupTest, Basics) { + using namespace kamping; + + Communicator comm; + + // Construction + auto empty_group = Group::empty(); + auto world_group = Group::world(); + EXPECT_EQ(Group(MPI_GROUP_EMPTY).compare(empty_group), GroupEquality::Identical); + EXPECT_EQ(Group(MPI_COMM_WORLD).compare(world_group), GroupEquality::Identical); + EXPECT_EQ(comm.group().compare(world_group), GroupEquality::Identical); + EXPECT_EQ(Group(comm.mpi_communicator()).compare(comm.group()), GroupEquality::Identical); + + EXPECT_TRUE(Group(MPI_GROUP_EMPTY).is_identical(empty_group)); + EXPECT_TRUE(Group(MPI_COMM_WORLD).is_identical(world_group)); + EXPECT_TRUE(comm.group().is_identical(world_group)); + EXPECT_TRUE(Group(comm.mpi_communicator()).is_identical(comm.group())); + + EXPECT_TRUE(Group(MPI_GROUP_EMPTY).has_same_ranks(empty_group)); + EXPECT_TRUE(Group(MPI_COMM_WORLD).has_same_ranks(world_group)); + EXPECT_TRUE(comm.group().has_same_ranks(world_group)); + EXPECT_TRUE(Group(comm.mpi_communicator()).has_same_ranks(comm.group())); + + // rank() and size() + EXPECT_EQ(empty_group.size(), 0); + EXPECT_EQ(world_group.size(), comm.size()); + EXPECT_EQ(world_group.rank(), comm.rank()); + + // compare() + EXPECT_TRUE(world_group.has_same_ranks(world_group)); + EXPECT_TRUE(empty_group.has_same_ranks(empty_group)); + + // difference() + auto world_empty_diff = world_group.difference(empty_group); + auto empty_world_diff = empty_group.difference(world_group); + auto world_world_diff = world_group.difference(world_group); + auto empty_empty_diff = empty_group.difference(empty_group); + EXPECT_TRUE(world_empty_diff.has_same_ranks(world_group)); + EXPECT_TRUE(empty_world_diff.has_same_ranks(empty_group)); + EXPECT_TRUE(world_world_diff.has_same_ranks(empty_group)); + EXPECT_TRUE(empty_empty_diff.has_same_ranks(empty_group)); + + // intersection() + auto world_empty_inter = world_group.intersection(empty_group); + auto empty_world_inter = empty_group.intersection(world_group); + auto world_world_inter = world_group.intersection(world_group); + auto empty_empty_inter = empty_group.intersection(empty_group); + EXPECT_TRUE(world_empty_inter.has_same_ranks(empty_group)); + EXPECT_TRUE(empty_world_inter.has_same_ranks(empty_group)); + EXPECT_TRUE(empty_empty_inter.has_same_ranks(empty_group)); + EXPECT_TRUE(world_world_inter.has_same_ranks(world_group)); + + // union_() + auto world_empty_union = world_group.union_(empty_group); + auto empty_world_union = empty_group.union_(world_group); + auto world_world_union = world_group.union_(world_group); + auto empty_empty_union = empty_group.union_(empty_group); + EXPECT_TRUE(world_empty_union.has_same_ranks(world_group)); + EXPECT_TRUE(empty_world_union.has_same_ranks(world_group)); + EXPECT_TRUE(empty_empty_union.has_same_ranks(empty_group)); + EXPECT_TRUE(world_world_union.has_same_ranks(world_group)); + + // Move assignment and move-copying + Group world_group_copy = Group::empty(); + world_group_copy = std::move(world_group); + auto world_group_copy2(std::move(world_group_copy)); + EXPECT_EQ(world_group_copy2.size(), comm.size()); + EXPECT_EQ(world_group_copy2.rank(), comm.rank()); + EXPECT_TRUE(world_group_copy2.has_same_ranks(comm.group())); +} From 15e6dda4f071d2a0c168ffc71679df929eb617b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Wed, 6 Mar 2024 14:17:51 +0100 Subject: [PATCH 2/6] Fix doxygen comments --- include/kamping/group.hpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/include/kamping/group.hpp b/include/kamping/group.hpp index fb991a45f..bc8949eb0 100644 --- a/include/kamping/group.hpp +++ b/include/kamping/group.hpp @@ -7,11 +7,14 @@ namespace kamping { +/// @enum kamping::GroupEquality /// @brief Describes the equality of two groups. -/// - Identical :: The order and members of the two groups are the same. -/// - Similar :: Only the members are the same, the order is different. -/// - Unequal :: otherwise -enum class GroupEquality { Identical, Similar, Unequal, Invalid }; +enum class GroupEquality : uint8_t { + Identical, /// The order and members of the two groups are the same. + Similar, /// Only the members are the same, the order is different. + Unequal, /// Otherwise + Invalid /// Tried to convert an invalid value to a GroupEquality. +}; /// @brief A group of MPI processes. class Group { @@ -95,21 +98,21 @@ class Group { } } - /// @Compare two groups. + /// @brief Compare two groups. /// @param other The group to compare with. /// @return True if the groups are identical; see \ref GroupEquality. False otherwise. [[nodiscard]] bool is_identical(Group const& other) const { return compare(other) == GroupEquality::Identical; } - /// @Compare two groups. + /// @brief Compare two groups. /// @param other The group to compare with. /// @return True if the groups are similar; see \ref GroupEquality. False otherwise. [[nodiscard]] bool is_similar(Group const& other) const { return compare(other) == GroupEquality::Similar; } - /// @Compare two groups. + /// @brief Compare two groups. /// @param other The group to compare with. /// @return True if the groups are identical; see \ref GroupEquality. False otherwise. [[nodiscard]] bool has_same_ranks(Group const& other) const { From 332ee9495dd3423ca3b883db331a43e628d5aa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Wed, 6 Mar 2024 14:19:06 +0100 Subject: [PATCH 3/6] Remove superflous ; --- include/kamping/group.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/kamping/group.hpp b/include/kamping/group.hpp index bc8949eb0..e4ec1fbf5 100644 --- a/include/kamping/group.hpp +++ b/include/kamping/group.hpp @@ -169,4 +169,4 @@ class Group { bool _owns_group; }; -}; // namespace kamping +} // namespace kamping From b979dfff704d95408da029db071d53d42c6ea529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Wed, 6 Mar 2024 14:23:36 +0100 Subject: [PATCH 4/6] Add licence header --- include/kamping/group.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/kamping/group.hpp b/include/kamping/group.hpp index e4ec1fbf5..4340bf439 100644 --- a/include/kamping/group.hpp +++ b/include/kamping/group.hpp @@ -1,3 +1,19 @@ +// This file is part of KaMPIng. +// +// Copyright 2024 The KaMPIng Authors +// +// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later +// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +// for more details. +// +// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see +// . + +/// @file +/// @brief An abstraction around `MPI_Group`. + #pragma once #include From bfcfe8b83a1a424b8f635f215f55b3e0062ad7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Wed, 6 Mar 2024 14:38:27 +0100 Subject: [PATCH 5/6] Another go at making Doxygen happy --- include/kamping/group.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/include/kamping/group.hpp b/include/kamping/group.hpp index 4340bf439..3ae81cf70 100644 --- a/include/kamping/group.hpp +++ b/include/kamping/group.hpp @@ -23,13 +23,12 @@ namespace kamping { -/// @enum kamping::GroupEquality /// @brief Describes the equality of two groups. enum class GroupEquality : uint8_t { - Identical, /// The order and members of the two groups are the same. - Similar, /// Only the members are the same, the order is different. - Unequal, /// Otherwise - Invalid /// Tried to convert an invalid value to a GroupEquality. + Identical, ///< The order and members of the two groups are the same. + Similar, ///< Only the members are the same, the order is different. + Unequal, ///< Otherwise + Invalid ///< Tried to convert an invalid value to a GroupEquality. }; /// @brief A group of MPI processes. From 823bc5b7fc56af4089fc06b28f77d76756110d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20H=C3=BCbner?= Date: Thu, 7 Mar 2024 09:31:09 +0100 Subject: [PATCH 6/6] Incorporate Nikas' feedback --- include/kamping/group.hpp | 4 ++-- tests/mpi_group_test.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/kamping/group.hpp b/include/kamping/group.hpp index 3ae81cf70..9f9897671 100644 --- a/include/kamping/group.hpp +++ b/include/kamping/group.hpp @@ -156,8 +156,8 @@ class Group { /// @brief Makes a group from the union of two groups. /// @param other The other group. /// @return A group containing all ranks present in either of the two groups. - /// @note The suffixing underscore is to avoid a name clash with the C++ keyword `union`. - Group union_(Group const& other) const { + /// @note The set_ prefix was choosen in order to avoid a name clash with the C++ keyword `union`. + Group set_union(Group const& other) const { MPI_Group un; MPI_Group_union(_group, other._group, &un); return Group(un); diff --git a/tests/mpi_group_test.cpp b/tests/mpi_group_test.cpp index 0a7a08e2f..9808bb988 100644 --- a/tests/mpi_group_test.cpp +++ b/tests/mpi_group_test.cpp @@ -22,7 +22,7 @@ #include "kamping/communicator.hpp" #include "kamping/group.hpp" -TEST(GroupTest, Basics) { +TEST(GroupTest, basics) { using namespace kamping; Communicator comm; @@ -74,11 +74,11 @@ TEST(GroupTest, Basics) { EXPECT_TRUE(empty_empty_inter.has_same_ranks(empty_group)); EXPECT_TRUE(world_world_inter.has_same_ranks(world_group)); - // union_() - auto world_empty_union = world_group.union_(empty_group); - auto empty_world_union = empty_group.union_(world_group); - auto world_world_union = world_group.union_(world_group); - auto empty_empty_union = empty_group.union_(empty_group); + // set_union() + auto world_empty_union = world_group.set_union(empty_group); + auto empty_world_union = empty_group.set_union(world_group); + auto world_world_union = world_group.set_union(world_group); + auto empty_empty_union = empty_group.set_union(empty_group); EXPECT_TRUE(world_empty_union.has_same_ranks(world_group)); EXPECT_TRUE(empty_world_union.has_same_ranks(world_group)); EXPECT_TRUE(empty_empty_union.has_same_ranks(empty_group));