test_basics.py 26.2 KB
Newer Older
1
import backend as F
2
import dgl
Quan (Andy) Gan's avatar
Quan (Andy) Gan committed
3
4
import numpy as np
import scipy.sparse as ssp
5
import networkx as nx
6
from dgl import DGLGraph
7
from collections import defaultdict as ddict
8
9
10
11

D = 5
reduce_msg_shapes = set()

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

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

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

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

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

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

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

VoVAllen's avatar
VoVAllen committed
129

130
131
def test_batch_setter_autograd():
    g = generate_graph(grad=True)
132
    h1 = g.ndata['h']
133
    # partial set
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']
VoVAllen's avatar
VoVAllen committed
139
        F.backward(h2, F.ones((10, D)) * 2)
140
141
    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.]))
142

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

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

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

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

245
246
247
248
249
    # 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():
VoVAllen's avatar
VoVAllen committed
250
        nxg.nodes[u]['h'] = F.tensor([u])
251
252
253
254
255
256
257
258
259
260
261
262
263
    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.]]))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    g = DGLGraph()

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

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

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

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

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

614

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

627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648

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')
649
650
        out_feat = g.edges[eid].data['norm_feat']
        result = (g.nodes[u].data['h'] + g.nodes[v].data['h']) * g.edges[eid].data['feat']
651
652
653
654
655
656
657
658
659
        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')

Quan (Andy) Gan's avatar
Quan (Andy) Gan committed
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681

# GitHub issue #1036
def test_group_apply_edges2():
    m = ssp.random(10, 10, 0.2)
    g = DGLGraph(m, readonly=True)
    g.ndata['deg'] = g.in_degrees()
    g.ndata['id'] = F.arange(0, g.number_of_nodes())
    g.edata['id'] = F.arange(0, g.number_of_edges())

    def apply(edges):
        w = edges.data['id']
        n_nodes, deg = w.shape

        dst = edges.dst['id'][:, 0]
        eid1 = F.asnumpy(g.in_edges(dst, 'eid')).reshape(n_nodes, deg).sort(1)
        eid2 = F.asnumpy(edges.data['id']).sort(1)
        assert np.array_equal(eid1, eid2)
        return {'id2': w}

    g.group_apply_edges('dst', apply, inplace=True)


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
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

Mufei Li's avatar
Mufei Li committed
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
    # test initializer1
    g = DGLGraph()
    g.add_nodes(2)
    g.add_edges([0, 1], [1, 1])
    g.set_n_initializer(dgl.init.zero_initializer)
    def foo(g):
        g = g.local_var()
        g.nodes[0].data['h'] = F.ones((1, 1))
        assert F.allclose(g.ndata['h'], F.tensor([[1.], [0.]]))
    foo(g)
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
    g.set_e_initializer(foo_e_initializer, field='h')
    def foo(g):
        g = g.local_var()
        g.edges[0, 1].data['h'] = F.ones((1, 1))
        assert F.allclose(g.edata['h'], F.ones((2, 1)))
        g.edges[0, 1].data['w'] = F.ones((1, 1))
        assert F.allclose(g.edata['w'], F.tensor([[1.], [0.]]))
    foo(g)

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
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
791

Mufei Li's avatar
Mufei Li committed
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
    # test initializer1
    g = DGLGraph()
    g.add_nodes(2)
    g.add_edges([0, 1], [1, 1])
    g.set_n_initializer(dgl.init.zero_initializer)
    def foo(g):
        with g.local_scope():
            g.nodes[0].data['h'] = F.ones((1, 1))
            assert F.allclose(g.ndata['h'], F.tensor([[1.], [0.]]))
    foo(g)
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
    g.set_e_initializer(foo_e_initializer, field='h')
    def foo(g):
        with g.local_scope():
            g.edges[0, 1].data['h'] = F.ones((1, 1))
            assert F.allclose(g.edata['h'], F.ones((2, 1)))
            g.edges[0, 1].data['w'] = F.ones((1, 1))
            assert F.allclose(g.edata['w'], F.tensor([[1.], [0.]]))
    foo(g)

814
if __name__ == '__main__':
815
    test_nx_conversion()
816
    test_batch_setter_getter()
817
    test_batch_setter_autograd()
818
    test_batch_send()
819
    test_batch_recv()
820
    test_apply_nodes()
821
    test_apply_edges()
822
    test_update_routines()
823
824
825
    test_recv_0deg()
    test_recv_0deg_newfld()
    test_update_all_0deg()
826
    test_pull_0deg()
827
    test_send_multigraph()
828
    test_dynamic_addition()
Haibin Lin's avatar
Haibin Lin committed
829
    test_repr()
830
    test_group_apply_edges()
Quan (Andy) Gan's avatar
Quan (Andy) Gan committed
831
    test_group_apply_edges2()
832
833
    test_local_var()
    test_local_scope()