test_segment.py 6.54 KB
Newer Older
rusty1s's avatar
rusty1s committed
1
2
3
4
from itertools import product

import pytest
import torch
rusty1s's avatar
rusty1s committed
5
from torch.autograd import gradcheck
rusty1s's avatar
rusty1s committed
6
import torch_scatter
rusty1s's avatar
rusty1s committed
7

rusty1s's avatar
rusty1s committed
8
from .utils import tensor, dtypes, devices
rusty1s's avatar
rusty1s committed
9

rusty1s's avatar
rusty1s committed
10
reductions = ['sum', 'add', 'mean', 'min', 'max']
rusty1s's avatar
rusty1s committed
11
12
13
14
15
16

tests = [
    {
        'src': [1, 2, 3, 4, 5, 6],
        'index': [0, 0, 1, 1, 1, 3],
        'indptr': [0, 2, 5, 5, 6],
rusty1s's avatar
rusty1s committed
17
        'sum': [3, 12, 0, 6],
rusty1s's avatar
rusty1s committed
18
        'add': [3, 12, 0, 6],
rusty1s's avatar
rusty1s committed
19
20
21
22
23
24
25
26
27
28
        'mean': [1.5, 4, 0, 6],
        'min': [1, 3, 0, 6],
        'arg_min': [0, 2, 6, 5],
        'max': [2, 5, 0, 6],
        'arg_max': [1, 4, 6, 5],
    },
    {
        'src': [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]],
        'index': [0, 0, 1, 1, 1, 3],
        'indptr': [0, 2, 5, 5, 6],
rusty1s's avatar
rusty1s committed
29
        'sum': [[4, 6], [21, 24], [0, 0], [11, 12]],
rusty1s's avatar
rusty1s committed
30
        'add': [[4, 6], [21, 24], [0, 0], [11, 12]],
rusty1s's avatar
rusty1s committed
31
32
33
34
35
36
37
38
39
40
        'mean': [[2, 3], [7, 8], [0, 0], [11, 12]],
        'min': [[1, 2], [5, 6], [0, 0], [11, 12]],
        'arg_min': [[0, 0], [2, 2], [6, 6], [5, 5]],
        'max': [[3, 4], [9, 10], [0, 0], [11, 12]],
        'arg_max': [[1, 1], [4, 4], [6, 6], [5, 5]],
    },
    {
        'src': [[1, 3, 5, 7, 9, 11], [2, 4, 6, 8, 10, 12]],
        'index': [[0, 0, 1, 1, 1, 3], [0, 0, 0, 1, 1, 2]],
        'indptr': [[0, 2, 5, 5, 6], [0, 3, 5, 6, 6]],
rusty1s's avatar
rusty1s committed
41
        'sum': [[4, 21, 0, 11], [12, 18, 12, 0]],
rusty1s's avatar
rusty1s committed
42
        'add': [[4, 21, 0, 11], [12, 18, 12, 0]],
rusty1s's avatar
rusty1s committed
43
44
45
46
47
48
49
        'mean': [[2, 7, 0, 11], [4, 9, 12, 0]],
        'min': [[1, 5, 0, 11], [2, 8, 12, 0]],
        'arg_min': [[0, 2, 6, 5], [0, 3, 5, 6]],
        'max': [[3, 9, 0, 11], [6, 10, 12, 0]],
        'arg_max': [[1, 4, 6, 5], [2, 4, 5, 6]],
    },
    {
rusty1s's avatar
rusty1s committed
50
51
52
        'src': [[[1, 2], [3, 4], [5, 6]], [[7, 9], [10, 11], [12, 13]]],
        'index': [[0, 0, 1], [0, 2, 2]],
        'indptr': [[0, 2, 3, 3], [0, 1, 1, 3]],
rusty1s's avatar
rusty1s committed
53
        'sum': [[[4, 6], [5, 6], [0, 0]], [[7, 9], [0, 0], [22, 24]]],
rusty1s's avatar
rusty1s committed
54
        'add': [[[4, 6], [5, 6], [0, 0]], [[7, 9], [0, 0], [22, 24]]],
rusty1s's avatar
rusty1s committed
55
56
57
58
59
        'mean': [[[2, 3], [5, 6], [0, 0]], [[7, 9], [0, 0], [11, 12]]],
        'min': [[[1, 2], [5, 6], [0, 0]], [[7, 9], [0, 0], [10, 11]]],
        'arg_min': [[[0, 0], [2, 2], [3, 3]], [[0, 0], [3, 3], [1, 1]]],
        'max': [[[3, 4], [5, 6], [0, 0]], [[7, 9], [0, 0], [12, 13]]],
        'arg_max': [[[1, 1], [2, 2], [3, 3]], [[0, 0], [3, 3], [2, 2]]],
rusty1s's avatar
rusty1s committed
60
61
62
63
64
    },
    {
        'src': [[1, 3], [2, 4]],
        'index': [[0, 0], [0, 0]],
        'indptr': [[0, 2], [0, 2]],
rusty1s's avatar
rusty1s committed
65
        'sum': [[4], [6]],
rusty1s's avatar
rusty1s committed
66
        'add': [[4], [6]],
rusty1s's avatar
rusty1s committed
67
68
69
70
71
72
73
74
75
76
        'mean': [[2], [3]],
        'min': [[1], [2]],
        'arg_min': [[0], [0]],
        'max': [[3], [4]],
        'arg_max': [[1], [1]],
    },
    {
        'src': [[[1, 1], [3, 3]], [[2, 2], [4, 4]]],
        'index': [[0, 0], [0, 0]],
        'indptr': [[0, 2], [0, 2]],
rusty1s's avatar
rusty1s committed
77
        'sum': [[[4, 4]], [[6, 6]]],
rusty1s's avatar
rusty1s committed
78
        'add': [[[4, 4]], [[6, 6]]],
rusty1s's avatar
rusty1s committed
79
80
81
82
83
84
85
86
87
88
89
        'mean': [[[2, 2]], [[3, 3]]],
        'min': [[[1, 1]], [[2, 2]]],
        'arg_min': [[[0, 0]], [[0, 0]]],
        'max': [[[3, 3]], [[4, 4]]],
        'arg_max': [[[1, 1]], [[1, 1]]],
    },
]


@pytest.mark.parametrize('test,reduce,dtype,device',
                         product(tests, reductions, dtypes, devices))
rusty1s's avatar
rusty1s committed
90
def test_forward(test, reduce, dtype, device):
rusty1s's avatar
rusty1s committed
91
92
93
94
95
    src = tensor(test['src'], dtype, device)
    index = tensor(test['index'], torch.long, device)
    indptr = tensor(test['indptr'], torch.long, device)
    expected = tensor(test[reduce], dtype, device)

rusty1s's avatar
rusty1s committed
96
97
98
99
100
101
    out = getattr(torch_scatter, f'segment_{reduce}_csr')(src, indptr)
    if isinstance(out, tuple):
        out, arg_out = out
        arg_expected = tensor(test[f'arg_{reduce}'], torch.long, device)
        assert torch.all(arg_out == arg_expected)
    assert torch.all(out == expected)
rusty1s's avatar
rusty1s committed
102

rusty1s's avatar
rusty1s committed
103
    out = getattr(torch_scatter, f'segment_{reduce}_coo')(src, index)
rusty1s's avatar
rusty1s committed
104
105
106
107
108
109
    if isinstance(out, tuple):
        out, arg_out = out
        arg_expected = tensor(test[f'arg_{reduce}'], torch.long, device)
        assert torch.all(arg_out == arg_expected)
    assert torch.all(out == expected)

rusty1s's avatar
rusty1s committed
110

rusty1s's avatar
rusty1s committed
111
@pytest.mark.parametrize('test,reduce,device',
rusty1s's avatar
rusty1s committed
112
                         product(tests, reductions, devices))
rusty1s's avatar
rusty1s committed
113
114
115
116
117
118
def test_backward(test, reduce, device):
    src = tensor(test['src'], torch.double, device)
    src.requires_grad_()
    index = tensor(test['index'], torch.long, device)
    indptr = tensor(test['indptr'], torch.long, device)

rusty1s's avatar
rusty1s committed
119
120
121
    assert gradcheck(torch_scatter.segment_csr, (src, indptr, None, reduce))
    assert gradcheck(torch_scatter.segment_coo,
                     (src, index, None, None, reduce))
rusty1s's avatar
rusty1s committed
122
123


rusty1s's avatar
rusty1s committed
124
125
@pytest.mark.parametrize('test,reduce,dtype,device',
                         product(tests, reductions, dtypes, devices))
rusty1s's avatar
rusty1s committed
126
def test_out(test, reduce, dtype, device):
rusty1s's avatar
rusty1s committed
127
128
129
130
131
    src = tensor(test['src'], dtype, device)
    index = tensor(test['index'], torch.long, device)
    indptr = tensor(test['indptr'], torch.long, device)
    expected = tensor(test[reduce], dtype, device)

rusty1s's avatar
rusty1s committed
132
    out = torch.full_like(expected, -2)
rusty1s's avatar
rusty1s committed
133

rusty1s's avatar
rusty1s committed
134
    getattr(torch_scatter, f'segment_{reduce}_csr')(src, indptr, out)
rusty1s's avatar
rusty1s committed
135
136
    assert torch.all(out == expected)

rusty1s's avatar
rusty1s committed
137
    out.fill_(-2)
rusty1s's avatar
rusty1s committed
138

rusty1s's avatar
rusty1s committed
139
    getattr(torch_scatter, f'segment_{reduce}_coo')(src, index, out)
rusty1s's avatar
rusty1s committed
140

rusty1s's avatar
rusty1s committed
141
    if reduce == 'sum' or reduce == 'add':
rusty1s's avatar
rusty1s committed
142
143
144
145
146
147
148
149
150
        expected = expected - 2
    elif reduce == 'mean':
        expected = out  # We can not really test this here.
    elif reduce == 'min':
        expected = expected.fill_(-2)
    elif reduce == 'max':
        expected[expected == 0] = -2
    else:
        raise ValueError
rusty1s's avatar
rusty1s committed
151

rusty1s's avatar
rusty1s committed
152
    assert torch.all(out == expected)
rusty1s's avatar
rusty1s committed
153
154
155
156


@pytest.mark.parametrize('test,reduce,dtype,device',
                         product(tests, reductions, dtypes, devices))
rusty1s's avatar
rusty1s committed
157
def test_non_contiguous(test, reduce, dtype, device):
rusty1s's avatar
rusty1s committed
158
159
160
161
162
163
164
165
166
167
168
169
    src = tensor(test['src'], dtype, device)
    index = tensor(test['index'], torch.long, device)
    indptr = tensor(test['indptr'], torch.long, device)
    expected = tensor(test[reduce], dtype, device)

    if src.dim() > 1:
        src = src.transpose(0, 1).contiguous().transpose(0, 1)
    if index.dim() > 1:
        index = index.transpose(0, 1).contiguous().transpose(0, 1)
    if indptr.dim() > 1:
        indptr = indptr.transpose(0, 1).contiguous().transpose(0, 1)

rusty1s's avatar
rusty1s committed
170
171
172
173
174
175
    out = getattr(torch_scatter, f'segment_{reduce}_csr')(src, indptr)
    if isinstance(out, tuple):
        out, arg_out = out
        arg_expected = tensor(test[f'arg_{reduce}'], torch.long, device)
        assert torch.all(arg_out == arg_expected)
    assert torch.all(out == expected)
rusty1s's avatar
rusty1s committed
176

rusty1s's avatar
rusty1s committed
177
    out = getattr(torch_scatter, f'segment_{reduce}_coo')(src, index)
rusty1s's avatar
rusty1s committed
178
179
180
181
182
    if isinstance(out, tuple):
        out, arg_out = out
        arg_expected = tensor(test[f'arg_{reduce}'], torch.long, device)
        assert torch.all(arg_out == arg_expected)
    assert torch.all(out == expected)