Repository: pyg-team/pytorch_geometric
Pull request #7036 (2023-03-25 14:44:14)

Test against nightly PyTorch releases

codecov[bot]: ## [Codecov](https://codecov.io/gh/pyg-team/pytorch_geometric/pull/7036?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyg-team) Report
> Merging [#7036](https://codecov.io/gh/pyg-team/pytorch_geometric/pull/7036?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyg-team) (f636d5d) into [master](https://codecov.io/gh/pyg-team/pytorch_geometric/commit/a51e1db0ff009037925451a2e994434053e4da06?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyg-team) (a51e1db) will **not change** coverage.
> The diff coverage is `n/a`.

> :exclamation: Current head f636d5d differs from pull request most recent head 63d9355. Consider uploading reports for the commit 63d9355 to get more accurate results

```diff
@@           Coverage Diff           @@
##           master    #7036   +/-   ##
=======================================
  Coverage   91.51%   91.51%           
=======================================
  Files         435      435           
  Lines       23777    23777           
=======================================
  Hits        21760    21760           
  Misses       2017     2017           
```



:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyg-team)


Patch:
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index 412dc6240..4bb57fa8f 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -31,12 +31,21 @@ runs:
           setup.py
 
     - name: Install PyTorch ${{ inputs.torch-version }}+${{ inputs.cuda-version }}
+      if: ${{ inputs.torch-version != 'nightly' }}
       run: |
         pip install torch==${{ inputs.torch-version }} --extra-index-url https://download.pytorch.org/whl/${{ inputs.cuda-version }}
         python -c "import torch; print('PyTorch:', torch.__version__)"
         python -c "import torch; print('CUDA:', torch.version.cuda)"
       shell: bash
 
+    - name: Install PyTorch ${{ inputs.torch-version }}+${{ inputs.cuda-version }}
+      if: ${{ inputs.torch-version == 'nightly' }}
+      run: |
+        pip install --pre torch --extra-index-url https://download.pytorch.org/whl/nightly/${{ inputs.cuda-version }}
+        python -c "import torch; print('PyTorch:', torch.__version__)"
+        python -c "import torch; print('CUDA:', torch.version.cuda)"
+      shell: bash
+
     - name: Install extension packages
       if: ${{ inputs.full_install == 'true' }}
       run: |
diff --git a/torch_geometric/typing.py b/torch_geometric/typing.py
index bb9c74e77..75af7e5c2 100644
--- a/torch_geometric/typing.py
+++ b/torch_geometric/typing.py
@@ -41,17 +41,50 @@ except (ImportError, OSError) as e:
     if isinstance(e, OSError):
         warnings.warn(f"An issue occurred while importing 'torch-sparse'. "
                       f"Disabling its usage. Stacktrace: {e}")
-    torch_sparse = object
     WITH_TORCH_SPARSE = False
 
     class SparseTensor:
-        def __init__(self, *args, **kwargs):
+        def __init__(
+            self,
+            row: Optional[torch.Tensor] = None,
+            rowptr: Optional[torch.Tensor] = None,
+            col: Optional[torch.Tensor] = None,
+            value: Optional[torch.Tensor] = None,
+            sparse_sizes: Optional[Tuple[Optional[int], Optional[int]]] = None,
+            is_sorted: bool = False,
+            trust_data: bool = False,
+        ):
             raise ImportError("'SparseTensor' requires 'torch-sparse'")
 
         @classmethod
-        def from_edge_index(cls, *args, **kwargs) -> 'SparseTensor':
+        def from_edge_index(
+            self,
+            edge_index: torch.Tensor,
+            edge_attr: Optional[torch.Tensor] = None,
+            sparse_sizes: Optional[Tuple[Optional[int], Optional[int]]] = None,
+            is_sorted: bool = False,
+            trust_data: bool = False,
+        ) -> 'SparseTensor':
             raise ImportError("'SparseTensor' requires 'torch-sparse'")
 
+        def size(self, dim: int) -> int:
+            raise ImportError("'SparseTensor' requires 'torch-sparse'")
+
+        def is_cuda(self) -> bool:
+            raise ImportError("'SparseTensor' requires 'torch-sparse'")
+
+        def to_torch_sparse_csr_tensor(
+            self,
+            dtype: Optional[torch.dtype] = None,
+        ) -> torch.Tensor:
+            raise ImportError("'SparseTensor' requires 'torch-sparse'")
+
+    class torch_sparse:
+        @staticmethod
+        def matmul(src: SparseTensor, other: Tensor,
+                   reduce: str = "sum") -> Tensor:
+            raise ImportError("'matmul' requires 'torch-sparse'")
+
 
 # Types for accessing data ####################################################
 


Test patch:
diff --git a/.github/workflows/latest_testing.yml b/.github/workflows/latest_testing.yml
new file mode 100644
index 000000000..1df55913f
--- /dev/null
+++ b/.github/workflows/latest_testing.yml
@@ -0,0 +1,67 @@
+name: Testing PyTorch nightly
+
+on:  # yamllint disable-line rule:truthy
+  push:
+    branches:
+      - master
+  pull_request:
+
+jobs:
+
+  latest_pytest:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 40
+
+      # Skip workflow if only certain files have been changed.
+      - name: Get changed files
+        id: changed-files-specific
+        uses: tj-actions/changed-files@v34
+        with:
+          files: |
+            benchmark/**
+            conda/**
+            docker/**
+            docs/**
+            examples/**
+            graphgym/**
+            CHANGELOG.md
+
+      - name: Setup packages
+        if: steps.changed-files-specific.outputs.only_changed != 'true'
+        uses: ./.github/actions/setup
+        with:
+          torch-version: nightly
+          full_install: false
+
+      - name: Install main package
+        if: steps.changed-files-specific.outputs.only_changed != 'true'
+        run: |
+          pip install -e .[full,test]
+
+      - name: Run tests
+        if: steps.changed-files-specific.outputs.only_changed != 'true'
+        run: |
+          pytest test/test_debug.py
+          pytest test/test_experimental.py
+          pytest test/test_home.py
+          pytest test/test_seed.py
+          pytest test/test_typing.py
+          pytest test/contrib/
+          # pytest test/data/
+          pytest test/datasets/
+          pytest test/explain/
+          pytest test/graphgym/
+          pytest test/io/
+          # pytest test/loader/
+          # pytest test/nn/
+          pytest test/profile/
+          pytest test/sampler/
+          pytest test/testing/
+          # pytest test/transforms/
+          pytest test/utils/
+          pytest test/visualization/
diff --git a/test/profile/test_profile_utils.py b/test/profile/test_profile_utils.py
index 6d52194ec..c9215be68 100644
--- a/test/profile/test_profile_utils.py
+++ b/test/profile/test_profile_utils.py
@@ -1,6 +1,5 @@
 import torch
 from torch.nn import Linear
-from torch_sparse import SparseTensor
 
 from torch_geometric.data import Data
 from torch_geometric.profile import (
@@ -15,7 +14,8 @@ from torch_geometric.profile.utils import (
     byte_to_megabyte,
     medibyte_to_megabyte,
 )
-from torch_geometric.testing import onlyCUDA
+from torch_geometric.testing import onlyCUDA, withPackage
+from torch_geometric.typing import SparseTensor
 
 
 def test_count_parameters():
@@ -28,6 +28,15 @@ def test_get_model_size():
 
 
 def test_get_data_size():
+    x = torch.randn(10, 128)
+    data = Data(x=x, y=x)
+
+    data_size = get_data_size(data)
+    assert data_size == 10 * 128 * 4
+
+
+@withPackage('torch_sparse')
+def test_get_data_size_with_sparse_tensor():
     x = torch.randn(10, 128)
     row, col = torch.randint(0, 10, (2, 100), dtype=torch.long)
     adj_t = SparseTensor(row=row, col=col, value=None, sparse_sizes=(10, 10))
diff --git a/test/utils/test_assortativity.py b/test/utils/test_assortativity.py
index 58b5fdb25..b1ac2f4e0 100644
--- a/test/utils/test_assortativity.py
+++ b/test/utils/test_assortativity.py
@@ -1,7 +1,8 @@
 import pytest
 import torch
-from torch_sparse import SparseTensor
 
+import torch_geometric.typing
+from torch_geometric.typing import SparseTensor
 from torch_geometric.utils import assortativity
 
 
@@ -12,9 +13,10 @@ def test_assortativity():
     out = assortativity(edge_index)
     assert pytest.approx(out, abs=1e-5) == 1.0
 
-    adj = SparseTensor.from_edge_index(edge_index, sparse_sizes=[6, 6])
-    out = assortativity(adj)
-    assert pytest.approx(out, abs=1e-5) == 1.0
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        adj = SparseTensor.from_edge_index(edge_index, sparse_sizes=[6, 6])
+        out = assortativity(adj)
+        assert pytest.approx(out, abs=1e-5) == 1.0
 
     # Completely disassortative graph:
     edge_index = torch.tensor([[0, 1, 2, 3, 4, 5, 5, 5, 5, 5],
@@ -22,6 +24,7 @@ def test_assortativity():
     out = assortativity(edge_index)
     assert pytest.approx(out, abs=1e-5) == -1.0
 
-    adj = SparseTensor.from_edge_index(edge_index, sparse_sizes=[6, 6])
-    out = assortativity(adj)
-    assert pytest.approx(out, abs=1e-5) == -1.0
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        adj = SparseTensor.from_edge_index(edge_index, sparse_sizes=[6, 6])
+        out = assortativity(adj)
+        assert pytest.approx(out, abs=1e-5) == -1.0
diff --git a/test/utils/test_homophily.py b/test/utils/test_homophily.py
index 7746d08d4..126cc7196 100644
--- a/test/utils/test_homophily.py
+++ b/test/utils/test_homophily.py
@@ -1,7 +1,8 @@
 import pytest
 import torch
-from torch_sparse import SparseTensor
 
+import torch_geometric.typing
+from torch_geometric.typing import SparseTensor
 from torch_geometric.utils import homophily
 
 
@@ -10,19 +11,23 @@ def test_homophily():
     y = torch.tensor([0, 0, 0, 0, 1])
     batch = torch.tensor([0, 0, 0, 1, 1])
     row, col = edge_index
-    adj = SparseTensor(row=row, col=col, sparse_sizes=(5, 5))
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        adj = SparseTensor(row=row, col=col, sparse_sizes=(5, 5))
 
     method = 'edge'
     assert pytest.approx(homophily(edge_index, y, method=method)) == 0.75
-    assert pytest.approx(homophily(adj, y, method=method)) == 0.75
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        assert pytest.approx(homophily(adj, y, method=method)) == 0.75
     assert homophily(edge_index, y, batch, method).tolist() == [1., 0.]
 
     method = 'node'
     assert pytest.approx(homophily(edge_index, y, method=method)) == 0.6
-    assert pytest.approx(homophily(adj, y, method=method)) == 0.6
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        assert pytest.approx(homophily(adj, y, method=method)) == 0.6
     assert homophily(edge_index, y, batch, method).tolist() == [1., 0.]
 
     method = 'edge_insensitive'
     assert pytest.approx(homophily(edge_index, y, method=method)) == 0.1999999
-    assert pytest.approx(homophily(adj, y, method=method)) == 0.1999999
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        assert pytest.approx(homophily(adj, y, method=method)) == 0.1999999
     assert homophily(edge_index, y, batch, method).tolist() == [0., 0.]
diff --git a/test/utils/test_segment.py b/test/utils/test_segment.py
index 5784e6215..754bbec73 100644
--- a/test/utils/test_segment.py
+++ b/test/utils/test_segment.py
@@ -1,11 +1,12 @@
 import pytest
 import torch
 
-from torch_geometric.testing import withCUDA
+from torch_geometric.testing import withCUDA, withPackage
 from torch_geometric.utils import segment
 
 
 @withCUDA
+@withPackage('torch_scatter')
 @pytest.mark.parametrize('reduce', ['sum', 'mean', 'min', 'max'])
 def test_segment(device, reduce):
     src = torch.randn(20, 16, device=device)
diff --git a/test/utils/test_softmax.py b/test/utils/test_softmax.py
index a84ce67b3..bfffba30c 100644
--- a/test/utils/test_softmax.py
+++ b/test/utils/test_softmax.py
@@ -1,6 +1,7 @@
+import pytest
 import torch
 
-import torch_geometric
+import torch_geometric.typing
 from torch_geometric.profile import benchmark
 from torch_geometric.utils import softmax
 
@@ -12,12 +13,17 @@ def test_softmax():
 
     out = softmax(src, index)
     assert out.tolist() == [0.5, 0.5, 1, 1]
-    assert softmax(src, None, ptr).tolist() == out.tolist()
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert softmax(src, None, ptr).tolist() == out.tolist()
+    else:
+        with pytest.raises(ImportError):
+            softmax(src, None, ptr)
 
     src = src.view(-1, 1)
     out = softmax(src, index)
     assert out.tolist() == [[0.5], [0.5], [1], [1]]
-    assert softmax(src, None, ptr).tolist() == out.tolist()
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert softmax(src, None, ptr).tolist() == out.tolist()
 
     jit = torch.jit.script(softmax)
     assert torch.allclose(jit(src, index), out)
@@ -46,19 +52,23 @@ def test_softmax_dim():
 
     src = torch.randn(4)
     assert torch.allclose(softmax(src, index, dim=0), src.softmax(dim=0))
-    assert torch.allclose(softmax(src, ptr=ptr, dim=0), src.softmax(dim=0))
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert torch.allclose(softmax(src, ptr=ptr, dim=0), src.softmax(dim=0))
 
     src = torch.randn(4, 16)
     assert torch.allclose(softmax(src, index, dim=0), src.softmax(dim=0))
-    assert torch.allclose(softmax(src, ptr=ptr, dim=0), src.softmax(dim=0))
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert torch.allclose(softmax(src, ptr=ptr, dim=0), src.softmax(dim=0))
 
     src = torch.randn(4, 4)
     assert torch.allclose(softmax(src, index, dim=-1), src.softmax(dim=-1))
-    assert torch.allclose(softmax(src, ptr=ptr, dim=-1), src.softmax(dim=-1))
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert torch.allclose(softmax(src, ptr=ptr, dim=-1), src.softmax(-1))
 
     src = torch.randn(4, 4, 16)
     assert torch.allclose(softmax(src, index, dim=1), src.softmax(dim=1))
-    assert torch.allclose(softmax(src, ptr=ptr, dim=1), src.softmax(dim=1))
+    if torch_geometric.typing.WITH_TORCH_SCATTER:
+        assert torch.allclose(softmax(src, ptr=ptr, dim=1), src.softmax(dim=1))
 
 
 if __name__ == '__main__':
diff --git a/test/utils/test_sparse.py b/test/utils/test_sparse.py
index 7814846a6..24d9e66af 100644
--- a/test/utils/test_sparse.py
+++ b/test/utils/test_sparse.py
@@ -1,8 +1,8 @@
 import torch
-from torch_sparse import SparseTensor
 
 import torch_geometric.typing
 from torch_geometric.testing import is_full_test
+from torch_geometric.typing import SparseTensor
 from torch_geometric.utils import (
     dense_to_sparse,
     is_sparse,
@@ -57,17 +57,21 @@ def test_is_torch_sparse_tensor():
     x = torch.randn(5, 5)
 
     assert not is_torch_sparse_tensor(x)
-    assert not is_torch_sparse_tensor(SparseTensor.from_dense(x))
     assert is_torch_sparse_tensor(x.to_sparse())
 
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        assert not is_torch_sparse_tensor(SparseTensor.from_dense(x))
+
 
 def test_is_sparse():
     x = torch.randn(5, 5)
 
     assert not is_sparse(x)
-    assert is_sparse(SparseTensor.from_dense(x))
     assert is_sparse(x.to_sparse())
 
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        assert is_sparse(SparseTensor.from_dense(x))
+
 
 def test_to_torch_coo_tensor():
     edge_index = torch.tensor([
diff --git a/test/utils/test_spmm.py b/test/utils/test_spmm.py
index 26f90ec16..666760369 100644
--- a/test/utils/test_spmm.py
+++ b/test/utils/test_spmm.py
@@ -5,6 +5,7 @@ import pytest
 import torch
 from torch import Tensor
 
+import torch_geometric.typing
 from torch_geometric.profile import benchmark
 from torch_geometric.testing import withCUDA, withPackage
 from torch_geometric.typing import SparseTensor
@@ -17,21 +18,23 @@ def test_spmm_basic(device, reduce):
     src = torch.randn(5, 4, device=device)
     other = torch.randn(4, 8, device=device)
 
-    out1 = src @ other
+    out1 = (src @ other) / (src.size(1) if reduce == 'mean' else 1)
     out2 = spmm(src.to_sparse_csr(), other, reduce=reduce)
-    out3 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
     assert out1.size() == (5, 8)
-    if reduce == 'sum':
-        assert torch.allclose(out1, out2, atol=1e-6)
-        assert torch.allclose(out1, out3, atol=1e-6)
-    assert torch.allclose(out2, out3, atol=1e-6)
+    assert torch.allclose(out1, out2, atol=1e-6)
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        out3 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
+        assert torch.allclose(out2, out3, atol=1e-6)
 
     # Test `mean` reduction with isolated nodes:
     src[0] = 0.
+    out1 = (src @ other) / (4. if reduce == 'mean' else 1.)
     out2 = spmm(src.to_sparse_csr(), other, reduce=reduce)
-    out3 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
     assert out1.size() == (5, 8)
-    assert torch.allclose(out2, out3, atol=1e-6)
+    assert torch.allclose(out1, out2, atol=1e-6)
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        out3 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
+        assert torch.allclose(out2, out3, atol=1e-6)
 
 
 @withCUDA
@@ -46,8 +49,10 @@ def test_spmm_reduce(device, reduce):
             spmm(src.to_sparse_csr(), other, reduce)
     else:
         out1 = spmm(src.to_sparse_csr(), other, reduce)
-        out2 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
-        assert torch.allclose(out1, out2)
+        assert out1.size() == (5, 8)
+        if torch_geometric.typing.WITH_TORCH_SPARSE:
+            out2 = spmm(SparseTensor.from_dense(src), other, reduce=reduce)
+            assert torch.allclose(out1, out2)
 
 
 @withCUDA
@@ -91,13 +96,13 @@ def test_spmm_jit(reduce):
     other = torch.randn(4, 8)
 
     out1 = src @ other
-    out2 = jit_torch_sparse(SparseTensor.from_dense(src), other, reduce=reduce)
-    out3 = jit_torch(src.to_sparse_csr(), other, reduce)
+    out2 = jit_torch(src.to_sparse_csr(), other, reduce)
     assert out1.size() == (5, 8)
     if reduce == 'sum':
         assert torch.allclose(out1, out2, atol=1e-6)
-        assert torch.allclose(out1, out3, atol=1e-6)
-    assert torch.allclose(out2, out3, atol=1e-6)
+    if torch_geometric.typing.WITH_TORCH_SPARSE:
+        out3 = jit_torch_sparse(SparseTensor.from_dense(src), other, reduce)
+        assert torch.allclose(out2, out3, atol=1e-6)
 
 
 if __name__ == '__main__':