test_basics.py 23 KB
Newer Older
Andrei Ivanov's avatar
Andrei Ivanov committed
1
import warnings
2
3
from collections import defaultdict as ddict

4
import backend as F
5

6
import dgl
7
import networkx as nx
Quan (Andy) Gan's avatar
Quan (Andy) Gan committed
8
import numpy as np
9
from utils import parametrize_idtype
10
11
12
13

D = 5
reduce_msg_shapes = set()

14

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

20

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

Minjie Wang's avatar
Minjie Wang committed
28

29
def apply_node_func(nodes):
30
31
    return {"h": nodes.data["h"] + nodes.data["accum"]}

Minjie Wang's avatar
Minjie Wang committed
32

33
def generate_graph_old(grad=False):
Andrei Ivanov's avatar
Andrei Ivanov committed
34
    g = dgl.graph([])
35
    g.add_nodes(10)  # 10 nodes
36
    # create a graph where 0 is the source and 9 is the sink
Minjie Wang's avatar
Minjie Wang committed
37
    # 17 edges
38
    for i in range(1, 9):
39
40
        g.add_edges(0, i)
        g.add_edges(i, 9)
41
    # add a back flow from 9 to 0
42
    g.add_edges(9, 0)
43
    g = g.to(F.ctx())
44
45
46
47
48
49
    ncol = F.randn((10, D))
    ecol = F.randn((17, D))
    if grad:
        ncol = F.attach_grad(ncol)
        ecol = F.attach_grad(ecol)

50
51
    g.ndata["h"] = ncol
    g.edata["w"] = ecol
52
53
    g.set_n_initializer(dgl.init.zero_initializer)
    g.set_e_initializer(dgl.init.zero_initializer)
54
55
    return g

56

57
def generate_graph(idtype, grad=False):
58
    """
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
    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
77
    """
78
79
80
81
82
83
84
85
86
87
    u = F.tensor([0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 9])
    v = F.tensor([1, 9, 2, 9, 3, 9, 4, 9, 5, 9, 6, 9, 7, 9, 8, 9, 0])
    g = dgl.graph((u, v), idtype=idtype)
    assert g.device == F.ctx()
    ncol = F.randn((10, D))
    ecol = F.randn((17, D))
    if grad:
        ncol = F.attach_grad(ncol)
        ecol = F.attach_grad(ecol)

88
89
    g.ndata["h"] = ncol
    g.edata["w"] = ecol
90
91
92
93
    g.set_n_initializer(dgl.init.zero_initializer)
    g.set_e_initializer(dgl.init.zero_initializer)
    return g

94

95
96
97
def test_compatible():
    g = generate_graph_old()

98

nv-dlasalle's avatar
nv-dlasalle committed
99
@parametrize_idtype
100
def test_batch_setter_getter(idtype):
101
    def _pfc(x):
102
103
        return list(F.zerocopy_to_numpy(x)[:, 0])

104
    g = generate_graph(idtype)
105
    # set all nodes
106
107
    g.ndata["h"] = F.zeros((10, D))
    assert F.allclose(g.ndata["h"], F.zeros((10, D)))
Minjie Wang's avatar
Minjie Wang committed
108
    # pop nodes
109
    old_len = len(g.ndata)
110
    g.ndata.pop("h")
111
    assert len(g.ndata) == old_len - 1
112
    g.ndata["h"] = F.zeros((10, D))
113
    # set partial nodes
114
    u = F.tensor([1, 3, 5], g.idtype)
115
116
117
118
119
120
121
122
123
124
125
126
127
    g.nodes[u].data["h"] = F.ones((3, D))
    assert _pfc(g.ndata["h"]) == [
        0.0,
        1.0,
        0.0,
        1.0,
        0.0,
        1.0,
        0.0,
        0.0,
        0.0,
        0.0,
    ]
128
    # get partial nodes
129
    u = F.tensor([1, 2, 3], g.idtype)
130
    assert _pfc(g.nodes[u].data["h"]) == [1.0, 0.0, 1.0]
131

132
    """
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
    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
151
    """
152
    # set all edges
153
154
    g.edata["l"] = F.zeros((17, D))
    assert _pfc(g.edata["l"]) == [0.0] * 17
Minjie Wang's avatar
Minjie Wang committed
155
    # pop edges
156
    old_len = len(g.edata)
157
    g.edata.pop("l")
158
    assert len(g.edata) == old_len - 1
159
    g.edata["l"] = F.zeros((17, D))
Minjie Wang's avatar
Minjie Wang committed
160
    # set partial edges (many-many)
161
162
    u = F.tensor([0, 0, 2, 5, 9], g.idtype)
    v = F.tensor([1, 3, 9, 9, 0], g.idtype)
163
164
165
166
    g.edges[u, v].data["l"] = F.ones((5, D))
    truth = [0.0] * 17
    truth[0] = truth[4] = truth[3] = truth[9] = truth[16] = 1.0
    assert _pfc(g.edata["l"]) == truth
167
    u = F.tensor([3, 4, 6], g.idtype)
168
    v = F.tensor([9, 9, 9], g.idtype)
169
170
171
    g.edges[u, v].data["l"] = F.ones((3, D))
    truth[5] = truth[7] = truth[11] = 1.0
    assert _pfc(g.edata["l"]) == truth
172
    u = F.tensor([0, 0, 0], g.idtype)
173
    v = F.tensor([4, 5, 6], g.idtype)
174
175
176
    g.edges[u, v].data["l"] = F.ones((3, D))
    truth[6] = truth[8] = truth[10] = 1.0
    assert _pfc(g.edata["l"]) == truth
177
178
    u = F.tensor([0, 6, 0], g.idtype)
    v = F.tensor([6, 9, 7], g.idtype)
179
180
    assert _pfc(g.edges[u, v].data["l"]) == [1.0, 1.0, 0.0]

181

nv-dlasalle's avatar
nv-dlasalle committed
182
@parametrize_idtype
183
184
def test_batch_setter_autograd(idtype):
    g = generate_graph(idtype, grad=True)
185
    h1 = g.ndata["h"]
186
    # partial set
187
    v = F.tensor([1, 2, 8], g.idtype)
188
189
    hh = F.attach_grad(F.zeros((len(v), D)))
    with F.record_grad():
190
191
        g.nodes[v].data["h"] = hh
        h2 = g.ndata["h"]
VoVAllen's avatar
VoVAllen committed
192
        F.backward(h2, F.ones((10, D)) * 2)
193
194
195
196
197
198
    assert F.array_equal(
        F.grad(h1)[:, 0],
        F.tensor([2.0, 0.0, 0.0, 2.0, 2.0, 2.0, 2.0, 2.0, 0.0, 2.0]),
    )
    assert F.array_equal(F.grad(hh)[:, 0], F.tensor([2.0, 2.0, 2.0]))

199

200
def _test_nx_conversion():
201
202
203
    # check conversion between networkx and DGLGraph

    def _check_nx_feature(nxg, nf, ef):
Minjie Wang's avatar
Minjie Wang committed
204
205
        # check node and edge feature of nxg
        # this is used to check to_networkx
206
207
208
209
210
211
212
        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]:
213
                    node_feat[k].append(F.unsqueeze(attr[k], 0))
214
            for k in node_feat:
215
216
                feat = F.cat(node_feat[k], 0)
                assert F.allclose(feat, nf[k])
217
218
219
220
221
        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):
222
223
                assert len(attr) == len(ef) + 1  # extra id
                eid = attr["id"]
224
                for k in ef:
225
                    edge_feat[k][eid] = F.unsqueeze(attr[k], 0)
226
            for k in edge_feat:
227
228
                feat = F.cat(edge_feat[k], 0)
                assert F.allclose(feat, ef[k])
229
230
231
        else:
            assert len(ef) == 0

232
233
234
235
236
    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))
237
    g = dgl.graph(([0, 1, 3, 4], [2, 4, 0, 3]))
238
239
    g.ndata.update({"n1": n1, "n2": n2, "n3": n3})
    g.edata.update({"e1": e1, "e2": e2})
240
241

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

Minjie Wang's avatar
Minjie Wang committed
247
    # convert to DGLGraph, nx graph has id in edge feature
248
    # use id feature to test non-tensor copy
249
    g = dgl.from_networkx(nxg, node_attrs=["n1"], edge_attrs=["e1", "id"])
Minjie Wang's avatar
Minjie Wang committed
250
    # check graph size
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
251
252
    assert g.num_nodes() == 5
    assert g.num_edges() == 4
Minjie Wang's avatar
Minjie Wang committed
253
254
255
256
257
    # 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
258
    assert F.allclose(g.ndata["n1"], n1)
Minjie Wang's avatar
Minjie Wang committed
259
    # with id in nx edge feature, e1 should follow original order
260
261
262
263
    assert F.allclose(g.edata["e1"], e1)
    assert F.array_equal(
        F.astype(g.edata["id"], F.int64), F.copy_to(F.arange(0, 4), F.cpu())
    )
264

Minjie Wang's avatar
Minjie Wang committed
265
    # test conversion after modifying DGLGraph
266
    g.edata.pop("id")  # pop id so we don't need to provide id when adding edges
267
268
    new_n = F.randn((2, 3))
    new_e = F.randn((3, 5))
269
    g.add_nodes(2, data={"n1": new_n})
270
    # add three edges, one is a multi-edge
271
    g.add_edges([3, 6, 0], [4, 5, 2], data={"e1": new_e})
272
273
    n1 = F.cat((n1, new_n), 0)
    e1 = F.cat((e1, new_e), 0)
274
    # convert to networkx again
275
    nxg = g.to_networkx(node_attrs=["n1"], edge_attrs=["e1"])
276
277
    assert len(nxg) == 7
    assert nxg.size() == 7
278
    _check_nx_feature(nxg, {"n1": n1}, {"e1": e1})
279

Minjie Wang's avatar
Minjie Wang committed
280
281
282
    # now test convert from networkx without id in edge feature
    # first pop id in edge feature
    for _, _, attr in nxg.edges(data=True):
283
        attr.pop("id")
Minjie Wang's avatar
Minjie Wang committed
284
    # test with a new graph
285
    g = dgl.from_networkx(nxg, node_attrs=["n1"], edge_attrs=["e1"])
Minjie Wang's avatar
Minjie Wang committed
286
    # check graph size
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
287
288
    assert g.num_nodes() == 7
    assert g.num_edges() == 7
Minjie Wang's avatar
Minjie Wang committed
289
290
291
292
    # check number of features
    assert len(g.ndata) == 1
    assert len(g.edata) == 1
    # check feature values
293
    assert F.allclose(g.ndata["n1"], n1)
Minjie Wang's avatar
Minjie Wang committed
294
295
296
    # edge feature order follows nxg.edges()
    edge_feat = []
    for _, _, attr in nxg.edges(data=True):
297
        edge_feat.append(F.unsqueeze(attr["e1"], 0))
298
    edge_feat = F.cat(edge_feat, 0)
299
    assert F.allclose(g.edata["e1"], edge_feat)
Minjie Wang's avatar
Minjie Wang committed
300

301
302
303
304
305
    # 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():
306
        nxg.nodes[u]["h"] = F.tensor([u])
307
    for u, v, d in nxg.edges(data=True):
308
        d["h"] = F.tensor([u, v])
309

310
    g = dgl.from_networkx(nxg, node_attrs=["h"], edge_attrs=["h"])
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
311
312
    assert g.num_nodes() == 3
    assert g.num_edges() == 4
313
314
    assert g.has_edge_between(0, 1)
    assert g.has_edge_between(1, 2)
315
316
317
318
319
    assert F.allclose(g.ndata["h"], F.tensor([[1.0], [2.0], [3.0]]))
    assert F.allclose(
        g.edata["h"], F.tensor([[1.0, 2.0], [1.0, 2.0], [2.0, 3.0], [2.0, 3.0]])
    )

320

nv-dlasalle's avatar
nv-dlasalle committed
321
@parametrize_idtype
322
def test_apply_nodes(idtype):
323
    def _upd(nodes):
324
325
        return {"h": nodes.data["h"] * 2}

326
    g = generate_graph(idtype)
327
    old = g.ndata["h"]
328
    g.apply_nodes(_upd)
329
    assert F.allclose(old * 2, g.ndata["h"])
330
    u = F.tensor([0, 3, 4, 6], g.idtype)
331
332
333
    g.apply_nodes(lambda nodes: {"h": nodes.data["h"] * 0.0}, u)
    assert F.allclose(F.gather_row(g.ndata["h"], u), F.zeros((4, D)))

334

nv-dlasalle's avatar
nv-dlasalle committed
335
@parametrize_idtype
336
def test_apply_edges(idtype):
337
    def _upd(edges):
338
339
        return {"w": edges.data["w"] * 2}

340
    g = generate_graph(idtype)
341
    old = g.edata["w"]
342
    g.apply_edges(_upd)
343
    assert F.allclose(old * 2, g.edata["w"])
344
345
    u = F.tensor([0, 0, 0, 4, 5, 6], g.idtype)
    v = F.tensor([1, 2, 3, 9, 9, 9], g.idtype)
346
    g.apply_edges(lambda edges: {"w": edges.data["w"] * 0.0}, (u, v))
347
    eid = F.tensor(g.edge_ids(u, v))
348
349
    assert F.allclose(F.gather_row(g.edata["w"], eid), F.zeros((6, D)))

350

nv-dlasalle's avatar
nv-dlasalle committed
351
@parametrize_idtype
352
353
def test_update_routines(idtype):
    g = generate_graph(idtype)
354

355
    # send_and_recv
356
    reduce_msg_shapes.clear()
357
358
    u = [0, 0, 0, 4, 5, 6]
    v = [1, 2, 3, 9, 9, 9]
359
    g.send_and_recv((u, v), message_func, reduce_func, apply_node_func)
360
    assert reduce_msg_shapes == {(1, 3, D), (3, 1, D)}
361
    reduce_msg_shapes.clear()
362
363
364
    try:
        g.send_and_recv([u, v])
        assert False
365
    except:
366
        pass
367

368
    # pull
369
    v = F.tensor([1, 2, 3, 9], g.idtype)
370
    reduce_msg_shapes.clear()
371
    g.pull(v, message_func, reduce_func, apply_node_func)
372
    assert reduce_msg_shapes == {(1, 8, D), (3, 1, D)}
373
374
    reduce_msg_shapes.clear()

375
    # push
376
    v = F.tensor([0, 1, 2, 3], g.idtype)
377
    reduce_msg_shapes.clear()
378
    g.push(v, message_func, reduce_func, apply_node_func)
379
    assert reduce_msg_shapes == {(1, 3, D), (8, 1, D)}
380
381
382
383
    reduce_msg_shapes.clear()

    # update_all
    reduce_msg_shapes.clear()
384
    g.update_all(message_func, reduce_func, apply_node_func)
385
    assert reduce_msg_shapes == {(1, 8, D), (9, 1, D)}
386
387
    reduce_msg_shapes.clear()

388

nv-dlasalle's avatar
nv-dlasalle committed
389
@parametrize_idtype
390
def test_update_all_0deg(idtype):
391
    # test#1
392
    g = dgl.graph(([1, 2, 3, 4], [0, 0, 0, 0]), idtype=idtype, device=F.ctx())
393

394
    def _message(edges):
395
396
        return {"m": edges.src["h"]}

397
    def _reduce(nodes):
398
399
        return {"x": nodes.data["h"] + F.sum(nodes.mailbox["m"], 1)}

400
    def _apply(nodes):
401
402
        return {"x": nodes.data["x"] * 2}

403
    def _init2(shape, dtype, ctx, ids):
404
        return 2 + F.zeros(shape, dtype, ctx)
405
406

    g.set_n_initializer(_init2, "x")
407
    old_repr = F.randn((5, 5))
408
    g.ndata["h"] = old_repr
409
    g.update_all(_message, _reduce, _apply)
410
    new_repr = g.ndata["x"]
411
412
    # 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
413
    # initializer and applied with UDF.
414
    assert F.allclose(new_repr[1:], 2 * (2 + F.zeros((4, 5))))
415
    assert F.allclose(new_repr[0], 2 * F.sum(old_repr, 0))
416

417
    # test#2: graph with no edge
418
    g = dgl.graph(([], []), num_nodes=5, idtype=idtype, device=F.ctx())
419
    g.ndata["h"] = old_repr
Andrei Ivanov's avatar
Andrei Ivanov committed
420
421
422
423
424
425
426
427
    # Intercepting the warning: The input graph for the user-defined edge
    # function does not contain valid edges.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=UserWarning)
        g.update_all(
            _message, _reduce, lambda nodes: {"h": nodes.data["h"] * 2}
        )

428
    new_repr = g.ndata["h"]
429
    # should fallback to apply
430
431
    assert F.allclose(new_repr, 2 * old_repr)

432

nv-dlasalle's avatar
nv-dlasalle committed
433
@parametrize_idtype
434
def test_pull_0deg(idtype):
435
    g = dgl.graph(([0], [1]), idtype=idtype, device=F.ctx())
436

437
    def _message(edges):
438
439
        return {"m": edges.src["h"]}

440
    def _reduce(nodes):
441
442
        return {"x": nodes.data["h"] + F.sum(nodes.mailbox["m"], 1)}

443
    def _apply(nodes):
444
445
        return {"x": nodes.data["x"] * 2}

446
    def _init2(shape, dtype, ctx, ids):
447
        return 2 + F.zeros(shape, dtype, ctx)
448
449

    g.set_n_initializer(_init2, "x")
450
    # test#1: pull both 0deg and non-0deg nodes
451
    old = F.randn((2, 5))
452
    g.ndata["h"] = old
453
    g.pull([0, 1], _message, _reduce, _apply)
454
    new = g.ndata["x"]
455
    # 0deg check: initialized with the func and got applied
456
    assert F.allclose(new[0], F.full_1d(5, 4, dtype=F.float32))
457
    # non-0deg check
458
    assert F.allclose(new[1], F.sum(old, 0) * 2)
459
460

    # test#2: pull only 0deg node
461
    old = F.randn((2, 5))
462
    g.ndata["h"] = old
Andrei Ivanov's avatar
Andrei Ivanov committed
463
464
465
466
467
468
    # Intercepting the warning: The input graph for the user-defined edge
    # function does not contain valid edges
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", category=UserWarning)
        g.pull(0, _message, _reduce, lambda nodes: {"h": nodes.data["h"] * 2})

469
    new = g.ndata["h"]
470
    # 0deg check: fallback to apply
471
    assert F.allclose(new[0], 2 * old[0])
472
    # non-0deg check: not touched
473
    assert F.allclose(new[1], old[1])
474

475

476
477
478
479
def test_dynamic_addition():
    N = 3
    D = 1

Andrei Ivanov's avatar
Andrei Ivanov committed
480
    g = dgl.graph([]).to(F.ctx())
481
482
483

    # Test node addition
    g.add_nodes(N)
484
    g.ndata.update({"h1": F.randn((N, D)), "h2": F.randn((N, D))})
485
    g.add_nodes(3)
486
    assert g.ndata["h1"].shape[0] == g.ndata["h2"].shape[0] == N + 3
487
488

    # Test edge addition
489
490
    g.add_edges(0, 1)
    g.add_edges(1, 0)
491
492
    g.edata.update({"h1": F.randn((2, D)), "h2": F.randn((2, D))})
    assert g.edata["h1"].shape[0] == g.edata["h2"].shape[0] == 2
493
494

    g.add_edges([0, 2], [2, 0])
495
496
    g.edata["h1"] = F.randn((4, D))
    assert g.edata["h1"].shape[0] == g.edata["h2"].shape[0] == 4
497

498
    g.add_edges(1, 2)
499
500
    g.edges[4].data["h1"] = F.randn((1, D))
    assert g.edata["h1"].shape[0] == g.edata["h2"].shape[0] == 5
501

502
    # test add edge with part of the features
503
504
    g.add_edges(2, 1, {"h1": F.randn((1, D))})
    assert len(g.edata["h1"]) == len(g.edata["h2"])
505

506

nv-dlasalle's avatar
nv-dlasalle committed
507
@parametrize_idtype
508
def test_repr(idtype):
509
510
511
    g = dgl.graph(
        ([0, 0, 1], [1, 2, 2]), num_nodes=10, idtype=idtype, device=F.ctx()
    )
512
    repr_string = g.__repr__()
Minjie Wang's avatar
Minjie Wang committed
513
    print(repr_string)
514
515
    g.ndata["x"] = F.zeros((10, 5))
    g.edata["y"] = F.zeros((3, 4))
516
    repr_string = g.__repr__()
Minjie Wang's avatar
Minjie Wang committed
517
    print(repr_string)
Haibin Lin's avatar
Haibin Lin committed
518

519

nv-dlasalle's avatar
nv-dlasalle committed
520
@parametrize_idtype
521
def test_local_var(idtype):
522
    g = dgl.graph(([0, 1, 2, 3], [1, 2, 3, 4]), idtype=idtype, device=F.ctx())
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
523
524
    g.ndata["h"] = F.zeros((g.num_nodes(), 3))
    g.edata["w"] = F.zeros((g.num_edges(), 4))
525

526
527
528
    # test override
    def foo(g):
        g = g.local_var()
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
529
530
        g.ndata["h"] = F.ones((g.num_nodes(), 3))
        g.edata["w"] = F.ones((g.num_edges(), 4))
531

532
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
533
534
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
535

536
537
538
    # test out-place update
    def foo(g):
        g = g.local_var()
539
540
541
        g.nodes[[2, 3]].data["h"] = F.ones((2, 3))
        g.edges[[2, 3]].data["w"] = F.ones((2, 4))

542
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
543
544
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
545

546
547
548
    # test out-place update 2
    def foo(g):
        g = g.local_var()
549
550
551
        g.apply_nodes(lambda nodes: {"h": nodes.data["h"] + 10}, [2, 3])
        g.apply_edges(lambda edges: {"w": edges.data["w"] + 10}, [2, 3])

552
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
553
554
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
555

556
557
558
    # test auto-pop
    def foo(g):
        g = g.local_var()
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
559
560
        g.ndata["hh"] = F.ones((g.num_nodes(), 3))
        g.edata["ww"] = F.ones((g.num_edges(), 4))
561

562
    foo(g)
563
564
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
565

Mufei Li's avatar
Mufei Li committed
566
    # test initializer1
567
    g = dgl.graph(([0, 1], [1, 1]), idtype=idtype, device=F.ctx())
Mufei Li's avatar
Mufei Li committed
568
    g.set_n_initializer(dgl.init.zero_initializer)
569

Mufei Li's avatar
Mufei Li committed
570
571
    def foo(g):
        g = g.local_var()
572
573
574
        g.nodes[0].data["h"] = F.ones((1, 1))
        assert F.allclose(g.ndata["h"], F.tensor([[1.0], [0.0]]))

Mufei Li's avatar
Mufei Li committed
575
    foo(g)
576

Mufei Li's avatar
Mufei Li committed
577
578
579
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
580
581
582

    g.set_e_initializer(foo_e_initializer, field="h")

Mufei Li's avatar
Mufei Li committed
583
584
    def foo(g):
        g = g.local_var()
585
586
587
588
589
        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], [0.0]]))

Mufei Li's avatar
Mufei Li committed
590
591
    foo(g)

592

nv-dlasalle's avatar
nv-dlasalle committed
593
@parametrize_idtype
594
def test_local_scope(idtype):
595
    g = dgl.graph(([0, 1, 2, 3], [1, 2, 3, 4]), idtype=idtype, device=F.ctx())
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
596
597
    g.ndata["h"] = F.zeros((g.num_nodes(), 3))
    g.edata["w"] = F.zeros((g.num_edges(), 4))
598

599
600
601
    # test override
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
602
603
            g.ndata["h"] = F.ones((g.num_nodes(), 3))
            g.edata["w"] = F.ones((g.num_edges(), 4))
604

605
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
606
607
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
608

609
610
611
    # test out-place update
    def foo(g):
        with g.local_scope():
612
613
614
            g.nodes[[2, 3]].data["h"] = F.ones((2, 3))
            g.edges[[2, 3]].data["w"] = F.ones((2, 4))

615
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
616
617
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
618

619
620
621
    # test out-place update 2
    def foo(g):
        with g.local_scope():
622
623
624
            g.apply_nodes(lambda nodes: {"h": nodes.data["h"] + 10}, [2, 3])
            g.apply_edges(lambda edges: {"w": edges.data["w"] + 10}, [2, 3])

625
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
626
627
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
628

629
630
631
    # test auto-pop
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
632
633
            g.ndata["hh"] = F.ones((g.num_nodes(), 3))
            g.edata["ww"] = F.ones((g.num_edges(), 4))
634

635
    foo(g)
636
637
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
638
639
640
641

    # test nested scope
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
642
643
            g.ndata["hh"] = F.ones((g.num_nodes(), 3))
            g.edata["ww"] = F.ones((g.num_edges(), 4))
644
            with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
645
646
                g.ndata["hhh"] = F.ones((g.num_nodes(), 3))
                g.edata["www"] = F.ones((g.num_edges(), 4))
647
648
649
            assert "hhh" not in g.ndata
            assert "www" not in g.edata

650
    foo(g)
651
652
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
653

Mufei Li's avatar
Mufei Li committed
654
    # test initializer1
655
    g = dgl.graph(([0, 1], [1, 1]), idtype=idtype, device=F.ctx())
Mufei Li's avatar
Mufei Li committed
656
    g.set_n_initializer(dgl.init.zero_initializer)
657

Mufei Li's avatar
Mufei Li committed
658
659
    def foo(g):
        with g.local_scope():
660
661
662
            g.nodes[0].data["h"] = F.ones((1, 1))
            assert F.allclose(g.ndata["h"], F.tensor([[1.0], [0.0]]))

Mufei Li's avatar
Mufei Li committed
663
    foo(g)
664

Mufei Li's avatar
Mufei Li committed
665
666
667
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
668
669
670

    g.set_e_initializer(foo_e_initializer, field="h")

Mufei Li's avatar
Mufei Li committed
671
672
    def foo(g):
        with g.local_scope():
673
674
675
676
677
            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], [0.0]]))

Mufei Li's avatar
Mufei Li committed
678
679
    foo(g)

Will's avatar
Will committed
680
681
682
683
684
685
686
687
688
689
690
691
692
    # test exception handling
    def foo(g):
        try:
            with g.local_scope():
                g.ndata["hh"] = F.ones((g.num_nodes(), 1))
                # throw TypeError
                1 + "1"
        except TypeError:
            pass
        assert "hh" not in g.ndata

    foo(g)

693

nv-dlasalle's avatar
nv-dlasalle committed
694
@parametrize_idtype
695
def test_isolated_nodes(idtype):
696
    g = dgl.graph(([0, 1], [1, 2]), num_nodes=5, idtype=idtype, device=F.ctx())
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
697
    assert g.num_nodes() == 5
698

699
700
701
702
703
704
    g = dgl.heterograph(
        {("user", "plays", "game"): ([0, 0, 1], [2, 3, 2])},
        {"user": 5, "game": 7},
        idtype=idtype,
        device=F.ctx(),
    )
705
    assert g.idtype == idtype
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
706
707
    assert g.num_nodes("user") == 5
    assert g.num_nodes("game") == 7
708
709

    # Test backward compatibility
710
711
712
713
714
715
    g = dgl.heterograph(
        {("user", "plays", "game"): ([0, 0, 1], [2, 3, 2])},
        {"user": 5, "game": 7},
        idtype=idtype,
        device=F.ctx(),
    )
716
    assert g.idtype == idtype
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
717
718
    assert g.num_nodes("user") == 5
    assert g.num_nodes("game") == 7
719

720

nv-dlasalle's avatar
nv-dlasalle committed
721
@parametrize_idtype
722
def test_send_multigraph(idtype):
723
    g = dgl.graph(([0, 0, 0, 2], [1, 1, 1, 1]), idtype=idtype, device=F.ctx())
724
725

    def _message_a(edges):
726
727
        return {"a": edges.data["a"]}

728
    def _message_b(edges):
729
730
        return {"a": edges.data["a"] * 3}

731
    def _reduce(nodes):
732
        return {"a": F.max(nodes.mailbox["a"], 1)}
733
734
735
736
737
738
739
740
741

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

    assert g.is_multigraph

    # send by eid
    old_repr = F.randn((4, 5))
    # send_and_recv_on
742
743
    g.ndata["a"] = F.zeros((3, 5))
    g.edata["a"] = old_repr
744
    g.send_and_recv([0, 2, 3], message_func=_message_a, reduce_func=_reduce)
745
746
747
748
    new_repr = g.ndata["a"]
    assert F.allclose(
        new_repr[1], answer(old_repr[0], old_repr[2], old_repr[3])
    )
749
750
    assert F.allclose(new_repr[[0, 2]], F.zeros((2, 5)))

751

nv-dlasalle's avatar
nv-dlasalle committed
752
@parametrize_idtype
753
754
755
756
def test_issue_1088(idtype):
    # This test ensures that message passing on a heterograph with one edge type
    # would not crash (GitHub issue #1088).
    import dgl.function as fn
757
758
759
760
761
762
763

    g = dgl.heterograph(
        {("U", "E", "V"): ([0, 1, 2], [1, 2, 3])}, idtype=idtype, device=F.ctx()
    )
    g.nodes["U"].data["x"] = F.randn((3, 3))
    g.update_all(fn.copy_u("x", "m"), fn.sum("m", "y"))

764

nv-dlasalle's avatar
nv-dlasalle committed
765
@parametrize_idtype
766
767
def test_degree_bucket_edge_ordering(idtype):
    import dgl.function as fn
768

769
770
    g = dgl.graph(
        ([1, 3, 5, 0, 4, 2, 3, 3, 4, 5], [1, 1, 0, 0, 1, 2, 2, 0, 3, 3]),
771
772
773
774
775
        idtype=idtype,
        device=F.ctx(),
    )
    g.edata["eid"] = F.copy_to(F.arange(0, 10), F.ctx())

776
    def reducer(nodes):
777
        eid = F.asnumpy(F.copy_to(nodes.mailbox["eid"], F.cpu()))
778
        assert np.array_equal(eid, np.sort(eid, 1))
779
780
781
782
        return {"n": F.sum(nodes.mailbox["eid"], 1)}

    g.update_all(fn.copy_e("eid", "eid"), reducer)

783

nv-dlasalle's avatar
nv-dlasalle committed
784
@parametrize_idtype
785
786
def test_issue_2484(idtype):
    import dgl.function as fn
787

788
789
    g = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
    x = F.copy_to(F.randn((4,)), F.ctx())
790
791
792
    g.ndata["x"] = x
    g.pull([2, 1], fn.u_add_v("x", "x", "m"), fn.sum("m", "x"))
    y1 = g.ndata["x"]
793

794
795
796
    g.ndata["x"] = x
    g.pull([1, 2], fn.u_add_v("x", "x", "m"), fn.sum("m", "x"))
    y2 = g.ndata["x"]
797
798

    assert F.allclose(y1, y2)