diff --git a/cpp/include/cugraph_c/graph.h b/cpp/include/cugraph_c/graph.h index d95a23416cf..239078e20ea 100644 --- a/cpp/include/cugraph_c/graph.h +++ b/cpp/include/cugraph_c/graph.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -327,6 +327,41 @@ CUGRAPH_EXPORT cugraph_error_code_t cugraph_graph_create_with_times_mg( cugraph_graph_t** graph, cugraph_error_t** error); +/** + * @brief Get the number of vertices in a graph + * + * Returns the global number of vertices. For multi-GPU graphs this value is + * identical on all ranks. + * + * @param [in] graph Pointer to graph + * @param [out] result Where to store the number of vertices + * @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_EXPORT cugraph_error_code_t cugraph_graph_number_of_vertices(cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error); + +/** + * @brief Get the number of edges in a graph + * + * Returns the total number of edges in the graph. For multi-GPU graphs this is the + * global edge count, identical on all ranks. + * + * @param [in] handle Handle for accessing resources + * @param [in] graph Pointer to graph + * @param [out] result Where to store the number of edges + * @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_EXPORT cugraph_error_code_t +cugraph_graph_number_of_edges(const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error); + /** * @brief Destroy an graph * diff --git a/cpp/src/c_api/graph_functions.cpp b/cpp/src/c_api/graph_functions.cpp index 1a581d2d9ce..d5a52c060f3 100644 --- a/cpp/src/c_api/graph_functions.cpp +++ b/cpp/src/c_api/graph_functions.cpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -206,6 +206,66 @@ struct two_hop_neighbors_functor : public cugraph::c_api::abstract_functor { } }; +struct number_of_vertices_functor : public cugraph::c_api::abstract_functor { + cugraph::c_api::cugraph_graph_t* graph_{nullptr}; + size_t result_{}; + + explicit number_of_vertices_functor(::cugraph_graph_t* graph) + : abstract_functor(), graph_(reinterpret_cast(graph)) + { + } + + template + void operator()() + { + if constexpr (!cugraph::is_candidate::value) { + unsupported(); + } else { + auto graph = + reinterpret_cast*>( + graph_->graph_); + + result_ = static_cast(graph->view().number_of_vertices()); + } + } +}; + +struct number_of_edges_functor : public cugraph::c_api::abstract_functor { + cugraph::c_api::cugraph_graph_t* graph_{nullptr}; + size_t result_{}; + + explicit number_of_edges_functor(::cugraph_graph_t* graph) + : abstract_functor(), graph_(reinterpret_cast(graph)) + { + } + + template + void operator()() + { + if constexpr (!cugraph::is_candidate::value) { + unsupported(); + } else { + auto graph = + reinterpret_cast*>( + graph_->graph_); + + result_ = static_cast(graph->number_of_edges()); + } + } +}; + struct count_multi_edges_functor : public cugraph::c_api::abstract_functor { raft::handle_t const& handle_{}; cugraph::c_api::cugraph_graph_t* graph_{nullptr}; @@ -377,6 +437,27 @@ extern "C" cugraph_error_code_t cugraph_two_hop_neighbors( return cugraph::c_api::run_algorithm(graph, functor, result, error); } +extern "C" cugraph_error_code_t cugraph_graph_number_of_vertices(cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error) +{ + number_of_vertices_functor functor(graph); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} + +extern "C" cugraph_error_code_t cugraph_graph_number_of_edges( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error) +{ + (void)handle; + number_of_edges_functor functor(graph); + + return cugraph::c_api::run_algorithm(graph, functor, result, error); +} + extern "C" cugraph_error_code_t cugraph_count_multi_edges(const cugraph_resource_handle_t* handle, cugraph_graph_t* graph, bool_t do_expensive_check, diff --git a/cpp/src/structure/graph_impl.cuh b/cpp/src/structure/graph_impl.cuh index 1e762b11aaf..2bec7a76568 100644 --- a/cpp/src/structure/graph_impl.cuh +++ b/cpp/src/structure/graph_impl.cuh @@ -487,6 +487,22 @@ graph_t(indices.size()); + } +#if 1 // FIXME: we should add host_allreduce to raft + this->number_of_edges_ = host_scalar_allreduce( + handle.get_comms(), local_count, raft::comms::op_t::SUM, handle.get_stream()); +#else + handle.get_comms().host_allreduce( + std::addressof(local_count), std::addressof(local_count), size_t{1}, raft::comms::op_t::SUM); + this->number_of_edges_ = local_count; +#endif + } } template diff --git a/cpp/tests/c_api/create_graph_test.c b/cpp/tests/c_api/create_graph_test.c index 156c51dd600..411b8ab58b3 100644 --- a/cpp/tests/c_api/create_graph_test.c +++ b/cpp/tests/c_api/create_graph_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2021-2025, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -100,6 +100,19 @@ int test_create_sg_graph_simple() &ret_error); TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); + size_t result_num_vertices = 0; + size_t result_num_edges = 0; + + ret_code = cugraph_graph_number_of_vertices(graph, &result_num_vertices, &ret_error); + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_graph_number_of_vertices failed."); + TEST_ASSERT( + test_ret_value, result_num_vertices == num_vertices, "number of vertices did not match"); + + ret_code = cugraph_graph_number_of_edges(handle, graph, &result_num_edges, &ret_error); + TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_graph_number_of_edges failed."); + TEST_ASSERT(test_ret_value, result_num_edges == num_edges, "number of edges did not match"); + cugraph_graph_free(graph); cugraph_type_erased_device_array_view_free(wgt_view); diff --git a/cpp/tests/c_api/mg_create_graph_test.c b/cpp/tests/c_api/mg_create_graph_test.c index 7e6c121b2d2..7f3c13b516a 100644 --- a/cpp/tests/c_api/mg_create_graph_test.c +++ b/cpp/tests/c_api/mg_create_graph_test.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright (c) 2022, NVIDIA CORPORATION. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,8 @@ #include "mg_test_utils.h" /* RUN_TEST */ #include +#include +#include #include #include @@ -107,6 +109,26 @@ int test_create_mg_graph_simple(const cugraph_resource_handle_t* handle) TEST_ASSERT(test_ret_value, ret_code == CUGRAPH_SUCCESS, "graph creation failed."); TEST_ALWAYS_ASSERT(ret_code == CUGRAPH_SUCCESS, cugraph_error_message(ret_error)); + { + int comm_size = cugraph_resource_handle_get_comm_size(handle); + size_t expected_vertices = num_vertices * comm_size; + size_t expected_edges = num_edges * comm_size; + size_t result_num_vertices; + size_t result_num_edges; + + ret_code = cugraph_graph_number_of_vertices(graph, &result_num_vertices, &ret_error); + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_graph_number_of_vertices failed."); + TEST_ASSERT( + test_ret_value, result_num_vertices == expected_vertices, "number of vertices did not match"); + + ret_code = cugraph_graph_number_of_edges(handle, graph, &result_num_edges, &ret_error); + TEST_ASSERT( + test_ret_value, ret_code == CUGRAPH_SUCCESS, "cugraph_graph_number_of_edges failed."); + TEST_ASSERT( + test_ret_value, result_num_edges == expected_edges, "number of edges did not match"); + } + cugraph_graph_free(graph); cugraph_type_erased_device_array_view_free(wgt_view); diff --git a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd index 5362271d5a5..35570aa899f 100644 --- a/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd +++ b/python/pylibcugraph/pylibcugraph/_cugraph_c/graph.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # Have cython use python 3 syntax @@ -70,6 +70,19 @@ cdef extern from "cugraph_c/graph.h": cugraph_graph_t** graph, cugraph_error_t** error); + cdef cugraph_error_code_t \ + cugraph_graph_number_of_vertices( + cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error) + + cdef cugraph_error_code_t \ + cugraph_graph_number_of_edges( + const cugraph_resource_handle_t* handle, + cugraph_graph_t* graph, + size_t* result, + cugraph_error_t** error) + cdef void \ cugraph_graph_free( cugraph_graph_t* graph diff --git a/python/pylibcugraph/pylibcugraph/graphs.pyx b/python/pylibcugraph/pylibcugraph/graphs.pyx index a1f0c309f03..7066be5af34 100644 --- a/python/pylibcugraph/pylibcugraph/graphs.pyx +++ b/python/pylibcugraph/pylibcugraph/graphs.pyx @@ -18,6 +18,8 @@ from pylibcugraph._cugraph_c.graph cimport ( cugraph_graph_create_with_times_mg, cugraph_graph_create_sg_from_csr, cugraph_graph_free, + cugraph_graph_number_of_vertices, + cugraph_graph_number_of_edges, ) from pylibcugraph._cugraph_c.array cimport ( cugraph_type_erased_device_array_view_type, @@ -38,6 +40,58 @@ from pylibcugraph.utilities.api_tools import ensure_valid_dtypes from libc.stdlib cimport malloc +cdef class _GPUGraph: + + def number_of_vertices(self): + """ + Return the total number of vertices in the graph. + + For multi-GPU graphs this value is identical on all ranks. + + Returns + ------- + int + The number of vertices in the graph. + """ + cdef size_t result + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_graph_number_of_vertices( + self.c_graph_ptr, &result, &error_ptr) + assert_success(error_code, error_ptr, "cugraph_graph_number_of_vertices") + + return result + + def number_of_edges(self, ResourceHandle resource_handle): + """ + Return the total number of edges in the graph. + + For multi-GPU graphs this value is identical on all ranks. + + Parameters + ---------- + resource_handle : ResourceHandle + Handle to the underlying device resources. + + Returns + ------- + int + The number of edges in the graph. + """ + cdef size_t result + cdef cugraph_error_code_t error_code + cdef cugraph_error_t* error_ptr + + error_code = cugraph_graph_number_of_edges( + resource_handle.c_resource_handle_ptr, + self.c_graph_ptr, + &result, + &error_ptr) + assert_success(error_code, error_ptr, "cugraph_graph_number_of_edges") + + return result + cdef class SGGraph(_GPUGraph): """ diff --git a/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py b/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py index ff32ef498b2..808f49506ae 100644 --- a/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py +++ b/python/pylibcugraph/pylibcugraph/tests/test_graph_sg.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022-2023, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import pytest @@ -130,8 +130,9 @@ def test_SGGraph_create_from_cudf(): graph_props = GraphProperties(is_multigraph=False, is_symmetric=False) + resource_handle = ResourceHandle() plc_graph = SGGraph( - resource_handle=ResourceHandle(), + resource_handle=resource_handle, graph_properties=graph_props, src_or_offset_array=edgelist["src"], dst_or_index_array=edgelist["dst"], @@ -143,5 +144,34 @@ def test_SGGraph_create_from_cudf(): do_expensive_check=True, input_array_format="COO", ) - print("done", flush=True) - print(f"created SGGraph {plc_graph=}", flush=True) + assert plc_graph.number_of_vertices() == 5 + assert plc_graph.number_of_edges(resource_handle) == 3 + + +def test_sg_graph_number_of_vertices_and_edges(): + import cupy as cp + import numpy as np + from pylibcugraph import ResourceHandle, GraphProperties, SGGraph + + resource_handle = ResourceHandle() + graph_props = GraphProperties(is_symmetric=False, is_multigraph=False) + + device_srcs = cp.asarray([0, 1, 1, 2, 2, 2, 3, 4], dtype=np.int32) + device_dsts = cp.asarray([1, 3, 4, 0, 1, 3, 5, 5], dtype=np.int32) + device_weights = cp.asarray( + [0.1, 2.1, 1.1, 5.1, 3.1, 4.1, 7.2, 3.2], dtype=np.float32 + ) + + graph = SGGraph( + resource_handle=resource_handle, + graph_properties=graph_props, + src_or_offset_array=device_srcs, + dst_or_index_array=device_dsts, + weight_array=device_weights, + store_transposed=False, + renumber=False, + do_expensive_check=False, + ) + + assert graph.number_of_vertices() == 6 + assert graph.number_of_edges(resource_handle) == 8