Skip to content

Commit

Permalink
Expose graph_view method to count multi edges through C API and PLC (r…
Browse files Browse the repository at this point in the history
…apidsai#4426)

Python property graph class does some expensive dask calls to determine if a graph is a multi-graph.

This PR exposes to the C API and the PLC layer the graph_view method that counts the number of multi-edges in the graph.  Specifically, the function returns the number of extra edges exist.  So if the edge `(u,v)` exists `k` times, that edge contributes `k-1` to the return value.

If the return value of this function is greater than 0 then the graph is a multi-graph.  If the return value is 0 then while the graph might be labeled a multi-graph, there are no multi-edges so it could be treated as a graph.

Closes rapidsai#2417 

Note that the property graph python code would still need to be modified to use this function.

Authors:
  - Chuck Hastings (https://github.com/ChuckHastings)

Approvers:
  - Seunghwa Kang (https://github.com/seunghwak)
  - Joseph Nke (https://github.com/jnke2016)
  - Alex Barghi (https://github.com/alexbarghi-nv)

URL: rapidsai#4426
  • Loading branch information
ChuckHastings authored May 17, 2024
1 parent 64d5477 commit 78b380e
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 0 deletions.
20 changes: 20 additions & 0 deletions cpp/include/cugraph_c/graph_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,26 @@ cugraph_error_code_t cugraph_allgather(const cugraph_resource_handle_t* handle,
cugraph_induced_subgraph_result_t** result,
cugraph_error_t** error);

/**
* @brief Count multi_edges
*
* Count the number of multi-edges in the graph
*
* @param [in] handle Handle for accessing resources.
* @param [in] graph Pointer to graph
* @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to
* true)
* @param [out] result Where to store the count of multi-edges
* @param [out] error Pointer to an error object storing details of any error. Will
* be populated if error code is not CUGRAPH_SUCCESS
* @return error code
*/
cugraph_error_code_t cugraph_count_multi_edges(const cugraph_resource_handle_t* handle,
cugraph_graph_t* graph,
bool_t do_expensive_check,
size_t* result,
cugraph_error_t** error);

/**
* @brief Opaque degree result type
*/
Expand Down
49 changes: 49 additions & 0 deletions cpp/src/c_api/graph_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,44 @@ struct two_hop_neighbors_functor : public cugraph::c_api::abstract_functor {
}
};

struct count_multi_edges_functor : public cugraph::c_api::abstract_functor {
raft::handle_t const& handle_{};
cugraph::c_api::cugraph_graph_t* graph_{nullptr};
size_t result_{};
bool do_expensive_check_{false};

count_multi_edges_functor(::cugraph_resource_handle_t const* handle,
::cugraph_graph_t* graph,
bool do_expensive_check)
: abstract_functor(),
handle_(*reinterpret_cast<cugraph::c_api::cugraph_resource_handle_t const*>(handle)->handle_),
graph_(reinterpret_cast<cugraph::c_api::cugraph_graph_t*>(graph)),
do_expensive_check_(do_expensive_check)
{
}

template <typename vertex_t,
typename edge_t,
typename weight_t,
typename edge_type_type_t,
bool store_transposed,
bool multi_gpu>
void operator()()
{
if constexpr (!cugraph::is_candidate<vertex_t, edge_t, weight_t>::value) {
unsupported();
} else {
auto graph =
reinterpret_cast<cugraph::graph_t<vertex_t, edge_t, store_transposed, multi_gpu>*>(
graph_->graph_);

auto graph_view = graph->view();

result_ = static_cast<size_t>(graph_view.count_multi_edges(handle_));
}
}
};

} // namespace

extern "C" cugraph_error_code_t cugraph_create_vertex_pairs(
Expand Down Expand Up @@ -281,3 +319,14 @@ extern "C" cugraph_error_code_t cugraph_two_hop_neighbors(

return cugraph::c_api::run_algorithm(graph, functor, result, error);
}

extern "C" cugraph_error_code_t cugraph_count_multi_edges(const cugraph_resource_handle_t* handle,
cugraph_graph_t* graph,
bool_t do_expensive_check,
size_t* result,
cugraph_error_t** error)
{
count_multi_edges_functor functor(handle, graph, do_expensive_check);

return cugraph::c_api::run_algorithm(graph, functor, result, error);
}
2 changes: 2 additions & 0 deletions cpp/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ if(BUILD_CUGRAPH_MG_TESTS)
ConfigureCTestMG(MG_CAPI_K_CORE_TEST c_api/mg_k_core_test.c)
ConfigureCTestMG(MG_CAPI_INDUCED_SUBGRAPH_TEST c_api/mg_induced_subgraph_test.c)
ConfigureCTestMG(MG_CAPI_DEGREES c_api/mg_degrees_test.c)
ConfigureCTestMG(MG_CAPI_COUNT_MULTI_EDGES c_api/mg_count_multi_edges_test.c)
ConfigureCTestMG(MG_CAPI_EGONET_TEST c_api/mg_egonet_test.c)
ConfigureCTestMG(MG_CAPI_TWO_HOP_NEIGHBORS_TEST c_api/mg_two_hop_neighbors_test.c)

Expand Down Expand Up @@ -777,6 +778,7 @@ ConfigureCTest(CAPI_SIMILARITY_TEST c_api/similarity_test.c)
ConfigureCTest(CAPI_K_CORE_TEST c_api/k_core_test.c)
ConfigureCTest(CAPI_INDUCED_SUBGRAPH_TEST c_api/induced_subgraph_test.c)
ConfigureCTest(CAPI_DEGREES c_api/degrees_test.c)
ConfigureCTest(CAPI_COUNT_MULTI_EDGES c_api/count_multi_edges_test.c)
ConfigureCTest(CAPI_EGONET_TEST c_api/egonet_test.c)
ConfigureCTest(CAPI_TWO_HOP_NEIGHBORS_TEST c_api/two_hop_neighbors_test.c)
ConfigureCTest(CAPI_K_TRUSS_TEST c_api/k_truss_test.c)
Expand Down
114 changes: 114 additions & 0 deletions cpp/tests/c_api/count_multi_edges_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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 "c_test_utils.h" /* RUN_TEST */

#include <cugraph_c/graph.h>
#include <cugraph_c/graph_functions.h>

#include <stdio.h>

typedef int32_t vertex_t;
typedef int32_t edge_t;
typedef float weight_t;

data_type_id_t vertex_tid = INT32;
data_type_id_t edge_tid = INT32;
data_type_id_t weight_tid = FLOAT32;
data_type_id_t edge_id_tid = INT32;
data_type_id_t edge_type_tid = INT32;

/*
* Create graph and count multi-edges
*/
int generic_count_multi_edges_test(vertex_t* h_src,
vertex_t* h_dst,
weight_t* h_wgt,
size_t num_vertices,
size_t num_edges,
bool_t store_transposed,
bool_t is_symmetric,
bool_t is_multigraph,
size_t multi_edges_count)
{
int test_ret_value = 0;

cugraph_error_code_t ret_code = CUGRAPH_SUCCESS;
cugraph_error_t* ret_error;

cugraph_resource_handle_t* handle = NULL;
cugraph_graph_t* graph = NULL;
size_t result = 0;

handle = cugraph_create_resource_handle(NULL);
TEST_ASSERT(test_ret_value, handle != NULL, "resource handle creation failed.");

ret_code = create_sg_test_graph(handle,
vertex_tid,
edge_tid,
h_src,
h_dst,
weight_tid,
h_wgt,
edge_type_tid,
NULL,
edge_id_tid,
NULL,
num_edges,
store_transposed,
FALSE,
is_symmetric,
is_multigraph,
&graph,
&ret_error);

TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "create_test_graph failed.");
TEST_ALWAYS_ASSERT(ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error));

ret_code = cugraph_count_multi_edges(handle, graph, FALSE, &result, &ret_error);

TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_count_multi_edges failed.");

TEST_ASSERT(test_ret_value, result == multi_edges_count, "multi-edge count did not match");

cugraph_graph_free(graph);
cugraph_error_free(ret_error);

return test_ret_value;
}

int test_multi_edges_count()
{
size_t num_edges = 14;
size_t num_vertices = 6;

vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 0, 1, 1, 3, 0, 1};
vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 1, 3, 0, 1, 1, 0};
weight_t h_wgt[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
size_t multi_edge_count = 4;

return generic_count_multi_edges_test(
h_src, h_dst, h_wgt, num_vertices, num_edges, TRUE, TRUE, TRUE, multi_edge_count);
}

/******************************************************************************/

int main(int argc, char** argv)
{
int result = 0;
result |= RUN_TEST(test_multi_edges_count);
return result;
}
118 changes: 118 additions & 0 deletions cpp/tests/c_api/mg_count_multi_edges_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* 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 "mg_test_utils.h" /* RUN_TEST */

#include <cugraph_c/graph.h>
#include <cugraph_c/graph_functions.h>

#include <stdio.h>

typedef int32_t vertex_t;
typedef int32_t edge_t;
typedef float weight_t;

data_type_id_t vertex_tid = INT32;
data_type_id_t edge_tid = INT32;
data_type_id_t weight_tid = FLOAT32;
data_type_id_t edge_id_tid = INT32;
data_type_id_t edge_type_tid = INT32;

/*
* Create graph and count multi-edges
*/
int generic_count_multi_edges_test(const cugraph_resource_handle_t* handle,
vertex_t* h_src,
vertex_t* h_dst,
weight_t* h_wgt,
size_t num_vertices,
size_t num_edges,
bool_t store_transposed,
bool_t is_symmetric,
bool_t is_multigraph,
size_t multi_edges_count)
{
int test_ret_value = 0;

cugraph_error_code_t ret_code = CUGRAPH_SUCCESS;
cugraph_error_t* ret_error;

cugraph_graph_t* graph = NULL;
size_t result = 0;

ret_code = create_mg_test_graph_new(handle,
vertex_tid,
edge_tid,
h_src,
h_dst,
weight_tid,
h_wgt,
edge_type_tid,
NULL,
edge_id_tid,
NULL,
num_edges,
store_transposed,
FALSE,
is_symmetric,
is_multigraph,
&graph,
&ret_error);

TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "create_test_graph failed.");
TEST_ALWAYS_ASSERT(ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error));

ret_code = cugraph_count_multi_edges(handle, graph, FALSE, &result, &ret_error);

TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_count_multi_edges failed.");

TEST_ASSERT(test_ret_value, result == multi_edges_count, "multi-edge count did not match");

cugraph_graph_free(graph);
cugraph_error_free(ret_error);

return test_ret_value;
}

int test_multi_edges_count(const cugraph_resource_handle_t* handle)
{
size_t num_edges = 14;
size_t num_vertices = 6;

vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 0, 1, 1, 3, 0, 1};
vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 1, 3, 0, 1, 1, 0};
weight_t h_wgt[] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
size_t multi_edge_count = 4;

return generic_count_multi_edges_test(
handle, h_src, h_dst, h_wgt, num_vertices, num_edges, TRUE, TRUE, TRUE, multi_edge_count);
}

/******************************************************************************/

int main(int argc, char** argv)
{
void* raft_handle = create_mg_raft_handle(argc, argv);
cugraph_resource_handle_t* handle = cugraph_create_resource_handle(raft_handle);

int result = 0;
result |= RUN_MG_TEST(test_multi_edges_count, handle);

cugraph_free_resource_handle(handle);
free_mg_raft_handle(raft_handle);

return result;
}
11 changes: 11 additions & 0 deletions python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ cdef extern from "cugraph_c/graph_functions.h":
cugraph_error_t** error
)

###########################################################################
# count multi-edges
cdef cugraph_error_code_t \
cugraph_count_multi_edges(
const cugraph_resource_handle_t *handle,
cugraph_graph_t* graph,
bool_t do_expenive_check,
size_t *result,
cugraph_error_t** error
)

###########################################################################
# degrees
ctypedef struct cugraph_degrees_result_t:
Expand Down
Loading

0 comments on commit 78b380e

Please sign in to comment.