From 4dfbec4e8929dfa2e1ac5dec317b949b5e7390aa Mon Sep 17 00:00:00 2001 From: Rick Ratzel <3039903+rlratzel@users.noreply.github.com> Date: Wed, 26 Jan 2022 19:04:00 -0600 Subject: [PATCH] Python API updates to enable explicit control of internal `graph_t` creation and deletion (#2023) Python API updates to enable explicit control of internal `graph_t` creation and deletion. These changes will allow users who need more control over how the internal `graph_t` object is managed to have additional APIs to do so. Current cugraph algos construct the appropriate `graph_t` at the C++ boundary, run the algo, and destroy the object each time. This is more convenient and safer in some cases since it does not require the user to understand the necessary data format requirements for each algo (CSC, CSR, etc.), but it adds overhead to each algo call. If a user knows which algos will be called ahead of time and the relevant graph creation options to use, they can reuse the underlying `graph_t` object and eliminate the redundant creation/deletion expense. These APIs also allow for benchmarks to measure only the algo run time without the initial graph creation time. These changes are grouped into a category of enhancements sometimes referred to as "expert mode" Changes here include: * Updates to `pylibcugraph` to add the APIs from the `libcugraph_c` library for `graph_t` management and calling Pagerank and SSSP * Addition of the `experimental` namespace to `pylibcugraph`, which is where some of the new APIs will be until we finalize decisions on names, signatures, etc. * Updates to cugraph Graph classes, Pagerank, and SSSP algos to call in to `pylibcugraph` Authors: - Rick Ratzel (https://github.com/rlratzel) - Chuck Hastings (https://github.com/ChuckHastings) - Seunghwa Kang (https://github.com/seunghwak) Approvers: - Chuck Hastings (https://github.com/ChuckHastings) - Seunghwa Kang (https://github.com/seunghwak) - Brad Rees (https://github.com/BradReesWork) URL: https://github.com/rapidsai/cugraph/pull/2023 --- cpp/include/cugraph_c/algorithms.h | 37 +-- cpp/include/cugraph_c/array.h | 168 ++++++++++-- cpp/include/cugraph_c/graph.h | 18 +- cpp/src/c_api/array.cpp | 176 +++++++++--- cpp/src/c_api/array.hpp | 56 +++- cpp/src/c_api/bfs.cpp | 28 +- cpp/src/c_api/extract_paths.cpp | 30 +- cpp/src/c_api/graph_mg.cpp | 18 +- cpp/src/c_api/graph_sg.cpp | 36 ++- cpp/src/c_api/pagerank.cpp | 55 ++-- cpp/src/structure/graph_impl.cuh | 2 +- cpp/tests/c_api/bfs_test.c | 28 +- cpp/tests/c_api/create_graph_test.c | 30 +- cpp/tests/c_api/extract_paths_test.c | 66 +++-- cpp/tests/c_api/pagerank_test.c | 51 +++- cpp/tests/c_api/sssp_test.c | 21 +- cpp/tests/c_api/test_utils.c | 34 ++- .../structure/transpose_storage_test.cpp | 11 +- python/pylibcugraph/pylibcugraph/README.md | 22 ++ python/pylibcugraph/pylibcugraph/__init__.py | 6 +- .../pylibcugraph/_cugraph_c/README.md | 5 + .../pylibcugraph/_cugraph_c/__init__.py | 0 .../pylibcugraph/_cugraph_c/algorithms.pxd | 166 ++++++++++++ .../pylibcugraph/_cugraph_c/array.pxd | 166 ++++++++++++ .../pylibcugraph/_cugraph_c/cugraph_api.pxd | 43 +++ .../pylibcugraph/_cugraph_c/error.pxd | 39 +++ .../pylibcugraph/_cugraph_c/graph.pxd | 82 ++++++ .../pylibcugraph/components/__init__.py | 17 -- .../pylibcugraph/experimental/__init__.py | 54 ++++ .../pylibcugraph/graph_properties.pxd | 23 ++ .../pylibcugraph/graph_properties.pyx | 39 +++ python/pylibcugraph/pylibcugraph/graphs.pxd | 29 ++ python/pylibcugraph/pylibcugraph/graphs.pyx | 171 ++++++++++++ python/pylibcugraph/pylibcugraph/pagerank.pyx | 256 ++++++++++++++++++ .../pylibcugraph/resource_handle.pxd | 23 ++ .../pylibcugraph/resource_handle.pyx | 34 +++ python/pylibcugraph/pylibcugraph/sssp.pyx | 249 +++++++++++++++++ .../pylibcugraph/tests/conftest.py | 199 +++++++++++++- .../tests/test_connected_components.py | 31 ++- .../pylibcugraph/tests/test_graph_sg.py | 104 +++++++ .../pylibcugraph/tests/test_pagerank.py | 127 +++++++++ .../pylibcugraph/tests/test_sssp.py | 160 +++++++++++ .../pylibcugraph/pylibcugraph/tests/utils.py | 64 ++++- .../pylibcugraph/utilities/__init__.py | 0 .../pylibcugraph/utilities/api_tools.py | 58 ++++ python/pylibcugraph/pylibcugraph/utils.pxd | 32 +++ python/pylibcugraph/pylibcugraph/utils.pyx | 66 +++++ python/pylibcugraph/setup.py | 5 +- 48 files changed, 2844 insertions(+), 291 deletions(-) create mode 100644 python/pylibcugraph/pylibcugraph/README.md create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/README.md create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/__init__.py create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/array.pxd create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/cugraph_api.pxd create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/error.pxd create mode 100644 python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd create mode 100644 python/pylibcugraph/pylibcugraph/experimental/__init__.py create mode 100644 python/pylibcugraph/pylibcugraph/graph_properties.pxd create mode 100644 python/pylibcugraph/pylibcugraph/graph_properties.pyx create mode 100644 python/pylibcugraph/pylibcugraph/graphs.pxd create mode 100644 python/pylibcugraph/pylibcugraph/graphs.pyx create mode 100644 python/pylibcugraph/pylibcugraph/pagerank.pyx create mode 100644 python/pylibcugraph/pylibcugraph/resource_handle.pxd create mode 100644 python/pylibcugraph/pylibcugraph/resource_handle.pyx create mode 100644 python/pylibcugraph/pylibcugraph/sssp.pyx create mode 100644 python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py create mode 100644 python/pylibcugraph/pylibcugraph/tests/test_pagerank.py create mode 100644 python/pylibcugraph/pylibcugraph/tests/test_sssp.py create mode 100644 python/pylibcugraph/pylibcugraph/utilities/__init__.py create mode 100644 python/pylibcugraph/pylibcugraph/utilities/api_tools.py create mode 100644 python/pylibcugraph/pylibcugraph/utils.pxd create mode 100644 python/pylibcugraph/pylibcugraph/utils.pyx diff --git a/cpp/include/cugraph_c/algorithms.h b/cpp/include/cugraph_c/algorithms.h index 9f0e5f2703e..fb958207a9d 100644 --- a/cpp/include/cugraph_c/algorithms.h +++ b/cpp/include/cugraph_c/algorithms.h @@ -37,7 +37,7 @@ typedef struct { * @param [in] result The result from pagerank * @return type erased array of vertex ids */ -cugraph_type_erased_device_array_t* cugraph_pagerank_result_get_vertices( +cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_vertices( cugraph_pagerank_result_t* result); /** @@ -46,7 +46,7 @@ cugraph_type_erased_device_array_t* cugraph_pagerank_result_get_vertices( * @param [in] result The result from pagerank * @return type erased array of pagerank values */ -cugraph_type_erased_device_array_t* cugraph_pagerank_result_get_pageranks( +cugraph_type_erased_device_array_view_t* cugraph_pagerank_result_get_pageranks( cugraph_pagerank_result_t* result); /** @@ -84,7 +84,7 @@ void cugraph_pagerank_result_free(cugraph_pagerank_result_t* result); cugraph_error_code_t cugraph_pagerank( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* precomputed_vertex_out_weight_sums, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, double alpha, double epsilon, size_t max_iterations, @@ -127,10 +127,10 @@ cugraph_error_code_t cugraph_pagerank( cugraph_error_code_t cugraph_personalized_pagerank( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* precomputed_vertex_out_weight_sums, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, // FIXME: Make this const, copy it if I need to temporarily modify internally - cugraph_type_erased_device_array_t* personalization_vertices, - const cugraph_type_erased_device_array_t* personalization_values, + cugraph_type_erased_device_array_view_t* personalization_vertices, + const cugraph_type_erased_device_array_view_t* personalization_values, double alpha, double epsilon, size_t max_iterations, @@ -155,7 +155,7 @@ typedef struct { * @param [in] result The result from bfs or sssp * @return type erased array of vertex ids */ -cugraph_type_erased_device_array_t* cugraph_paths_result_get_vertices( +cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_vertices( cugraph_paths_result_t* result); /** @@ -164,7 +164,7 @@ cugraph_type_erased_device_array_t* cugraph_paths_result_get_vertices( * @param [in] result The result from bfs or sssp * @return type erased array of distances */ -cugraph_type_erased_device_array_t* cugraph_paths_result_get_distances( +cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_distances( cugraph_paths_result_t* result); /** @@ -175,7 +175,7 @@ cugraph_type_erased_device_array_t* cugraph_paths_result_get_distances( * compute_predecessors was FALSE in the call to bfs or sssp that * produced this result. */ -cugraph_type_erased_device_array_t* cugraph_paths_result_get_predecessors( +cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_predecessors( cugraph_paths_result_t* result); /** @@ -216,7 +216,7 @@ cugraph_error_code_t cugraph_bfs( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, // FIXME: Make this const, copy it if I need to temporarily modify internally - cugraph_type_erased_device_array_t* sources, + cugraph_type_erased_device_array_view_t* sources, bool_t direction_optimizing, size_t depth_limit, bool_t compute_predecessors, @@ -280,13 +280,14 @@ typedef struct { * be populated if error code is not CUGRAPH_SUCCESS * @return error code */ -cugraph_error_code_t cugraph_extract_paths(const cugraph_resource_handle_t* handle, - cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* sources, - const cugraph_paths_result_t* paths_result, - const cugraph_type_erased_device_array_t* destinations, - cugraph_extract_paths_result_t** result, - cugraph_error_t** error); +cugraph_error_code_t cugraph_extract_paths( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* sources, + const cugraph_paths_result_t* paths_result, + const cugraph_type_erased_device_array_view_t* destinations, + cugraph_extract_paths_result_t** result, + cugraph_error_t** error); /** * @brief Get the max path length from extract_paths result @@ -302,7 +303,7 @@ size_t cugraph_extract_paths_result_get_max_path_length(cugraph_extract_paths_re * @param [in] result The result from extract_paths * @return type erased array pointing to the matrix in device memory */ -cugraph_type_erased_device_array_t* cugraph_extract_paths_result_get_paths( +cugraph_type_erased_device_array_view_t* cugraph_extract_paths_result_get_paths( cugraph_extract_paths_result_t* result); /** diff --git a/cpp/include/cugraph_c/array.h b/cpp/include/cugraph_c/array.h index 50b085fbd3b..86ed6d9208b 100644 --- a/cpp/include/cugraph_c/array.h +++ b/cpp/include/cugraph_c/array.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -26,16 +26,24 @@ typedef struct { int align_; } cugraph_type_erased_device_array_t; +typedef struct { + int align_; +} cugraph_type_erased_device_array_view_t; + typedef struct { int align_; } cugraph_type_erased_host_array_t; +typedef struct { + int align_; +} cugraph_type_erased_host_array_view_t; + /** * @brief Create a type erased device array * * @param [in] handle Handle for accessing resources - * @param [in] dtype The type of array to create * @param [in] n_elems The number of elements in the array + * @param [in] dtype The type of array to create * @param [out] array Pointer to the location to store the pointer to the device array * @param [out] error Pointer to an error object storing details of any error. Will * be populated if error code is not CUGRAPH_SUCCESS @@ -43,8 +51,8 @@ typedef struct { */ cugraph_error_code_t cugraph_type_erased_device_array_create( const cugraph_resource_handle_t* handle, - data_type_id_t dtype, size_t n_elems, + data_type_id_t dtype, cugraph_type_erased_device_array_t** array, cugraph_error_t** error); @@ -55,44 +63,89 @@ cugraph_error_code_t cugraph_type_erased_device_array_create( */ void cugraph_type_erased_device_array_free(cugraph_type_erased_device_array_t* p); +#if 0 +// FIXME: Not implemented, need to discuss if this can work. We will either implement +// this later or delete it from the interface once we resolve how to handle this /** - * @brief Get the size of a type erased device array + * @brief Release the raw pointer of the type erased device array + * + * The caller is now responsible for freeing the device pointer * * @param [in] p Pointer to the type erased device array + * @return Pointer (device memory) for the data in the array + */ +void* cugraph_type_erased_device_array_release(cugraph_type_erased_device_array_t* p); +#endif + +/** + * @brief Create a type erased device array view from + * a type erased device array + * + * @param [in] array Pointer to the type erased device array + * @return Pointer to the view of the host array + */ +cugraph_type_erased_device_array_view_t* cugraph_type_erased_device_array_view( + cugraph_type_erased_device_array_t* array); + +/** + * @brief Create a type erased device array view from + * a raw device pointer. + * + * @param [in] pointer Raw device pointer + * @param [in] n_elems The number of elements in the array + * @param [in] dtype The type of array to create + * @return Pointer to the view of the host array + */ +cugraph_type_erased_device_array_view_t* cugraph_type_erased_device_array_view_create( + void* pointer, size_t n_elems, data_type_id_t dtype); + +/** + * @brief Destroy a type erased device array view + * + * @param [in] p Pointer to the type erased device array view + */ +void cugraph_type_erased_device_array_view_free(cugraph_type_erased_device_array_view_t* p); + +/** + * @brief Get the size of a type erased device array view + * + * @param [in] p Pointer to the type erased device array view * @return The number of elements in the array */ -size_t cugraph_type_erased_device_array_size(const cugraph_type_erased_device_array_t* p); +size_t cugraph_type_erased_device_array_view_size(const cugraph_type_erased_device_array_view_t* p); /** - * @brief Get the type of a type erased device array + * @brief Get the type of a type erased device array view * - * @param [in] p Pointer to the type erased device array + * @param [in] p Pointer to the type erased device array view * @return The type of the elements in the array */ -data_type_id_t cugraph_type_erased_device_array_type(const cugraph_type_erased_device_array_t* p); +data_type_id_t cugraph_type_erased_device_array_view_type( + const cugraph_type_erased_device_array_view_t* p); /** - * @brief Get the raw pointer of the type erased device array + * @brief Get the raw pointer of the type erased device array view * - * @param [in] p Pointer to the type erased device array + * @param [in] p Pointer to the type erased device array view * @return Pointer (device memory) for the data in the array */ -const void* cugraph_type_erased_device_array_pointer(const cugraph_type_erased_device_array_t* p); +const void* cugraph_type_erased_device_array_view_pointer( + const cugraph_type_erased_device_array_view_t* p); /** * @brief Create a type erased host array * * @param [in] handle Handle for accessing resources - * @param [in] dtype The type of array to create * @param [in] n_elems The number of elements in the array + * @param [in] dtype The type of array to create * @param [out] array Pointer to the location to store the pointer to the host array * @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_type_erased_host_array_create(const cugraph_resource_handle_t* handle, - data_type_id_t dtype, size_t n_elems, + data_type_id_t dtype, cugraph_type_erased_host_array_t** array, cugraph_error_t** error); @@ -103,43 +156,86 @@ cugraph_error_code_t cugraph_type_erased_host_array_create(const cugraph_resourc */ void cugraph_type_erased_host_array_free(cugraph_type_erased_host_array_t* p); +#if 0 +// FIXME: Not implemented, need to discuss if this can work. We will either implement +// this later or delete it from the interface once we resolve how to handle this /** - * @brief Get the size of a type erased host array + * @brief Release the raw pointer of the type erased host array + * + * The caller is now responsible for freeing the host pointer * * @param [in] p Pointer to the type erased host array + * @return Pointer (host memory) for the data in the array + */ +void* cugraph_type_erased_host_array_release(cugraph_type_erased_host_array_t* p); +#endif + +/** + * @brief Create a type erased host array view from + * a type erased host array + * + * @param [in] array Pointer to the type erased host array + * @return Pointer to the view of the host array + */ +cugraph_type_erased_host_array_view_t* cugraph_type_erased_host_array_view( + cugraph_type_erased_host_array_t* array); + +/** + * @brief Create a type erased host array view from + * a raw host pointer. + * + * @param [in] pointer Raw host pointer + * @param [in] n_elems The number of elements in the array + * @param [in] dtype The type of array to create + * @return pointer to the view of the host array + */ +cugraph_type_erased_host_array_view_t* cugraph_type_erased_host_array_view_create( + void* pointer, size_t n_elems, data_type_id_t dtype); + +/** + * @brief Destroy a type erased host array view + * + * @param [in] p Pointer to the type erased host array view + */ +void cugraph_type_erased_host_array_view_free(cugraph_type_erased_host_array_view_t* p); + +/** + * @brief Get the size of a type erased host array view + * + * @param [in] p Pointer to the type erased host array view * @return The number of elements in the array */ -size_t cugraph_type_erased_host_array_size(const cugraph_type_erased_host_array_t* p); +size_t cugraph_type_erased_host_array_size(const cugraph_type_erased_host_array_view_t* p); /** - * @brief Get the type of a type erased host array + * @brief Get the type of a type erased host array view * - * @param [in] p Pointer to the type erased host array + * @param [in] p Pointer to the type erased host array view * @return The type of the elements in the array */ -data_type_id_t cugraph_type_erased_host_array_type(const cugraph_type_erased_host_array_t* p); +data_type_id_t cugraph_type_erased_host_array_type(const cugraph_type_erased_host_array_view_t* p); /** - * @brief Get the raw pointer of the type erased host array + * @brief Get the raw pointer of the type erased host array view * - * @param [in] p Pointer to the type erased host array + * @param [in] p Pointer to the type erased host array view * @return Pointer (host memory) for the data in the array */ -void* cugraph_type_erased_host_array_pointer(const cugraph_type_erased_host_array_t* p); +void* cugraph_type_erased_host_array_pointer(const cugraph_type_erased_host_array_view_t* p); /** - * @brief Copy data from host to a type erased device array + * @brief Copy data from host to a type erased device array view * * @param [in] handle Handle for accessing resources - * @param [out] dst Pointer to the type erased device array + * @param [out] dst Pointer to the type erased device array view * @param [in] h_src Pointer to host array to copy into device memory * @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_type_erased_device_array_copy_from_host( +cugraph_error_code_t cugraph_type_erased_device_array_view_copy_from_host( const cugraph_resource_handle_t* handle, - cugraph_type_erased_device_array_t* dst, + cugraph_type_erased_device_array_view_t* dst, const byte_t* h_src, cugraph_error_t** error); @@ -148,15 +244,31 @@ cugraph_error_code_t cugraph_type_erased_device_array_copy_from_host( * * @param [in] handle Handle for accessing resources * @param [out] h_dst Pointer to host array - * @param [in] src Pointer to the type erased device array to copy from + * @param [in] src Pointer to the type erased device array view source * @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_type_erased_device_array_copy_to_host( +cugraph_error_code_t cugraph_type_erased_device_array_view_copy_to_host( const cugraph_resource_handle_t* handle, byte_t* h_dst, - const cugraph_type_erased_device_array_t* src, + const cugraph_type_erased_device_array_view_t* src, + cugraph_error_t** error); + +/** + * @brief Copy data between two type erased device array views + * + * @param [in] handle Handle for accessing resources + * @param [out] dst Pointer to type erased device array view destination + * @param [in] src Pointer to type erased device array view source + * @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_type_erased_device_array_view_copy( + const cugraph_resource_handle_t* handle, + cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* src, cugraph_error_t** error); #ifdef __cplusplus diff --git a/cpp/include/cugraph_c/graph.h b/cpp/include/cugraph_c/graph.h index 6860290ec82..6c64c317d17 100644 --- a/cpp/include/cugraph_c/graph.h +++ b/cpp/include/cugraph_c/graph.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -55,9 +55,9 @@ typedef struct { */ cugraph_error_code_t cugraph_sg_graph_create(const cugraph_resource_handle_t* handle, const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_t* src, - const cugraph_type_erased_device_array_t* dst, - const cugraph_type_erased_device_array_t* weights, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, bool_t store_transposed, bool_t renumber, bool_t check, @@ -97,11 +97,11 @@ void cugraph_sg_graph_free(cugraph_graph_t* graph); cugraph_error_code_t cugraph_mg_graph_create( const cugraph_resource_handle_t* handle, const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_t* src, - const cugraph_type_erased_device_array_t* dst, - const cugraph_type_erased_device_array_t* weights, - const cugraph_type_erased_host_array_t* vertex_partition_offsets, - const cugraph_type_erased_host_array_t* segment_offsets, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, + const cugraph_type_erased_host_array_view_t* vertex_partition_offsets, + const cugraph_type_erased_host_array_view_t* segment_offsets, bool_t store_transposed, size_t num_vertices, size_t num_edges, diff --git a/cpp/src/c_api/array.cpp b/cpp/src/c_api/array.cpp index 01253954b1e..c6e6d8ac262 100644 --- a/cpp/src/c_api/array.cpp +++ b/cpp/src/c_api/array.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -32,8 +32,8 @@ cugraph::visitors::DTypes dtypes_mapping[] = {cugraph::visitors::DTypes::INT32, extern "C" cugraph_error_code_t cugraph_type_erased_device_array_create( const cugraph_resource_handle_t* handle, - data_type_id_t dtype, size_t n_elems, + data_type_id_t dtype, cugraph_type_erased_device_array_t** array, cugraph_error_t** error) { @@ -68,33 +68,71 @@ extern "C" void cugraph_type_erased_device_array_free(cugraph_type_erased_device delete internal_pointer; } -extern "C" size_t cugraph_type_erased_device_array_size(const cugraph_type_erased_device_array_t* p) +#if 0 +// NOTE: This can't work. rmm::device_buffer doesn't support release, that would leave a raw +// pointer in the wild with no idea how to free it. I suppose that could be done +// (I imagine you can do that with unique_ptr), but it's not currently supported and I'm +// not sure *this* use case is sufficient justification to adding a potentially +// dangerous feature. +extern "C" void* cugraph_type_erased_device_array_release(cugraph_type_erased_device_array_t* p) +{ + auto internal_pointer = reinterpret_cast(p); + return internal_pointer->data_.release(); +} +#endif + +extern "C" cugraph_type_erased_device_array_view_t* cugraph_type_erased_device_array_view( + cugraph_type_erased_device_array_t* array) +{ + auto internal_pointer = + reinterpret_cast(array); + return reinterpret_cast(internal_pointer->view()); +} + +cugraph_type_erased_device_array_view_t* cugraph_type_erased_device_array_view_create( + void* pointer, size_t n_elems, data_type_id_t dtype) +{ + return reinterpret_cast( + new cugraph::c_api::cugraph_type_erased_device_array_view_t{ + pointer, n_elems, n_elems * (::data_type_sz[dtype]), dtype}); +} + +extern "C" void cugraph_type_erased_device_array_view_free( + cugraph_type_erased_device_array_view_t* p) +{ + auto internal_pointer = + reinterpret_cast(p); + delete internal_pointer; +} + +extern "C" size_t cugraph_type_erased_device_array_view_size( + const cugraph_type_erased_device_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); + reinterpret_cast(p); return internal_pointer->size_; } -extern "C" data_type_id_t cugraph_type_erased_device_array_type( - const cugraph_type_erased_device_array_t* p) +extern "C" data_type_id_t cugraph_type_erased_device_array_view_type( + const cugraph_type_erased_device_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); + reinterpret_cast(p); return internal_pointer->type_; } -extern "C" const void* cugraph_type_erased_device_array_pointer( - const cugraph_type_erased_device_array_t* p) +extern "C" const void* cugraph_type_erased_device_array_view_pointer( + const cugraph_type_erased_device_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); - return internal_pointer->data_.data(); + reinterpret_cast(p); + return internal_pointer->data_; } extern "C" cugraph_error_code_t cugraph_type_erased_host_array_create( const cugraph_resource_handle_t* handle, - data_type_id_t dtype, size_t n_elems, + data_type_id_t dtype, cugraph_type_erased_host_array_t** array, cugraph_error_t** error) { @@ -112,11 +150,10 @@ extern "C" cugraph_error_code_t cugraph_type_erased_host_array_create( size_t n_bytes = n_elems * (::data_type_sz[dtype]); - cugraph::c_api::cugraph_type_erased_host_array_t* ret_value = + *array = reinterpret_cast( new cugraph::c_api::cugraph_type_erased_host_array_t{ - new std::byte[n_bytes], n_elems, n_bytes, dtype}; + std::make_unique(n_bytes), n_elems, n_bytes, dtype}); - *array = reinterpret_cast(ret_value); return CUGRAPH_SUCCESS; } catch (std::exception const& ex) { auto tmp_error = new cugraph::c_api::cugraph_error_t{ex.what()}; @@ -128,35 +165,68 @@ extern "C" cugraph_error_code_t cugraph_type_erased_host_array_create( extern "C" void cugraph_type_erased_host_array_free(cugraph_type_erased_host_array_t* p) { auto internal_pointer = reinterpret_cast(p); - delete[] internal_pointer->data_; delete internal_pointer; } -extern "C" size_t cugraph_type_erased_host_array_size(const cugraph_type_erased_host_array_t* p) +#if 0 +// Leaving this one out since we're not doing the more important device version +extern "C" void* cugraph_type_erased_host_array_release(const cugraph_type_erased_host_array_t* p) +{ + auto internal_pointer = reinterpret_cast(p); + return internal_pointer->data_.release(); +} +#endif + +extern "C" cugraph_type_erased_host_array_view_t* cugraph_type_erased_host_array_view( + cugraph_type_erased_host_array_t* array) +{ + auto internal_pointer = + reinterpret_cast(array); + return reinterpret_cast(internal_pointer->view()); +} + +extern "C" cugraph_type_erased_host_array_view_t* cugraph_type_erased_host_array_view_create( + void* pointer, size_t n_elems, data_type_id_t dtype) +{ + return reinterpret_cast( + new cugraph::c_api::cugraph_type_erased_host_array_view_t{ + static_cast(pointer), n_elems, n_elems * (::data_type_sz[dtype]), dtype}); +} + +extern "C" void cugraph_type_erased_host_array_view_free(cugraph_type_erased_host_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); + reinterpret_cast(p); + delete internal_pointer; +} + +extern "C" size_t cugraph_type_erased_host_array_size( + const cugraph_type_erased_host_array_view_t* p) +{ + auto internal_pointer = + reinterpret_cast(p); return internal_pointer->size_; } -extern "C" data_type_id_t cugraph_type_erased_host_array_type( - const cugraph_type_erased_host_array_t* p) +extern "C" data_type_id_t cugraph_type_erased_host_array_view_type( + const cugraph_type_erased_host_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); + reinterpret_cast(p); return internal_pointer->type_; } -extern "C" void* cugraph_type_erased_host_array_pointer(const cugraph_type_erased_host_array_t* p) +extern "C" void* cugraph_type_erased_host_array_pointer( + const cugraph_type_erased_host_array_view_t* p) { auto internal_pointer = - reinterpret_cast(p); + reinterpret_cast(p); return internal_pointer->data_; } -extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_from_host( +extern "C" cugraph_error_code_t cugraph_type_erased_device_array_view_copy_from_host( const cugraph_resource_handle_t* handle, - cugraph_type_erased_device_array_t* dst, + cugraph_type_erased_device_array_view_t* dst, const byte_t* h_src, cugraph_error_t** error) { @@ -165,7 +235,7 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_from_host( try { raft::handle_t const* raft_handle = reinterpret_cast(handle); auto internal_pointer = - reinterpret_cast(dst); + reinterpret_cast(dst); if (!raft_handle) { *error = reinterpret_cast( @@ -173,9 +243,9 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_from_host( return CUGRAPH_INVALID_HANDLE; } - raft::update_device(reinterpret_cast(internal_pointer->data_.data()), + raft::update_device(reinterpret_cast(internal_pointer->data_), h_src, - internal_pointer->data_.size(), + internal_pointer->num_bytes(), raft_handle->get_stream()); return CUGRAPH_SUCCESS; @@ -186,10 +256,10 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_from_host( } } -extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_to_host( +extern "C" cugraph_error_code_t cugraph_type_erased_device_array_view_copy_to_host( const cugraph_resource_handle_t* handle, byte_t* h_dst, - const cugraph_type_erased_device_array_t* src, + const cugraph_type_erased_device_array_view_t* src, cugraph_error_t** error) { *error = nullptr; @@ -197,7 +267,7 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_to_host( try { raft::handle_t const* raft_handle = reinterpret_cast(handle); auto internal_pointer = - reinterpret_cast(src); + reinterpret_cast(src); if (!raft_handle) { *error = reinterpret_cast( @@ -206,8 +276,8 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_to_host( } raft::update_host(h_dst, - reinterpret_cast(internal_pointer->data_.data()), - internal_pointer->data_.size(), + reinterpret_cast(internal_pointer->data_), + internal_pointer->num_bytes(), raft_handle->get_stream()); return CUGRAPH_SUCCESS; @@ -217,3 +287,43 @@ extern "C" cugraph_error_code_t cugraph_type_erased_device_array_copy_to_host( return CUGRAPH_UNKNOWN_ERROR; } } + +extern "C" cugraph_error_code_t cugraph_type_erased_device_array_view_copy( + const cugraph_resource_handle_t* handle, + cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* src, + cugraph_error_t** error) +{ + *error = nullptr; + + try { + raft::handle_t const* raft_handle = reinterpret_cast(handle); + auto internal_pointer_dst = + reinterpret_cast(dst); + auto internal_pointer_src = + reinterpret_cast(src); + + if (!raft_handle) { + *error = reinterpret_cast( + new cugraph::c_api::cugraph_error_t{"invalid resource handle"}); + return CUGRAPH_INVALID_HANDLE; + } + + if (internal_pointer_src->num_bytes() != internal_pointer_dst->num_bytes()) { + *error = reinterpret_cast( + new cugraph::c_api::cugraph_error_t{"source and destination arrays are different sizes"}); + return CUGRAPH_INVALID_INPUT; + } + + raft::copy(reinterpret_cast(internal_pointer_dst->data_), + reinterpret_cast(internal_pointer_src->data_), + internal_pointer_src->num_bytes(), + raft_handle->get_stream()); + + return CUGRAPH_SUCCESS; + } catch (std::exception const& ex) { + auto tmp_error = new cugraph::c_api::cugraph_error_t{ex.what()}; + *error = reinterpret_cast(tmp_error); + return CUGRAPH_UNKNOWN_ERROR; + } +} diff --git a/cpp/src/c_api/array.hpp b/cpp/src/c_api/array.hpp index 2df54fe7d30..26465e05d3b 100644 --- a/cpp/src/c_api/array.hpp +++ b/cpp/src/c_api/array.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -27,16 +27,39 @@ namespace c_api { extern cugraph::visitors::DTypes dtypes_mapping[data_type_id_t::NTYPES]; +struct cugraph_type_erased_device_array_view_t { + void* data_; + size_t size_; + size_t num_bytes_; + data_type_id_t type_; + + template + T* as_type() + { + return reinterpret_cast(data_); + } + + template + T const* as_type() const + { + return reinterpret_cast(data_); + } + + size_t num_bytes() const { return num_bytes_; } +}; + struct cugraph_type_erased_device_array_t { + // NOTE: size must be first here because the device buffer is released size_t size_; + // Why doesn't rmm::device_buffer support release? rmm::device_buffer data_; data_type_id_t type_; cugraph_type_erased_device_array_t(size_t size, - size_t nbytes, + size_t num_bytes, data_type_id_t type, rmm::cuda_stream_view const& stream_view) - : data_(nbytes, stream_view), size_(size), type_(type) + : size_(size), data_(num_bytes, stream_view), type_(type) { } @@ -46,29 +69,36 @@ struct cugraph_type_erased_device_array_t { { } - template - T* as_type() + auto view() { - return reinterpret_cast(data_.data()); + return new cugraph_type_erased_device_array_view_t{data_.data(), size_, data_.size(), type_}; } +}; + +struct cugraph_type_erased_host_array_view_t { + std::byte* data_; + size_t size_; + size_t num_bytes_; + data_type_id_t type_; template - T const* as_type() const + T* as_type() { - return reinterpret_cast(data_.data()); + return reinterpret_cast(data_); } + + size_t num_bytes() const { return num_bytes_; } }; struct cugraph_type_erased_host_array_t { - std::byte* data_; + std::unique_ptr data_; size_t size_; - size_t nbytes_; + size_t num_bytes_; data_type_id_t type_; - template - T* as_type() + auto view() { - return reinterpret_cast(data_); + return new cugraph_type_erased_host_array_view_t{data_.get(), size_, num_bytes_, type_}; } }; diff --git a/cpp/src/c_api/bfs.cpp b/cpp/src/c_api/bfs.cpp index e3efba84784..87e1051928d 100644 --- a/cpp/src/c_api/bfs.cpp +++ b/cpp/src/c_api/bfs.cpp @@ -33,7 +33,7 @@ namespace c_api { struct bfs_functor : public abstract_functor { raft::handle_t const& handle_; cugraph_graph_t* graph_; - cugraph_type_erased_device_array_t* sources_; + cugraph_type_erased_device_array_view_t* sources_; bool direction_optimizing_; size_t depth_limit_; bool compute_predecessors_; @@ -42,7 +42,7 @@ struct bfs_functor : public abstract_functor { bfs_functor(raft::handle_t const& handle, cugraph_graph_t* graph, - cugraph_type_erased_device_array_t* sources, + cugraph_type_erased_device_array_view_t* sources, bool direction_optimizing, size_t depth_limit, bool compute_predecessors, @@ -141,25 +141,28 @@ struct bfs_functor : public abstract_functor { } // namespace c_api } // namespace cugraph -extern "C" cugraph_type_erased_device_array_t* cugraph_paths_result_get_vertices( +extern "C" cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_vertices( cugraph_paths_result_t* result) { auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast(internal_pointer->vertex_ids_); + return reinterpret_cast( + internal_pointer->vertex_ids_->view()); } -extern "C" cugraph_type_erased_device_array_t* cugraph_paths_result_get_distances( +extern "C" cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_distances( cugraph_paths_result_t* result) { auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast(internal_pointer->distances_); + return reinterpret_cast( + internal_pointer->distances_->view()); } -extern "C" cugraph_type_erased_device_array_t* cugraph_paths_result_get_predecessors( +extern "C" cugraph_type_erased_device_array_view_t* cugraph_paths_result_get_predecessors( cugraph_paths_result_t* result) { auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast(internal_pointer->predecessors_); + return reinterpret_cast( + internal_pointer->predecessors_->view()); } extern "C" void cugraph_paths_result_free(cugraph_paths_result_t* result) @@ -173,7 +176,7 @@ extern "C" void cugraph_paths_result_free(cugraph_paths_result_t* result) extern "C" cugraph_error_code_t cugraph_bfs(const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - cugraph_type_erased_device_array_t* sources, + cugraph_type_erased_device_array_view_t* sources, bool_t direction_optimizing, size_t depth_limit, bool_t compute_predecessors, @@ -185,9 +188,10 @@ extern "C" cugraph_error_code_t cugraph_bfs(const cugraph_resource_handle_t* han *error = nullptr; try { - auto p_handle = reinterpret_cast(handle); - auto p_graph = reinterpret_cast(graph); - auto p_sources = reinterpret_cast(sources); + auto p_handle = reinterpret_cast(handle); + auto p_graph = reinterpret_cast(graph); + auto p_sources = + reinterpret_cast(sources); cugraph::c_api::bfs_functor functor(*p_handle, p_graph, diff --git a/cpp/src/c_api/extract_paths.cpp b/cpp/src/c_api/extract_paths.cpp index 5f6cd6885c5..35b3eba355d 100644 --- a/cpp/src/c_api/extract_paths.cpp +++ b/cpp/src/c_api/extract_paths.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -38,16 +38,16 @@ struct cugraph_extract_paths_result_t { struct extract_paths_functor : public abstract_functor { raft::handle_t const& handle_; cugraph_graph_t* graph_; - cugraph_type_erased_device_array_t const* sources_; + cugraph_type_erased_device_array_view_t const* sources_; cugraph_paths_result_t const* paths_result_; - cugraph_type_erased_device_array_t const* destinations_; + cugraph_type_erased_device_array_view_t const* destinations_; cugraph_extract_paths_result_t* result_{}; extract_paths_functor(raft::handle_t const& handle, cugraph_graph_t* graph, - cugraph_type_erased_device_array_t const* sources, + cugraph_type_erased_device_array_view_t const* sources, cugraph_paths_result_t const* paths_result, - cugraph_type_erased_device_array_t const* destinations) + cugraph_type_erased_device_array_view_t const* destinations) : abstract_functor(), handle_(handle), graph_(graph), @@ -93,8 +93,8 @@ struct extract_paths_functor : public abstract_functor { rmm::device_uvector predecessors(paths_result_->predecessors_->size_, handle_.get_stream()); raft::copy(predecessors.data(), - paths_result_->predecessors_->as_type(), - paths_result_->predecessors_->size_, + paths_result_->predecessors_->view()->as_type(), + paths_result_->predecessors_->view()->size_, handle_.get_stream()); // @@ -120,7 +120,7 @@ struct extract_paths_functor : public abstract_functor { cugraph::extract_bfs_paths( handle_, graph_view, - paths_result_->distances_->as_type(), + paths_result_->distances_->view()->as_type(), predecessors.data(), destinations.data(), destinations.size()); @@ -147,11 +147,12 @@ extern "C" size_t cugraph_extract_paths_result_get_max_path_length( return internal_pointer->max_path_length_; } -cugraph_type_erased_device_array_t* cugraph_extract_paths_result_get_paths( +cugraph_type_erased_device_array_view_t* cugraph_extract_paths_result_get_paths( cugraph_extract_paths_result_t* result) { auto internal_pointer = reinterpret_cast(result); - return reinterpret_cast(internal_pointer->paths_); + return reinterpret_cast( + internal_pointer->paths_->view()); } extern "C" void cugraph_extract_paths_result_free(cugraph_extract_paths_result_t* result) @@ -164,9 +165,9 @@ extern "C" void cugraph_extract_paths_result_free(cugraph_extract_paths_result_t extern "C" cugraph_error_code_t cugraph_extract_paths( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* sources, + const cugraph_type_erased_device_array_view_t* sources, const cugraph_paths_result_t* paths_result, - const cugraph_type_erased_device_array_t* destinations, + const cugraph_type_erased_device_array_view_t* destinations, cugraph_extract_paths_result_t** result, cugraph_error_t** error) { @@ -177,11 +178,12 @@ extern "C" cugraph_error_code_t cugraph_extract_paths( auto p_handle = reinterpret_cast(handle); auto p_graph = reinterpret_cast(graph); auto p_sources = - reinterpret_cast(sources); + reinterpret_cast(sources); auto p_paths_result = reinterpret_cast(paths_result); auto p_destinations = - reinterpret_cast(destinations); + reinterpret_cast( + destinations); cugraph::c_api::extract_paths_functor functor( *p_handle, p_graph, p_sources, p_paths_result, p_destinations); diff --git a/cpp/src/c_api/graph_mg.cpp b/cpp/src/c_api/graph_mg.cpp index 9fc3d9ba7af..b2448e46796 100644 --- a/cpp/src/c_api/graph_mg.cpp +++ b/cpp/src/c_api/graph_mg.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -16,14 +16,20 @@ #include +// FIXME: assume that this function will directly call +// create_graph_from_edgelist() instead of invoking a graph constructor, in +// which case, some parameters here (e.g. vertex_partition_offsets, +// segment_offsets) are related to implementation details and unnecessary if +// this function calls create_graph_from_edgelist(). + extern "C" cugraph_error_code_t cugraph_mg_graph_create( const cugraph_resource_handle_t* handle, const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_t* src, - const cugraph_type_erased_device_array_t* dst, - const cugraph_type_erased_device_array_t* weights, - const cugraph_type_erased_host_array_t* vertex_partition_offsets, - const cugraph_type_erased_host_array_t* segment_offsets, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, + const cugraph_type_erased_host_array_view_t* vertex_partition_offsets, + const cugraph_type_erased_host_array_view_t* segment_offsets, bool_t store_transposed, size_t num_vertices, size_t num_edges, diff --git a/cpp/src/c_api/graph_sg.cpp b/cpp/src/c_api/graph_sg.cpp index e51e94edeeb..6546e135bce 100644 --- a/cpp/src/c_api/graph_sg.cpp +++ b/cpp/src/c_api/graph_sg.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -26,15 +26,17 @@ #include +#include + namespace cugraph { namespace c_api { struct create_graph_functor : public abstract_functor { raft::handle_t const& handle_; cugraph_graph_properties_t const* properties_; - c_api::cugraph_type_erased_device_array_t const* src_; - c_api::cugraph_type_erased_device_array_t const* dst_; - c_api::cugraph_type_erased_device_array_t const* weights_; + c_api::cugraph_type_erased_device_array_view_t const* src_; + c_api::cugraph_type_erased_device_array_view_t const* dst_; + c_api::cugraph_type_erased_device_array_view_t const* weights_; bool_t renumber_; bool_t check_; data_type_id_t edge_type_; @@ -42,9 +44,9 @@ struct create_graph_functor : public abstract_functor { create_graph_functor(raft::handle_t const& handle, cugraph_graph_properties_t const* properties, - c_api::cugraph_type_erased_device_array_t const* src, - c_api::cugraph_type_erased_device_array_t const* dst, - c_api::cugraph_type_erased_device_array_t const* weights, + c_api::cugraph_type_erased_device_array_view_t const* src, + c_api::cugraph_type_erased_device_array_view_t const* dst, + c_api::cugraph_type_erased_device_array_view_t const* weights, bool_t renumber, bool_t check, data_type_id_t edge_type) @@ -173,9 +175,9 @@ struct destroy_graph_functor : public abstract_functor { extern "C" cugraph_error_code_t cugraph_sg_graph_create( const cugraph_resource_handle_t* handle, const cugraph_graph_properties_t* properties, - const cugraph_type_erased_device_array_t* src, - const cugraph_type_erased_device_array_t* dst, - const cugraph_type_erased_device_array_t* weights, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, bool_t store_transposed, bool_t renumber, bool_t check, @@ -183,16 +185,21 @@ extern "C" cugraph_error_code_t cugraph_sg_graph_create( cugraph_error_t** error) { constexpr bool multi_gpu = false; - constexpr size_t int32_threshold{2 ^ 31 - 1}; + // FIXME: There could be a case where the vertex_t is int64_t but the number + // of edges is less than 2^31-1. The if statement below could then be modified + // to catch this case. + constexpr size_t int32_threshold{std::numeric_limits::max()}; *graph = nullptr; *error = nullptr; auto p_handle = reinterpret_cast(handle); - auto p_src = reinterpret_cast(src); - auto p_dst = reinterpret_cast(dst); + auto p_src = + reinterpret_cast(src); + auto p_dst = + reinterpret_cast(dst); auto p_weights = - reinterpret_cast(weights); + reinterpret_cast(weights); CAPI_EXPECTS(p_src->size_ == p_dst->size_, CUGRAPH_INVALID_INPUT, @@ -202,6 +209,7 @@ extern "C" cugraph_error_code_t cugraph_sg_graph_create( CUGRAPH_INVALID_INPUT, "Invalid input arguments: src type != dst type.", *error); + CAPI_EXPECTS(!weights || (p_weights->size_ == p_src->size_), CUGRAPH_INVALID_INPUT, "Invalid input arguments: src size != weights size.", diff --git a/cpp/src/c_api/pagerank.cpp b/cpp/src/c_api/pagerank.cpp index c309b3a4789..ec2c1d4dd32 100644 --- a/cpp/src/c_api/pagerank.cpp +++ b/cpp/src/c_api/pagerank.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, NVIDIA CORPORATION. + * Copyright (c) 2021-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. @@ -39,9 +39,9 @@ struct cugraph_pagerank_result_t { struct pagerank_functor : public abstract_functor { raft::handle_t const& handle_; cugraph_graph_t* graph_; - cugraph_type_erased_device_array_t const* precomputed_vertex_out_weight_sums_; - cugraph_type_erased_device_array_t* personalization_vertices_; - cugraph_type_erased_device_array_t const* personalization_values_; + 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_; @@ -49,16 +49,17 @@ struct pagerank_functor : public abstract_functor { bool do_expensive_check_; cugraph_pagerank_result_t* result_{}; - pagerank_functor(raft::handle_t const& handle, - cugraph_graph_t* graph, - cugraph_type_erased_device_array_t const* precomputed_vertex_out_weight_sums, - cugraph_type_erased_device_array_t* personalization_vertices, - cugraph_type_erased_device_array_t const* personalization_values, - double alpha, - double epsilon, - size_t max_iterations, - bool has_initial_guess, - bool do_expensive_check) + pagerank_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) : abstract_functor(), handle_(handle), graph_(graph), @@ -151,18 +152,20 @@ struct pagerank_functor : public abstract_functor { } // namespace c_api } // namespace cugraph -extern "C" cugraph_type_erased_device_array_t* cugraph_pagerank_result_get_vertices( +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_); + return reinterpret_cast( + internal_pointer->vertex_ids_->view()); } -extern "C" cugraph_type_erased_device_array_t* cugraph_pagerank_result_get_pageranks( +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_); + return reinterpret_cast( + internal_pointer->pageranks_->view()); } extern "C" void cugraph_pagerank_result_free(cugraph_pagerank_result_t* result) @@ -176,7 +179,7 @@ extern "C" void cugraph_pagerank_result_free(cugraph_pagerank_result_t* result) extern "C" cugraph_error_code_t cugraph_pagerank( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* precomputed_vertex_out_weight_sums, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, double alpha, double epsilon, size_t max_iterations, @@ -193,7 +196,7 @@ extern "C" cugraph_error_code_t cugraph_pagerank( auto p_graph = reinterpret_cast(graph); auto p_precomputed_vertex_out_weight_sums = - reinterpret_cast( + reinterpret_cast( precomputed_vertex_out_weight_sums); cugraph::c_api::pagerank_functor functor(*p_handle, @@ -231,9 +234,9 @@ extern "C" cugraph_error_code_t cugraph_pagerank( extern "C" cugraph_error_code_t cugraph_personalized_pagerank( const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, - const cugraph_type_erased_device_array_t* precomputed_vertex_out_weight_sums, - cugraph_type_erased_device_array_t* personalization_vertices, - const cugraph_type_erased_device_array_t* personalization_values, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, + cugraph_type_erased_device_array_view_t* personalization_vertices, + const cugraph_type_erased_device_array_view_t* personalization_values, double alpha, double epsilon, size_t max_iterations, @@ -250,13 +253,13 @@ extern "C" cugraph_error_code_t cugraph_personalized_pagerank( auto p_graph = reinterpret_cast(graph); auto p_precomputed_vertex_out_weight_sums = - reinterpret_cast( + reinterpret_cast( precomputed_vertex_out_weight_sums); auto p_personalization_vertices = - reinterpret_cast( + reinterpret_cast( personalization_vertices); auto p_personalization_values = - reinterpret_cast( + reinterpret_cast( personalization_vertices); cugraph::c_api::pagerank_functor functor(*p_handle, diff --git a/cpp/src/structure/graph_impl.cuh b/cpp/src/structure/graph_impl.cuh index e969bb4a6a3..ef64e60ac2f 100644 --- a/cpp/src/structure/graph_impl.cuh +++ b/cpp/src/structure/graph_impl.cuh @@ -1190,8 +1190,8 @@ graph_t( handle, std::move(vertex_span), - std::move(edgelist_cols), std::move(edgelist_rows), + std::move(edgelist_cols), std::move(edgelist_weights), graph_properties_t{is_multigraph, false}, renumber); diff --git a/cpp/tests/c_api/bfs_test.c b/cpp/tests/c_api/bfs_test.c index bb93d0ce1be..e62237cc0cd 100644 --- a/cpp/tests/c_api/bfs_test.c +++ b/cpp/tests/c_api/bfs_test.c @@ -46,6 +46,7 @@ int generic_bfs_test(vertex_t* h_src, cugraph_graph_t* p_graph = NULL; cugraph_paths_result_t* p_result = NULL; cugraph_type_erased_device_array_t* p_sources = NULL; + cugraph_type_erased_device_array_view_t* p_source_view = NULL; p_handle = cugraph_create_resource_handle(); TEST_ASSERT(test_ret_value, p_handle != NULL, "resource handle creation failed."); @@ -53,16 +54,23 @@ int generic_bfs_test(vertex_t* h_src, ret_code = create_test_graph( p_handle, h_src, h_dst, h_wgt, num_edges, store_transposed, &p_graph, &ret_error); + /* + * FIXME: in create_graph_test.c, variables are defined but then hard-coded to + * the constant INT32. It would be better to pass the types into the functions + * in both cases so that the test cases could be parameterized in the main. + */ ret_code = - cugraph_type_erased_device_array_create(p_handle, INT32, num_seeds, &p_sources, &ret_error); + cugraph_type_erased_device_array_create(p_handle, num_seeds, INT32, &p_sources, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "p_sources create failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host( - p_handle, p_sources, (byte_t*)h_seeds, &ret_error); + p_source_view = cugraph_type_erased_device_array_view(p_sources); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host( + p_handle, p_source_view, (byte_t*)h_seeds, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); ret_code = cugraph_bfs( - p_handle, p_graph, p_sources, FALSE, depth_limit, TRUE, FALSE, &p_result, &ret_error); + p_handle, p_graph, p_source_view, FALSE, depth_limit, TRUE, FALSE, &p_result, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_bfs failed."); cugraph_type_erased_device_array_t* vertices; @@ -77,15 +85,15 @@ int generic_bfs_test(vertex_t* h_src, vertex_t h_distances[num_vertices]; vertex_t h_predecessors[num_vertices]; - ret_code = cugraph_type_erased_device_array_copy_to_host( + 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_copy_to_host( + ret_code = cugraph_type_erased_device_array_view_copy_to_host( p_handle, (byte_t*)h_distances, distances, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); - ret_code = cugraph_type_erased_device_array_copy_to_host( + ret_code = cugraph_type_erased_device_array_view_copy_to_host( p_handle, (byte_t*)h_predecessors, predecessors, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); @@ -142,9 +150,9 @@ int test_bfs_with_transpose() vertex_t src[] = {0, 1, 1, 2, 2, 2, 3, 4}; vertex_t dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; weight_t wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; - vertex_t seeds[] = {5}; - vertex_t expected_distances[] = {3, 2, 2, 1, 1, 0}; - vertex_t expected_predecessors[] = {1, 3, 3, 5, 5, -1}; + vertex_t seeds[] = {0}; + vertex_t expected_distances[] = {0, 1, 2147483647, 2, 2, 3}; + vertex_t expected_predecessors[] = {-1, 0, -1, 1, 1, 3}; // Bfs wants store_transposed = FALSE // This call will force cugraph_bfs to transpose the graph diff --git a/cpp/tests/c_api/create_graph_test.c b/cpp/tests/c_api/create_graph_test.c index e4368fe2f5a..63837b52074 100644 --- a/cpp/tests/c_api/create_graph_test.c +++ b/cpp/tests/c_api/create_graph_test.c @@ -56,31 +56,38 @@ int test_create_sg_graph_simple() cugraph_type_erased_device_array_t* src; cugraph_type_erased_device_array_t* dst; cugraph_type_erased_device_array_t* wgt; + cugraph_type_erased_device_array_view_t* src_view; + cugraph_type_erased_device_array_view_t* dst_view; + cugraph_type_erased_device_array_view_t* wgt_view; - ret_code = cugraph_type_erased_device_array_create(p_handle, vertex_tid, num_edges, &src, &ret_error); + ret_code = cugraph_type_erased_device_array_create(p_handle, num_edges, vertex_tid, &src, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src create failed."); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); - ret_code = cugraph_type_erased_device_array_create(p_handle, vertex_tid, num_edges, &dst, &ret_error); + ret_code = cugraph_type_erased_device_array_create(p_handle, num_edges, vertex_tid, &dst, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "dst create failed."); - ret_code = cugraph_type_erased_device_array_create(p_handle, weight_tid, num_edges, &wgt, &ret_error); + ret_code = cugraph_type_erased_device_array_create(p_handle, num_edges, weight_tid, &wgt, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt create failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host(p_handle, src, (byte_t*)h_src, &ret_error); + src_view = cugraph_type_erased_device_array_view(src); + dst_view = cugraph_type_erased_device_array_view(dst); + wgt_view = cugraph_type_erased_device_array_view(wgt); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host(p_handle, src_view, (byte_t*)h_src, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host(p_handle, dst, (byte_t*)h_dst, &ret_error); + ret_code = cugraph_type_erased_device_array_view_copy_from_host(p_handle, dst_view, (byte_t*)h_dst, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "dst copy_from_host failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host(p_handle, wgt, (byte_t*)h_wgt, &ret_error); + ret_code = cugraph_type_erased_device_array_view_copy_from_host(p_handle, wgt_view, (byte_t*)h_wgt, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); ret_code = cugraph_sg_graph_create(p_handle, &properties, - src, - dst, - wgt, + src_view, + dst_view, + wgt_view, FALSE, FALSE, FALSE, @@ -90,10 +97,11 @@ int test_create_sg_graph_simple() cugraph_sg_graph_free(p_graph); + cugraph_type_erased_device_array_view_free(wgt_view); + cugraph_type_erased_device_array_view_free(dst_view); + cugraph_type_erased_device_array_view_free(src_view); cugraph_type_erased_device_array_free(wgt); - cugraph_type_erased_device_array_free(dst); - cugraph_type_erased_device_array_free(src); cugraph_free_resource_handle(p_handle); diff --git a/cpp/tests/c_api/extract_paths_test.c b/cpp/tests/c_api/extract_paths_test.c index f2ddc65dda0..3f9ea8e3911 100644 --- a/cpp/tests/c_api/extract_paths_test.c +++ b/cpp/tests/c_api/extract_paths_test.c @@ -44,12 +44,14 @@ int generic_bfs_test_with_extract_paths(vertex_t* h_src, 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_paths_result_t* p_paths_result = NULL; - cugraph_extract_paths_result_t* p_extract_paths_result = NULL; - cugraph_type_erased_device_array_t* p_sources = NULL; - cugraph_type_erased_device_array_t* p_destinations = NULL; + cugraph_resource_handle_t* p_handle = NULL; + cugraph_graph_t* p_graph = NULL; + cugraph_paths_result_t* p_paths_result = NULL; + cugraph_extract_paths_result_t* p_extract_paths_result = NULL; + cugraph_type_erased_device_array_t* p_sources = NULL; + cugraph_type_erased_device_array_t* p_destinations = NULL; + cugraph_type_erased_device_array_view_t* p_sources_view = NULL; + cugraph_type_erased_device_array_view_t* p_destinations_view = NULL; p_handle = cugraph_create_resource_handle(); TEST_ASSERT(test_ret_value, p_handle != NULL, "resource handle creation failed."); @@ -58,48 +60,66 @@ int generic_bfs_test_with_extract_paths(vertex_t* h_src, p_handle, h_src, h_dst, h_wgt, num_edges, store_transposed, &p_graph, &ret_error); ret_code = - cugraph_type_erased_device_array_create(p_handle, INT32, num_seeds, &p_sources, &ret_error); + cugraph_type_erased_device_array_create(p_handle, num_seeds, INT32, &p_sources, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "p_sources create failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host( - p_handle, p_sources, (byte_t*)h_seeds, &ret_error); + p_sources_view = cugraph_type_erased_device_array_view(p_sources); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host( + p_handle, p_sources_view, (byte_t*)h_seeds, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); ret_code = cugraph_type_erased_device_array_create( - p_handle, INT32, num_destinations, &p_destinations, &ret_error); + p_handle, num_destinations, INT32, &p_destinations, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "p_destinations create failed."); - ret_code = cugraph_type_erased_device_array_copy_from_host( - p_handle, p_destinations, (byte_t*)h_destinations, &ret_error); + p_destinations_view = cugraph_type_erased_device_array_view(p_destinations); + + ret_code = cugraph_type_erased_device_array_view_copy_from_host( + p_handle, p_destinations_view, (byte_t*)h_destinations, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); - ret_code = cugraph_bfs( - p_handle, p_graph, p_sources, FALSE, depth_limit, TRUE, FALSE, &p_paths_result, &ret_error); + ret_code = cugraph_bfs(p_handle, + p_graph, + p_sources_view, + FALSE, + depth_limit, + TRUE, + FALSE, + &p_paths_result, + &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_bfs failed."); ret_code = cugraph_extract_paths(p_handle, p_graph, - p_sources, + p_sources_view, p_paths_result, - p_destinations, + p_destinations_view, &p_extract_paths_result, &ret_error); size_t max_path_length = cugraph_extract_paths_result_get_max_path_length(p_extract_paths_result); - cugraph_type_erased_device_array_t* paths = + TEST_ASSERT( + test_ret_value, max_path_length == expected_max_path_length, "path lengths don't match"); + + cugraph_type_erased_device_array_view_t* paths_view = cugraph_extract_paths_result_get_paths(p_extract_paths_result); - size_t paths_size = cugraph_type_erased_device_array_size(paths); + size_t paths_size = cugraph_type_erased_device_array_view_size(paths_view); + vertex_t h_paths[paths_size]; - ret_code = - cugraph_type_erased_device_array_copy_to_host(p_handle, (byte_t*)h_paths, paths, &ret_error); + ret_code = cugraph_type_erased_device_array_view_copy_to_host( + p_handle, (byte_t*)h_paths, paths_view, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); for (int i = 0; (i < paths_size) && (test_ret_value == 0); ++i) { TEST_ASSERT(test_ret_value, expected_paths[i] == h_paths[i], "paths don't match"); } + cugraph_type_erased_device_array_view_free(paths_view); + cugraph_type_erased_device_array_view_free(p_sources_view); + cugraph_type_erased_device_array_view_free(p_destinations_view); cugraph_type_erased_device_array_free(p_sources); cugraph_type_erased_device_array_free(p_destinations); cugraph_extract_paths_result_free(p_extract_paths_result); @@ -148,10 +168,10 @@ int test_bfs_with_extract_paths_with_transpose() vertex_t src[] = {0, 1, 1, 2, 2, 2, 3, 4}; vertex_t dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; weight_t wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; - vertex_t seeds[] = {5}; - vertex_t destinations[] = {0}; + vertex_t seeds[] = {0}; + vertex_t destinations[] = {5}; vertex_t expected_max_path_length = 4; - vertex_t expected_paths[] = {5, 3, 1, 0}; + vertex_t expected_paths[] = {0, 1, 3, 5}; // Bfs wants store_transposed = FALSE // This call will force cugraph_bfs to transpose the graph diff --git a/cpp/tests/c_api/pagerank_test.c b/cpp/tests/c_api/pagerank_test.c index fb2faa210f7..f8a9e71afb0 100644 --- a/cpp/tests/c_api/pagerank_test.c +++ b/cpp/tests/c_api/pagerank_test.c @@ -52,13 +52,14 @@ int generic_pagerank_test(vertex_t* h_src, 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_pagerank( 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_pagerank failed."); - cugraph_type_erased_device_array_t* vertices; - cugraph_type_erased_device_array_t* pageranks; + 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); @@ -66,11 +67,11 @@ int generic_pagerank_test(vertex_t* h_src, vertex_t h_vertices[num_vertices]; weight_t h_pageranks[num_vertices]; - ret_code = cugraph_type_erased_device_array_copy_to_host( + 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_copy_to_host( + 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."); @@ -80,6 +81,8 @@ 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_sg_graph_free(p_graph); cugraph_free_resource_handle(p_handle); @@ -125,7 +128,43 @@ int test_pagerank_with_transpose() // This call will force cugraph_pagerank to transpose the graph // But we're passing src/dst backwards so the results will be the same return generic_pagerank_test( - h_dst, h_src, h_wgt, h_result, num_vertices, num_edges, FALSE, alpha, epsilon, max_iterations); + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, FALSE, alpha, epsilon, max_iterations); +} + +int test_pagerank_4() +{ + size_t num_edges = 3; + size_t num_vertices = 4; + + vertex_t h_src[] = {0, 1, 2}; + vertex_t h_dst[] = {1, 2, 3}; + weight_t h_wgt[] = {1.f, 1.f, 1.f}; + weight_t h_result[] = {0.11615584790706635f, 0.21488840878009796f, 0.29881080985069275f, 0.37014490365982056f}; + + double alpha = 0.85; + double epsilon = 1.0e-6; + size_t max_iterations = 500; + + return generic_pagerank_test( + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, FALSE, alpha, epsilon, max_iterations); +} + +int test_pagerank_4_with_transpose() +{ + size_t num_edges = 3; + size_t num_vertices = 4; + + vertex_t h_src[] = {0, 1, 2}; + vertex_t h_dst[] = {1, 2, 3}; + weight_t h_wgt[] = {1.f, 1.f, 1.f}; + weight_t h_result[] = {0.11615584790706635f, 0.21488840878009796f, 0.29881080985069275f, 0.37014490365982056f}; + + double alpha = 0.85; + double epsilon = 1.0e-6; + size_t max_iterations = 500; + + return generic_pagerank_test( + h_src, h_dst, h_wgt, h_result, num_vertices, num_edges, TRUE, alpha, epsilon, max_iterations); } /******************************************************************************/ @@ -135,5 +174,7 @@ int main(int argc, char** argv) int result = 0; result |= RUN_TEST(test_pagerank); result |= RUN_TEST(test_pagerank_with_transpose); + result |= RUN_TEST(test_pagerank_4); + result |= RUN_TEST(test_pagerank_4_with_transpose); return result; } diff --git a/cpp/tests/c_api/sssp_test.c b/cpp/tests/c_api/sssp_test.c index 48ebb7c302b..4b210d3bcc7 100644 --- a/cpp/tests/c_api/sssp_test.c +++ b/cpp/tests/c_api/sssp_test.c @@ -58,9 +58,9 @@ int generic_sssp_test(vertex_t* h_src, p_handle, p_graph, source, cutoff, TRUE, FALSE, &p_result, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_sssp failed."); - cugraph_type_erased_device_array_t* vertices; - cugraph_type_erased_device_array_t* distances; - cugraph_type_erased_device_array_t* predecessors; + cugraph_type_erased_device_array_view_t* vertices; + cugraph_type_erased_device_array_view_t* distances; + cugraph_type_erased_device_array_view_t* predecessors; vertices = cugraph_paths_result_get_vertices(p_result); distances = cugraph_paths_result_get_distances(p_result); @@ -70,15 +70,15 @@ int generic_sssp_test(vertex_t* h_src, weight_t h_distances[num_vertices]; vertex_t h_predecessors[num_vertices]; - ret_code = cugraph_type_erased_device_array_copy_to_host( + 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_copy_to_host( + ret_code = cugraph_type_erased_device_array_view_copy_to_host( p_handle, (byte_t*)h_distances, distances, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); - ret_code = cugraph_type_erased_device_array_copy_to_host( + ret_code = cugraph_type_erased_device_array_view_copy_to_host( p_handle, (byte_t*)h_predecessors, predecessors, &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "copy_to_host failed."); @@ -92,6 +92,9 @@ int generic_sssp_test(vertex_t* h_src, "sssp predecessors don't match"); } + cugraph_type_erased_device_array_view_free(vertices); + cugraph_type_erased_device_array_view_free(distances); + cugraph_type_erased_device_array_view_free(predecessors); cugraph_paths_result_free(p_result); cugraph_sg_graph_free(p_graph); cugraph_free_resource_handle(p_handle); @@ -132,15 +135,15 @@ int test_sssp_with_transpose() vertex_t src[] = {0, 1, 1, 2, 2, 2, 3, 4}; vertex_t dst[] = {1, 3, 4, 0, 1, 3, 5, 5}; weight_t wgt[] = {0.1f, 2.1f, 1.1f, 5.1f, 3.1f, 4.1f, 7.2f, 3.2f}; - weight_t expected_distances[] = {4.4, 4.3, 7.4, 7.2f, 3.2f, 0.0f}; - vertex_t expected_predecessors[] = {1, 4, 1, 5, 5, -1}; + weight_t expected_distances[] = {0.0f, 0.1f, FLT_MAX, 2.2f, 1.2f, 4.4f}; + vertex_t expected_predecessors[] = {-1, 0, -1, 1, 1, 4}; // Bfs wants store_transposed = FALSE // This call will force cugraph_sssp to transpose the graph return generic_sssp_test(src, dst, wgt, - 5, + 0, expected_distances, expected_predecessors, num_vertices, diff --git a/cpp/tests/c_api/test_utils.c b/cpp/tests/c_api/test_utils.c index ee7c840fe36..95a765f6b2b 100644 --- a/cpp/tests/c_api/test_utils.c +++ b/cpp/tests/c_api/test_utils.c @@ -52,36 +52,54 @@ int create_test_graph(const cugraph_resource_handle_t* p_handle, cugraph_type_erased_device_array_t* src; cugraph_type_erased_device_array_t* dst; cugraph_type_erased_device_array_t* wgt; + cugraph_type_erased_device_array_view_t* src_view; + cugraph_type_erased_device_array_view_t* dst_view; + cugraph_type_erased_device_array_view_t* wgt_view; ret_code = - cugraph_type_erased_device_array_create(p_handle, vertex_tid, num_edges, &src, ret_error); + cugraph_type_erased_device_array_create(p_handle, num_edges, vertex_tid, &src, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src create failed."); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, cugraph_error_message(*ret_error)); ret_code = - cugraph_type_erased_device_array_create(p_handle, vertex_tid, num_edges, &dst, ret_error); + cugraph_type_erased_device_array_create(p_handle, num_edges, vertex_tid, &dst, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "dst create failed."); ret_code = - cugraph_type_erased_device_array_create(p_handle, weight_tid, num_edges, &wgt, ret_error); + cugraph_type_erased_device_array_create(p_handle, num_edges, weight_tid, &wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt create failed."); + src_view = cugraph_type_erased_device_array_view(src); + dst_view = cugraph_type_erased_device_array_view(dst); + wgt_view = cugraph_type_erased_device_array_view(wgt); + ret_code = - cugraph_type_erased_device_array_copy_from_host(p_handle, src, (byte_t*)h_src, ret_error); + cugraph_type_erased_device_array_view_copy_from_host(p_handle, src_view, (byte_t*)h_src, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "src copy_from_host failed."); ret_code = - cugraph_type_erased_device_array_copy_from_host(p_handle, dst, (byte_t*)h_dst, ret_error); + cugraph_type_erased_device_array_view_copy_from_host(p_handle, dst_view, (byte_t*)h_dst, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "dst copy_from_host failed."); ret_code = - cugraph_type_erased_device_array_copy_from_host(p_handle, wgt, (byte_t*)h_wgt, ret_error); + cugraph_type_erased_device_array_view_copy_from_host(p_handle, wgt_view, (byte_t*)h_wgt, ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "wgt copy_from_host failed."); - ret_code = cugraph_sg_graph_create( - p_handle, &properties, src, dst, wgt, store_transposed, FALSE, FALSE, p_graph, ret_error); + ret_code = cugraph_sg_graph_create(p_handle, + &properties, + src_view, + dst_view, + wgt_view, + store_transposed, + FALSE, + FALSE, + p_graph, + ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); + cugraph_type_erased_device_array_view_free(wgt_view); + cugraph_type_erased_device_array_view_free(dst_view); + cugraph_type_erased_device_array_view_free(src_view); cugraph_type_erased_device_array_free(wgt); cugraph_type_erased_device_array_free(dst); cugraph_type_erased_device_array_free(src); diff --git a/cpp/tests/structure/transpose_storage_test.cpp b/cpp/tests/structure/transpose_storage_test.cpp index c216d8d89c4..a44d50f6f49 100644 --- a/cpp/tests/structure/transpose_storage_test.cpp +++ b/cpp/tests/structure/transpose_storage_test.cpp @@ -149,10 +149,9 @@ class Tests_TransposeStorage std::vector> storage_transposed_edges( h_storage_transposed_rows.size()); for (size_t i = 0; i < storage_transposed_edges.size(); ++i) { - storage_transposed_edges[i] = - std::make_tuple(h_storage_transposed_cols[i], - h_storage_transposed_rows[i], - (*h_storage_transposed_weights)[i]); // flip rows and cols + storage_transposed_edges[i] = std::make_tuple(h_storage_transposed_rows[i], + h_storage_transposed_cols[i], + (*h_storage_transposed_weights)[i]); } std::sort(storage_transposed_edges.begin(), storage_transposed_edges.end()); @@ -168,8 +167,8 @@ class Tests_TransposeStorage std::vector> storage_transposed_edges( h_storage_transposed_rows.size()); for (size_t i = 0; i < storage_transposed_edges.size(); ++i) { - storage_transposed_edges[i] = std::make_tuple( - h_storage_transposed_cols[i], h_storage_transposed_rows[i]); // flip rows and cols + storage_transposed_edges[i] = + std::make_tuple(h_storage_transposed_rows[i], h_storage_transposed_cols[i]); } std::sort(storage_transposed_edges.begin(), storage_transposed_edges.end()); diff --git a/python/pylibcugraph/pylibcugraph/README.md b/python/pylibcugraph/pylibcugraph/README.md new file mode 100644 index 00000000000..620b708d7a5 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/README.md @@ -0,0 +1,22 @@ +# `pylibcugraph` + +This directory contains the sources to the `pylibcugraph` package. The sources +are primarily cython files which are built using the `setup.py` file in the +parent directory and depend on the `libcugraph_c` and `libcugraph` libraries and +headers. + +## components +The `connected_components` APIs. + +## structure +Internal utilities and types for use with the libcugraph C++ library. + +## utilities +Utility functions. + +## experimental +This subpackage defines the "experimental" APIs. many of these APIs are defined +elsewhere and simply imported into the `experimental/__init__.py` file. + +## tests +pytest tests for `pylibcugraph`. diff --git a/python/pylibcugraph/pylibcugraph/__init__.py b/python/pylibcugraph/pylibcugraph/__init__.py index 868d5d779da..0385e2ef573 100644 --- a/python/pylibcugraph/pylibcugraph/__init__.py +++ b/python/pylibcugraph/pylibcugraph/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, NVIDIA CORPORATION. +# Copyright (c) 2021-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 @@ -11,7 +11,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from pylibcugraph.components import ( +from .components._connectivity import ( strongly_connected_components, weakly_connected_components, ) + +from . import experimental diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/README.md b/python/pylibcugraph/pylibcugraph/_cugraph_c/README.md new file mode 100644 index 00000000000..cb0c6edff2b --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/README.md @@ -0,0 +1,5 @@ +# `pylibcugraph/_cugraph_c` + +This directory contains cython `.pxd` files which describe the cugraph C library +to cython. The contents here are simply a mapping of the cugraph_c C APIs to +cython for use in the cython code in the parent directory. \ No newline at end of file diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/__init__.py b/python/pylibcugraph/pylibcugraph/_cugraph_c/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd new file mode 100644 index 00000000000..64a3d39933f --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/algorithms.pxd @@ -0,0 +1,166 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + bool_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, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) + + +cdef extern from "cugraph_c/algorithms.h": + ########################################################################### + # pagerank + ctypedef struct cugraph_pagerank_result_t: + pass + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_pagerank_result_get_vertices( + cugraph_pagerank_result_t* result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_pagerank_result_get_pageranks( + cugraph_pagerank_result_t* result + ) + + cdef void \ + cugraph_pagerank_result_free( + cugraph_pagerank_result_t* result + ) + + cdef cugraph_error_code_t \ + cugraph_pagerank( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, + double alpha, + double epsilon, + size_t max_iterations, + bool_t has_initial_guess, + bool_t do_expensive_check, + cugraph_pagerank_result_t** result, + cugraph_error_t** error + ) + + cdef cugraph_error_code_t \ + cugraph_personalized_pagerank( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* precomputed_vertex_out_weight_sums, + cugraph_type_erased_device_array_view_t* personalization_vertices, + const cugraph_type_erased_device_array_view_t* personalization_values, + double alpha, + double epsilon, + size_t max_iterations, + bool_t has_initial_guess, + bool_t do_expensive_check, + cugraph_pagerank_result_t** result, + cugraph_error_t** error + ) + + ########################################################################### + # paths and path extraction + ctypedef struct cugraph_paths_result_t: + pass + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_paths_result_get_vertices( + cugraph_paths_result_t* result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_paths_result_get_distances( + cugraph_paths_result_t* result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_paths_result_get_predecessors( + cugraph_paths_result_t* result + ) + + cdef void \ + cugraph_paths_result_free( + cugraph_paths_result_t* result + ) + + ctypedef struct cugraph_extract_paths_result_t: + pass + + cdef cugraph_error_code_t \ + cugraph_extract_paths( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + const cugraph_type_erased_device_array_view_t* sources, + const cugraph_paths_result_t* paths_result, + const cugraph_type_erased_device_array_view_t* destinations, + cugraph_extract_paths_result_t** result, + cugraph_error_t** error + ) + + cdef size_t \ + cugraph_extract_paths_result_get_max_path_length( + cugraph_extract_paths_result_t* result + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_extract_paths_result_get_paths( + cugraph_extract_paths_result_t* result + ) + + cdef void \ + cugraph_extract_paths_result_free( + cugraph_extract_paths_result_t* result + ) + + ########################################################################### + # bfs + cdef cugraph_error_code_t \ + cugraph_bfs( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + # FIXME: this may become const + cugraph_type_erased_device_array_view_t* sources, + bool_t direction_optimizing, + size_t depth_limit, + bool_t compute_predecessors, + bool_t do_expensive_check, + cugraph_paths_result_t** result, + cugraph_error_t** error + ) + + ########################################################################### + # sssp + cdef cugraph_error_code_t \ + cugraph_sssp( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + size_t source, + double cutoff, + bool_t compute_predecessors, + bool_t do_expensive_check, + cugraph_paths_result_t** result, + cugraph_error_t** error + ) diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/array.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/array.pxd new file mode 100644 index 00000000000..91998d477d3 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/array.pxd @@ -0,0 +1,166 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) +from pylibcugraph._cugraph_c.cugraph_api cimport ( + cugraph_resource_handle_t, + data_type_id_t, + byte_t, +) + + +cdef extern from "cugraph_c/array.h": + + ctypedef struct cugraph_type_erased_device_array_t: + pass + + ctypedef struct cugraph_type_erased_device_array_view_t: + pass + + ctypedef struct cugraph_type_erased_host_array_t: + pass + + ctypedef struct cugraph_type_erased_host_array_view_t: + pass + + cdef cugraph_error_code_t \ + cugraph_type_erased_device_array_create( + const cugraph_resource_handle_t* handle, + data_type_id_t dtype, + size_t n_elems, + cugraph_type_erased_device_array_t** array, + cugraph_error_t** error + ) + + cdef void \ + cugraph_type_erased_device_array_free( + cugraph_type_erased_device_array_t* p + ) + + # cdef void* \ + # cugraph_type_erased_device_array_release( + # cugraph_type_erased_device_array_t* p + # ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_type_erased_device_array_view( + cugraph_type_erased_device_array_t* array + ) + + cdef cugraph_type_erased_device_array_view_t* \ + cugraph_type_erased_device_array_view_create( + void* pointer, + size_t n_elems, + data_type_id_t dtype + ) + + cdef void \ + cugraph_type_erased_device_array_view_free( + cugraph_type_erased_device_array_view_t* p + ) + + cdef size_t \ + cugraph_type_erased_device_array_view_size( + const cugraph_type_erased_device_array_view_t* p + ) + + cdef data_type_id_t \ + cugraph_type_erased_device_array_view_type( + const cugraph_type_erased_device_array_view_t* p + ) + + cdef const void* \ + cugraph_type_erased_device_array_view_pointer( + const cugraph_type_erased_device_array_view_t* p + ) + + cdef cugraph_error_code_t \ + cugraph_type_erased_host_array_create( + const cugraph_resource_handle_t* handle, + data_type_id_t dtype, + size_t n_elems, + cugraph_type_erased_host_array_t** array, + cugraph_error_t** error + ) + + cdef void \ + cugraph_type_erased_host_array_free( + cugraph_type_erased_host_array_t* p + ) + + # cdef void* \ + # cugraph_type_erased_host_array_release( + # cugraph_type_erased_host_array_t* p + # ) + + cdef cugraph_type_erased_host_array_view_t* \ + cugraph_type_erased_host_array_view( + cugraph_type_erased_host_array_t* array + ) + + cdef cugraph_type_erased_host_array_view_t* \ + cugraph_type_erased_host_array_view_create( + void* pointer, + size_t n_elems, + data_type_id_t dtype + ) + + cdef void \ + cugraph_type_erased_host_array_view_free( + cugraph_type_erased_host_array_view_t* p + ) + + cdef size_t \ + cugraph_type_erased_host_array_size( + const cugraph_type_erased_host_array_t* p + ) + + cdef data_type_id_t \ + cugraph_type_erased_host_array_type( + const cugraph_type_erased_host_array_t* p + ) + + cdef void* \ + cugraph_type_erased_host_array_pointer( + const cugraph_type_erased_host_array_view_t* p + ) + + cdef cugraph_error_code_t \ + cugraph_type_erased_device_array_view_copy_from_host( + const cugraph_resource_handle_t* handle, + cugraph_type_erased_device_array_view_t* dst, + const byte_t* h_src, + cugraph_error_t** error + ) + + cdef cugraph_error_code_t \ + cugraph_type_erased_device_array_view_copy_to_host( + const cugraph_resource_handle_t* handle, + byte_t* h_dst, + const cugraph_type_erased_device_array_view_t* src, + cugraph_error_t** error + ) + + cdef cugraph_error_code_t \ + cugraph_type_erased_device_array_view_copy( + const cugraph_resource_handle_t* handle, + cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* src, + cugraph_error_t** error + ) diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/cugraph_api.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/cugraph_api.pxd new file mode 100644 index 00000000000..276b8062849 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/cugraph_api.pxd @@ -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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport int8_t + + +cdef extern from "cugraph_c/cugraph_api.h": + + ctypedef enum bool_t: + FALSE + TRUE + + ctypedef enum data_type_id_t: + INT32 + INT64 + FLOAT32 + FLOAT64 + + ctypedef int8_t byte_t + + ctypedef struct cugraph_resource_handle_t: + pass + + cdef cugraph_resource_handle_t* \ + cugraph_create_resource_handle() + + cdef void \ + cugraph_free_resource_handle( + cugraph_resource_handle_t* p_handle + ) diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/error.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/error.pxd new file mode 100644 index 00000000000..4b20daa1135 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/error.pxd @@ -0,0 +1,39 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +cdef extern from "cugraph_c/error.h": + + ctypedef enum cugraph_error_code_t: + CUGRAPH_SUCCESS + CUGRAPH_UNKNOWN_ERROR + CUGRAPH_INVALID_HANDLE + CUGRAPH_ALLOC_ERROR + CUGRAPH_INVALID_INPUT + CUGRAPH_NOT_IMPLEMENTED + CUGRAPH_UNSUPPORTED_TYPE_COMBINATION + + ctypedef struct cugraph_error_t: + pass + + const char* \ + cugraph_error_message( + const cugraph_error_t* error + ) + + void \ + cugraph_error_free( + cugraph_error_t* error + ) diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd new file mode 100644 index 00000000000..8cc4b06093e --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd @@ -0,0 +1,82 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + bool_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_host_array_view_t, +) + + +cdef extern from "cugraph_c/graph.h": + + ctypedef struct cugraph_graph_t: + pass + + ctypedef struct cugraph_graph_properties_t: + bool_t is_symmetric + bool_t is_multigraph + + cdef cugraph_error_code_t \ + cugraph_sg_graph_create( + const cugraph_resource_handle_t* handle, + const cugraph_graph_properties_t* properties, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, + bool_t store_transposed, + bool_t renumber, + bool_t check, + cugraph_graph_t** graph, + cugraph_error_t** error) + + # This may get renamed to cugraph_graph_free() + cdef void \ + cugraph_sg_graph_free( + cugraph_graph_t* graph + ) + + # FIXME: the API parameters may change to remove vertex_partition_offsets + # and segment_offsets + cdef cugraph_error_code_t \ + cugraph_mg_graph_create( + const cugraph_resource_handle_t* handle, + const cugraph_graph_properties_t* properties, + const cugraph_type_erased_device_array_view_t* src, + const cugraph_type_erased_device_array_view_t* dst, + const cugraph_type_erased_device_array_view_t* weights, + const cugraph_type_erased_host_array_view_t* vertex_partition_offsets, + const cugraph_type_erased_host_array_view_t* segment_offsets, + bool_t store_transposed, + size_t num_vertices, + size_t num_edges, + bool_t check, + cugraph_graph_t** graph, + cugraph_error_t** error + ) + + # This may get renamed to or replaced with cugraph_graph_free() + cdef void \ + cugraph_mg_graph_free( + cugraph_graph_t* graph + ) diff --git a/python/pylibcugraph/pylibcugraph/components/__init__.py b/python/pylibcugraph/pylibcugraph/components/__init__.py index b4d9daae59e..e69de29bb2d 100644 --- a/python/pylibcugraph/pylibcugraph/components/__init__.py +++ b/python/pylibcugraph/pylibcugraph/components/__init__.py @@ -1,17 +0,0 @@ -# Copyright (c) 2021, 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. - -from pylibcugraph.components._connectivity import ( - strongly_connected_components, - weakly_connected_components, -) diff --git a/python/pylibcugraph/pylibcugraph/experimental/__init__.py b/python/pylibcugraph/pylibcugraph/experimental/__init__.py new file mode 100644 index 00000000000..81d95cd56c5 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/experimental/__init__.py @@ -0,0 +1,54 @@ +# 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. + +""" +The "experimental" package contains packages, functions, classes, etc. that +are ready for use but do not have their API signatures or implementation +finalized yet. This allows users to provide early feedback while still +permitting bigger design changes to take place. + +ALL APIS IN EXPERIMENTAL ARE SUBJECT TO CHANGE OR REMOVAL. + +Calling experimental objects will raise a PendingDeprecationWarning warning. + +If an object is "promoted" to the public API, the experimental namespace will +continue to also have that object present for at least another release. A +different warning will be output in that case, indicating that the experimental +API has been promoted and will no longer be importable from experimental much +longer. +""" + +from pylibcugraph.utilities.api_tools import experimental_warning_wrapper + +# experimental_warning_wrapper() wraps the object in a function that provides +# the appropriate warning about using experimental code. + +# The convention of naming functions with the "EXPERIMENTAL__" prefix +# discourages users from directly importing experimental objects that don't have +# the appropriate warnings, such as what the wrapper and the "experimental" +# namespace name provides. + +from pylibcugraph.graphs import EXPERIMENTAL__SGGraph +SGGraph = experimental_warning_wrapper(EXPERIMENTAL__SGGraph) + +from pylibcugraph.resource_handle import EXPERIMENTAL__ResourceHandle +ResourceHandle = experimental_warning_wrapper(EXPERIMENTAL__ResourceHandle) + +from pylibcugraph.graph_properties import EXPERIMENTAL__GraphProperties +GraphProperties = experimental_warning_wrapper(EXPERIMENTAL__GraphProperties) + +from pylibcugraph.pagerank import EXPERIMENTAL__pagerank +pagerank = experimental_warning_wrapper(EXPERIMENTAL__pagerank) + +from pylibcugraph.sssp import EXPERIMENTAL__sssp +sssp = experimental_warning_wrapper(EXPERIMENTAL__sssp) diff --git a/python/pylibcugraph/pylibcugraph/graph_properties.pxd b/python/pylibcugraph/pylibcugraph/graph_properties.pxd new file mode 100644 index 00000000000..ed3290186b2 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/graph_properties.pxd @@ -0,0 +1,23 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_properties_t, +) + + +cdef class EXPERIMENTAL__GraphProperties: + cdef cugraph_graph_properties_t c_graph_properties diff --git a/python/pylibcugraph/pylibcugraph/graph_properties.pyx b/python/pylibcugraph/pylibcugraph/graph_properties.pyx new file mode 100644 index 00000000000..dc8b2a51225 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/graph_properties.pyx @@ -0,0 +1,39 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +cdef class EXPERIMENTAL__GraphProperties: + """ + Class wrapper around C cugraph_graph_properties_t struct + """ + def __cinit__(self, is_symmetric=False, is_multigraph=False): + self.c_graph_properties.is_symmetric = is_symmetric + self.c_graph_properties.is_multigraph = is_multigraph + + @property + def is_symmetric(self): + return bool(self.c_graph_properties.is_symmetric) + + @is_symmetric.setter + def is_symmetric(self, value): + self.c_graph_properties.is_symmetric = value + + @property + def is_multigraph(self): + return bool(self.c_graph_properties.is_multigraph) + + @is_multigraph.setter + def is_multigraph(self, value): + self.c_graph_properties.is_multigraph = value diff --git a/python/pylibcugraph/pylibcugraph/graphs.pxd b/python/pylibcugraph/pylibcugraph/graphs.pxd new file mode 100644 index 00000000000..9da256f9928 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/graphs.pxd @@ -0,0 +1,29 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) + + +cdef class EXPERIMENTAL__Graph: + cdef cugraph_graph_t* c_graph_ptr + +cdef class EXPERIMENTAL__SGGraph(EXPERIMENTAL__Graph): + pass + +# cdef class EXPERIMENTAL__MGGraph(EXPERIMENTAL__Graph): +# pass diff --git a/python/pylibcugraph/pylibcugraph/graphs.pyx b/python/pylibcugraph/pylibcugraph/graphs.pyx new file mode 100644 index 00000000000..381191c3e51 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/graphs.pyx @@ -0,0 +1,171 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport uintptr_t + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + bool_t, + cugraph_resource_handle_t, + data_type_id_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_free, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, + cugraph_sg_graph_create, + cugraph_graph_properties_t, + cugraph_sg_graph_free, +) +from pylibcugraph.resource_handle cimport ( + EXPERIMENTAL__ResourceHandle, +) +from pylibcugraph.graph_properties cimport ( + EXPERIMENTAL__GraphProperties, +) +from pylibcugraph.utils cimport ( + assert_success, + assert_CAI_type, +) + + +cdef class EXPERIMENTAL__SGGraph(EXPERIMENTAL__Graph): + """ + RAII-stye Graph class for use with single-GPU APIs that manages the + individual create/free calls and the corresponding cugraph_graph_t pointer. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph_properties : GraphProperties + Object defining intended properties for the graph. + + src_array : device array type + Device array containing the vertex identifiers of the source of each + directed edge. The order of the array corresponds to the ordering of the + dst_array, where the ith item in src_array and the ith item in dst_array + define the ith edge of the graph. + + dst_array : device array type + Device array containing the vertex identifiers of the destination of each + directed edge. The order of the array corresponds to the ordering of the + src_array, where the ith item in src_array and the ith item in dst_array + define the ith edge of the graph. + + weight_array : device array type + Device array containing the weight values of each directed edge. The + order of the array corresponds to the ordering of the src_array and + dst_array arrays, where the ith item in weight_array is the weight value + of the ith edge of the graph. + + store_transposed : bool + Set to True if the graph should be transposed. This is required for some + algorithms, such as pagerank. + + renumber : bool + Set to True to indicate the vertices used in src_array and dst_array are + not appropriate for use as internal array indices, and should be mapped + to continuous integers starting from 0. + + do_expensive_check : bool + If True, performs more extensive tests on the inputs to ensure + validitity, at the expense of increased run time. + """ + def __cinit__(self, + EXPERIMENTAL__ResourceHandle resource_handle, + EXPERIMENTAL__GraphProperties graph_properties, + src_array, + dst_array, + weight_array, + store_transposed, + renumber, + do_expensive_check): + + # FIXME: add tests for these + if not(isinstance(store_transposed, (int, bool))): + raise TypeError("expected int or bool for store_transposed, got " + f"{type(store_transposed)}") + if not(isinstance(renumber, (int, bool))): + raise TypeError("expected int or bool for renumber, got " + f"{type(renumber)}") + if not(isinstance(do_expensive_check, (int, bool))): + raise TypeError("expected int or bool for do_expensive_check, got " + f"{type(do_expensive_check)}") + assert_CAI_type(src_array, "src_array") + assert_CAI_type(dst_array, "dst_array") + assert_CAI_type(weight_array, "weight_array") + + # FIXME: assert that src_array and dst_array have the same type + + cdef cugraph_error_t* error_ptr + cdef cugraph_error_code_t error_code + + # FIXME: set dtype properly + cdef uintptr_t cai_srcs_ptr = \ + src_array.__cuda_array_interface__["data"][0] + cdef cugraph_type_erased_device_array_view_t* srcs_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cai_srcs_ptr, + len(src_array), + data_type_id_t.INT32) + + # FIXME: set dtype properly + cdef uintptr_t cai_dsts_ptr = \ + dst_array.__cuda_array_interface__["data"][0] + cdef cugraph_type_erased_device_array_view_t* dsts_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cai_dsts_ptr, + len(dst_array), + data_type_id_t.INT32) + + # FIXME: set dtype properly + cdef uintptr_t cai_weights_ptr = \ + weight_array.__cuda_array_interface__["data"][0] + cdef cugraph_type_erased_device_array_view_t* weights_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cai_weights_ptr, + len(weight_array), + data_type_id_t.FLOAT32) + + error_code = cugraph_sg_graph_create( + resource_handle.c_resource_handle_ptr, + &(graph_properties.c_graph_properties), + srcs_view_ptr, + dsts_view_ptr, + weights_view_ptr, + store_transposed, + renumber, + do_expensive_check, + &(self.c_graph_ptr), + &error_ptr) + + assert_success(error_code, error_ptr, + "cugraph_sg_graph_create()") + + # FIXME: free the views + + def __dealloc__(self): + if self.c_graph_ptr is not NULL: + cugraph_sg_graph_free(self.c_graph_ptr) diff --git a/python/pylibcugraph/pylibcugraph/pagerank.pyx b/python/pylibcugraph/pylibcugraph/pagerank.pyx new file mode 100644 index 00000000000..a1b5a704693 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/pagerank.pyx @@ -0,0 +1,256 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport uintptr_t + +from pylibcugraph._cugraph_c.cugraph_api 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_size, + cugraph_type_erased_device_array_view_type, + cugraph_type_erased_device_array_view_create, + cugraph_type_erased_device_array_view_free, + cugraph_type_erased_device_array_view_copy, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) +from pylibcugraph._cugraph_c.algorithms cimport ( + cugraph_pagerank_result_t, + cugraph_pagerank, + cugraph_pagerank_result_get_vertices, + cugraph_pagerank_result_get_pageranks, + cugraph_pagerank_result_free, +) + +from pylibcugraph.resource_handle cimport ( + EXPERIMENTAL__ResourceHandle, +) +from pylibcugraph.graphs cimport ( + EXPERIMENTAL__Graph, +) +from pylibcugraph.utils cimport ( + assert_success, + assert_CAI_type, + get_numpy_type_from_c_type, +) + + +def EXPERIMENTAL__pagerank(EXPERIMENTAL__ResourceHandle resource_handle, + EXPERIMENTAL__Graph graph, + precomputed_vertex_out_weight_sums, + double alpha, + double epsilon, + size_t max_iterations, + bool_t has_initial_guess, + bool_t do_expensive_check): + """ + Find the PageRank score for every vertex in a graph by computing an + approximation of the Pagerank eigenvector using the power method. The + number of iterations depends on the properties of the network itself; it + increases when the tolerance descreases and/or alpha increases toward the + limiting value of 1. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph : SGGraph + The input graph. + + precomputed_vertex_out_weight_sums : None + This parameter is unsupported in this release and only None is + accepted. + + alpha : float + The damping factor alpha represents the probability to follow an + outgoing edge, standard value is 0.85. + Thus, 1.0-alpha is the probability to “teleport” to a random vertex. + Alpha should be greater than 0.0 and strictly lower than 1.0. + + epsilon : float + Set the tolerance the approximation, this parameter should be a small + magnitude value. + The lower the tolerance the better the approximation. If this value is + 0.0f, cuGraph will use the default value which is 1.0E-5. + Setting too small a tolerance can lead to non-convergence due to + numerical roundoff. Usually values between 0.01 and 0.00001 are + acceptable. + + max_iterations : int + The maximum number of iterations before an answer is returned. This can + be used to limit the execution time and do an early exit before the + solver reaches the convergence tolerance. + If this value is lower or equal to 0 cuGraph will use the default + value, which is 100. + + has_initial_guess : bool + This parameter is unsupported in this release and only False is + accepted. + + do_expensive_check : bool + If True, performs more extensive tests on the inputs to ensure + validitity, at the expense of increased run time. + + Returns + ------- + A tuple of device arrays, where the first item in the tuple is a device + array containing the vertex identifiers, and the second item is a device + array containing the pagerank values for the corresponding vertices. For + example, the vertex identifier at the ith element of the vertex array has + the pagerank value of the ith element in the pagerank array. + + 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.experimental.ResourceHandle() + >>> graph_props = pylibcugraph.experimental.GraphProperties( + ... is_symmetric=False, is_multigraph=False) + >>> G = pylibcugraph.experimental.SGGraph( + ... resource_handle, graph_props, srcs, dsts, weights, + ... store_transposed=True, renumber=False, do_expensive_check=False) + >>> (vertices, pageranks) = pylibcugraph.experimental.pagerank( + ... resource_handle, G, None, alpha=0.85, epsilon=1.0e-6, + ... max_iterations=500, has_initial_guess=False, + ... do_expensive_check=False) + >>> vertices + array([0, 1, 2, 3], dtype=int32) + >>> pageranks + array([0.11615585, 0.21488841, 0.2988108 , 0.3701449 ], dtype=float32) + """ + + # FIXME: import these modules here for now until a better pattern can be + # used for optional imports (perhaps 'import_optional()' from cugraph), or + # these are made hard dependencies. + try: + import cupy + except ModuleNotFoundError: + raise RuntimeError("pagerank requires the cupy package, which could " + "not be imported") + try: + import numpy + except ModuleNotFoundError: + raise RuntimeError("pagerank requires the numpy package, which could " + "not be imported") + + if has_initial_guess is True: + raise ValueError("has_initial_guess must be False for the current " + "release.") + + assert_CAI_type(precomputed_vertex_out_weight_sums, + "precomputed_vertex_out_weight_sums", + allow_None=True) + # FIXME: assert that precomputed_vertex_out_weight_sums type == weight type + + 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_type_erased_device_array_view_t* \ + precomputed_vertex_out_weight_sums_ptr = NULL + if precomputed_vertex_out_weight_sums: + raise NotImplementedError("None is temporarily the only supported " + "value for precomputed_vertex_out_weight_sums") + + cdef cugraph_pagerank_result_t* result_ptr + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_pagerank(c_resource_handle_ptr, + c_graph_ptr, + precomputed_vertex_out_weight_sums_ptr, + alpha, + epsilon, + max_iterations, + has_initial_guess, + do_expensive_check, + &result_ptr, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_pagerank") + + # Extract individual device array pointers from result + cdef cugraph_type_erased_device_array_view_t* vertices_ptr + cdef cugraph_type_erased_device_array_view_t* pageranks_ptr + vertices_ptr = cugraph_pagerank_result_get_vertices(result_ptr) + pageranks_ptr = cugraph_pagerank_result_get_pageranks(result_ptr) + + # Extract meta-data needed to copy results + cdef data_type_id_t vertex_type = \ + cugraph_type_erased_device_array_view_type(vertices_ptr) + cdef data_type_id_t pagerank_type = \ + cugraph_type_erased_device_array_view_type(pageranks_ptr) + vertex_numpy_type = get_numpy_type_from_c_type(vertex_type) + pagerank_numpy_type = get_numpy_type_from_c_type(pagerank_type) + + num_vertices = cugraph_type_erased_device_array_view_size(vertices_ptr) + + # Set up cupy arrays to return and copy results to: + # * create cupy array object (these will be what the caller uses). + # * access the underlying device pointers. + # * create device array views which can be used with the copy APIs. + # * call copy APIs. This will copy data to the array pointed to the pointer + # in the cupy array objects that will be returned. + # * free view objects. + cupy_vertices = cupy.array(numpy.zeros(num_vertices), + dtype=vertex_numpy_type) + cupy_pageranks = cupy.array(numpy.zeros(num_vertices), + dtype=pagerank_numpy_type) + + cdef uintptr_t cupy_vertices_ptr = \ + cupy_vertices.__cuda_array_interface__["data"][0] + cdef uintptr_t cupy_pageranks_ptr = \ + cupy_pageranks.__cuda_array_interface__["data"][0] + + cdef cugraph_type_erased_device_array_view_t* cupy_vertices_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cupy_vertices_ptr, num_vertices, vertex_type) + + cdef cugraph_type_erased_device_array_view_t* cupy_pageranks_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cupy_pageranks_ptr, num_vertices, vertex_type) + + error_code = cugraph_type_erased_device_array_view_copy( + c_resource_handle_ptr, + cupy_vertices_view_ptr, + vertices_ptr, + &error_ptr) + assert_success(error_code, error_ptr, + "cugraph_type_erased_device_array_view_copy") + error_code = cugraph_type_erased_device_array_view_copy( + c_resource_handle_ptr, + cupy_pageranks_view_ptr, + pageranks_ptr, + &error_ptr) + assert_success(error_code, error_ptr, + "cugraph_type_erased_device_array_view_copy") + + cugraph_type_erased_device_array_view_free(cupy_pageranks_view_ptr) + cugraph_type_erased_device_array_view_free(cupy_vertices_view_ptr) + cugraph_pagerank_result_free(result_ptr) + + return (cupy_vertices, cupy_pageranks) diff --git a/python/pylibcugraph/pylibcugraph/resource_handle.pxd b/python/pylibcugraph/pylibcugraph/resource_handle.pxd new file mode 100644 index 00000000000..341110acc82 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/resource_handle.pxd @@ -0,0 +1,23 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + cugraph_resource_handle_t, +) + + +cdef class EXPERIMENTAL__ResourceHandle: + cdef cugraph_resource_handle_t* c_resource_handle_ptr diff --git a/python/pylibcugraph/pylibcugraph/resource_handle.pyx b/python/pylibcugraph/pylibcugraph/resource_handle.pyx new file mode 100644 index 00000000000..7e78262bbc4 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/resource_handle.pyx @@ -0,0 +1,34 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + cugraph_create_resource_handle, + cugraph_free_resource_handle, +) + + +cdef class EXPERIMENTAL__ResourceHandle: + """ + RAII-stye resource handle class to manage individual create/free calls and + the corresponding pointer to a cugraph_resource_handle_t + """ + def __cinit__(self): + self.c_resource_handle_ptr = cugraph_create_resource_handle() + # FIXME: check for error + + def __dealloc__(self): + # FIXME: free only if handle is a valid pointer + cugraph_free_resource_handle(self.c_resource_handle_ptr) diff --git a/python/pylibcugraph/pylibcugraph/sssp.pyx b/python/pylibcugraph/pylibcugraph/sssp.pyx new file mode 100644 index 00000000000..af3eed36186 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/sssp.pyx @@ -0,0 +1,249 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from libc.stdint cimport uintptr_t + +from pylibcugraph._cugraph_c.cugraph_api 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_size, + cugraph_type_erased_device_array_view_type, + cugraph_type_erased_device_array_view_create, + cugraph_type_erased_device_array_view_free, + cugraph_type_erased_device_array_view_copy, +) +from pylibcugraph._cugraph_c.graph cimport ( + cugraph_graph_t, +) +from pylibcugraph._cugraph_c.algorithms cimport ( + cugraph_sssp, + cugraph_paths_result_t, + cugraph_paths_result_get_vertices, + cugraph_paths_result_get_distances, + cugraph_paths_result_get_predecessors, + cugraph_paths_result_free, +) + +from pylibcugraph.resource_handle cimport ( + EXPERIMENTAL__ResourceHandle, +) +from pylibcugraph.graphs cimport ( + EXPERIMENTAL__Graph, +) +from pylibcugraph.utils cimport ( + assert_success, + get_numpy_type_from_c_type, +) + + +def EXPERIMENTAL__sssp(EXPERIMENTAL__ResourceHandle resource_handle, + EXPERIMENTAL__Graph graph, + size_t source, + double cutoff, + bool_t compute_predecessors, + bool_t do_expensive_check): + """ + Compute the distance and predecessors for shortest paths from the specified + source to all the vertices in the graph. The returned distances array will + contain the distance from the source to each vertex in the returned vertex + array at the same index. The returned predecessors array will contain the + previous vertex in the shortest path for each vertex in the vertex array at + the same index. Vertices that are unreachable will have a distance of + infinity denoted by the maximum value of the data type and the predecessor + set as -1. The source vertex predecessor will be set to -1. Graphs with + negative weight cycles are not supported. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources needed for referencing data + and running algorithms. + + graph : SGGraph + The input graph. + + source : + The vertex identifier of the source vertex. + + cutoff : + Maximum edge weight sum to consider. + + compute_predecessors : bool + This parameter must be set to True for this release. + + do_expensive_check : bool + If True, performs more extensive tests on the inputs to ensure + validitity, at the expense of increased run time. + + Returns + ------- + A 3-tuple, where the first item in the tuple is a device array containing + the vertex identifiers, the second item is a device array containing the + distance for each vertex from the source vertex, and the third item is a + device array containing the vertex identifier of the preceding vertex in the + path for that vertex. For example, the vertex identifier at the ith element + of the vertex array has a distance from the source vertex of the ith element + in the distance array, and the preceding vertex in the path is the ith + element in the predecessor array. + + 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.experimental.ResourceHandle() + >>> graph_props = pylibcugraph.experimental.GraphProperties( + ... is_symmetric=False, is_multigraph=False) + >>> G = pylibcugraph.experimental.SGGraph( + ... resource_handle, graph_props, srcs, dsts, weights, + ... store_transposed=False, renumber=False, do_expensive_check=False) + >>> (vertices, distances, predecessors) = pylibcugraph.experimental.sssp( + ... resource_handle, G, source=1, cutoff=999, + ... compute_predecessors=True, do_expensive_check=False) + >>> vertices + array([0, 1, 2, 3], dtype=int32) + >>> distances + array([3.4028235e+38, 0.0000000e+00, 1.0000000e+00, 2.0000000e+00], + dtype=float32) + >>> predecessors + array([-1, -1, 1, 2], dtype=int32) + """ + + # FIXME: import these modules here for now until a better pattern can be + # used for optional imports (perhaps 'import_optional()' from cugraph), or + # these are made hard dependencies. + try: + import cupy + except ModuleNotFoundError: + raise RuntimeError("sssp requires the cupy package, which could not " + "be imported") + try: + import numpy + except ModuleNotFoundError: + raise RuntimeError("sssp requires the numpy package, which could not " + "be imported") + + if compute_predecessors is False: + raise ValueError("compute_predecessors must be True for the current " + "release.") + + 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_paths_result_t* result_ptr + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_sssp(c_resource_handle_ptr, + c_graph_ptr, + source, + cutoff, + compute_predecessors, + do_expensive_check, + &result_ptr, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_sssp") + + # Extract individual device array pointers from result + cdef cugraph_type_erased_device_array_view_t* vertices_ptr + cdef cugraph_type_erased_device_array_view_t* distances_ptr + cdef cugraph_type_erased_device_array_view_t* predecessors_ptr + vertices_ptr = cugraph_paths_result_get_vertices(result_ptr) + distances_ptr = cugraph_paths_result_get_distances(result_ptr) + predecessors_ptr = cugraph_paths_result_get_predecessors(result_ptr) + + # Extract meta-data needed to copy results + cdef data_type_id_t vertex_type = \ + cugraph_type_erased_device_array_view_type(vertices_ptr) + cdef data_type_id_t distance_type = \ + cugraph_type_erased_device_array_view_type(distances_ptr) + cdef data_type_id_t predecessor_type = \ + cugraph_type_erased_device_array_view_type(predecessors_ptr) + vertex_numpy_type = get_numpy_type_from_c_type(vertex_type) + distance_numpy_type = get_numpy_type_from_c_type(distance_type) + predecessor_numpy_type = get_numpy_type_from_c_type(predecessor_type) + + num_vertices = cugraph_type_erased_device_array_view_size(vertices_ptr) + + # Set up cupy arrays to return and copy results to: + # * create cupy array object (these will be what the caller uses). + # * access the underlying device pointers. + # * create device array views which can be used with the copy APIs. + # * call copy APIs. This will copy data to the array pointed to the pointer + # in the cupy array objects that will be returned. + # * free view objects. + cupy_vertices = cupy.array(numpy.zeros(num_vertices), + dtype=vertex_numpy_type) + cupy_distances = cupy.array(numpy.zeros(num_vertices), + dtype=distance_numpy_type) + cupy_predecessors = cupy.array(numpy.zeros(num_vertices), + dtype=predecessor_numpy_type) + + cdef uintptr_t cupy_vertices_ptr = \ + cupy_vertices.__cuda_array_interface__["data"][0] + cdef uintptr_t cupy_distances_ptr = \ + cupy_distances.__cuda_array_interface__["data"][0] + cdef uintptr_t cupy_predecessors_ptr = \ + cupy_predecessors.__cuda_array_interface__["data"][0] + + cdef cugraph_type_erased_device_array_view_t* cupy_vertices_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cupy_vertices_ptr, num_vertices, vertex_type) + cdef cugraph_type_erased_device_array_view_t* cupy_distances_view_ptr = \ + cugraph_type_erased_device_array_view_create( + cupy_distances_ptr, num_vertices, vertex_type) + cdef cugraph_type_erased_device_array_view_t* cupy_predecessors_view_ptr =\ + cugraph_type_erased_device_array_view_create( + cupy_predecessors_ptr, num_vertices, vertex_type) + + error_code = cugraph_type_erased_device_array_view_copy( + c_resource_handle_ptr, + cupy_vertices_view_ptr, + vertices_ptr, + &error_ptr) + assert_success(error_code, error_ptr, + "cugraph_type_erased_device_array_view_copy") + error_code = cugraph_type_erased_device_array_view_copy( + c_resource_handle_ptr, + cupy_distances_view_ptr, + distances_ptr, + &error_ptr) + assert_success(error_code, error_ptr, + "cugraph_type_erased_device_array_view_copy") + error_code = cugraph_type_erased_device_array_view_copy( + c_resource_handle_ptr, + cupy_predecessors_view_ptr, + predecessors_ptr, + &error_ptr) + assert_success(error_code, error_ptr, + "cugraph_type_erased_device_array_view_copy") + + cugraph_type_erased_device_array_view_free(cupy_distances_view_ptr) + cugraph_type_erased_device_array_view_free(cupy_predecessors_view_ptr) + cugraph_type_erased_device_array_view_free(cupy_vertices_view_ptr) + cugraph_paths_result_free(result_ptr) + + return (cupy_vertices, cupy_distances, cupy_predecessors) diff --git a/python/pylibcugraph/pylibcugraph/tests/conftest.py b/python/pylibcugraph/pylibcugraph/tests/conftest.py index dae3c7fe2e1..df77e58d6be 100644 --- a/python/pylibcugraph/pylibcugraph/tests/conftest.py +++ b/python/pylibcugraph/pylibcugraph/tests/conftest.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, NVIDIA CORPORATION. +# Copyright (c) 2021-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 @@ -11,15 +11,198 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pandas as pd +import cupy as cp +import numpy as np + import pytest +from . import utils + + +# ============================================================================= +# Fixture parameters +# ============================================================================= +class COOTestGraphDeviceData: + def __init__(self, srcs, dsts, weights, name): + self.srcs = srcs + self.dsts = dsts + self.weights = weights + self.name = name + self.is_valid = not(name.startswith("Invalid")) + + +InvalidNumWeights_1 = COOTestGraphDeviceData( + srcs=cp.asarray([0, 1, 2], dtype=np.int32), + dsts=cp.asarray([1, 2, 3], dtype=np.int32), + weights=cp.asarray([1.0, 1.0, 1.0, 1.0], dtype=np.float32), + name="InvalidNumWeights_1" + ) + +InvalidNumVerts_1 = COOTestGraphDeviceData( + srcs=cp.asarray([1, 2], dtype=np.int32), + dsts=cp.asarray([1, 2, 3], dtype=np.int32), + weights=cp.asarray([1.0, 1.0, 1.0], dtype=np.float32), + name="InvalidNumVerts_1" + ) + +Simple_1 = COOTestGraphDeviceData( + srcs=cp.asarray([0, 1, 2], dtype=np.int32), + dsts=cp.asarray([1, 2, 3], dtype=np.int32), + weights=cp.asarray([1.0, 1.0, 1.0], dtype=np.float32), + name="Simple_1" + ) + +Simple_2 = COOTestGraphDeviceData( + srcs=cp.asarray([0, 1, 1, 2, 2, 2, 3, 4], dtype=np.int32), + dsts=cp.asarray([1, 3, 4, 0, 1, 3, 5, 5], dtype=np.int32), + weights=cp.asarray([0.1, 2.1, 1.1, 5.1, 3.1, 4.1, 7.2, 3.2], + dtype=np.float32), + name="Simple_2" + ) + + +# The objects in these lists must have a "name" attr, since fixtures will +# access that to pass to tests, which then may use the name to associate to +# expected test results. The name attr is also used for the pytest test ID. +valid_datasets = [utils.RAPIDS_DATASET_ROOT_DIR_PATH/"karate.csv", + utils.RAPIDS_DATASET_ROOT_DIR_PATH/"dolphins.csv", + Simple_1, + Simple_2, + ] +all_datasets = valid_datasets + \ + [InvalidNumWeights_1, + InvalidNumVerts_1, + ] + -@pytest.fixture -def package_under_test(): +# ============================================================================= +# Helper functions +# ============================================================================= +def get_graph_data_for_dataset(ds, ds_name): """ - Create a fixture to import the package under test. This is useful since - bugs that prevent the package under test from being imported will not - prevent pytest from collecting, listing, running, etc. the tests. + Given an object representing either a path to a dataset on disk, or an + object containing raw data, return a series of arrays that can be used to + construct a graph object. The final value is a bool used to indicate if the + data is valid or not (invalid to test error handling). """ - import pylibcugraph - return pylibcugraph + if isinstance(ds, COOTestGraphDeviceData): + device_srcs = ds.srcs + device_dsts = ds.dsts + device_weights = ds.weights + is_valid = ds.is_valid + else: + pdf = pd.read_csv(ds, + delimiter=" ", header=None, + names=["0", "1", "weight"], + dtype={"0": "int32", "1": "int32", + "weight": "float32"}, + ) + device_srcs = cp.asarray(pdf["0"].to_numpy(), dtype=np.int32) + device_dsts = cp.asarray(pdf["1"].to_numpy(), dtype=np.int32) + device_weights = cp.asarray(pdf["weight"].to_numpy(), dtype=np.float32) + # Assume all datasets on disk are valid + is_valid = True + + return (device_srcs, device_dsts, device_weights, ds_name, is_valid) + + +def create_SGGraph(device_srcs, + device_dsts, + device_weights, + transposed=False): + """ + Creates and returns a SGGraph instance and the corresponding ResourceHandle + using the parameters passed in. + """ + from pylibcugraph.experimental import (SGGraph, + ResourceHandle, + GraphProperties, + ) + resource_handle = ResourceHandle() + graph_props = GraphProperties(is_symmetric=False, is_multigraph=False) + + g = SGGraph(resource_handle, + graph_props, + device_srcs, + device_dsts, + device_weights, + store_transposed=transposed, + renumber=False, + do_expensive_check=False) + + # FIXME: add coverage for renumber=True and do_expensive_check=True + + return (g, resource_handle) + + +# ============================================================================= +# Pytest fixtures +# ============================================================================= +@pytest.fixture(scope="package", + params=[pytest.param(ds, id=ds.name) for ds in all_datasets]) +def graph_data(request): + """ + Return a series of cupy arrays that can be used to construct Graph + objects. The parameterization includes invalid arrays which can be used to + test error handling, so the final value returned indicated if the arrays + are valid or not. + """ + return get_graph_data_for_dataset(request.param, request.param.name) + + +@pytest.fixture(scope="package", + params=[pytest.param(ds, id=ds.name) for ds in valid_datasets]) +def valid_graph_data(request): + """ + Return a series of cupy arrays that can be used to construct Graph objects, + all of which are valid (last value in returned tuple is always True). + """ + return get_graph_data_for_dataset(request.param, request.param.name) + + +@pytest.fixture(scope="package") +def sg_graph_objs(valid_graph_data, request): + """ + Returns a tuple containing the SGGraph object constructed from + parameterized values returned by the valid_graph_data fixture, + the associated resource handle, and the name of the dataset + used to construct the graph. + """ + (device_srcs, device_dsts, device_weights, ds_name, is_valid) = \ + valid_graph_data + + if is_valid is False: + pytest.exit("got invalid graph data - expecting only valid data") + + (g, resource_handle) = \ + create_SGGraph(device_srcs, + device_dsts, + device_weights, + transposed=False) + + return (g, resource_handle, ds_name) + + +@pytest.fixture(scope="package") +def sg_transposed_graph_objs(valid_graph_data, request): + """ + Returns a tuple containing the SGGraph object constructed from + parameterized values returned by the valid_graph_data fixture, + the associated resource handle, and the name of the dataset + used to construct the graph. + The SGGraph object is created with the transposed arg set to True. + """ + (device_srcs, device_dsts, device_weights, ds_name, is_valid) = \ + valid_graph_data + + if is_valid is False: + pytest.exit("got invalid graph data - expecting only valid data") + + (g, resource_handle) = \ + create_SGGraph(device_srcs, + device_dsts, + device_weights, + transposed=True) + + return (g, resource_handle, ds_name) diff --git a/python/pylibcugraph/pylibcugraph/tests/test_connected_components.py b/python/pylibcugraph/pylibcugraph/tests/test_connected_components.py index e4316bbdc47..9bef1e3b6df 100644 --- a/python/pylibcugraph/pylibcugraph/tests/test_connected_components.py +++ b/python/pylibcugraph/pylibcugraph/tests/test_connected_components.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, NVIDIA CORPORATION. +# Copyright (c) 2021-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 @@ -21,6 +21,10 @@ from . import utils + +# ============================================================================= +# Test data +# ============================================================================= _test_data = { "graph1": # asymmetric { @@ -96,6 +100,9 @@ } +# ============================================================================= +# Pytest fixtures +# ============================================================================= @pytest.fixture(scope="module", params=[pytest.param(value, id=key) for (key, value) in _test_data.items()]) @@ -138,6 +145,9 @@ def input_and_expected_output(request): expected_output_dict) +# ============================================================================= +# Helper functions +# ============================================================================= def _check_labels(vertex_ordered_labels, expected_vertex_comps): """ vertex_ordered_labels is a list of labels, ordered by the position of the @@ -172,8 +182,9 @@ def _check_labels(vertex_ordered_labels, expected_vertex_comps): assert actual_vertex_comps == sorted(expected_vertex_comps) -############################################################################### +# ============================================================================= # Tests +# ============================================================================= def test_import(): """ Ensure pylibcugraph is importable. @@ -182,11 +193,11 @@ def test_import(): import pylibcugraph # noqa: F401 -def test_scc(package_under_test, input_and_expected_output): +def test_scc(input_and_expected_output): """ Tests strongly_connected_components() """ - pylibcugraph = package_under_test + import pylibcugraph ((cupy_offsets, cupy_indices, cupy_labels_to_populate, num_verts, num_edges), expected_output_dict) = input_and_expected_output @@ -204,11 +215,11 @@ def test_scc(package_under_test, input_and_expected_output): expected_output_dict["scc_comp_vertices"]) -def test_wcc(package_under_test, input_and_expected_output): +def test_wcc(input_and_expected_output): """ Tests weakly_connected_components() """ - pylibcugraph = package_under_test + import pylibcugraph ((cupy_offsets, cupy_indices, cupy_labels_to_populate, num_verts, num_edges), expected_output_dict) = input_and_expected_output @@ -228,12 +239,12 @@ def test_wcc(package_under_test, input_and_expected_output): @pytest.mark.parametrize("api_name", ["strongly_connected_components", "weakly_connected_components"]) -def test_non_CAI_input(package_under_test, api_name): +def test_non_CAI_input(api_name): """ Ensures that the *_connected_components() APIs only accepts instances of objects that have a __cuda_array_interface__ """ - pylibcugraph = package_under_test + import pylibcugraph cupy_array = cp.ndarray(range(8)) python_list = list(range(8)) api = getattr(pylibcugraph, api_name) @@ -270,10 +281,11 @@ def test_non_CAI_input(package_under_test, api_name): @pytest.mark.parametrize("api_name", ["strongly_connected_components", "weakly_connected_components"]) -def test_bad_dtypes(package_under_test, api_name): +def test_bad_dtypes(api_name): """ Ensures that only supported dtypes are accepted. """ + import pylibcugraph graph = [ [0, 1, 1, 0, 0], [0, 0, 1, 0, 0], @@ -285,7 +297,6 @@ def test_bad_dtypes(package_under_test, api_name): num_verts = scipy_csr.get_shape()[0] num_edges = scipy_csr.nnz - pylibcugraph = package_under_test api = getattr(pylibcugraph, api_name) cp_offsets = cp.asarray(scipy_csr.indptr) diff --git a/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py b/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py new file mode 100644 index 00000000000..fedcca8cae8 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py @@ -0,0 +1,104 @@ +# 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. + +import pytest + + +# ============================================================================= +# Pytest fixtures +# ============================================================================= +# fixtures used in this test module are defined in conftest.py + + +# ============================================================================= +# Tests +# ============================================================================= +def test_graph_properties(): + from pylibcugraph.experimental import GraphProperties + + gp = GraphProperties() + assert gp.is_symmetric is False + assert gp.is_multigraph is False + + gp.is_symmetric = True + assert gp.is_symmetric is True + gp.is_symmetric = 0 + assert gp.is_symmetric is False + with pytest.raises(TypeError): + gp.is_symmetric = "foo" + + gp.is_multigraph = True + assert gp.is_multigraph is True + gp.is_multigraph = 0 + assert gp.is_multigraph is False + with pytest.raises(TypeError): + gp.is_multigraph = "foo" + + gp = GraphProperties(is_symmetric=True, is_multigraph=True) + assert gp.is_symmetric is True + assert gp.is_multigraph is True + + gp = GraphProperties(is_multigraph=True, is_symmetric=False) + assert gp.is_symmetric is False + assert gp.is_multigraph is True + + with pytest.raises(TypeError): + gp = GraphProperties(is_symmetric="foo", is_multigraph=False) + + with pytest.raises(TypeError): + gp = GraphProperties(is_multigraph=[]) + + +def test_resource_handle(): + from pylibcugraph.experimental import ResourceHandle + # This type has no attributes and is just defined to pass a struct from C + # back in to C. In the future it may take args to acquire specific + # resources, but for now just make sure nothing crashes. + rh = ResourceHandle() + del rh + + +def test_sg_graph(graph_data): + from pylibcugraph.experimental import (SGGraph, + ResourceHandle, + GraphProperties, + ) + # is_valid will only be True if the arrays are expected to produce a valid + # graph. If False, ensure SGGraph() raises the proper exception. + (device_srcs, device_dsts, device_weights, ds_name, is_valid) = graph_data + + graph_props = GraphProperties(is_symmetric=False, is_multigraph=False) + resource_handle = ResourceHandle() + + if is_valid: + g = SGGraph(resource_handle, # noqa:F841 + graph_props, + device_srcs, + device_dsts, + device_weights, + store_transposed=False, + renumber=False, + do_expensive_check=False) + # call SGGraph.__dealloc__() + del g + + else: + with pytest.raises(RuntimeError): + SGGraph(resource_handle, + graph_props, + device_srcs, + device_dsts, + device_weights, + store_transposed=False, + renumber=False, + do_expensive_check=False) diff --git a/python/pylibcugraph/pylibcugraph/tests/test_pagerank.py b/python/pylibcugraph/pylibcugraph/tests/test_pagerank.py new file mode 100644 index 00000000000..27b1731cc10 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/tests/test_pagerank.py @@ -0,0 +1,127 @@ +# 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. + +import pytest +import cupy as cp +import numpy as np + + +# ============================================================================= +# Test data +# ============================================================================= +_alpha = 0.85 +_epsilon = 1.0e-6 +_max_iterations = 500 + +# Map the names of input data to expected pagerank output +# The result names correspond to the datasets defined in conftest.py +_test_data = {"karate.csv": + (cp.asarray(range(34), dtype=np.int32), + cp.asarray( + [0.096998, 0.052877, 0.057078, 0.035860, 0.021978, 0.029111, + 0.029111, 0.024491, 0.029766, 0.014309, 0.021978, 0.009565, + 0.014645, 0.029536, 0.014536, 0.014536, 0.016784, 0.014559, + 0.014536, 0.019605, 0.014536, 0.014559, 0.014536, 0.031522, + 0.021076, 0.021006, 0.015044, 0.025640, 0.019573, 0.026288, + 0.024590, 0.037158, 0.071693, 0.100919, + ], + dtype=np.float32), + ), + "dolphins.csv": + (cp.asarray(range(62), dtype=np.int32), + cp.asarray( + [0.01696534, 0.02465084, 0.01333804, 0.00962903, + 0.00507979, 0.01442816, 0.02005379, 0.01564308, + 0.01709825, 0.02345867, 0.01510835, 0.00507979, + 0.0048353, 0.02615709, 0.03214436, 0.01988301, + 0.01662675, 0.03172837, 0.01939547, 0.01292825, + 0.02464085, 0.01693892, 0.00541593, 0.00986347, + 0.01690569, 0.01150429, 0.0112102, 0.01713019, + 0.01484573, 0.02645844, 0.0153021, 0.00541593, + 0.01330877, 0.02842296, 0.01591988, 0.00491821, + 0.02061337, 0.02987523, 0.02393915, 0.00776477, + 0.02196631, 0.01613769, 0.01761861, 0.02169104, + 0.01283079, 0.02951408, 0.00882587, 0.01733948, + 0.00526172, 0.00887672, 0.01923187, 0.03129924, + 0.01207255, 0.00818102, 0.02165103, 0.00749415, + 0.0083263, 0.0300956, 0.00496289, 0.01476788, + 0.00619018, 0.01103916, + ], + dtype=np.float32), + ), + "Simple_1": + (cp.asarray(range(4), dtype=np.int32), + cp.asarray( + [0.11615585, 0.21488841, 0.2988108, 0.3701449], + dtype=np.float32) + ), + "Simple_2": + (cp.asarray(range(6), dtype=np.int32), + cp.asarray( + [0.09902544, 0.17307726, 0.0732199, 0.1905103, + 0.12379099, 0.34037617, + ], + dtype=np.float32) + ), + } + +# ============================================================================= +# Pytest fixtures +# ============================================================================= +# fixtures used in this test module are defined in conftest.py + + +# ============================================================================= +# Tests +# ============================================================================= + +# FIXME: add tests for non-transposed graphs too, which should either work (via +# auto-transposing in C) or raise the appropriate exception. + +def test_pagerank(sg_transposed_graph_objs): + from pylibcugraph.experimental import pagerank + + (g, resource_handle, ds_name) = sg_transposed_graph_objs + (expected_verts, expected_pageranks) = _test_data[ds_name] + + precomputed_vertex_out_weight_sums = None + has_initial_guess = False + do_expensive_check = False + + result = pagerank(resource_handle, + g, + precomputed_vertex_out_weight_sums, + _alpha, + _epsilon, + _max_iterations, + has_initial_guess, + do_expensive_check) + + num_expected_verts = len(expected_verts) + (actual_verts, actual_pageranks) = result + + # Do a simple check using the vertices as array indices. First, ensure + # the test data vertices start from 0 with no gaps. + assert sum(range(num_expected_verts)) == sum(expected_verts) + + assert actual_verts.dtype == expected_verts.dtype + assert actual_pageranks.dtype == expected_pageranks.dtype + + actual_pageranks = actual_pageranks.tolist() + actual_verts = actual_verts.tolist() + expected_pageranks = expected_pageranks.tolist() + + for i in range(num_expected_verts): + assert actual_pageranks[i] == \ + pytest.approx(expected_pageranks[actual_verts[i]], 1e-4), \ + f"actual != expected for result at index {i}" diff --git a/python/pylibcugraph/pylibcugraph/tests/test_sssp.py b/python/pylibcugraph/pylibcugraph/tests/test_sssp.py new file mode 100644 index 00000000000..9649a9f98df --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/tests/test_sssp.py @@ -0,0 +1,160 @@ +# 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. + +import pytest +import cupy as cp +import numpy as np + + +# ============================================================================= +# Test data +# ============================================================================= + +# Map the names of input data to expected pagerank output +# The result names correspond to the datasets defined in conftest.py +_test_data = {"karate.csv": { + "start_vertex": 1, + "vertex": cp.asarray(range(34), dtype=np.int32), + "distance": cp.asarray( + [1., 0., 1., 1., 2., 2., 2., 1., 2., 2., 2., 2., 2., 1., + 3., 3., 3., 1., 3., 1., 3., 1., 3., 3., 3., 3., 3., 2., + 2., 3., 1., 2., 2., 2., + ], + dtype=np.float32), + "predecessor": cp.asarray( + [1, -1, 1, 1, 0, 0, 0, 1, 0, 2, 0, 0, 0, 1, 32, + 32, 5, 1, 32, 1, 32, 1, 32, 32, 27, 31, 33, 2, 2, 32, + 1, 0, 2, 13, + ], + dtype=np.int32), + }, + "dolphins.csv": { + "start_vertex": 1, + "vertex": cp.asarray(range(62), dtype=np.int32), + "distance": cp.asarray( + [3., 0., 4., 3., 4., 3., 2., 2., 2., 2., 3., 4., 4., 2., + 3., 3., 3., 1., 3., 1., 2., 3., 2., 2., 4., 2., 1., 1., + 1., 4., 2., 2., 3., 3., 3., 5., 1., 2., 3., 2., 2., 1., + 3., 3., 3., 3., 4., 2., 3., 4., 3., 3., 3., 4., 1., 4., + 3., 2., 4., 2., 4., 3., + ], + dtype=np.float32), + "predecessor": cp.asarray( + [40, -1, 10, 59, 51, 13, 54, 54, 28, 41, 47, 51, 33, 41, + 37, 40, 37, 1, 20, 1, 28, 37, 17, 36, 45, 17, 1, 1, + 1, 10, 19, 17, 9, 37, 37, 29, 1, 36, 20, 36, 36, 1, + 30, 37, 20, 23, 43, 28, 57, 34, 20, 23, 40, 43, 1, 51, + 6, 41, 38, 36, 32, 37, + ], + dtype=np.int32), + }, + "Simple_1": { + "start_vertex": 1, + "vertex": cp.asarray(range(4), dtype=np.int32), + "distance": cp.asarray( + [3.4028235e+38, 0.0000000e+00, 1.0000000e+00, + 2.0000000e+00, + ], + dtype=np.float32), + "predecessor": cp.asarray( + [-1, -1, 1, 2, + ], + dtype=np.int32), + }, + "Simple_2": { + "start_vertex": 1, + "vertex": cp.asarray(range(6), dtype=np.int32), + "distance": cp.asarray( + [3.4028235e+38, 0.0000000e+00, 3.4028235e+38, + 2.0999999e+00, 1.1000000e+00, 4.3000002e+00 + ], + dtype=np.float32), + "predecessor": cp.asarray( + [-1, -1, -1, 1, 1, 4, + ], + dtype=np.int32), + }, + } + +# ============================================================================= +# Pytest fixtures +# ============================================================================= +# fixtures used in this test module are defined in conftest.py + + +# ============================================================================= +# Helper functions +# ============================================================================= + + +# ============================================================================= +# Tests +# ============================================================================= +def test_sssp(sg_graph_objs): + from pylibcugraph.experimental import sssp + + (g, resource_handle, ds_name) = sg_graph_objs + + (source, + expected_verts, + expected_distances, + expected_predecessors) = _test_data[ds_name].values() + + cutoff = 999999999 # maximum edge weight sum to consider + compute_predecessors = True + do_expensive_check = False + + result = sssp(resource_handle, + g, + source, + cutoff, + compute_predecessors, + do_expensive_check) + + num_expected_verts = len(expected_verts) + (actual_verts, actual_distances, actual_predecessors) = result + + # Do a simple check using the vertices as array indices. First, ensure + # the test data vertices start from 0 with no gaps. + assert sum(range(num_expected_verts)) == sum(expected_verts) + + assert actual_verts.dtype == expected_verts.dtype + assert actual_distances.dtype == expected_distances.dtype + assert actual_predecessors.dtype == expected_predecessors.dtype + + actual_verts = actual_verts.tolist() + actual_distances = actual_distances.tolist() + actual_predecessors = actual_predecessors.tolist() + expected_distances = expected_distances.tolist() + expected_predecessors = expected_predecessors.tolist() + + for i in range(num_expected_verts): + actual_distance = actual_distances[i] + expected_distance = expected_distances[actual_verts[i]] + # The distance value will be a MAX value (2**128) if there is no + # predecessor, so only do a closer compare if either the actual or + # expected are not that MAX value. + if (actual_distance <= 3.4e38) or (expected_distance <= 3.4e38): + assert actual_distance == \ + pytest.approx(expected_distance, 1e-4), \ + f"actual != expected for distance result at index {i}" + + # The array of predecessors for graphs with multiple paths that are + # equally short are non-deterministic, so skip those checks for + # specific graph inputs. + # FIXME: add a helper to verify paths are correct when results are + # valid but non-deterministic + if ds_name not in ["karate.csv", "dolphins.csv"]: + assert actual_predecessors[i] == \ + pytest.approx(expected_predecessors[actual_verts[i]], 1e-4), \ + f"actual != expected for predecessor result at index {i}" diff --git a/python/pylibcugraph/pylibcugraph/tests/utils.py b/python/pylibcugraph/pylibcugraph/tests/utils.py index 2856fb8af5a..bdf15b8efa4 100644 --- a/python/pylibcugraph/pylibcugraph/tests/utils.py +++ b/python/pylibcugraph/pylibcugraph/tests/utils.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021, NVIDIA CORPORATION. +# Copyright (c) 2021-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 @@ -13,7 +13,9 @@ import os from pathlib import Path +from itertools import product +import pytest import cudf @@ -21,11 +23,6 @@ RAPIDS_DATASET_ROOT_DIR_PATH = Path(RAPIDS_DATASET_ROOT_DIR) -DATASETS = [RAPIDS_DATASET_ROOT_DIR_PATH/f for f in [ - "karate.csv", - "dolphins.csv"]] - - def read_csv_file(csv_file, weights_dtype="float32"): return cudf.read_csv( csv_file, @@ -33,3 +30,58 @@ def read_csv_file(csv_file, weights_dtype="float32"): dtype=["int32", "int32", weights_dtype], header=None, ) + + +def genFixtureParamsProduct(*args): + """ + Returns the cartesian product of the param lists passed in. The lists must + be flat lists of pytest.param objects, and the result will be a flat list + of pytest.param objects with values and meta-data combined accordingly. A + flat list of pytest.param objects is required for pytest fixtures to + properly recognize the params. The combinations also include ids generated + from the param values and id names associated with each list. For example: + + genFixtureParamsProduct( ([pytest.param(True, marks=[pytest.mark.A_good]), + pytest.param(False, marks=[pytest.mark.A_bad])], + "A"), + ([pytest.param(True, marks=[pytest.mark.B_good]), + pytest.param(False, marks=[pytest.mark.B_bad])], + "B") ) + + results in fixture param combinations: + + True, True - marks=[A_good, B_good] - id="A=True,B=True" + True, False - marks=[A_good, B_bad] - id="A=True,B=False" + False, True - marks=[A_bad, B_good] - id="A=False,B=True" + False, False - marks=[A_bad, B_bad] - id="A=False,B=False" + + Simply using itertools.product on the lists would result in a list of + sublists of individual param objects (ie. not "merged"), which would not be + recognized properly as params for a fixture by pytest. + + NOTE: This function is only needed for parameterized fixtures. + Tests/benchmarks will automatically get this behavior when specifying + multiple @pytest.mark.parameterize(param_name, param_value_list) + decorators. + """ + # Ensure each arg is a list of pytest.param objs, then separate the params + # and IDs. + paramLists = [] + ids = [] + paramType = pytest.param().__class__ + for (paramList, id) in args: + for i in range(len(paramList)): + if not isinstance(paramList[i], paramType): + paramList[i] = pytest.param(paramList[i]) + paramLists.append(paramList) + ids.append(id) + + retList = [] + for paramCombo in product(*paramLists): + values = [p.values[0] for p in paramCombo] + marks = [m for p in paramCombo for m in p.marks] + comboid = ",".join( + ["%s=%s" % (id, p.values[0]) for (p, id) in zip(paramCombo, ids)] + ) + retList.append(pytest.param(values, marks=marks, id=comboid)) + return retList diff --git a/python/pylibcugraph/pylibcugraph/utilities/__init__.py b/python/pylibcugraph/pylibcugraph/utilities/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/pylibcugraph/pylibcugraph/utilities/api_tools.py b/python/pylibcugraph/pylibcugraph/utilities/api_tools.py new file mode 100644 index 00000000000..e0281d86e5c --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/utilities/api_tools.py @@ -0,0 +1,58 @@ +# 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. + +import functools +import warnings +import inspect + +experimental_prefix = "EXPERIMENTAL" + + +def experimental_warning_wrapper(obj, make_public_name=True): + """ + Return a callable obj wrapped in a callable the prints a warning about it + being "experimental" (an object that is in the public API but subject to + change or removal) prior to calling it and returning its value. + + If make_public_name is False, the object's name used in the warning message + is left unmodified. If True (default), any leading __ and/or EXPERIMENTAL + string are removed from the name used in warning messages. This allows an + object to be named with a "private" name in the public API so it can remain + hidden while it is still experimental, but have a public name within the + experimental namespace so it can be easily discovered and used. + """ + obj_name = obj.__qualname__ + if make_public_name: + obj_name = obj_name.lstrip(experimental_prefix) + obj_name = obj_name.lstrip("__") + + # Assume the caller of this function is the module containing the + # experimental obj and try to get its namespace name. Default to no + # namespace name if it could not be found. + call_stack = inspect.stack() + calling_frame = call_stack[1].frame + ns_name = calling_frame.f_locals.get("__name__") + if ns_name is not None: + ns_name += "." + else: + ns_name = "" + + warning_msg = (f"{ns_name}{obj_name} is experimental and will change " + "or be removed in a future release.") + + @functools.wraps(obj) + def callable_warning_wrapper(*args, **kwargs): + warnings.warn(warning_msg, PendingDeprecationWarning) + return obj(*args, **kwargs) + + return callable_warning_wrapper diff --git a/python/pylibcugraph/pylibcugraph/utils.pxd b/python/pylibcugraph/pylibcugraph/utils.pxd new file mode 100644 index 00000000000..655f68e1163 --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/utils.pxd @@ -0,0 +1,32 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +from pylibcugraph._cugraph_c.cugraph_api cimport ( + data_type_id_t, +) +from pylibcugraph._cugraph_c.error cimport ( + cugraph_error_code_t, + cugraph_error_t, +) + + +cdef assert_success(cugraph_error_code_t code, + cugraph_error_t* err, + api_name) + +cdef assert_CAI_type(obj, var_name, allow_None=*) + +cdef get_numpy_type_from_c_type(data_type_id_t c_type) diff --git a/python/pylibcugraph/pylibcugraph/utils.pyx b/python/pylibcugraph/pylibcugraph/utils.pyx new file mode 100644 index 00000000000..608773a6e9a --- /dev/null +++ b/python/pylibcugraph/pylibcugraph/utils.pyx @@ -0,0 +1,66 @@ +# 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. + +# Have cython use python 3 syntax +# cython: language_level = 3 + +import numpy + +# FIXME: add tests for this +cdef assert_success(cugraph_error_code_t code, + cugraph_error_t* err, + api_name): + if code != cugraph_error_code_t.CUGRAPH_SUCCESS: + if code == cugraph_error_code_t.CUGRAPH_UNKNOWN_ERROR: + code_str = "CUGRAPH_UNKNOWN_ERROR" + elif code == cugraph_error_code_t.CUGRAPH_INVALID_HANDLE: + code_str = "CUGRAPH_INVALID_HANDLE" + elif code == cugraph_error_code_t.CUGRAPH_ALLOC_ERROR: + code_str = "CUGRAPH_ALLOC_ERROR" + elif code == cugraph_error_code_t.CUGRAPH_INVALID_INPUT: + code_str = "CUGRAPH_INVALID_INPUT" + elif code == cugraph_error_code_t.CUGRAPH_NOT_IMPLEMENTED: + code_str = "CUGRAPH_NOT_IMPLEMENTED" + elif code == cugraph_error_code_t.CUGRAPH_UNSUPPORTED_TYPE_COMBINATION: + code_str = "CUGRAPH_UNSUPPORTED_TYPE_COMBINATION" + else: + code_str = "unknown error code" + # FIXME: extract message using cugraph_error_message() + # FIXME: If error_ptr has a value, free it using cugraph_error_free() + raise RuntimeError(f"non-success value returned from {api_name}: {code_str}") + + +cdef assert_CAI_type(obj, var_name, allow_None=False): + if allow_None: + if obj is None: + return + msg = f"{var_name} must be None or support __cuda_array_interface__" + else: + msg = f"{var_name} does not support __cuda_array_interface__" + + if not(hasattr(obj, "__cuda_array_interface__")): + raise TypeError(msg) + + +cdef get_numpy_type_from_c_type(data_type_id_t c_type): + if c_type == data_type_id_t.INT32: + return numpy.int32 + elif c_type == data_type_id_t.INT64: + return numpy.int64 + elif c_type == data_type_id_t.FLOAT32: + return numpy.float32 + elif c_type == data_type_id_t.FLOAT64: + return numpy.float64 + else: + raise RuntimeError("Internal error: got invalid data type enum value " + f"from C: {c_type}") diff --git a/python/pylibcugraph/setup.py b/python/pylibcugraph/setup.py index 74835d2ac32..da8832b80ea 100644 --- a/python/pylibcugraph/setup.py +++ b/python/pylibcugraph/setup.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018-2021, NVIDIA CORPORATION. +# Copyright (c) 2018-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 @@ -130,7 +130,8 @@ def run(self): cuda_lib_dir, os.path.join(os.sys.prefix, "lib") ], - libraries=['cudart', 'cusparse', 'cusolver', 'cugraph', 'nccl'], + libraries=['cudart', 'cusparse', 'cusolver', 'cugraph', 'nccl', + 'cugraph_c'], language='c++', extra_compile_args=['-std=c++17']) ]