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 support in C API for handling unweighted graphs in algorithms that expect weights #3513

Merged
21 changes: 21 additions & 0 deletions cpp/include/cugraph/edge_property.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#pragma once

#include <cugraph/detail/utility_wrappers.hpp>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this include still necessary?

#include <cugraph/utilities/dataframe_buffer.hpp>
#include <cugraph/utilities/thrust_tuple_utils.hpp>

Expand Down Expand Up @@ -126,6 +127,26 @@ class edge_dummy_property_t {
auto view() const { return edge_dummy_property_view_t{}; }
};

template <typename GraphViewType, typename T>
auto create_constant_edge_property(raft::handle_t const& handle,
GraphViewType const& graph_view,
T constant_value)
{
edge_property_t<GraphViewType, T> edge_property(handle, graph_view);

auto mutable_view = edge_property.mutable_view();

std::for_each(
thrust::make_zip_iterator(mutable_view.value_firsts().begin(),
mutable_view.edge_counts().begin()),
thrust::make_zip_iterator(mutable_view.value_firsts().end(), mutable_view.edge_counts().end()),
[&handle, constant_value](auto tuple) {
detail::scalar_fill(handle, thrust::get<0>(tuple), thrust::get<1>(tuple), constant_value);
});

return edge_property;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have fill_edge_src|dst_property() (https://github.com/rapidsai/cugraph/blob/branch-23.06/cpp/src/prims/fill_edge_src_dst_property.cuh).

So, it will be more consistent to create fill_edge_property() and call this instead of adding this function. I can quickly write fill_edge_property() if you agree.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merged your PR and then my change to use it.


template <typename edge_t, typename... Ts>
auto view_concat(edge_property_view_t<edge_t, Ts> const&... views)
{
Expand Down
8 changes: 5 additions & 3 deletions cpp/src/c_api/core_result.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION.
* Copyright (c) 2022-2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -95,8 +95,10 @@ cugraph_type_erased_device_array_view_t* cugraph_k_core_result_get_weights(
cugraph_k_core_result_t* result)
{
auto internal_pointer = reinterpret_cast<cugraph::c_api::cugraph_k_core_result_t*>(result);
return reinterpret_cast<cugraph_type_erased_device_array_view_t*>(
internal_pointer->weights_->view());
return (internal_pointer->weights_ == nullptr)
? NULL
: reinterpret_cast<cugraph_type_erased_device_array_view_t*>(
internal_pointer->weights_->view());
}

void cugraph_k_core_result_free(cugraph_k_core_result_t* result)
Expand Down
7 changes: 5 additions & 2 deletions cpp/src/c_api/induced_subgraph_result.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, NVIDIA CORPORATION.
* Copyright (c) 2022-2023, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,7 +39,10 @@ extern "C" cugraph_type_erased_device_array_view_t* cugraph_induced_subgraph_get
{
auto internal_pointer =
reinterpret_cast<cugraph::c_api::cugraph_induced_subgraph_result_t*>(induced_subgraph);
return reinterpret_cast<cugraph_type_erased_device_array_view_t*>(internal_pointer->wgt_->view());
return (internal_pointer->wgt_ == nullptr)
? NULL
: reinterpret_cast<cugraph_type_erased_device_array_view_t*>(
internal_pointer->wgt_->view());
}

extern "C" cugraph_type_erased_device_array_view_t* cugraph_induced_subgraph_get_subgraph_offsets(
Expand Down
50 changes: 45 additions & 5 deletions cpp/src/c_api/legacy_spectral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,18 @@ struct balanced_cut_clustering_functor : public cugraph::c_api::abstract_functor
auto graph_view = graph->view();
auto edge_partition_view = graph_view.local_edge_partition_view();

rmm::device_uvector<weight_t> tmp_weights(0, handle_.get_stream());
if (edge_weights == nullptr) {
tmp_weights.resize(edge_partition_view.indices().size(), handle_.get_stream());
cugraph::detail::scalar_fill(handle_, tmp_weights.data(), tmp_weights.size(), weight_t{1});
}

cugraph::legacy::GraphCSRView<vertex_t, edge_t, weight_t> legacy_graph_view(
const_cast<edge_t*>(edge_partition_view.offsets().data()),
const_cast<vertex_t*>(edge_partition_view.indices().data()),
const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
(edge_weights == nullptr)
? tmp_weights.data()
: const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
edge_partition_view.offsets().size() - 1,
edge_partition_view.indices().size());

Expand Down Expand Up @@ -209,10 +217,18 @@ struct spectral_clustering_functor : public cugraph::c_api::abstract_functor {
auto graph_view = graph->view();
auto edge_partition_view = graph_view.local_edge_partition_view();

rmm::device_uvector<weight_t> tmp_weights(0, handle_.get_stream());
if (edge_weights == nullptr) {
tmp_weights.resize(edge_partition_view.indices().size(), handle_.get_stream());
cugraph::detail::scalar_fill(handle_, tmp_weights.data(), tmp_weights.size(), weight_t{1});
}

cugraph::legacy::GraphCSRView<vertex_t, edge_t, weight_t> legacy_graph_view(
const_cast<edge_t*>(edge_partition_view.offsets().data()),
const_cast<vertex_t*>(edge_partition_view.indices().data()),
const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
(edge_weights == nullptr)
? tmp_weights.data()
: const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
edge_partition_view.offsets().size() - 1,
edge_partition_view.indices().size());

Expand Down Expand Up @@ -298,10 +314,18 @@ struct analyze_clustering_ratio_cut_functor : public cugraph::c_api::abstract_fu
auto graph_view = graph->view();
auto edge_partition_view = graph_view.local_edge_partition_view();

rmm::device_uvector<weight_t> tmp_weights(0, handle_.get_stream());
if (edge_weights == nullptr) {
tmp_weights.resize(edge_partition_view.indices().size(), handle_.get_stream());
cugraph::detail::scalar_fill(handle_, tmp_weights.data(), tmp_weights.size(), weight_t{1});
}

cugraph::legacy::GraphCSRView<vertex_t, edge_t, weight_t> legacy_graph_view(
const_cast<edge_t*>(edge_partition_view.offsets().data()),
const_cast<vertex_t*>(edge_partition_view.indices().data()),
const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
(edge_weights == nullptr)
? tmp_weights.data()
: const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
edge_partition_view.offsets().size() - 1,
edge_partition_view.indices().size());

Expand Down Expand Up @@ -405,10 +429,18 @@ struct analyze_clustering_edge_cut_functor : public cugraph::c_api::abstract_fun
auto graph_view = graph->view();
auto edge_partition_view = graph_view.local_edge_partition_view();

rmm::device_uvector<weight_t> tmp_weights(0, handle_.get_stream());
if (edge_weights == nullptr) {
tmp_weights.resize(edge_partition_view.indices().size(), handle_.get_stream());
cugraph::detail::scalar_fill(handle_, tmp_weights.data(), tmp_weights.size(), weight_t{1});
}

cugraph::legacy::GraphCSRView<vertex_t, edge_t, weight_t> legacy_graph_view(
const_cast<edge_t*>(edge_partition_view.offsets().data()),
const_cast<vertex_t*>(edge_partition_view.indices().data()),
const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
(edge_weights == nullptr)
? tmp_weights.data()
: const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
edge_partition_view.offsets().size() - 1,
edge_partition_view.indices().size());

Expand Down Expand Up @@ -512,10 +544,18 @@ struct analyze_clustering_modularity_functor : public cugraph::c_api::abstract_f
auto graph_view = graph->view();
auto edge_partition_view = graph_view.local_edge_partition_view();

rmm::device_uvector<weight_t> tmp_weights(0, handle_.get_stream());
if (edge_weights == nullptr) {
tmp_weights.resize(edge_partition_view.indices().size(), handle_.get_stream());
cugraph::detail::scalar_fill(handle_, tmp_weights.data(), tmp_weights.size(), weight_t{1});
}

cugraph::legacy::GraphCSRView<vertex_t, edge_t, weight_t> legacy_graph_view(
const_cast<edge_t*>(edge_partition_view.offsets().data()),
const_cast<vertex_t*>(edge_partition_view.indices().data()),
const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
(edge_weights == nullptr)
? tmp_weights.data()
: const_cast<weight_t*>(edge_weights->view().value_firsts().front()),
edge_partition_view.offsets().size() - 1,
edge_partition_view.indices().size());

Expand Down
10 changes: 9 additions & 1 deletion cpp/src/c_api/leiden.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,18 @@ struct leiden_functor : public cugraph::c_api::abstract_functor {
rmm::device_uvector<vertex_t> clusters(graph_view.local_vertex_partition_range_size(),
handle_.get_stream());

// FIXME: Revisit the constant edge property idea. We could consider an alternate
// implementation (perhaps involving the thrust::constant_iterator), or we
// could add support in Leiden for std::nullopt as the edge weights behaving
// as desired and only instantiating a real edge_property_view_t for the
// coarsened graphs.
auto [level, modularity] = cugraph::leiden(
handle_,
graph_view,
(edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
(edge_weights != nullptr)
? std::make_optional(edge_weights->view())
: std::make_optional(
cugraph::create_constant_edge_property(handle_, graph_view, weight_t{1}).view()),
clusters.data(),
max_level_,
static_cast<weight_t>(resolution_));
Expand Down
10 changes: 9 additions & 1 deletion cpp/src/c_api/louvain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,18 @@ struct louvain_functor : public cugraph::c_api::abstract_functor {
rmm::device_uvector<vertex_t> clusters(graph_view.local_vertex_partition_range_size(),
handle_.get_stream());

// FIXME: Revisit the constant edge property idea. We could consider an alternate
// implementation (perhaps involving the thrust::constant_iterator), or we
// could add support in Louvain for std::nullopt as the edge weights behaving
// as desired and only instantiating a real edge_property_view_t for the
// coarsened graphs.
auto [level, modularity] = cugraph::louvain(
handle_,
graph_view,
(edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
(edge_weights != nullptr)
? std::make_optional(edge_weights->view())
: std::make_optional(
cugraph::create_constant_edge_property(handle_, graph_view, weight_t{1}).view()),
clusters.data(),
max_level_,
static_cast<weight_t>(resolution_));
Expand Down
50 changes: 40 additions & 10 deletions cpp/tests/c_api/egonet_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ int generic_egonet_test(vertex_t* h_src,
cugraph_error_code_t ret_code = CUGRAPH_SUCCESS;
cugraph_error_t* ret_error;

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;

cugraph_resource_handle_t* resource_handle = NULL;
cugraph_graph_t* graph = NULL;
cugraph_type_erased_device_array_t* seeds = NULL;
Expand All @@ -52,16 +58,7 @@ int generic_egonet_test(vertex_t* h_src,
resource_handle = cugraph_create_resource_handle(NULL);
TEST_ASSERT(test_ret_value, resource_handle != NULL, "resource handle creation failed.");

ret_code = create_test_graph(resource_handle,
h_src,
h_dst,
h_wgt,
num_edges,
store_transposed,
FALSE,
FALSE,
&graph,
&ret_error);
ret_code = create_sg_test_graph(resource_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, FALSE, FALSE, &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));
Expand Down Expand Up @@ -109,9 +106,11 @@ int generic_egonet_test(vertex_t* h_src,
resource_handle, (byte_t*)h_result_dst, dst, &ret_error);
TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed.");

#if 0
ret_code = cugraph_type_erased_device_array_view_copy_to_host(
resource_handle, (byte_t*)h_result_wgt, wgt, &ret_error);
TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed.");
#endif

ret_code = cugraph_type_erased_device_array_view_copy_to_host(
resource_handle, (byte_t*)h_result_offsets, offsets, &ret_error);
Expand Down Expand Up @@ -185,11 +184,42 @@ int test_egonet()
FALSE);
}

int test_egonet_no_weights()
{
size_t num_edges = 9;
size_t num_vertices = 6;
size_t radius = 2;
size_t num_seeds = 2;

vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 3, 4};
vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 4, 5, 5};
vertex_t h_seeds[] = {0, 1};

vertex_t h_result_src[] = {0, 1, 1, 3, 1, 1, 3, 3, 4};
vertex_t h_result_dst[] = {1, 3, 4, 4, 3, 4, 4, 5, 5};
size_t h_result_offsets[] = {0, 4, 9};

// Egonet wants store_transposed = FALSE
return generic_egonet_test(h_src,
h_dst,
NULL,
h_seeds,
h_result_src,
h_result_dst,
h_result_offsets,
num_vertices,
num_edges,
num_seeds,
radius,
FALSE);
}

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

int main(int argc, char** argv)
{
int result = 0;
result |= RUN_TEST(test_egonet);
result |= RUN_TEST(test_egonet_no_weights);
return result;
}
55 changes: 40 additions & 15 deletions cpp/tests/c_api/k_core_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ int generic_k_core_test(vertex_t* h_src,
cugraph_error_code_t ret_code = CUGRAPH_SUCCESS;
cugraph_error_t* ret_error;

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;

cugraph_resource_handle_t* resource_handle = NULL;
cugraph_graph_t* graph = NULL;
cugraph_core_result_t* core_result = NULL;
Expand All @@ -51,16 +57,7 @@ int generic_k_core_test(vertex_t* h_src,
resource_handle = cugraph_create_resource_handle(NULL);
TEST_ASSERT(test_ret_value, resource_handle != NULL, "resource handle creation failed.");

ret_code = create_test_graph(resource_handle,
h_src,
h_dst,
h_wgt,
num_edges,
store_transposed,
FALSE,
TRUE,
&graph,
&ret_error);
ret_code = create_sg_test_graph(resource_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, TRUE, FALSE, &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));
Expand Down Expand Up @@ -101,9 +98,11 @@ int generic_k_core_test(vertex_t* h_src,
resource_handle, (byte_t*)h_dst_vertices, dst_vertices, &ret_error);
TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed.");

ret_code = cugraph_type_erased_device_array_view_copy_to_host(
resource_handle, (byte_t*)h_weights, weights, &ret_error);
TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed.");
if (weights != NULL) {
ret_code = cugraph_type_erased_device_array_view_copy_to_host(
resource_handle, (byte_t*)h_weights, weights, &ret_error);
TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed.");
}

TEST_ASSERT(test_ret_value,
number_of_result_edges == num_result_edges,
Expand All @@ -115,11 +114,11 @@ int generic_k_core_test(vertex_t* h_src,
M[i][j] = 0;

for (int i = 0; i < num_result_edges; ++i)
M[h_result_src[i]][h_result_dst[i]] = h_result_wgt[i];
M[h_result_src[i]][h_result_dst[i]] = (h_result_wgt != NULL) ? h_result_wgt[i] : 1.0;

for (int i = 0; (i < number_of_result_edges) && (test_ret_value == 0); ++i) {
TEST_ASSERT(test_ret_value,
M[h_src_vertices[i]][h_dst_vertices[i]] == h_weights[i],
M[h_src_vertices[i]][h_dst_vertices[i]] == (h_result_wgt != NULL) ? h_weights[i] : 1.0,
"edge does not match");
}

Expand Down Expand Up @@ -160,11 +159,37 @@ int test_k_core()
FALSE);
}

int test_k_core_no_weights()
{
size_t num_edges = 22;
size_t num_vertices = 7;
size_t num_result_edges = 12;
size_t k = 3;

vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 1, 3, 4, 0, 1, 3, 5, 5, 3, 1, 4, 5, 5, 6};
vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 0, 1, 1, 2, 2, 2, 3, 4, 4, 5, 3, 1, 6, 5};
vertex_t h_result_src[] = {1, 1, 3, 4, 3, 4, 3, 4, 5, 5, 1, 5};
vertex_t h_result_dst[] = {3, 4, 5, 5, 1, 3, 4, 1, 3, 4, 5, 1};

return generic_k_core_test(h_src,
h_dst,
NULL,
h_result_src,
h_result_dst,
NULL,
num_vertices,
num_edges,
num_result_edges,
k,
FALSE);
}

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

int main(int argc, char** argv)
{
int result = 0;
result |= RUN_TEST(test_k_core);
result |= RUN_TEST(test_k_core_no_weights);
return result;
}
Loading