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

MNMG Approximation Algorithm for the Weighted Matching Problem #4315

Merged
merged 21 commits into from
May 21, 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
2 changes: 2 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ set(CUGRAPH_SOURCES
src/structure/symmetrize_edgelist_mg.cu
src/community/triangle_count_sg.cu
src/community/triangle_count_mg.cu
src/community/approx_weighted_matching_sg.cu
src/community/approx_weighted_matching_mg.cu
src/traversal/k_hop_nbrs_sg.cu
src/traversal/k_hop_nbrs_mg.cu
src/mtmg/vertex_result.cu
Expand Down
26 changes: 26 additions & 0 deletions cpp/include/cugraph/algorithms.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2368,6 +2368,32 @@ rmm::device_uvector<vertex_t> vertex_coloring(
graph_view_t<vertex_t, edge_t, false, multi_gpu> const& graph_view,
raft::random::RngState& rng_state);

/*
* @brief Approximate Weighted Matching
*
* A matching in an undirected graph G = (V, E) is a pairing of adjacent vertices
* such that each vertex is matched with at most one other vertex, the objective
* being to match as many vertices as possible or to maximise the sum of the
* weights of the matched edges. Here we provide an implementation of an
* approximation algorithm to the weighted Maximum matching. See
* https://web.archive.org/web/20081031230449id_/http://www.ii.uib.no/~fredrikm/fredrik/papers/CP75.pdf
* for further information.
*
* @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
* @tparam edge_t Type of edge identifiers. Needs to be an integral type.
* @tparam multi_gpu Flag indicating whether template instantiation should target single-GPU (false)
* @param[in] handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator,
* and handles to various CUDA libraries) to run graph algorithms.
* @param[in] graph_view Graph view object.
* @param[in] edge_weight_view View object holding edge weights for @p graph_view.
* @return A tuple of device vector of matched vertex ids and sum of the weights of the matched
* edges.
*/
template <typename vertex_t, typename edge_t, typename weight_t, bool multi_gpu>
std::tuple<rmm::device_uvector<vertex_t>, weight_t> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<vertex_t, edge_t, false, multi_gpu> const& graph_view,
edge_property_view_t<edge_t, weight_t const*> edge_weight_view);
} // namespace cugraph

/**
Expand Down
392 changes: 392 additions & 0 deletions cpp/src/community/approx_weighted_matching_impl.cuh

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions cpp/src/community/approx_weighted_matching_mg.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "approx_weighted_matching_impl.cuh"

namespace cugraph {

template std::tuple<rmm::device_uvector<int32_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int32_t, false, true> const& graph_view,
edge_property_view_t<int32_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int32_t, false, true> const& graph_view,
edge_property_view_t<int32_t, double const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int64_t, false, true> const& graph_view,
edge_property_view_t<int64_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int64_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int64_t, int64_t, false, true> const& graph_view,
edge_property_view_t<int64_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int64_t, false, true> const& graph_view,
edge_property_view_t<int64_t, double const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int64_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int64_t, int64_t, false, true> const& graph_view,
edge_property_view_t<int64_t, double const*> edge_weight_view);

} // namespace cugraph
50 changes: 50 additions & 0 deletions cpp/src/community/approx_weighted_matching_sg.cu
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "approx_weighted_matching_impl.cuh"

namespace cugraph {

template std::tuple<rmm::device_uvector<int32_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int32_t, false, false> const& graph_view,
edge_property_view_t<int32_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int32_t, false, false> const& graph_view,
edge_property_view_t<int32_t, double const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int64_t, false, false> const& graph_view,
edge_property_view_t<int64_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int64_t>, float> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int64_t, int64_t, false, false> const& graph_view,
edge_property_view_t<int64_t, float const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int32_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int32_t, int64_t, false, false> const& graph_view,
edge_property_view_t<int64_t, double const*> edge_weight_view);

template std::tuple<rmm::device_uvector<int64_t>, double> approximate_weighted_matching(
raft::handle_t const& handle,
graph_view_t<int64_t, int64_t, false, false> const& graph_view,
edge_property_view_t<int64_t, double const*> edge_weight_view);

} // namespace cugraph
8 changes: 8 additions & 0 deletions cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ ConfigureTest(LOUVAIN_TEST community/louvain_test.cpp)
# - LEIDEN tests ----------------------------------------------------------------------------------
ConfigureTest(LEIDEN_TEST community/leiden_test.cpp)

###################################################################################################
# - WEIGHTED MATCHING tests ----------------------------------------------------------------------------------
ConfigureTest(WEIGHTED_MATCHING_TEST community/weighted_matching_test.cpp)

###################################################################################################
# - Legacy ECG tests -------------------------------------------------------------------------------------
ConfigureTest(LEGACY_ECG_TEST community/legacy_ecg_test.cpp)
Expand Down Expand Up @@ -570,6 +574,10 @@ if(BUILD_CUGRAPH_MG_TESTS)
# - MG LEIDEN tests --------------------------------------------------------------------------
ConfigureTestMG(MG_LEIDEN_TEST community/mg_leiden_test.cpp)

###############################################################################################
# - MG WEIGHTED MATCHING tests --------------------------------------------------------------------------
ConfigureTestMG(MG_WEIGHTED_MATCHING_TEST community/mg_weighted_matching_test.cpp)

###############################################################################################
# - MG ECG tests --------------------------------------------------------------------------
ConfigureTestMG(MG_ECG_TEST community/mg_ecg_test.cpp)
Expand Down
232 changes: 232 additions & 0 deletions cpp/tests/community/mg_weighted_matching_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* Copyright (c) 2024, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governin_from_mtxg permissions and
* limitations under the License.
*/

#include "utilities/base_fixture.hpp"
#include "utilities/conversion_utilities.hpp"
#include "utilities/property_generator_utilities.hpp"
#include "utilities/test_graphs.hpp"

#include <cugraph/algorithms.hpp>
#include <cugraph/edge_partition_view.hpp>
#include <cugraph/edge_property.hpp>
#include <cugraph/edge_src_dst_property.hpp>
#include <cugraph/graph_functions.hpp>
#include <cugraph/graph_view.hpp>
#include <cugraph/utilities/dataframe_buffer.hpp>
#include <cugraph/utilities/high_res_timer.hpp>
#include <cugraph/utilities/host_scalar_comm.hpp>

#include <raft/random/rng_state.hpp>

#include <gtest/gtest.h>

#include <chrono>
#include <iostream>
#include <random>

struct WeightedMatching_UseCase {
bool edge_masking{false};
bool check_correctness{true};
naimnv marked this conversation as resolved.
Show resolved Hide resolved
};

template <typename input_usecase_t>
class Tests_MGWeightedMatching
: public ::testing::TestWithParam<std::tuple<WeightedMatching_UseCase, input_usecase_t>> {
public:
Tests_MGWeightedMatching() {}

static void SetUpTestCase() { handle_ = cugraph::test::initialize_mg_handle(); }
static void TearDownTestCase() { handle_.reset(); }

virtual void SetUp() {}
virtual void TearDown() {}

template <typename vertex_t, typename edge_t, typename weight_t, typename result_t>
void run_current_test(std::tuple<WeightedMatching_UseCase, input_usecase_t> const& param)
{
auto [weighted_matching_usecase, input_usecase] = param;

HighResTimer hr_timer{};

if (cugraph::test::g_perf) {
RAFT_CUDA_TRY(cudaDeviceSynchronize());
handle_->get_comms().barrier();
hr_timer.start("MG Construct graph");
}

constexpr bool multi_gpu = true;

bool test_weighted = true;
bool renumber = true;
bool drop_self_loops = false;
bool drop_multi_edges = false;

auto [mg_graph, mg_edge_weights, mg_renumber_map] =
cugraph::test::construct_graph<vertex_t, edge_t, weight_t, false, multi_gpu>(
*handle_, input_usecase, test_weighted, renumber, drop_self_loops, drop_multi_edges);

std::tie(mg_graph, mg_edge_weights, mg_renumber_map) = cugraph::symmetrize_graph(
*handle_,
std::move(mg_graph),
std::move(mg_edge_weights),
mg_renumber_map ? std::optional<rmm::device_uvector<vertex_t>>(std::move(*mg_renumber_map))
: std::nullopt,
false);

if (cugraph::test::g_perf) {
RAFT_CUDA_TRY(cudaDeviceSynchronize());
handle_->get_comms().barrier();
hr_timer.stop();
hr_timer.display_and_clear(std::cout);
}

auto mg_graph_view = mg_graph.view();
auto mg_edge_weight_view =
mg_edge_weights ? std::make_optional((*mg_edge_weights).view()) : std::nullopt;

std::optional<cugraph::edge_property_t<decltype(mg_graph_view), bool>> edge_mask{std::nullopt};
if (weighted_matching_usecase.edge_masking) {
edge_mask = cugraph::test::generate<decltype(mg_graph_view), bool>::edge_property(
*handle_, mg_graph_view, 2);
mg_graph_view.attach_edge_mask((*edge_mask).view());
}

rmm::device_uvector<vertex_t> mg_partners(0, handle_->get_stream());
weight_t mg_matching_weights;

std::forward_as_tuple(mg_partners, mg_matching_weights) =
cugraph::approximate_weighted_matching<vertex_t, edge_t, weight_t, multi_gpu>(
*handle_, mg_graph_view, (*mg_edge_weights).view());

if (weighted_matching_usecase.check_correctness) {
auto h_mg_partners = cugraph::test::to_host(*handle_, mg_partners);

auto constexpr invalid_partner = cugraph::invalid_vertex_id<vertex_t>::value;

rmm::device_uvector<vertex_t> mg_aggregate_partners(0, handle_->get_stream());
std::tie(std::ignore, mg_aggregate_partners) =
cugraph::test::mg_vertex_property_values_to_sg_vertex_property_values(
*handle_,
std::optional<raft::device_span<vertex_t const>>{std::nullopt},
mg_graph_view.local_vertex_partition_range(),
std::optional<raft::device_span<vertex_t const>>{std::nullopt},
std::optional<raft::device_span<vertex_t const>>{std::nullopt},
raft::device_span<vertex_t const>(mg_partners.data(), mg_partners.size()));

cugraph::graph_t<vertex_t, edge_t, false, false> sg_graph(*handle_);
std::optional<
cugraph::edge_property_t<cugraph::graph_view_t<vertex_t, edge_t, false, false>, weight_t>>
sg_edge_weights{std::nullopt};
std::tie(sg_graph, sg_edge_weights, std::ignore) = cugraph::test::mg_graph_to_sg_graph(
*handle_,
mg_graph_view,
mg_edge_weight_view,
std::optional<raft::device_span<vertex_t const>>(std::nullopt),
false);

if (handle_->get_comms().get_rank() == 0) {
auto sg_graph_view = sg_graph.view();

rmm::device_uvector<vertex_t> sg_partners(0, handle_->get_stream());
weight_t sg_matching_weights;

std::forward_as_tuple(sg_partners, sg_matching_weights) =
cugraph::approximate_weighted_matching<vertex_t, edge_t, weight_t, false>(
*handle_, sg_graph_view, (*sg_edge_weights).view());
auto h_sg_partners = cugraph::test::to_host(*handle_, sg_partners);
auto h_mg_aggregate_partners = cugraph::test::to_host(*handle_, mg_aggregate_partners);

ASSERT_FLOAT_EQ(mg_matching_weights, sg_matching_weights)
<< "SG and MG matching weights are different";
ASSERT_TRUE(
std::equal(h_sg_partners.begin(), h_sg_partners.end(), h_mg_aggregate_partners.begin()));
}
}
}

private:
static std::unique_ptr<raft::handle_t> handle_;
};

template <typename input_usecase_t>
std::unique_ptr<raft::handle_t> Tests_MGWeightedMatching<input_usecase_t>::handle_ = nullptr;

using Tests_MGWeightedMatching_File = Tests_MGWeightedMatching<cugraph::test::File_Usecase>;
using Tests_MGWeightedMatching_Rmat = Tests_MGWeightedMatching<cugraph::test::Rmat_Usecase>;

TEST_P(Tests_MGWeightedMatching_File, CheckInt32Int32FloatFloat)
{
run_current_test<int32_t, int32_t, float, int>(
override_File_Usecase_with_cmd_line_arguments(GetParam()));
}

TEST_P(Tests_MGWeightedMatching_File, CheckInt32Int64FloatFloat)
{
run_current_test<int32_t, int64_t, float, int>(
override_File_Usecase_with_cmd_line_arguments(GetParam()));
}

TEST_P(Tests_MGWeightedMatching_File, CheckInt64Int64FloatFloat)
{
run_current_test<int64_t, int64_t, float, int>(
override_File_Usecase_with_cmd_line_arguments(GetParam()));
}

TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt32Int32FloatFloat)
{
run_current_test<int32_t, int32_t, float, int>(
override_Rmat_Usecase_with_cmd_line_arguments(GetParam()));
}

TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt32Int64FloatFloat)
{
run_current_test<int32_t, int64_t, float, int>(
override_Rmat_Usecase_with_cmd_line_arguments(GetParam()));
}

TEST_P(Tests_MGWeightedMatching_Rmat, CheckInt64Int64FloatFloat)
{
run_current_test<int64_t, int64_t, float, int>(
override_Rmat_Usecase_with_cmd_line_arguments(GetParam()));
}

INSTANTIATE_TEST_SUITE_P(
file_test,
Tests_MGWeightedMatching_File,
::testing::Combine(::testing::Values(WeightedMatching_UseCase{false},
WeightedMatching_UseCase{true}),
::testing::Values(cugraph::test::File_Usecase("test/datasets/karate.mtx"))));

INSTANTIATE_TEST_SUITE_P(rmat_small_test,
Tests_MGWeightedMatching_Rmat,
::testing::Combine(::testing::Values(WeightedMatching_UseCase{false},
WeightedMatching_UseCase{true}),
::testing::Values(cugraph::test::Rmat_Usecase(
3, 2, 0.57, 0.19, 0.19, 0, true, false))));

INSTANTIATE_TEST_SUITE_P(
rmat_benchmark_test, /* note that scale & edge factor can be overridden in benchmarking (with
--gtest_filter to select only the rmat_benchmark_test with a specific
vertex & edge type combination) by command line arguments and do not
include more than one Rmat_Usecase that differ only in scale or edge
factor (to avoid running same benchmarks more than once) */
Tests_MGWeightedMatching_Rmat,
::testing::Combine(
::testing::Values(WeightedMatching_UseCase{false, false},
WeightedMatching_UseCase{true, false}),
::testing::Values(cugraph::test::Rmat_Usecase(20, 32, 0.57, 0.19, 0.19, 0, true, false))));

CUGRAPH_MG_TEST_PROGRAM_MAIN()
Loading
Loading