Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a basic wrapper around MPI_Group #663

Merged
merged 6 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/kamping/communicator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -300,6 +301,12 @@ class Communicator : public Plugins<Communicator<DefaultContainerType, Plugins..
return split_by_type(MPI_COMM_TYPE_SHARED);
}

/// @brief Return the group associated with this communicator.
/// @return The group associated with this communicator.
[[nodiscard]] Group group() const {
return Group(mpi_communicator());
}

/// @brief Create subcommunicators.
///
/// This method requires globally available information on the ranks in the subcommunicators.
Expand Down
187 changes: 187 additions & 0 deletions include/kamping/group.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// 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
// <https://www.gnu.org/licenses/>.

/// @file
/// @brief An abstraction around `MPI_Group`.

#pragma once

#include <kassert/kassert.hpp>
#include <mpi.h>

#include "kamping/checking_casts.hpp"

namespace kamping {

/// @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.
};

/// @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 <typename Comm>
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 <typename Comm>
// 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;
}
}

/// @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;
}

/// @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;
}

/// @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 {
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 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);
}

/// @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_t>(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<size_t>(rank);
}

private:
MPI_Group _group;
bool _owns_group;
};

} // namespace kamping
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions tests/mpi_group_test.cpp
Original file line number Diff line number Diff line change
@@ -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
// <https://www.gnu.org/licenses/>.

#include <vector>

#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include <kassert/kassert.hpp>
#include <mpi.h>

#include "kamping/assertion_levels.hpp"
#include "kamping/communicator.hpp"
#include "kamping/group.hpp"

TEST(GroupTest, basics) {
using namespace kamping;

Communicator<std::vector> 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));

// 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));
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()));
}
Loading