Commit e2b7f745 authored by Mufei Li's avatar Mufei Li Committed by Quan (Andy) Gan
Browse files

[Hetero] Doc (#892)

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* Update

* style fixes & undefined name fix

* transpose=False in test
parent b3d596b5
.. _apigraph: .. _apigraph:
DGLGraph -- Graph with node/edge features DGLGraph -- Untyped graph with node/edge features
========================================= =========================================
.. currentmodule:: dgl .. currentmodule:: dgl
...@@ -79,7 +79,6 @@ Converting from/to other format ...@@ -79,7 +79,6 @@ Converting from/to other format
DGLGraph.adjacency_matrix DGLGraph.adjacency_matrix
DGLGraph.adjacency_matrix_scipy DGLGraph.adjacency_matrix_scipy
DGLGraph.incidence_matrix DGLGraph.incidence_matrix
DGLGraph.to
Using Node/edge features Using Node/edge features
------------------------ ------------------------
...@@ -121,3 +120,4 @@ Computing with DGLGraph ...@@ -121,3 +120,4 @@ Computing with DGLGraph
DGLGraph.prop_edges DGLGraph.prop_edges
DGLGraph.filter_nodes DGLGraph.filter_nodes
DGLGraph.filter_edges DGLGraph.filter_edges
DGLGraph.to
.. _apiheterograph:
DGLHeteroGraph -- Typed graph with node/edge features
=====================================================
.. currentmodule:: dgl
.. autoclass:: DGLHeteroGraph
Conversion to and from heterogeneous graphs
-----------------------------------------
.. automodule:: dgl.convert
.. currentmodule:: dgl
.. autosummary::
:toctree: ../../generated/
graph
bipartite
hetero_from_relations
to_hetero
to_homo
to_networkx
DGLHeteroGraph.adjacency_matrix
DGLHeteroGraph.incidence_matrix
Querying metagraph structure
----------------------------
.. autosummary::
:toctree: ../../generated/
DGLHeteroGraph.ntypes
DGLHeteroGraph.etypes
DGLHeteroGraph.canonical_etypes
DGLHeteroGraph.metagraph
DGLHeteroGraph.to_canonical_etype
DGLHeteroGraph.get_ntype_id
DGLHeteroGraph.get_etype_id
Querying graph structure
------------------------
.. autosummary::
:toctree: ../../generated/
DGLHeteroGraph.number_of_nodes
DGLHeteroGraph.number_of_edges
DGLHeteroGraph.is_multigraph
DGLHeteroGraph.is_readonly
DGLHeteroGraph.has_node
DGLHeteroGraph.has_nodes
DGLHeteroGraph.has_edge_between
DGLHeteroGraph.has_edges_between
DGLHeteroGraph.predecessors
DGLHeteroGraph.successors
DGLHeteroGraph.edge_id
DGLHeteroGraph.edge_ids
DGLHeteroGraph.find_edges
DGLHeteroGraph.in_edges
DGLHeteroGraph.out_edges
DGLHeteroGraph.all_edges
DGLHeteroGraph.in_degree
DGLHeteroGraph.in_degrees
DGLHeteroGraph.out_degree
DGLHeteroGraph.out_degrees
Using Node/edge features
------------------------
.. autosummary::
:toctree: ../../generated/
DGLHeteroGraph.nodes
DGLHeteroGraph.ndata
DGLHeteroGraph.edges
DGLHeteroGraph.edata
DGLHeteroGraph.node_attr_schemes
DGLHeteroGraph.edge_attr_schemes
DGLHeteroGraph.set_n_initializer
DGLHeteroGraph.set_e_initializer
DGLHeteroGraph.local_var
DGLHeteroGraph.local_scope
Transforming graph
------------------
.. autosummary::
:toctree: ../../generated/
DGLHeteroGraph.subgraph
DGLHeteroGraph.edge_subgraph
DGLHeteroGraph.node_type_subgraph
DGLHeteroGraph.edge_type_subgraph
Computing with DGLHeteroGraph
-----------------------------
.. autosummary::
:toctree: ../../generated/
DGLHeteroGraph.apply_nodes
DGLHeteroGraph.apply_edges
DGLHeteroGraph.group_apply_edges
DGLHeteroGraph.send
DGLHeteroGraph.recv
DGLHeteroGraph.multi_recv
DGLHeteroGraph.send_and_recv
DGLHeteroGraph.multi_send_and_recv
DGLHeteroGraph.pull
DGLHeteroGraph.multi_pull
DGLHeteroGraph.push
DGLHeteroGraph.update_all
DGLHeteroGraph.multi_update_all
DGLHeteroGraph.prop_nodes
DGLHeteroGraph.prop_edges
DGLHeteroGraph.filter_nodes
DGLHeteroGraph.filter_edges
DGLHeteroGraph.to
...@@ -5,6 +5,7 @@ API Reference ...@@ -5,6 +5,7 @@ API Reference
:maxdepth: 2 :maxdepth: 2
graph graph
heterograph
init init
batch batch
function function
......
.. _apigraph: .. _apisubgraph:
DGLSubGraph -- Class for subgraph data structure DGLSubGraph -- Class for subgraph data structure
================================================ ================================================
......
...@@ -9,7 +9,7 @@ from . import heterograph_index ...@@ -9,7 +9,7 @@ from . import heterograph_index
from .heterograph import DGLHeteroGraph, combine_frames from .heterograph import DGLHeteroGraph, combine_frames
from . import graph_index from . import graph_index
from . import utils from . import utils
from .base import NTYPE, ETYPE, NID, EID from .base import NTYPE, ETYPE, NID, EID, DGLError
__all__ = [ __all__ = [
'graph', 'graph',
...@@ -22,21 +22,52 @@ __all__ = [ ...@@ -22,21 +22,52 @@ __all__ = [
] ]
def graph(data, ntype='_N', etype='_E', card=None, **kwargs): def graph(data, ntype='_N', etype='_E', card=None, **kwargs):
"""Create a graph. """Create a graph with one type of nodes and edges.
The graph has only one type of nodes and edges.
In the sparse matrix perspective, :func:`dgl.graph` creates a graph In the sparse matrix perspective, :func:`dgl.graph` creates a graph
whose adjacency matrix must be square while :func:`dgl.bipartite` whose adjacency matrix must be square while :func:`dgl.bipartite`
creates a graph that does not necessarily have square adjacency matrix. creates a graph that does not necessarily have square adjacency matrix.
Parameters
----------
data : graph data
Data to initialize graph structure. Supported data formats are
(1) list of edge pairs (e.g. [(0, 2), (3, 1), ...])
(2) pair of vertex IDs representing end nodes (e.g. ([0, 3, ...], [2, 1, ...]))
(3) scipy sparse matrix
(4) networkx graph
ntype : str, optional
Node type name. (Default: _N)
etype : str, optional
Edge type name. (Default: _E)
card : int, optional
Cardinality (number of nodes in the graph). If None, infer from input data, i.e.
the largest node ID plus 1. (Default: None)
kwargs : key-word arguments, optional
Other key word arguments. Only comes into effect when we are using a NetworkX
graph. It can consist of:
* edge_id_attr_name
``Str``, key name for edge ids in the NetworkX graph. If not found, we
will consider the graph not to have pre-specified edge ids.
* node_attrs
``List of str``, names for node features to retrieve from the NetworkX graph
* edge_attrs
``List of str``, names for edge features to retrieve from the NetworkX graph
Returns
-------
DGLHeteroGraph
Examples Examples
-------- --------
Create from edges pairs: Create from pairs of edges with form (src, dst)
>>> g = dgl.graph([(0, 2), (0, 3), (1, 2)]) >>> g = dgl.graph([(0, 2), (0, 3), (1, 2)])
Creat from pair of vertex IDs lists Create from source and destination vertex ID lists
>>> u = [0, 0, 1] >>> u = [0, 0, 1]
>>> v = [2, 3, 2] >>> v = [2, 3, 2]
...@@ -70,28 +101,6 @@ def graph(data, ntype='_N', etype='_E', card=None, **kwargs): ...@@ -70,28 +101,6 @@ def graph(data, ntype='_N', etype='_E', card=None, **kwargs):
['follows'] ['follows']
>>> g.canonical_etypes >>> g.canonical_etypes
[('user', 'follows', 'user')] [('user', 'follows', 'user')]
Parameters
----------
data : graph data
Data to initialize graph structure. Supported data formats are
(1) list of edge pairs (e.g. [(0, 2), (3, 1), ...])
(2) pair of vertex IDs representing end nodes (e.g. ([0, 3, ...], [2, 1, ...]))
(3) scipy sparse matrix
(4) networkx graph
ntype : str, optional
Node type name. (Default: _N)
etype : str, optional
Edge type name. (Default: _E)
card : int, optional
Cardinality (number of nodes in the graph). If None, infer from input data.
(Default: None)
kwargs : key-word arguments, optional
Other key word arguments.
Returns
-------
DGLHeteroGraph
""" """
if card is not None: if card is not None:
urange, vrange = card, card urange, vrange = card, card
...@@ -119,9 +128,40 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs): ...@@ -119,9 +128,40 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs):
whose adjacency matrix must be square while :func:`dgl.bipartite` whose adjacency matrix must be square while :func:`dgl.bipartite`
creates a graph that does not necessarily have square adjacency matrix. creates a graph that does not necessarily have square adjacency matrix.
Parameters
----------
data : graph data
Data to initialize graph structure. Supported data formats are
(1) list of edge pairs (e.g. [(0, 2), (3, 1), ...])
(2) pair of vertex IDs representing end nodes (e.g. ([0, 3, ...], [2, 1, ...]))
(3) scipy sparse matrix
(4) networkx graph
utype : str, optional
Source node type name. (Default: _U)
etype : str, optional
Edge type name. (Default: _E)
vtype : str, optional
Destination node type name. (Default: _V)
card : pair of int, optional
Cardinality (number of nodes in the source and destination group). If None,
infer from input data, i.e. the largest node ID plus 1 for each type. (Default: None)
kwargs : key-word arguments, optional
Other key word arguments. Only comes into effect when we are using a NetworkX
graph. It can consist of:
* edge_id_attr_name
``Str``, key name for edge ids in the NetworkX graph. If not found, we
will consider the graph not to have pre-specified edge ids.
Returns
-------
DGLHeteroGraph
Examples Examples
-------- --------
Create from edges pairs: Create from pairs of edges
>>> g = dgl.bipartite([(0, 2), (0, 3), (1, 2)], 'user', 'plays', 'game') >>> g = dgl.bipartite([(0, 2), (0, 3), (1, 2)], 'user', 'plays', 'game')
>>> g.ntypes >>> g.ntypes
...@@ -137,7 +177,7 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs): ...@@ -137,7 +177,7 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs):
>>> g.number_of_edges('plays') # 'plays' could be omitted here >>> g.number_of_edges('plays') # 'plays' could be omitted here
3 3
Creat from pair of vertex IDs lists Create from source and destination vertex ID lists
>>> u = [0, 0, 1] >>> u = [0, 0, 1]
>>> v = [2, 3, 2] >>> v = [2, 3, 2]
...@@ -168,37 +208,13 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs): ...@@ -168,37 +208,13 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs):
>>> import networkx as nx >>> import networkx as nx
>>> nxg = nx.complete_bipartite_graph(3, 4) >>> nxg = nx.complete_bipartite_graph(3, 4)
>>> g = dgl.graph(nxg, 'user', 'plays', 'game') >>> g = dgl.bipartite(nxg, 'user', 'plays', 'game')
>>> g.number_of_nodes('user') >>> g.number_of_nodes('user')
3 3
>>> g.number_of_nodes('game') >>> g.number_of_nodes('game')
4 4
>>> g.edges() >>> g.edges()
(tensor([0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]), tensor([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3])) (tensor([0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2]), tensor([0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3]))
Parameters
----------
data : graph data
Data to initialize graph structure. Supported data formats are
(1) list of edge pairs (e.g. [(0, 2), (3, 1), ...])
(2) pair of vertex IDs representing end nodes (e.g. ([0, 3, ...], [2, 1, ...]))
(3) scipy sparse matrix
(4) networkx graph
utype : str, optional
Source node type name. (Default: _U)
etype : str, optional
Edge type name. (Default: _E)
vtype : str, optional
Destination node type name. (Default: _V)
card : pair of int, optional
Cardinality (number of nodes in the source and destination group). If None,
infer from input data. (Default: None)
kwargs : key-word arguments, optional
Other key word arguments.
Returns
-------
DGLHeteroGraph
""" """
if utype == vtype: if utype == vtype:
raise DGLError('utype should not be equal to vtype. Use ``dgl.graph`` instead.') raise DGLError('utype should not be equal to vtype. Use ``dgl.graph`` instead.')
...@@ -221,21 +237,18 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs): ...@@ -221,21 +237,18 @@ def bipartite(data, utype='_U', etype='_E', vtype='_V', card=None, **kwargs):
def hetero_from_relations(rel_graphs): def hetero_from_relations(rel_graphs):
"""Create a heterograph from per-relation graphs. """Create a heterograph from per-relation graphs.
TODO(minjie): this API can be generalized as a union operation of
the input graphs
TODO(minjie): handle node/edge data
Parameters Parameters
---------- ----------
rel_graphs : list of DGLHeteroGraph rel_graphs : list of DGLHeteroGraph
Graph for each relation. Each element corresponds to a heterograph for one (src, edge, dst) relation.
Returns Returns
------- -------
DGLHeteroGraph DGLHeteroGraph
A heterograph. A heterograph consisting of all relations.
""" """
# TODO(minjie): this API can be generalized as a union operation of the input graphs
# TODO(minjie): handle node/edge data
# infer meta graph # infer meta graph
ntype_dict = {} # ntype -> ntid ntype_dict = {} # ntype -> ntid
meta_edges = [] meta_edges = []
...@@ -244,11 +257,11 @@ def hetero_from_relations(rel_graphs): ...@@ -244,11 +257,11 @@ def hetero_from_relations(rel_graphs):
for rgrh in rel_graphs: for rgrh in rel_graphs:
assert len(rgrh.etypes) == 1 assert len(rgrh.etypes) == 1
stype, etype, dtype = rgrh.canonical_etypes[0] stype, etype, dtype = rgrh.canonical_etypes[0]
if not stype in ntype_dict: if stype not in ntype_dict:
ntype_dict[stype] = len(ntypes) ntype_dict[stype] = len(ntypes)
ntypes.append(stype) ntypes.append(stype)
stid = ntype_dict[stype] stid = ntype_dict[stype]
if not dtype in ntype_dict: if dtype not in ntype_dict:
ntype_dict[dtype] = len(ntypes) ntype_dict[dtype] = len(ntypes)
ntypes.append(dtype) ntypes.append(dtype)
dtid = ntype_dict[dtype] dtid = ntype_dict[dtype]
...@@ -343,36 +356,32 @@ def heterograph(data_dict, num_nodes_dict=None): ...@@ -343,36 +356,32 @@ def heterograph(data_dict, num_nodes_dict=None):
return hetero_from_relations(rel_graphs) return hetero_from_relations(rel_graphs)
def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph=None): def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph=None):
"""Convert the given graph to a heterogeneous graph. """Convert the given homogeneous graph to a heterogeneous graph.
The input graph should have only one type of nodes and edges. Each node and edge The input graph should have only one type of nodes and edges. Each node and edge
stores an integer feature (under ``ntype_field`` and ``etype_field``), representing stores an integer feature (under ``ntype_field`` and ``etype_field``), representing
the type id, which which can be used to retrieve the type names stored the type id, which can be used to retrieve the type names stored
in the given ``ntypes`` and ``etypes`` arguments. in the given ``ntypes`` and ``etypes`` arguments.
The function will automatically distinguish edge types that have the same given The function will automatically distinguish edge types that have the same given
type IDs but different src and dst type IDs. For example, we allow both edges A and B type IDs but different src and dst type IDs. For example, we allow both edges A and B
to have the same type ID 3, but one has (0, 1) and the other as (2, 3) as the to have the same type ID 0, but one has (0, 1) and the other as (2, 3) as the
(src, dst) type IDs. In this case, the function will "split" edge type 3 into two types: (src, dst) type IDs. In this case, the function will "split" edge type 0 into two types:
(0, ty_A, 1) and (2, ty_B, 3). In another word, these two edges share the same edge (0, ty_A, 1) and (2, ty_B, 3). In another word, these two edges share the same edge
type name, but can be distinguished by a canonical edge type tuple. type name, but can be distinguished by a canonical edge type tuple.
Examples
--------
TBD
Parameters Parameters
---------- ----------
G : DGLHeteroGraph G : DGLHeteroGraph
Input homogenous graph. Input homogeneous graph.
ntypes : list of str ntypes : list of str
The node type names. The node type names.
etypes : list of str etypes : list of str
The edge type names. The edge type names.
ntype_field : str, optional ntype_field : str, optional
The feature field used to store node type. (Default: dgl.NTYPE) The feature field used to store node type. (Default: ``dgl.NTYPE``)
etype_field : str, optional etype_field : str, optional
The feature field used to store edge type. (Default: dgl.ETYPE) The feature field used to store edge type. (Default: ``dgl.ETYPE``)
metagraph : networkx MultiDiGraph, optional metagraph : networkx MultiDiGraph, optional
Metagraph of the returned heterograph. Metagraph of the returned heterograph.
If provided, DGL assumes that G can indeed be described with the given metagraph. If provided, DGL assumes that G can indeed be described with the given metagraph.
...@@ -382,9 +391,8 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph ...@@ -382,9 +391,8 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph
Returns Returns
------- -------
DGLHeteroGraph DGLHeteroGraph
A heterograph. A heterograph. The parent node and edge ID are stored in the column
The parent node and edge ID are stored in the column dgl.NID and dgl.EID ``dgl.NID`` and ``dgl.EID`` respectively for all node/edge types.
respectively for all node/edge types.
Notes Notes
----- -----
...@@ -393,8 +401,47 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph ...@@ -393,8 +401,47 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph
and destination types differ. and destination types differ.
The node IDs of a single type in the returned heterogeneous graph is ordered The node IDs of a single type in the returned heterogeneous graph is ordered
the same as the nodes with the same ``ntype_field`` feature. Edge IDs of the same as the nodes with the same ``ntype_field`` feature. Edge IDs of
a single type is similar. a single type is similar.
Examples
--------
>>> g1 = dgl.bipartite([(0, 1), (1, 2)], 'user', 'develops', 'activity')
>>> g2 = dgl.bipartite([(0, 0), (1, 1)], 'developer', 'develops', 'game')
>>> hetero_g = dgl.hetero_from_relations([g1, g2])
>>> print(hetero_g)
Graph(num_nodes={'user': 2, 'activity': 3, 'developer': 2, 'game': 2},
num_edges={'develops': 2},
metagraph=[('user', 'activity'), ('developer', 'game')])
We first convert the heterogeneous graph to a homogeneous graph.
>>> homo_g = dgl.to_homo(hetero_g)
>>> print(homo_g)
Graph(num_nodes=9, num_edges=4,
ndata_schemes={'_TYPE': Scheme(shape=(), dtype=torch.int64),
'_ID': Scheme(shape=(), dtype=torch.int64)}
edata_schemes={'_TYPE': Scheme(shape=(), dtype=torch.int64),
'_ID': Scheme(shape=(), dtype=torch.int64)})
>>> homo_g.ndata
{'_TYPE': tensor([0, 0, 1, 1, 1, 2, 2, 3, 3]), '_ID': tensor([0, 1, 0, 1, 2, 0, 1, 0, 1])}
Nodes 0, 1 for 'user', 2, 3, 4 for 'activity', 5, 6 for 'developer', 7, 8 for 'game'
>>> homo_g.edata
{'_TYPE': tensor([0, 0, 1, 1]), '_ID': tensor([0, 1, 0, 1])}
Edges 0, 1 for ('user', 'develops', 'activity'), 2, 3 for ('developer', 'develops', 'game')
Now convert the homogeneous graph back to a heterogeneous graph.
>>> hetero_g_2 = dgl.to_hetero(homo_g, hetero_g.ntypes, hetero_g.etypes)
>>> print(hetero_g_2)
Graph(num_nodes={'user': 2, 'activity': 3, 'developer': 2, 'game': 2},
num_edges={'develops': 2},
metagraph=[('user', 'activity'), ('developer', 'game')])
See Also
--------
dgl.to_homo
""" """
# TODO(minjie): use hasattr to support DGLGraph input; should be fixed once # TODO(minjie): use hasattr to support DGLGraph input; should be fixed once
# DGLGraph is merged with DGLHeteroGraph # DGLGraph is merged with DGLHeteroGraph
...@@ -486,29 +533,42 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph ...@@ -486,29 +533,42 @@ def to_hetero(G, ntypes, etypes, ntype_field=NTYPE, etype_field=ETYPE, metagraph
return hg return hg
def to_homo(G): def to_homo(G):
"""Convert the given graph to a homogeneous graph. """Convert the given heterogeneous graph to a homogeneous graph.
The returned graph has only one type of nodes and etypes. The returned graph has only one type of nodes and edges.
Node and edge types are stored as features in the returned graph. Each feature Node and edge types are stored as features in the returned graph. Each feature
is an integer representing the type id, which can be used to retrieve the type is an integer representing the type id, which can be used to retrieve the type
names stored in ``G.ntypes`` and ``G.etypes`` arguments. names stored in ``G.ntypes`` and ``G.etypes`` arguments.
Examples
--------
TBD
Parameters Parameters
---------- ----------
G : DGLHeteroGraph G : DGLHeteroGraph
Input heterogenous graph. Input heterogeneous graph.
Returns Returns
------- -------
DGLHeteroGraph DGLHeteroGraph
A homogenous graph. A homogeneous graph. The parent node and edge type/ID are stored in
The parent node and edge type/ID are stored in columns dgl.NTYPE/dgl.NID and columns ``dgl.NTYPE/dgl.NID`` and ``dgl.ETYPE/dgl.EID`` respectively.
dgl.ETYPE/dgl.EID respectively.
Examples
--------
>>> follows_g = dgl.graph([(0, 1), (1, 2)], 'user', 'follows')
>>> devs_g = dgl.bipartite([(0, 0), (1, 1)], 'developer', 'develops', 'game')
>>> hetero_g = dgl.hetero_from_relations([follows_g, devs_g])
>>> homo_g = dgl.to_homo(hetero_g)
>>> homo_g.ndata
{'_TYPE': tensor([0, 0, 0, 1, 1, 2, 2]), '_ID': tensor([0, 1, 2, 0, 1, 0, 1])}
First three nodes for 'user', next two for 'developer' and the last two for 'game'
>>> homo_g.edata
{'_TYPE': tensor([0, 0, 1, 1]), '_ID': tensor([0, 1, 0, 1])}
First two edges for 'follows', next two for 'develops'
See Also
--------
dgl.to_hetero
""" """
num_nodes_per_ntype = [G.number_of_nodes(ntype) for ntype in G.ntypes] num_nodes_per_ntype = [G.number_of_nodes(ntype) for ntype in G.ntypes]
offset_per_ntype = np.insert(np.cumsum(num_nodes_per_ntype), 0, 0) offset_per_ntype = np.insert(np.cumsum(num_nodes_per_ntype), 0, 0)
...@@ -580,7 +640,7 @@ def create_from_edges(u, v, utype, etype, vtype, urange=None, vrange=None): ...@@ -580,7 +640,7 @@ def create_from_edges(u, v, utype, etype, vtype, urange=None, vrange=None):
maximum of the destination node IDs in the edge list plus 1. (Default: None) maximum of the destination node IDs in the edge list plus 1. (Default: None)
Returns Returns
------ -------
DGLHeteroGraph DGLHeteroGraph
""" """
u = utils.toindex(u) u = utils.toindex(u)
...@@ -599,14 +659,10 @@ def create_from_edges(u, v, utype, etype, vtype, urange=None, vrange=None): ...@@ -599,14 +659,10 @@ def create_from_edges(u, v, utype, etype, vtype, urange=None, vrange=None):
return DGLHeteroGraph(hgidx, [utype, vtype], [etype]) return DGLHeteroGraph(hgidx, [utype, vtype], [etype])
def create_from_edge_list(elist, utype, etype, vtype, urange=None, vrange=None): def create_from_edge_list(elist, utype, etype, vtype, urange=None, vrange=None):
"""Internal function to create a graph from a list of edge tuples with types. """Internal function to create a heterograph from a list of edge tuples with types.
utype could be equal to vtype utype could be equal to vtype
Examples
--------
TBD
Parameters Parameters
---------- ----------
elist : iterable of int pairs elist : iterable of int pairs
...@@ -637,7 +693,7 @@ def create_from_edge_list(elist, utype, etype, vtype, urange=None, vrange=None): ...@@ -637,7 +693,7 @@ def create_from_edge_list(elist, utype, etype, vtype, urange=None, vrange=None):
return create_from_edges(u, v, utype, etype, vtype, urange, vrange) return create_from_edges(u, v, utype, etype, vtype, urange, vrange)
def create_from_scipy(spmat, utype, etype, vtype, with_edge_id=False): def create_from_scipy(spmat, utype, etype, vtype, with_edge_id=False):
"""Internal function to create a graph from a scipy sparse matrix with types. """Internal function to create a heterograph from a scipy sparse matrix with types.
Parameters Parameters
---------- ----------
...@@ -684,7 +740,26 @@ def create_from_networkx(nx_graph, ...@@ -684,7 +740,26 @@ def create_from_networkx(nx_graph,
edge_id_attr_name='id', edge_id_attr_name='id',
node_attrs=None, node_attrs=None,
edge_attrs=None): edge_attrs=None):
"""Create graph that has only one set of nodes and edges. """Create a heterograph that has only one set of nodes and edges.
Parameters
----------
nx_graph : NetworkX graph
ntype : str
Type name for both source and destination nodes
etype : str
Type name for edges
edge_id_attr_name : str, optional
Key name for edge ids in the NetworkX graph. If not found, we
will consider the graph not to have pre-specified edge ids. (Default: 'id')
node_attrs : list of str
Names for node features to retrieve from the NetworkX graph (Default: None)
edge_attrs : list of str
Names for edge features to retrieve from the NetworkX graph (Default: None)
Returns
-------
g : DGLHeteroGraph
""" """
if not nx_graph.is_directed(): if not nx_graph.is_directed():
nx_graph = nx_graph.to_directed() nx_graph = nx_graph.to_directed()
...@@ -767,13 +842,33 @@ def create_from_networkx_bipartite(nx_graph, ...@@ -767,13 +842,33 @@ def create_from_networkx_bipartite(nx_graph,
edge_id_attr_name='id', edge_id_attr_name='id',
node_attrs=None, node_attrs=None,
edge_attrs=None): edge_attrs=None):
"""Create graph that has only one set of nodes and edges. """Create a heterograph that has one set of source nodes, one set of
destination nodes and one set of edges.
The input graph must follow the bipartite graph convention of networkx. Parameters
Each node has an attribute ``bipartite`` with values 0 and 1 indicating which ----------
set it belongs to. nx_graph : NetworkX graph
The input graph must follow the bipartite graph convention of networkx.
Each node has an attribute ``bipartite`` with values 0 and 1 indicating
which set it belongs to. Only edges from node set 0 to node set 1 are
added to the returned graph.
utype : str
Source node type name.
etype : str
Edge type name.
vtype : str
Destination node type name.
edge_id_attr_name : str, optional
Key name for edge ids in the NetworkX graph. If not found, we
will consider the graph not to have pre-specified edge ids. (Default: 'id')
node_attrs : list of str
Names for node features to retrieve from the NetworkX graph (Default: None)
edge_attrs : list of str
Names for edge features to retrieve from the NetworkX graph (Default: None)
Only edges from node set 0 to node set 1 are added to the returned graph. Returns
-------
g : DGLHeteroGraph
""" """
if not nx_graph.is_directed(): if not nx_graph.is_directed():
nx_graph = nx_graph.to_directed() nx_graph = nx_graph.to_directed()
...@@ -810,15 +905,28 @@ def create_from_networkx_bipartite(nx_graph, ...@@ -810,15 +905,28 @@ def create_from_networkx_bipartite(nx_graph,
g = create_from_edges(src, dst, utype, etype, vtype, len(top_nodes), len(bottom_nodes)) g = create_from_edges(src, dst, utype, etype, vtype, len(top_nodes), len(bottom_nodes))
# TODO attributes # TODO attributes
assert node_attrs is None assert node_attrs is None, 'Retrieval of node attributes are not supported yet.'
assert edge_attrs is None assert edge_attrs is None, 'Retrieval of edge attributes are not supported yet.'
return g return g
def to_networkx(g, node_attrs=None, edge_attrs=None): def to_networkx(g, node_attrs=None, edge_attrs=None):
"""Convert to networkx graph. """Convert to networkx graph.
See Also The edge id will be saved as the 'id' edge attribute.
--------
DGLHeteroGraph.to_networkx Parameters
----------
g : DGLGraph or DGLHeteroGraph
For DGLHeteroGraphs, we currently only support the
case of one node type and one edge type.
node_attrs : iterable of str, optional
The node attributes to be copied. (Default: None)
edge_attrs : iterable of str, optional
The edge attributes to be copied. (Default: None)
Returns
-------
networkx.DiGraph
The nx graph
""" """
return g.to_networkx(node_attrs, edge_attrs) return g.to_networkx(node_attrs, edge_attrs)
This diff is collapsed.
...@@ -6,7 +6,7 @@ import scipy ...@@ -6,7 +6,7 @@ import scipy
from ._ffi.object import register_object, ObjectBase from ._ffi.object import register_object, ObjectBase
from ._ffi.function import _init_api from ._ffi.function import _init_api
from .base import DGLError from .base import DGLError, dgl_warning
from . import backend as F from . import backend as F
from . import utils from . import utils
......
...@@ -280,7 +280,7 @@ class LazyDict(Mapping): ...@@ -280,7 +280,7 @@ class LazyDict(Mapping):
self._keys = keys self._keys = keys
def __getitem__(self, key): def __getitem__(self, key):
if not key in self._keys: if key not in self._keys:
raise KeyError(key) raise KeyError(key)
return self._fn(key) return self._fn(key)
...@@ -422,7 +422,7 @@ class CtxCachedObject(object): ...@@ -422,7 +422,7 @@ class CtxCachedObject(object):
self._ctx_dict = {} self._ctx_dict = {}
def __call__(self, ctx): def __call__(self, ctx):
if not ctx in self._ctx_dict: if ctx not in self._ctx_dict:
self._ctx_dict[ctx] = self._generator(ctx) self._ctx_dict[ctx] = self._generator(ctx)
return self._ctx_dict[ctx] return self._ctx_dict[ctx]
...@@ -445,7 +445,7 @@ def cached_member(cache, prefix): ...@@ -445,7 +445,7 @@ def cached_member(cache, prefix):
def wrapper(self, *args): def wrapper(self, *args):
dic = getattr(self, cache) dic = getattr(self, cache)
key = '%s-%s' % (prefix, '-'.join([str(a) for a in args])) key = '%s-%s' % (prefix, '-'.join([str(a) for a in args]))
if not key in dic: if key not in dic:
dic[key] = func(self, *args) dic[key] = func(self, *args)
return dic[key] return dic[key]
return wrapper return wrapper
......
...@@ -117,8 +117,10 @@ def test_query(): ...@@ -117,8 +117,10 @@ def test_query():
assert g.out_degree(9) == 1 assert g.out_degree(9) == 1
assert F.allclose(g.out_degrees([8, 9]), F.tensor([0, 1])) assert F.allclose(g.out_degrees([8, 9]), F.tensor([0, 1]))
assert np.array_equal(F.sparse_to_numpy(g.adjacency_matrix()), scipy_coo_input().toarray().T) assert np.array_equal(
assert np.array_equal(F.sparse_to_numpy(g.adjacency_matrix(transpose=True)), scipy_coo_input().toarray()) F.sparse_to_numpy(g.adjacency_matrix(transpose=False)), scipy_coo_input().toarray().T)
assert np.array_equal(
F.sparse_to_numpy(g.adjacency_matrix(transpose=True)), scipy_coo_input().toarray())
def _test(g): def _test(g):
# test twice to see whether the cached format works or not # test twice to see whether the cached format works or not
...@@ -191,8 +193,10 @@ def test_query(): ...@@ -191,8 +193,10 @@ def test_query():
assert g.out_degree(9) == 1 assert g.out_degree(9) == 1
assert F.allclose(g.out_degrees([8, 9]), F.tensor([0, 1])) assert F.allclose(g.out_degrees([8, 9]), F.tensor([0, 1]))
assert np.array_equal(F.sparse_to_numpy(g.adjacency_matrix()), scipy_coo_input().toarray().T) assert np.array_equal(
assert np.array_equal(F.sparse_to_numpy(g.adjacency_matrix(transpose=True)), scipy_coo_input().toarray()) F.sparse_to_numpy(g.adjacency_matrix(transpose=False)), scipy_coo_input().toarray().T)
assert np.array_equal(
F.sparse_to_numpy(g.adjacency_matrix(transpose=True)), scipy_coo_input().toarray())
def _test_csr(g): def _test_csr(g):
# test twice to see whether the cached format works or not # test twice to see whether the cached format works or not
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment