From 641bfc85f9605b054faae838b7938be1a5bffdea Mon Sep 17 00:00:00 2001 From: Chuck Hastings Date: Mon, 14 Mar 2022 14:19:25 -0400 Subject: [PATCH 1/4] initial cut at HITS implementation --- cpp/include/cugraph_c/algorithms.h | 99 +++++++++++++ cpp/src/c_api/hits.cpp | 217 +++++++++++++++++++++++++++++ cpp/tests/c_api/hits_test.c | 139 ++++++++++++++++++ 3 files changed, 455 insertions(+) create mode 100644 cpp/src/c_api/hits.cpp create mode 100644 cpp/tests/c_api/hits_test.c diff --git a/cpp/include/cugraph_c/algorithms.h b/cpp/include/cugraph_c/algorithms.h index dd5a7d850fb..4edd6e514cc 100644 --- a/cpp/include/cugraph_c/algorithms.h +++ b/cpp/include/cugraph_c/algorithms.h @@ -139,6 +139,105 @@ cugraph_error_code_t cugraph_personalized_pagerank( cugraph_pagerank_result_t** result, cugraph_error_t** error); +/** + * @brief Opaque hits result type + */ +typedef struct { + int32_t align_; +} cugraph_hits_result_t; + +/** + * @brief Get the vertex ids from the hits result + * + * @param [in] result The result from hits + * @return type erased array of vertex ids + */ +cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_vertices( + cugraph_hits_result_t* result); + +/** + * @brief Get the hubs values from the hits result + * + * @param [in] result The result from hits + * @return type erased array of hubs values + */ +cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_hubs( + cugraph_pagerank_result_t* result); + +/** + * @brief Get the authorities values from the hits result + * + * @param [in] result The result from hits + * @return type erased array of authorities values + */ +cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_authorities( + cugraph_pagerank_result_t* result); + +/** + * @brief Get the score differences between the last two iterations + * + * @param [in] result The result from hits + * @return score differences + */ +double cugraph_hits_result_get_hub_score_differences(cugraph_hits_result_t* result); + +/** + * @brief Get the actual number of iterations + * + * @param [in] result The result from hits + * @return actual number of iterations + */ +size_t cugraph_hits_result_get_number_of_iterations(cugraph_hits_result_t* result); + +/** + * @brief Free hits result + * + * @param [in] result The result from hits + */ +void cugraph_hits_result_free(cugraph_hits_result_t* result); + +/** + * @brief Compute hits + * + * @param [in] handle Handle for accessing resources + * @param [in] graph Pointer to graph + * @param [in] epsilon Error tolerance to check convergence. Convergence is assumed + * if the sum of the differences in Hits values between two + * consecutive iterations is less than the number of vertices + * in the graph multiplied by @p epsilon. + * @param [in] max_iterations + * Maximum number of Hits iterations. + * @param [in] initial_hubs_guess_vertices + * Pointer to optional type erased device array containing + * the vertex ids for an initial hubs guess. If set to NULL + * there is no initial guess. + * @param [in] initial_hubs_guess_values + * Pointer to optional type erased device array containing + * the values for an initial hubs guess. If set to NULL + * there is no initial guess. Note that both + * @p initial_hubs_guess_vertices and @p initial_hubs_guess_values + * have to be specified (or they both have to be NULL). Otherwise + * this will be treated as an error. + * @param [in] normalize A flag to normalize the results (if set to `true`) + * @param [in] do_expensive_check A flag to run expensive checks for input arguments (if set to + * `true`). + * @param [out] result Opaque pointer to hits 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_hits( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + double epsilon, + size_t max_iterations, + const cugraph_type_erased_device_array_view_t* initial_hubs_guess_vertices, + const cugraph_type_erased_device_array_view_t* initial_hubs_guess_values, + bool_t normalize, + bool_t do_expensive_check, + cugraph_pagerank_result_t** result, + cugraph_error_t** error); + /** * @brief Opaque paths result type * diff --git a/cpp/src/c_api/hits.cpp b/cpp/src/c_api/hits.cpp new file mode 100644 index 00000000000..3d979df327f --- /dev/null +++ b/cpp/src/c_api/hits.cpp @@ -0,0 +1,217 @@ +/* + * 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 + +namespace cugraph { +namespace c_api { + +struct cugraph_hits_result_t { + cugraph_type_erased_device_array_t* vertex_ids_; + cugraph_type_erased_device_array_t* hubs_; + cugraph_type_erased_device_array_t* authorities_; + double hub_score_differences_; + size_t number_of_iterations_; +}; + +} // namespace c_api +} // namespace cugraph + +namespace { + +struct hits_functor : public abstract_functor { + raft::handle_t const& handle_; + cugraph_graph_t* graph_; + double epsilon_; + size_t max_iterations_; + cugraph_type_erased_device_array_view_t const* initial_hubs_guess_vertices_; + cugraph_type_erased_device_array_view_t const* initial_hubs_guess_values_; + bool do_expensive_check_; + cugraph_hits_result_t* result_{}; + + hits_functor(cugraph_resource_handle_t const* handle, + ::cugraph_graph_t* graph, + ::cugraph_type_erased_device_array_view_t const* personalization_values, + double epsilon, + size_t max_iterations, + ::cugraph_type_erased_device_array_view_t const* initial_hubs_guess, + bool do_expensive_check) + : abstract_functor(), + handle_(*reinterpret_cast(handle)), + graph_(reinterpret_cast(graph)), + epsilon_(epsilon), + max_iterations_(max_iterations), + inital_hubs_guess_( + reinterpret_cast( + inital_hubs_guess)), + 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 { + // HITS 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 vertex_ids(graph->get_number_of_local_vertices(), + handle_.get_stream()); + rmm::device_uvector hubs(graph->get_number_of_local_vertices(), + handle_.get_stream()); + rmm::device_uvector authorities(graph->get_number_of_local_vertices(), + handle_.get_stream()); + +#if 0 + if (initial_hubs_guess_ != nullptr) { + // + // Need to renumber initial_hubs_guess_vertices + // Need to shuffle and populate hubs + // + // This is the original pagerank code, it will be sort of like this + 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_); + } + + // TODO: Add these to the result + auto [hub_score_differences, number_of_iterations] = + cugraph::hits(handle_, + graph_view, + hubs.data(), + authorities.data(), + epsilon_, + max_iterations_, + has_initial_hubs_guess, + normalize_, + do_expensive_check_); + + raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); +#else + unsupported(); +#endif + + result_ = new cugraph_hits_result_t{ + new cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), + new cugraph_type_erased_device_array_t(hubs, graph_->weight_type_), + new cugraph_type_erased_device_array_t(authorities, graph_->weight_type_)}; + } + } +}; + +} // namespace + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_vertices( + cugraph_hits_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_hits_result_get_hubs( + cugraph_hits_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return reinterpret_cast( + internal_pointer->hubs_->view()); +} + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_authorities( + cugraph_hits_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return reinterpret_cast( + internal_pointer->authorities_->view()); +} + +extern "C" double cugraph_hits_result_get_hub_score_differences(cugraph_hits_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return internal_pointer->hub_score_differences_; +} + +extern "C" size_t cugraph_hits_result_get_number_of_iterations(cugraph_hits_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + return internal_pointer->number_of_iterations_; +} + +extern "C" void cugraph_hits_result_free(cugraph_hits_result_t* result) +{ + auto internal_pointer = reinterpret_cast(result); + delete internal_pointer->vertex_ids_; + delete internal_pointer->hubs_; + delete internal_pointer->authorities_; + delete internal_pointer; +} + +extern "C" cugraph_error_code_t cugraph_hits( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + double epsilon, + size_t max_iterations, + const cugraph_type_erased_device_array_view_t* initial_hubs_guess_vertices, + const cugraph_type_erased_device_array_view_t* initial_hubs_guess_values, + bool_t do_expensive_check, + cugraph_hits_result_t** result, + cugraph_error_t** error) +{ + hits_functor functor(handle, + graph, + epsilon, + max_iterations, + initial_hubs_guess_vertices, + initial_hubs_guess_values, + do_expensive_check); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} diff --git a/cpp/tests/c_api/hits_test.c b/cpp/tests/c_api/hits_test.c new file mode 100644 index 00000000000..69e75b35b8f --- /dev/null +++ b/cpp/tests/c_api/hits_test.c @@ -0,0 +1,139 @@ +/* + * 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_hits_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_hits_result_t* p_result = NULL; + + p_handle = cugraph_create_resource_handle(); + 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, &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_hits( + p_handle, p_graph, NULL, alpha, epsilon, max_iterations, FALSE, FALSE, &p_result, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_hits failed."); + + cugraph_type_erased_device_array_view_t* vertices; + cugraph_type_erased_device_array_view_t* pageranks; + + vertices = cugraph_hits_result_get_vertices(p_result); + pageranks = cugraph_hits_result_get_pageranks(p_result); + + vertex_t h_vertices[num_vertices]; + weight_t h_pageranks[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_pageranks, pageranks, &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_pageranks[i], 0.001), + "pagerank results don't match"); + } + + cugraph_type_erased_device_array_view_free(pageranks); + cugraph_type_erased_device_array_view_free(vertices); + cugraph_hits_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_hits() +{ + 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_hits_test( + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, TRUE, alpha, epsilon, max_iterations); +} + +int test_hits_with_transpose() +{ + 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 + // This call will force cugraph_hits to transpose the graph + // But we're passing src/dst backwards so the results will be the same + return generic_hits_test( + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, FALSE, alpha, epsilon, max_iterations); +} + +int main(int argc, char** argv) +{ + int result = 0; + result |= RUN_TEST(test_hits); + return result; +} From f753204d09286741db4c77638848044d22009826 Mon Sep 17 00:00:00 2001 From: Chuck Hastings Date: Wed, 16 Mar 2022 15:32:43 -0400 Subject: [PATCH 2/4] add HITS definition to the C API --- cpp/src/c_api/hits.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cpp/src/c_api/hits.cpp b/cpp/src/c_api/hits.cpp index 3d979df327f..8f9d795b608 100644 --- a/cpp/src/c_api/hits.cpp +++ b/cpp/src/c_api/hits.cpp @@ -54,7 +54,7 @@ struct hits_functor : public abstract_functor { bool do_expensive_check_; cugraph_hits_result_t* result_{}; - hits_functor(cugraph_resource_handle_t const* handle, + hits_functor(::cugraph_resource_handle_t const* handle, ::cugraph_graph_t* graph, ::cugraph_type_erased_device_array_view_t const* personalization_values, double epsilon, @@ -62,7 +62,7 @@ struct hits_functor : public abstract_functor { ::cugraph_type_erased_device_array_view_t const* initial_hubs_guess, bool do_expensive_check) : abstract_functor(), - handle_(*reinterpret_cast(handle)), + handle_(*reinterpret_cast(handle)->handle_), graph_(reinterpret_cast(graph)), epsilon_(epsilon), max_iterations_(max_iterations), @@ -105,8 +105,12 @@ struct hits_functor : public abstract_functor { handle_.get_stream()); rmm::device_uvector authorities(graph->get_number_of_local_vertices(), handle_.get_stream()); + weight_t hub_score_differences{0}; + size_t number_of_iterations{0}; #if 0 + // FIXME: Implementation will look something like this. + if (initial_hubs_guess_ != nullptr) { // // Need to renumber initial_hubs_guess_vertices @@ -123,16 +127,16 @@ struct hits_functor : public abstract_functor { } // TODO: Add these to the result - auto [hub_score_differences, number_of_iterations] = - cugraph::hits(handle_, - graph_view, - hubs.data(), - authorities.data(), - epsilon_, - max_iterations_, - has_initial_hubs_guess, - normalize_, - do_expensive_check_); + std::tie(hub_score_differences, number_of_iterations) = + cugraph::hits(handle_, + graph_view, + hubs.data(), + authorities.data(), + epsilon_, + max_iterations_, + has_initial_hubs_guess, + normalize_, + do_expensive_check_); raft::copy(vertex_ids.data(), number_map->data(), vertex_ids.size(), handle_.get_stream()); #else @@ -142,7 +146,9 @@ struct hits_functor : public abstract_functor { result_ = new cugraph_hits_result_t{ new cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), new cugraph_type_erased_device_array_t(hubs, graph_->weight_type_), - new cugraph_type_erased_device_array_t(authorities, graph_->weight_type_)}; + new cugraph_type_erased_device_array_t(authorities, graph_->weight_type_), + hub_score_differences, + number_of_iterations}; } } }; From 88efe59e68f3abf9dbede1a10072976c3d24d26a Mon Sep 17 00:00:00 2001 From: Chuck Hastings Date: Wed, 16 Mar 2022 15:35:20 -0400 Subject: [PATCH 3/4] missed a few pagerank references that were copied and needed to be updated to hits --- cpp/include/cugraph_c/algorithms.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/include/cugraph_c/algorithms.h b/cpp/include/cugraph_c/algorithms.h index dcdb4df10ce..89016e112f1 100644 --- a/cpp/include/cugraph_c/algorithms.h +++ b/cpp/include/cugraph_c/algorithms.h @@ -162,7 +162,7 @@ cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_vertices( * @return type erased array of hubs values */ cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_hubs( - cugraph_pagerank_result_t* result); + cugraph_hits_result_t* result); /** * @brief Get the authorities values from the hits result @@ -171,7 +171,7 @@ cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_hubs( * @return type erased array of authorities values */ cugraph_type_erased_device_array_view_t* cugraph_hits_result_get_authorities( - cugraph_pagerank_result_t* result); + cugraph_hits_result_t* result); /** * @brief Get the score differences between the last two iterations @@ -235,7 +235,7 @@ cugraph_error_code_t cugraph_hits( const cugraph_type_erased_device_array_view_t* initial_hubs_guess_values, bool_t normalize, bool_t do_expensive_check, - cugraph_pagerank_result_t** result, + cugraph_hits_result_t** result, cugraph_error_t** error); /** From e05aa466366d1b6402292b1394735a701b7f9491 Mon Sep 17 00:00:00 2001 From: Chuck Hastings Date: Thu, 17 Mar 2022 14:08:04 -0400 Subject: [PATCH 4/4] Get C CPI for hits to compile --- cpp/CMakeLists.txt | 1 + cpp/src/c_api/hits.cpp | 43 +++++++++------ cpp/tests/CMakeLists.txt | 1 + cpp/tests/c_api/hits_test.c | 107 ++++++++++++++++++++++++------------ 4 files changed, 99 insertions(+), 53 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index d96973c2bf0..993cf269771 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -354,6 +354,7 @@ 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/hits.cpp src/c_api/bfs.cpp src/c_api/sssp.cpp src/c_api/extract_paths.cpp diff --git a/cpp/src/c_api/hits.cpp b/cpp/src/c_api/hits.cpp index 8f9d795b608..7ca5f20e4d0 100644 --- a/cpp/src/c_api/hits.cpp +++ b/cpp/src/c_api/hits.cpp @@ -18,14 +18,13 @@ #include #include +#include #include #include #include #include -#include - #include namespace cugraph { @@ -44,31 +43,37 @@ struct cugraph_hits_result_t { namespace { -struct hits_functor : public abstract_functor { +struct hits_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_; - cugraph_graph_t* graph_; + cugraph::c_api::cugraph_graph_t* graph_; double epsilon_; size_t max_iterations_; - cugraph_type_erased_device_array_view_t const* initial_hubs_guess_vertices_; - cugraph_type_erased_device_array_view_t const* initial_hubs_guess_values_; + cugraph::c_api::cugraph_type_erased_device_array_view_t const* initial_hubs_guess_vertices_; + cugraph::c_api::cugraph_type_erased_device_array_view_t const* initial_hubs_guess_values_; + bool normalize_; bool do_expensive_check_; - cugraph_hits_result_t* result_{}; + cugraph::c_api::cugraph_hits_result_t* result_{}; hits_functor(::cugraph_resource_handle_t const* handle, ::cugraph_graph_t* graph, - ::cugraph_type_erased_device_array_view_t const* personalization_values, double epsilon, size_t max_iterations, - ::cugraph_type_erased_device_array_view_t const* initial_hubs_guess, + ::cugraph_type_erased_device_array_view_t const* initial_hubs_guess_vertices, + ::cugraph_type_erased_device_array_view_t const* initial_hubs_guess_values, + bool normalize, bool do_expensive_check) : abstract_functor(), handle_(*reinterpret_cast(handle)->handle_), graph_(reinterpret_cast(graph)), epsilon_(epsilon), max_iterations_(max_iterations), - inital_hubs_guess_( + initial_hubs_guess_vertices_( + reinterpret_cast( + initial_hubs_guess_vertices)), + initial_hubs_guess_values_( reinterpret_cast( - inital_hubs_guess)), + initial_hubs_guess_values)), + normalize_(normalize), do_expensive_check_(do_expensive_check) { } @@ -99,11 +104,11 @@ struct hits_functor : public abstract_functor { auto number_map = reinterpret_cast*>(graph_->number_map_); - rmm::device_uvector vertex_ids(graph->get_number_of_local_vertices(), + rmm::device_uvector vertex_ids(graph_view.get_number_of_local_vertices(), handle_.get_stream()); - rmm::device_uvector hubs(graph->get_number_of_local_vertices(), + rmm::device_uvector hubs(graph_view.get_number_of_local_vertices(), handle_.get_stream()); - rmm::device_uvector authorities(graph->get_number_of_local_vertices(), + rmm::device_uvector authorities(graph_view.get_number_of_local_vertices(), handle_.get_stream()); weight_t hub_score_differences{0}; size_t number_of_iterations{0}; @@ -143,10 +148,10 @@ struct hits_functor : public abstract_functor { unsupported(); #endif - result_ = new cugraph_hits_result_t{ - new cugraph_type_erased_device_array_t(vertex_ids, graph_->vertex_type_), - new cugraph_type_erased_device_array_t(hubs, graph_->weight_type_), - new cugraph_type_erased_device_array_t(authorities, graph_->weight_type_), + result_ = new cugraph::c_api::cugraph_hits_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(hubs, graph_->weight_type_), + new cugraph::c_api::cugraph_type_erased_device_array_t(authorities, graph_->weight_type_), hub_score_differences, number_of_iterations}; } @@ -207,6 +212,7 @@ extern "C" cugraph_error_code_t cugraph_hits( size_t max_iterations, const cugraph_type_erased_device_array_view_t* initial_hubs_guess_vertices, const cugraph_type_erased_device_array_view_t* initial_hubs_guess_values, + bool_t normalize, bool_t do_expensive_check, cugraph_hits_result_t** result, cugraph_error_t** error) @@ -217,6 +223,7 @@ extern "C" cugraph_error_code_t cugraph_hits( max_iterations, initial_hubs_guess_vertices, initial_hubs_guess_values, + normalize, 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 0b487abe2bf..8d058944f25 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -652,6 +652,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_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) ConfigureCTest(CAPI_EXTRACT_PATHS_TEST c_api/extract_paths_test.c) diff --git a/cpp/tests/c_api/hits_test.c b/cpp/tests/c_api/hits_test.c index 69e75b35b8f..caa2bb292e5 100644 --- a/cpp/tests/c_api/hits_test.c +++ b/cpp/tests/c_api/hits_test.c @@ -26,15 +26,16 @@ typedef int32_t edge_t; typedef float weight_t; int generic_hits_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) + vertex_t* h_dst, + weight_t* h_wgt, + weight_t* h_result_hubs, + weight_t* h_result_authorities, + 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; @@ -43,9 +44,9 @@ int generic_hits_test(vertex_t* h_src, cugraph_resource_handle_t* p_handle = NULL; cugraph_graph_t* p_graph = NULL; - cugraph_hits_result_t* p_result = NULL; + cugraph_hits_result_t* p_result = NULL; - p_handle = cugraph_create_resource_handle(); + p_handle = cugraph_create_resource_handle(NULL); TEST_ASSERT(test_ret_value, p_handle != NULL, "resource handle creation failed."); ret_code = create_test_graph( @@ -55,38 +56,53 @@ int generic_hits_test(vertex_t* h_src, TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); ret_code = cugraph_hits( - p_handle, p_graph, NULL, alpha, epsilon, max_iterations, FALSE, FALSE, &p_result, &ret_error); + p_handle, p_graph, epsilon, max_iterations, NULL, NULL, FALSE, FALSE, &p_result, &ret_error); + + TEST_ASSERT(test_ret_value, ret_code != CUGRAPH_SUCCESS, "cugraph_hits worked, but it's not implemented!!!"); + +#if 0 TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_hits failed."); cugraph_type_erased_device_array_view_t* vertices; - cugraph_type_erased_device_array_view_t* pageranks; + cugraph_type_erased_device_array_view_t* hubs; + cugraph_type_erased_device_array_view_t* authorities; - vertices = cugraph_hits_result_get_vertices(p_result); - pageranks = cugraph_hits_result_get_pageranks(p_result); + vertices = cugraph_hits_result_get_vertices(p_result); + hubs = cugraph_hits_result_get_hubs(p_result); + authorities = cugraph_hits_result_get_authorities(p_result); + double score_differences = cugraph_hits_result_get_hub_score_differences(p_result); + size_t num_iterations = cugraph_hits_result_get_number_of_iterations(p_result); vertex_t h_vertices[num_vertices]; - weight_t h_pageranks[num_vertices]; + weight_t h_hubs[num_vertices]; + weight_t h_authorities[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_hubs, hubs, &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_pageranks, pageranks, &ret_error); + p_handle, (byte_t*)h_authorities, authorities, &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_pageranks[i], 0.001), - "pagerank results don't match"); + nearlyEqual(h_result_hubs[h_vertices[i]], h_hubs[i], 0.001), + "hubs results don't match"); + TEST_ASSERT(test_ret_value, + nearlyEqual(h_result_authorities[h_vertices[i]], h_authorities[i], 0.001), + "authorities results don't match"); } - cugraph_type_erased_device_array_view_free(pageranks); - cugraph_type_erased_device_array_view_free(vertices); cugraph_hits_result_free(p_result); cugraph_sg_graph_free(p_graph); cugraph_free_resource_handle(p_handle); cugraph_error_free(ret_error); +#endif return test_ret_value; } @@ -96,18 +112,28 @@ int test_hits() 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}; + 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_hubs[] = {0.0915528, 0.168382, 0.0656831, 0.191468, 0.120677, 0.362237}; + weight_t h_authorities[] = {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_hits_test( - h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, TRUE, alpha, epsilon, max_iterations); + // hits wants store_transposed = TRUE + return generic_hits_test(h_src, + h_dst, + h_wgt, + h_hubs, + h_authorities, + num_vertices, + num_edges, + TRUE, + alpha, + epsilon, + max_iterations); } int test_hits_with_transpose() @@ -115,25 +141,36 @@ int test_hits_with_transpose() 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}; + 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_hubs[] = {0.0915528, 0.168382, 0.0656831, 0.191468, 0.120677, 0.362237}; + weight_t h_authorities[] = {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 + // Hits wants store_transposed = TRUE // This call will force cugraph_hits to transpose the graph // But we're passing src/dst backwards so the results will be the same - return generic_hits_test( - h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, FALSE, alpha, epsilon, max_iterations); + return generic_hits_test(h_src, + h_dst, + h_wgt, + h_hubs, + h_authorities, + num_vertices, + num_edges, + FALSE, + alpha, + epsilon, + max_iterations); } int main(int argc, char** argv) { int result = 0; result |= RUN_TEST(test_hits); + result |= RUN_TEST(test_hits_with_transpose); return result; }