"test/test_tensor.py" did not exist on "049416bcd157b079d744d3d1b458a5ee10bf3434"
test_batched_heterograph.py 15.2 KB
Newer Older
1
2
import dgl
import backend as F
Mufei Li's avatar
Mufei Li committed
3
import unittest
4
import pytest
5
6

from dgl.base import ALL
7
from utils import parametrize_dtype
8
from test_utils import check_graph_equal, get_cases
9

Jinjing Zhou's avatar
Jinjing Zhou committed
10

11
12
13
14
15
16
17
18
19
20
21
22
23
24
def check_equivalence_between_heterographs(g1, g2, node_attrs=None, edge_attrs=None):
    assert g1.ntypes == g2.ntypes
    assert g1.etypes == g2.etypes
    assert g1.canonical_etypes == g2.canonical_etypes

    for nty in g1.ntypes:
        assert g1.number_of_nodes(nty) == g2.number_of_nodes(nty)

    for ety in g1.etypes:
        if len(g1._etype2canonical[ety]) > 0:
            assert g1.number_of_edges(ety) == g2.number_of_edges(ety)

    for ety in g1.canonical_etypes:
        assert g1.number_of_edges(ety) == g2.number_of_edges(ety)
25
26
        src1, dst1, eid1 = g1.edges(etype=ety, form='all')
        src2, dst2, eid2 = g2.edges(etype=ety, form='all')
27
28
        assert F.allclose(src1, src2)
        assert F.allclose(dst1, dst2)
29
        assert F.allclose(eid1, eid2)
30
31
32

    if node_attrs is not None:
        for nty in node_attrs.keys():
33
34
            if g1.number_of_nodes(nty) == 0:
                continue
35
            for feat_name in node_attrs[nty]:
Jinjing Zhou's avatar
Jinjing Zhou committed
36
37
                assert F.allclose(
                    g1.nodes[nty].data[feat_name], g2.nodes[nty].data[feat_name])
38
39
40

    if edge_attrs is not None:
        for ety in edge_attrs.keys():
41
42
            if g1.number_of_edges(ety) == 0:
                continue
43
            for feat_name in edge_attrs[ety]:
Jinjing Zhou's avatar
Jinjing Zhou committed
44
45
46
                assert F.allclose(
                    g1.edges[ety].data[feat_name], g2.edges[ety].data[feat_name])

47

48
@pytest.mark.parametrize('gs', get_cases(['two_hetero_batch']))
49
@parametrize_dtype
50
def test_topology(gs, idtype):
51
    """Test batching two DGLHeteroGraphs where some nodes are isolated in some relations"""
52
53
54
    g1, g2 = gs
    g1 = g1.astype(idtype).to(F.ctx())
    g2 = g2.astype(idtype).to(F.ctx())
55
    bg = dgl.batch([g1, g2])
56

57
58
    assert bg.idtype == idtype
    assert bg.device == F.ctx()
59
60
61
62
63
64
65
    assert bg.ntypes == g2.ntypes
    assert bg.etypes == g2.etypes
    assert bg.canonical_etypes == g2.canonical_etypes
    assert bg.batch_size == 2

    # Test number of nodes
    for ntype in bg.ntypes:
66
67
        print(ntype)
        assert F.asnumpy(bg.batch_num_nodes(ntype)).tolist() == [
68
69
            g1.number_of_nodes(ntype), g2.number_of_nodes(ntype)]
        assert bg.number_of_nodes(ntype) == (
Jinjing Zhou's avatar
Jinjing Zhou committed
70
            g1.number_of_nodes(ntype) + g2.number_of_nodes(ntype))
71
72

    # Test number of edges
73
    for etype in bg.canonical_etypes:
74
        assert F.asnumpy(bg.batch_num_edges(etype)).tolist() == [
75
76
77
78
79
80
            g1.number_of_edges(etype), g2.number_of_edges(etype)]
        assert bg.number_of_edges(etype) == (
            g1.number_of_edges(etype) + g2.number_of_edges(etype))

    # Test relabeled nodes
    for ntype in bg.ntypes:
Jinjing Zhou's avatar
Jinjing Zhou committed
81
82
        assert list(F.asnumpy(bg.nodes(ntype))) == list(
            range(bg.number_of_nodes(ntype)))
83
84

    # Test relabeled edges
85
    src, dst = bg.edges(etype=('user', 'follows', 'user'))
86
87
    assert list(F.asnumpy(src)) == [0, 1, 4, 5]
    assert list(F.asnumpy(dst)) == [1, 2, 5, 6]
88
    src, dst = bg.edges(etype=('user', 'follows', 'developer'))
89
90
    assert list(F.asnumpy(src)) == [0, 1, 4, 5]
    assert list(F.asnumpy(dst)) == [1, 2, 4, 5]
91
    src, dst, eid = bg.edges(etype='plays', form='all')
92
93
94
95
96
    assert list(F.asnumpy(src)) == [0, 1, 2, 3, 4, 5, 6]
    assert list(F.asnumpy(dst)) == [0, 0, 1, 1, 2, 2, 3]
    assert list(F.asnumpy(eid)) == [0, 1, 2, 3, 4, 5, 6]

    # Test unbatching graphs
97
    g3, g4 = dgl.unbatch(bg)
98
99
100
    check_equivalence_between_heterographs(g1, g3)
    check_equivalence_between_heterographs(g2, g4)

101
102
103
104
105
106
    # Test dtype cast
    if idtype == "int32":
        bg_cast = bg.long()
    else:
        bg_cast = bg.int()
    assert bg.batch_size == bg_cast.batch_size
107

108
109
110
    # Test local var
    bg_local = bg.local_var()
    assert bg.batch_size == bg_local.batch_size
111

Jinjing Zhou's avatar
Jinjing Zhou committed
112

113
@parametrize_dtype
114
def test_batching_batched(idtype):
115
116
    """Test batching a DGLHeteroGraph and a BatchedDGLHeteroGraph."""
    g1 = dgl.heterograph({
117
118
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([0, 1], [0, 0])
119
    }, idtype=idtype, device=F.ctx())
120
    g2 = dgl.heterograph({
121
122
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([0, 1], [0, 0])
123
124
    }, idtype=idtype, device=F.ctx())
    bg1 = dgl.batch([g1, g2])
125
    g3 = dgl.heterograph({
126
127
        ('user', 'follows', 'user'): ([0], [1]),
        ('user', 'plays', 'game'): ([1], [0])
128
129
130
131
    }, idtype=idtype, device=F.ctx())
    bg2 = dgl.batch([bg1, g3])
    assert bg2.idtype == idtype
    assert bg2.device == F.ctx()
132
133
134
135
136
137
138
    assert bg2.ntypes == g3.ntypes
    assert bg2.etypes == g3.etypes
    assert bg2.canonical_etypes == g3.canonical_etypes
    assert bg2.batch_size == 3

    # Test number of nodes
    for ntype in bg2.ntypes:
139
        assert F.asnumpy(bg2.batch_num_nodes(ntype)).tolist() == [
140
141
            g1.number_of_nodes(ntype), g2.number_of_nodes(ntype), g3.number_of_nodes(ntype)]
        assert bg2.number_of_nodes(ntype) == (
Jinjing Zhou's avatar
Jinjing Zhou committed
142
            g1.number_of_nodes(ntype) + g2.number_of_nodes(ntype) + g3.number_of_nodes(ntype))
143
144
145

    # Test number of edges
    for etype in bg2.canonical_etypes:
146
        assert F.asnumpy(bg2.batch_num_edges(etype)).tolist() == [
147
148
            g1.number_of_edges(etype), g2.number_of_edges(etype), g3.number_of_edges(etype)]
        assert bg2.number_of_edges(etype) == (
Jinjing Zhou's avatar
Jinjing Zhou committed
149
            g1.number_of_edges(etype) + g2.number_of_edges(etype) + g3.number_of_edges(etype))
150
151
152

    # Test relabeled nodes
    for ntype in bg2.ntypes:
Jinjing Zhou's avatar
Jinjing Zhou committed
153
154
        assert list(F.asnumpy(bg2.nodes(ntype))) == list(
            range(bg2.number_of_nodes(ntype)))
155
156

    # Test relabeled edges
157
    src, dst = bg2.edges(etype='follows')
158
159
    assert list(F.asnumpy(src)) == [0, 1, 3, 4, 6]
    assert list(F.asnumpy(dst)) == [1, 2, 4, 5, 7]
160
    src, dst = bg2.edges(etype='plays')
161
162
163
164
    assert list(F.asnumpy(src)) == [0, 1, 3, 4, 7]
    assert list(F.asnumpy(dst)) == [0, 0, 1, 1, 2]

    # Test unbatching graphs
165
    g4, g5, g6 = dgl.unbatch(bg2)
166
167
168
169
    check_equivalence_between_heterographs(g1, g4)
    check_equivalence_between_heterographs(g2, g5)
    check_equivalence_between_heterographs(g3, g6)

Jinjing Zhou's avatar
Jinjing Zhou committed
170

171
@parametrize_dtype
172
def test_features(idtype):
173
174
    """Test the features of batched DGLHeteroGraphs"""
    g1 = dgl.heterograph({
175
176
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([0, 1], [0, 0])
177
    }, idtype=idtype, device=F.ctx())
178
179
180
181
182
183
184
185
186
    g1.nodes['user'].data['h1'] = F.tensor([[0.], [1.], [2.]])
    g1.nodes['user'].data['h2'] = F.tensor([[3.], [4.], [5.]])
    g1.nodes['game'].data['h1'] = F.tensor([[0.]])
    g1.nodes['game'].data['h2'] = F.tensor([[1.]])
    g1.edges['follows'].data['h1'] = F.tensor([[0.], [1.]])
    g1.edges['follows'].data['h2'] = F.tensor([[2.], [3.]])
    g1.edges['plays'].data['h1'] = F.tensor([[0.], [1.]])

    g2 = dgl.heterograph({
187
188
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([0, 1], [0, 0])
189
    }, idtype=idtype, device=F.ctx())
190
191
192
193
194
195
196
197
    g2.nodes['user'].data['h1'] = F.tensor([[0.], [1.], [2.]])
    g2.nodes['user'].data['h2'] = F.tensor([[3.], [4.], [5.]])
    g2.nodes['game'].data['h1'] = F.tensor([[0.]])
    g2.nodes['game'].data['h2'] = F.tensor([[1.]])
    g2.edges['follows'].data['h1'] = F.tensor([[0.], [1.]])
    g2.edges['follows'].data['h2'] = F.tensor([[2.], [3.]])
    g2.edges['plays'].data['h1'] = F.tensor([[0.], [1.]])

198
199
    # test default setting
    bg = dgl.batch([g1, g2])
200
201
202
203
204
205
206
207
208
209
    assert F.allclose(bg.nodes['user'].data['h1'],
                      F.cat([g1.nodes['user'].data['h1'], g2.nodes['user'].data['h1']], dim=0))
    assert F.allclose(bg.nodes['user'].data['h2'],
                      F.cat([g1.nodes['user'].data['h2'], g2.nodes['user'].data['h2']], dim=0))
    assert F.allclose(bg.nodes['game'].data['h1'],
                      F.cat([g1.nodes['game'].data['h1'], g2.nodes['game'].data['h1']], dim=0))
    assert F.allclose(bg.nodes['game'].data['h2'],
                      F.cat([g1.nodes['game'].data['h2'], g2.nodes['game'].data['h2']], dim=0))
    assert F.allclose(bg.edges['follows'].data['h1'],
                      F.cat([g1.edges['follows'].data['h1'], g2.edges['follows'].data['h1']], dim=0))
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
    assert F.allclose(bg.edges['follows'].data['h2'],
                      F.cat([g1.edges['follows'].data['h2'], g2.edges['follows'].data['h2']], dim=0))
    assert F.allclose(bg.edges['plays'].data['h1'],
                      F.cat([g1.edges['plays'].data['h1'], g2.edges['plays'].data['h1']], dim=0))

    # test specifying ndata/edata
    bg = dgl.batch([g1, g2], ndata=['h2'], edata=['h1'])
    assert F.allclose(bg.nodes['user'].data['h2'],
                      F.cat([g1.nodes['user'].data['h2'], g2.nodes['user'].data['h2']], dim=0))
    assert F.allclose(bg.nodes['game'].data['h2'],
                      F.cat([g1.nodes['game'].data['h2'], g2.nodes['game'].data['h2']], dim=0))
    assert F.allclose(bg.edges['follows'].data['h1'],
                      F.cat([g1.edges['follows'].data['h1'], g2.edges['follows'].data['h1']], dim=0))
    assert F.allclose(bg.edges['plays'].data['h1'],
                      F.cat([g1.edges['plays'].data['h1'], g2.edges['plays'].data['h1']], dim=0))
    assert 'h1' not in bg.nodes['user'].data
    assert 'h1' not in bg.nodes['game'].data
    assert 'h2' not in bg.edges['follows'].data
228
229

    # Test unbatching graphs
230
    g3, g4 = dgl.unbatch(bg)
231
232
    check_equivalence_between_heterographs(
        g1, g3,
233
        node_attrs={'user': ['h2'], 'game': ['h2']},
234
235
236
        edge_attrs={('user', 'follows', 'user'): ['h1']})
    check_equivalence_between_heterographs(
        g2, g4,
237
        node_attrs={'user': ['h2'], 'game': ['h2']},
238
239
        edge_attrs={('user', 'follows', 'user'): ['h1']})

240
241
242
243
    # test legacy
    bg = dgl.batch([g1, g2], edge_attrs=['h1'])
    assert 'h2' not in bg.edges['follows'].data.keys()

Jinjing Zhou's avatar
Jinjing Zhou committed
244

245
@unittest.skipIf(F.backend_name == 'mxnet', reason="MXNet does not support split array with zero-length segment.")
246
@parametrize_dtype
247
def test_empty_relation(idtype):
248
249
    """Test the features of batched DGLHeteroGraphs"""
    g1 = dgl.heterograph({
250
251
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([], [])
252
    }, idtype=idtype, device=F.ctx())
253
254
255
256
257
258
    g1.nodes['user'].data['h1'] = F.tensor([[0.], [1.], [2.]])
    g1.nodes['user'].data['h2'] = F.tensor([[3.], [4.], [5.]])
    g1.edges['follows'].data['h1'] = F.tensor([[0.], [1.]])
    g1.edges['follows'].data['h2'] = F.tensor([[2.], [3.]])

    g2 = dgl.heterograph({
259
260
        ('user', 'follows', 'user'): ([0, 1], [1, 2]),
        ('user', 'plays', 'game'): ([0, 1], [0, 0])
261
    }, idtype=idtype, device=F.ctx())
262
263
264
265
266
267
268
269
    g2.nodes['user'].data['h1'] = F.tensor([[0.], [1.], [2.]])
    g2.nodes['user'].data['h2'] = F.tensor([[3.], [4.], [5.]])
    g2.nodes['game'].data['h1'] = F.tensor([[0.]])
    g2.nodes['game'].data['h2'] = F.tensor([[1.]])
    g2.edges['follows'].data['h1'] = F.tensor([[0.], [1.]])
    g2.edges['follows'].data['h2'] = F.tensor([[2.], [3.]])
    g2.edges['plays'].data['h1'] = F.tensor([[0.], [1.]])

270
271
272
273
274
275
276
277
278
279
280
    bg = dgl.batch([g1, g2])

    # Test number of nodes
    for ntype in bg.ntypes:
        assert F.asnumpy(bg.batch_num_nodes(ntype)).tolist() == [
            g1.number_of_nodes(ntype), g2.number_of_nodes(ntype)]

    # Test number of edges
    for etype in bg.canonical_etypes:
        assert F.asnumpy(bg.batch_num_edges(etype)).tolist() == [
            g1.number_of_edges(etype), g2.number_of_edges(etype)]
281

282
    # Test features
283
284
285
286
287
288
289
290
    assert F.allclose(bg.nodes['user'].data['h1'],
                      F.cat([g1.nodes['user'].data['h1'], g2.nodes['user'].data['h1']], dim=0))
    assert F.allclose(bg.nodes['user'].data['h2'],
                      F.cat([g1.nodes['user'].data['h2'], g2.nodes['user'].data['h2']], dim=0))
    assert F.allclose(bg.nodes['game'].data['h1'], g2.nodes['game'].data['h1'])
    assert F.allclose(bg.nodes['game'].data['h2'], g2.nodes['game'].data['h2'])
    assert F.allclose(bg.edges['follows'].data['h1'],
                      F.cat([g1.edges['follows'].data['h1'], g2.edges['follows'].data['h1']], dim=0))
Jinjing Zhou's avatar
Jinjing Zhou committed
291
292
    assert F.allclose(bg.edges['plays'].data['h1'],
                      g2.edges['plays'].data['h1'])
293
294

    # Test unbatching graphs
295
    g3, g4 = dgl.unbatch(bg)
296
297
298
299
300
301
302
303
304
305
    check_equivalence_between_heterographs(
        g1, g3,
        node_attrs={'user': ['h1', 'h2'], 'game': ['h1', 'h2']},
        edge_attrs={('user', 'follows', 'user'): ['h1']})
    check_equivalence_between_heterographs(
        g2, g4,
        node_attrs={'user': ['h1', 'h2'], 'game': ['h1', 'h2']},
        edge_attrs={('user', 'follows', 'user'): ['h1']})

    # Test graphs without edges
306
307
    g1 = dgl.heterograph({('u', 'r', 'v'): ([], [])}, {'u': 0, 'v': 4})
    g2 = dgl.heterograph({('u', 'r', 'v'): ([], [])}, {'u': 1, 'v': 5})
308
    dgl.batch([g1, g2])
309

Jinjing Zhou's avatar
Jinjing Zhou committed
310

Mufei Li's avatar
Mufei Li committed
311
@parametrize_dtype
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def test_unbatch2(idtype):
    # batch 3 graphs but unbatch to 2
    g1 = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
    g2 = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
    g3 = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
    bg = dgl.batch([g1, g2, g3])
    bnn = F.tensor([8, 4])
    bne = F.tensor([6, 3])
    f1, f2 = dgl.unbatch(bg, node_split=bnn, edge_split=bne)
    u, v = f1.edges(order='eid')
    assert F.allclose(u, F.tensor([0, 1, 2, 4, 5, 6]))
    assert F.allclose(v, F.tensor([1, 2, 3, 5, 6, 7]))
    u, v = f2.edges(order='eid')
    assert F.allclose(u, F.tensor([0, 1, 2]))
    assert F.allclose(v, F.tensor([1, 2, 3]))

    # batch 2 but unbatch to 3
    bg = dgl.batch([f1, f2])
    gg1, gg2, gg3 = dgl.unbatch(bg, F.tensor([4, 4, 4]), F.tensor([3, 3, 3]))
    check_graph_equal(g1, gg1)
    check_graph_equal(g2, gg2)
    check_graph_equal(g3, gg3)
334

Jinjing Zhou's avatar
Jinjing Zhou committed
335

336
337
@parametrize_dtype
def test_batch_keeps_empty_data(idtype):
Jinjing Zhou's avatar
Jinjing Zhou committed
338
339
    g1 = dgl.heterograph({("a", "to", "a"): ([], [])}
                         ).astype(idtype).to(F.ctx())
340
    g1.nodes["a"].data["nh"] = F.tensor([])
Jinjing Zhou's avatar
Jinjing Zhou committed
341
342
343
    g1.edges[("a", "to", "a")].data["eh"] = F.tensor([])
    g2 = dgl.heterograph({("a", "to", "a"): ([], [])}
                         ).astype(idtype).to(F.ctx())
344
    g2.nodes["a"].data["nh"] = F.tensor([])
Jinjing Zhou's avatar
Jinjing Zhou committed
345
    g2.edges[("a", "to", "a")].data["eh"] = F.tensor([])
346
347
348
349
    g = dgl.batch([g1, g2])
    assert "nh" in g.nodes["a"].data
    assert "eh" in g.edges[("a", "to", "a")].data

Jinjing Zhou's avatar
Jinjing Zhou committed
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

@unittest.skipIf(F._default_context_str == 'gpu', reason="Issue is not related with GPU")
def test_batch_netypes():
    # Test for https://github.com/dmlc/dgl/issues/2808
    import networkx as nx
    B = nx.DiGraph()
    B.add_nodes_from([1, 2, 3, 4], bipartite=0,
                     some_attr=F.tensor([1, 2, 3, 4], dtype=F.float32))
    B.add_nodes_from(["a", "b", "c"], bipartite=1)
    B.add_edges_from([(1, "a"), (1, "b"), (2, "b"),
                      (2, "c"), (3, "c"), (4, "a")])

    g_dict = {0: dgl.bipartite_from_networkx(B, 'A', 'e', 'B'),
              1: dgl.bipartite_from_networkx(B, 'B', 'e', 'A'),
              2: dgl.bipartite_from_networkx(B, 'A', 'e', 'B', u_attrs=['some_attr']),
              3: dgl.bipartite_from_networkx(B, 'B', 'e', 'A', u_attrs=['some_attr'])
              }
    for _, g in g_dict.items():
        dgl.batch((g, g, g))


371
if __name__ == '__main__':
Jinjing Zhou's avatar
Jinjing Zhou committed
372
373
374
    # test_topology('int32')
    # test_batching_batched('int32')
    # test_batched_features('int32')
375
    # test_empty_relation('int64')
Jinjing Zhou's avatar
Jinjing Zhou committed
376
    # test_to_device('int32')
377
    pass