From 18f6bb9a93968d61901c9ff59dea068b387644b6 Mon Sep 17 00:00:00 2001 From: Dhyey Mavani Date: Sat, 4 Oct 2025 08:17:43 +0000 Subject: [PATCH 1/2] Fix RuntimeError in maybe_num_nodes_dict with empty edge indices This commit fixes a bug where maybe_num_nodes_dict would crash with a RuntimeError when processing heterogeneous graphs containing empty edge indices. The error occurred because calling .max() on an empty tensor requires specifying a reduction dimension. The fix adds checks for empty tensors (numel() > 0) before calling .max(), returning 0 for empty edge indices, which is the correct behavior for graphs with no edges of a particular type. Added comprehensive test coverage for: - Mixed empty and non-empty edge indices - All empty edge indices - Empty edge indices with provided num_nodes_dict This bug would affect users working with heterogeneous graphs where some edge types have no edges, which is a common scenario in real-world applications like recommendation systems or knowledge graphs. Co-authored-by: Ona --- test/utils/test_num_nodes.py | 27 +++++++++++++++++++++++++++ torch_geometric/utils/num_nodes.py | 4 ++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/test/utils/test_num_nodes.py b/test/utils/test_num_nodes.py index ce18d6113315..bdf5b5d1b2a3 100644 --- a/test/utils/test_num_nodes.py +++ b/test/utils/test_num_nodes.py @@ -30,3 +30,30 @@ def test_maybe_num_nodes_dict(): '1': 3, '2': 6, } + + +def test_maybe_num_nodes_dict_empty_edge_index(): + # Test with empty edge indices (regression test for bug fix) + edge_index_dict = { + ('user', 'rates', 'movie'): torch.tensor([[], []], dtype=torch.long), + ('user', 'follows', 'user'): torch.tensor([[0, 1], [1, 2]], + dtype=torch.long), + } + + result = maybe_num_nodes_dict(edge_index_dict) + assert result == {'user': 3, 'movie': 0} + + # Test with all empty edge indices + edge_index_dict_all_empty = { + ('user', 'rates', 'movie'): torch.tensor([[], []], dtype=torch.long), + ('movie', 'in', 'genre'): torch.tensor([[], []], dtype=torch.long), + } + + result_all_empty = maybe_num_nodes_dict(edge_index_dict_all_empty) + assert result_all_empty == {'user': 0, 'movie': 0, 'genre': 0} + + # Test with provided num_nodes_dict and empty edges + num_nodes_dict = {'movie': 10} + result_with_provided = maybe_num_nodes_dict(edge_index_dict, + num_nodes_dict) + assert result_with_provided == {'user': 3, 'movie': 10} diff --git a/torch_geometric/utils/num_nodes.py b/torch_geometric/utils/num_nodes.py index 1f5e3b6b3392..2525771b74c1 100644 --- a/torch_geometric/utils/num_nodes.py +++ b/torch_geometric/utils/num_nodes.py @@ -52,12 +52,12 @@ def maybe_num_nodes_dict( key = keys[0] if key not in found_types: - N = int(edge_index[0].max() + 1) + N = int(edge_index[0].max() + 1) if edge_index[0].numel() > 0 else 0 num_nodes_dict[key] = max(N, num_nodes_dict.get(key, N)) key = keys[-1] if key not in found_types: - N = int(edge_index[1].max() + 1) + N = int(edge_index[1].max() + 1) if edge_index[1].numel() > 0 else 0 num_nodes_dict[key] = max(N, num_nodes_dict.get(key, N)) return num_nodes_dict From 2395a9a3ad923f92ad9cc89c0f81109254ff4f4b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Oct 2025 08:25:10 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/utils/test_num_nodes.py | 9 +++++---- torch_geometric/utils/num_nodes.py | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/utils/test_num_nodes.py b/test/utils/test_num_nodes.py index bdf5b5d1b2a3..f02341b52237 100644 --- a/test/utils/test_num_nodes.py +++ b/test/utils/test_num_nodes.py @@ -35,9 +35,10 @@ def test_maybe_num_nodes_dict(): def test_maybe_num_nodes_dict_empty_edge_index(): # Test with empty edge indices (regression test for bug fix) edge_index_dict = { - ('user', 'rates', 'movie'): torch.tensor([[], []], dtype=torch.long), - ('user', 'follows', 'user'): torch.tensor([[0, 1], [1, 2]], - dtype=torch.long), + ('user', 'rates', 'movie'): + torch.tensor([[], []], dtype=torch.long), + ('user', 'follows', 'user'): + torch.tensor([[0, 1], [1, 2]], dtype=torch.long), } result = maybe_num_nodes_dict(edge_index_dict) @@ -55,5 +56,5 @@ def test_maybe_num_nodes_dict_empty_edge_index(): # Test with provided num_nodes_dict and empty edges num_nodes_dict = {'movie': 10} result_with_provided = maybe_num_nodes_dict(edge_index_dict, - num_nodes_dict) + num_nodes_dict) assert result_with_provided == {'user': 3, 'movie': 10} diff --git a/torch_geometric/utils/num_nodes.py b/torch_geometric/utils/num_nodes.py index 2525771b74c1..4be8759d3744 100644 --- a/torch_geometric/utils/num_nodes.py +++ b/torch_geometric/utils/num_nodes.py @@ -52,12 +52,14 @@ def maybe_num_nodes_dict( key = keys[0] if key not in found_types: - N = int(edge_index[0].max() + 1) if edge_index[0].numel() > 0 else 0 + N = int(edge_index[0].max() + + 1) if edge_index[0].numel() > 0 else 0 num_nodes_dict[key] = max(N, num_nodes_dict.get(key, N)) key = keys[-1] if key not in found_types: - N = int(edge_index[1].max() + 1) if edge_index[1].numel() > 0 else 0 + N = int(edge_index[1].max() + + 1) if edge_index[1].numel() > 0 else 0 num_nodes_dict[key] = max(N, num_nodes_dict.get(key, N)) return num_nodes_dict