test_basics.py 24.1 KB
Newer Older
1
import backend as F
2
import dgl
3
import networkx as nx
4
from dgl import DGLGraph
5
from collections import defaultdict as ddict
6
7
8
9

D = 5
reduce_msg_shapes = set()

10
def message_func(edges):
11
12
    assert F.ndim(edges.src['h']) == 2
    assert F.shape(edges.src['h'])[1] == D
13
    return {'m' : edges.src['h']}
14

15
16
def reduce_func(nodes):
    msgs = nodes.mailbox['m']
Minjie Wang's avatar
Minjie Wang committed
17
    reduce_msg_shapes.add(tuple(msgs.shape))
18
19
20
    assert F.ndim(msgs) == 3
    assert F.shape(msgs)[2] == D
    return {'accum' : F.sum(msgs, 1)}
Minjie Wang's avatar
Minjie Wang committed
21

22
23
def apply_node_func(nodes):
    return {'h' : nodes.data['h'] + nodes.data['accum']}
Minjie Wang's avatar
Minjie Wang committed
24

25
def generate_graph(grad=False):
26
    g = DGLGraph()
27
    g.add_nodes(10) # 10 nodes
28
    # create a graph where 0 is the source and 9 is the sink
Minjie Wang's avatar
Minjie Wang committed
29
    # 17 edges
30
31
32
33
34
    for i in range(1, 9):
        g.add_edge(0, i)
        g.add_edge(i, 9)
    # add a back flow from 9 to 0
    g.add_edge(9, 0)
35
36
37
38
39
40
    ncol = F.randn((10, D))
    ecol = F.randn((17, D))
    if grad:
        ncol = F.attach_grad(ncol)
        ecol = F.attach_grad(ecol)

41
42
    g.ndata['h'] = ncol
    g.edata['w'] = ecol
43
44
    g.set_n_initializer(dgl.init.zero_initializer)
    g.set_e_initializer(dgl.init.zero_initializer)
45
46
47
48
    return g

def test_batch_setter_getter():
    def _pfc(x):
49
        return list(F.zerocopy_to_numpy(x)[:,0])
50
51
    g = generate_graph()
    # set all nodes
52
53
    g.ndata['h'] = F.zeros((10, D))
    assert F.allclose(g.ndata['h'], F.zeros((10, D)))
Minjie Wang's avatar
Minjie Wang committed
54
    # pop nodes
55
    old_len = len(g.ndata)
Minjie Wang's avatar
Minjie Wang committed
56
    assert _pfc(g.pop_n_repr('h')) == [0.] * 10
57
    assert len(g.ndata) == old_len - 1
58
    g.ndata['h'] = F.zeros((10, D))
59
    # set partial nodes
60
61
    u = F.tensor([1, 3, 5])
    g.nodes[u].data['h'] = F.ones((3, D))
62
    assert _pfc(g.ndata['h']) == [0., 1., 0., 1., 0., 1., 0., 0., 0., 0.]
63
    # get partial nodes
64
    u = F.tensor([1, 2, 3])
65
    assert _pfc(g.nodes[u].data['h']) == [1., 0., 1.]
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

    '''
    s, d, eid
    0, 1, 0
    1, 9, 1
    0, 2, 2
    2, 9, 3
    0, 3, 4
    3, 9, 5
    0, 4, 6
    4, 9, 7
    0, 5, 8
    5, 9, 9
    0, 6, 10
    6, 9, 11
    0, 7, 12
    7, 9, 13
    0, 8, 14
    8, 9, 15
    9, 0, 16
    '''
    # set all edges
88
    g.edata['l'] = F.zeros((17, D))
89
    assert _pfc(g.edata['l']) == [0.] * 17
Minjie Wang's avatar
Minjie Wang committed
90
    # pop edges
91
    old_len = len(g.edata)
Minjie Wang's avatar
Minjie Wang committed
92
    assert _pfc(g.pop_e_repr('l')) == [0.] * 17
93
    assert len(g.edata) == old_len - 1
94
    g.edata['l'] = F.zeros((17, D))
Minjie Wang's avatar
Minjie Wang committed
95
    # set partial edges (many-many)
96
97
98
    u = F.tensor([0, 0, 2, 5, 9])
    v = F.tensor([1, 3, 9, 9, 0])
    g.edges[u, v].data['l'] = F.ones((5, D))
Minjie Wang's avatar
Minjie Wang committed
99
100
    truth = [0.] * 17
    truth[0] = truth[4] = truth[3] = truth[9] = truth[16] = 1.
101
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
102
    # set partial edges (many-one)
103
104
105
    u = F.tensor([3, 4, 6])
    v = F.tensor([9])
    g.edges[u, v].data['l'] = F.ones((3, D))
Minjie Wang's avatar
Minjie Wang committed
106
    truth[5] = truth[7] = truth[11] = 1.
107
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
108
    # set partial edges (one-many)
109
110
111
    u = F.tensor([0])
    v = F.tensor([4, 5, 6])
    g.edges[u, v].data['l'] = F.ones((3, D))
Minjie Wang's avatar
Minjie Wang committed
112
    truth[6] = truth[8] = truth[10] = 1.
113
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
114
    # get partial edges (many-many)
115
116
    u = F.tensor([0, 6, 0])
    v = F.tensor([6, 9, 7])
117
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 0.]
Minjie Wang's avatar
Minjie Wang committed
118
    # get partial edges (many-one)
119
120
    u = F.tensor([5, 6, 7])
    v = F.tensor([9])
121
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 0.]
Minjie Wang's avatar
Minjie Wang committed
122
    # get partial edges (one-many)
123
124
    u = F.tensor([0])
    v = F.tensor([3, 4, 5])
125
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 1.]
126

127
128
def test_batch_setter_autograd():
    g = generate_graph(grad=True)
129
    h1 = g.ndata['h']
130
    # partial set
131
132
133
134
135
136
137
138
    v = F.tensor([1, 2, 8])
    hh = F.attach_grad(F.zeros((len(v), D)))
    with F.record_grad():
        g.nodes[v].data['h'] = hh
        h2 = g.ndata['h']
    F.backward(h2, F.ones((10, D)) * 2)
    assert F.array_equal(F.grad(h1)[:,0], F.tensor([2., 0., 0., 2., 2., 2., 2., 2., 0., 2.]))
    assert F.array_equal(F.grad(hh)[:,0], F.tensor([2., 2., 2.]))
139

140
141
142
143
def test_nx_conversion():
    # check conversion between networkx and DGLGraph

    def _check_nx_feature(nxg, nf, ef):
Minjie Wang's avatar
Minjie Wang committed
144
145
        # check node and edge feature of nxg
        # this is used to check to_networkx
146
147
148
149
150
151
152
        num_nodes = len(nxg)
        num_edges = nxg.size()
        if num_nodes > 0:
            node_feat = ddict(list)
            for nid, attr in nxg.nodes(data=True):
                assert len(attr) == len(nf)
                for k in nxg.nodes[nid]:
153
                    node_feat[k].append(F.unsqueeze(attr[k], 0))
154
            for k in node_feat:
155
156
                feat = F.cat(node_feat[k], 0)
                assert F.allclose(feat, nf[k])
157
158
159
160
161
162
163
164
        else:
            assert len(nf) == 0
        if num_edges > 0:
            edge_feat = ddict(lambda: [0] * num_edges)
            for u, v, attr in nxg.edges(data=True):
                assert len(attr) == len(ef) + 1 # extra id
                eid = attr['id']
                for k in ef:
165
                    edge_feat[k][eid] = F.unsqueeze(attr[k], 0)
166
            for k in edge_feat:
167
168
                feat = F.cat(edge_feat[k], 0)
                assert F.allclose(feat, ef[k])
169
170
171
        else:
            assert len(ef) == 0

172
173
174
175
176
    n1 = F.randn((5, 3))
    n2 = F.randn((5, 10))
    n3 = F.randn((5, 4))
    e1 = F.randn((4, 5))
    e2 = F.randn((4, 7))
177
178
179
180
181
182
183
184
185
186
187
188
    g = DGLGraph(multigraph=True)
    g.add_nodes(5)
    g.add_edges([0,1,3,4], [2,4,0,3])
    g.ndata.update({'n1': n1, 'n2': n2, 'n3': n3})
    g.edata.update({'e1': e1, 'e2': e2})

    # convert to networkx
    nxg = g.to_networkx(node_attrs=['n1', 'n3'], edge_attrs=['e1', 'e2'])
    assert len(nxg) == 5
    assert nxg.size() == 4
    _check_nx_feature(nxg, {'n1': n1, 'n3': n3}, {'e1': e1, 'e2': e2})

Minjie Wang's avatar
Minjie Wang committed
189
    # convert to DGLGraph, nx graph has id in edge feature
190
191
    # use id feature to test non-tensor copy
    g.from_networkx(nxg, node_attrs=['n1'], edge_attrs=['e1', 'id'])
Minjie Wang's avatar
Minjie Wang committed
192
    # check graph size
193
194
    assert g.number_of_nodes() == 5
    assert g.number_of_edges() == 4
Minjie Wang's avatar
Minjie Wang committed
195
196
197
198
199
    # check number of features
    # test with existing dglgraph (so existing features should be cleared)
    assert len(g.ndata) == 1
    assert len(g.edata) == 2
    # check feature values
200
    assert F.allclose(g.ndata['n1'], n1)
Minjie Wang's avatar
Minjie Wang committed
201
    # with id in nx edge feature, e1 should follow original order
202
    assert F.allclose(g.edata['e1'], e1)
203
    assert F.array_equal(g.get_e_repr()['id'], F.copy_to(F.arange(0, 4), F.cpu()))
204

Minjie Wang's avatar
Minjie Wang committed
205
206
    # test conversion after modifying DGLGraph
    g.pop_e_repr('id') # pop id so we don't need to provide id when adding edges
207
208
    new_n = F.randn((2, 3))
    new_e = F.randn((3, 5))
209
210
211
    g.add_nodes(2, data={'n1': new_n})
    # add three edges, one is a multi-edge
    g.add_edges([3, 6, 0], [4, 5, 2], data={'e1': new_e})
212
213
    n1 = F.cat((n1, new_n), 0)
    e1 = F.cat((e1, new_e), 0)
214
215
216
217
218
219
    # convert to networkx again
    nxg = g.to_networkx(node_attrs=['n1'], edge_attrs=['e1'])
    assert len(nxg) == 7
    assert nxg.size() == 7
    _check_nx_feature(nxg, {'n1': n1}, {'e1': e1})

Minjie Wang's avatar
Minjie Wang committed
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    # now test convert from networkx without id in edge feature
    # first pop id in edge feature
    for _, _, attr in nxg.edges(data=True):
        attr.pop('id')
    # test with a new graph
    g = DGLGraph(multigraph=True)
    g.from_networkx(nxg, node_attrs=['n1'], edge_attrs=['e1'])
    # check graph size
    assert g.number_of_nodes() == 7
    assert g.number_of_edges() == 7
    # check number of features
    assert len(g.ndata) == 1
    assert len(g.edata) == 1
    # check feature values
234
    assert F.allclose(g.ndata['n1'], n1)
Minjie Wang's avatar
Minjie Wang committed
235
236
237
    # edge feature order follows nxg.edges()
    edge_feat = []
    for _, _, attr in nxg.edges(data=True):
238
239
240
        edge_feat.append(F.unsqueeze(attr['e1'], 0))
    edge_feat = F.cat(edge_feat, 0)
    assert F.allclose(g.edata['e1'], edge_feat)
Minjie Wang's avatar
Minjie Wang committed
241

242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
    # Test converting from a networkx graph whose nodes are
    # not labeled with consecutive-integers.
    nxg = nx.cycle_graph(5)
    nxg.remove_nodes_from([0, 4])
    for u in nxg.nodes():
        nxg.node[u]['h'] = F.tensor([u])
    for u, v, d in nxg.edges(data=True):
        d['h'] = F.tensor([u, v])

    g = dgl.DGLGraph()
    g.from_networkx(nxg, node_attrs=['h'], edge_attrs=['h'])
    assert g.number_of_nodes() == 3
    assert g.number_of_edges() == 4
    assert g.has_edge_between(0, 1)
    assert g.has_edge_between(1, 2)
    assert F.allclose(g.ndata['h'], F.tensor([[1.], [2.], [3.]]))
    assert F.allclose(g.edata['h'], F.tensor([[1., 2.], [1., 2.],
                                              [2., 3.], [2., 3.]]))

261
262
def test_batch_send():
    g = generate_graph()
263
    def _fmsg(edges):
264
        assert tuple(F.shape(edges.src['h'])) == (5, D)
265
        return {'m' : edges.src['h']}
Minjie Wang's avatar
Minjie Wang committed
266
    g.register_message_func(_fmsg)
267
    # many-many send
268
269
    u = F.tensor([0, 0, 0, 0, 0])
    v = F.tensor([1, 2, 3, 4, 5])
270
    g.send((u, v))
271
    # one-many send
272
273
    u = F.tensor([0])
    v = F.tensor([1, 2, 3, 4, 5])
274
    g.send((u, v))
275
    # many-one send
276
277
    u = F.tensor([1, 2, 3, 4, 5])
    v = F.tensor([9])
278
    g.send((u, v))
279

280
def test_batch_recv():
Minjie Wang's avatar
Minjie Wang committed
281
    # basic recv test
282
    g = generate_graph()
Minjie Wang's avatar
Minjie Wang committed
283
284
285
    g.register_message_func(message_func)
    g.register_reduce_func(reduce_func)
    g.register_apply_node_func(apply_node_func)
286
287
    u = F.tensor([0, 0, 0, 4, 5, 6])
    v = F.tensor([1, 2, 3, 9, 9, 9])
Minjie Wang's avatar
Minjie Wang committed
288
    reduce_msg_shapes.clear()
289
    g.send((u, v))
290
    g.recv(F.unique(v))
Minjie Wang's avatar
Minjie Wang committed
291
292
293
    assert(reduce_msg_shapes == {(1, 3, D), (3, 1, D)})
    reduce_msg_shapes.clear()

294
295
296
297
298
299
300
def test_apply_nodes():
    def _upd(nodes):
        return {'h' : nodes.data['h'] * 2}
    g = generate_graph()
    g.register_apply_node_func(_upd)
    old = g.ndata['h']
    g.apply_nodes()
301
302
    assert F.allclose(old * 2, g.ndata['h'])
    u = F.tensor([0, 3, 4, 6])
303
    g.apply_nodes(lambda nodes : {'h' : nodes.data['h'] * 0.}, u)
304
    assert F.allclose(F.gather_row(g.ndata['h'], u), F.zeros((4, D)))
305

306
def test_apply_edges():
307
308
309
    def _upd(edges):
        return {'w' : edges.data['w'] * 2}
    g = generate_graph()
310
    g.register_apply_edge_func(_upd)
311
    old = g.edata['w']
312
    g.apply_edges()
313
314
315
    assert F.allclose(old * 2, g.edata['w'])
    u = F.tensor([0, 0, 0, 4, 5, 6])
    v = F.tensor([1, 2, 3, 9, 9, 9])
316
    g.apply_edges(lambda edges : {'w' : edges.data['w'] * 0.}, (u, v))
317
    eid = F.tensor(g.edge_ids(u, v))
318
    assert F.allclose(F.gather_row(g.edata['w'], eid), F.zeros((6, D)))
319

320
321
def test_update_routines():
    g = generate_graph()
Minjie Wang's avatar
Minjie Wang committed
322
323
324
    g.register_message_func(message_func)
    g.register_reduce_func(reduce_func)
    g.register_apply_node_func(apply_node_func)
325

326
    # send_and_recv
327
    reduce_msg_shapes.clear()
328
329
    u = [0, 0, 0, 4, 5, 6]
    v = [1, 2, 3, 9, 9, 9]
330
    g.send_and_recv((u, v))
331
332
    assert(reduce_msg_shapes == {(1, 3, D), (3, 1, D)})
    reduce_msg_shapes.clear()
333
334
335
    try:
        g.send_and_recv([u, v])
        assert False
336
    except:
337
        pass
338

339
    # pull
340
    v = F.tensor([1, 2, 3, 9])
341
    reduce_msg_shapes.clear()
342
    g.pull(v)
343
344
345
    assert(reduce_msg_shapes == {(1, 8, D), (3, 1, D)})
    reduce_msg_shapes.clear()

346
    # push
347
    v = F.tensor([0, 1, 2, 3])
348
    reduce_msg_shapes.clear()
349
    g.push(v)
350
351
352
353
354
355
356
357
358
    assert(reduce_msg_shapes == {(1, 3, D), (8, 1, D)})
    reduce_msg_shapes.clear()

    # update_all
    reduce_msg_shapes.clear()
    g.update_all()
    assert(reduce_msg_shapes == {(1, 8, D), (9, 1, D)})
    reduce_msg_shapes.clear()

359
360
361
362
363
364
365
366
def test_recv_0deg():
    # test recv with 0deg nodes;
    g = DGLGraph()
    g.add_nodes(2)
    g.add_edge(0, 1)
    def _message(edges):
        return {'m' : edges.src['h']}
    def _reduce(nodes):
367
        return {'h' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
368
369
370
    def _apply(nodes):
        return {'h' : nodes.data['h'] * 2}
    def _init2(shape, dtype, ctx, ids):
371
        return 2 + F.zeros(shape, dtype, ctx)
372
373
374
375
376
    g.register_message_func(_message)
    g.register_reduce_func(_reduce)
    g.register_apply_node_func(_apply)
    g.set_n_initializer(_init2, 'h')
    # test#1: recv both 0deg and non-0deg nodes
377
    old = F.randn((2, 5))
378
379
380
381
382
    g.ndata['h'] = old
    g.send((0, 1))
    g.recv([0, 1])
    new = g.ndata.pop('h')
    # 0deg check: initialized with the func and got applied
383
    assert F.allclose(new[0], F.full_1d(5, 4, F.float32))
384
    # non-0deg check
385
    assert F.allclose(new[1], F.sum(old, 0) * 2)
386
387

    # test#2: recv only 0deg node is equal to apply
388
    old = F.randn((2, 5))
389
390
391
392
393
    g.ndata['h'] = old
    g.send((0, 1))
    g.recv(0)
    new = g.ndata.pop('h')
    # 0deg check: equal to apply_nodes
394
    assert F.allclose(new[0], 2 * old[0])
395
    # non-0deg check: untouched
396
    assert F.allclose(new[1], old[1])
397
398
399
400
401
402
403
404
405

def test_recv_0deg_newfld():
    # test recv with 0deg nodes; the reducer also creates a new field
    g = DGLGraph()
    g.add_nodes(2)
    g.add_edge(0, 1)
    def _message(edges):
        return {'m' : edges.src['h']}
    def _reduce(nodes):
406
        return {'h1' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
407
408
409
    def _apply(nodes):
        return {'h1' : nodes.data['h1'] * 2}
    def _init2(shape, dtype, ctx, ids):
410
        return 2 + F.zeros(shape, dtype=dtype, ctx=ctx)
411
412
413
414
    g.register_message_func(_message)
    g.register_reduce_func(_reduce)
    g.register_apply_node_func(_apply)
    # test#1: recv both 0deg and non-0deg nodes
415
    old = F.randn((2, 5))
416
417
418
419
420
421
    g.set_n_initializer(_init2, 'h1')
    g.ndata['h'] = old
    g.send((0, 1))
    g.recv([0, 1])
    new = g.ndata.pop('h1')
    # 0deg check: initialized with the func and got applied
422
    assert F.allclose(new[0], F.full_1d(5, 4, dtype=F.float32))
423
    # non-0deg check
424
    assert F.allclose(new[1], F.sum(old, 0) * 2)
425
426

    # test#2: recv only 0deg node
427
    old = F.randn((2, 5))
428
    g.ndata['h'] = old
429
    g.ndata['h1'] = F.full((2, 5), -1, F.int64)  # this is necessary
430
431
432
433
    g.send((0, 1))
    g.recv(0)
    new = g.ndata.pop('h1')
    # 0deg check: fallback to apply
434
    assert F.allclose(new[0], F.full_1d(5, -2, F.int64))
435
    # non-0deg check: not changed
436
    assert F.allclose(new[1], F.full_1d(5, -1, F.int64))
437
438
439

def test_update_all_0deg():
    # test#1
440
    g = DGLGraph()
Minjie Wang's avatar
Minjie Wang committed
441
    g.add_nodes(5)
442
443
444
445
    g.add_edge(1, 0)
    g.add_edge(2, 0)
    g.add_edge(3, 0)
    g.add_edge(4, 0)
446
447
448
    def _message(edges):
        return {'m' : edges.src['h']}
    def _reduce(nodes):
449
        return {'h' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
450
451
    def _apply(nodes):
        return {'h' : nodes.data['h'] * 2}
452
    def _init2(shape, dtype, ctx, ids):
453
        return 2 + F.zeros(shape, dtype, ctx)
454
    g.set_n_initializer(_init2, 'h')
455
    old_repr = F.randn((5, 5))
456
    g.ndata['h'] = old_repr
457
    g.update_all(_message, _reduce, _apply)
458
    new_repr = g.ndata['h']
459
460
    # the first row of the new_repr should be the sum of all the node
    # features; while the 0-deg nodes should be initialized by the
461
    # initializer and applied with UDF.
462
463
    assert F.allclose(new_repr[1:], 2*(2+F.zeros((4,5))))
    assert F.allclose(new_repr[0], 2 * F.sum(old_repr, 0))
464

465
466
467
468
469
470
471
472
    # test#2: graph with no edge
    g = DGLGraph()
    g.add_nodes(5)
    g.set_n_initializer(_init2, 'h')
    g.ndata['h'] = old_repr
    g.update_all(_message, _reduce, _apply)
    new_repr = g.ndata['h']
    # should fallback to apply
473
    assert F.allclose(new_repr, 2*old_repr)
474

475
def test_pull_0deg():
476
    g = DGLGraph()
Minjie Wang's avatar
Minjie Wang committed
477
    g.add_nodes(2)
478
    g.add_edge(0, 1)
479
480
481
    def _message(edges):
        return {'m' : edges.src['h']}
    def _reduce(nodes):
482
        return {'h' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
483
484
485
    def _apply(nodes):
        return {'h' : nodes.data['h'] * 2}
    def _init2(shape, dtype, ctx, ids):
486
        return 2 + F.zeros(shape, dtype, ctx)
487
488
489
490
491
    g.register_message_func(_message)
    g.register_reduce_func(_reduce)
    g.register_apply_node_func(_apply)
    g.set_n_initializer(_init2, 'h')
    # test#1: pull both 0deg and non-0deg nodes
492
    old = F.randn((2, 5))
493
494
495
496
    g.ndata['h'] = old
    g.pull([0, 1])
    new = g.ndata.pop('h')
    # 0deg check: initialized with the func and got applied
497
    assert F.allclose(new[0], F.full_1d(5, 4, dtype=F.float32))
498
    # non-0deg check
499
    assert F.allclose(new[1], F.sum(old, 0) * 2)
500
501

    # test#2: pull only 0deg node
502
    old = F.randn((2, 5))
503
504
505
506
    g.ndata['h'] = old
    g.pull(0)
    new = g.ndata.pop('h')
    # 0deg check: fallback to apply
507
    assert F.allclose(new[0], 2*old[0])
508
    # non-0deg check: not touched
509
    assert F.allclose(new[1], old[1])
510

511
512
513
514
515
516
517
518
def test_send_multigraph():
    g = DGLGraph(multigraph=True)
    g.add_nodes(3)
    g.add_edge(0, 1)
    g.add_edge(0, 1)
    g.add_edge(0, 1)
    g.add_edge(2, 1)

519
520
521
522
523
    def _message_a(edges):
        return {'a': edges.data['a']}
    def _message_b(edges):
        return {'a': edges.data['a'] * 3}
    def _reduce(nodes):
524
        return {'a': F.max(nodes.mailbox['a'], 1)}
525
526

    def answer(*args):
527
        return F.max(F.stack(args, 0), 0)
528
529

    # send by eid
530
531
    old_repr = F.randn((4, 5))
    g.ndata['a'] = F.zeros((3, 5))
532
533
534
535
    g.edata['a'] = old_repr
    g.send([0, 2], message_func=_message_a)
    g.recv(1, _reduce)
    new_repr = g.ndata['a']
536
    assert F.allclose(new_repr[1], answer(old_repr[0], old_repr[2]))
537

538
    g.ndata['a'] = F.zeros((3, 5))
539
540
541
542
    g.edata['a'] = old_repr
    g.send([0, 2, 3], message_func=_message_a)
    g.recv(1, _reduce)
    new_repr = g.ndata['a']
543
    assert F.allclose(new_repr[1], answer(old_repr[0], old_repr[2], old_repr[3]))
544
545

    # send on multigraph
546
    g.ndata['a'] = F.zeros((3, 5))
547
548
549
550
    g.edata['a'] = old_repr
    g.send(([0, 2], [1, 1]), _message_a)
    g.recv(1, _reduce)
    new_repr = g.ndata['a']
551
    assert F.allclose(new_repr[1], F.max(old_repr, 0))
552
553

    # consecutive send and send_on
554
    g.ndata['a'] = F.zeros((3, 5))
555
556
557
558
559
    g.edata['a'] = old_repr
    g.send((2, 1), _message_a)
    g.send([0, 1], message_func=_message_b)
    g.recv(1, _reduce)
    new_repr = g.ndata['a']
560
    assert F.allclose(new_repr[1], answer(old_repr[0] * 3, old_repr[1] * 3, old_repr[3]))
561
562

    # consecutive send_on
563
    g.ndata['a'] = F.zeros((3, 5))
564
565
566
567
568
    g.edata['a'] = old_repr
    g.send(0, message_func=_message_a)
    g.send(1, message_func=_message_b)
    g.recv(1, _reduce)
    new_repr = g.ndata['a']
569
    assert F.allclose(new_repr[1], answer(old_repr[0], old_repr[1] * 3))
570
571

    # send_and_recv_on
572
    g.ndata['a'] = F.zeros((3, 5))
573
574
575
    g.edata['a'] = old_repr
    g.send_and_recv([0, 2, 3], message_func=_message_a, reduce_func=_reduce)
    new_repr = g.ndata['a']
576
577
    assert F.allclose(new_repr[1], answer(old_repr[0], old_repr[2], old_repr[3]))
    assert F.allclose(new_repr[[0, 2]], F.zeros((2, 5)))
578

579
580
581
582
583
584
585
586
def test_dynamic_addition():
    N = 3
    D = 1

    g = DGLGraph()

    # Test node addition
    g.add_nodes(N)
587
588
    g.ndata.update({'h1': F.randn((N, D)),
                    'h2': F.randn((N, D))})
589
    g.add_nodes(3)
590
    assert g.ndata['h1'].shape[0] == g.ndata['h2'].shape[0] == N + 3
591
592
593
594

    # Test edge addition
    g.add_edge(0, 1)
    g.add_edge(1, 0)
595
596
    g.edata.update({'h1': F.randn((2, D)),
                    'h2': F.randn((2, D))})
597
    assert g.edata['h1'].shape[0] == g.edata['h2'].shape[0] == 2
598
599

    g.add_edges([0, 2], [2, 0])
600
    g.edata['h1'] = F.randn((4, D))
601
    assert g.edata['h1'].shape[0] == g.edata['h2'].shape[0] == 4
602
603

    g.add_edge(1, 2)
604
    g.edges[4].data['h1'] = F.randn((1, D))
605
    assert g.edata['h1'].shape[0] == g.edata['h2'].shape[0] == 5
606

607
    # test add edge with part of the features
608
    g.add_edge(2, 1, {'h1': F.randn((1, D))})
609
610
    assert len(g.edata['h1']) == len(g.edata['h2'])

611

Haibin Lin's avatar
Haibin Lin committed
612
613
614
615
616
def test_repr():
    G = dgl.DGLGraph()
    G.add_nodes(10)
    G.add_edge(0, 1)
    repr_string = G.__repr__()
Minjie Wang's avatar
Minjie Wang committed
617
    print(repr_string)
618
    G.ndata['x'] = F.zeros((10, 5))
Haibin Lin's avatar
Haibin Lin committed
619
    G.add_edges([0, 1], 2)
620
    G.edata['y'] = F.zeros((3, 4))
Haibin Lin's avatar
Haibin Lin committed
621
    repr_string = G.__repr__()
Minjie Wang's avatar
Minjie Wang committed
622
    print(repr_string)
Haibin Lin's avatar
Haibin Lin committed
623

624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645

def test_group_apply_edges():
    def edge_udf(edges):
        h = F.sum(edges.data['feat'] * (edges.src['h'] + edges.dst['h']), dim=2)
        normalized_feat = F.softmax(h, dim=1)
        return {"norm_feat": normalized_feat}

    g = DGLGraph()
    g.add_nodes(10)
    g.add_edges(0, [1, 2, 3, 4, 5, 6, 7, 8])
    g.add_edges(1, [2, 3, 4, 6, 7, 8])
    g.add_edges(2, [2, 3, 4, 5, 6, 7, 8])

    g.ndata['h'] = F.randn((g.number_of_nodes(), D))
    g.edata['feat'] = F.randn((g.number_of_edges(), D))

    def _test(group_by):
        g.group_apply_edges(group_by=group_by, func=edge_udf)
        if group_by == 'src':
            u, v, eid = g.out_edges(1, form='all')
        else:
            u, v, eid = g.in_edges(5, form='all')
646
647
        out_feat = g.edges[eid].data['norm_feat']
        result = (g.nodes[u].data['h'] + g.nodes[v].data['h']) * g.edges[eid].data['feat']
648
649
650
651
652
653
654
655
656
        result = F.softmax(F.sum(result, dim=1), dim=0)
        assert F.allclose(out_feat, result)

    # test group by source nodes
    _test('src')

    # test group by destination nodes
    _test('dst')

657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
def test_local_var():
    g = DGLGraph(nx.path_graph(5))
    g.ndata['h'] = F.zeros((g.number_of_nodes(), 3))
    g.edata['w'] = F.zeros((g.number_of_edges(), 4))
    # test override
    def foo(g):
        g = g.local_var()
        g.ndata['h'] = F.ones((g.number_of_nodes(), 3))
        g.edata['w'] = F.ones((g.number_of_edges(), 4))
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test out-place update
    def foo(g):
        g = g.local_var()
        g.nodes[[2, 3]].data['h'] = F.ones((2, 3))
        g.edges[[2, 3]].data['w'] = F.ones((2, 4))
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test out-place update 2
    def foo(g):
        g = g.local_var()
        g.apply_nodes(lambda nodes: {'h' : nodes.data['h'] + 10}, [2, 3])
        g.apply_edges(lambda edges: {'w' : edges.data['w'] + 10}, [2, 3])
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test auto-pop
    def foo(g):
        g = g.local_var()
        g.ndata['hh'] = F.ones((g.number_of_nodes(), 3))
        g.edata['ww'] = F.ones((g.number_of_edges(), 4))
    foo(g)
    assert 'hh' not in g.ndata
    assert 'ww' not in g.edata

def test_local_scope():
    g = DGLGraph(nx.path_graph(5))
    g.ndata['h'] = F.zeros((g.number_of_nodes(), 3))
    g.edata['w'] = F.zeros((g.number_of_edges(), 4))
    # test override
    def foo(g):
        with g.local_scope():
            g.ndata['h'] = F.ones((g.number_of_nodes(), 3))
            g.edata['w'] = F.ones((g.number_of_edges(), 4))
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test out-place update
    def foo(g):
        with g.local_scope():
            g.nodes[[2, 3]].data['h'] = F.ones((2, 3))
            g.edges[[2, 3]].data['w'] = F.ones((2, 4))
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test out-place update 2
    def foo(g):
        with g.local_scope():
            g.apply_nodes(lambda nodes: {'h' : nodes.data['h'] + 10}, [2, 3])
            g.apply_edges(lambda edges: {'w' : edges.data['w'] + 10}, [2, 3])
    foo(g)
    assert F.allclose(g.ndata['h'], F.zeros((g.number_of_nodes(), 3)))
    assert F.allclose(g.edata['w'], F.zeros((g.number_of_edges(), 4)))
    # test auto-pop
    def foo(g):
        with g.local_scope():
            g.ndata['hh'] = F.ones((g.number_of_nodes(), 3))
            g.edata['ww'] = F.ones((g.number_of_edges(), 4))
    foo(g)
    assert 'hh' not in g.ndata
    assert 'ww' not in g.edata

    # test nested scope
    def foo(g):
        with g.local_scope():
            g.ndata['hh'] = F.ones((g.number_of_nodes(), 3))
            g.edata['ww'] = F.ones((g.number_of_edges(), 4))
            with g.local_scope():
                g.ndata['hhh'] = F.ones((g.number_of_nodes(), 3))
                g.edata['www'] = F.ones((g.number_of_edges(), 4))
            assert 'hhh' not in g.ndata
            assert 'www' not in g.edata
    foo(g)
    assert 'hh' not in g.ndata
    assert 'ww' not in g.edata
744

745
if __name__ == '__main__':
746
    test_nx_conversion()
747
    test_batch_setter_getter()
748
    test_batch_setter_autograd()
749
    test_batch_send()
750
    test_batch_recv()
751
    test_apply_nodes()
752
    test_apply_edges()
753
    test_update_routines()
754
755
756
    test_recv_0deg()
    test_recv_0deg_newfld()
    test_update_all_0deg()
757
    test_pull_0deg()
758
    test_send_multigraph()
759
    test_dynamic_addition()
Haibin Lin's avatar
Haibin Lin committed
760
    test_repr()
761
    test_group_apply_edges()
762
763
    test_local_var()
    test_local_scope()