Skip to content

Commit 2e459d8

Browse files
committed
add SG SCC tests
1 parent aad66de commit 2e459d8

File tree

2 files changed

+275
-6
lines changed

2 files changed

+275
-6
lines changed

cpp/tests/CMakeLists.txt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#=============================================================================
1+
#=============================================================================
22
# cmake-format: off
33
# SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
44
# SPDX-License-Identifier: Apache-2.0
@@ -353,11 +353,6 @@ ConfigureTest(EGONET_TEST community/egonet_test.cpp GPUS 1 PERCENT 75)
353353
# - FORCE ATLAS 2 tests --------------------------------------------------------------------------
354354
ConfigureTest(LEGACY_FA2_TEST layout/legacy/force_atlas2_test.cu)
355355

356-
###################################################################################################
357-
# - STRONGLY CONNECTED COMPONENTS tests ----------------------------------------------------------
358-
ConfigureTest(SCC_TEST components/scc_test.cu)
359-
target_include_directories(SCC_TEST PRIVATE "${CUGRAPH_SOURCE_DIR}/src")
360-
361356
###################################################################################################
362357
#-Hungarian (Linear Assignment Problem) tests ----------------------------------------------------
363358
ConfigureTest(HUNGARIAN_TEST linear_assignment/hungarian_test.cu)
@@ -471,6 +466,10 @@ ConfigureTest(EDGE_BETWEENNESS_CENTRALITY_TEST centrality/edge_betweenness_centr
471466
# - WEAKLY CONNECTED COMPONENTS tests -------------------------------------------------------------
472467
ConfigureTest(WEAKLY_CONNECTED_COMPONENTS_TEST components/weakly_connected_components_test.cpp)
473468

469+
###################################################################################################
470+
# - STRONGLY CONNECTED COMPONENTS tests -------------------------------------------------------------
471+
ConfigureTest(STRONGLY_CONNECTED_COMPONENTS_TEST components/strongly_connected_components_test.cpp)
472+
474473
###################################################################################################
475474
# - MIS tests -------------------------------------------------------------------------------------
476475
ConfigureTest(MIS_TEST components/mis_test.cu)
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include "utilities/base_fixture.hpp"
7+
#include "utilities/conversion_utilities.hpp"
8+
#include "utilities/property_generator_utilities.hpp"
9+
#include "utilities/test_graphs.hpp"
10+
11+
#include <cugraph/algorithms.hpp>
12+
#include <cugraph/graph.hpp>
13+
#include <cugraph/graph_functions.hpp>
14+
#include <cugraph/graph_view.hpp>
15+
#include <cugraph/utilities/high_res_timer.hpp>
16+
17+
#include <raft/core/handle.hpp>
18+
#include <raft/util/cudart_utils.hpp>
19+
20+
#include <rmm/device_uvector.hpp>
21+
#include <rmm/mr/cuda_memory_resource.hpp>
22+
23+
#include <gtest/gtest.h>
24+
25+
#include <algorithm>
26+
#include <iterator>
27+
#include <limits>
28+
#include <stack>
29+
#include <unordered_map>
30+
#include <vector>
31+
32+
// Tarjan's strongly connected components algorithm.
33+
// (https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm)
34+
template <typename vertex_t, typename edge_t>
35+
void strongly_connected_components_reference(edge_t const* offsets,
36+
vertex_t const* indices,
37+
vertex_t* components,
38+
vertex_t num_vertices)
39+
{
40+
using index_t = size_t;
41+
constexpr index_t invalid_index = std::numeric_limits<index_t>::max();
42+
43+
std::vector<index_t> index(num_vertices, invalid_index);
44+
std::vector<index_t> lowlink(num_vertices, invalid_index);
45+
std::vector<bool> on_stack(num_vertices, false);
46+
std::stack<vertex_t> S{};
47+
index_t current_index{0};
48+
vertex_t next_component_id{0};
49+
50+
std::fill(components, components + num_vertices, cugraph::invalid_component_id<vertex_t>::value);
51+
52+
auto strongconnect = [&](vertex_t v, auto&& strongconnect_ref) -> void {
53+
index[v] = current_index;
54+
lowlink[v] = current_index;
55+
++current_index;
56+
S.push(v);
57+
on_stack[v] = true;
58+
59+
// Consider successors of v (outgoing edges)
60+
edge_t nbr_begin = offsets[v];
61+
edge_t nbr_end = offsets[v + 1];
62+
for (edge_t e = nbr_begin; e != nbr_end; ++e) {
63+
vertex_t w = indices[e];
64+
if (index[w] == invalid_index) {
65+
strongconnect_ref(w, strongconnect_ref);
66+
lowlink[v] = std::min(lowlink[v], lowlink[w]);
67+
} else if (on_stack[w]) {
68+
lowlink[v] = std::min(lowlink[v], index[w]);
69+
}
70+
}
71+
72+
// If v is a root node, pop the stack and assign component id
73+
if (lowlink[v] == index[v]) {
74+
vertex_t w;
75+
do {
76+
w = S.top();
77+
S.pop();
78+
on_stack[w] = false;
79+
components[w] = next_component_id;
80+
} while (w != v);
81+
++next_component_id;
82+
}
83+
};
84+
85+
for (vertex_t v = 0; v < num_vertices; ++v) {
86+
if (index[v] == invalid_index) { strongconnect(v, strongconnect); }
87+
}
88+
}
89+
90+
struct StronglyConnectedComponents_Usecase {
91+
bool edge_masking{false};
92+
bool check_correctness{true};
93+
};
94+
95+
template <typename input_usecase_t>
96+
class Tests_StronglyConnectedComponent
97+
: public ::testing::TestWithParam<
98+
std::tuple<StronglyConnectedComponents_Usecase, input_usecase_t>> {
99+
public:
100+
Tests_StronglyConnectedComponent() {}
101+
102+
static void SetUpTestCase() {}
103+
static void TearDownTestCase() {}
104+
105+
virtual void SetUp() {}
106+
virtual void TearDown() {}
107+
108+
template <typename vertex_t, typename edge_t>
109+
void run_current_test(
110+
StronglyConnectedComponents_Usecase const& strongly_connected_components_usecase,
111+
input_usecase_t const& input_usecase)
112+
{
113+
constexpr bool renumber = true;
114+
115+
using weight_t = float; // dummy
116+
117+
raft::handle_t handle{};
118+
HighResTimer hr_timer{};
119+
120+
if (cugraph::test::g_perf) {
121+
RAFT_CUDA_TRY(cudaDeviceSynchronize()); // for consistent performance measurement
122+
hr_timer.start("Construct graph");
123+
}
124+
125+
cugraph::graph_t<vertex_t, edge_t, false, false> graph(handle);
126+
std::optional<rmm::device_uvector<vertex_t>> d_renumber_map_labels{std::nullopt};
127+
std::tie(graph, std::ignore, d_renumber_map_labels) =
128+
cugraph::test::construct_graph<vertex_t, edge_t, weight_t, false, false>(
129+
handle, input_usecase, false, renumber);
130+
131+
if (cugraph::test::g_perf) {
132+
RAFT_CUDA_TRY(cudaDeviceSynchronize()); // for consistent performance measurement
133+
hr_timer.stop();
134+
hr_timer.display_and_clear(std::cout);
135+
}
136+
137+
auto graph_view = graph.view();
138+
ASSERT_FALSE(graph_view.is_symmetric())
139+
<< "Strongly connected components works only on directed (asymmetric) graphs.";
140+
141+
std::optional<cugraph::edge_property_t<edge_t, bool>> edge_mask{std::nullopt};
142+
if (strongly_connected_components_usecase.edge_masking) {
143+
edge_mask =
144+
cugraph::test::generate<decltype(graph_view), bool>::edge_property(handle, graph_view, 2);
145+
graph_view.attach_edge_mask(edge_mask->view());
146+
}
147+
148+
if (cugraph::test::g_perf) {
149+
RAFT_CUDA_TRY(cudaDeviceSynchronize()); // for consistent performance measurement
150+
hr_timer.start("Strongly_connected_components");
151+
}
152+
153+
auto d_components = cugraph::strongly_connected_components(handle, graph_view);
154+
155+
if (cugraph::test::g_perf) {
156+
RAFT_CUDA_TRY(cudaDeviceSynchronize()); // for consistent performance measurement
157+
hr_timer.stop();
158+
hr_timer.display_and_clear(std::cout);
159+
}
160+
161+
if (strongly_connected_components_usecase.check_correctness) {
162+
cugraph::graph_t<vertex_t, edge_t, false, false> unrenumbered_graph(handle);
163+
if (renumber) {
164+
std::tie(unrenumbered_graph, std::ignore, std::ignore) =
165+
cugraph::test::construct_graph<vertex_t, edge_t, weight_t, false, false>(
166+
handle, input_usecase, false, false);
167+
}
168+
auto unrenumbered_graph_view = renumber ? unrenumbered_graph.view() : graph_view;
169+
170+
auto h_offsets = cugraph::test::to_host(
171+
handle, unrenumbered_graph_view.local_edge_partition_view().offsets());
172+
auto h_indices = cugraph::test::to_host(
173+
handle, unrenumbered_graph_view.local_edge_partition_view().indices());
174+
175+
std::vector<vertex_t> h_reference_components(unrenumbered_graph_view.number_of_vertices());
176+
177+
strongly_connected_components_reference(h_offsets.data(),
178+
h_indices.data(),
179+
h_reference_components.data(),
180+
unrenumbered_graph_view.number_of_vertices());
181+
182+
std::vector<vertex_t> h_cugraph_components{};
183+
if (renumber) {
184+
rmm::device_uvector<vertex_t> d_unrenumbered_components(size_t{0}, handle.get_stream());
185+
std::tie(std::ignore, d_unrenumbered_components) =
186+
cugraph::test::sort_by_key<vertex_t, vertex_t>(
187+
handle, *d_renumber_map_labels, d_components);
188+
h_cugraph_components = cugraph::test::to_host(handle, d_unrenumbered_components);
189+
} else {
190+
h_cugraph_components = cugraph::test::to_host(handle, d_components);
191+
}
192+
193+
std::unordered_map<vertex_t, vertex_t> cuda_to_reference_map{};
194+
for (size_t i = 0; i < h_reference_components.size(); ++i) {
195+
cuda_to_reference_map.insert({h_cugraph_components[i], h_reference_components[i]});
196+
}
197+
std::transform(
198+
h_cugraph_components.begin(),
199+
h_cugraph_components.end(),
200+
h_cugraph_components.begin(),
201+
[&cuda_to_reference_map](auto cugraph_c) { return cuda_to_reference_map[cugraph_c]; });
202+
203+
ASSERT_TRUE(std::equal(
204+
h_reference_components.begin(), h_reference_components.end(), h_cugraph_components.begin()))
205+
<< "components do not match with the reference values.";
206+
}
207+
}
208+
};
209+
210+
using Tests_StronglyConnectedComponents_File =
211+
Tests_StronglyConnectedComponent<cugraph::test::File_Usecase>;
212+
using Tests_StronglyConnectedComponents_Rmat =
213+
Tests_StronglyConnectedComponent<cugraph::test::Rmat_Usecase>;
214+
215+
TEST_P(Tests_StronglyConnectedComponents_File, CheckInt32Int32)
216+
{
217+
auto param = GetParam();
218+
run_current_test<int32_t, int32_t>(std::get<0>(param), std::get<1>(param));
219+
}
220+
221+
TEST_P(Tests_StronglyConnectedComponents_Rmat, CheckInt32Int32)
222+
{
223+
auto param = GetParam();
224+
run_current_test<int32_t, int32_t>(
225+
std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param)));
226+
}
227+
228+
TEST_P(Tests_StronglyConnectedComponents_Rmat, CheckInt64Int64)
229+
{
230+
auto param = GetParam();
231+
run_current_test<int64_t, int64_t>(
232+
std::get<0>(param), override_Rmat_Usecase_with_cmd_line_arguments(std::get<1>(param)));
233+
}
234+
235+
INSTANTIATE_TEST_SUITE_P(
236+
file_test,
237+
Tests_StronglyConnectedComponents_File,
238+
::testing::Values(
239+
std::make_tuple(StronglyConnectedComponents_Usecase{false},
240+
cugraph::test::File_Usecase("test/datasets/karate-asymmetric.csv")),
241+
std::make_tuple(StronglyConnectedComponents_Usecase{true},
242+
cugraph::test::File_Usecase("test/datasets/karate-asymmetric.csv")),
243+
std::make_tuple(StronglyConnectedComponents_Usecase{false},
244+
cugraph::test::File_Usecase("test/datasets/cage6.mtx")),
245+
std::make_tuple(StronglyConnectedComponents_Usecase{true},
246+
cugraph::test::File_Usecase("test/datasets/cage6.mtx"))));
247+
248+
INSTANTIATE_TEST_SUITE_P(
249+
rmat_small_test,
250+
Tests_StronglyConnectedComponents_Rmat,
251+
::testing::Values(
252+
std::make_tuple(StronglyConnectedComponents_Usecase{false},
253+
cugraph::test::Rmat_Usecase(10, 16, 0.57, 0.19, 0.19, 0, false, false)),
254+
std::make_tuple(StronglyConnectedComponents_Usecase{true},
255+
cugraph::test::Rmat_Usecase(10, 16, 0.57, 0.19, 0.19, 0, false, false))));
256+
257+
INSTANTIATE_TEST_SUITE_P(
258+
rmat_benchmark_test, /* note that scale & edge factor can be overridden in benchmarking (with
259+
--gtest_filter to select only the rmat_benchmark_test with a specific
260+
vertex & edge type combination) by command line arguments and do not
261+
include more than one Rmat_Usecase that differ only in scale or edge
262+
factor (to avoid running same benchmarks more than once) */
263+
Tests_StronglyConnectedComponents_Rmat,
264+
::testing::Values(
265+
std::make_tuple(StronglyConnectedComponents_Usecase{false, false},
266+
cugraph::test::Rmat_Usecase(20, 16, 0.57, 0.19, 0.19, 0, false, false)),
267+
std::make_tuple(StronglyConnectedComponents_Usecase{true, false},
268+
cugraph::test::Rmat_Usecase(20, 16, 0.57, 0.19, 0.19, 0, false, false))));
269+
270+
CUGRAPH_TEST_PROGRAM_MAIN()

0 commit comments

Comments
 (0)