From f55c3e57e6a532157ec4d976b95c8b6ed68e5685 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Mon, 4 Mar 2024 14:27:51 -0800 Subject: [PATCH 01/21] Add degrees to C API --- cpp/CMakeLists.txt | 2 + cpp/include/cugraph_c/graph_functions.h | 66 +++++++++++- cpp/src/c_api/degrees.cpp | 100 ++++++++++++++++++ cpp/src/c_api/degrees_result.cpp | 56 ++++++++++ cpp/src/c_api/degrees_result.hpp | 31 ++++++ cpp/tests/CMakeLists.txt | 2 + cpp/tests/c_api/degrees_test.c | 131 +++++++++++++++++++++++ cpp/tests/c_api/mg_degrees_test.c | 134 ++++++++++++++++++++++++ 8 files changed, 521 insertions(+), 1 deletion(-) create mode 100644 cpp/src/c_api/degrees.cpp create mode 100644 cpp/src/c_api/degrees_result.cpp create mode 100644 cpp/src/c_api/degrees_result.hpp create mode 100644 cpp/tests/c_api/degrees_test.c create mode 100644 cpp/tests/c_api/mg_degrees_test.c diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index c812cd8e4b3..ec5a65f5ad7 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -411,6 +411,8 @@ endif() add_library(cugraph_c src/c_api/resource_handle.cpp src/c_api/array.cpp + src/c_api/degrees.cpp + src/c_api/degrees_result.cpp src/c_api/error.cpp src/c_api/graph_sg.cpp src/c_api/graph_mg.cpp diff --git a/cpp/include/cugraph_c/graph_functions.h b/cpp/include/cugraph_c/graph_functions.h index 19b69922fa5..d97e4d488a2 100644 --- a/cpp/include/cugraph_c/graph_functions.h +++ b/cpp/include/cugraph_c/graph_functions.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, NVIDIA CORPORATION. + * Copyright (c) 2022-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. @@ -229,6 +229,70 @@ cugraph_error_code_t cugraph_allgather(const cugraph_resource_handle_t* handle, cugraph_induced_subgraph_result_t** result, cugraph_error_t** error); +/** + * @brief Opaque degree result type + */ +typedef struct { + int32_t align_; +} cugraph_degrees_result_t; + +/** + * @brief Compute degrees + * + * Compute the degrees for the vertices 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 Opaque pointer to gathered edgelist result + * @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_degrees(const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error); + +/** + * @brief Get the vertex ids + * + * @param [in] degrees_result Opaque pointer to degree result + * @return type erased array view of vertex ids + */ +cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_vertices( + cugraph_degrees_result_t* degrees_result); + +/** + * @brief Get the in degrees + * + * @param [in] degrees_result Opaque pointer to degree result + * @return type erased array view of vertex ids + */ +cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_in_degrees( + cugraph_degrees_result_t* degrees_result); + +/** + * @brief Get the out degrees + * + * If the graph is symmetric, in degrees and out degrees will be equal (and + * will be stored in the same memory). + * + * @param [in] degrees_result Opaque pointer to degree result + * @return type erased array view of vertex ids + */ +cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_out_degrees( + cugraph_degrees_result_t* degrees_result); + +/** + * @brief Free degree result + * + * @param [in] degrees_result Opaque pointer to degree result + */ +void cugraph_degrees_result_free(cugraph_degrees_result_t* degrees_result); + #ifdef __cplusplus } #endif diff --git a/cpp/src/c_api/degrees.cpp b/cpp/src/c_api/degrees.cpp new file mode 100644 index 00000000000..0b580b8a81e --- /dev/null +++ b/cpp/src/c_api/degrees.cpp @@ -0,0 +1,100 @@ +/* + * 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 + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace { + +struct degrees_functor : public cugraph::c_api::abstract_functor { + raft::handle_t const& handle_; + cugraph::c_api::cugraph_graph_t* graph_{}; + bool do_expensive_check_{}; + cugraph::c_api::cugraph_degrees_result_t* result_{}; + + degrees_functor(cugraph_resource_handle_t const* handle, + cugraph_graph_t* graph, + bool do_expensive_check) + : abstract_functor(), + handle_(*reinterpret_cast(handle)->handle_), + graph_(reinterpret_cast(graph)), + do_expensive_check_(do_expensive_check) + { + } + + template + void operator()() + { + // FIXME: Think about how to handle SG vice MG + if constexpr (!cugraph::is_candidate::value) { + unsupported(); + } else { + auto graph = + reinterpret_cast*>( + graph_->graph_); + + auto graph_view = graph->view(); + + auto number_map = reinterpret_cast*>(graph_->number_map_); + + rmm::device_uvector in_degrees(0, handle_.get_stream()); + rmm::device_uvector out_degrees(0, handle_.get_stream()); + + in_degrees = graph_view.compute_in_degrees(handle_); + if (!graph_view.is_symmetric()) { out_degrees = graph_view.compute_out_degrees(handle_); } + + rmm::device_uvector vertex_ids(graph_view.local_vertex_partition_range_size(), + handle_.get_stream()); + raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); + + result_ = new cugraph::c_api::cugraph_degrees_result_t{ + new cugraph::c_api::cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), + new cugraph::c_api::cugraph_type_erased_device_array_t(in_degrees, graph_->edge_type_), + graph_view.is_symmetric() ? nullptr + : new cugraph::c_api::cugraph_type_erased_device_array_t( + out_degrees, graph_->edge_type_)}; + } + } +}; + +} // namespace + +extern "C" cugraph_error_code_t cugraph_degrees(const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error) +{ + degrees_functor functor(handle, graph, do_expensive_check); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} diff --git a/cpp/src/c_api/degrees_result.cpp b/cpp/src/c_api/degrees_result.cpp new file mode 100644 index 00000000000..fbb532bcb29 --- /dev/null +++ b/cpp/src/c_api/degrees_result.cpp @@ -0,0 +1,56 @@ +/* + * 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_api/degrees_result.hpp" + +#include + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_vertices( + cugraph_degrees_result_t* degrees_result) +{ + auto internal_pointer = + reinterpret_cast(degrees_result); + return reinterpret_cast( + internal_pointer->vertex_ids_->view()); +} + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_in_degrees( + cugraph_degrees_result_t* degrees_result) +{ + auto internal_pointer = + reinterpret_cast(degrees_result); + return reinterpret_cast( + internal_pointer->in_degrees_->view()); +} + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_out_degrees( + cugraph_degrees_result_t* degrees_result) +{ + auto internal_pointer = + reinterpret_cast(degrees_result); + return reinterpret_cast( + internal_pointer->out_degrees_->view()); +} + +extern "C" void cugraph_degrees_result_free(cugraph_degrees_result_t* degrees_result) +{ + auto internal_pointer = + reinterpret_cast(degrees_result); + delete internal_pointer->vertex_ids_; + delete internal_pointer->in_degrees_; + delete internal_pointer->out_degrees_; + delete internal_pointer; +} diff --git a/cpp/src/c_api/degrees_result.hpp b/cpp/src/c_api/degrees_result.hpp new file mode 100644 index 00000000000..e7f0ad8c996 --- /dev/null +++ b/cpp/src/c_api/degrees_result.hpp @@ -0,0 +1,31 @@ +/* + * 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. + */ + +#pragma once + +#include "c_api/array.hpp" + +namespace cugraph { +namespace c_api { + +struct cugraph_degrees_result_t { + cugraph_type_erased_device_array_t* vertex_ids_{}; + cugraph_type_erased_device_array_t* in_degrees_{}; + cugraph_type_erased_device_array_t* out_degrees_{}; +}; + +} // namespace c_api +} // namespace cugraph diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 1e5d0489b1f..479a73a9a82 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -683,6 +683,7 @@ if(BUILD_CUGRAPH_MG_TESTS) ConfigureCTestMG(MG_CAPI_SIMILARITY_TEST c_api/mg_similarity_test.c) 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_EGONET_TEST c_api/mg_egonet_test.c) ConfigureCTestMG(MG_CAPI_TWO_HOP_NEIGHBORS_TEST c_api/mg_two_hop_neighbors_test.c) @@ -750,6 +751,7 @@ ConfigureCTest(CAPI_CORE_NUMBER_TEST c_api/core_number_test.c) 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_EGONET_TEST c_api/egonet_test.c) ConfigureCTest(CAPI_TWO_HOP_NEIGHBORS_TEST c_api/two_hop_neighbors_test.c) ConfigureCTest(CAPI_LEGACY_K_TRUSS_TEST c_api/legacy_k_truss_test.c) diff --git a/cpp/tests/c_api/degrees_test.c b/cpp/tests/c_api/degrees_test.c new file mode 100644 index 00000000000..0328ac60182 --- /dev/null +++ b/cpp/tests/c_api/degrees_test.c @@ -0,0 +1,131 @@ +/* + * 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 +#include + +#include + +typedef int32_t vertex_t; +typedef int32_t edge_t; +typedef float weight_t; + +/* + * Simple check of creating a graph from a COO on device memory. + */ +int generic_degrees_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, + edge_t *h_in_degrees, + edge_t *h_out_degrees) +{ + 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; + cugraph_degrees_result_t* result = NULL; + + handle = cugraph_create_resource_handle(NULL); + TEST_ASSERT(test_ret_value, handle != NULL, "resource handle creation failed."); + + ret_code = create_test_graph( + handle, h_src, h_dst, h_wgt, num_edges, store_transposed, 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)); + + ret_code = cugraph_degrees( + handle, graph, FALSE, &result, &ret_error); + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_induced_subgraph failed."); + + cugraph_type_erased_device_array_view_t* result_vertices; + cugraph_type_erased_device_array_view_t* result_in_degrees; + cugraph_type_erased_device_array_view_t* result_out_degrees; + + result_vertices = cugraph_degrees_result_get_vertices(result); + result_in_degrees = cugraph_degrees_result_get_in_degrees(result); + result_out_degrees = cugraph_degrees_result_get_out_degrees(result); + + size_t num_result_vertices = cugraph_type_erased_device_array_view_size(result_vertices); + + vertex_t h_result_vertices[num_result_vertices]; + edge_t h_result_in_degrees[num_result_vertices]; + edge_t h_result_out_degrees[num_result_vertices]; + + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_vertices, result_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( + handle, (byte_t*)h_result_in_degrees, result_in_degrees, &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( + handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + + TEST_ASSERT(test_ret_value, num_result_vertices == num_vertices, "results not the same size"); + + for (size_t i = 0; (i < num_result_vertices) && (test_ret_value == 0); ++i) { + TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); + TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + } + + cugraph_degrees_result_free(result); + cugraph_graph_free(graph); + cugraph_error_free(ret_error); + + return test_ret_value; +} + +int test_degrees() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {1, 2, 0, 2, 1, 2}; + vertex_t h_out_degrees[] = {1, 2, 3, 1, 1, 0}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + FALSE, + h_in_degrees, + h_out_degrees); +} + +/******************************************************************************/ + +int main(int argc, char** argv) +{ + int result = 0; + result |= RUN_TEST(test_degrees); + return result; +} diff --git a/cpp/tests/c_api/mg_degrees_test.c b/cpp/tests/c_api/mg_degrees_test.c new file mode 100644 index 00000000000..8ba942c313d --- /dev/null +++ b/cpp/tests/c_api/mg_degrees_test.c @@ -0,0 +1,134 @@ +/* + * 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 +#include + +#include + +typedef int32_t vertex_t; +typedef int32_t edge_t; +typedef float weight_t; + +/* + * Simple check of creating a graph from a COO on device memory. + */ +int generic_degrees_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, + edge_t* h_in_degrees, + edge_t* h_out_degrees) +{ + int test_ret_value = 0; + + cugraph_error_code_t ret_code = CUGRAPH_SUCCESS; + cugraph_error_t* ret_error; + + cugraph_graph_t* graph = NULL; + cugraph_degrees_result_t* result = NULL; + + ret_code = create_mg_test_graph( + handle, h_src, h_dst, h_wgt, num_edges, store_transposed, 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)); + + ret_code = cugraph_degrees( + handle, graph, FALSE, &result, &ret_error); + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + + cugraph_type_erased_device_array_view_t* result_vertices; + cugraph_type_erased_device_array_view_t* result_in_degrees; + cugraph_type_erased_device_array_view_t* result_out_degrees; + + result_vertices = cugraph_degrees_result_get_vertices(result); + result_in_degrees = cugraph_degrees_result_get_in_degrees(result); + result_out_degrees = cugraph_degrees_result_get_out_degrees(result); + + size_t num_result_vertices = cugraph_type_erased_device_array_view_size(result_vertices); + + vertex_t h_result_vertices[num_result_vertices]; + edge_t h_result_in_degrees[num_result_vertices]; + edge_t h_result_out_degrees[num_result_vertices]; + + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_vertices, result_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( + handle, (byte_t*)h_result_in_degrees, result_in_degrees, &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( + handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + + for (size_t i = 0; (i < num_result_vertices) && (test_ret_value == 0); ++i) { + TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); + TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + } + + cugraph_degrees_result_free(result); + cugraph_graph_free(graph); + cugraph_error_free(ret_error); + return test_ret_value; +} + +int test_degrees(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {1, 2, 0, 2, 1, 2}; + vertex_t h_out_degrees[] = {1, 2, 3, 1, 1, 0}; + + // Pagerank wants store_transposed = TRUE + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + TRUE, + h_in_degrees, + h_out_degrees); +} + +/******************************************************************************/ + +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_degrees, handle); + + cugraph_free_resource_handle(handle); + free_mg_raft_handle(raft_handle); + + return result; +} From a1dfaf396388c306d8c0f1a12c79bed9ecbd13d1 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Mon, 4 Mar 2024 15:03:06 -0800 Subject: [PATCH 02/21] add symmetric test --- cpp/src/c_api/degrees_result.cpp | 7 +++++-- cpp/tests/c_api/degrees_test.c | 28 +++++++++++++++++++++++++++- cpp/tests/c_api/mg_degrees_test.c | 30 +++++++++++++++++++++++++++++- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/cpp/src/c_api/degrees_result.cpp b/cpp/src/c_api/degrees_result.cpp index fbb532bcb29..8e8e5a704d9 100644 --- a/cpp/src/c_api/degrees_result.cpp +++ b/cpp/src/c_api/degrees_result.cpp @@ -41,8 +41,11 @@ extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_o { auto internal_pointer = reinterpret_cast(degrees_result); - return reinterpret_cast( - internal_pointer->out_degrees_->view()); + return internal_pointer->out_degrees_ == nullptr + ? reinterpret_cast( + internal_pointer->in_degrees_->view()) + : reinterpret_cast( + internal_pointer->out_degrees_->view()); } extern "C" void cugraph_degrees_result_free(cugraph_degrees_result_t* degrees_result) diff --git a/cpp/tests/c_api/degrees_test.c b/cpp/tests/c_api/degrees_test.c index 0328ac60182..1c689ed5f8d 100644 --- a/cpp/tests/c_api/degrees_test.c +++ b/cpp/tests/c_api/degrees_test.c @@ -34,6 +34,7 @@ int generic_degrees_test(vertex_t* h_src, size_t num_vertices, size_t num_edges, bool_t store_transposed, + bool_t is_symmetric, edge_t *h_in_degrees, edge_t *h_out_degrees) { @@ -50,7 +51,7 @@ int generic_degrees_test(vertex_t* h_src, TEST_ASSERT(test_ret_value, handle != NULL, "resource handle creation failed."); ret_code = create_test_graph( - handle, h_src, h_dst, h_wgt, num_edges, store_transposed, FALSE, FALSE, &graph, &ret_error); + handle, h_src, h_dst, h_wgt, num_edges, store_transposed, FALSE, is_symmetric, &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)); @@ -117,6 +118,30 @@ int test_degrees() num_vertices, num_edges, FALSE, + FALSE, + h_in_degrees, + h_out_degrees); +} + +int test_degrees_symmetric() +{ + size_t num_edges = 16; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 1, 3, 4, 0, 1, 3, 5, 5}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 0, 1, 1, 2, 2, 2, 3, 4}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f, + 0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {2, 4, 3, 3, 2, 2}; + vertex_t h_out_degrees[] = {2, 4, 3, 3, 2, 2}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + FALSE, + TRUE, h_in_degrees, h_out_degrees); } @@ -127,5 +152,6 @@ int main(int argc, char** argv) { int result = 0; result |= RUN_TEST(test_degrees); + result |= RUN_TEST(test_degrees_symmetric); return result; } diff --git a/cpp/tests/c_api/mg_degrees_test.c b/cpp/tests/c_api/mg_degrees_test.c index 8ba942c313d..1ce8bb6f147 100644 --- a/cpp/tests/c_api/mg_degrees_test.c +++ b/cpp/tests/c_api/mg_degrees_test.c @@ -35,6 +35,7 @@ int generic_degrees_test(const cugraph_resource_handle_t* handle, size_t num_vertices, size_t num_edges, bool_t store_transposed, + bool_t is_symmetric, edge_t* h_in_degrees, edge_t* h_out_degrees) { @@ -47,7 +48,7 @@ int generic_degrees_test(const cugraph_resource_handle_t* handle, cugraph_degrees_result_t* result = NULL; ret_code = create_mg_test_graph( - handle, h_src, h_dst, h_wgt, num_edges, store_transposed, FALSE, &graph, &ret_error); + handle, h_src, h_dst, h_wgt, num_edges, store_transposed, is_symmetric, &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)); @@ -113,6 +114,32 @@ int test_degrees(const cugraph_resource_handle_t* handle) num_vertices, num_edges, TRUE, + FALSE, + h_in_degrees, + h_out_degrees); +} + +int test_degrees_symmetric(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 16; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 1, 3, 4, 0, 1, 3, 5, 5}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 0, 1, 1, 2, 2, 2, 3, 4}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f, + 0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {2, 4, 3, 3, 2, 2}; + vertex_t h_out_degrees[] = {2, 4, 3, 3, 2, 2}; + + // Pagerank wants store_transposed = TRUE + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + TRUE, + TRUE, h_in_degrees, h_out_degrees); } @@ -126,6 +153,7 @@ int main(int argc, char** argv) int result = 0; result |= RUN_MG_TEST(test_degrees, handle); + result |= RUN_MG_TEST(test_degrees_symmetric, handle); cugraph_free_resource_handle(handle); free_mg_raft_handle(raft_handle); From bafa8a3c0abc3f09bb20f051ead9023e734dd007 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Fri, 8 Mar 2024 14:40:15 -0800 Subject: [PATCH 03/21] adding _in and _out variants and source_vertices parameter --- cpp/include/cugraph_c/graph_functions.h | 50 +++- cpp/src/c_api/degrees.cpp | 145 ++++++++-- cpp/src/c_api/degrees_result.cpp | 14 +- cpp/src/c_api/degrees_result.hpp | 1 + .../_cugraph_c/graph_functions.pxd | 52 ++++ python/pylibcugraph/pylibcugraph/degrees.pyx | 273 ++++++++++++++++++ 6 files changed, 509 insertions(+), 26 deletions(-) create mode 100644 python/pylibcugraph/pylibcugraph/degrees.pyx diff --git a/cpp/include/cugraph_c/graph_functions.h b/cpp/include/cugraph_c/graph_functions.h index d97e4d488a2..94b06189796 100644 --- a/cpp/include/cugraph_c/graph_functions.h +++ b/cpp/include/cugraph_c/graph_functions.h @@ -236,6 +236,52 @@ typedef struct { int32_t align_; } cugraph_degrees_result_t; +/** + * @brief Compute in degrees + * + * Compute the in degrees for the vertices in the graph. + * + * @param [in] handle Handle for accessing resources. + * @param [in] graph Pointer to graph + * @param [in] source_vertices Device array of vertices we want to compute in degrees for. + * @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to + * true) + * @param [out] result Opaque pointer to degrees result + * @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_in_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error); + +/** + * @brief Compute out degrees + * + * Compute the out degrees for the vertices in the graph. + * + * @param [in] handle Handle for accessing resources. + * @param [in] graph Pointer to graph + * @param [in] source_vertices Device array of vertices we want to compute out degrees for. + * @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to + * true) + * @param [out] result Opaque pointer to degrees result + * @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_out_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error); + /** * @brief Compute degrees * @@ -243,15 +289,17 @@ typedef struct { * * @param [in] handle Handle for accessing resources. * @param [in] graph Pointer to graph + * @param [in] source_vertices Device array of vertices we want to compute degrees for. * @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to * true) - * @param [out] result Opaque pointer to gathered edgelist result + * @param [out] result Opaque pointer to degrees result * @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_degrees(const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, bool_t do_expensive_check, cugraph_degrees_result_t** result, cugraph_error_t** error); diff --git a/cpp/src/c_api/degrees.cpp b/cpp/src/c_api/degrees.cpp index 0b580b8a81e..536807a2e52 100644 --- a/cpp/src/c_api/degrees.cpp +++ b/cpp/src/c_api/degrees.cpp @@ -33,15 +33,26 @@ namespace { struct degrees_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; cugraph::c_api::cugraph_graph_t* graph_{}; - bool do_expensive_check_{}; + cugraph::c_api::cugraph_type_erased_device_array_view_t const* source_vertices_; + bool in_degrees_{false}; + bool out_degrees_{false}; + bool do_expensive_check_{false}; cugraph::c_api::cugraph_degrees_result_t* result_{}; degrees_functor(cugraph_resource_handle_t const* handle, cugraph_graph_t* graph, + ::cugraph_type_erased_device_array_view_t const* source_vertices, + bool in_degrees, + bool out_degrees, bool do_expensive_check) : abstract_functor(), handle_(*reinterpret_cast(handle)->handle_), graph_(reinterpret_cast(graph)), + source_vertices_( + reinterpret_cast( + source_vertices)), + in_degrees_{in_degrees}, + out_degrees_{out_degrees}, do_expensive_check_(do_expensive_check) { } @@ -66,35 +77,129 @@ struct degrees_functor : public cugraph::c_api::abstract_functor { auto number_map = reinterpret_cast*>(graph_->number_map_); - rmm::device_uvector in_degrees(0, handle_.get_stream()); - rmm::device_uvector out_degrees(0, handle_.get_stream()); - - in_degrees = graph_view.compute_in_degrees(handle_); - if (!graph_view.is_symmetric()) { out_degrees = graph_view.compute_out_degrees(handle_); } - - rmm::device_uvector vertex_ids(graph_view.local_vertex_partition_range_size(), - handle_.get_stream()); - raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); + std::optional> in_degrees{std::nullopt}; + std::optional> out_degrees{std::nullopt}; + + if (in_degrees_ && out_degrees_ && graph_view.is_symmetric()) { + in_degrees = store_transposed ? graph_view.compute_in_degrees(handle_) + : graph_view.compute_out_degrees(handle_); + // out_degrees will be extracted from in_degrees in the result + } else { + if (in_degrees_) in_degrees = graph_view.compute_in_degrees(handle_); + + if (out_degrees_) out_degrees = graph_view.compute_out_degrees(handle_); + } + + rmm::device_uvector vertex_ids(0, handle_.get_stream()); + + if (source_vertices) { + // FIXME: Would be more efficient if graph_view.compute_*_degrees could take a vertex + // subset + vertex_ids.resize(source_vertices_->size, handle_.get_stream()); + raft::copy( + vertex_ids.data(), source_vertices_->data(), vertex_ids.size(), handle_.get_stream()); + + cugraph::renumber_ext_vertices_local( + handle_, + vertex_ids.data(), + vertex_ids.size(), + number_map->data(), + graph_view.local_vertex_partition_range_first(), + graph_view.local_vertex_partition_range_last(), + do_expensive_check_); + + auto vertex_partition = + vertex_partition_device_view_t(vertex_partition_view); + + auto vertices_iter = thrust::make_transform_iterator( + vertex_ids.begin(), + cuda::proclaim_return_type([vertex_partition] __device__(auto v) { + return vertex_partition.local_vertex_partition_offset_from_vertex_nocheck(v); + })); + + if (in_degrees && out_degrees) { + rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle.get_stream()); + rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle.get_stream()); + thrust::gather( + rmm::exec_policy(stream), + vertex_iter, + vertex_iter + vertex_ids.size(), + thrust::make_zip_iterator(in_degrees->begin(), out_degrees->begin()), + thrust::make_zip_iterator(tmp_in_degrees.begin(), tmp_out_degrees.begin())); + in_degrees = std::move(tmp_in_degrees); + out_degrees = std::move(tmp_out_degrees); + } else if (in_degrees) { + rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle.get_stream()); + thrust::gather(rmm::exec_policy(stream), + vertex_iter, + vertex_iter + vertex_ids.size(), + in_degrees->begin(), + tmp_in_degrees.begin()); + in_degrees = std::move(tmp_in_degrees); + } else { + rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle.get_stream()); + thrust::gather(rmm::exec_policy(stream), + vertex_iter, + vertex_iter + vertex_ids.size(), + out_degrees->begin(), + tmp_out_degrees.begin()); + out_degrees = std::move(tmp_out_degrees); + } + } else { + vertex_ids.resize(graph_view.local_vertex_partition_range_size(), handle_.get_stream()); + raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); + } result_ = new cugraph::c_api::cugraph_degrees_result_t{ + graph_view.is_symmetric(), new cugraph::c_api::cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), - new cugraph::c_api::cugraph_type_erased_device_array_t(in_degrees, graph_->edge_type_), - graph_view.is_symmetric() ? nullptr - : new cugraph::c_api::cugraph_type_erased_device_array_t( - out_degrees, graph_->edge_type_)}; + in_degrees + ? new cugraph::c_api::cugraph_type_erased_device_array_t(*in_degrees, graph_->edge_type_) + : nullptr, + out_degrees + ? new cugraph::c_api::cugraph_type_erased_device_array_t(*out_degrees, graph_->edge_type_) + : nullptr}; } } }; } // namespace -extern "C" cugraph_error_code_t cugraph_degrees(const cugraph_resource_handle_t* handle, - cugraph_graph_t* graph, - bool_t do_expensive_check, - cugraph_degrees_result_t** result, - cugraph_error_t** error) +extern "C" cugraph_error_code_t cugraph_in_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error) +{ + degrees_functor functor(handle, graph, source_vertices, true, false, do_expensive_check); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} + +extern "C" cugraph_error_code_t cugraph_out_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error) +{ + degrees_functor functor(handle, graph, source_vertices, false, true, do_expensive_check); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} + +extern "C" cugraph_error_code_t cugraph_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error) { - degrees_functor functor(handle, graph, do_expensive_check); + degrees_functor functor(handle, graph, source_vertices, true, true, do_expensive_check); return cugraph::c_api::run_algorithm(graph, functor, result, error); } diff --git a/cpp/src/c_api/degrees_result.cpp b/cpp/src/c_api/degrees_result.cpp index 8e8e5a704d9..a4649e36d05 100644 --- a/cpp/src/c_api/degrees_result.cpp +++ b/cpp/src/c_api/degrees_result.cpp @@ -32,8 +32,10 @@ extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_i { auto internal_pointer = reinterpret_cast(degrees_result); - return reinterpret_cast( - internal_pointer->in_degrees_->view()); + return internal_pointer->in_degrees_ == nullptr + ? nullptr + : reinterpret_cast( + internal_pointer->in_degrees_->view()); } extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_out_degrees( @@ -41,11 +43,13 @@ extern "C" cugraph_type_erased_device_array_view_t* cugraph_degrees_result_get_o { auto internal_pointer = reinterpret_cast(degrees_result); - return internal_pointer->out_degrees_ == nullptr + return internal_pointer->out_degrees_ != nullptr + ? reinterpret_cast( + internal_pointer->out_degrees_->view()) + : internal_pointer->is_symmetric ? reinterpret_cast( internal_pointer->in_degrees_->view()) - : reinterpret_cast( - internal_pointer->out_degrees_->view()); + : nullptr; } extern "C" void cugraph_degrees_result_free(cugraph_degrees_result_t* degrees_result) diff --git a/cpp/src/c_api/degrees_result.hpp b/cpp/src/c_api/degrees_result.hpp index e7f0ad8c996..c6e9bffa5a1 100644 --- a/cpp/src/c_api/degrees_result.hpp +++ b/cpp/src/c_api/degrees_result.hpp @@ -22,6 +22,7 @@ namespace cugraph { namespace c_api { struct cugraph_degrees_result_t { + bool is_symmetric{false}; cugraph_type_erased_device_array_t* vertex_ids_{}; cugraph_type_erased_device_array_t* in_degrees_{}; cugraph_type_erased_device_array_t* out_degrees_{}; diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd index 90bc041e5f0..b9fa14f5546 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd @@ -182,3 +182,55 @@ cdef extern from "cugraph_c/graph_functions.h": cugraph_induced_subgraph_result_t** result, cugraph_error_t** error ) + + ########################################################################### + # degrees + ctypedef struct cugraph_degrees_result_t: + pass + + cdef cugraph_error_code_t \ + cugraph_in_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error + ) + + cdef cugraph_error_code_t \ + cugraph_out_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error + ) + + cdef cugraph_error_code_t \ + cugraph_degrees( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + bool_t do_expensive_check, + cugraph_degrees_result_t** result, + cugraph_error_t** error + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_degrees_result_get_vertices( + cugraph_degrees_result_t* degrees_result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_degrees_result_get_in_degrees( + cugraph_degrees_result_t* degrees_result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_degrees_result_get_out_degrees( + cugraph_degrees_result_t* degrees_result + ) + + cdef void \ + cugraph_degrees_result_free( + cugraph_degrees_result_t* degrees_result + ) diff --git a/python/pylibcugraph/pylibcugraph/degrees.pyx b/python/pylibcugraph/pylibcugraph/degrees.pyx new file mode 100644 index 00000000000..22309c31f76 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/degrees.pyx @@ -0,0 +1,273 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport uintptr_t + +from pylibcugraph._cugraph_c.resource_handle cimport ( + bool_t, + data_type_id_t, + cugraph_resource_handle_t, +) +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) +from pylibcugraph._cugraph_c.array cimport ( + cugraph_type_erased_device_array_view_t, + cugraph_type_erased_device_array_view_create, + cugraph_type_erased_device_array_view_free, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) +from pylibcugraph._cugraph_c.centrality_algorithms cimport ( + cugraph_centrality_result_t, + cugraph_eigenvector_centrality, + cugraph_centrality_result_get_vertices, + cugraph_centrality_result_get_values, + cugraph_centrality_result_free, +) +from pylibcugraph.resource_handle cimport ( + ResourceHandle, +) +from pylibcugraph.graphs cimport ( + _GPUGraph, +) +from pylibcugraph.utils cimport ( + assert_success, + copy_to_cupy_array, + assert_CAI_type, + get_c_type_from_numpy_type, +) + + +#in degrees +#out degrees +#degrees [both] +def in_degrees(ResourceHandle resource_handle, + _GPUGraph graph, + bool_t do_expensive_check): + """ + Compute the in degrees for the nodes of the graph. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph : SGGraph or MGGraph + The input graph, for either Single or Multi-GPU operations. + + do_expensive_check : bool_t + A flag to run expensive checks for input arguments if True. + + Returns + ------- + A tuple of device arrays, where the first item in the tuple is a device + array containing the vertices, the second item in the tuple is a device + array containing the in degrees for the vertices. + + Examples + -------- + >>> import pylibcugraph, cupy, numpy + >>> srcs = cupy.asarray([0, 1, 2], dtype=numpy.int32) + >>> dsts = cupy.asarray([1, 2, 3], dtype=numpy.int32) + >>> weights = cupy.asarray([1.0, 1.0, 1.0], dtype=numpy.float32) + >>> resource_handle = pylibcugraph.ResourceHandle() + >>> graph_props = pylibcugraph.GraphProperties( + ... is_symmetric=False, is_multigraph=False) + >>> G = pylibcugraph.SGGraph( + ... resource_handle, graph_props, srcs, dsts, weight_array=weights, + ... store_transposed=True, renumber=False, do_expensive_check=False) + >>> (vertices, in_degrees) = pylibcugraph.in_degrees( + resource_handle, G, False) + + """ + + cdef cugraph_resource_handle_t* c_resource_handle_ptr = \ + resource_handle.c_resource_handle_ptr + cdef cugraph_graph_t* c_graph_ptr = graph.c_graph_ptr + + cdef cugraph_degrees_result_t* result_ptr + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_in_degrees(c_resource_handle_ptr, + c_graph_ptr, + do_expensive_check, + &result_ptr, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_in_degrees") + + # Extract individual device array pointers from result and copy to cupy + # arrays for returning. + cdef cugraph_type_erased_device_array_view_t* vertices_ptr = \ + cugraph_degrees_result_get_vertices(result_ptr) + cdef cugraph_type_erased_device_array_view_t* in_degrees_ptr = \ + cugraph_degrees_result_get_in_degrees(result_ptr) + + cupy_vertices = copy_to_cupy_array(c_resource_handle_ptr, vertices_ptr) + cupy_in_degrees = copy_to_cupy_array(c_resource_handle_ptr, in_degrees_ptr) + + cugraph_degrees_result_free(result_ptr) + + return (cupy_vertices, cupy_in_degrees) + +def out_degrees(ResourceHandle resource_handle, + _GPUGraph graph, + bool_t do_expensive_check): + """ + Compute the out degrees for the nodes of the graph. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph : SGGraph or MGGraph + The input graph, for either Single or Multi-GPU operations. + + do_expensive_check : bool_t + A flag to run expensive checks for input arguments if True. + + Returns + ------- + A tuple of device arrays, where the first item in the tuple is a device + array containing the vertices, the second item in the tuple is a device + array containing the out degrees for the vertices. + + Examples + -------- + >>> import pylibcugraph, cupy, numpy + >>> srcs = cupy.asarray([0, 1, 2], dtype=numpy.int32) + >>> dsts = cupy.asarray([1, 2, 3], dtype=numpy.int32) + >>> weights = cupy.asarray([1.0, 1.0, 1.0], dtype=numpy.float32) + >>> resource_handle = pylibcugraph.ResourceHandle() + >>> graph_props = pylibcugraph.GraphProperties( + ... is_symmetric=False, is_multigraph=False) + >>> G = pylibcugraph.SGGraph( + ... resource_handle, graph_props, srcs, dsts, weight_array=weights, + ... store_transposed=True, renumber=False, do_expensive_check=False) + >>> (vertices, out_degrees) = pylibcugraph.out_degrees( + resource_handle, G, False) + + """ + + cdef cugraph_resource_handle_t* c_resource_handle_ptr = \ + resource_handle.c_resource_handle_ptr + cdef cugraph_graph_t* c_graph_ptr = graph.c_graph_ptr + + cdef cugraph_degrees_result_t* result_ptr + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_out_degrees(c_resource_handle_ptr, + c_graph_ptr, + do_expensive_check, + &result_ptr, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_out_degrees") + + # Extract individual device array pointers from result and copy to cupy + # arrays for returning. + cdef cugraph_type_erased_device_array_view_t* vertices_ptr = \ + cugraph_degrees_result_get_vertices(result_ptr) + cdef cugraph_type_erased_device_array_view_t* out_degrees_ptr = \ + cugraph_degrees_result_get_out_degrees(result_ptr) + + cupy_vertices = copy_to_cupy_array(c_resource_handle_ptr, vertices_ptr) + cupy_out_degrees = copy_to_cupy_array(c_resource_handle_ptr, out_degrees_ptr) + + cugraph_degrees_result_free(result_ptr) + + return (cupy_vertices, cupy_out_degrees) + + +def degrees(ResourceHandle resource_handle, + _GPUGraph graph, + bool_t do_expensive_check): + """ + Compute the degrees for the nodes of the graph. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph : SGGraph or MGGraph + The input graph, for either Single or Multi-GPU operations. + + do_expensive_check : bool_t + A flag to run expensive checks for input arguments if True. + + Returns + ------- + A tuple of device arrays, where the first item in the tuple is a device + array containing the vertices, the second item in the tuple is a device + array containing the in degrees for the vertices, the third item in the + tuple is a device array containing the out degrees for the vertices. + + Examples + -------- + >>> import pylibcugraph, cupy, numpy + >>> srcs = cupy.asarray([0, 1, 2], dtype=numpy.int32) + >>> dsts = cupy.asarray([1, 2, 3], dtype=numpy.int32) + >>> weights = cupy.asarray([1.0, 1.0, 1.0], dtype=numpy.float32) + >>> resource_handle = pylibcugraph.ResourceHandle() + >>> graph_props = pylibcugraph.GraphProperties( + ... is_symmetric=False, is_multigraph=False) + >>> G = pylibcugraph.SGGraph( + ... resource_handle, graph_props, srcs, dsts, weight_array=weights, + ... store_transposed=True, renumber=False, do_expensive_check=False) + >>> (vertices, in_degrees, out_degrees) = pylibcugraph.degrees( + resource_handle, G, False) + + """ + + cdef cugraph_resource_handle_t* c_resource_handle_ptr = \ + resource_handle.c_resource_handle_ptr + cdef cugraph_graph_t* c_graph_ptr = graph.c_graph_ptr + + cdef cugraph_degrees_result_t* result_ptr + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_degrees(c_resource_handle_ptr, + c_graph_ptr, + do_expensive_check, + &result_ptr, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_degrees") + + # Extract individual device array pointers from result and copy to cupy + # arrays for returning. + cdef cugraph_type_erased_device_array_view_t* vertices_ptr = \ + cugraph_degrees_result_get_vertices(result_ptr) + cdef cugraph_type_erased_device_array_view_t* in_degrees_ptr = \ + cugraph_degrees_result_get_in_degrees(result_ptr) + cdef cugraph_type_erased_device_array_view_t* out_degrees_ptr = \ + cugraph_degrees_result_get_out_degrees(result_ptr) + + cupy_vertices = copy_to_cupy_array(c_resource_handle_ptr, vertices_ptr) + cupy_in_degrees = copy_to_cupy_array(c_resource_handle_ptr, in_degrees_ptr) + cupy_out_degrees = copy_to_cupy_array(c_resource_handle_ptr, out_degrees_ptr) + + cugraph_degrees_result_free(result_ptr) + + return (cupy_vertices, cupy_in_degrees, cupy_out_degrees) From 44b322c876b777f67c6ab2bf5f52d89e56bfb06c Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Fri, 8 Mar 2024 14:40:43 -0800 Subject: [PATCH 04/21] rename degrees to a .cu file --- cpp/src/c_api/{degrees.cpp => degrees.cu} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cpp/src/c_api/{degrees.cpp => degrees.cu} (100%) diff --git a/cpp/src/c_api/degrees.cpp b/cpp/src/c_api/degrees.cu similarity index 100% rename from cpp/src/c_api/degrees.cpp rename to cpp/src/c_api/degrees.cu From 01043258c152dd0ae6168832a1c85855185961a2 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Fri, 8 Mar 2024 14:41:08 -0800 Subject: [PATCH 05/21] rename degrees to a .cu file --- cpp/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 0f9717d4cd7..41dc853e73d 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -411,7 +411,7 @@ endif() add_library(cugraph_c src/c_api/resource_handle.cpp src/c_api/array.cpp - src/c_api/degrees.cpp + src/c_api/degrees.cu src/c_api/degrees_result.cpp src/c_api/error.cpp src/c_api/graph_sg.cpp From 1e8fa24a5974fc6c273fa4ec4827a4b40ded41a9 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Fri, 8 Mar 2024 16:50:11 -0800 Subject: [PATCH 06/21] debugged source_vertices option --- cpp/src/c_api/degrees.cu | 70 +++-- cpp/tests/c_api/c_test_utils.h | 4 +- cpp/tests/c_api/degrees_test.c | 256 ++++++++++++++++- cpp/tests/c_api/mg_degrees_test.c | 269 +++++++++++++++++- cpp/tests/c_api/test_utils.cpp | 14 +- .../_cugraph_c/graph_functions.pxd | 3 + python/pylibcugraph/pylibcugraph/degrees.pyx | 21 +- 7 files changed, 579 insertions(+), 58 deletions(-) diff --git a/cpp/src/c_api/degrees.cu b/cpp/src/c_api/degrees.cu index 536807a2e52..d6481efa905 100644 --- a/cpp/src/c_api/degrees.cu +++ b/cpp/src/c_api/degrees.cu @@ -14,17 +14,21 @@ * limitations under the License. */ +#include "c_api/abstract_functor.hpp" +#include "c_api/degrees_result.hpp" +#include "c_api/graph.hpp" +#include "c_api/resource_handle.hpp" +#include "c_api/utils.hpp" + #include #include +#include #include #include +#include -#include -#include -#include -#include -#include +#include #include @@ -92,14 +96,21 @@ struct degrees_functor : public cugraph::c_api::abstract_functor { rmm::device_uvector vertex_ids(0, handle_.get_stream()); - if (source_vertices) { + if (source_vertices_) { // FIXME: Would be more efficient if graph_view.compute_*_degrees could take a vertex // subset - vertex_ids.resize(source_vertices_->size, handle_.get_stream()); - raft::copy( - vertex_ids.data(), source_vertices_->data(), vertex_ids.size(), handle_.get_stream()); + vertex_ids.resize(source_vertices_->size_, handle_.get_stream()); + raft::copy(vertex_ids.data(), + source_vertices_->as_type(), + vertex_ids.size(), + handle_.get_stream()); + + if constexpr (multi_gpu) { + vertex_ids = cugraph::detail::shuffle_ext_vertices_to_local_gpu_by_vertex_partitioning( + handle_, std::move(vertex_ids)); + } - cugraph::renumber_ext_vertices_local( + cugraph::renumber_ext_vertices( handle_, vertex_ids.data(), vertex_ids.size(), @@ -108,8 +119,8 @@ struct degrees_functor : public cugraph::c_api::abstract_functor { graph_view.local_vertex_partition_range_last(), do_expensive_check_); - auto vertex_partition = - vertex_partition_device_view_t(vertex_partition_view); + auto vertex_partition = cugraph::vertex_partition_device_view_t( + graph_view.local_vertex_partition_view()); auto vertices_iter = thrust::make_transform_iterator( vertex_ids.begin(), @@ -118,33 +129,42 @@ struct degrees_functor : public cugraph::c_api::abstract_functor { })); if (in_degrees && out_degrees) { - rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle.get_stream()); - rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle.get_stream()); + rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle_.get_stream()); + rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle_.get_stream()); thrust::gather( - rmm::exec_policy(stream), - vertex_iter, - vertex_iter + vertex_ids.size(), + handle_.get_thrust_policy(), + vertices_iter, + vertices_iter + vertex_ids.size(), thrust::make_zip_iterator(in_degrees->begin(), out_degrees->begin()), thrust::make_zip_iterator(tmp_in_degrees.begin(), tmp_out_degrees.begin())); in_degrees = std::move(tmp_in_degrees); out_degrees = std::move(tmp_out_degrees); } else if (in_degrees) { - rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle.get_stream()); - thrust::gather(rmm::exec_policy(stream), - vertex_iter, - vertex_iter + vertex_ids.size(), + rmm::device_uvector tmp_in_degrees(vertex_ids.size(), handle_.get_stream()); + thrust::gather(handle_.get_thrust_policy(), + vertices_iter, + vertices_iter + vertex_ids.size(), in_degrees->begin(), tmp_in_degrees.begin()); in_degrees = std::move(tmp_in_degrees); } else { - rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle.get_stream()); - thrust::gather(rmm::exec_policy(stream), - vertex_iter, - vertex_iter + vertex_ids.size(), + rmm::device_uvector tmp_out_degrees(vertex_ids.size(), handle_.get_stream()); + thrust::gather(handle_.get_thrust_policy(), + vertices_iter, + vertices_iter + vertex_ids.size(), out_degrees->begin(), tmp_out_degrees.begin()); out_degrees = std::move(tmp_out_degrees); } + + cugraph::unrenumber_local_int_vertices( + handle_, + vertex_ids.data(), + vertex_ids.size(), + number_map->data(), + graph_view.local_vertex_partition_range_first(), + graph_view.local_vertex_partition_range_last(), + do_expensive_check_); } else { vertex_ids.resize(graph_view.local_vertex_partition_range_size(), handle_.get_stream()); raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); diff --git a/cpp/tests/c_api/c_test_utils.h b/cpp/tests/c_api/c_test_utils.h index ab9fbeccd4b..fbbf6333ee3 100644 --- a/cpp/tests/c_api/c_test_utils.h +++ b/cpp/tests/c_api/c_test_utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -101,6 +101,8 @@ int create_sg_test_graph(const cugraph_resource_handle_t* handle, cugraph_graph_t** graph, cugraph_error_t** ret_error); +size_t cugraph_size_t_allreduce(const cugraph_resource_handle_t* handle, size_t value); + #ifdef __cplusplus } #endif diff --git a/cpp/tests/c_api/degrees_test.c b/cpp/tests/c_api/degrees_test.c index 1c689ed5f8d..10a038b323b 100644 --- a/cpp/tests/c_api/degrees_test.c +++ b/cpp/tests/c_api/degrees_test.c @@ -33,6 +33,10 @@ int generic_degrees_test(vertex_t* h_src, weight_t* h_wgt, size_t num_vertices, size_t num_edges, + vertex_t* h_vertices, + size_t num_vertices_to_compute, + bool_t in_degrees, + bool_t out_degrees, bool_t store_transposed, bool_t is_symmetric, edge_t *h_in_degrees, @@ -56,10 +60,48 @@ int generic_degrees_test(vertex_t* h_src, 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_degrees( - handle, graph, FALSE, &result, &ret_error); - TEST_ASSERT( - test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_induced_subgraph failed."); + if (h_vertices == NULL) { + if (in_degrees && out_degrees) { + ret_code = cugraph_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } else if (in_degrees) { + ret_code = cugraph_in_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } else { + ret_code = cugraph_out_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } + + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + } else { + cugraph_type_erased_device_array_t* vertices = NULL; + cugraph_type_erased_device_array_view_t* vertices_view = NULL; + + ret_code = + cugraph_type_erased_device_array_create(handle, num_vertices_to_compute, INT32, &vertices, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "seeds create failed."); + + vertices_view = cugraph_type_erased_device_array_view(vertices); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host( + handle, vertices_view, (byte_t*)h_vertices, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); + + if (in_degrees && out_degrees) { + ret_code = cugraph_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } else if (in_degrees) { + ret_code = cugraph_in_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } else { + ret_code = cugraph_out_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } + + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + } cugraph_type_erased_device_array_view_t* result_vertices; cugraph_type_erased_device_array_view_t* result_in_degrees; @@ -79,19 +121,32 @@ int generic_degrees_test(vertex_t* h_src, handle, (byte_t*)h_result_vertices, result_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( - handle, (byte_t*)h_result_in_degrees, result_in_degrees, &ret_error); - TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + if (result_in_degrees != NULL) { + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_in_degrees, result_in_degrees, &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( - handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); - TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + if (result_out_degrees != NULL) { + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + } - TEST_ASSERT(test_ret_value, num_result_vertices == num_vertices, "results not the same size"); + if (h_vertices != NULL) { + TEST_ASSERT(test_ret_value, num_result_vertices == num_vertices_to_compute, "results not the same size"); + } else { + TEST_ASSERT(test_ret_value, num_result_vertices == num_vertices, "results not the same size"); + } for (size_t i = 0; (i < num_result_vertices) && (test_ret_value == 0); ++i) { - TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); - TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + if (h_in_degrees != NULL) { + TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); + } + + if (h_out_degrees != NULL) { + TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + } } cugraph_degrees_result_free(result); @@ -117,6 +172,10 @@ int test_degrees() h_wgt, num_vertices, num_edges, + NULL, + 0, + TRUE, + TRUE, FALSE, FALSE, h_in_degrees, @@ -140,12 +199,177 @@ int test_degrees_symmetric() h_wgt, num_vertices, num_edges, + NULL, + 0, + TRUE, + TRUE, + FALSE, + TRUE, + h_in_degrees, + h_out_degrees); +} + +int test_in_degrees() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {1, 2, 0, 2, 1, 2}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + NULL, + 0, + TRUE, + FALSE, + FALSE, + TRUE, + h_in_degrees, + NULL); +} + +int test_out_degrees() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_out_degrees[] = {1, 2, 3, 1, 1, 0}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + NULL, + 0, + FALSE, + TRUE, + FALSE, + TRUE, + NULL, + h_out_degrees); +} + +int test_degrees_subset() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 0, 2, -1, 2}; + vertex_t h_out_degrees[] = {-1, -1, 3, 1, -1, 0}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + TRUE, + TRUE, + FALSE, + FALSE, + h_in_degrees, + h_out_degrees); +} + +int test_degrees_symmetric_subset() +{ + size_t num_edges = 16; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 1, 3, 4, 0, 1, 3, 5, 5}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 0, 1, 1, 2, 2, 2, 3, 4}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f, + 0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 3, 3, -1, 2}; + vertex_t h_out_degrees[] = {-1, -1, 3, 3, -1, 2}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + TRUE, + TRUE, FALSE, TRUE, h_in_degrees, h_out_degrees); } +int test_in_degrees_subset() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 0, 2, -1, 2}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + TRUE, + FALSE, + FALSE, + TRUE, + h_in_degrees, + NULL); +} + +int test_out_degrees_subset() +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_out_degrees[] = {-1, -1, 3, 1, -1, 0}; + + return generic_degrees_test(h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + FALSE, + TRUE, + FALSE, + TRUE, + NULL, + h_out_degrees); +} + /******************************************************************************/ int main(int argc, char** argv) @@ -153,5 +377,11 @@ int main(int argc, char** argv) int result = 0; result |= RUN_TEST(test_degrees); result |= RUN_TEST(test_degrees_symmetric); + result |= RUN_TEST(test_in_degrees); + result |= RUN_TEST(test_out_degrees); + result |= RUN_TEST(test_degrees_subset); + result |= RUN_TEST(test_degrees_symmetric_subset); + result |= RUN_TEST(test_in_degrees_subset); + result |= RUN_TEST(test_out_degrees_subset); return result; } diff --git a/cpp/tests/c_api/mg_degrees_test.c b/cpp/tests/c_api/mg_degrees_test.c index 1ce8bb6f147..3312dd4f5bb 100644 --- a/cpp/tests/c_api/mg_degrees_test.c +++ b/cpp/tests/c_api/mg_degrees_test.c @@ -34,6 +34,10 @@ int generic_degrees_test(const cugraph_resource_handle_t* handle, weight_t* h_wgt, size_t num_vertices, size_t num_edges, + vertex_t* h_vertices, + size_t num_vertices_to_compute, + bool_t in_degrees, + bool_t out_degrees, bool_t store_transposed, bool_t is_symmetric, edge_t* h_in_degrees, @@ -53,10 +57,53 @@ int generic_degrees_test(const cugraph_resource_handle_t* handle, 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_degrees( - handle, graph, FALSE, &result, &ret_error); - TEST_ASSERT( - test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + if (h_vertices == NULL) { + if (in_degrees && out_degrees) { + ret_code = cugraph_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } else if (in_degrees) { + ret_code = cugraph_in_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } else { + ret_code = cugraph_out_degrees( + handle, graph, NULL, FALSE, &result, &ret_error); + } + + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + } else { + cugraph_type_erased_device_array_t* vertices = NULL; + cugraph_type_erased_device_array_view_t* vertices_view = NULL; + + int rank = cugraph_resource_handle_get_rank(handle); + + size_t num_to_allocate = 0; + if (rank == 0) num_to_allocate = num_vertices_to_compute; + + ret_code = + cugraph_type_erased_device_array_create(handle, num_to_allocate, INT32, &vertices, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "seeds create failed."); + + vertices_view = cugraph_type_erased_device_array_view(vertices); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host( + handle, vertices_view, (byte_t*)h_vertices, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); + + if (in_degrees && out_degrees) { + ret_code = cugraph_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } else if (in_degrees) { + ret_code = cugraph_in_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } else { + ret_code = cugraph_out_degrees( + handle, graph, vertices_view, FALSE, &result, &ret_error); + } + + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_extract_degrees failed."); + } cugraph_type_erased_device_array_view_t* result_vertices; cugraph_type_erased_device_array_view_t* result_in_degrees; @@ -76,17 +123,34 @@ int generic_degrees_test(const cugraph_resource_handle_t* handle, handle, (byte_t*)h_result_vertices, result_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( - handle, (byte_t*)h_result_in_degrees, result_in_degrees, &ret_error); - TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + if (result_in_degrees != NULL) { + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_in_degrees, result_in_degrees, &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( - handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); - TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + if (result_out_degrees != NULL) { + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_result_out_degrees, result_out_degrees, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + } + + if (h_vertices != NULL) { + size_t xxx = cugraph_size_t_allreduce(handle, num_result_vertices); + TEST_ASSERT(test_ret_value, cugraph_size_t_allreduce(handle, num_result_vertices) == num_vertices_to_compute, "results not the same size"); + } else { + size_t xxx = cugraph_size_t_allreduce(handle, num_result_vertices); + TEST_ASSERT(test_ret_value, cugraph_size_t_allreduce(handle, num_result_vertices) == num_vertices, "results not the same size"); + } for (size_t i = 0; (i < num_result_vertices) && (test_ret_value == 0); ++i) { - TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); - TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + if (h_in_degrees != NULL) { + TEST_ASSERT(test_ret_value, h_result_in_degrees[i] == h_in_degrees[h_result_vertices[i]], "in degree did not match"); + } + + if (h_out_degrees != NULL) { + TEST_ASSERT(test_ret_value, h_result_out_degrees[i] == h_out_degrees[h_result_vertices[i]], "out degree did not match"); + } } cugraph_degrees_result_free(result); @@ -113,6 +177,10 @@ int test_degrees(const cugraph_resource_handle_t* handle) h_wgt, num_vertices, num_edges, + NULL, + 0, + TRUE, + TRUE, TRUE, FALSE, h_in_degrees, @@ -138,12 +206,183 @@ int test_degrees_symmetric(const cugraph_resource_handle_t* handle) h_wgt, num_vertices, num_edges, + NULL, + 0, + TRUE, + TRUE, + TRUE, + TRUE, + h_in_degrees, + h_out_degrees); +} + +int test_in_degrees(const cugraph_resource_handle_t *handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_in_degrees[] = {1, 2, 0, 2, 1, 2}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + NULL, + 0, + TRUE, + FALSE, + FALSE, + TRUE, + h_in_degrees, + NULL); +} + +int test_out_degrees(const cugraph_resource_handle_t *handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_out_degrees[] = {1, 2, 3, 1, 1, 0}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + NULL, + 0, + FALSE, + TRUE, + FALSE, + TRUE, + NULL, + h_out_degrees); +} + +int test_degrees_subset(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 0, 2, -1, 2}; + vertex_t h_out_degrees[] = {-1, -1, 3, 1, -1, 0}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, TRUE, TRUE, + FALSE, + FALSE, h_in_degrees, h_out_degrees); } +int test_degrees_symmetric_subset(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 16; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4, 1, 3, 4, 0, 1, 3, 5, 5}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5, 0, 1, 1, 2, 2, 2, 3, 4}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f, + 0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 3, 3, -1, 2}; + vertex_t h_out_degrees[] = {-1, -1, 3, 3, -1, 2}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + TRUE, + TRUE, + FALSE, + TRUE, + h_in_degrees, + h_out_degrees); +} + +int test_in_degrees_subset(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_in_degrees[] = {-1, -1, 0, 2, -1, 2}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + TRUE, + FALSE, + FALSE, + TRUE, + h_in_degrees, + NULL); +} + +int test_out_degrees_subset(const cugraph_resource_handle_t* handle) +{ + size_t num_edges = 8; + size_t num_vertices = 6; + size_t num_vertices_to_compute = 3; + + vertex_t h_src[] = {0, 1, 1, 2, 2, 2, 3, 4}; + vertex_t h_dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; + weight_t h_wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; + vertex_t h_vertices[] = {2, 3, 5}; + vertex_t h_out_degrees[] = {-1, -1, 3, 1, -1, 0}; + + return generic_degrees_test(handle, + h_src, + h_dst, + h_wgt, + num_vertices, + num_edges, + h_vertices, + num_vertices_to_compute, + FALSE, + TRUE, + FALSE, + TRUE, + NULL, + h_out_degrees); +} + /******************************************************************************/ int main(int argc, char** argv) @@ -154,6 +393,12 @@ int main(int argc, char** argv) int result = 0; result |= RUN_MG_TEST(test_degrees, handle); result |= RUN_MG_TEST(test_degrees_symmetric, handle); + result |= RUN_MG_TEST(test_in_degrees, handle); + result |= RUN_MG_TEST(test_out_degrees, handle); + result |= RUN_MG_TEST(test_degrees_subset, handle); + result |= RUN_MG_TEST(test_degrees_symmetric_subset, handle); + result |= RUN_MG_TEST(test_in_degrees_subset, handle); + result |= RUN_MG_TEST(test_out_degrees_subset, handle); cugraph_free_resource_handle(handle); free_mg_raft_handle(raft_handle); diff --git a/cpp/tests/c_api/test_utils.cpp b/cpp/tests/c_api/test_utils.cpp index e37cc4555dd..3013cbb7cc6 100644 --- a/cpp/tests/c_api/test_utils.cpp +++ b/cpp/tests/c_api/test_utils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2023, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -15,6 +15,9 @@ */ #include "c_test_utils.h" +#include "c_api/resource_handle.hpp" + +#include #include @@ -388,3 +391,12 @@ int create_sg_test_graph(const cugraph_resource_handle_t* handle, return test_ret_value; } + +extern "C" size_t cugraph_size_t_allreduce(const cugraph_resource_handle_t* handle, size_t value) +{ + auto internal_handle = reinterpret_cast(handle); + return cugraph::host_scalar_allreduce(internal_handle->handle_->get_comms(), + value, + raft::comms::op_t::SUM, + internal_handle->handle_->get_stream()); +} diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd index b9fa14f5546..6f1ac1f640b 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph_functions.pxd @@ -192,6 +192,7 @@ cdef extern from "cugraph_c/graph_functions.h": cugraph_in_degrees( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, bool_t do_expensive_check, cugraph_degrees_result_t** result, cugraph_error_t** error @@ -201,6 +202,7 @@ cdef extern from "cugraph_c/graph_functions.h": cugraph_out_degrees( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, bool_t do_expensive_check, cugraph_degrees_result_t** result, cugraph_error_t** error @@ -210,6 +212,7 @@ cdef extern from "cugraph_c/graph_functions.h": cugraph_degrees( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* source_vertices, bool_t do_expensive_check, cugraph_degrees_result_t** result, cugraph_error_t** error diff --git a/python/pylibcugraph/pylibcugraph/degrees.pyx b/python/pylibcugraph/pylibcugraph/degrees.pyx index 22309c31f76..1ef138d0af9 100644 --- a/python/pylibcugraph/pylibcugraph/degrees.pyx +++ b/python/pylibcugraph/pylibcugraph/degrees.pyx @@ -54,11 +54,9 @@ from pylibcugraph.utils cimport ( ) -#in degrees -#out degrees -#degrees [both] def in_degrees(ResourceHandle resource_handle, _GPUGraph graph, + source_vertices, bool_t do_expensive_check): """ Compute the in degrees for the nodes of the graph. @@ -72,6 +70,9 @@ def in_degrees(ResourceHandle resource_handle, graph : SGGraph or MGGraph The input graph, for either Single or Multi-GPU operations. + source_vertices : cupy array + The nodes for which we will compute degrees. + do_expensive_check : bool_t A flag to run expensive checks for input arguments if True. @@ -94,7 +95,7 @@ def in_degrees(ResourceHandle resource_handle, ... resource_handle, graph_props, srcs, dsts, weight_array=weights, ... store_transposed=True, renumber=False, do_expensive_check=False) >>> (vertices, in_degrees) = pylibcugraph.in_degrees( - resource_handle, G, False) + resource_handle, G, None, False) """ @@ -129,6 +130,7 @@ def in_degrees(ResourceHandle resource_handle, def out_degrees(ResourceHandle resource_handle, _GPUGraph graph, + source_vertices, bool_t do_expensive_check): """ Compute the out degrees for the nodes of the graph. @@ -142,6 +144,9 @@ def out_degrees(ResourceHandle resource_handle, graph : SGGraph or MGGraph The input graph, for either Single or Multi-GPU operations. + source_vertices : cupy array + The nodes for which we will compute degrees. + do_expensive_check : bool_t A flag to run expensive checks for input arguments if True. @@ -164,7 +169,7 @@ def out_degrees(ResourceHandle resource_handle, ... resource_handle, graph_props, srcs, dsts, weight_array=weights, ... store_transposed=True, renumber=False, do_expensive_check=False) >>> (vertices, out_degrees) = pylibcugraph.out_degrees( - resource_handle, G, False) + resource_handle, G, None, False) """ @@ -200,6 +205,7 @@ def out_degrees(ResourceHandle resource_handle, def degrees(ResourceHandle resource_handle, _GPUGraph graph, + source_vertices, bool_t do_expensive_check): """ Compute the degrees for the nodes of the graph. @@ -213,6 +219,9 @@ def degrees(ResourceHandle resource_handle, graph : SGGraph or MGGraph The input graph, for either Single or Multi-GPU operations. + source_vertices : cupy array + The nodes for which we will compute degrees. + do_expensive_check : bool_t A flag to run expensive checks for input arguments if True. @@ -236,7 +245,7 @@ def degrees(ResourceHandle resource_handle, ... resource_handle, graph_props, srcs, dsts, weight_array=weights, ... store_transposed=True, renumber=False, do_expensive_check=False) >>> (vertices, in_degrees, out_degrees) = pylibcugraph.degrees( - resource_handle, G, False) + resource_handle, G, None, False) """ From 20c69ef53e8a7fa92cee5f29e7aa25bbf239781a Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 07:56:58 -0700 Subject: [PATCH 07/21] add python implementation of degrees centrality leveraging the C and PLC API --- .../graph_implementation/simpleGraph.py | 138 +++++++++++++----- .../pylibcugraph/pylibcugraph/CMakeLists.txt | 1 + python/pylibcugraph/pylibcugraph/__init__.py | 2 + python/pylibcugraph/pylibcugraph/degrees.pyx | 40 ++++- 4 files changed, 136 insertions(+), 45 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index 121a4c6245a..81e501ba31e 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -27,6 +27,9 @@ from pylibcugraph import ( get_two_hop_neighbors as pylibcugraph_get_two_hop_neighbors, select_random_vertices as pylibcugraph_select_random_vertices, + degrees as pylibcugraph_degrees, + in_degrees as pylibcugraph_in_degrees, + out_degrees as pylibcugraph_out_degrees ) from pylibcugraph import ( @@ -854,6 +857,97 @@ def number_of_edges(self, directed_edges=False): raise ValueError("Graph is Empty") return self.properties.edge_count + + def degrees_function(self, vertex_subset=None, degree_type="in_degree"): + """ + Compute vertex in-degree, out-degree, degree and degrees. + + 1) Vertex in-degree is the number of edges pointing into the vertex. + 2) Vertex out-degree is the number of edges pointing out from the vertex. + 3) Vertex degree, is the total number of edges incident to a vertex + (both in and out edges) + 4) Vertex degrees computes vertex in-degree and out-degree. + + By default, this method computes vertex in-degree, out-degree, degree + and degrees for the entire set of vertices. If vertex_subset is provided, + this method optionally filters out all but those listed in + vertex_subset. + + Parameters + ---------- + vertex_subset : cudf.Series or iterable container, optional + A container of vertices for displaying corresponding in-degree. + If not set, degrees are computed for the entire set of vertices. + + degree_type : str (default='in_degree') + + Returns + ------- + df : cudf.DataFrame + GPU DataFrame of size N (the default) or the size of the given + vertices (vertex_subset) containing the in_degree, out_degrees, + degree or degrees. The ordering is relative to the adjacency list, + or that given by the specified vertex_subset. + + Examples + -------- + >>> M = cudf.read_csv(datasets_path / 'karate.csv', delimiter=' ', + ... dtype=['int32', 'int32', 'float32'], header=None) + >>> G = cugraph.Graph() + >>> G.from_cudf_edgelist(M, '0', '1') + >>> df = G.degrees_function([0,9,12], "in_degree") + + """ + if vertex_subset is not None: + if not isinstance(vertex_subset, cudf.Series): + vertex_subset = cudf.Series(vertex_subset) + if self.properties.renumbered is True: + vertex_subset = self.renumber_map.to_internal_vertex_id(vertex_subset) + vertex_subset_type = self.edgelist.edgelist_df["src"].dtype + vertex_subset = vertex_subset.astype(vertex_subset_type) + + + do_expensive_check = False + df = cudf.DataFrame() + vertex = None + + if degree_type == "in_degree": + vertex, in_degrees = pylibcugraph_in_degrees( + resource_handle=ResourceHandle(), + graph=self._plc_graph, + source_vertices=vertex_subset, + do_expensive_check=do_expensive_check, + ) + df["degree"] = in_degrees + if degree_type == "out_degree": + vertex, out_degrees = pylibcugraph_out_degrees( + resource_handle=ResourceHandle(), + graph=self._plc_graph, + source_vertices=vertex_subset, + do_expensive_check=do_expensive_check, + ) + df["degree"] = out_degrees + if degree_type in ["degree", "degrees"]: + vertex, in_degrees, out_degrees = pylibcugraph_degrees( + resource_handle=ResourceHandle(), + graph=self._plc_graph, + source_vertices=vertex_subset, + do_expensive_check=do_expensive_check, + ) + if degree_type == "degrees": + df["in_degree"] = in_degrees + df["out_degree"] = out_degrees + + else: + df["degree"] = in_degrees + out_degrees + + df["vertex"] = vertex + if self.properties.renumbered is True: + df = self.renumber_map.unrenumber(df, "vertex") + + return df + + def in_degree(self, vertex_subset=None): """ Compute vertex in-degree. Vertex in-degree is the number of edges @@ -892,9 +986,9 @@ def in_degree(self, vertex_subset=None): >>> df = G.in_degree([0,9,12]) """ - in_degree = self._degree(vertex_subset, direction=Direction.IN) + return self.degrees_function(vertex_subset, "in_degree") - return in_degree + def out_degree(self, vertex_subset=None): """ @@ -934,8 +1028,7 @@ def out_degree(self, vertex_subset=None): >>> df = G.out_degree([0,9,12]) """ - out_degree = self._degree(vertex_subset, direction=Direction.OUT) - return out_degree + return self.degrees_function(vertex_subset, "out_degree") def degree(self, vertex_subset=None): """ @@ -976,7 +1069,7 @@ def degree(self, vertex_subset=None): >>> subset_df = G.degree([0,9,12]) """ - return self._degree(vertex_subset) + return self.degrees_function(vertex_subset, "degree") # FIXME: vertex_subset could be a DataFrame for multi-column vertices def degrees(self, vertex_subset=None): @@ -1019,40 +1112,7 @@ def degrees(self, vertex_subset=None): >>> df = G.degrees([0,9,12]) """ - ( - vertex_col, - in_degree_col, - out_degree_col, - ) = graph_primtypes_wrapper._degrees(self) - - df = cudf.DataFrame() - df["vertex"] = vertex_col - df["in_degree"] = in_degree_col - df["out_degree"] = out_degree_col - - if self.properties.renumbered: - # Get the internal vertex IDs - nodes = self.renumber_map.df_internal_to_external["id"] - else: - nodes = self.nodes() - # If the vertex IDs are not contiguous, remove results for the - # isolated vertices - df = df[df["vertex"].isin(nodes.to_cupy())] - - if vertex_subset is not None: - if not isinstance(vertex_subset, cudf.Series): - vertex_subset = cudf.Series(vertex_subset) - if self.properties.renumbered: - vertex_subset = self.renumber_map.to_internal_vertex_id( - vertex_subset - ) - vertex_subset = vertex_subset.to_cupy() - df = df[df["vertex"].isin(vertex_subset)] - - if self.properties.renumbered: - df = self.renumber_map.unrenumber(df, "vertex") - - return df + return self.degrees_function(vertex_subset, "degrees") def _degree(self, vertex_subset, direction=Direction.ALL): vertex_col, degree_col = graph_primtypes_wrapper._degree(self, direction) diff --git a/python/pylibcugraph/pylibcugraph/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/CMakeLists.txt index c2e22fc1ff7..6d1148de134 100644 --- a/python/pylibcugraph/pylibcugraph/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/CMakeLists.txt @@ -57,6 +57,7 @@ set(cython_sources utils.pyx weakly_connected_components.pyx replicate_edgelist.pyx + degrees.pyx ) set(linked_libraries cugraph::cugraph;cugraph::cugraph_c) diff --git a/python/pylibcugraph/pylibcugraph/__init__.py b/python/pylibcugraph/pylibcugraph/__init__.py index ab518e24cae..dcdef05e106 100644 --- a/python/pylibcugraph/pylibcugraph/__init__.py +++ b/python/pylibcugraph/pylibcugraph/__init__.py @@ -95,6 +95,8 @@ from pylibcugraph.sorensen_coefficients import sorensen_coefficients +from pylibcugraph.degrees import in_degrees, out_degrees, degrees + from pylibcugraph import exceptions diff --git a/python/pylibcugraph/pylibcugraph/degrees.pyx b/python/pylibcugraph/pylibcugraph/degrees.pyx index 1ef138d0af9..2db83cadbc9 100644 --- a/python/pylibcugraph/pylibcugraph/degrees.pyx +++ b/python/pylibcugraph/pylibcugraph/degrees.pyx @@ -33,12 +33,15 @@ from pylibcugraph._cugraph_c.array cimport ( from pylibcugraph._cugraph_c.graph cimport ( cugraph_graph_t, ) -from pylibcugraph._cugraph_c.centrality_algorithms cimport ( - cugraph_centrality_result_t, - cugraph_eigenvector_centrality, - cugraph_centrality_result_get_vertices, - cugraph_centrality_result_get_values, - cugraph_centrality_result_free, +from pylibcugraph._cugraph_c.graph_functions cimport ( + cugraph_degrees_result_t, + cugraph_degrees, + cugraph_in_degrees, + cugraph_out_degrees, + cugraph_degrees_result_get_vertices, + cugraph_degrees_result_get_in_degrees, + cugraph_degrees_result_get_out_degrees, + cugraph_degrees_result_free, ) from pylibcugraph.resource_handle cimport ( ResourceHandle, @@ -51,6 +54,7 @@ from pylibcugraph.utils cimport ( copy_to_cupy_array, assert_CAI_type, get_c_type_from_numpy_type, + create_cugraph_type_erased_device_array_view_from_py_obj, ) @@ -107,8 +111,16 @@ def in_degrees(ResourceHandle resource_handle, cdef cugraph_error_code_t error_code cdef cugraph_error_t* error_ptr + assert_CAI_type(source_vertices, "source_vertices", True) + + cdef cugraph_type_erased_device_array_view_t* \ + source_vertices_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj( + source_vertices) + error_code = cugraph_in_degrees(c_resource_handle_ptr, c_graph_ptr, + source_vertices_ptr, do_expensive_check, &result_ptr, &error_ptr) @@ -181,8 +193,16 @@ def out_degrees(ResourceHandle resource_handle, cdef cugraph_error_code_t error_code cdef cugraph_error_t* error_ptr + assert_CAI_type(source_vertices, "source_vertices", True) + + cdef cugraph_type_erased_device_array_view_t* \ + source_vertices_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj( + source_vertices) + error_code = cugraph_out_degrees(c_resource_handle_ptr, c_graph_ptr, + source_vertices_ptr, do_expensive_check, &result_ptr, &error_ptr) @@ -257,8 +277,16 @@ def degrees(ResourceHandle resource_handle, cdef cugraph_error_code_t error_code cdef cugraph_error_t* error_ptr + assert_CAI_type(source_vertices, "source_vertices", True) + + cdef cugraph_type_erased_device_array_view_t* \ + source_vertices_ptr = \ + create_cugraph_type_erased_device_array_view_from_py_obj( + source_vertices) + error_code = cugraph_degrees(c_resource_handle_ptr, c_graph_ptr, + source_vertices_ptr, do_expensive_check, &result_ptr, &error_ptr) From b2de6fe2096bf347fc538149108f258277d342a4 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:03:12 -0700 Subject: [PATCH 08/21] fix typo --- cpp/src/c_api/abstract_functor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/c_api/abstract_functor.hpp b/cpp/src/c_api/abstract_functor.hpp index 219b1256065..8d3ed11341f 100644 --- a/cpp/src/c_api/abstract_functor.hpp +++ b/cpp/src/c_api/abstract_functor.hpp @@ -27,7 +27,7 @@ namespace c_api { struct abstract_functor { // Move to abstract functor... make operator a void, add cugraph_graph_t * result to functor // try that with instantiation questions - std::unique_ptr error_{std::make_unique("")}; + std::unique_ptr error_ = {std::make_unique("")}; cugraph_error_code_t error_code_{CUGRAPH_SUCCESS}; void unsupported() From b74f58d94b28ec1615cd8cc9e210f7d81aef8d9d Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:12:26 -0700 Subject: [PATCH 09/21] raise an error when an invalid degree type is passed --- .../graph_implementation/simpleGraph.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index 81e501ba31e..8c6870443eb 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -29,7 +29,7 @@ select_random_vertices as pylibcugraph_select_random_vertices, degrees as pylibcugraph_degrees, in_degrees as pylibcugraph_in_degrees, - out_degrees as pylibcugraph_out_degrees + out_degrees as pylibcugraph_out_degrees, ) from pylibcugraph import ( @@ -904,9 +904,11 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): if self.properties.renumbered is True: vertex_subset = self.renumber_map.to_internal_vertex_id(vertex_subset) vertex_subset_type = self.edgelist.edgelist_df["src"].dtype - vertex_subset = vertex_subset.astype(vertex_subset_type) + else: + vertex_subset_type = self.input_df.dtypes[0] + + vertex_subset = vertex_subset.astype(vertex_subset_type) - do_expensive_check = False df = cudf.DataFrame() vertex = None @@ -919,7 +921,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): do_expensive_check=do_expensive_check, ) df["degree"] = in_degrees - if degree_type == "out_degree": + elif degree_type == "out_degree": vertex, out_degrees = pylibcugraph_out_degrees( resource_handle=ResourceHandle(), graph=self._plc_graph, @@ -927,7 +929,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): do_expensive_check=do_expensive_check, ) df["degree"] = out_degrees - if degree_type in ["degree", "degrees"]: + elif degree_type in ["degree", "degrees"]: vertex, in_degrees, out_degrees = pylibcugraph_degrees( resource_handle=ResourceHandle(), graph=self._plc_graph, @@ -940,7 +942,12 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): else: df["degree"] = in_degrees + out_degrees + else: + raise ValueError("Incorrect degree type passed: degree type are ", + "'in_degree', 'out_degree', 'degree' and 'degrees'") + + df["vertex"] = vertex if self.properties.renumbered is True: df = self.renumber_map.unrenumber(df, "vertex") From db2cd485e0396114bb3e3024ba977352dbf122f1 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:16:11 -0700 Subject: [PATCH 10/21] add mg implementation of degree centrality --- .../simpleDistributedGraph.py | 277 +++++++++--------- 1 file changed, 146 insertions(+), 131 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index cdf1e937e67..941ae8b196f 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -28,6 +28,9 @@ GraphProperties, get_two_hop_neighbors as pylibcugraph_get_two_hop_neighbors, select_random_vertices as pylibcugraph_select_random_vertices, + degrees as pylibcugraph_degrees, + in_degrees as pylibcugraph_in_degrees, + out_degrees as pylibcugraph_out_degrees, ) from cugraph.structure import graph_primtypes_wrapper @@ -536,6 +539,145 @@ def number_of_edges(self, directed_edges=False): raise RuntimeError("Graph is Empty") return self.properties.edge_count + def degrees_function(self, vertex_subset=None, degree_type="in_degree"): + """ + Compute vertex in-degree, out-degree, degree and degrees. + + 1) Vertex in-degree is the number of edges pointing into the vertex. + 2) Vertex out-degree is the number of edges pointing out from the vertex. + 3) Vertex degree, is the total number of edges incident to a vertex + (both in and out edges) + 4) Vertex degrees computes vertex in-degree and out-degree. + + By default, this method computes vertex in-degree, out-degree, degree + and degrees for the entire set of vertices. If vertex_subset is provided, + this method optionally filters out all but those listed in + vertex_subset. + + Parameters + ---------- + vertex_subset : cudf.Series or iterable container, optional + A container of vertices for displaying corresponding in-degree. + If not set, degrees are computed for the entire set of vertices. + + degree_type : str (default='in_degree') + + Returns + ------- + df : dask_cudf.DataFrame + GPU DataFrame of size N (the default) or the size of the given + vertices (vertex_subset) containing the in_degree, out_degrees, + degree or degrees. The ordering is relative to the adjacency list, + or that given by the specified vertex_subset. + + Examples + -------- + >>> M = cudf.read_csv(datasets_path / 'karate.csv', delimiter=' ', + ... dtype=['int32', 'int32', 'float32'], header=None) + >>> G = cugraph.Graph() + >>> G.from_cudf_edgelist(M, '0', '1') + >>> df = G.degrees_function([0,9,12], "in_degree") + + """ + _client = default_client() + + def _call_plc_degrees_function( + sID, mg_graph_x, source_vertices, degree_type): + + if degree_type == "in_degree": + results = pylibcugraph_in_degrees( + resource_handle=ResourceHandle(Comms.get_handle(sID).getHandle()), + graph=mg_graph_x, + source_vertices=source_vertices, + do_expensive_check=False, + ) + elif degree_type == "out_degree": + results = pylibcugraph_out_degrees( + resource_handle=ResourceHandle(Comms.get_handle(sID).getHandle()), + graph=mg_graph_x, + source_vertices=source_vertices, + do_expensive_check=False, + ) + elif degree_type in ["degree", "degrees"]: + results = pylibcugraph_degrees( + resource_handle=ResourceHandle(Comms.get_handle(sID).getHandle()), + graph=mg_graph_x, + source_vertices=source_vertices, + do_expensive_check=False, + ) + else: + raise ValueError("Incorrect degree type passed: degree type are ", + "'in_degree', 'out_degree', 'degree' and 'degrees'") + + return results + + if isinstance(vertex_subset, int): + vertex_subset = [vertex_subset] + + if isinstance(vertex_subset, list): + vertex_subset = cudf.Series(vertex_subset) + + if vertex_subset is not None: + if self.renumbered: + vertex_subset = self.renumber_map.to_internal_vertex_id(vertex_subset) + vertex_subset_type = self.edgelist.edgelist_df.dtypes[0] + else: + vertex_subset_type = self.input_df.dtypes[0] + + vertex_subset = vertex_subset.astype(vertex_subset_type) + + cupy_result = [ + _client.submit( + _call_plc_degrees_function, + Comms.get_session_id(), + self._plc_graph[w], + vertex_subset, + degree_type, + workers=[w], + allow_other_workers=False, + ) + for w in Comms.get_workers() + ] + + wait(cupy_result) + + def convert_to_cudf(cp_arrays, degree_type): + """ + Creates a cudf DataFrame from cupy arrays from pylibcugraph wrapper + """ + df = cudf.DataFrame() + df["vertex"] = cp_arrays[0] + if degree_type in ["in_degree", "out_degree"]: + df["degree"] = cp_arrays[1] + # degree_type must be either 'degree' or 'degrees' + else: + if degree_type == "degrees": + df["in_degree"] = cp_arrays[1] + df["out_degree"] = cp_arrays[2] + else: + df["degree"] = cp_arrays[1] + cp_arrays[2] + return df + + cudf_result = [ + _client.submit( + convert_to_cudf, cp_arrays, degree_type, + workers=_client.who_has(cp_arrays)[cp_arrays.key] + ) + for cp_arrays in cupy_result + ] + + wait(cudf_result) + ddf = dask_cudf.from_delayed(cudf_result).persist() + wait(ddf) + + # Wait until the inactive futures are released + wait([(r.release(), c_r.release()) for r, c_r in zip(cupy_result, cudf_result)]) + + if self.properties.renumbered: + ddf = self.renumber_map.unrenumber(ddf, "vertex") + + return ddf + def in_degree(self, vertex_subset=None): """ Compute vertex in-degree. Vertex in-degree is the number of edges @@ -572,59 +714,7 @@ def in_degree(self, vertex_subset=None): >>> df = G.in_degree([0,9,12]) """ - src_col_name = self.source_columns - dst_col_name = self.destination_columns - - # select only the vertex columns - if not isinstance(src_col_name, list) and not isinstance(dst_col_name, list): - vertex_col_names = [src_col_name] + [dst_col_name] - - df = self.input_df[vertex_col_names] - df = df.drop(columns=src_col_name) - - nodes = self.nodes() - if isinstance(nodes, dask_cudf.Series): - nodes = nodes.to_frame() - - if not isinstance(dst_col_name, list): - df = df.rename(columns={dst_col_name: "vertex"}) - dst_col_name = "vertex" - - vertex_col_names = df.columns - nodes.columns = vertex_col_names - - df["degree"] = 1 - - # FIXME: leverage the C++ in_degree for optimal performance - in_degree = ( - df.groupby(dst_col_name) - .degree.count(split_out=df.npartitions) - .reset_index() - ) - - # Add vertices with zero in_degree - in_degree = nodes.merge(in_degree, how="outer").fillna(0) - - # Convert vertex_subset to dataframe. - if vertex_subset is not None: - if not isinstance(vertex_subset, (dask_cudf.DataFrame, cudf.DataFrame)): - if isinstance(vertex_subset, dask_cudf.Series): - vertex_subset = vertex_subset.to_frame() - else: - df = cudf.DataFrame() - if isinstance(vertex_subset, (cudf.Series, list)): - df["vertex"] = vertex_subset - vertex_subset = df - if isinstance(vertex_subset, (dask_cudf.DataFrame, cudf.DataFrame)): - vertex_subset.columns = vertex_col_names - in_degree = in_degree.merge(vertex_subset, how="inner") - else: - raise TypeError( - f"Expected type are: cudf, dask_cudf objects, " - f"iterable container, got " - f"{type(vertex_subset)}" - ) - return in_degree + return self.degrees_function(vertex_subset, "in_degree") def out_degree(self, vertex_subset=None): """ @@ -662,60 +752,7 @@ def out_degree(self, vertex_subset=None): >>> df = G.out_degree([0,9,12]) """ - src_col_name = self.source_columns - dst_col_name = self.destination_columns - - # select only the vertex columns - if not isinstance(src_col_name, list) and not isinstance(dst_col_name, list): - vertex_col_names = [src_col_name] + [dst_col_name] - - df = self.input_df[vertex_col_names] - df = df.drop(columns=dst_col_name) - - nodes = self.nodes() - if isinstance(nodes, dask_cudf.Series): - nodes = nodes.to_frame() - - if not isinstance(src_col_name, list): - df = df.rename(columns={src_col_name: "vertex"}) - src_col_name = "vertex" - - vertex_col_names = df.columns - - nodes.columns = vertex_col_names - - df["degree"] = 1 - # leverage the C++ out_degree for optimal performance - out_degree = ( - df.groupby(src_col_name) - .degree.count(split_out=df.npartitions) - .reset_index() - ) - - # Add vertices with zero out_degree - out_degree = nodes.merge(out_degree, how="outer").fillna(0) - - # Convert vertex_subset to dataframe. - if vertex_subset is not None: - if not isinstance(vertex_subset, (dask_cudf.DataFrame, cudf.DataFrame)): - if isinstance(vertex_subset, dask_cudf.Series): - vertex_subset = vertex_subset.to_frame() - else: - df = cudf.DataFrame() - if isinstance(vertex_subset, (cudf.Series, list)): - df["vertex"] = vertex_subset - vertex_subset = df - if isinstance(vertex_subset, (dask_cudf.DataFrame, cudf.DataFrame)): - vertex_subset.columns = vertex_col_names - out_degree = out_degree.merge(vertex_subset, how="inner") - else: - raise TypeError( - f"Expected type are: cudf, dask_cudf objects, " - f"iterable container, got " - f"{type(vertex_subset)}" - ) - - return out_degree + return self.degrees_function(vertex_subset, "out_degree") def degree(self, vertex_subset=None): """ @@ -754,15 +791,7 @@ def degree(self, vertex_subset=None): """ - vertex_in_degree = self.in_degree(vertex_subset) - vertex_out_degree = self.out_degree(vertex_subset) - # FIXME: leverage the C++ degree for optimal performance - vertex_degree = dask_cudf.concat([vertex_in_degree, vertex_out_degree]) - vertex_degree = vertex_degree.groupby(["vertex"], as_index=False).sum( - split_out=self.input_df.npartitions - ) - - return vertex_degree + return self.degrees_function(vertex_subset, "degree") # FIXME: vertex_subset could be a DataFrame for multi-column vertices def degrees(self, vertex_subset=None): @@ -802,21 +831,7 @@ def degrees(self, vertex_subset=None): >>> df = G.degrees([0,9,12]) """ - raise NotImplementedError("Not supported for distributed graph") - - def _degree(self, vertex_subset, direction=Direction.ALL): - vertex_col, degree_col = graph_primtypes_wrapper._mg_degree(self, direction) - df = cudf.DataFrame() - df["vertex"] = vertex_col - df["degree"] = degree_col - - if self.renumbered is True: - df = self.renumber_map.unrenumber(df, "vertex") - - if vertex_subset is not None: - df = df[df["vertex"].isin(vertex_subset)] - - return df + return self.degrees_function(vertex_subset, "degrees") def get_two_hop_neighbors(self, start_vertices=None): """ From 519e3a86be998dc22769ccc128fc118e28dda4be Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:18:30 -0700 Subject: [PATCH 11/21] unskip single gpu mg tests --- .../cugraph/tests/centrality/test_degree_centrality_mg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py index a46f4b9463b..02de7c3fd28 100644 --- a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py +++ b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py @@ -41,7 +41,6 @@ def setup_function(): @pytest.mark.mg -@pytest.mark.skipif(is_single_gpu(), reason="skipping MG testing on Single GPU system") @pytest.mark.parametrize("directed", IS_DIRECTED) @pytest.mark.parametrize("data_file", DATA_PATH) def test_dask_mg_degree(dask_client, directed, data_file): From db9fdd608ee428307a8c335121ae3018571da145 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:21:48 -0700 Subject: [PATCH 12/21] remove unused import --- .../simpleDistributedGraph.py | 27 ++++++++++--------- .../graph_implementation/simpleGraph.py | 26 +++++++++--------- .../centrality/test_degree_centrality_mg.py | 1 - python/pylibcugraph/pylibcugraph/degrees.pyx | 7 ++--- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 941ae8b196f..4d3d193ec10 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -33,8 +33,6 @@ out_degrees as pylibcugraph_out_degrees, ) -from cugraph.structure import graph_primtypes_wrapper -from cugraph.structure.graph_primtypes_wrapper import Direction from cugraph.structure.number_map import NumberMap from cugraph.structure.symmetrize import symmetrize from cugraph.dask.common.part_utils import ( @@ -542,13 +540,13 @@ def number_of_edges(self, directed_edges=False): def degrees_function(self, vertex_subset=None, degree_type="in_degree"): """ Compute vertex in-degree, out-degree, degree and degrees. - + 1) Vertex in-degree is the number of edges pointing into the vertex. 2) Vertex out-degree is the number of edges pointing out from the vertex. 3) Vertex degree, is the total number of edges incident to a vertex (both in and out edges) 4) Vertex degrees computes vertex in-degree and out-degree. - + By default, this method computes vertex in-degree, out-degree, degree and degrees for the entire set of vertices. If vertex_subset is provided, this method optionally filters out all but those listed in @@ -559,7 +557,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): vertex_subset : cudf.Series or iterable container, optional A container of vertices for displaying corresponding in-degree. If not set, degrees are computed for the entire set of vertices. - + degree_type : str (default='in_degree') Returns @@ -581,8 +579,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): """ _client = default_client() - def _call_plc_degrees_function( - sID, mg_graph_x, source_vertices, degree_type): + def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): if degree_type == "in_degree": results = pylibcugraph_in_degrees( @@ -598,7 +595,7 @@ def _call_plc_degrees_function( source_vertices=source_vertices, do_expensive_check=False, ) - elif degree_type in ["degree", "degrees"]: + elif degree_type in ["degree", "degrees"]: results = pylibcugraph_degrees( resource_handle=ResourceHandle(Comms.get_handle(sID).getHandle()), graph=mg_graph_x, @@ -606,9 +603,11 @@ def _call_plc_degrees_function( do_expensive_check=False, ) else: - raise ValueError("Incorrect degree type passed: degree type are ", - "'in_degree', 'out_degree', 'degree' and 'degrees'") - + raise ValueError( + "Incorrect degree type passed: degree type are ", + "'in_degree', 'out_degree', 'degree' and 'degrees'", + ) + return results if isinstance(vertex_subset, int): @@ -660,8 +659,10 @@ def convert_to_cudf(cp_arrays, degree_type): cudf_result = [ _client.submit( - convert_to_cudf, cp_arrays, degree_type, - workers=_client.who_has(cp_arrays)[cp_arrays.key] + convert_to_cudf, + cp_arrays, + degree_type, + workers=_client.who_has(cp_arrays)[cp_arrays.key], ) for cp_arrays in cupy_result ] diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index 8c6870443eb..b2998783211 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -857,17 +857,16 @@ def number_of_edges(self, directed_edges=False): raise ValueError("Graph is Empty") return self.properties.edge_count - def degrees_function(self, vertex_subset=None, degree_type="in_degree"): """ Compute vertex in-degree, out-degree, degree and degrees. - + 1) Vertex in-degree is the number of edges pointing into the vertex. 2) Vertex out-degree is the number of edges pointing out from the vertex. 3) Vertex degree, is the total number of edges incident to a vertex (both in and out edges) 4) Vertex degrees computes vertex in-degree and out-degree. - + By default, this method computes vertex in-degree, out-degree, degree and degrees for the entire set of vertices. If vertex_subset is provided, this method optionally filters out all but those listed in @@ -878,7 +877,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): vertex_subset : cudf.Series or iterable container, optional A container of vertices for displaying corresponding in-degree. If not set, degrees are computed for the entire set of vertices. - + degree_type : str (default='in_degree') Returns @@ -902,11 +901,13 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): if not isinstance(vertex_subset, cudf.Series): vertex_subset = cudf.Series(vertex_subset) if self.properties.renumbered is True: - vertex_subset = self.renumber_map.to_internal_vertex_id(vertex_subset) + vertex_subset = self.renumber_map.to_internal_vertex_id( + vertex_subset + ) vertex_subset_type = self.edgelist.edgelist_df["src"].dtype else: vertex_subset_type = self.input_df.dtypes[0] - + vertex_subset = vertex_subset.astype(vertex_subset_type) do_expensive_check = False @@ -929,7 +930,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): do_expensive_check=do_expensive_check, ) df["degree"] = out_degrees - elif degree_type in ["degree", "degrees"]: + elif degree_type in ["degree", "degrees"]: vertex, in_degrees, out_degrees = pylibcugraph_degrees( resource_handle=ResourceHandle(), graph=self._plc_graph, @@ -943,10 +944,10 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): else: df["degree"] = in_degrees + out_degrees else: - raise ValueError("Incorrect degree type passed: degree type are ", - "'in_degree', 'out_degree', 'degree' and 'degrees'") - - + raise ValueError( + "Incorrect degree type passed: degree type are ", + "'in_degree', 'out_degree', 'degree' and 'degrees'", + ) df["vertex"] = vertex if self.properties.renumbered is True: @@ -954,7 +955,6 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): return df - def in_degree(self, vertex_subset=None): """ Compute vertex in-degree. Vertex in-degree is the number of edges @@ -995,8 +995,6 @@ def in_degree(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "in_degree") - - def out_degree(self, vertex_subset=None): """ Compute vertex out-degree. Vertex out-degree is the number of edges diff --git a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py index 02de7c3fd28..ecafca53b7e 100644 --- a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py +++ b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py @@ -18,7 +18,6 @@ import cudf import dask_cudf import cugraph -from cugraph.dask.common.mg_utils import is_single_gpu from cugraph.testing.utils import RAPIDS_DATASET_ROOT_DIR_PATH from cudf.testing import assert_series_equal diff --git a/python/pylibcugraph/pylibcugraph/degrees.pyx b/python/pylibcugraph/pylibcugraph/degrees.pyx index 2db83cadbc9..7818da441bd 100644 --- a/python/pylibcugraph/pylibcugraph/degrees.pyx +++ b/python/pylibcugraph/pylibcugraph/degrees.pyx @@ -27,8 +27,6 @@ from pylibcugraph._cugraph_c.error cimport ( ) from pylibcugraph._cugraph_c.array cimport ( cugraph_type_erased_device_array_view_t, - cugraph_type_erased_device_array_view_create, - cugraph_type_erased_device_array_view_free, ) from pylibcugraph._cugraph_c.graph cimport ( cugraph_graph_t, @@ -53,7 +51,6 @@ from pylibcugraph.utils cimport ( assert_success, copy_to_cupy_array, assert_CAI_type, - get_c_type_from_numpy_type, create_cugraph_type_erased_device_array_view_from_py_obj, ) @@ -112,7 +109,7 @@ def in_degrees(ResourceHandle resource_handle, cdef cugraph_error_t* error_ptr assert_CAI_type(source_vertices, "source_vertices", True) - + cdef cugraph_type_erased_device_array_view_t* \ source_vertices_ptr = \ create_cugraph_type_erased_device_array_view_from_py_obj( @@ -194,7 +191,7 @@ def out_degrees(ResourceHandle resource_handle, cdef cugraph_error_t* error_ptr assert_CAI_type(source_vertices, "source_vertices", True) - + cdef cugraph_type_erased_device_array_view_t* \ source_vertices_ptr = \ create_cugraph_type_erased_device_array_view_from_py_obj( From 5e6f83d739a7c755832a13c418d2c113096685d9 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:22:49 -0700 Subject: [PATCH 13/21] update copyright --- .../cugraph/tests/centrality/test_degree_centrality_mg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py index ecafca53b7e..1bef1e0872b 100644 --- a/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py +++ b/python/cugraph/cugraph/tests/centrality/test_degree_centrality_mg.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2023, NVIDIA CORPORATION. +# Copyright (c) 2018-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 From ef3ae342a366d60cdf7af7ac158c9a770ffce4ff Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Sun, 10 Mar 2024 10:23:39 -0700 Subject: [PATCH 14/21] update copyright --- python/cugraph/cugraph/centrality/degree_centrality.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cugraph/cugraph/centrality/degree_centrality.py b/python/cugraph/cugraph/centrality/degree_centrality.py index 12d39f4127e..9a7aecee595 100644 --- a/python/cugraph/cugraph/centrality/degree_centrality.py +++ b/python/cugraph/cugraph/centrality/degree_centrality.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-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 From cadb1193ff9d6b234676283d0f774b868fcdc8b0 Mon Sep 17 00:00:00 2001 From: Charles Hastings Date: Mon, 11 Mar 2024 09:18:52 -0700 Subject: [PATCH 15/21] update copyright date --- python/pylibcugraph/pylibcugraph/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/pylibcugraph/pylibcugraph/CMakeLists.txt b/python/pylibcugraph/pylibcugraph/CMakeLists.txt index 6d1148de134..7cc90145949 100644 --- a/python/pylibcugraph/pylibcugraph/CMakeLists.txt +++ b/python/pylibcugraph/pylibcugraph/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (c) 2022-2023, NVIDIA CORPORATION. +# Copyright (c) 2022-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 From fd56ff8c0b1f8ea95e7c307dbda3fd2ef4775008 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 14:59:50 -0700 Subject: [PATCH 16/21] revert change --- python/cugraph/cugraph/centrality/degree_centrality.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/cugraph/cugraph/centrality/degree_centrality.py b/python/cugraph/cugraph/centrality/degree_centrality.py index 9a7aecee595..12d39f4127e 100644 --- a/python/cugraph/cugraph/centrality/degree_centrality.py +++ b/python/cugraph/cugraph/centrality/degree_centrality.py @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2024, 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. # You may obtain a copy of the License at From e4e45422709e011fac6b02e80cfa4ed1ecfa2292 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 15:08:35 -0700 Subject: [PATCH 17/21] update exception message --- .../graph_implementation/simpleDistributedGraph.py | 8 ++++---- .../cugraph/structure/graph_implementation/simpleGraph.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 4d3d193ec10..3d1931ce35c 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -548,7 +548,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): 4) Vertex degrees computes vertex in-degree and out-degree. By default, this method computes vertex in-degree, out-degree, degree - and degrees for the entire set of vertices. If vertex_subset is provided, + or degrees for the entire set of vertices. If vertex_subset is provided, this method optionally filters out all but those listed in vertex_subset. @@ -604,9 +604,9 @@ def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): ) else: raise ValueError( - "Incorrect degree type passed: degree type are ", - "'in_degree', 'out_degree', 'degree' and 'degrees'", - ) + "Incorrect degree type passed, valid values are ", + f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'" + ) return results diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index b2998783211..e0261827e5f 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -868,7 +868,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): 4) Vertex degrees computes vertex in-degree and out-degree. By default, this method computes vertex in-degree, out-degree, degree - and degrees for the entire set of vertices. If vertex_subset is provided, + or degrees for the entire set of vertices. If vertex_subset is provided, this method optionally filters out all but those listed in vertex_subset. @@ -945,8 +945,8 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): df["degree"] = in_degrees + out_degrees else: raise ValueError( - "Incorrect degree type passed: degree type are ", - "'in_degree', 'out_degree', 'degree' and 'degrees'", + "Incorrect degree type passed, valid values are ", + f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'" ) df["vertex"] = vertex From 62c93f3a9538857b096510297405d9c10fba118c Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 15:14:55 -0700 Subject: [PATCH 18/21] update dtype extraction --- .../structure/graph_implementation/simpleDistributedGraph.py | 5 +++-- .../cugraph/structure/graph_implementation/simpleGraph.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 3d1931ce35c..0637bba574e 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -619,11 +619,12 @@ def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): if vertex_subset is not None: if self.renumbered: vertex_subset = self.renumber_map.to_internal_vertex_id(vertex_subset) - vertex_subset_type = self.edgelist.edgelist_df.dtypes[0] + vertex_subset_type = self.edgelist.edgelist_df.dtypes.iloc[0] else: - vertex_subset_type = self.input_df.dtypes[0] + vertex_subset_type = self.input_df.dtypes.iloc[0] vertex_subset = vertex_subset.astype(vertex_subset_type) + cupy_result = [ _client.submit( diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index e0261827e5f..fb5c18b8b47 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -904,9 +904,9 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): vertex_subset = self.renumber_map.to_internal_vertex_id( vertex_subset ) - vertex_subset_type = self.edgelist.edgelist_df["src"].dtype + vertex_subset_type = self.edgelist.edgelist_df.dtypes.iloc[0] else: - vertex_subset_type = self.input_df.dtypes[0] + vertex_subset_type = self.input_df.dtypes.iloc[0] vertex_subset = vertex_subset.astype(vertex_subset_type) From 219428fe666943385be8cbcf7e9430def65d0f11 Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 15:43:56 -0700 Subject: [PATCH 19/21] add type annotation --- .../simpleDistributedGraph.py | 39 ++++++++----- .../graph_implementation/simpleGraph.py | 56 +++++++------------ 2 files changed, 45 insertions(+), 50 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 0637bba574e..75ef6c1f6b2 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -12,7 +12,7 @@ # limitations under the License. import gc -from typing import Union +from typing import Union, Iterable import warnings import cudf @@ -537,7 +537,11 @@ def number_of_edges(self, directed_edges=False): raise RuntimeError("Graph is Empty") return self.properties.edge_count - def degrees_function(self, vertex_subset=None, degree_type="in_degree"): + def degrees_function( + self, + vertex_subset: Union[cudf.Series, dask_cudf.Series, Iterable] = None, + degree_type: str = "in_degree", + ) -> dask_cudf.DataFrame: """ Compute vertex in-degree, out-degree, degree and degrees. @@ -554,7 +558,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): Parameters ---------- - vertex_subset : cudf.Series or iterable container, optional + vertex_subset : cudf.Series or dask_cudf.Series, iterable container, optional A container of vertices for displaying corresponding in-degree. If not set, degrees are computed for the entire set of vertices. @@ -579,7 +583,9 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): """ _client = default_client() - def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): + def _call_plc_degrees_function( + sID: bytes, mg_graph_x, source_vertices: cudf.Series, degree_type: str + ) -> cp.array: if degree_type == "in_degree": results = pylibcugraph_in_degrees( @@ -604,9 +610,9 @@ def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): ) else: raise ValueError( - "Incorrect degree type passed, valid values are ", - f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'" - ) + "Incorrect degree type passed, valid values are ", + f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'", + ) return results @@ -624,7 +630,6 @@ def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): vertex_subset_type = self.input_df.dtypes.iloc[0] vertex_subset = vertex_subset.astype(vertex_subset_type) - cupy_result = [ _client.submit( @@ -641,7 +646,7 @@ def _call_plc_degrees_function(sID, mg_graph_x, source_vertices, degree_type): wait(cupy_result) - def convert_to_cudf(cp_arrays, degree_type): + def convert_to_cudf(cp_arrays: cp.ndarray, degree_type: bool) -> cudf.DataFrame: """ Creates a cudf DataFrame from cupy arrays from pylibcugraph wrapper """ @@ -680,7 +685,9 @@ def convert_to_cudf(cp_arrays, degree_type): return ddf - def in_degree(self, vertex_subset=None): + def in_degree( + self, vertex_subset: Union[cudf.Series, dask_cudf.Series, Iterable] = None + ) -> dask_cudf.DataFrame: """ Compute vertex in-degree. Vertex in-degree is the number of edges pointing into the vertex. By default, this method computes vertex @@ -718,7 +725,9 @@ def in_degree(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "in_degree") - def out_degree(self, vertex_subset=None): + def out_degree( + self, vertex_subset: Union[cudf.Series, dask_cudf.Series, Iterable] = None + ) -> dask_cudf.DataFrame: """ Compute vertex out-degree. Vertex out-degree is the number of edges pointing out from the vertex. By default, this method computes vertex @@ -756,7 +765,9 @@ def out_degree(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "out_degree") - def degree(self, vertex_subset=None): + def degree( + self, vertex_subset: Union[cudf.Series, dask_cudf.Series, Iterable] = None + ) -> dask_cudf.DataFrame: """ Compute vertex degree, which is the total number of edges incident to a vertex (both in and out edges). By default, this method computes @@ -796,7 +807,9 @@ def degree(self, vertex_subset=None): return self.degrees_function(vertex_subset, "degree") # FIXME: vertex_subset could be a DataFrame for multi-column vertices - def degrees(self, vertex_subset=None): + def degrees( + self, vertex_subset: Union[cudf.Series, dask_cudf.Series, Iterable] = None + ) -> dask_cudf.DataFrame: """ Compute vertex in-degree and out-degree. By default, this method computes vertex degrees for the entire set of vertices. If diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index fb5c18b8b47..49b41752813 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -23,7 +23,7 @@ import numpy as np import warnings from cugraph.dask.structure import replication -from typing import Union, Dict +from typing import Union, Dict, Iterable from pylibcugraph import ( get_two_hop_neighbors as pylibcugraph_get_two_hop_neighbors, select_random_vertices as pylibcugraph_select_random_vertices, @@ -857,7 +857,11 @@ def number_of_edges(self, directed_edges=False): raise ValueError("Graph is Empty") return self.properties.edge_count - def degrees_function(self, vertex_subset=None, degree_type="in_degree"): + def degrees_function( + self, + vertex_subset: Union[cudf.Series, Iterable] = None, + degree_type: str = "in_degree", + ) -> cudf.DataFrame: """ Compute vertex in-degree, out-degree, degree and degrees. @@ -946,7 +950,7 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): else: raise ValueError( "Incorrect degree type passed, valid values are ", - f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'" + f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'", ) df["vertex"] = vertex @@ -955,7 +959,9 @@ def degrees_function(self, vertex_subset=None, degree_type="in_degree"): return df - def in_degree(self, vertex_subset=None): + def in_degree( + self, vertex_subset: Union[cudf.Series, Iterable] = None + ) -> cudf.DataFrame: """ Compute vertex in-degree. Vertex in-degree is the number of edges pointing into the vertex. By default, this method computes vertex @@ -995,7 +1001,9 @@ def in_degree(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "in_degree") - def out_degree(self, vertex_subset=None): + def out_degree( + self, vertex_subset: Union[cudf.Series, Iterable] = None + ) -> cudf.DataFrame: """ Compute vertex out-degree. Vertex out-degree is the number of edges pointing out from the vertex. By default, this method computes vertex @@ -1035,7 +1043,9 @@ def out_degree(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "out_degree") - def degree(self, vertex_subset=None): + def degree( + self, vertex_subset: Union[cudf.Series, Iterable] = None + ) -> cudf.DataFrame: """ Compute vertex degree, which is the total number of edges incident to a vertex (both in and out edges). By default, this method computes @@ -1077,7 +1087,9 @@ def degree(self, vertex_subset=None): return self.degrees_function(vertex_subset, "degree") # FIXME: vertex_subset could be a DataFrame for multi-column vertices - def degrees(self, vertex_subset=None): + def degrees( + self, vertex_subset: Union[cudf.Series, Iterable] = None + ) -> cudf.DataFrame: """ Compute vertex in-degree and out-degree. By default, this method computes vertex degrees for the entire set of vertices. If @@ -1119,36 +1131,6 @@ def degrees(self, vertex_subset=None): """ return self.degrees_function(vertex_subset, "degrees") - def _degree(self, vertex_subset, direction=Direction.ALL): - vertex_col, degree_col = graph_primtypes_wrapper._degree(self, direction) - df = cudf.DataFrame() - df["vertex"] = vertex_col - df["degree"] = degree_col - - if self.properties.renumbered: - # Get the internal vertex IDs - nodes = self.renumber_map.df_internal_to_external["id"] - else: - nodes = self.nodes() - # If the vertex IDs are not contiguous, remove results for the - # isolated vertices - df = df[df["vertex"].isin(nodes.to_cupy())] - - if vertex_subset is not None: - if not isinstance(vertex_subset, cudf.Series): - vertex_subset = cudf.Series(vertex_subset) - if self.properties.renumbered: - vertex_subset = self.renumber_map.to_internal_vertex_id( - vertex_subset - ) - vertex_subset = vertex_subset.to_cupy() - df = df[df["vertex"].isin(vertex_subset)] - - if self.properties.renumbered: - df = self.renumber_map.unrenumber(df, "vertex") - - return df - def _make_plc_graph( self, value_col: Dict[str, cudf.DataFrame] = None, From 6459c1e565241ae5e8901451029e589e369fb5ca Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 15:45:48 -0700 Subject: [PATCH 20/21] remove unused import --- .../cugraph/structure/graph_implementation/simpleGraph.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index 49b41752813..8eb1888036e 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -12,7 +12,6 @@ # limitations under the License. from cugraph.structure import graph_primtypes_wrapper -from cugraph.structure.graph_primtypes_wrapper import Direction from cugraph.structure.symmetrize import symmetrize from cugraph.structure.number_map import NumberMap import cugraph.dask.common.mg_utils as mg_utils From e5a30c1b65443e5bb61dbff2c3dc0229310107ba Mon Sep 17 00:00:00 2001 From: jnke2016 Date: Mon, 11 Mar 2024 15:56:17 -0700 Subject: [PATCH 21/21] fix style --- .../structure/graph_implementation/simpleDistributedGraph.py | 3 ++- .../cugraph/structure/graph_implementation/simpleGraph.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py index 75ef6c1f6b2..0ef5eaf1b9e 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleDistributedGraph.py @@ -611,7 +611,8 @@ def _call_plc_degrees_function( else: raise ValueError( "Incorrect degree type passed, valid values are ", - f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'", + "'in_degree', 'out_degree', 'degree' and 'degrees' ", + f"got '{degree_type}'", ) return results diff --git a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py index 8eb1888036e..99934e02b10 100644 --- a/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py +++ b/python/cugraph/cugraph/structure/graph_implementation/simpleGraph.py @@ -949,9 +949,9 @@ def degrees_function( else: raise ValueError( "Incorrect degree type passed, valid values are ", - f"'in_degree', 'out_degree', 'degree' and 'degrees', got '{degree_type}'", + "'in_degree', 'out_degree', 'degree' and 'degrees' ", + f"got '{degree_type}'", ) - df["vertex"] = vertex if self.properties.renumbered is True: df = self.renumber_map.unrenumber(df, "vertex")