nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: pyg-team/pytorch_geometric
Pull request #8643 (2023-12-19 20:20:06)

In-place support of `EdgeIndex` ops


Patch:
diff --git a/torch_geometric/edge_index.py b/torch_geometric/edge_index.py
index fd5d3d093..44491d8ed 100644
--- a/torch_geometric/edge_index.py
+++ b/torch_geometric/edge_index.py
@@ -1048,16 +1048,18 @@ def index_select(
     input: EdgeIndex,
     dim: int,
     index: Tensor,
+    *,
+    out: Optional[Tensor] = None,
 ) -> Union[EdgeIndex, Tensor]:
 
-    out = Tensor.__torch_function__(  #
-        torch.index_select, (Tensor, ), (input, dim, index))
+    output = Tensor.__torch_function__(  #
+        torch.index_select, (Tensor, ), (input, dim, index), dict(out=out))
 
     if dim == 1 or dim == -1:
-        out = out.as_subclass(EdgeIndex)
-        out._sparse_size = input.sparse_size()
+        output = output.as_subclass(EdgeIndex)
+        output._sparse_size = input.sparse_size()
 
-    return out
+    return output
 
 
 @implements(torch.narrow)


Test patch:
diff --git a/test/test_edge_index.py b/test/test_edge_index.py
index a7b94da0c..f8844c81d 100644
--- a/test/test_edge_index.py
+++ b/test/test_edge_index.py
@@ -425,16 +425,25 @@ def test_index_select(dtype, device, is_undirected):
     kwargs = dict(dtype=dtype, device=device, is_undirected=is_undirected)
     adj = EdgeIndex([[0, 1, 1, 2], [1, 0, 2, 1]], sort_order='row', **kwargs)
 
-    out = adj.index_select(1, tensor([1, 3], device=device))
+    index = tensor([1, 3], device=device)
+    out = adj.index_select(1, index)
     assert out.equal(tensor([[1, 2], [0, 1]], device=device))
     assert isinstance(out, EdgeIndex)
     assert not out.is_sorted
     assert not out.is_undirected
 
-    out = adj.index_select(0, tensor([0], device=device))
+    index = tensor([0], device=device)
+    out = adj.index_select(0, index)
     assert out.equal(tensor([[0, 1, 1, 2]], device=device))
     assert not isinstance(out, EdgeIndex)
 
+    index = tensor([1, 3], device=device)
+    inplace = torch.empty(2, 2, dtype=dtype, device=device)
+    out = torch.index_select(adj, 1, index, out=inplace)
+    assert out.data_ptr() == inplace.data_ptr()
+    assert isinstance(out, EdgeIndex)
+    assert not isinstance(inplace, EdgeIndex)
+
 
 @withCUDA
 @pytest.mark.parametrize('dtype', DTYPES)
Repository: pyg-team/pytorch_geometric
Pull request #8666 (2023-12-22 14:10:21)

Support `EdgeIndex` in `add_remaining_self_loops`


Patch:
diff --git a/torch_geometric/utils/loop.py b/torch_geometric/utils/loop.py
index c4e5e0de7..a33f8d442 100644
--- a/torch_geometric/utils/loop.py
+++ b/torch_geometric/utils/loop.py
@@ -622,8 +622,15 @@ def add_remaining_self_loops(  # noqa: F811
     N = maybe_num_nodes(edge_index, num_nodes)
     mask = edge_index[0] != edge_index[1]
 
-    loop_index = torch.arange(0, N, dtype=torch.long, device=edge_index.device)
-    loop_index = loop_index.unsqueeze(0).repeat(2, 1)
+    device = edge_index.device
+    if torch.jit.is_scripting():
+        loop_index = torch.arange(0, N, device=device).view(1, -1).repeat(2, 1)
+    else:
+        loop_index = EdgeIndex(
+            torch.arange(0, N, device=device).view(1, -1).repeat(2, 1),
+            sparse_size=(N, N),
+            is_undirected=True,
+        )
 
     if edge_attr is not None:
 
@@ -635,7 +642,17 @@ def add_remaining_self_loops(  # noqa: F811
 
         edge_attr = torch.cat([edge_attr[mask], loop_attr], dim=0)
 
-    edge_index = torch.cat([edge_index[:, mask], loop_index], dim=1)
+    is_undirected = False
+    if not torch.jit.is_scripting() and isinstance(edge_index, EdgeIndex):
+        is_undirected = edge_index.is_undirected
+
+    edge_index = edge_index[:, mask]
+
+    if not torch.jit.is_scripting() and isinstance(edge_index, EdgeIndex):
+        edge_index._is_undirected = is_undirected
+
+    edge_index = torch.cat([edge_index, loop_index], dim=1)
+
     return edge_index, edge_attr
 
 


Test patch:
diff --git a/test/utils/test_loop.py b/test/utils/test_loop.py
index a381bbb0f..361cc1e82 100644
--- a/test/utils/test_loop.py
+++ b/test/utils/test_loop.py
@@ -222,6 +222,18 @@ def test_add_remaining_self_loops():
     assert out[1].tolist() == [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.],
                                [0., 1., 0.]]
 
+    edge_index = EdgeIndex(
+        edge_index,
+        sparse_size=(2, 2),
+        sort_order='row',
+        is_undirected=True,
+    )
+    out, _ = add_remaining_self_loops(edge_index)
+    assert out.tolist() == expected
+    assert out.sparse_size() == (2, 2)
+    assert out.sort_order is None
+    assert out.is_undirected
+
 
 def test_add_remaining_self_loops_without_initial_loops():
     edge_index = torch.tensor([[0, 1], [1, 0]])
Repository: pyg-team/pytorch_geometric
Pull request #9199 (2024-04-15 11:52:58)

Ignore `edge_label` and `edge_label_index` in `ToSparseTensor()` transform
Fixes https://github.com/pyg-team/pytorch_geometric/issues/9196


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6e4c1072..bc4043841 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 
+- Fixed incorrect treatment of `edge_label` and `edge_label_index` in `ToSparseTensor` transform ([#9199](https://github.com/pyg-team/pytorch_geometric/pull/9199))
 - Fixed `EgoData` processing in `SnapDataset` in case filenames are unsorted ([#9195](https://github.com/pyg-team/pytorch_geometric/pull/9195))
 - Fixed empty graph and isolated node handling in `to_dgl` ([#9188](https://github.com/pyg-team/pytorch_geometric/pull/9188))
 - Fixed bug in `to_scipy_sparse_matrix` when cuda is set as default torch device ([#9146](https://github.com/pyg-team/pytorch_geometric/pull/9146))
diff --git a/torch_geometric/transforms/to_sparse_tensor.py b/torch_geometric/transforms/to_sparse_tensor.py
index e8eedc0c6..ef962e5a3 100644
--- a/torch_geometric/transforms/to_sparse_tensor.py
+++ b/torch_geometric/transforms/to_sparse_tensor.py
@@ -79,7 +79,7 @@ class ToSparseTensor(BaseTransform):
 
             keys, values = [], []
             for key, value in store.items():
-                if key == 'edge_index':
+                if key in {'edge_index', 'edge_label', 'edge_label_index'}:
                     continue
 
                 if store.is_edge_attr(key):


Test patch:
diff --git a/test/transforms/test_random_link_split.py b/test/transforms/test_random_link_split.py
index 7c755d654..a36318069 100644
--- a/test/transforms/test_random_link_split.py
+++ b/test/transforms/test_random_link_split.py
@@ -7,7 +7,7 @@ from torch_geometric.testing import (
     onlyFullTest,
     onlyOnline,
 )
-from torch_geometric.transforms import RandomLinkSplit
+from torch_geometric.transforms import RandomLinkSplit, ToSparseTensor
 from torch_geometric.utils import is_undirected, to_undirected
 
 
@@ -85,6 +85,20 @@ def test_random_link_split():
     assert train_data.edge_label.size(0) == 6
 
 
+def test_random_link_split_with_to_sparse_tensor():
+    edge_index = torch.tensor([[0, 1, 1, 2, 2, 3, 3, 4, 4, 5],
+                               [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]])
+    data = Data(edge_index=edge_index, num_nodes=6)
+
+    transform = RandomLinkSplit(num_val=2, num_test=2, neg_sampling_ratio=0.0)
+    train_data1, _, _ = transform(data)
+    assert train_data1.edge_index.size(1) == train_data1.edge_label.size(0)
+
+    train_data2 = ToSparseTensor()(train_data1)
+    assert train_data1.edge_label.equal(train_data2.edge_label)
+    assert train_data1.edge_label_index.equal(train_data2.edge_label_index)
+
+
 def test_random_link_split_with_label():
     edge_index = torch.tensor([[0, 1, 1, 2, 2, 3, 3, 4, 4, 5],
                                [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]])
Repository: pyg-team/pytorch_geometric
Pull request #9245 (2024-04-26 12:19:58)

Allow keyword arguments in arbitrary order in `MessagePassing.propagate`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4632892d5..104ebfa3b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,7 +39,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 
-- Fix node mapping bug in `RCDD` dataset ([#9234](https://github.com/pyg-team/pytorch_geometric/pull/9234))
+- Fixed arbitrary keyword ordering in `MessagePassing.propgate` ([#9245](https://github.com/pyg-team/pytorch_geometric/pull/9245))
+- Fixed node mapping bug in `RCDD` dataset ([#9234](https://github.com/pyg-team/pytorch_geometric/pull/9234))
 - Fixed incorrect treatment of `edge_label` and `edge_label_index` in `ToSparseTensor` transform ([#9199](https://github.com/pyg-team/pytorch_geometric/pull/9199))
 - Fixed `EgoData` processing in `SnapDataset` in case filenames are unsorted ([#9195](https://github.com/pyg-team/pytorch_geometric/pull/9195))
 - Fixed empty graph and isolated node handling in `to_dgl` ([#9188](https://github.com/pyg-team/pytorch_geometric/pull/9188))
diff --git a/torch_geometric/inspector.py b/torch_geometric/inspector.py
index e0d147b31..8f6e7bd04 100644
--- a/torch_geometric/inspector.py
+++ b/torch_geometric/inspector.py
@@ -401,7 +401,8 @@ class Inspector:
             match = find_parenthesis_content(source, f'self.{func_name}')
             if match is not None:
                 for i, kwarg in enumerate(split(match, sep=',')):
-                    if exclude is not None and i in exclude:
+                    if ('=' not in kwarg and exclude is not None
+                            and i in exclude):
                         continue
 
                     name_and_content = re.split(r'\s*=\s*', kwarg)


Test patch:
diff --git a/test/nn/conv/test_message_passing.py b/test/nn/conv/test_message_passing.py
index 2f2544e76..25c4ba8af 100644
--- a/test/nn/conv/test_message_passing.py
+++ b/test/nn/conv/test_message_passing.py
@@ -176,6 +176,22 @@ def test_my_commented_conv():
     jit(x, edge_index)
 
 
+class MyKwargsConv(MessagePassing):
+    def forward(self, x: Tensor, edge_index: Tensor) -> Tensor:
+        return self.propagate(x=x, edge_index=edge_index)
+
+
+def test_my_kwargs_conv():
+    x = torch.randn(4, 8)
+    edge_index = torch.tensor([[0, 1, 2, 3], [0, 0, 1, 1]])
+
+    conv = MyKwargsConv()
+    conv(x, edge_index)
+
+    jit = torch.jit.script(conv)
+    jit(x, edge_index)
+
+
 def test_my_conv_out_of_bounds():
     x = torch.randn(3, 8)
     value = torch.randn(4)
Repository: pyg-team/pytorch_geometric
Pull request #9271 (2024-05-03 13:43:09)

Add `EdgeIndex.to_vector()`


Patch:
diff --git a/torch_geometric/edge_index.py b/torch_geometric/edge_index.py
index b2478d75f..8eb174913 100644
--- a/torch_geometric/edge_index.py
+++ b/torch_geometric/edge_index.py
@@ -1256,6 +1256,17 @@ class EdgeIndex(Tensor):
         self._cat_metadata = None
         return self
 
+    def to_vector(self) -> Tensor:
+        r"""Converts :class:`EdgeIndex` into a one-dimensional index
+        vector representation.
+        """
+        num_rows, num_cols = self.get_sparse_size()
+
+        if num_rows * num_cols > torch_geometric.typing.MAX_INT64:
+            raise ValueError("'to_vector()' will result in an overflow")
+
+        return self._data[0] * num_rows + self._data[1]
+
 
 class SortReturnType(NamedTuple):
     values: EdgeIndex


Test patch:
diff --git a/test/test_edge_index.py b/test/test_edge_index.py
index ca045b988..46dbe89ee 100644
--- a/test/test_edge_index.py
+++ b/test/test_edge_index.py
@@ -1088,6 +1088,15 @@ def test_global_mapping(device, dtype):
     assert out.equal(expected)
 
 
+@withCUDA
+@pytest.mark.parametrize('dtype', DTYPES)
+def test_to_vector(device, dtype):
+    adj = EdgeIndex([[0, 1, 1, 2], [1, 0, 2, 1]], device=device, dtype=dtype)
+    out = adj.to_vector()
+    assert not isinstance(out, EdgeIndex)
+    assert out.equal(tensor([1, 3, 5, 7], device=device))
+
+
 @withCUDA
 @pytest.mark.parametrize('dtype', DTYPES)
 def test_save_and_load(dtype, device, tmp_path):
Repository: pyg-team/pytorch_geometric
Pull request #9285 (2024-05-04 16:39:35)

Add `Index.flip(0)`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0921d820f..609fd587c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Added
 
-- Added `torch_geometric.Index` ([#9276](https://github.com/pyg-team/pytorch_geometric/pull/9276), [#9277](https://github.com/pyg-team/pytorch_geometric/pull/9277), [#9278](https://github.com/pyg-team/pytorch_geometric/pull/9278), [#9279](https://github.com/pyg-team/pytorch_geometric/pull/9279), [#9280](https://github.com/pyg-team/pytorch_geometric/pull/9280), [#9281](https://github.com/pyg-team/pytorch_geometric/pull/9281), [#9284](https://github.com/pyg-team/pytorch_geometric/pull/9284))
+- Added `torch_geometric.Index` ([#9276](https://github.com/pyg-team/pytorch_geometric/pull/9276), [#9277](https://github.com/pyg-team/pytorch_geometric/pull/9277), [#9278](https://github.com/pyg-team/pytorch_geometric/pull/9278), [#9279](https://github.com/pyg-team/pytorch_geometric/pull/9279), [#9280](https://github.com/pyg-team/pytorch_geometric/pull/9280), [#9281](https://github.com/pyg-team/pytorch_geometric/pull/9281), [#9284](https://github.com/pyg-team/pytorch_geometric/pull/9284), [#9285](https://github.com/pyg-team/pytorch_geometric/pull/9285))
 - Added support for PyTorch 2.3 ([#9240](https://github.com/pyg-team/pytorch_geometric/pull/9240))
 - Added support for `EdgeIndex` in `message_and_aggregate` ([#9131](https://github.com/pyg-team/pytorch_geometric/pull/9131))
 - Added `CornellTemporalHyperGraphDataset` ([#9090](https://github.com/pyg-team/pytorch_geometric/pull/9090))
diff --git a/torch_geometric/index.py b/torch_geometric/index.py
index 42dc69244..ac762bc5d 100644
--- a/torch_geometric/index.py
+++ b/torch_geometric/index.py
@@ -497,3 +497,17 @@ def _cat(
     )
 
     return out
+
+
+@implements(aten.flip.default)
+def _flip(
+    input: Index,
+    dims: Union[List[int], Tuple[int, ...]],
+) -> Index:
+
+    data = aten.flip.default(input._data, dims)
+
+    out = Index(data)
+    out._dim_size = input.dim_size
+
+    return out


Test patch:
diff --git a/test/test_index.py b/test/test_index.py
index 3e930fcc2..57bc1c9bc 100644
--- a/test/test_index.py
+++ b/test/test_index.py
@@ -305,3 +305,16 @@ def test_cat(dtype, device):
     assert out.data_ptr() == inplace.data_ptr()
     assert not isinstance(out, Index)
     assert not isinstance(inplace, Index)
+
+
+@withCUDA
+@pytest.mark.parametrize('dtype', DTYPES)
+def test_flip(dtype, device):
+    kwargs = dict(dtype=dtype, device=device)
+    index = Index([0, 1, 1, 2], dim_size=3, is_sorted=True, **kwargs)
+
+    out = index.flip(0)
+    assert isinstance(out, Index)
+    assert out.equal(tensor([2, 1, 1, 0], device=device))
+    assert out.dim_size == 3
+    assert not out.is_sorted
Repository: pyg-team/pytorch_geometric
Pull request #9295 (2024-05-05 09:44:25)

Fix `dtype` conversion for `Index|EdgeIndex` in multi-process `DataLoader`


Patch:
diff --git a/torch_geometric/edge_index.py b/torch_geometric/edge_index.py
index 0dab2e362..2e7508934 100644
--- a/torch_geometric/edge_index.py
+++ b/torch_geometric/edge_index.py
@@ -478,6 +478,11 @@ class EdgeIndex(Tensor):
         r"""Returns whether indices are bidirectional."""
         return self._is_undirected
 
+    @property
+    def dtype(self) -> torch.dtype:  # type: ignore
+        # TODO Remove once PyTorch does not override `dtype` in `DataLoader`.
+        return self._data.dtype
+
     # Cache Interface #########################################################
 
     @overload
diff --git a/torch_geometric/index.py b/torch_geometric/index.py
index 6062be5d1..21fe96fec 100644
--- a/torch_geometric/index.py
+++ b/torch_geometric/index.py
@@ -199,6 +199,11 @@ class Index(Tensor):
         r"""TODO."""
         return self._is_sorted
 
+    @property
+    def dtype(self) -> torch.dtype:  # type: ignore
+        # TODO Remove once PyTorch does not override `dtype` in `DataLoader`.
+        return self._data.dtype
+
     # Cache Interface #########################################################
 
     def get_dim_size(self) -> int:


Test patch:
diff --git a/test/loader/test_dataloader.py b/test/loader/test_dataloader.py
index 2464c32a1..097611c06 100644
--- a/test/loader/test_dataloader.py
+++ b/test/loader/test_dataloader.py
@@ -206,6 +206,7 @@ def test_index_dataloader(num_workers):
 
     for batch in loader:
         assert isinstance(batch.index, Index)
+        assert batch.index.dtype == torch.long
         assert batch.index.dim_size == 7
         assert batch.index.is_sorted
 
@@ -236,6 +237,7 @@ def test_edge_index_dataloader(num_workers, sort_order):
 
     for batch in loader:
         assert isinstance(batch.edge_index, EdgeIndex)
+        assert batch.edge_index.dtype == torch.long
         assert batch.edge_index.sparse_size() == (6, 6)
         assert batch.edge_index.sort_order == sort_order
         assert batch.edge_index.is_undirected
Repository: pyg-team/pytorch_geometric
Pull request #9433 (2024-06-18 09:07:38)

Support other than two-dimensional inputs in `AttentionalAggregation`


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 54e1fd3d9..7b210f63b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Changed
 
+- Support other than two-dimensional inputs in `AttentionalAggregation` ([#9433](https://github.com/pyg-team/pytorch_geometric/pull/9433))
 - Improved model performance of the `examples/ogbn_papers_100m.py` script ([#9386](https://github.com/pyg-team/pytorch_geometric/pull/9386))
 - Added the `fmt` arg to `Dataset.get_summary` ([#9408](https://github.com/pyg-team/pytorch_geometric/pull/9408))
 - Skipped zero atom molecules in `MoleculeNet` ([#9318](https://github.com/pyg-team/pytorch_geometric/pull/9318))
diff --git a/torch_geometric/nn/aggr/attention.py b/torch_geometric/nn/aggr/attention.py
index 5cfd76984..4e4dc5c7a 100644
--- a/torch_geometric/nn/aggr/attention.py
+++ b/torch_geometric/nn/aggr/attention.py
@@ -65,8 +65,6 @@ class AttentionalAggregation(Aggregation):
                 ptr: Optional[Tensor] = None, dim_size: Optional[int] = None,
                 dim: int = -2) -> Tensor:
 
-        self.assert_two_dimensional_input(x, dim)
-
         if self.gate_mlp is not None:
             gate = self.gate_mlp(x, batch=index, batch_size=dim_size)
         else:


Test patch:
diff --git a/test/nn/aggr/test_attention.py b/test/nn/aggr/test_attention.py
index 3671f7559..dcfaeb17c 100644
--- a/test/nn/aggr/test_attention.py
+++ b/test/nn/aggr/test_attention.py
@@ -6,9 +6,10 @@ from torch_geometric.nn import MLP
 from torch_geometric.nn.aggr import AttentionalAggregation
 
 
-def test_attentional_aggregation():
+@pytest.mark.parametrize('dim', [2, 3])
+def test_attentional_aggregation(dim):
     channels = 16
-    x = torch.randn(6, channels)
+    x = torch.randn(6, channels) if dim == 2 else torch.randn(2, 6, channels)
     index = torch.tensor([0, 0, 1, 1, 1, 2])
     ptr = torch.tensor([0, 2, 5, 6])
 
@@ -20,10 +21,10 @@ def test_attentional_aggregation():
                          f'nn=MLP({channels}, {channels}))')
 
     out = aggr(x, index)
-    assert out.size() == (3, channels)
+    assert out.size() == (3, channels) if dim == 2 else (2, 3, channels)
 
     if (not torch_geometric.typing.WITH_TORCH_SCATTER
-            and not torch_geometric.typing.WITH_PT20):
+            and (dim == 3 or not torch_geometric.typing.WITH_PT20)):
         with pytest.raises(ImportError, match="requires the 'torch-scatter'"):
             aggr(x, ptr=ptr)
     else:
Repository: pyg-team/pytorch_geometric
Pull request #9605 (2024-08-19 09:07:56)

Fix `VirtualNode` for empty edge indices


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f22c4893b..ecc716aba 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -65,6 +65,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 
 ### Fixed
 
+- Fixed `VirtualNode` transform for empty edge indices ([#9605](https://github.com/pyg-team/pytorch_geometric/pull/9605))
 - Fixed an issue where import order in the multi-GPU `cugraph` example could cause an `rmm` error ([#9577](https://github.com/pyg-team/pytorch_geometric/pull/9577))
 - Made the output of the single-GPU `cugraph` example more readable ([#9577](https://github.com/pyg-team/pytorch_geometric/pull/9577))
 - Fixed `load_state_dict` behavior with lazy parameters in `HeteroDictLinear` ([#9493](https://github.com/pyg-team/pytorch_geometric/pull/9493))
diff --git a/torch_geometric/transforms/virtual_node.py b/torch_geometric/transforms/virtual_node.py
index 3f7a59170..3bb8297de 100644
--- a/torch_geometric/transforms/virtual_node.py
+++ b/torch_geometric/transforms/virtual_node.py
@@ -37,7 +37,8 @@ class VirtualNode(BaseTransform):
         col = torch.cat([col, full, arange], dim=0)
         edge_index = torch.stack([row, col], dim=0)
 
-        new_type = edge_type.new_full((num_nodes, ), int(edge_type.max()) + 1)
+        num_edge_types = int(edge_type.max()) if edge_type.numel() > 0 else 0
+        new_type = edge_type.new_full((num_nodes, ), num_edge_types + 1)
         edge_type = torch.cat([edge_type, new_type, new_type + 1], dim=0)
 
         old_data = copy.copy(data)


Test patch:
diff --git a/test/transforms/test_virtual_node.py b/test/transforms/test_virtual_node.py
index 7111c771a..35517eb76 100644
--- a/test/transforms/test_virtual_node.py
+++ b/test/transforms/test_virtual_node.py
@@ -36,3 +36,17 @@ def test_virtual_node():
     assert data.num_nodes == 5
 
     assert data.edge_type.tolist() == [0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]
+
+    data = Data(x=x, edge_index=torch.empty(2, 0, dtype=torch.long))
+    data = VirtualNode()(data)
+    assert len(data) == 3
+
+    assert data.x.size() == (5, 16)
+    assert torch.allclose(data.x[:4], x)
+    assert data.x[4:].abs().sum() == 0
+
+    assert data.edge_index.tolist() == [
+        [0, 1, 2, 3, 4, 4, 4, 4],
+        [4, 4, 4, 4, 0, 1, 2, 3],
+    ]
+    assert data.edge_type.tolist() == [1, 1, 1, 1, 2, 2, 2, 2]
Repository: pyg-team/pytorch_geometric
Pull request #9778 (2024-11-13 04:03:28)

Improve `__inc__` error message and add tests


Patch:
diff --git a/torch_geometric/data/data.py b/torch_geometric/data/data.py
index a13f2ad82..690b35698 100644
--- a/torch_geometric/data/data.py
+++ b/torch_geometric/data/data.py
@@ -659,9 +659,13 @@ class Data(BaseData, FeatureStore, GraphStore):
                 return value.get_dim_size()
             return int(value.max()) + 1
         elif 'index' in key or key == 'face':
-            if self.num_nodes is None:
-                raise ValueError(f'Can not infer inc number for {key}!')
-            return self.num_nodes
+            num_nodes = self.num_nodes
+            if num_nodes is None:
+                raise RuntimeError(f"Unable to infer 'num_nodes' from the "
+                                   f"attribute '{key}'. Please explicitly set "
+                                   f"'num_nodes' as an attribute of 'data' to "
+                                   f"prevent this error")
+            return num_nodes
         else:
             return 0
 


Test patch:
diff --git a/test/data/test_data.py b/test/data/test_data.py
index 3b94d4169..9c95fcb58 100644
--- a/test/data/test_data.py
+++ b/test/data/test_data.py
@@ -582,3 +582,14 @@ def test_data_time_handling(num_nodes, num_edges):
 
     out = out.sort_by_time()
     assert torch.equal(out.time, data.time.repeat_interleave(2))
+
+
+def test_data_inc():
+    data = Data(edge_index=torch.tensor([[0, 1], [1, 0]]))
+    with pytest.warns(UserWarning, match="Unable to accurately infer"):
+        assert data.__inc__('edge_index', data.edge_index) == 2
+
+        data = Data(index=torch.empty(2, 0, dtype=torch.long))
+    with pytest.raises(RuntimeError, match="Unable to infer"):
+        with pytest.warns(UserWarning, match="Unable to accurately infer"):
+            data.__inc__('index', data.edge_index)