Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 7da3712

Browse files
committed
fix(cugraph): filter isolated nodes and remap indices for all algorithms
cuGraph requires contiguous vertex indices starting from 0. When a graph contains isolated nodes (nodes with no edges), the Memgraph GraphView includes them but cuGraph's internal renumbering excludes them, causing array index mismatches and "Index out of range" crashes. Solution: - CreateCugraphFromMemgraph now filters nodes with degree 0 before building the cuGraph edge list - Returns a renumber_map that maps cuGraph's contiguous indices back to the original GraphView node indices - All algorithms updated to use renumber_map when translating results back to Memgraph node IDs Example: Graph with 137,852 nodes but only 108,024 have edges. Previously crashed. Now correctly processes 108,024 connected nodes. Affected algorithms: betweenness_centrality, pagerank, hits, katz_centrality, louvain, leiden, personalized_pagerank, spectral_clustering
1 parent 04a0455 commit 7da3712

File tree

10 files changed

+151
-92
lines changed

10 files changed

+151
-92
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,5 @@ python/mage/link_prediction/random_customer_results.txt
208208
python/mage/link_prediction/cora_results.txt
209209
python/mage/link_prediction/random_features_services_results.txt
210210
python/mage/link_prediction/issue.py
211-
python/mage/link_prediction/issue2.py
211+
python/mage/link_prediction/issue2.py.build-staging/
212+
rebuild-cugraph.sh

cpp/cugraph_module/algorithms/betweenness_centrality.cu

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ constexpr char const *kArgumentDirected = "directed";
3030
constexpr char const *kArgumentK = "k";
3131

3232
constexpr char const *kResultFieldNode = "node";
33-
constexpr char const *kResultFieldBetweenness = "betweenness";
33+
constexpr char const *kResultFieldBetweennessCentrality = "betweenness_centrality";
3434

3535
void InsertBetweennessRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const std::uint64_t node_id,
3636
double betweenness) {
@@ -46,7 +46,7 @@ void InsertBetweennessRecord(mgp_graph *graph, mgp_result *result, mgp_memory *m
4646
if (record == nullptr) throw mg_exception::NotEnoughMemoryException();
4747

4848
mg_utility::InsertNodeValueResult(record, kResultFieldNode, node, memory);
49-
mg_utility::InsertDoubleValueResult(record, kResultFieldBetweenness, betweenness, memory);
49+
mg_utility::InsertDoubleValueResult(record, kResultFieldBetweennessCentrality, betweenness, memory);
5050
}
5151

5252
void BetweennessCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
@@ -64,7 +64,7 @@ void BetweennessCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *res
6464
auto stream = handle.get_stream();
6565

6666
// Betweenness centrality uses store_transposed = false
67-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
67+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
6868
*mg_graph.get(), graph_type, handle);
6969

7070
auto cu_graph_view = cu_graph.view();
@@ -121,8 +121,10 @@ void BetweennessCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *res
121121
raft::update_host(h_betweenness.data(), betweenness.data(), n_vertices, stream);
122122
handle.sync_stream();
123123

124+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
124125
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
125-
InsertBetweennessRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_betweenness[node_id]);
126+
auto original_id = renumber_map[node_id];
127+
InsertBetweennessRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_betweenness[node_id]);
126128
}
127129
} catch (const std::exception &e) {
128130
// We must not let any exceptions out of our module.
@@ -150,7 +152,7 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
150152
mgp::proc_add_opt_arg(betweenness_proc, kArgumentK, mgp::type_int(), default_k);
151153

152154
mgp::proc_add_result(betweenness_proc, kResultFieldNode, mgp::type_node());
153-
mgp::proc_add_result(betweenness_proc, kResultFieldBetweenness, mgp::type_float());
155+
mgp::proc_add_result(betweenness_proc, kResultFieldBetweennessCentrality, mgp::type_float());
154156
} catch (const std::exception &e) {
155157
mgp_value_destroy(default_normalized);
156158
mgp_value_destroy(default_directed);

cpp/cugraph_module/algorithms/hits.cu

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ constexpr char const *kProcedureHits = "get";
2525

2626
constexpr char const *kArgumentMaxIterations = "max_iterations";
2727
constexpr char const *kArgumentTolerance = "tolerance";
28-
constexpr char const *kArgumentNormalize = "normalize";
28+
constexpr char const *kArgumentNormalized = "normalized";
2929

3030
constexpr char const *kResultFieldNode = "node";
31-
constexpr char const *kResultFieldHub = "hub";
32-
constexpr char const *kResultFieldAuthority = "authority";
31+
constexpr char const *kResultFieldHubScore = "hubs";
32+
constexpr char const *kResultFieldAuthoritiesScore = "authorities";
3333

3434
void InsertHitsRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory, const std::uint64_t node_id,
3535
double hub, double authority) {
@@ -45,8 +45,8 @@ void InsertHitsRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory,
4545
if (record == nullptr) throw mg_exception::NotEnoughMemoryException();
4646

4747
mg_utility::InsertNodeValueResult(record, kResultFieldNode, node, memory);
48-
mg_utility::InsertDoubleValueResult(record, kResultFieldHub, hub, memory);
49-
mg_utility::InsertDoubleValueResult(record, kResultFieldAuthority, authority, memory);
48+
mg_utility::InsertDoubleValueResult(record, kResultFieldHubScore, hub, memory);
49+
mg_utility::InsertDoubleValueResult(record, kResultFieldAuthoritiesScore, authority, memory);
5050
}
5151

5252
void HitsProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
@@ -63,7 +63,7 @@ void HitsProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *
6363
auto stream = handle.get_stream();
6464

6565
// HITS requires store_transposed = true
66-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
66+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
6767
*mg_graph.get(), mg_graph::GraphType::kDirectedGraph, handle);
6868

6969
auto cu_graph_view = cu_graph.view();
@@ -92,8 +92,10 @@ void HitsProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *
9292
raft::update_host(h_authorities.data(), authorities.data(), n_vertices, stream);
9393
handle.sync_stream();
9494

95+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
9596
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
96-
InsertHitsRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_hubs[node_id],
97+
auto original_id = renumber_map[node_id];
98+
InsertHitsRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_hubs[node_id],
9799
h_authorities[node_id]);
98100
}
99101
} catch (const std::exception &e) {
@@ -117,11 +119,11 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
117119

118120
mgp::proc_add_opt_arg(hits_proc, kArgumentMaxIterations, mgp::type_int(), default_max_iterations);
119121
mgp::proc_add_opt_arg(hits_proc, kArgumentTolerance, mgp::type_float(), default_tolerance);
120-
mgp::proc_add_opt_arg(hits_proc, kArgumentNormalize, mgp::type_bool(), default_normalize);
122+
mgp::proc_add_opt_arg(hits_proc, kArgumentNormalized, mgp::type_bool(), default_normalize);
121123

122124
mgp::proc_add_result(hits_proc, kResultFieldNode, mgp::type_node());
123-
mgp::proc_add_result(hits_proc, kResultFieldHub, mgp::type_float());
124-
mgp::proc_add_result(hits_proc, kResultFieldAuthority, mgp::type_float());
125+
mgp::proc_add_result(hits_proc, kResultFieldHubScore, mgp::type_float());
126+
mgp::proc_add_result(hits_proc, kResultFieldAuthoritiesScore, mgp::type_float());
125127
} catch (const std::exception &e) {
126128
mgp_value_destroy(default_max_iterations);
127129
mgp_value_destroy(default_tolerance);

cpp/cugraph_module/algorithms/katz_centrality.cu

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ constexpr char const *kArgumentAlpha = "alpha";
2727
constexpr char const *kArgumentBeta = "beta";
2828
constexpr char const *kArgumentEpsilon = "epsilon";
2929
constexpr char const *kArgumentMaxIterations = "max_iterations";
30-
constexpr char const *kArgumentNormalize = "normalize";
30+
constexpr char const *kArgumentNormalized = "normalized";
3131

3232
constexpr char const *kResultFieldNode = "node";
33-
constexpr char const *kResultFieldKatz = "katz";
33+
constexpr char const *kResultFieldKatzCentrality = "katz_centrality";
3434

3535
const double kDefaultWeight = 1.0;
3636

@@ -48,7 +48,7 @@ void InsertKatzRecord(mgp_graph *graph, mgp_result *result, mgp_memory *memory,
4848
if (record == nullptr) throw mg_exception::NotEnoughMemoryException();
4949

5050
mg_utility::InsertNodeValueResult(record, kResultFieldNode, node, memory);
51-
mg_utility::InsertDoubleValueResult(record, kResultFieldKatz, katz, memory);
51+
mg_utility::InsertDoubleValueResult(record, kResultFieldKatzCentrality, katz, memory);
5252
}
5353

5454
void KatzCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory *memory) {
@@ -57,7 +57,7 @@ void KatzCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mg
5757
auto beta = mgp::value_get_double(mgp::list_at(args, 1));
5858
auto epsilon = mgp::value_get_double(mgp::list_at(args, 2));
5959
auto max_iterations = static_cast<std::size_t>(mgp::value_get_int(mgp::list_at(args, 3)));
60-
auto normalize = mgp::value_get_bool(mgp::list_at(args, 4));
60+
auto normalized = mgp::value_get_bool(mgp::list_at(args, 4));
6161

6262
auto mg_graph = mg_utility::GetGraphView(graph, result, memory, mg_graph::GraphType::kDirectedGraph);
6363
if (mg_graph->Empty()) return;
@@ -67,7 +67,7 @@ void KatzCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mg
6767
auto stream = handle.get_stream();
6868

6969
// Katz centrality requires store_transposed = true
70-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
70+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
7171
*mg_graph.get(), mg_graph::GraphType::kDirectedGraph, handle);
7272

7373
auto cu_graph_view = cu_graph.view();
@@ -91,16 +91,18 @@ void KatzCentralityProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mg
9191
static_cast<result_t>(epsilon),
9292
max_iterations,
9393
false, // has_initial_guess
94-
normalize,
94+
normalized,
9595
false); // do_expensive_check
9696

9797
// Copy results to host and output
9898
std::vector<result_t> h_katz(n_vertices);
9999
raft::update_host(h_katz.data(), katz_centralities.data(), n_vertices, stream);
100100
handle.sync_stream();
101101

102+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
102103
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
103-
InsertKatzRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_katz[node_id]);
104+
auto original_id = renumber_map[node_id];
105+
InsertKatzRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_katz[node_id]);
104106
}
105107
} catch (const std::exception &e) {
106108
// We must not let any exceptions out of our module.
@@ -115,38 +117,38 @@ extern "C" int mgp_init_module(struct mgp_module *module, struct mgp_memory *mem
115117
mgp_value *default_beta;
116118
mgp_value *default_epsilon;
117119
mgp_value *default_max_iterations;
118-
mgp_value *default_normalize;
120+
mgp_value *default_normalized;
119121
try {
120122
auto *katz_proc = mgp::module_add_read_procedure(module, kProcedureKatzCentrality, KatzCentralityProc);
121123

122124
default_alpha = mgp::value_make_double(0.1, memory);
123125
default_beta = mgp::value_make_double(1.0, memory);
124126
default_epsilon = mgp::value_make_double(1e-6, memory);
125127
default_max_iterations = mgp::value_make_int(100, memory);
126-
default_normalize = mgp::value_make_bool(false, memory);
128+
default_normalized = mgp::value_make_bool(false, memory);
127129

128130
mgp::proc_add_opt_arg(katz_proc, kArgumentAlpha, mgp::type_float(), default_alpha);
129131
mgp::proc_add_opt_arg(katz_proc, kArgumentBeta, mgp::type_float(), default_beta);
130132
mgp::proc_add_opt_arg(katz_proc, kArgumentEpsilon, mgp::type_float(), default_epsilon);
131133
mgp::proc_add_opt_arg(katz_proc, kArgumentMaxIterations, mgp::type_int(), default_max_iterations);
132-
mgp::proc_add_opt_arg(katz_proc, kArgumentNormalize, mgp::type_bool(), default_normalize);
134+
mgp::proc_add_opt_arg(katz_proc, kArgumentNormalized, mgp::type_bool(), default_normalized);
133135

134136
mgp::proc_add_result(katz_proc, kResultFieldNode, mgp::type_node());
135-
mgp::proc_add_result(katz_proc, kResultFieldKatz, mgp::type_float());
137+
mgp::proc_add_result(katz_proc, kResultFieldKatzCentrality, mgp::type_float());
136138
} catch (const std::exception &e) {
137139
mgp_value_destroy(default_alpha);
138140
mgp_value_destroy(default_beta);
139141
mgp_value_destroy(default_epsilon);
140142
mgp_value_destroy(default_max_iterations);
141-
mgp_value_destroy(default_normalize);
143+
mgp_value_destroy(default_normalized);
142144
return 1;
143145
}
144146

145147
mgp_value_destroy(default_alpha);
146148
mgp_value_destroy(default_beta);
147149
mgp_value_destroy(default_epsilon);
148150
mgp_value_destroy(default_max_iterations);
149-
mgp_value_destroy(default_normalize);
151+
mgp_value_destroy(default_normalized);
150152
return 0;
151153
}
152154

cpp/cugraph_module/algorithms/leiden.cu

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ void LeidenProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory
6060
auto stream = handle.get_stream();
6161

6262
// Leiden requires store_transposed = false
63-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
63+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
6464
*mg_graph.get(), mg_graph::GraphType::kUndirectedGraph, handle);
6565

6666
auto cu_graph_view = cu_graph.view();
@@ -92,8 +92,10 @@ void LeidenProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memory
9292
raft::update_host(h_clustering.data(), clustering_result.data(), n_vertices, stream);
9393
handle.sync_stream();
9494

95+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
9596
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
96-
InsertLeidenRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_clustering[node_id]);
97+
auto original_id = renumber_map[node_id];
98+
InsertLeidenRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_clustering[node_id]);
9799
}
98100
} catch (const std::exception &e) {
99101
mgp::result_set_error_msg(result, e.what());

cpp/cugraph_module/algorithms/louvain.cu

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ void LouvainProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memor
5858
auto stream = handle.get_stream();
5959

6060
// Louvain requires store_transposed = false
61-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
61+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, false, false>(
6262
*mg_graph.get(), mg_graph::GraphType::kUndirectedGraph, handle);
6363

6464
auto cu_graph_view = cu_graph.view();
@@ -90,8 +90,10 @@ void LouvainProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memor
9090
raft::update_host(h_clustering.data(), clustering_result.data(), n_vertices, stream);
9191
handle.sync_stream();
9292

93+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
9394
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
94-
InsertLouvainRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_clustering[node_id]);
95+
auto original_id = renumber_map[node_id];
96+
InsertLouvainRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_clustering[node_id]);
9597
}
9698
} catch (const std::exception &e) {
9799
mgp::result_set_error_msg(result, e.what());

cpp/cugraph_module/algorithms/pagerank.cu

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ void PagerankProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memo
6161
auto stream = handle.get_stream();
6262

6363
// PageRank requires store_transposed = true
64-
auto [cu_graph, edge_props] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
64+
auto [cu_graph, edge_props, renumber_map] = mg_cugraph::CreateCugraphFromMemgraph<vertex_t, edge_t, weight_t, true, false>(
6565
*mg_graph.get(), mg_graph::GraphType::kDirectedGraph, handle);
6666

6767
auto cu_graph_view = cu_graph.view();
@@ -88,8 +88,10 @@ void PagerankProc(mgp_list *args, mgp_graph *graph, mgp_result *result, mgp_memo
8888
raft::update_host(h_pageranks.data(), pageranks.data(), n_vertices, stream);
8989
handle.sync_stream();
9090

91+
// Use renumber_map to translate cuGraph indices back to original GraphView indices
9192
for (vertex_t node_id = 0; node_id < static_cast<vertex_t>(n_vertices); ++node_id) {
92-
InsertPagerankRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(node_id), h_pageranks[node_id]);
93+
auto original_id = renumber_map[node_id];
94+
InsertPagerankRecord(graph, result, memory, mg_graph->GetMemgraphNodeId(original_id), h_pageranks[node_id]);
9395
}
9496
} catch (const std::exception &e) {
9597
mgp::result_set_error_msg(result, e.what());

0 commit comments

Comments
 (0)