From 7fe6d0c85732d57a95cd2260fce1a2e1fd93489c Mon Sep 17 00:00:00 2001 From: Minjie Wang Date: Thu, 8 Nov 2018 22:57:16 -0500 Subject: [PATCH] [Doc] Refactor API doc (#128) * refactor API doc; add markup link in tutorials * fix readme --- README.md | 4 +- docs/.gitignore | 3 +- docs/clean.sh | 6 + docs/source/api/python/batch.rst | 16 +-- docs/source/api/python/function.rst | 25 ++++ docs/source/api/python/graph.rst | 113 +++++++++++++++++- docs/source/api/python/index.rst | 6 +- docs/source/api/python/traversal.rst | 12 ++ docs/source/conf.py | 18 +++ python/dgl/batched_graph.py | 2 +- python/dgl/graph.py | 167 +++++++++++++++++++++++++-- tutorials/1_first.py | 8 +- tutorials/2_basics.py | 26 +++-- tutorials/3_pagerank.py | 19 +-- tutorials/models/1_gcn.py | 2 +- 15 files changed, 379 insertions(+), 48 deletions(-) create mode 100755 docs/clean.sh create mode 100644 docs/source/api/python/function.rst create mode 100644 docs/source/api/python/traversal.rst diff --git a/README.md b/README.md index 8c51726616..56e526b0f5 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # Deep Graph Library -[![Build Status](http://34.239.175.180:80/buildStatus/icon?job=DGL/master)](http://34.239.175.180:80/job/DGL/job/master/) +[![Build Status](http://ci.dgl.ai:80/buildStatus/icon?job=DGL/master)](http://ci.dgl.ai:80/job/DGL/job/master/) [![GitHub license](https://dmlc.github.io/img/apache2.svg)](./LICENSE) For how to install and how to play with DGL, please read our -[Documentation](http://216.165.71.225:23232/index.html) +[Documentation](http://docs.dgl.ai) ## Contribution rules diff --git a/docs/.gitignore b/docs/.gitignore index 6f8d9032e1..d2787b11e1 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,4 +1,5 @@ build # tutorials are auto-generated -tutorials +source/tutorials +source/generated diff --git a/docs/clean.sh b/docs/clean.sh new file mode 100755 index 0000000000..3f87a0a8d6 --- /dev/null +++ b/docs/clean.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +make clean +rm -rf build +rm -rf source/tutorials +rm -rf source/generated diff --git a/docs/source/api/python/batch.rst b/docs/source/api/python/batch.rst index e94a72ee87..77f7d071f8 100644 --- a/docs/source/api/python/batch.rst +++ b/docs/source/api/python/batch.rst @@ -1,9 +1,11 @@ -dgl.BatchedDGLGraph -------------------- -.. autoclass:: dgl.BatchedDGLGraph - :members: - :show-inheritance: +BatchedDGLGraph +=============== -.. autofunction:: dgl.batch +.. automodule:: dgl.batched_graph +.. autoclass:: BatchedDGLGraph -.. autofunction:: dgl.unbatch +.. autosummary:: + :toctree: ../../generated/ + + batch + unbatch diff --git a/docs/source/api/python/function.rst b/docs/source/api/python/function.rst new file mode 100644 index 0000000000..31e81f3d84 --- /dev/null +++ b/docs/source/api/python/function.rst @@ -0,0 +1,25 @@ +.. _apifunction: + +Builtin functions +================= + +.. automodule:: dgl.function + +Message functions +----------------- + +.. autosummary:: + :toctree: ../../generated/ + + copy_src + copy_edge + src_mul_edge + +Reduce functions +---------------- + +.. autosummary:: + :toctree: ../../generated/ + + sum + max diff --git a/docs/source/api/python/graph.rst b/docs/source/api/python/graph.rst index d98f8ec7cd..0c5cc9bc74 100644 --- a/docs/source/api/python/graph.rst +++ b/docs/source/api/python/graph.rst @@ -1,7 +1,108 @@ -dgl.DGLGraph ------------- -.. automodule:: dgl.graph +.. _apigraph: -.. autoclass:: dgl.DGLGraph - :members: - :inherited-members: +DGLGraph -- Graph with node/edge features +========================================= + +.. currentmodule:: dgl +.. autoclass:: DGLGraph + +Adding nodes and edges +---------------------- + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.__init__ + DGLGraph.add_nodes + DGLGraph.add_edge + DGLGraph.add_edges + DGLGraph.clear + +Querying graph structure +------------------------ + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.number_of_nodes + DGLGraph.number_of_edges + DGLGraph.__len__ + DGLGraph.is_multigraph + DGLGraph.has_node + DGLGraph.has_nodes + DGLGraph.__contains__ + DGLGraph.has_edge_between + DGLGraph.has_edges_between + DGLGraph.predecessors + DGLGraph.successors + DGLGraph.edge_id + DGLGraph.edge_ids + DGLGraph.find_edges + DGLGraph.in_edges + DGLGraph.out_edges + DGLGraph.all_edges + DGLGraph.in_degree + DGLGraph.in_degrees + DGLGraph.out_degree + DGLGraph.out_degrees + +Transforming graph +------------------ + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.subgraph + DGLGraph.subgraphs + DGLGraph.edge_subgraph + DGLGraph.line_graph + +Converting from/to other format +------------------------------- + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.to_networkx + DGLGraph.from_networkx + DGLGraph.from_scipy_sparse_matrix + DGLGraph.adjacency_matrix + DGLGraph.incidence_matrix + +Using Node/edge features +------------------------ + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.nodes + DGLGraph.edges + DGLGraph.ndata + DGLGraph.edata + DGLGraph.node_attr_schemes + DGLGraph.edge_attr_schemes + DGLGraph.set_n_initializer + DGLGraph.set_e_initializer + +Computing with DGLGraph +----------------------- + +.. autosummary:: + :toctree: ../../generated/ + + DGLGraph.register_message_func + DGLGraph.register_reduce_func + DGLGraph.register_apply_node_func + DGLGraph.register_apply_edge_func + DGLGraph.apply_nodes + DGLGraph.apply_edges + DGLGraph.send + DGLGraph.recv + DGLGraph.send_and_recv + DGLGraph.pull + DGLGraph.push + DGLGraph.update_all + DGLGraph.prop_nodes + DGLGraph.prop_edges + DGLGraph.filter_nodes + DGLGraph.filter_edges diff --git a/docs/source/api/python/index.rst b/docs/source/api/python/index.rst index 220e6830bd..7475244cdd 100644 --- a/docs/source/api/python/index.rst +++ b/docs/source/api/python/index.rst @@ -1,8 +1,10 @@ -Python APIs -=========== +API Reference +============= .. toctree:: :maxdepth: 2 graph batch + function + traversal diff --git a/docs/source/api/python/traversal.rst b/docs/source/api/python/traversal.rst new file mode 100644 index 0000000000..689921b984 --- /dev/null +++ b/docs/source/api/python/traversal.rst @@ -0,0 +1,12 @@ +Graph Traversal +=============== + +.. automodule:: dgl.traversal + +.. autosummary:: + :toctree: ../../generated/ + + bfs_nodes_generator + topological_nodes_generator + dfs_edges_generator + dfs_labeled_edges_generator diff --git a/docs/source/conf.py b/docs/source/conf.py index 862bce44d9..6f6906662b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -182,14 +182,32 @@ epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- +autosummary_generate = True + +intersphinx_mapping = { + 'python': ('https://docs.python.org/{.major}'.format(sys.version_info), None), + 'numpy': ('http://docs.scipy.org/doc/numpy/', None), + 'scipy': ('http://docs.scipy.org/doc/scipy/reference', None), + 'matplotlib': ('http://matplotlib.org/', None), + 'networkx' : ('https://networkx.github.io/documentation/stable', None), +} # sphinx gallery configurations from sphinx_gallery.sorting import FileNameSortKey examples_dirs = ['../../tutorials'] # path to find sources gallery_dirs = ['tutorials'] # path to generate docs +reference_url = { + 'dgl' : None, + 'numpy': 'http://docs.scipy.org/doc/numpy/', + 'scipy': 'http://docs.scipy.org/doc/scipy/reference', + 'matplotlib': 'http://matplotlib.org/', + 'networkx' : 'https://networkx.github.io/documentation/stable', +} sphinx_gallery_conf = { + 'backreferences_dir' : 'generated/backreferences', + 'doc_module' : ('dgl', 'numpy'), 'examples_dirs' : examples_dirs, 'gallery_dirs' : gallery_dirs, 'within_subsection_order' : FileNameSortKey, diff --git a/python/dgl/batched_graph.py b/python/dgl/batched_graph.py index cc1b113755..a23fb09c65 100644 --- a/python/dgl/batched_graph.py +++ b/python/dgl/batched_graph.py @@ -13,7 +13,7 @@ from . import utils __all__ = ['BatchedDGLGraph', 'batch', 'unbatch', 'split'] class BatchedDGLGraph(DGLGraph): - """The batched DGL graph. + """Class for batched DGL graphs. The batched graph is read-only. diff --git a/python/dgl/graph.py b/python/dgl/graph.py index 71203073a4..2e9872c3ce 100644 --- a/python/dgl/graph.py +++ b/python/dgl/graph.py @@ -19,22 +19,117 @@ from .view import NodeView, EdgeView __all__ = ['DGLGraph'] class DGLGraph(object): - """Base graph class specialized for neural networks on graphs. + """Base graph class. - TODO(minjie): document of batching semantics + The graph stores nodes, edges and also their features. + + DGL graph is always directional. Undirected graph can be represented using + two bi-directional edges. + + Nodes are identified by consecutive integers starting from zero. + + Edges can be specified by two end points (u, v) or the integer id assigned + when the edges are added. + + Node and edge features are stored as a dictionary from the feature name + to the feature data (in tensor). Parameters ---------- - graph_data : graph data + graph_data : graph data, optional Data to initialize graph. Same as networkx's semantics. - node_frame : FrameRef + node_frame : FrameRef, optional Node feature storage. - edge_frame : FrameRef + edge_frame : FrameRef, optional Edge feature storage. multigraph : bool, optional Whether the graph would be a multigraph (default: False) readonly : bool, optional Whether the graph structure is read-only (default: False). + + Examples + -------- + Create an empty graph with no nodes and edges. + + >>> G = dgl.DGLGraph() + + G can be grown in several ways. + + **Nodes:** + + Add N nodes: + + >>> G.add_nodes(10) # 10 isolated nodes are added + + **Edges:** + + Add one edge at a time, + + >>> G.add_edge(0, 1) + + or multiple edges, + + >>> G.add_edges([1, 2, 3], [3, 4, 5]) # three edges: 1->3, 2->4, 3->5 + + or multiple edges starting from the same node, + + >>> G.add_edges(4, [7, 8, 9]) # three edges: 4->7, 4->8, 4->9 + + or multiple edges pointing to the same node, + + >>> G.add_edges([2, 6, 8], 5) # three edges: 2->5, 6->5, 8->5 + + or multiple edges using tensor type (demo in pytorch syntax). + + >>> import torch as th + >>> G.add_edges(th.tensor([3, 4, 5]), 1) # three edges: 3->1, 4->1, 5->1 + + NOTE: Removing nodes and edges is not supported by DGLGraph. + + **Features:** + + Both nodes and edges can have feature data. Features are stored as + key/value pair. The key must be hashable while the value must be tensor + type. Features are batched on the first dimension. + + Use G.ndata to get/set features for all nodes. + + >>> G = dgl.DGLGraph() + >>> G.add_nodes(3) + >>> G.ndata['x'] = th.zeros((3, 5)) # init 3 nodes with zero vector(len=5) + >>> G.ndata + {'x' : tensor([[0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0.]])} + + Use G.nodes to get/set features for some nodes. + + >>> G.nodes[[0, 2]].data['x'] = th.ones((2, 5)) + >>> G.ndata + {'x' : tensor([[1., 1., 1., 1., 1.], + [0., 0., 0., 0., 0.], + [1., 1., 1., 1., 1.]])} + + Similarly, use G.edata and G.edges to get/set features for edges. + + >>> G.add_edges([0, 1], 2) # 0->2, 1->2 + >>> G.edata['y'] = th.zeros((2, 4)) # init 2 edges with zero vector(len=4) + >>> G.edata + {'y' : tensor([[0., 0., 0., 0.], + [0., 0., 0., 0.]])} + >>> G.edges[1, 2].data['y'] = th.ones((1, 4)) + >>> G.edata + {'y' : tensor([[0., 0., 0., 0.], + [1., 1., 1., 1.]])} + + Note that each edge is assigned a unique id equal to its adding + order. So edge 1->2 has id=1. DGL supports directly use edge id + to access edge features. + + >>> G.edges[0].data['y'] += 2. + >>> G.edata + {'y' : tensor([[2., 2., 2., 2.], + [1., 1., 1., 1.]])} """ def __init__(self, graph_data=None, @@ -89,6 +184,10 @@ class DGLGraph(object): The dst node. reprs : dict Optional edge representation. + + See Also + -------- + add_edges """ self._graph.add_edge(u, v) #TODO(minjie): change frames @@ -109,6 +208,10 @@ class DGLGraph(object): The dst nodes. reprs : dict Optional node representations. + + See Also + -------- + add_edge """ u = utils.toindex(u) v = utils.toindex(v) @@ -178,6 +281,10 @@ class DGLGraph(object): ------- bool True if the node exists + + See Also + -------- + has_nodes """ return self.has_node(vid) @@ -197,6 +304,10 @@ class DGLGraph(object): ------- tensor 0-1 array indicating existence + + See Also + -------- + has_node """ vids = utils.toindex(vids) rst = self._graph.has_nodes(vids) @@ -216,6 +327,10 @@ class DGLGraph(object): ------- bool True if the edge exists + + See Also + -------- + has_edges_between """ return self._graph.has_edge_between(u, v) @@ -233,6 +348,10 @@ class DGLGraph(object): ------- tensor 0-1 array indicating existence + + See Also + -------- + has_edge_between """ u = utils.toindex(u) v = utils.toindex(v) @@ -291,6 +410,10 @@ class DGLGraph(object): int or tensor The edge id if force_multi == True and the graph is a simple graph. The edge id array otherwise. + + See Also + -------- + edge_ids """ idx = self._graph.edge_id(u, v) return idx.tousertensor() if force_multi or self.is_multigraph else idx[0] @@ -313,6 +436,10 @@ class DGLGraph(object): tensor, or (tensor, tensor, tensor) If force_multi is True or the graph is multigraph, return (src nodes, dst nodes, edge ids) Otherwise, return a single tensor of edge ids. + + See Also + -------- + edge_id """ u = utils.toindex(u) v = utils.toindex(v) @@ -332,8 +459,10 @@ class DGLGraph(object): Returns ------- - tensor, tensor - The source and destination node IDs. + tensor + The source nodes. + tensor + The destination nodes. """ eid = utils.toindex(eid) src, dst, _ = self._graph.find_edges(eid) @@ -440,6 +569,10 @@ class DGLGraph(object): ------- int The in degree. + + See Also + -------- + in_degrees """ return self._graph.in_degree(v) @@ -455,6 +588,10 @@ class DGLGraph(object): ------- tensor The in degree array. + + See Also + -------- + in_degree """ v = utils.toindex(v) return self._graph.in_degrees(v).tousertensor() @@ -471,6 +608,10 @@ class DGLGraph(object): ------- int The out degree. + + See Also + -------- + out_degrees """ return self._graph.out_degree(v) @@ -486,6 +627,10 @@ class DGLGraph(object): ------- tensor The out degree array. + + See Also + -------- + out_degree """ v = utils.toindex(v) return self._graph.out_degrees(v).tousertensor() @@ -1273,6 +1418,10 @@ class DGLGraph(object): ------- G : DGLSubGraph The subgraph. + + See Also + -------- + subgraphs """ induced_nodes = utils.toindex(nodes) sgi = self._graph.node_subgraph(induced_nodes) @@ -1291,6 +1440,10 @@ class DGLGraph(object): ------- G : A list of DGLSubGraph The subgraphs. + + See Also + -------- + subgraph """ induced_nodes = [utils.toindex(n) for n in nodes] sgis = self._graph.node_subgraphs(induced_nodes) diff --git a/tutorials/1_first.py b/tutorials/1_first.py index 8c8cf2d766..b9427030c6 100644 --- a/tutorials/1_first.py +++ b/tutorials/1_first.py @@ -1,11 +1,13 @@ """ +.. currentmodule:: dgl + DGL at a glance ========================= **Author**: Minjie Wang, Quan Gan, Zheng Zhang The goal of DGL is to build, train, and deploy *machine learning models* -on *graph-structured data*. To achieve this, DGL provides a ``DGLGraph`` +on *graph-structured data*. To achieve this, DGL provides a :class:`DGLGraph` class that defines the graph structure and the information on its nodes and edges. It also provides a set of feature transformation methods and message passing methods to propagate information between nodes and edges. @@ -54,7 +56,7 @@ def an_interesting_graph(): return g ############################################################################### -# One thing to be aware of is that DGL graphs are directional: +# One thing to be aware of is that :class:`DGLGraph` is directional: g_boring = a_boring_graph() g_better = an_interesting_graph() @@ -96,7 +98,7 @@ def super_useful_comp(g): return readout(g) ############################################################################### -# The point is, regardless of what kind of graphs and the form of repretations, +# The point is, regardless of what kind of graphs and the form of representations, # DGL handles it uniformly and efficiently. g_boring = a_boring_graph() diff --git a/tutorials/2_basics.py b/tutorials/2_basics.py index 3d42773fe2..128e1d56fd 100644 --- a/tutorials/2_basics.py +++ b/tutorials/2_basics.py @@ -1,4 +1,6 @@ """ +.. currentmodule:: dgl + DGL Basics ========== @@ -13,8 +15,8 @@ The Goal of this tutorial: ############################################################################### # Graph Creation # -------------- -# The design of ``DGLGraph`` was influenced by other graph libraries. Indeed, -# you can create a graph from networkx, and convert it into a ``DGLGraph`` and +# The design of :class:`DGLGraph` was influenced by other graph libraries. Indeed, +# you can create a graph from networkx, and convert it into a :class:`DGLGraph` and # vice versa: import networkx as nx @@ -33,13 +35,14 @@ plt.show() ############################################################################### -# They are the same graph, except that DGLGraph are *always* directional. +# They are the same graph, except that :class:`DGLGraph` is *always* directional. # # One can also create a graph by calling DGL's own interface. # -# Now let's build a star graph. DGLGraph nodes are consecutive range of -# integers between 0 and ``g.number_of_nodes()`` and can grow by calling -# ``g.add_nodes``. DGLGraph edges are in order of their additions. Note that +# Now let's build a star graph. :class:`DGLGraph` nodes are consecutive range of +# integers between 0 and :func:`number_of_nodes() ` +# and can grow by calling :func:`add_nodes `. +# :class:`DGLGraph` edges are in order of their additions. Note that # edges are accessed in much the same way as nodes, with one extra feature # of *edge broadcasting*: @@ -72,11 +75,11 @@ plt.show() ############################################################################### # Feature Assignment # ------------------ -# One can also assign features to nodes and edges of a ``DGLGraph``. The +# One can also assign features to nodes and edges of a :class:`DGLGraph`. The # features are represented as dictionary of names (strings) and tensors, # called **fields**. # -# The following code snippet assigns each node a 3-D vector. +# The following code snippet assigns each node a vector (len=3). # # .. note:: # @@ -91,8 +94,9 @@ g.ndata['x'] = x ############################################################################### -# ``ndata`` is a syntax sugar to access states of all nodes, states are stored -# in a container `data` that hosts user defined dictionary. +# :func:`ndata ` is a syntax sugar to access states of all nodes, +# states are stored +# in a container ``data`` that hosts user defined dictionary. print(g.ndata['x'] == g.nodes[:].data['x']) @@ -138,7 +142,7 @@ g.edata.pop('w') ############################################################################### # Multigraphs # ~~~~~~~~~~~ -# Many graph applications need multi-edges. To enable this, construct DGLGraph +# Many graph applications need multi-edges. To enable this, construct :class:`DGLGraph` # with ``multigraph=True``. g_multi = dgl.DGLGraph(multigraph=True) diff --git a/tutorials/3_pagerank.py b/tutorials/3_pagerank.py index 2453854b72..4224d9d0ad 100644 --- a/tutorials/3_pagerank.py +++ b/tutorials/3_pagerank.py @@ -1,4 +1,6 @@ """ +.. currentmodule:: dgl + PageRank with DGL Message Passing ================================= @@ -34,7 +36,7 @@ interface. # A naive implementation # ---------------------- # Let us first create a graph with 100 nodes with NetworkX and convert it to a -# ``DGLGraph``: +# :class:`DGLGraph`: import networkx as nx import matplotlib.pyplot as plt @@ -153,8 +155,8 @@ def pagerank_level2(g): ############################################################################### # Besides ``update_all``, we also have ``pull``, ``push``, and ``send_and_recv`` -# in this level-2 category. Please refer to their own API reference documents -# for more details. (TODO: a link to the document). +# in this level-2 category. Please refer to the :doc:`API reference <../api/python/graph>` +# for more details. ############################################################################### @@ -164,11 +166,13 @@ def pagerank_level2(g): # provides **builtin functions**. For example, two builtin functions can be # used in the PageRank example. # -# * ``dgl.function.copy_src(src, out)`` is an edge UDF that computes the +# * :func:`dgl.function.copy_src(src, out) ` +# is an edge UDF that computes the # output using the source node feature data. User needs to specify the name of # the source feature data (``src``) and the output name (``out``). # -# * ``dgl.function.sum(msg, out)`` is a node UDF that sums the messages in +# * :func:`dgl.function.sum(msg, out) ` is a node UDF +# that sums the messages in # the node's mailbox. User needs to specify the message name (``msg``) and the # output name (``out``). # @@ -184,7 +188,8 @@ def pagerank_builtin(g): ############################################################################### -# Here, we directly provide the UDFs to the `update_all` as its arguments. +# Here, we directly provide the UDFs to the :func:`update_all ` +# as its arguments. # This will override the previously registered UDFs. # # In addition to cleaner code, using builtin functions also gives DGL the @@ -194,7 +199,7 @@ def pagerank_builtin(g): # # `This section `_ describes why spMV can speed up the scatter-gather # phase in PageRank. For more details about the builtin functions in DGL, -# please read their API reference documents. (TODO: a link here). +# please read the :doc:`API reference <../api/python/function>`. # # You can also download and run the codes to feel the difference. diff --git a/tutorials/models/1_gcn.py b/tutorials/models/1_gcn.py index 7434acbcdb..cee5b13ed9 100644 --- a/tutorials/models/1_gcn.py +++ b/tutorials/models/1_gcn.py @@ -160,4 +160,4 @@ for epoch in range(30): # multiplication kernels (such as Kipf's # `pygcn `_ code). The above DGL implementation # in fact has already used this trick due to the use of builtin functions. To -# understand what is under the hood, please read our tutorial on :doc:` PageRank <3_pagerank>`. +# understand what is under the hood, please read our tutorial on :doc:`PageRank <../3_pagerank>`.