graph_index.py 32.8 KB
Newer Older
Minjie Wang's avatar
Minjie Wang committed
1
"""Module for graph index class definition."""
Minjie Wang's avatar
Minjie Wang committed
2
3
from __future__ import absolute_import

4
import ctypes
Minjie Wang's avatar
Minjie Wang committed
5
6
import numpy as np
import networkx as nx
7
import scipy
Minjie Wang's avatar
Minjie Wang committed
8

9
from ._ffi.base import c_array
Minjie Wang's avatar
Minjie Wang committed
10
from ._ffi.function import _init_api
Minjie Wang's avatar
Minjie Wang committed
11
from .base import DGLError
Minjie Wang's avatar
Minjie Wang committed
12
from . import backend as F
Minjie Wang's avatar
Minjie Wang committed
13
from . import utils
Minjie Wang's avatar
Minjie Wang committed
14

15
16
GraphIndexHandle = ctypes.c_void_p

Minjie Wang's avatar
Minjie Wang committed
17
18
19
20
21
class GraphIndex(object):
    """Graph index object.

    Parameters
    ----------
22
23
    handle : GraphIndexHandle
        Handler
Minjie Wang's avatar
Minjie Wang committed
24
    """
25
    def __init__(self, handle=None, multigraph=None, readonly=None):
26
        self._handle = handle
27
28
        self._multigraph = multigraph
        self._readonly = readonly
Minjie Wang's avatar
Minjie Wang committed
29
        self._cache = {}
Minjie Wang's avatar
Minjie Wang committed
30
31

    def __del__(self):
Minjie Wang's avatar
Minjie Wang committed
32
        """Free this graph index object."""
33
34
        if hasattr(self, '_handle'):
            _CAPI_DGLGraphFree(self._handle)
Minjie Wang's avatar
Minjie Wang committed
35

36
37
38
39
    def __getstate__(self):
        src, dst, _ = self.edges()
        n_nodes = self.number_of_nodes()
        multigraph = self.is_multigraph()
40
        readonly = self.is_readonly()
41

42
        return n_nodes, multigraph, readonly, src, dst
43
44
45

    def __setstate__(self, state):
        """The pickle state of GraphIndex is defined as a triplet
46
        (number_of_nodes, multigraph, readonly, src_nodes, dst_nodes)
47
        """
48
        n_nodes, multigraph, readonly, src, dst = state
49

50
51
52
        self._cache = {}
        self._multigraph = multigraph
        self._readonly = readonly
53
        if readonly:
54
            self._init(src, dst, utils.toindex(F.arange(0, len(src))), n_nodes)
55
56
57
58
59
        else:
            self._handle = _CAPI_DGLGraphCreateMutable(multigraph)
            self.clear()
            self.add_nodes(n_nodes)
            self.add_edges(src, dst)
60

61
    def _init(self, src_ids, dst_ids, edge_ids, num_nodes):
62
63
64
        """The actual init function"""
        assert len(src_ids) == len(dst_ids)
        assert len(src_ids) == len(edge_ids)
65
66
67
68
69
70
71
        self._handle = _CAPI_DGLGraphCreate(
            src_ids.todgltensor(),
            dst_ids.todgltensor(),
            edge_ids.todgltensor(),
            self._multigraph,
            int(num_nodes),
            self._readonly)
72

Minjie Wang's avatar
Minjie Wang committed
73
    def add_nodes(self, num):
Minjie Wang's avatar
Minjie Wang committed
74
        """Add nodes.
75

Minjie Wang's avatar
Minjie Wang committed
76
77
78
79
80
        Parameters
        ----------
        num : int
            Number of nodes to be added.
        """
Minjie Wang's avatar
Minjie Wang committed
81
        _CAPI_DGLGraphAddVertices(self._handle, num)
82
        self.clear_cache()
Minjie Wang's avatar
Minjie Wang committed
83
84

    def add_edge(self, u, v):
Minjie Wang's avatar
Minjie Wang committed
85
        """Add one edge.
86

Minjie Wang's avatar
Minjie Wang committed
87
88
89
90
91
92
93
        Parameters
        ----------
        u : int
            The src node.
        v : int
            The dst node.
        """
Minjie Wang's avatar
Minjie Wang committed
94
        _CAPI_DGLGraphAddEdge(self._handle, u, v)
95
        self.clear_cache()
Minjie Wang's avatar
Minjie Wang committed
96
97

    def add_edges(self, u, v):
Minjie Wang's avatar
Minjie Wang committed
98
        """Add many edges.
99

Minjie Wang's avatar
Minjie Wang committed
100
101
102
103
104
105
106
107
108
        Parameters
        ----------
        u : utils.Index
            The src nodes.
        v : utils.Index
            The dst nodes.
        """
        u_array = u.todgltensor()
        v_array = v.todgltensor()
Minjie Wang's avatar
Minjie Wang committed
109
        _CAPI_DGLGraphAddEdges(self._handle, u_array, v_array)
110
        self.clear_cache()
Minjie Wang's avatar
Minjie Wang committed
111
112

    def clear(self):
Minjie Wang's avatar
Minjie Wang committed
113
        """Clear the graph."""
Minjie Wang's avatar
Minjie Wang committed
114
        _CAPI_DGLGraphClear(self._handle)
115
116
117
118
        self.clear_cache()

    def clear_cache(self):
        """Clear the cached graph structures."""
Minjie Wang's avatar
Minjie Wang committed
119
        self._cache.clear()
Minjie Wang's avatar
Minjie Wang committed
120

121
122
123
124
125
126
127
128
    def is_multigraph(self):
        """Return whether the graph is a multigraph

        Returns
        -------
        bool
            True if it is a multigraph, False otherwise.
        """
129
130
131
        if self._multigraph is None:
            self._multigraph = bool(_CAPI_DGLGraphIsMultigraph(self._handle))
        return self._multigraph
132

Da Zheng's avatar
Da Zheng committed
133
134
135
136
137
138
139
140
    def is_readonly(self):
        """Indicate whether the graph index is read-only.

        Returns
        -------
        bool
            True if it is a read-only graph, False otherwise.
        """
141
142
143
        if self._readonly is None:
            self._readonly = bool(_CAPI_DGLGraphIsReadonly(self._handle))
        return self._readonly
Da Zheng's avatar
Da Zheng committed
144

145
146
147
148
149
150
151
152
153
154
155
156
157
    def readonly(self, readonly_state=True):
        """Set the readonly state of graph index in-place.

        Parameters
        ----------
        readonly_state : bool
            New readonly state of current graph index.
        """
        n_nodes, multigraph, _, src, dst = self.__getstate__()
        self.clear_cache()
        state = (n_nodes, multigraph, readonly_state, src, dst)
        self.__setstate__(state)

Minjie Wang's avatar
Minjie Wang committed
158
    def number_of_nodes(self):
Minjie Wang's avatar
Minjie Wang committed
159
160
161
162
163
164
165
        """Return the number of nodes.

        Returns
        -------
        int
            The number of nodes
        """
Minjie Wang's avatar
Minjie Wang committed
166
167
168
        return _CAPI_DGLGraphNumVertices(self._handle)

    def number_of_edges(self):
Minjie Wang's avatar
Minjie Wang committed
169
170
171
172
173
174
175
        """Return the number of edges.

        Returns
        -------
        int
            The number of edges
        """
Minjie Wang's avatar
Minjie Wang committed
176
177
        return _CAPI_DGLGraphNumEdges(self._handle)

Minjie Wang's avatar
Minjie Wang committed
178
179
180
181
182
183
184
185
186
187
188
    def has_node(self, vid):
        """Return true if the node exists.

        Parameters
        ----------
        vid : int
            The nodes

        Returns
        -------
        bool
189
            True if the node exists, False otherwise.
Minjie Wang's avatar
Minjie Wang committed
190
        """
191
        return bool(_CAPI_DGLGraphHasVertex(self._handle, vid))
Minjie Wang's avatar
Minjie Wang committed
192

Minjie Wang's avatar
Minjie Wang committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
    def has_nodes(self, vids):
        """Return true if the nodes exist.

        Parameters
        ----------
        vid : utils.Index
            The nodes

        Returns
        -------
        utils.Index
            0-1 array indicating existence
        """
        vid_array = vids.todgltensor()
Minjie Wang's avatar
Minjie Wang committed
207
        return utils.toindex(_CAPI_DGLGraphHasVertices(self._handle, vid_array))
Minjie Wang's avatar
Minjie Wang committed
208

209
    def has_edge_between(self, u, v):
Minjie Wang's avatar
Minjie Wang committed
210
211
212
213
214
215
216
217
218
219
220
221
        """Return true if the edge exists.

        Parameters
        ----------
        u : int
            The src node.
        v : int
            The dst node.

        Returns
        -------
        bool
222
            True if the edge exists, False otherwise
Minjie Wang's avatar
Minjie Wang committed
223
        """
224
        return bool(_CAPI_DGLGraphHasEdgeBetween(self._handle, int(u), int(v)))
Minjie Wang's avatar
Minjie Wang committed
225

226
    def has_edges_between(self, u, v):
Minjie Wang's avatar
Minjie Wang committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
        """Return true if the edge exists.

        Parameters
        ----------
        u : utils.Index
            The src nodes.
        v : utils.Index
            The dst nodes.

        Returns
        -------
        utils.Index
            0-1 array indicating existence
        """
        u_array = u.todgltensor()
        v_array = v.todgltensor()
243
        return utils.toindex(_CAPI_DGLGraphHasEdgesBetween(self._handle, u_array, v_array))
Minjie Wang's avatar
Minjie Wang committed
244
245

    def predecessors(self, v, radius=1):
Minjie Wang's avatar
Minjie Wang committed
246
247
248
249
250
251
252
253
254
255
256
257
258
259
        """Return the predecessors of the node.

        Parameters
        ----------
        v : int
            The node.
        radius : int, optional
            The radius of the neighborhood.

        Returns
        -------
        utils.Index
            Array of predecessors
        """
Minjie Wang's avatar
Minjie Wang committed
260
        return utils.toindex(_CAPI_DGLGraphPredecessors(self._handle, v, radius))
Minjie Wang's avatar
Minjie Wang committed
261
262

    def successors(self, v, radius=1):
Minjie Wang's avatar
Minjie Wang committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
        """Return the successors of the node.

        Parameters
        ----------
        v : int
            The node.
        radius : int, optional
            The radius of the neighborhood.

        Returns
        -------
        utils.Index
            Array of successors
        """
Minjie Wang's avatar
Minjie Wang committed
277
        return utils.toindex(_CAPI_DGLGraphSuccessors(self._handle, v, radius))
Minjie Wang's avatar
Minjie Wang committed
278
279

    def edge_id(self, u, v):
280
        """Return the id array of all edges between u and v.
Minjie Wang's avatar
Minjie Wang committed
281
282
283
284
285
286
287
288
289
290

        Parameters
        ----------
        u : int
            The src node.
        v : int
            The dst node.

        Returns
        -------
291
292
        utils.Index
            The edge id array.
Minjie Wang's avatar
Minjie Wang committed
293
        """
294
        return utils.toindex(_CAPI_DGLGraphEdgeId(self._handle, int(u), int(v)))
Minjie Wang's avatar
Minjie Wang committed
295
296

    def edge_ids(self, u, v):
297
        """Return a triplet of arrays that contains the edge IDs.
Minjie Wang's avatar
Minjie Wang committed
298
299
300
301
302
303
304
305
306
307
308

        Parameters
        ----------
        u : utils.Index
            The src nodes.
        v : utils.Index
            The dst nodes.

        Returns
        -------
        utils.Index
309
310
311
312
313
            The src nodes.
        utils.Index
            The dst nodes.
        utils.Index
            The edge ids.
Minjie Wang's avatar
Minjie Wang committed
314
315
316
        """
        u_array = u.todgltensor()
        v_array = v.todgltensor()
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
        edge_array = _CAPI_DGLGraphEdgeIds(self._handle, u_array, v_array)

        src = utils.toindex(edge_array(0))
        dst = utils.toindex(edge_array(1))
        eid = utils.toindex(edge_array(2))

        return src, dst, eid

    def find_edges(self, eid):
        """Return a triplet of arrays that contains the edge IDs.

        Parameters
        ----------
        eid : utils.Index
            The edge ids.

        Returns
        -------
        utils.Index
            The src nodes.
        utils.Index
            The dst nodes.
        utils.Index
            The edge ids.
        """
        eid_array = eid.todgltensor()
        edge_array = _CAPI_DGLGraphFindEdges(self._handle, eid_array)

        src = utils.toindex(edge_array(0))
        dst = utils.toindex(edge_array(1))
        eid = utils.toindex(edge_array(2))

        return src, dst, eid
Minjie Wang's avatar
Minjie Wang committed
350

351
    def in_edges(self, v):
Minjie Wang's avatar
Minjie Wang committed
352
353
354
355
356
357
        """Return the in edges of the node(s).

        Parameters
        ----------
        v : utils.Index
            The node(s).
358

Minjie Wang's avatar
Minjie Wang committed
359
360
361
362
363
364
365
366
367
368
369
        Returns
        -------
        utils.Index
            The src nodes.
        utils.Index
            The dst nodes.
        utils.Index
            The edge ids.
        """
        if len(v) == 1:
            edge_array = _CAPI_DGLGraphInEdges_1(self._handle, v[0])
Minjie Wang's avatar
Minjie Wang committed
370
        else:
Minjie Wang's avatar
Minjie Wang committed
371
            v_array = v.todgltensor()
372
            edge_array = _CAPI_DGLGraphInEdges_2(self._handle, v_array)
Minjie Wang's avatar
Minjie Wang committed
373
374
375
        src = utils.toindex(edge_array(0))
        dst = utils.toindex(edge_array(1))
        eid = utils.toindex(edge_array(2))
376
        return src, dst, eid
Minjie Wang's avatar
Minjie Wang committed
377

378
    def out_edges(self, v):
Minjie Wang's avatar
Minjie Wang committed
379
380
381
382
383
384
        """Return the out edges of the node(s).

        Parameters
        ----------
        v : utils.Index
            The node(s).
385

Minjie Wang's avatar
Minjie Wang committed
386
387
388
389
390
391
392
393
394
395
396
        Returns
        -------
        utils.Index
            The src nodes.
        utils.Index
            The dst nodes.
        utils.Index
            The edge ids.
        """
        if len(v) == 1:
            edge_array = _CAPI_DGLGraphOutEdges_1(self._handle, v[0])
Minjie Wang's avatar
Minjie Wang committed
397
        else:
Minjie Wang's avatar
Minjie Wang committed
398
            v_array = v.todgltensor()
399
            edge_array = _CAPI_DGLGraphOutEdges_2(self._handle, v_array)
Minjie Wang's avatar
Minjie Wang committed
400
401
402
        src = utils.toindex(edge_array(0))
        dst = utils.toindex(edge_array(1))
        eid = utils.toindex(edge_array(2))
403
        return src, dst, eid
Minjie Wang's avatar
Minjie Wang committed
404

405
    @utils.cached_member(cache='_cache', prefix='edges')
406
    def edges(self, order=None):
Minjie Wang's avatar
Minjie Wang committed
407
408
409
410
        """Return all the edges

        Parameters
        ----------
411
412
413
414
415
416
        order : string
            The order of the returned edges. Currently support:

            - 'srcdst' : sorted by their src and dst ids.
            - 'eid'    : sorted by edge Ids.
            - None     : the arbitrary order.
417

Minjie Wang's avatar
Minjie Wang committed
418
419
420
421
422
423
424
425
426
        Returns
        -------
        utils.Index
            The src nodes.
        utils.Index
            The dst nodes.
        utils.Index
            The edge ids.
        """
427
        key = 'edges_s%s' % order
428
        if key not in self._cache:
429
430
431
            if order is None:
                order = ""
            edge_array = _CAPI_DGLGraphEdges(self._handle, order)
432
433
434
435
436
            src = utils.toindex(edge_array(0))
            dst = utils.toindex(edge_array(1))
            eid = utils.toindex(edge_array(2))
            self._cache[key] = (src, dst, eid)
        return self._cache[key]
437
438

    def in_degree(self, v):
Minjie Wang's avatar
Minjie Wang committed
439
440
441
442
443
444
445
446
447
448
449
450
        """Return the in degree of the node.

        Parameters
        ----------
        v : int
            The node.

        Returns
        -------
        int
            The in degree.
        """
451
        return _CAPI_DGLGraphInDegree(self._handle, int(v))
Minjie Wang's avatar
Minjie Wang committed
452

453
    def in_degrees(self, v):
Minjie Wang's avatar
Minjie Wang committed
454
455
456
457
458
459
460
461
462
463
464
465
466
        """Return the in degrees of the nodes.

        Parameters
        ----------
        v : utils.Index
            The nodes.

        Returns
        -------
        int
            The in degree array.
        """
        v_array = v.todgltensor()
Minjie Wang's avatar
Minjie Wang committed
467
        return utils.toindex(_CAPI_DGLGraphInDegrees(self._handle, v_array))
Minjie Wang's avatar
Minjie Wang committed
468

469
    def out_degree(self, v):
Minjie Wang's avatar
Minjie Wang committed
470
471
472
473
474
475
476
477
478
479
480
481
        """Return the out degree of the node.

        Parameters
        ----------
        v : int
            The node.

        Returns
        -------
        int
            The out degree.
        """
482
        return _CAPI_DGLGraphOutDegree(self._handle, int(v))
Minjie Wang's avatar
Minjie Wang committed
483

484
    def out_degrees(self, v):
Minjie Wang's avatar
Minjie Wang committed
485
486
487
488
489
490
491
492
493
494
495
496
497
        """Return the out degrees of the nodes.

        Parameters
        ----------
        v : utils.Index
            The nodes.

        Returns
        -------
        int
            The out degree array.
        """
        v_array = v.todgltensor()
Minjie Wang's avatar
Minjie Wang committed
498
        return utils.toindex(_CAPI_DGLGraphOutDegrees(self._handle, v_array))
Minjie Wang's avatar
Minjie Wang committed
499

Minjie Wang's avatar
Minjie Wang committed
500
501
502
503
504
505
506
507
508
509
    def node_subgraph(self, v):
        """Return the induced node subgraph.

        Parameters
        ----------
        v : utils.Index
            The nodes.

        Returns
        -------
510
        SubgraphIndex
Minjie Wang's avatar
Minjie Wang committed
511
512
513
514
515
            The subgraph index.
        """
        v_array = v.todgltensor()
        rst = _CAPI_DGLGraphVertexSubgraph(self._handle, v_array)
        induced_edges = utils.toindex(rst(2))
516
517
        return SubgraphIndex(rst(0), self, v, induced_edges)

518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
    def node_subgraphs(self, vs_arr):
        """Return the induced node subgraphs.

        Parameters
        ----------
        vs_arr : a list of utils.Index
            The nodes.

        Returns
        -------
        a vector of SubgraphIndex
            The subgraph index.
        """
        gis = []
        for v in vs_arr:
            gis.append(self.node_subgraph(v))
        return gis

536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
    def edge_subgraph(self, e):
        """Return the induced edge subgraph.

        Parameters
        ----------
        e : utils.Index
            The edges.

        Returns
        -------
        SubgraphIndex
            The subgraph index.
        """
        e_array = e.todgltensor()
        rst = _CAPI_DGLGraphEdgeSubgraph(self._handle, e_array)
        induced_nodes = utils.toindex(rst(1))
        return SubgraphIndex(rst(0), self, induced_nodes, e)
Minjie Wang's avatar
Minjie Wang committed
553

554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
    @utils.cached_member(cache='_cache', prefix='scipy_adj')
    def adjacency_matrix_scipy(self, transpose, fmt):
        """Return the scipy adjacency matrix representation of this graph.

        By default, a row of returned adjacency matrix represents the destination
        of an edge and the column represents the source.

        When transpose is True, a row represents the source and a column represents
        a destination.

        The elements in the adajency matrix are edge ids.

        Parameters
        ----------
        transpose : bool
            A flag to transpose the returned adjacency matrix.
        fmt : str
            Indicates the format of returned adjacency matrix.

        Returns
        -------
        scipy.sparse.spmatrix
            The scipy representation of adjacency matrix.
        """
        if not isinstance(transpose, bool):
            raise DGLError('Expect bool value for "transpose" arg,'
                           ' but got %s.' % (type(transpose)))
        rst = _CAPI_DGLGraphGetAdj(self._handle, transpose, fmt)
        if fmt == "csr":
            indptr = utils.toindex(rst(0)).tonumpy()
            indices = utils.toindex(rst(1)).tonumpy()
            shuffle = utils.toindex(rst(2)).tonumpy()
            n = self.number_of_nodes()
            return scipy.sparse.csr_matrix((shuffle, indices, indptr), shape=(n, n))
        elif fmt == 'coo':
            idx = utils.toindex(rst(0)).tonumpy()
            n = self.number_of_nodes()
            m = self.number_of_edges()
            row, col = np.reshape(idx, (2, m))
            shuffle = np.arange(0, m)
            return scipy.sparse.coo_matrix((shuffle, (row, col)), shape=(n, n))
        else:
            raise Exception("unknown format")


599
    @utils.cached_member(cache='_cache', prefix='adj')
600
    def adjacency_matrix(self, transpose, ctx):
Minjie Wang's avatar
Minjie Wang committed
601
602
        """Return the adjacency matrix representation of this graph.

603
604
605
606
607
608
609
610
        By default, a row of returned adjacency matrix represents the destination
        of an edge and the column represents the source.

        When transpose is True, a row represents the source and a column represents
        a destination.

        Parameters
        ----------
611
        transpose : bool
brett koonce's avatar
brett koonce committed
612
            A flag to transpose the returned adjacency matrix.
613
614
        ctx : context
            The context of the returned matrix.
615

Minjie Wang's avatar
Minjie Wang committed
616
617
        Returns
        -------
618
619
        SparseTensor
            The adjacency matrix.
620
621
622
        utils.Index
            A index for data shuffling due to sparse format change. Return None
            if shuffle is not required.
Minjie Wang's avatar
Minjie Wang committed
623
        """
624
625
626
        if not isinstance(transpose, bool):
            raise DGLError('Expect bool value for "transpose" arg,'
                           ' but got %s.' % (type(transpose)))
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
        fmt = F.get_preferred_sparse_format()
        rst = _CAPI_DGLGraphGetAdj(self._handle, transpose, fmt)
        if fmt == "csr":
            indptr = F.copy_to(utils.toindex(rst(0)).tousertensor(), ctx)
            indices = F.copy_to(utils.toindex(rst(1)).tousertensor(), ctx)
            shuffle = utils.toindex(rst(2))
            dat = F.ones(indices.shape, dtype=F.float32, ctx=ctx)
            return F.sparse_matrix(dat, ('csr', indices, indptr),
                                   (self.number_of_nodes(), self.number_of_nodes()))[0], shuffle
        elif fmt == "coo":
            ## FIXME(minjie): data type
            idx = F.copy_to(utils.toindex(rst(0)).tousertensor(), ctx)
            m = self.number_of_edges()
            idx = F.reshape(idx, (2, m))
            dat = F.ones((m,), dtype=F.float32, ctx=ctx)
            n = self.number_of_nodes()
            adj, shuffle_idx = F.sparse_matrix(dat, ('coo', idx), (n, n))
            shuffle_idx = utils.toindex(shuffle_idx) if shuffle_idx is not None else None
            return adj, shuffle_idx
646
        else:
647
            raise Exception("unknown format")
648

649
    @utils.cached_member(cache='_cache', prefix='inc')
Minjie Wang's avatar
Minjie Wang committed
650
    def incidence_matrix(self, typestr, ctx):
GaiYu0's avatar
GaiYu0 committed
651
        """Return the incidence matrix representation of this graph.
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669

        An incidence matrix is an n x m sparse matrix, where n is
        the number of nodes and m is the number of edges. Each nnz
        value indicating whether the edge is incident to the node
        or not.

        There are three types of an incidence matrix `I`:
        * "in":
          - I[v, e] = 1 if e is the in-edge of v (or v is the dst node of e);
          - I[v, e] = 0 otherwise.
        * "out":
          - I[v, e] = 1 if e is the out-edge of v (or v is the src node of e);
          - I[v, e] = 0 otherwise.
        * "both":
          - I[v, e] = 1 if e is the in-edge of v;
          - I[v, e] = -1 if e is the out-edge of v;
          - I[v, e] = 0 otherwise (including self-loop).

GaiYu0's avatar
GaiYu0 committed
670
671
        Parameters
        ----------
Minjie Wang's avatar
Minjie Wang committed
672
        typestr : str
673
674
675
            Can be either "in", "out" or "both"
        ctx : context
            The context of returned incidence matrix.
GaiYu0's avatar
GaiYu0 committed
676
677
678

        Returns
        -------
679
680
        SparseTensor
            The incidence matrix.
681
682
683
        utils.Index
            A index for data shuffling due to sparse format change. Return None
            if shuffle is not required.
GaiYu0's avatar
GaiYu0 committed
684
        """
685
        src, dst, eid = self.edges()
686
687
688
689
690
        src = src.tousertensor(ctx)  # the index of the ctx will be cached
        dst = dst.tousertensor(ctx)  # the index of the ctx will be cached
        eid = eid.tousertensor(ctx)  # the index of the ctx will be cached
        n = self.number_of_nodes()
        m = self.number_of_edges()
Minjie Wang's avatar
Minjie Wang committed
691
        if typestr == 'in':
692
693
694
695
696
            row = F.unsqueeze(dst, 0)
            col = F.unsqueeze(eid, 0)
            idx = F.cat([row, col], dim=0)
            # FIXME(minjie): data type
            dat = F.ones((m,), dtype=F.float32, ctx=ctx)
697
            inc, shuffle_idx = F.sparse_matrix(dat, ('coo', idx), (n, m))
Minjie Wang's avatar
Minjie Wang committed
698
        elif typestr == 'out':
699
700
701
702
703
            row = F.unsqueeze(src, 0)
            col = F.unsqueeze(eid, 0)
            idx = F.cat([row, col], dim=0)
            # FIXME(minjie): data type
            dat = F.ones((m,), dtype=F.float32, ctx=ctx)
704
            inc, shuffle_idx = F.sparse_matrix(dat, ('coo', idx), (n, m))
Minjie Wang's avatar
Minjie Wang committed
705
        elif typestr == 'both':
706
707
708
709
710
711
            # first remove entries for self loops
            mask = F.logical_not(F.equal(src, dst))
            src = F.boolean_mask(src, mask)
            dst = F.boolean_mask(dst, mask)
            eid = F.boolean_mask(eid, mask)
            n_entries = F.shape(src)[0]
712
713
714
715
            # create index
            row = F.unsqueeze(F.cat([src, dst], dim=0), 0)
            col = F.unsqueeze(F.cat([eid, eid], dim=0), 0)
            idx = F.cat([row, col], dim=0)
716
            # FIXME(minjie): data type
717
718
            x = -F.ones((n_entries,), dtype=F.float32, ctx=ctx)
            y = F.ones((n_entries,), dtype=F.float32, ctx=ctx)
719
            dat = F.cat([x, y], dim=0)
720
            inc, shuffle_idx = F.sparse_matrix(dat, ('coo', idx), (n, m))
721
        else:
Minjie Wang's avatar
Minjie Wang committed
722
            raise DGLError('Invalid incidence matrix type: %s' % str(typestr))
723
724
        shuffle_idx = utils.toindex(shuffle_idx) if shuffle_idx is not None else None
        return inc, shuffle_idx
GaiYu0's avatar
GaiYu0 committed
725

Minjie Wang's avatar
Minjie Wang committed
726
    def to_networkx(self):
Minjie Wang's avatar
Minjie Wang committed
727
728
        """Convert to networkx graph.

Minjie Wang's avatar
Minjie Wang committed
729
730
        The edge id will be saved as the 'id' edge attribute.

Minjie Wang's avatar
Minjie Wang committed
731
732
733
734
735
        Returns
        -------
        networkx.DiGraph
            The nx graph
        """
Minjie Wang's avatar
Minjie Wang committed
736
        src, dst, eid = self.edges()
737
        ret = nx.MultiDiGraph() if self.is_multigraph() else nx.DiGraph()
738
        ret.add_nodes_from(range(self.number_of_nodes()))
Minjie Wang's avatar
Minjie Wang committed
739
740
        for u, v, e in zip(src, dst, eid):
            ret.add_edge(u, v, id=e)
Minjie Wang's avatar
Minjie Wang committed
741
742
743
744
        return ret

    def from_networkx(self, nx_graph):
        """Convert from networkx graph.
Minjie Wang's avatar
Minjie Wang committed
745

Minjie Wang's avatar
Minjie Wang committed
746
747
        If 'id' edge attribute exists, the edge will be added follows
        the edge id order. Otherwise, order is undefined.
748

Minjie Wang's avatar
Minjie Wang committed
749
750
751
752
753
        Parameters
        ----------
        nx_graph : networkx.DiGraph
            The nx graph
        """
754
755
        if not isinstance(nx_graph, nx.Graph):
            nx_graph = (nx.MultiDiGraph(nx_graph) if self.is_multigraph()
Minjie Wang's avatar
Minjie Wang committed
756
                        else nx.DiGraph(nx_graph))
757
        else:
758
759
760
761
            if not nx_graph.is_directed():
                # to_directed creates a deep copy of the networkx graph even if
                # the original graph is already directed and we do not want to do it.
                nx_graph = nx_graph.to_directed()
762

Minjie Wang's avatar
Minjie Wang committed
763
        num_nodes = nx_graph.number_of_nodes()
764
765
766
        if not self.is_readonly():
            self.clear()
            self.add_nodes(num_nodes)
767
768

        if nx_graph.number_of_edges() == 0:
769
770
            if self.is_readonly():
                raise Exception("can't create an empty immutable graph")
771
772
773
774
            return

        # nx_graph.edges(data=True) returns src, dst, attr_dict
        has_edge_id = 'id' in next(iter(nx_graph.edges(data=True)))[-1]
Minjie Wang's avatar
Minjie Wang committed
775
776
777
778
        if has_edge_id:
            num_edges = nx_graph.number_of_edges()
            src = np.zeros((num_edges,), dtype=np.int64)
            dst = np.zeros((num_edges,), dtype=np.int64)
779
            for u, v, attr in nx_graph.edges(data=True):
Minjie Wang's avatar
Minjie Wang committed
780
                eid = attr['id']
781
782
                src[eid] = u
                dst[eid] = v
Minjie Wang's avatar
Minjie Wang committed
783
784
785
        else:
            src = []
            dst = []
786
787
788
            for e in nx_graph.edges:
                src.append(e[0])
                dst.append(e[1])
789
790
791
792
        eid = np.arange(0, len(src), dtype=np.int64)
        num_nodes = nx_graph.number_of_nodes()
        # We store edge Ids as an edge attribute.
        eid = utils.toindex(eid)
Minjie Wang's avatar
Minjie Wang committed
793
794
        src = utils.toindex(src)
        dst = utils.toindex(dst)
795
        self._init(src, dst, eid, num_nodes)
796

Minjie Wang's avatar
Minjie Wang committed
797

GaiYu0's avatar
GaiYu0 committed
798
799
800
801
802
    def from_scipy_sparse_matrix(self, adj):
        """Convert from scipy sparse matrix.

        Parameters
        ----------
GaiYu0's avatar
GaiYu0 committed
803
        adj : scipy sparse matrix
GaiYu0's avatar
GaiYu0 committed
804
        """
805
806
807
        if not self.is_readonly():
            self.clear()
        num_nodes = max(adj.shape[0], adj.shape[1])
GaiYu0's avatar
GaiYu0 committed
808
809
810
        adj_coo = adj.tocoo()
        src = utils.toindex(adj_coo.row)
        dst = utils.toindex(adj_coo.col)
811
        edge_ids = utils.toindex(F.arange(0, len(adj_coo.row)))
812
        self._init(src, dst, edge_ids, num_nodes)
813

GaiYu0's avatar
GaiYu0 committed
814

815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
    def from_csr_matrix(self, indptr, indices, edge_dir, shared_mem_name=""):
        """Load a graph from the CSR matrix.

        Parameters
        ----------
        indptr : a 1D tensor
            index pointer in the CSR format
        indices : a 1D tensor
            column index array in the CSR format
        edge_dir : string
            the edge direction. The supported option is "in" and "out".
        shared_mem_name : string
            the name of shared memory
        """
        assert self.is_readonly()
        indptr = utils.toindex(indptr)
        indices = utils.toindex(indices)
        edge_ids = utils.toindex(F.arange(0, len(indices)))
        self._handle = _CAPI_DGLGraphCSRCreate(
            indptr.todgltensor(),
            indices.todgltensor(),
            edge_ids.todgltensor(),
            shared_mem_name,
            self._multigraph,
            edge_dir)


    def from_shared_mem_csr_matrix(self, shared_mem_name,
                                   num_nodes, num_edges, edge_dir):
        """Load a graph from the shared memory in the CSR format.

        Parameters
        ----------
        shared_mem_name : string
            the name of shared memory
        num_nodes : int
            the number of nodes
        num_edges : int
            the number of edges
        edge_dir : string
            the edge direction. The supported option is "in" and "out".
        """
        assert self.is_readonly()
        self._handle = _CAPI_DGLGraphCSRCreateMMap(
            shared_mem_name,
            num_nodes, num_edges,
            self._multigraph,
            edge_dir)


865
866
867
    def from_edge_list(self, elist):
        """Convert from an edge list.

brett koonce's avatar
brett koonce committed
868
        Parameters
869
870
871
872
        ---------
        elist : list
            List of (u, v) edge tuple.
        """
873
874
        if not self.is_readonly():
            self.clear()
875
876
877
        src, dst = zip(*elist)
        src = np.array(src)
        dst = np.array(dst)
878
879
        src_ids = utils.toindex(src)
        dst_ids = utils.toindex(dst)
880
881
882
883
        num_nodes = max(src.max(), dst.max()) + 1
        min_nodes = min(src.min(), dst.min())
        if min_nodes != 0:
            raise DGLError('Invalid edge list. Nodes must start from 0.')
884
        edge_ids = utils.toindex(F.arange(0, len(src)))
885
        self._init(src_ids, dst_ids, edge_ids, num_nodes)
886

GaiYu0's avatar
GaiYu0 committed
887
    def line_graph(self, backtracking=True):
GaiYu0's avatar
GaiYu0 committed
888
889
        """Return the line graph of this graph.

GaiYu0's avatar
GaiYu0 committed
890
891
892
893
894
895
        Parameters
        ----------
        backtracking : bool, optional (default=False)
          Whether (i, j) ~ (j, i) in L(G).
          (i, j) ~ (j, i) is the behavior of networkx.line_graph.

GaiYu0's avatar
GaiYu0 committed
896
897
898
899
900
        Returns
        -------
        GraphIndex
            The line graph of this graph.
        """
GaiYu0's avatar
cpp lg  
GaiYu0 committed
901
902
        handle = _CAPI_DGLGraphLineGraph(self._handle, backtracking)
        return GraphIndex(handle)
903
904

class SubgraphIndex(GraphIndex):
905
    """Graph index for subgraph.
906

907
908
909
910
911
912
913
914
915
916
917
918
    Parameters
    ----------
    handle : GraphIndexHandle
        The capi handle.
    paranet : GraphIndex
        The parent graph index.
    induced_nodes : utils.Index
        The parent node ids in this subgraph.
    induced_edges : utils.Index
        The parent edge ids in this subgraph.
    """
    def __init__(self, handle, parent, induced_nodes, induced_edges):
Da Zheng's avatar
Da Zheng committed
919
        super(SubgraphIndex, self).__init__(handle, parent.is_multigraph(), parent.is_readonly())
920
921
922
923
924
        self._parent = parent
        self._induced_nodes = induced_nodes
        self._induced_edges = induced_edges

    def add_nodes(self, num):
925
        """Add nodes. Disabled because SubgraphIndex is read-only."""
926
927
928
        raise RuntimeError('Readonly graph. Mutation is not allowed.')

    def add_edge(self, u, v):
929
        """Add edges. Disabled because SubgraphIndex is read-only."""
930
931
932
        raise RuntimeError('Readonly graph. Mutation is not allowed.')

    def add_edges(self, u, v):
933
        """Add edges. Disabled because SubgraphIndex is read-only."""
934
935
936
937
        raise RuntimeError('Readonly graph. Mutation is not allowed.')

    @property
    def induced_nodes(self):
938
939
940
941
942
943
944
        """Return parent node ids.

        Returns
        -------
        utils.Index
            The parent node ids.
        """
945
946
        return self._induced_nodes

947
948
949
950
951
952
953
954
955
956
957
    @property
    def induced_edges(self):
        """Return parent edge ids.

        Returns
        -------
        utils.Index
            The parent edge ids.
        """
        return self._induced_edges

Gan Quan's avatar
Gan Quan committed
958
959
    def __getstate__(self):
        raise NotImplementedError(
Minjie Wang's avatar
Minjie Wang committed
960
            "SubgraphIndex pickling is not supported yet.")
Gan Quan's avatar
Gan Quan committed
961
962
963

    def __setstate__(self, state):
        raise NotImplementedError(
Minjie Wang's avatar
Minjie Wang committed
964
            "SubgraphIndex unpickling is not supported yet.")
Gan Quan's avatar
Gan Quan committed
965

966
967
968
969
970
def map_to_subgraph_nid(subgraph, parent_nids):
    """Map parent node Ids to the subgraph node Ids.

    Parameters
    ----------
971
    subgraph: SubgraphIndex
972
973
974
975
976
977
978
979
980
981
982
        the graph index of a subgraph

    parent_nids: utils.Index
        Node Ids in the parent graph.

    Returns
    -------
    utils.Index
        Node Ids in the subgraph.
    """
    return utils.toindex(_CAPI_DGLMapSubgraphNID(subgraph.induced_nodes.todgltensor(),
Minjie Wang's avatar
Minjie Wang committed
983
                                                 parent_nids.todgltensor()))
984

985
986
def transform_ids(mapping, ids):
    """Transform ids by the given mapping.
Da Zheng's avatar
Da Zheng committed
987
988
989

    Parameters
    ----------
990
991
992
993
    mapping : utils.Index
        The id mapping. new_id = mapping[old_id]
    ids : utils.Index
        The old ids.
Da Zheng's avatar
Da Zheng committed
994
995
996
997

    Returns
    -------
    utils.Index
998
        The new ids.
Da Zheng's avatar
Da Zheng committed
999
    """
1000
1001
    return utils.toindex(_CAPI_DGLMapSubgraphNID(
        mapping.todgltensor(), ids.todgltensor()))
Da Zheng's avatar
Da Zheng committed
1002

Minjie Wang's avatar
Minjie Wang committed
1003
1004
def disjoint_union(graphs):
    """Return a disjoint union of the input graphs.
1005

Minjie Wang's avatar
Minjie Wang committed
1006
    The new graph will include all the nodes/edges in the given graphs.
brett koonce's avatar
brett koonce committed
1007
    Nodes/Edges will be relabeled by adding the cumsum of the previous graph sizes
Minjie Wang's avatar
Minjie Wang committed
1008
1009
1010
    in the given sequence order. For example, giving input [g1, g2, g3], where
    they have 5, 6, 7 nodes respectively. Then node#2 of g2 will become node#7
    in the result graph. Edge ids are re-assigned similarly.
1011

Minjie Wang's avatar
Minjie Wang committed
1012
1013
1014
1015
    Parameters
    ----------
    graphs : iterable of GraphIndex
        The input graphs
1016

Minjie Wang's avatar
Minjie Wang committed
1017
1018
1019
1020
1021
1022
1023
1024
1025
    Returns
    -------
    GraphIndex
        The disjoint union
    """
    inputs = c_array(GraphIndexHandle, [gr._handle for gr in graphs])
    inputs = ctypes.cast(inputs, ctypes.c_void_p)
    handle = _CAPI_DGLDisjointUnion(inputs, len(graphs))
    return GraphIndex(handle)
1026

Minjie Wang's avatar
Minjie Wang committed
1027
1028
def disjoint_partition(graph, num_or_size_splits):
    """Partition the graph disjointly.
1029

Minjie Wang's avatar
Minjie Wang committed
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
    This is a reverse operation of DisjointUnion. The graph will be partitioned
    into num graphs. This requires the given number of partitions to evenly
    divides the number of nodes in the graph. If the a size list is given,
    the sum of the given sizes is equal.

    Parameters
    ----------
    graph : GraphIndex
        The graph to be partitioned
    num_or_size_splits : int or utils.Index
        The partition number of size splits

    Returns
    -------
    list of GraphIndex
        The partitioned graphs
    """
    if isinstance(num_or_size_splits, utils.Index):
        rst = _CAPI_DGLDisjointPartitionBySizes(
Minjie Wang's avatar
Minjie Wang committed
1049
1050
            graph._handle,
            num_or_size_splits.todgltensor())
Minjie Wang's avatar
Minjie Wang committed
1051
1052
    else:
        rst = _CAPI_DGLDisjointPartitionByNum(
Minjie Wang's avatar
Minjie Wang committed
1053
1054
            graph._handle,
            int(num_or_size_splits))
Minjie Wang's avatar
Minjie Wang committed
1055
1056
1057
1058
1059
1060
    graphs = []
    for val in rst.asnumpy():
        handle = ctypes.cast(int(val), ctypes.c_void_p)
        graphs.append(GraphIndex(handle))
    return graphs

1061
def create_graph_index(graph_data=None, multigraph=False, readonly=False):
1062
1063
1064
1065
1066
1067
    """Create a graph index object.

    Parameters
    ----------
    graph_data : graph data, optional
        Data to initialize graph. Same as networkx's semantics.
1068
1069
    multigraph : bool, optional
        Whether the graph is multigraph (default is False)
1070
    """
Minjie Wang's avatar
Minjie Wang committed
1071
    if isinstance(graph_data, GraphIndex):
Minjie Wang's avatar
Minjie Wang committed
1072
        # FIXME(minjie): this return is not correct for mutable graph index
Minjie Wang's avatar
Minjie Wang committed
1073
        return graph_data
1074

Minjie Wang's avatar
Minjie Wang committed
1075
    if readonly:
1076
1077
1078
1079
1080
        # FIXME(zhengda): we should construct a C graph index before constructing GraphIndex.
        gidx = GraphIndex(None, multigraph, readonly)
    else:
        handle = _CAPI_DGLGraphCreateMutable(multigraph)
        gidx = GraphIndex(handle, multigraph, readonly)
1081

1082
1083
1084
    if graph_data is None and readonly:
        raise Exception("can't create an empty immutable graph")
    elif graph_data is None:
Minjie Wang's avatar
Minjie Wang committed
1085
        return gidx
1086

1087
1088
1089
    # edge list
    if isinstance(graph_data, (list, tuple)):
        try:
Minjie Wang's avatar
Minjie Wang committed
1090
1091
1092
            gidx.from_edge_list(graph_data)
            return gidx
        except Exception:  # pylint: disable=broad-except
1093
1094
            raise DGLError('Graph data is not a valid edge list.')

1095
1096
1097
    # scipy format
    if isinstance(graph_data, scipy.sparse.spmatrix):
        try:
Minjie Wang's avatar
Minjie Wang committed
1098
1099
1100
            gidx.from_scipy_sparse_matrix(graph_data)
            return gidx
        except Exception:  # pylint: disable=broad-except
1101
            raise DGLError('Graph data is not a valid scipy sparse matrix.')
1102
1103
1104

    # networkx - any format
    try:
Minjie Wang's avatar
Minjie Wang committed
1105
1106
        gidx.from_networkx(graph_data)
    except Exception:  # pylint: disable=broad-except
1107
        raise DGLError('Error while creating graph from input of type "%s".'
Minjie Wang's avatar
Minjie Wang committed
1108
                       % type(graph_data))
1109

Minjie Wang's avatar
Minjie Wang committed
1110
    return gidx
1111

1112

Minjie Wang's avatar
Minjie Wang committed
1113
_init_api("dgl.graph_index")