test_basics.py 22.3 KB
Newer Older
1
2
3
import unittest
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
9
import numpy as np
import scipy.sparse as ssp
10
from dgl import DGLGraph
11
from utils import parametrize_idtype
12
13
14
15

D = 5
reduce_msg_shapes = set()

16

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

22

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

Minjie Wang's avatar
Minjie Wang committed
30

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

Minjie Wang's avatar
Minjie Wang committed
34

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

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

58

59
def generate_graph(idtype, grad=False):
60
    """
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    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
79
    """
80
81
82
83
84
85
86
87
88
89
    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)

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

96

97
98
99
def test_compatible():
    g = generate_graph_old()

100

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

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

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

183

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

201

202
def _test_nx_conversion():
203
204
205
    # check conversion between networkx and DGLGraph

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

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

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

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

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

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

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

312
    g = dgl.from_networkx(nxg, node_attrs=["h"], edge_attrs=["h"])
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
313
314
    assert g.num_nodes() == 3
    assert g.num_edges() == 4
315
316
    assert g.has_edge_between(0, 1)
    assert g.has_edge_between(1, 2)
317
318
319
320
321
    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]])
    )

322

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

328
    g = generate_graph(idtype)
329
    old = g.ndata["h"]
330
    g.apply_nodes(_upd)
331
    assert F.allclose(old * 2, g.ndata["h"])
332
    u = F.tensor([0, 3, 4, 6], g.idtype)
333
334
335
    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)))

336

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

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

352

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

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

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

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

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

390

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

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

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

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

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

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

419
    # test#2: graph with no edge
420
    g = dgl.graph(([], []), num_nodes=5, idtype=idtype, device=F.ctx())
421
422
423
    g.ndata["h"] = old_repr
    g.update_all(_message, _reduce, lambda nodes: {"h": nodes.data["h"] * 2})
    new_repr = g.ndata["h"]
424
    # should fallback to apply
425
426
    assert F.allclose(new_repr, 2 * old_repr)

427

nv-dlasalle's avatar
nv-dlasalle committed
428
@parametrize_idtype
429
def test_pull_0deg(idtype):
430
    g = dgl.graph(([0], [1]), idtype=idtype, device=F.ctx())
431

432
    def _message(edges):
433
434
        return {"m": edges.src["h"]}

435
    def _reduce(nodes):
436
437
        return {"x": nodes.data["h"] + F.sum(nodes.mailbox["m"], 1)}

438
    def _apply(nodes):
439
440
        return {"x": nodes.data["x"] * 2}

441
    def _init2(shape, dtype, ctx, ids):
442
        return 2 + F.zeros(shape, dtype, ctx)
443
444

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

    # test#2: pull only 0deg node
456
    old = F.randn((2, 5))
457
458
459
    g.ndata["h"] = old
    g.pull(0, _message, _reduce, lambda nodes: {"h": nodes.data["h"] * 2})
    new = g.ndata["h"]
460
    # 0deg check: fallback to apply
461
    assert F.allclose(new[0], 2 * old[0])
462
    # non-0deg check: not touched
463
    assert F.allclose(new[1], old[1])
464

465

466
467
468
469
470
def test_dynamic_addition():
    N = 3
    D = 1

    g = DGLGraph()
471
    g = g.to(F.ctx())
472
473
474

    # Test node addition
    g.add_nodes(N)
475
    g.ndata.update({"h1": F.randn((N, D)), "h2": F.randn((N, D))})
476
    g.add_nodes(3)
477
    assert g.ndata["h1"].shape[0] == g.ndata["h2"].shape[0] == N + 3
478
479

    # Test edge addition
480
481
    g.add_edges(0, 1)
    g.add_edges(1, 0)
482
483
    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
484
485

    g.add_edges([0, 2], [2, 0])
486
487
    g.edata["h1"] = F.randn((4, D))
    assert g.edata["h1"].shape[0] == g.edata["h2"].shape[0] == 4
488

489
    g.add_edges(1, 2)
490
491
    g.edges[4].data["h1"] = F.randn((1, D))
    assert g.edata["h1"].shape[0] == g.edata["h2"].shape[0] == 5
492

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

497

nv-dlasalle's avatar
nv-dlasalle committed
498
@parametrize_idtype
499
def test_repr(idtype):
500
501
502
    g = dgl.graph(
        ([0, 0, 1], [1, 2, 2]), num_nodes=10, idtype=idtype, device=F.ctx()
    )
503
    repr_string = g.__repr__()
Minjie Wang's avatar
Minjie Wang committed
504
    print(repr_string)
505
506
    g.ndata["x"] = F.zeros((10, 5))
    g.edata["y"] = F.zeros((3, 4))
507
    repr_string = g.__repr__()
Minjie Wang's avatar
Minjie Wang committed
508
    print(repr_string)
Haibin Lin's avatar
Haibin Lin committed
509

510

nv-dlasalle's avatar
nv-dlasalle committed
511
@parametrize_idtype
512
def test_local_var(idtype):
513
    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
514
515
    g.ndata["h"] = F.zeros((g.num_nodes(), 3))
    g.edata["w"] = F.zeros((g.num_edges(), 4))
516

517
518
519
    # test override
    def foo(g):
        g = g.local_var()
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
520
521
        g.ndata["h"] = F.ones((g.num_nodes(), 3))
        g.edata["w"] = F.ones((g.num_edges(), 4))
522

523
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
524
525
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
526

527
528
529
    # test out-place update
    def foo(g):
        g = g.local_var()
530
531
532
        g.nodes[[2, 3]].data["h"] = F.ones((2, 3))
        g.edges[[2, 3]].data["w"] = F.ones((2, 4))

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

537
538
539
    # test out-place update 2
    def foo(g):
        g = g.local_var()
540
541
542
        g.apply_nodes(lambda nodes: {"h": nodes.data["h"] + 10}, [2, 3])
        g.apply_edges(lambda edges: {"w": edges.data["w"] + 10}, [2, 3])

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

547
548
549
    # test auto-pop
    def foo(g):
        g = g.local_var()
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
550
551
        g.ndata["hh"] = F.ones((g.num_nodes(), 3))
        g.edata["ww"] = F.ones((g.num_edges(), 4))
552

553
    foo(g)
554
555
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
556

Mufei Li's avatar
Mufei Li committed
557
    # test initializer1
558
    g = dgl.graph(([0, 1], [1, 1]), idtype=idtype, device=F.ctx())
Mufei Li's avatar
Mufei Li committed
559
    g.set_n_initializer(dgl.init.zero_initializer)
560

Mufei Li's avatar
Mufei Li committed
561
562
    def foo(g):
        g = g.local_var()
563
564
565
        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
566
    foo(g)
567

Mufei Li's avatar
Mufei Li committed
568
569
570
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
571
572
573

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

Mufei Li's avatar
Mufei Li committed
574
575
    def foo(g):
        g = g.local_var()
576
577
578
579
580
        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
581
582
    foo(g)

583

nv-dlasalle's avatar
nv-dlasalle committed
584
@parametrize_idtype
585
def test_local_scope(idtype):
586
    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
587
588
    g.ndata["h"] = F.zeros((g.num_nodes(), 3))
    g.edata["w"] = F.zeros((g.num_edges(), 4))
589

590
591
592
    # test override
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
593
594
            g.ndata["h"] = F.ones((g.num_nodes(), 3))
            g.edata["w"] = F.ones((g.num_edges(), 4))
595

596
    foo(g)
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
597
598
    assert F.allclose(g.ndata["h"], F.zeros((g.num_nodes(), 3)))
    assert F.allclose(g.edata["w"], F.zeros((g.num_edges(), 4)))
599

600
601
602
    # test out-place update
    def foo(g):
        with g.local_scope():
603
604
605
            g.nodes[[2, 3]].data["h"] = F.ones((2, 3))
            g.edges[[2, 3]].data["w"] = F.ones((2, 4))

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

610
611
612
    # test out-place update 2
    def foo(g):
        with g.local_scope():
613
614
615
            g.apply_nodes(lambda nodes: {"h": nodes.data["h"] + 10}, [2, 3])
            g.apply_edges(lambda edges: {"w": edges.data["w"] + 10}, [2, 3])

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

620
621
622
    # test auto-pop
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
623
624
            g.ndata["hh"] = F.ones((g.num_nodes(), 3))
            g.edata["ww"] = F.ones((g.num_edges(), 4))
625

626
    foo(g)
627
628
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
629
630
631
632

    # test nested scope
    def foo(g):
        with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
633
634
            g.ndata["hh"] = F.ones((g.num_nodes(), 3))
            g.edata["ww"] = F.ones((g.num_edges(), 4))
635
            with g.local_scope():
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
636
637
                g.ndata["hhh"] = F.ones((g.num_nodes(), 3))
                g.edata["www"] = F.ones((g.num_edges(), 4))
638
639
640
            assert "hhh" not in g.ndata
            assert "www" not in g.edata

641
    foo(g)
642
643
    assert "hh" not in g.ndata
    assert "ww" not in g.edata
644

Mufei Li's avatar
Mufei Li committed
645
    # test initializer1
646
    g = dgl.graph(([0, 1], [1, 1]), idtype=idtype, device=F.ctx())
Mufei Li's avatar
Mufei Li committed
647
    g.set_n_initializer(dgl.init.zero_initializer)
648

Mufei Li's avatar
Mufei Li committed
649
650
    def foo(g):
        with g.local_scope():
651
652
653
            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
654
    foo(g)
655

Mufei Li's avatar
Mufei Li committed
656
657
658
    # test initializer2
    def foo_e_initializer(shape, dtype, ctx, id_range):
        return F.ones(shape)
659
660
661

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

Mufei Li's avatar
Mufei Li committed
662
663
    def foo(g):
        with g.local_scope():
664
665
666
667
668
            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
669
670
    foo(g)

671

nv-dlasalle's avatar
nv-dlasalle committed
672
@parametrize_idtype
673
def test_isolated_nodes(idtype):
674
    g = dgl.graph(([0, 1], [1, 2]), num_nodes=5, idtype=idtype, device=F.ctx())
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
675
    assert g.num_nodes() == 5
676

677
678
679
680
681
682
    g = dgl.heterograph(
        {("user", "plays", "game"): ([0, 0, 1], [2, 3, 2])},
        {"user": 5, "game": 7},
        idtype=idtype,
        device=F.ctx(),
    )
683
    assert g.idtype == idtype
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
684
685
    assert g.num_nodes("user") == 5
    assert g.num_nodes("game") == 7
686
687

    # Test backward compatibility
688
689
690
691
692
693
    g = dgl.heterograph(
        {("user", "plays", "game"): ([0, 0, 1], [2, 3, 2])},
        {"user": 5, "game": 7},
        idtype=idtype,
        device=F.ctx(),
    )
694
    assert g.idtype == idtype
Hongzhi (Steve), Chen's avatar
Hongzhi (Steve), Chen committed
695
696
    assert g.num_nodes("user") == 5
    assert g.num_nodes("game") == 7
697

698

nv-dlasalle's avatar
nv-dlasalle committed
699
@parametrize_idtype
700
def test_send_multigraph(idtype):
701
    g = dgl.graph(([0, 0, 0, 2], [1, 1, 1, 1]), idtype=idtype, device=F.ctx())
702
703

    def _message_a(edges):
704
705
        return {"a": edges.data["a"]}

706
    def _message_b(edges):
707
708
        return {"a": edges.data["a"] * 3}

709
    def _reduce(nodes):
710
        return {"a": F.max(nodes.mailbox["a"], 1)}
711
712
713
714
715
716
717
718
719

    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
720
721
    g.ndata["a"] = F.zeros((3, 5))
    g.edata["a"] = old_repr
722
    g.send_and_recv([0, 2, 3], message_func=_message_a, reduce_func=_reduce)
723
724
725
726
    new_repr = g.ndata["a"]
    assert F.allclose(
        new_repr[1], answer(old_repr[0], old_repr[2], old_repr[3])
    )
727
728
    assert F.allclose(new_repr[[0, 2]], F.zeros((2, 5)))

729

nv-dlasalle's avatar
nv-dlasalle committed
730
@parametrize_idtype
731
732
733
734
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
735
736
737
738
739
740
741

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

742

nv-dlasalle's avatar
nv-dlasalle committed
743
@parametrize_idtype
744
745
def test_degree_bucket_edge_ordering(idtype):
    import dgl.function as fn
746

747
748
    g = dgl.graph(
        ([1, 3, 5, 0, 4, 2, 3, 3, 4, 5], [1, 1, 0, 0, 1, 2, 2, 0, 3, 3]),
749
750
751
752
753
        idtype=idtype,
        device=F.ctx(),
    )
    g.edata["eid"] = F.copy_to(F.arange(0, 10), F.ctx())

754
    def reducer(nodes):
755
        eid = F.asnumpy(F.copy_to(nodes.mailbox["eid"], F.cpu()))
756
        assert np.array_equal(eid, np.sort(eid, 1))
757
758
759
760
        return {"n": F.sum(nodes.mailbox["eid"], 1)}

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

761

nv-dlasalle's avatar
nv-dlasalle committed
762
@parametrize_idtype
763
764
def test_issue_2484(idtype):
    import dgl.function as fn
765

766
767
    g = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
    x = F.copy_to(F.randn((4,)), F.ctx())
768
769
770
    g.ndata["x"] = x
    g.pull([2, 1], fn.u_add_v("x", "x", "m"), fn.sum("m", "x"))
    y1 = g.ndata["x"]
771

772
773
774
    g.ndata["x"] = x
    g.pull([1, 2], fn.u_add_v("x", "x", "m"), fn.sum("m", "x"))
    y2 = g.ndata["x"]
775
776

    assert F.allclose(y1, y2)