test_basics.py 20.9 KB
Newer Older
1
2
3
4
5
# Currently readonly graph construction only accepts sparse tensor in MXNet,
# and pytorch doesn't support readonly graph or graph creation from sparse
# tensor.  For now, readonly graph test is postponed until we have better
# readonly graph support.
import backend as F
6
import dgl
7
import networkx as nx
8
from dgl import DGLGraph
9
from collections import defaultdict as ddict
10
11
12
13

D = 5
reduce_msg_shapes = set()

14
def message_func(edges):
15
16
    assert F.ndim(edges.src['h']) == 2
    assert F.shape(edges.src['h'])[1] == D
17
    return {'m' : edges.src['h']}
18

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

26
27
def apply_node_func(nodes):
    return {'h' : nodes.data['h'] + nodes.data['accum']}
Minjie Wang's avatar
Minjie Wang committed
28

29
def generate_graph(grad=False):
30
    g = DGLGraph()
31
    g.add_nodes(10) # 10 nodes
32
    # create a graph where 0 is the source and 9 is the sink
Minjie Wang's avatar
Minjie Wang committed
33
    # 17 edges
34
35
36
37
38
    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)
39
40
41
42
43
44
    ncol = F.randn((10, D))
    ecol = F.randn((17, D))
    if grad:
        ncol = F.attach_grad(ncol)
        ecol = F.attach_grad(ecol)

45
46
    g.ndata['h'] = ncol
    g.edata['w'] = ecol
47
48
    g.set_n_initializer(dgl.init.zero_initializer)
    g.set_e_initializer(dgl.init.zero_initializer)
49
50
51
52
    return g

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

    '''
    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
92
    g.edata['l'] = F.zeros((17, D))
93
    assert _pfc(g.edata['l']) == [0.] * 17
Minjie Wang's avatar
Minjie Wang committed
94
    # pop edges
95
    old_len = len(g.edata)
Minjie Wang's avatar
Minjie Wang committed
96
    assert _pfc(g.pop_e_repr('l')) == [0.] * 17
97
    assert len(g.edata) == old_len - 1
98
    g.edata['l'] = F.zeros((17, D))
Minjie Wang's avatar
Minjie Wang committed
99
    # set partial edges (many-many)
100
101
102
    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
103
104
    truth = [0.] * 17
    truth[0] = truth[4] = truth[3] = truth[9] = truth[16] = 1.
105
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
106
    # set partial edges (many-one)
107
108
109
    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
110
    truth[5] = truth[7] = truth[11] = 1.
111
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
112
    # set partial edges (one-many)
113
114
115
    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
116
    truth[6] = truth[8] = truth[10] = 1.
117
    assert _pfc(g.edata['l']) == truth
Minjie Wang's avatar
Minjie Wang committed
118
    # get partial edges (many-many)
119
120
    u = F.tensor([0, 6, 0])
    v = F.tensor([6, 9, 7])
121
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 0.]
Minjie Wang's avatar
Minjie Wang committed
122
    # get partial edges (many-one)
123
124
    u = F.tensor([5, 6, 7])
    v = F.tensor([9])
125
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 0.]
Minjie Wang's avatar
Minjie Wang committed
126
    # get partial edges (one-many)
127
128
    u = F.tensor([0])
    v = F.tensor([3, 4, 5])
129
    assert _pfc(g.edges[u, v].data['l']) == [1., 1., 1.]
130

131
132
def test_batch_setter_autograd():
    g = generate_graph(grad=True)
133
    h1 = g.ndata['h']
134
    # partial set
135
136
137
138
139
140
141
142
    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.]))
143

144
145
146
147
def test_nx_conversion():
    # check conversion between networkx and DGLGraph

    def _check_nx_feature(nxg, nf, ef):
Minjie Wang's avatar
Minjie Wang committed
148
149
        # check node and edge feature of nxg
        # this is used to check to_networkx
150
151
152
153
154
155
156
        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]:
157
                    node_feat[k].append(F.unsqueeze(attr[k], 0))
158
            for k in node_feat:
159
160
                feat = F.cat(node_feat[k], 0)
                assert F.allclose(feat, nf[k])
161
162
163
164
165
166
167
168
        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:
169
                    edge_feat[k][eid] = F.unsqueeze(attr[k], 0)
170
            for k in edge_feat:
171
172
                feat = F.cat(edge_feat[k], 0)
                assert F.allclose(feat, ef[k])
173
174
175
        else:
            assert len(ef) == 0

176
177
178
179
180
    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))
181
182
183
184
185
186
187
188
189
190
191
192
    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
193
    # convert to DGLGraph, nx graph has id in edge feature
194
195
    # 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
196
    # check graph size
197
198
    assert g.number_of_nodes() == 5
    assert g.number_of_edges() == 4
Minjie Wang's avatar
Minjie Wang committed
199
200
201
202
203
    # 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
204
    assert F.allclose(g.ndata['n1'], n1)
Minjie Wang's avatar
Minjie Wang committed
205
    # with id in nx edge feature, e1 should follow original order
206
207
    assert F.allclose(g.edata['e1'], e1)
    assert F.array_equal(g.get_e_repr()['id'], F.arange(0, 4))
208

Minjie Wang's avatar
Minjie Wang committed
209
210
    # test conversion after modifying DGLGraph
    g.pop_e_repr('id') # pop id so we don't need to provide id when adding edges
211
212
    new_n = F.randn((2, 3))
    new_e = F.randn((3, 5))
213
214
215
    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})
216
217
    n1 = F.cat((n1, new_n), 0)
    e1 = F.cat((e1, new_e), 0)
218
219
220
221
222
223
    # 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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
    # 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
238
    assert F.allclose(g.ndata['n1'], n1)
Minjie Wang's avatar
Minjie Wang committed
239
240
241
    # edge feature order follows nxg.edges()
    edge_feat = []
    for _, _, attr in nxg.edges(data=True):
242
243
244
        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
245

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
    # 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.]]))

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

284
def test_batch_recv():
Minjie Wang's avatar
Minjie Wang committed
285
    # basic recv test
286
    g = generate_graph()
Minjie Wang's avatar
Minjie Wang committed
287
288
289
    g.register_message_func(message_func)
    g.register_reduce_func(reduce_func)
    g.register_apply_node_func(apply_node_func)
290
291
    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
292
    reduce_msg_shapes.clear()
293
    g.send((u, v))
294
    g.recv(F.unique(v))
Minjie Wang's avatar
Minjie Wang committed
295
296
297
    assert(reduce_msg_shapes == {(1, 3, D), (3, 1, D)})
    reduce_msg_shapes.clear()

298
299
300
301
302
303
304
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()
305
306
    assert F.allclose(old * 2, g.ndata['h'])
    u = F.tensor([0, 3, 4, 6])
307
    g.apply_nodes(lambda nodes : {'h' : nodes.data['h'] * 0.}, u)
308
    assert F.allclose(F.gather_row(g.ndata['h'], u), F.zeros((4, D)))
309

310
def test_apply_edges():
311
312
313
    def _upd(edges):
        return {'w' : edges.data['w'] * 2}
    g = generate_graph()
314
    g.register_apply_edge_func(_upd)
315
    old = g.edata['w']
316
    g.apply_edges()
317
318
319
    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])
320
    g.apply_edges(lambda edges : {'w' : edges.data['w'] * 0.}, (u, v))
321
    eid = g.edge_ids(u, v)
322
    assert F.allclose(F.gather_row(g.edata['w'], eid), F.zeros((6, D)))
323

324
325
def test_update_routines():
    g = generate_graph()
Minjie Wang's avatar
Minjie Wang committed
326
327
328
    g.register_message_func(message_func)
    g.register_reduce_func(reduce_func)
    g.register_apply_node_func(apply_node_func)
329

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

343
    # pull
344
    v = F.tensor([1, 2, 3, 9])
345
    reduce_msg_shapes.clear()
346
    g.pull(v)
347
348
349
    assert(reduce_msg_shapes == {(1, 8, D), (3, 1, D)})
    reduce_msg_shapes.clear()

350
    # push
351
    v = F.tensor([0, 1, 2, 3])
352
    reduce_msg_shapes.clear()
353
    g.push(v)
354
355
356
357
358
359
360
361
362
    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()

363
364
365
366
367
368
369
370
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):
371
        return {'h' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
372
373
374
    def _apply(nodes):
        return {'h' : nodes.data['h'] * 2}
    def _init2(shape, dtype, ctx, ids):
375
        return 2 + F.zeros(shape, dtype, ctx)
376
377
378
379
380
    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
381
    old = F.randn((2, 5))
382
383
384
385
386
    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
387
    assert F.allclose(new[0], F.full_1d(5, 4, F.float32))
388
    # non-0deg check
389
    assert F.allclose(new[1], F.sum(old, 0) * 2)
390
391

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

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):
410
        return {'h1' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
411
412
413
    def _apply(nodes):
        return {'h1' : nodes.data['h1'] * 2}
    def _init2(shape, dtype, ctx, ids):
414
        return 2 + F.zeros(shape, dtype=dtype, ctx=ctx)
415
416
417
418
    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
419
    old = F.randn((2, 5))
420
421
422
423
424
425
    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
426
    assert F.allclose(new[0], F.full_1d(5, 4, dtype=F.float32))
427
    # non-0deg check
428
    assert F.allclose(new[1], F.sum(old, 0) * 2)
429
430

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

def test_update_all_0deg():
    # test#1
444
    g = DGLGraph()
Minjie Wang's avatar
Minjie Wang committed
445
    g.add_nodes(5)
446
447
448
449
    g.add_edge(1, 0)
    g.add_edge(2, 0)
    g.add_edge(3, 0)
    g.add_edge(4, 0)
450
451
452
    def _message(edges):
        return {'m' : edges.src['h']}
    def _reduce(nodes):
453
        return {'h' : nodes.data['h'] + F.sum(nodes.mailbox['m'], 1)}
454
455
    def _apply(nodes):
        return {'h' : nodes.data['h'] * 2}
456
    def _init2(shape, dtype, ctx, ids):
457
        return 2 + F.zeros(shape, dtype, ctx)
458
    g.set_n_initializer(_init2, 'h')
459
    old_repr = F.randn((5, 5))
460
    g.ndata['h'] = old_repr
461
    g.update_all(_message, _reduce, _apply)
462
    new_repr = g.ndata['h']
463
464
    # 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
465
    # initializer and applied with UDF.
466
467
    assert F.allclose(new_repr[1:], 2*(2+F.zeros((4,5))))
    assert F.allclose(new_repr[0], 2 * F.sum(old_repr, 0))
468

469
470
471
472
473
474
475
476
    # 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
477
    assert F.allclose(new_repr, 2*old_repr)
478

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

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

515
516
517
518
519
520
521
522
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)

523
524
525
526
527
    def _message_a(edges):
        return {'a': edges.data['a']}
    def _message_b(edges):
        return {'a': edges.data['a'] * 3}
    def _reduce(nodes):
528
        return {'a': F.max(nodes.mailbox['a'], 1)}
529
530

    def answer(*args):
531
        return F.max(F.stack(args, 0), 0)
532
533

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

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

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

    # consecutive send and send_on
558
    g.ndata['a'] = F.zeros((3, 5))
559
560
561
562
563
    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']
564
    assert F.allclose(new_repr[1], answer(old_repr[0] * 3, old_repr[1] * 3, old_repr[3]))
565
566

    # consecutive send_on
567
    g.ndata['a'] = F.zeros((3, 5))
568
569
570
571
572
    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']
573
    assert F.allclose(new_repr[1], answer(old_repr[0], old_repr[1] * 3))
574
575

    # send_and_recv_on
576
    g.ndata['a'] = F.zeros((3, 5))
577
578
579
    g.edata['a'] = old_repr
    g.send_and_recv([0, 2, 3], message_func=_message_a, reduce_func=_reduce)
    new_repr = g.ndata['a']
580
581
    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)))
582

583
584
585
586
587
588
589
590
def test_dynamic_addition():
    N = 3
    D = 1

    g = DGLGraph()

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

    # Test edge addition
    g.add_edge(0, 1)
    g.add_edge(1, 0)
599
600
    g.edata.update({'h1': F.randn((2, D)),
                    'h2': F.randn((2, D))})
601
    assert g.edata['h1'].shape[0] == g.edata['h2'].shape[0] == 2
602
603

    g.add_edges([0, 2], [2, 0])
604
    g.edata['h1'] = F.randn((4, D))
605
    assert g.edata['h1'].shape[0] == g.edata['h2'].shape[0] == 4
606
607

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

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

615

Haibin Lin's avatar
Haibin Lin committed
616
617
618
619
620
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
621
    print(repr_string)
622
    G.ndata['x'] = F.zeros((10, 5))
Haibin Lin's avatar
Haibin Lin committed
623
    G.add_edges([0, 1], 2)
624
    G.edata['y'] = F.zeros((3, 4))
Haibin Lin's avatar
Haibin Lin committed
625
    repr_string = G.__repr__()
Minjie Wang's avatar
Minjie Wang committed
626
    print(repr_string)
Haibin Lin's avatar
Haibin Lin committed
627

628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661

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')
        out_feat = g.edata['norm_feat'][eid]
        result = (g.ndata['h'][u] + g.ndata['h'][v]) * g.edata['feat'][eid]
        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')


662
if __name__ == '__main__':
663
    test_nx_conversion()
664
    test_batch_setter_getter()
665
    test_batch_setter_autograd()
666
    test_batch_send()
667
    test_batch_recv()
668
    test_apply_nodes()
669
    test_apply_edges()
670
    test_update_routines()
671
672
673
    test_recv_0deg()
    test_recv_0deg_newfld()
    test_update_all_0deg()
674
    test_pull_0deg()
675
    test_send_multigraph()
676
    test_dynamic_addition()
Haibin Lin's avatar
Haibin Lin committed
677
    test_repr()
678
    test_group_apply_edges()