From 99560845f2c8ac83c1e5cac2017b2060ed7169b7 Mon Sep 17 00:00:00 2001 From: Chuck Hastings <45364586+ChuckHastings@users.noreply.github.com> Date: Tue, 5 Apr 2022 21:03:03 -0400 Subject: [PATCH] Define C API for eigenvector centrality (#2180) This PR defines the C API for eigenvector centrality. It is the first of a series of PRs to address #2146 It also does the following: * Renames `cugraph_pagerank_result_t` to `cugraph_centrality_result_t` to allow it to be reused for all centrality algorithms * Reorganizes the code for the results into a separate file * Provides an initial implementation of eigenvector centrality that can be used for testing the API. NOTE: It literally calls pagerank with some hardcoded parameters. The return formats for the algorithms are the same, so this will mimic the API behavior (although the scores will be different once eigenvector centrality is implemented * Provided a C unit test that validates the results (will need to be updated to validate eigenvector centrality results once the real algorithm is in place) Note that this is a breaking change since it modifies the type of the pagerank result. Authors: - Chuck Hastings (https://github.com/ChuckHastings) - Rick Ratzel (https://github.com/rlratzel) Approvers: - Seunghwa Kang (https://github.com/seunghwak) - Rick Ratzel (https://github.com/rlratzel) URL: https://github.com/rapidsai/cugraph/pull/2180 --- cpp/CMakeLists.txt | 2 + cpp/include/cugraph_c/algorithms.h | 58 ++++-- cpp/src/c_api/centrality_result.cpp | 43 +++++ cpp/src/c_api/centrality_result.hpp | 30 ++++ cpp/src/c_api/eigenvector_centrality.cpp | 125 +++++++++++++ cpp/src/c_api/pagerank.cpp | 134 ++++++-------- cpp/tests/CMakeLists.txt | 2 + cpp/tests/c_api/eigenvector_centrality_test.c | 118 ++++++++++++ .../c_api/mg_eigenvector_centrality_test.c | 169 ++++++++++++++++++ cpp/tests/c_api/mg_pagerank_test.c | 10 +- cpp/tests/c_api/pagerank_test.c | 10 +- .../cugraph/cugraph/structure/hypergraph.py | 16 +- .../pylibcugraph/_cugraph_c/algorithms.pxd | 18 +- python/pylibcugraph/pylibcugraph/pagerank.pyx | 16 +- 14 files changed, 617 insertions(+), 134 deletions(-) create mode 100644 cpp/src/c_api/centrality_result.cpp create mode 100644 cpp/src/c_api/centrality_result.hpp create mode 100644 cpp/src/c_api/eigenvector_centrality.cpp create mode 100644 cpp/tests/c_api/eigenvector_centrality_test.c create mode 100644 cpp/tests/c_api/mg_eigenvector_centrality_test.c diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 950e9ddc823..fdf1e86ae22 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -355,6 +355,8 @@ add_library(cugraph_c SHARED src/c_api/graph_sg.cpp src/c_api/graph_mg.cpp src/c_api/pagerank.cpp + src/c_api/centrality_result.cpp + src/c_api/eigenvector_centrality.cpp src/c_api/hits.cpp src/c_api/bfs.cpp src/c_api/sssp.cpp diff --git a/cpp/include/cugraph_c/algorithms.h b/cpp/include/cugraph_c/algorithms.h index cfaa89bb490..987f6d5a687 100644 --- a/cpp/include/cugraph_c/algorithms.h +++ b/cpp/include/cugraph_c/algorithms.h @@ -25,36 +25,36 @@ extern "C" { #endif /** - * @brief Opaque pagerank result type + * @brief Opaque centrality result type */ typedef struct { int32_t align_; -} cugraph_pagerank_result_t; +} cugraph_centrality_result_t; /** - * @brief Get the vertex ids from the pagerank result + * @brief Get the vertex ids from the centrality result * - * @param [in] result The result from pagerank + * @param [in] result The result from a centrality algorithm * @return type erased array of vertex ids */ -cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_vertices( - cugraph_pagerank_result_t* result); +cugraph_type_erased_device_array_view_t* cugraph_centrality_result_get_vertices( + cugraph_centrality_result_t* result); /** - * @brief Get the pagerank values from the pagerank result + * @brief Get the centrality values from a centrality algorithm result * - * @param [in] result The result from pagerank - * @return type erased array of pagerank values + * @param [in] result The result from a centrality algorithm + * @return type erased array view of centrality values */ -cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_pageranks( - cugraph_pagerank_result_t* result); +cugraph_type_erased_device_array_view_t* cugraph_centrality_result_get_values( + cugraph_centrality_result_t* result); /** - * @brief Free pagerank result + * @brief Free centrality result * - * @param [in] result The result from pagerank + * @param [in] result The result from a centrality algorithm */ -void cugraph_pagerank_result_free(cugraph_pagerank_result_t* result); +void cugraph_centrality_result_free(cugraph_centrality_result_t* result); /** * @brief Compute pagerank @@ -90,7 +90,7 @@ cugraph_error_code_t cugraph_pagerank( size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error); /** @@ -136,9 +136,35 @@ cugraph_error_code_t cugraph_personalized_pagerank( size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error); +/** + * @brief Compute eigenvector centrality + * + * Computed using the power method. + * + * @param [in] handle Handle for accessing resources + * @param [in] graph Pointer to graph + * @param [in] epsilon Error tolerance to check convergence. Convergence is measured + * comparing the L1 norm until it is less than epsilon + * @param [in] max_iterations Maximum number of power iterations, will not exceed this number + * of iterations even if we haven't converged + * @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to + * `true`). + * @param [out] result Opaque pointer to eigenvector centrality results + * @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_eigenvector_centrality(const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + double epsilon, + size_t max_iterations, + bool_t do_expensive_check, + cugraph_centrality_result_t** result, + cugraph_error_t** error); + /** * @brief Opaque hits result type */ diff --git a/cpp/src/c_api/centrality_result.cpp b/cpp/src/c_api/centrality_result.cpp new file mode 100644 index 00000000000..20794dff4bc --- /dev/null +++ b/cpp/src/c_api/centrality_result.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2022, 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 + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_centrality_result_get_vertices( + cugraph_centrality_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return reinterpret_cast( + internal_pointer->vertex_ids_->view()); +} + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_centrality_result_get_values( + cugraph_centrality_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return reinterpret_cast( + internal_pointer->values_->view()); +} + +extern "C" void cugraph_centrality_result_free(cugraph_centrality_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + delete internal_pointer->vertex_ids_; + delete internal_pointer->values_; + delete internal_pointer; +} diff --git a/cpp/src/c_api/centrality_result.hpp b/cpp/src/c_api/centrality_result.hpp new file mode 100644 index 00000000000..230ad2f2ab9 --- /dev/null +++ b/cpp/src/c_api/centrality_result.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 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 + +namespace cugraph { +namespace c_api { + +struct cugraph_centrality_result_t { + cugraph_type_erased_device_array_t* vertex_ids_{}; + cugraph_type_erased_device_array_t* values_{}; +}; + +} // namespace c_api +} // namespace cugraph diff --git a/cpp/src/c_api/eigenvector_centrality.cpp b/cpp/src/c_api/eigenvector_centrality.cpp new file mode 100644 index 00000000000..a7e90bff14a --- /dev/null +++ b/cpp/src/c_api/eigenvector_centrality.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022, 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 eigenvector_centrality_functor : public cugraph::c_api::abstract_functor { + raft::handle_t const& handle_; + cugraph::c_api::cugraph_graph_t* graph_{}; + double epsilon_{}; + size_t max_iterations_{}; + bool do_expensive_check_{}; + cugraph::c_api::cugraph_centrality_result_t* result_{}; + + eigenvector_centrality_functor(cugraph_resource_handle_t const* handle, + cugraph_graph_t* graph, + double epsilon, + size_t max_iterations, + bool do_expensive_check) + : abstract_functor(), + handle_(*reinterpret_cast(handle)->handle_), + graph_(reinterpret_cast(graph)), + epsilon_(epsilon), + max_iterations_(max_iterations), + 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 { + // Eigenvector Centrality expects store_transposed == true + if constexpr (!store_transposed) { + error_code_ = cugraph::c_api:: + transpose_storage( + handle_, graph_, error_.get()); + if (error_code_ != CUGRAPH_SUCCESS) return; + } + + auto graph = reinterpret_cast*>( + graph_->graph_); + + auto graph_view = graph->view(); + + auto number_map = reinterpret_cast*>(graph_->number_map_); + + rmm::device_uvector centralities(graph_view.get_number_of_local_vertices(), + handle_.get_stream()); + + // FIXME: For now we'll call pagerank which returns a similarly formatted thing + cugraph::pagerank( + handle_, + graph_view, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + centralities.data(), + weight_t{0.95}, + static_cast(epsilon_), + max_iterations_, + false, + do_expensive_check_); + + rmm::device_uvector vertex_ids(graph_view.get_number_of_local_vertices(), + handle_.get_stream()); + raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); + + result_ = new cugraph::c_api::cugraph_centrality_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(centralities, graph_->weight_type_)}; + } + } +}; + +} // namespace + +extern "C" cugraph_error_code_t cugraph_eigenvector_centrality( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + double epsilon, + size_t max_iterations, + bool_t do_expensive_check, + cugraph_centrality_result_t** result, + cugraph_error_t** error) +{ + eigenvector_centrality_functor functor( + handle, graph, epsilon, max_iterations, do_expensive_check); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} diff --git a/cpp/src/c_api/pagerank.cpp b/cpp/src/c_api/pagerank.cpp index 24c0425d2d2..c1e4ca5785a 100644 --- a/cpp/src/c_api/pagerank.cpp +++ b/cpp/src/c_api/pagerank.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -27,33 +28,28 @@ #include -namespace cugraph { -namespace c_api { +namespace { -struct cugraph_pagerank_result_t { - cugraph_type_erased_device_array_t* vertex_ids_; - cugraph_type_erased_device_array_t* pageranks_; -}; - -struct pagerank_functor : public abstract_functor { +struct pagerank_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; - cugraph_graph_t* graph_; - cugraph_type_erased_device_array_view_t const* precomputed_vertex_out_weight_sums_; - cugraph_type_erased_device_array_view_t* personalization_vertices_; - cugraph_type_erased_device_array_view_t const* personalization_values_; - double alpha_; - double epsilon_; - size_t max_iterations_; - bool has_initial_guess_; - bool do_expensive_check_; - cugraph_pagerank_result_t* result_{}; + cugraph::c_api::cugraph_graph_t* graph_{}; + cugraph::c_api::cugraph_type_erased_device_array_view_t const* + precomputed_vertex_out_weight_sums_{}; + cugraph::c_api::cugraph_type_erased_device_array_view_t* personalization_vertices_{}; + cugraph::c_api::cugraph_type_erased_device_array_view_t const* personalization_values_{}; + double alpha_{}; + double epsilon_{}; + size_t max_iterations_{}; + bool has_initial_guess_{}; + bool do_expensive_check_{}; + cugraph::c_api::cugraph_centrality_result_t* result_{}; pagerank_functor( - ::cugraph_resource_handle_t const* handle, - ::cugraph_graph_t* graph, - ::cugraph_type_erased_device_array_view_t const* precomputed_vertex_out_weight_sums, - ::cugraph_type_erased_device_array_view_t* personalization_vertices, - ::cugraph_type_erased_device_array_view_t const* personalization_values, + cugraph_resource_handle_t const* handle, + cugraph_graph_t* graph, + cugraph_type_erased_device_array_view_t const* precomputed_vertex_out_weight_sums, + cugraph_type_erased_device_array_view_t* personalization_vertices, + cugraph_type_erased_device_array_view_t const* personalization_values, double alpha, double epsilon, size_t max_iterations, @@ -112,13 +108,14 @@ struct pagerank_functor : public abstract_functor { // // Need to renumber personalization_vertices // - renumber_ext_vertices(handle_, - personalization_vertices_->as_type(), - personalization_vertices_->size_, - number_map->data(), - graph_view.get_local_vertex_first(), - graph_view.get_local_vertex_last(), - do_expensive_check_); + cugraph::renumber_ext_vertices( + handle_, + personalization_vertices_->as_type(), + personalization_vertices_->size_, + number_map->data(), + graph_view.get_local_vertex_first(), + graph_view.get_local_vertex_last(), + do_expensive_check_); } cugraph::pagerank( @@ -147,39 +144,14 @@ struct pagerank_functor : public abstract_functor { handle_.get_stream()); raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); - result_ = new cugraph_pagerank_result_t{ - new cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), - new cugraph_type_erased_device_array_t(pageranks, graph_->weight_type_)}; + result_ = new cugraph::c_api::cugraph_centrality_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(pageranks, graph_->weight_type_)}; } } }; -} // namespace c_api -} // namespace cugraph - -extern "C" cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_vertices( - cugraph_pagerank_result_t* result) -{ - auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast( - internal_pointer->vertex_ids_->view()); -} - -extern "C" cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_pageranks( - cugraph_pagerank_result_t* result) -{ - auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast( - internal_pointer->pageranks_->view()); -} - -extern "C" void cugraph_pagerank_result_free(cugraph_pagerank_result_t* result) -{ - auto internal_pointer = reinterpret_cast(result); - delete internal_pointer->vertex_ids_; - delete internal_pointer->pageranks_; - delete internal_pointer; -} +} // namespace extern "C" cugraph_error_code_t cugraph_pagerank( const cugraph_resource_handle_t* handle, @@ -190,19 +162,19 @@ extern "C" cugraph_error_code_t cugraph_pagerank( size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error) { - cugraph::c_api::pagerank_functor functor(handle, - graph, - precomputed_vertex_out_weight_sums, - nullptr, - nullptr, - alpha, - epsilon, - max_iterations, - has_initial_guess, - do_expensive_check); + pagerank_functor functor(handle, + graph, + precomputed_vertex_out_weight_sums, + nullptr, + nullptr, + alpha, + epsilon, + max_iterations, + has_initial_guess, + do_expensive_check); return cugraph::c_api::run_algorithm(graph, functor, result, error); } @@ -218,19 +190,19 @@ extern "C" cugraph_error_code_t cugraph_personalized_pagerank( size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error) { - cugraph::c_api::pagerank_functor functor(handle, - graph, - precomputed_vertex_out_weight_sums, - personalization_vertices, - personalization_values, - alpha, - epsilon, - max_iterations, - has_initial_guess, - do_expensive_check); + pagerank_functor functor(handle, + graph, + precomputed_vertex_out_weight_sums, + personalization_vertices, + personalization_values, + alpha, + epsilon, + max_iterations, + has_initial_guess, + do_expensive_check); return cugraph::c_api::run_algorithm(graph, functor, result, error); } diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index d79bbab5b29..b9530f66e5e 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -620,6 +620,7 @@ if(BUILD_CUGRAPH_MG_TESTS) # - MG C API tests ------------------------------------------------------------------------ ConfigureCTestMG(MG_CAPI_CREATE_GRAPH c_api/mg_create_graph_test.c c_api/mg_test_utils.cpp) ConfigureCTestMG(MG_CAPI_PAGERANK c_api/mg_pagerank_test.c c_api/mg_test_utils.cpp) + ConfigureCTestMG(MG_CAPI_EIGENVECTOR_CENTRALITY c_api/mg_eigenvector_centrality_test.c c_api/mg_test_utils.cpp) ConfigureCTestMG(MG_CAPI_HITS c_api/mg_hits_test.c c_api/mg_test_utils.cpp) ConfigureCTestMG(MG_CAPI_UNIFORM_NEIGHBOR_SAMPLE c_api/mg_uniform_neighbor_sample_test.c c_api/mg_test_utils.cpp) else() @@ -662,6 +663,7 @@ target_link_libraries(cugraph_c_testutil ConfigureCTest(CAPI_CREATE_GRAPH_TEST c_api/create_graph_test.c) ConfigureCTest(CAPI_PAGERANK_TEST c_api/pagerank_test.c) +ConfigureCTest(CAPI_EIGENVECTOR_CENTRALITY_TEST c_api/eigenvector_centrality_test.c) ConfigureCTest(CAPI_HITS_TEST c_api/hits_test.c) ConfigureCTest(CAPI_BFS_TEST c_api/bfs_test.c) ConfigureCTest(CAPI_SSSP_TEST c_api/sssp_test.c) diff --git a/cpp/tests/c_api/eigenvector_centrality_test.c b/cpp/tests/c_api/eigenvector_centrality_test.c new file mode 100644 index 00000000000..5e82449b7f4 --- /dev/null +++ b/cpp/tests/c_api/eigenvector_centrality_test.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022, 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; + +int generic_eigenvector_centrality_test(vertex_t* h_src, + vertex_t* h_dst, + weight_t* h_wgt, + weight_t* h_result, + size_t num_vertices, + size_t num_edges, + bool_t store_transposed, + double alpha, + double epsilon, + size_t max_iterations) +{ + int test_ret_value = 0; + + cugraph_error_code_t ret_code = CUGRAPH_SUCCESS; + cugraph_error_t* ret_error; + + cugraph_resource_handle_t* p_handle = NULL; + cugraph_graph_t* p_graph = NULL; + cugraph_centrality_result_t* p_result = NULL; + + p_handle = cugraph_create_resource_handle(NULL); + TEST_ASSERT(test_ret_value, p_handle != NULL, "resource handle creation failed."); + + ret_code = create_test_graph( + p_handle, h_src, h_dst, h_wgt, num_edges, store_transposed, FALSE, &p_graph, &ret_error); + + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "create_test_graph failed."); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); + + ret_code = cugraph_eigenvector_centrality( + p_handle, p_graph, epsilon, max_iterations, FALSE, &p_result, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_eigenvector_centrality failed."); + + cugraph_type_erased_device_array_view_t* vertices; + cugraph_type_erased_device_array_view_t* centralities; + + vertices = cugraph_centrality_result_get_vertices(p_result); + centralities = cugraph_centrality_result_get_values(p_result); + + vertex_t h_vertices[num_vertices]; + weight_t h_centralities[num_vertices]; + + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + p_handle, (byte_t*)h_vertices, 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( + p_handle, (byte_t*)h_centralities, centralities, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + + for (int i = 0; (i < num_vertices) && (test_ret_value == 0); ++i) { + TEST_ASSERT(test_ret_value, + nearlyEqual(h_result[h_vertices[i]], h_centralities[i], 0.001), + "centralities results don't match"); + } + + cugraph_centrality_result_free(p_result); + cugraph_sg_graph_free(p_graph); + cugraph_free_resource_handle(p_handle); + cugraph_error_free(ret_error); + + return test_ret_value; +} + +int test_eigenvector_centrality() +{ + 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}; + weight_t h_result[] = {0.0915528, 0.168382, 0.0656831, 0.191468, 0.120677, 0.362237}; + + double alpha = 0.95; + double epsilon = 0.0001; + size_t max_iterations = 20; + + // Pagerank wants store_transposed = TRUE + return generic_eigenvector_centrality_test( + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, TRUE, alpha, epsilon, max_iterations); +} + +/******************************************************************************/ + +int main(int argc, char** argv) +{ + int result = 0; + result |= RUN_TEST(test_eigenvector_centrality); + return result; +} diff --git a/cpp/tests/c_api/mg_eigenvector_centrality_test.c b/cpp/tests/c_api/mg_eigenvector_centrality_test.c new file mode 100644 index 00000000000..d8bf291edef --- /dev/null +++ b/cpp/tests/c_api/mg_eigenvector_centrality_test.c @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2022, 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; + +int generic_eigenvector_centrality_test(const cugraph_resource_handle_t* handle, + vertex_t* h_src, + vertex_t* h_dst, + weight_t* h_wgt, + weight_t* h_result, + size_t num_vertices, + size_t num_edges, + bool_t store_transposed, + double alpha, + double epsilon, + size_t max_iterations) +{ + int test_ret_value = 0; + + cugraph_error_code_t ret_code = CUGRAPH_SUCCESS; + cugraph_error_t* ret_error; + + cugraph_graph_t* p_graph = NULL; + cugraph_centrality_result_t* p_result = NULL; + + ret_code = create_mg_test_graph( + handle, h_src, h_dst, h_wgt, num_edges, store_transposed, &p_graph, &ret_error); + + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "create_mg_test_graph failed."); + + ret_code = cugraph_eigenvector_centrality( + handle, p_graph, epsilon, max_iterations, FALSE, &p_result, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_eigenvector_centrality failed."); + + // NOTE: Because we get back vertex ids and centralities, we can simply compare + // the returned values with the expected results for the entire + // graph. Each GPU will have a subset of the total vertices, so + // they will do a subset of the comparisons. + cugraph_type_erased_device_array_view_t* vertices; + cugraph_type_erased_device_array_view_t* centralities; + + vertices = cugraph_centrality_result_get_vertices(p_result); + centralities = cugraph_centrality_result_get_values(p_result); + + vertex_t h_vertices[num_vertices]; + weight_t h_centralities[num_vertices]; + + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + handle, (byte_t*)h_vertices, 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_centralities, centralities, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); + + size_t num_local_vertices = cugraph_type_erased_device_array_view_size(vertices); + + for (int i = 0; (i < num_local_vertices) && (test_ret_value == 0); ++i) { + TEST_ASSERT(test_ret_value, + nearlyEqual(h_result[h_vertices[i]], h_centralities[i], 0.001), + "eigenvector centrality results don't match"); + } + + cugraph_centrality_result_free(p_result); + cugraph_mg_graph_free(p_graph); + cugraph_error_free(ret_error); + + return test_ret_value; +} + +int test_eigenvector_centrality(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}; + weight_t h_result[] = {0.0915528, 0.168382, 0.0656831, 0.191468, 0.120677, 0.362237}; + + double alpha = 0.95; + double epsilon = 0.0001; + size_t max_iterations = 20; + + // Pagerank wants store_transposed = TRUE + return generic_eigenvector_centrality_test(handle, + h_src, + h_dst, + h_wgt, + h_result, + num_vertices, + num_edges, + TRUE, + alpha, + epsilon, + max_iterations); +} + +/******************************************************************************/ + +int main(int argc, char** argv) +{ + // Set up MPI: + int comm_rank; + int comm_size; + int num_gpus_per_node; + cudaError_t status; + int mpi_status; + int result = 0; + cugraph_resource_handle_t* handle = NULL; + cugraph_error_t* ret_error; + cugraph_error_code_t ret_code = CUGRAPH_SUCCESS; + int prows = 1; + + C_MPI_TRY(MPI_Init(&argc, &argv)); + C_MPI_TRY(MPI_Comm_rank(MPI_COMM_WORLD, &comm_rank)); + C_MPI_TRY(MPI_Comm_size(MPI_COMM_WORLD, &comm_size)); + C_CUDA_TRY(cudaGetDeviceCount(&num_gpus_per_node)); + C_CUDA_TRY(cudaSetDevice(comm_rank % num_gpus_per_node)); + +#if 0 + // TODO: Need something a bit more sophisticated for bigger systems + prows = (int)sqrt((double)comm_size); + while (comm_size % prows != 0) { + --prows; + } + + ret_code = cugraph_resource_handle_init_comms(handle, prows, &ret_error); + TEST_ASSERT(result, ret_code == CUGRAPH_SUCCESS, "handle create failed."); + TEST_ASSERT(result, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); +#endif + + void* raft_handle = create_raft_handle(prows); + handle = cugraph_create_resource_handle(raft_handle); + + if (result == 0) { + result |= RUN_MG_TEST(test_eigenvector_centrality, handle); + + cugraph_free_resource_handle(handle); + } + + free_raft_handle(raft_handle); + + C_MPI_TRY(MPI_Finalize()); + + return result; +} diff --git a/cpp/tests/c_api/mg_pagerank_test.c b/cpp/tests/c_api/mg_pagerank_test.c index 941e5e09c73..e7ead863c4d 100644 --- a/cpp/tests/c_api/mg_pagerank_test.c +++ b/cpp/tests/c_api/mg_pagerank_test.c @@ -43,7 +43,7 @@ int generic_pagerank_test(const cugraph_resource_handle_t* handle, cugraph_error_t* ret_error; cugraph_graph_t* p_graph = NULL; - cugraph_pagerank_result_t* p_result = NULL; + cugraph_centrality_result_t* p_result = NULL; ret_code = create_mg_test_graph( handle, h_src, h_dst, h_wgt, num_edges, store_transposed, &p_graph, &ret_error); @@ -61,8 +61,8 @@ int generic_pagerank_test(const cugraph_resource_handle_t* handle, cugraph_type_erased_device_array_view_t* vertices; cugraph_type_erased_device_array_view_t* pageranks; - vertices = cugraph_pagerank_result_get_vertices(p_result); - pageranks = cugraph_pagerank_result_get_pageranks(p_result); + vertices = cugraph_centrality_result_get_vertices(p_result); + pageranks = cugraph_centrality_result_get_values(p_result); vertex_t h_vertices[num_vertices]; weight_t h_pageranks[num_vertices]; @@ -83,9 +83,7 @@ int generic_pagerank_test(const cugraph_resource_handle_t* handle, "pagerank results don't match"); } - cugraph_type_erased_device_array_view_free(pageranks); - cugraph_type_erased_device_array_view_free(vertices); - cugraph_pagerank_result_free(p_result); + cugraph_centrality_result_free(p_result); cugraph_mg_graph_free(p_graph); cugraph_error_free(ret_error); diff --git a/cpp/tests/c_api/pagerank_test.c b/cpp/tests/c_api/pagerank_test.c index 60ac9ddf5e4..715692aaa0d 100644 --- a/cpp/tests/c_api/pagerank_test.c +++ b/cpp/tests/c_api/pagerank_test.c @@ -43,7 +43,7 @@ int generic_pagerank_test(vertex_t* h_src, cugraph_resource_handle_t* p_handle = NULL; cugraph_graph_t* p_graph = NULL; - cugraph_pagerank_result_t* p_result = NULL; + cugraph_centrality_result_t* p_result = NULL; p_handle = cugraph_create_resource_handle(NULL); TEST_ASSERT(test_ret_value, p_handle != NULL, "resource handle creation failed."); @@ -61,8 +61,8 @@ int generic_pagerank_test(vertex_t* h_src, cugraph_type_erased_device_array_view_t* vertices; cugraph_type_erased_device_array_view_t* pageranks; - vertices = cugraph_pagerank_result_get_vertices(p_result); - pageranks = cugraph_pagerank_result_get_pageranks(p_result); + vertices = cugraph_centrality_result_get_vertices(p_result); + pageranks = cugraph_centrality_result_get_values(p_result); vertex_t h_vertices[num_vertices]; weight_t h_pageranks[num_vertices]; @@ -81,9 +81,7 @@ int generic_pagerank_test(vertex_t* h_src, "pagerank results don't match"); } - cugraph_type_erased_device_array_view_free(pageranks); - cugraph_type_erased_device_array_view_free(vertices); - cugraph_pagerank_result_free(p_result); + cugraph_centrality_result_free(p_result); cugraph_sg_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); diff --git a/python/cugraph/cugraph/structure/hypergraph.py b/python/cugraph/cugraph/structure/hypergraph.py index b22caef15ba..365c26da6be 100644 --- a/python/cugraph/cugraph/structure/hypergraph.py +++ b/python/cugraph/cugraph/structure/hypergraph.py @@ -185,7 +185,7 @@ def hypergraph( else _str_scalar_to_category(len(events), "event") if not dropna: - for key, col in events[columns].iteritems(): + for key, col in events[columns].items(): if cudf.api.types.is_string_dtype(col.dtype): events[key].fillna("null", inplace=True) @@ -297,10 +297,10 @@ def _create_entity_nodes( )) ] + [ (key, cudf.core.column.column_empty(0, col.dtype)) - for key, col in events[columns].iteritems() + for key, col in events[columns].items() ]))] - for key, col in events[columns].iteritems(): + for key, col in events[columns].items(): cat = categories.get(key, key) col = col.unique() col = col.nans_to_nulls().dropna() if dropna else col @@ -378,11 +378,11 @@ def _create_hyper_edges( ]) + ([] if drop_edge_attrs else [ (key, cudf.core.column.column_empty(0, col.dtype)) - for key, col in events[edge_attrs].iteritems() + for key, col in events[edge_attrs].items() ]) ))] - for key, col in events[columns].iteritems(): + for key, col in events[columns].items(): cat = categories.get(key, key) fs = [EVENTID] + ([key] if drop_edge_attrs else edge_attrs) df = events[fs].dropna(subset=[key]) if dropna else events[fs] @@ -448,11 +448,11 @@ def _create_direct_edges( ]) + ([] if drop_edge_attrs else [ (key, cudf.core.column.column_empty(0, col.dtype)) - for key, col in events[edge_attrs].iteritems() + for key, col in events[edge_attrs].items() ]) ))] - for key1, col1 in events[sorted(edge_shape.keys())].iteritems(): + for key1, col1 in events[sorted(edge_shape.keys())].items(): cat1 = categories.get(key1, key1) if isinstance(edge_shape[key1], str): @@ -462,7 +462,7 @@ def _create_direct_edges( elif not isinstance(edge_shape[key1], (set, list, tuple)): raise ValueError("EDGES must be a dict of column name(s)") - for key2, col2 in events[sorted(edge_shape[key1])].iteritems(): + for key2, col2 in events[sorted(edge_shape[key1])].items(): cat2 = categories.get(key2, key2) fs = [EVENTID] + ([key1, key2] if drop_edge_attrs else edge_attrs) df = ( diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd index d7a0755df85..d4f1d083c73 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd @@ -34,22 +34,22 @@ from pylibcugraph._cugraph_c.graph cimport ( cdef extern from "cugraph_c/algorithms.h": ########################################################################### # pagerank - ctypedef struct cugraph_pagerank_result_t: + ctypedef struct cugraph_centrality_result_t: pass cdef cugraph_type_erased_device_array_view_t* \ - cugraph_pagerank_result_get_vertices( - cugraph_pagerank_result_t* result + cugraph_centrality_result_get_vertices( + cugraph_centrality_result_t* result ) cdef cugraph_type_erased_device_array_view_t* \ - cugraph_pagerank_result_get_pageranks( - cugraph_pagerank_result_t* result + cugraph_centrality_result_get_values( + cugraph_centrality_result_t* result ) cdef void \ - cugraph_pagerank_result_free( - cugraph_pagerank_result_t* result + cugraph_centrality_result_free( + cugraph_centrality_result_t* result ) cdef cugraph_error_code_t \ @@ -62,7 +62,7 @@ cdef extern from "cugraph_c/algorithms.h": size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error ) @@ -78,7 +78,7 @@ cdef extern from "cugraph_c/algorithms.h": size_t max_iterations, bool_t has_initial_guess, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_centrality_result_t** result, cugraph_error_t** error ) diff --git a/python/pylibcugraph/pylibcugraph/pagerank.pyx b/python/pylibcugraph/pylibcugraph/pagerank.pyx index 0c46d2f97b7..53a47fc5bcc 100644 --- a/python/pylibcugraph/pylibcugraph/pagerank.pyx +++ b/python/pylibcugraph/pylibcugraph/pagerank.pyx @@ -30,11 +30,11 @@ from pylibcugraph._cugraph_c.graph cimport ( cugraph_graph_t, ) from pylibcugraph._cugraph_c.algorithms cimport ( - cugraph_pagerank_result_t, + cugraph_centrality_result_t, cugraph_pagerank, - cugraph_pagerank_result_get_vertices, - cugraph_pagerank_result_get_pageranks, - cugraph_pagerank_result_free, + cugraph_centrality_result_get_vertices, + cugraph_centrality_result_get_values, + cugraph_centrality_result_free, ) from pylibcugraph.resource_handle cimport ( EXPERIMENTAL__ResourceHandle, @@ -169,7 +169,7 @@ def EXPERIMENTAL__pagerank(EXPERIMENTAL__ResourceHandle resource_handle, raise NotImplementedError("None is temporarily the only supported " "value for precomputed_vertex_out_weight_sums") - cdef cugraph_pagerank_result_t* result_ptr + cdef cugraph_centrality_result_t* result_ptr cdef cugraph_error_code_t error_code cdef cugraph_error_t* error_ptr @@ -188,13 +188,13 @@ def EXPERIMENTAL__pagerank(EXPERIMENTAL__ResourceHandle resource_handle, # 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_pagerank_result_get_vertices(result_ptr) + cugraph_centrality_result_get_vertices(result_ptr) cdef cugraph_type_erased_device_array_view_t* pageranks_ptr = \ - cugraph_pagerank_result_get_pageranks(result_ptr) + cugraph_centrality_result_get_values(result_ptr) cupy_vertices = copy_to_cupy_array(c_resource_handle_ptr, vertices_ptr) cupy_pageranks = copy_to_cupy_array(c_resource_handle_ptr, pageranks_ptr) - cugraph_pagerank_result_free(result_ptr) + cugraph_centrality_result_free(result_ptr) return (cupy_vertices, cupy_pageranks)