Commit 7cca5007 authored by Mufei Li's avatar Mufei Li Committed by Minjie Wang
Browse files

[Doc] Computing with DGLGraph (#212)

* Docs for graph computing

* Docs for graph computing

* Fix

* WIP

* update the docstring
parent c99f4237
...@@ -4,6 +4,16 @@ User-defined function related data structures ...@@ -4,6 +4,16 @@ User-defined function related data structures
================================================== ==================================================
.. currentmodule:: dgl.udf .. currentmodule:: dgl.udf
.. automodule:: dgl.udf
There are two types of user-defined functions in DGL:
* **Node UDF** of signature ``NodeBatch -> dict``. The argument represents
a batch of nodes. The returned dictionary should have ``str`` type key and ``tensor``
type values.
* **Edge UDF** of signature ``EdgeBatch -> dict``. The argument represents
a batch of edges. The returned dictionary should have ``str`` type key and ``tensor``
type values.
EdgeBatch EdgeBatch
--------- ---------
......
...@@ -81,7 +81,11 @@ class DGLGraph(object): ...@@ -81,7 +81,11 @@ class DGLGraph(object):
>>> G.add_edges([2, 6, 8], 5) # three edges: 2->5, 6->5, 8->5 >>> 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). or multiple edges using tensor type
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import torch as th >>> import torch as th
>>> G.add_edges(th.tensor([3, 4, 5]), 1) # three edges: 3->1, 4->1, 5->1 >>> G.add_edges(th.tensor([3, 4, 5]), 1) # three edges: 3->1, 4->1, 5->1
...@@ -132,6 +136,39 @@ class DGLGraph(object): ...@@ -132,6 +136,39 @@ class DGLGraph(object):
>>> G.edata >>> G.edata
{'y' : tensor([[2., 2., 2., 2.], {'y' : tensor([[2., 2., 2., 2.],
[1., 1., 1., 1.]])} [1., 1., 1., 1.]])}
**Message Passing:**
One common operation for updating node features is message passing,
where the source nodes send messages through edges to the destinations.
With :class:`DGLGraph`, we can do this with :func:`send` and :func:`recv`.
In the example below, the source nodes add 1 to their node features as
the messages and send the messages to the destinations.
>>> # Define the function for sending messages.
>>> def send_source(edges): return {'m': edges.src['x'] + 1}
>>> # Set the function defined to be the default message function.
>>> G.register_message_func(send_source)
>>> # Send messages through all edges.
>>> G.send(G.edges())
Just like you need to go to your mailbox for retrieving mails, the destination
nodes also need to receive the messages and potentially update their features.
>>> # Define a function for summing messages received and replacing the original feature.
>>> def simple_reduce(nodes): return {'x': nodes.mailbox['m'].sum(1)}
>>> # Set the function defined to be the default message reduce function.
>>> G.register_reduce_func(simple_reduce)
>>> # All existing edges have node 2 as the destination.
>>> # Receive the messages for node 2 and update its feature.
>>> G.recv(v=2)
>>> G.ndata
{'x': tensor([[1., 1., 1., 1., 1.],
[0., 0., 0., 0., 0.],
[3., 3., 3., 3., 3.]])} # 3 = (1 + 1) + (0 + 1)
For more examples about message passing, please read our tutorials.
""" """
def __init__(self, def __init__(self,
graph_data=None, graph_data=None,
...@@ -188,7 +225,12 @@ class DGLGraph(object): ...@@ -188,7 +225,12 @@ class DGLGraph(object):
>>> g.number_of_nodes() >>> g.number_of_nodes()
5 5
Adding new nodes with features (using PyTorch as example): Adding new nodes with features:
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import torch as th >>> import torch as th
>>> g.add_nodes(2, {'x': th.ones(2, 4)}) # default zero initializer >>> g.add_nodes(2, {'x': th.ones(2, 4)}) # default zero initializer
>>> g.ndata['x'] >>> g.ndata['x']
...@@ -235,6 +277,11 @@ class DGLGraph(object): ...@@ -235,6 +277,11 @@ class DGLGraph(object):
>>> G.add_edge(0, 1) >>> G.add_edge(0, 1)
Adding new edge with features Adding new edge with features
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import torch as th >>> import torch as th
>>> G.add_edge(0, 2, {'x': th.ones(1, 4)}) >>> G.add_edge(0, 2, {'x': th.ones(1, 4)})
>>> G.edges() >>> G.edges()
...@@ -284,6 +331,11 @@ class DGLGraph(object): ...@@ -284,6 +331,11 @@ class DGLGraph(object):
>>> G.add_edges([0, 2], [1, 3]) # add edges (0, 1) and (2, 3) >>> G.add_edges([0, 2], [1, 3]) # add edges (0, 1) and (2, 3)
Adding new edges with features Adding new edges with features
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import torch as th >>> import torch as th
>>> G.add_edges([1, 3], [2, 0], {'x': th.ones(2, 4)}) # (1, 2), (3, 0) >>> G.add_edges([1, 3], [2, 0], {'x': th.ones(2, 4)}) # (1, 2), (3, 0)
>>> G.edata['x'] >>> G.edata['x']
...@@ -1356,56 +1408,133 @@ class DGLGraph(object): ...@@ -1356,56 +1408,133 @@ class DGLGraph(object):
def register_message_func(self, func): def register_message_func(self, func):
"""Register global message function. """Register global message function.
Once registered, ``func`` will be used as the default
message function in message passing operations, including
:func:`send`, :func:`send_and_recv`, :func:`pull`,
:func:`push`, :func:`update_all`.
Parameters Parameters
---------- ----------
func : callable func : callable
Message function on the edge. Message function on the edge. The function should be
an :mod:`Edge UDF <dgl.udf>`.
See Also
--------
send
send_and_recv
pull
push
update_all
""" """
self._message_func = func self._message_func = func
def register_reduce_func(self, func): def register_reduce_func(self, func):
"""Register global message reduce function. """Register global message reduce function.
Once registered, ``func`` will be used as the default
message reduce function in message passing operations, including
:func:`recv`, :func:`send_and_recv`, :func:`push`, :func:`pull`,
:func:`update_all`.
Parameters Parameters
---------- ----------
func : str or callable func : callable
Reduce function on incoming edges. Reduce function on the node. The function should be
a :mod:`Node UDF <dgl.udf>`.
See Also
--------
recv
send_and_recv
push
pull
update_all
""" """
self._reduce_func = func self._reduce_func = func
def register_apply_node_func(self, func): def register_apply_node_func(self, func):
"""Register global node apply function. """Register global node apply function.
Once registered, ``func`` will be used as the default apply
node function. Related operations include :func:`apply_nodes`,
:func:`recv`, :func:`send_and_recv`, :func:`push`, :func:`pull`,
:func:`update_all`.
Parameters Parameters
---------- ----------
func : callable func : callable
Apply function on the node. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
See Also
--------
apply_nodes
register_apply_edge_func
""" """
self._apply_node_func = func self._apply_node_func = func
def register_apply_edge_func(self, func): def register_apply_edge_func(self, func):
"""Register global edge apply function. """Register global edge apply function.
Once registered, ``func`` will be used as the default apply
edge function in :func:`apply_edges`.
Parameters Parameters
---------- ----------
edge_func : callable func : callable
Apply function on the edge. Apply function on the edge. The function should be
an :mod:`Edge UDF <dgl.udf>`.
See Also
--------
apply_edges
register_apply_node_func
""" """
self._apply_edge_func = func self._apply_edge_func = func
def apply_nodes(self, func="default", v=ALL, inplace=False): def apply_nodes(self, func="default", v=ALL, inplace=False):
"""Apply the function on the node features. """Apply the function on the nodes to update their features.
Applying a None function will be ignored. If None is provided for ``func``, nothing will happen.
Parameters Parameters
---------- ----------
func : callable, optional func : callable or None, optional
The UDF applied on the node features. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
v : int, iterable of int, tensor, optional v : int, iterable of int, tensor, optional
The node id(s). The node (ids) on which to apply ``func``. The default
inplace: bool, optional value is all the nodes.
inplace : bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.ones(3, 1)
>>> # Increment the node feature by 1.
>>> def increment_feature(nodes): return {'x': nodes.data['x'] + 1}
>>> g.apply_nodes(func=increment_feature, v=[0, 2]) # Apply func to nodes 0, 2
>>> g.ndata
{'x': tensor([[2.],
[1.],
[2.]])}
See Also
--------
register_apply_node_func
apply_edges
""" """
if func == "default": if func == "default":
func = self._apply_node_func func = self._apply_node_func
...@@ -1421,22 +1550,51 @@ class DGLGraph(object): ...@@ -1421,22 +1550,51 @@ class DGLGraph(object):
Runtime.run(prog) Runtime.run(prog)
def apply_edges(self, func="default", edges=ALL, inplace=False): def apply_edges(self, func="default", edges=ALL, inplace=False):
"""Apply the function on the edge features. """Apply the function on the edges to update their features.
If None is provided for ``func``, nothing will happen.
Parameters Parameters
---------- ----------
func : callable, optional func : callable, optional
The UDF applied on the edge features. Apply function on the edge. The function should be
edges : edges, optional an :mod:`Edge UDF <dgl.udf>`.
Edges can be a pair of endpoint nodes (u, v), or a edges : valid edges type, optional
tensor of edge ids. The default value is all the edges. Edges on which to apply ``func``. See :func:`send` for valid
edges type. Default is all the edges.
inplace: bool, optional inplace: bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Notes Notes
----- -----
On multigraphs, if u and v are specified, then all the edges On multigraphs, if :math:`u` and :math:`v` are specified, then all the edges
between u and v will be updated. between :math:`u` and :math:`v` will be updated.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.add_edges([0, 1], [1, 2]) # 0 -> 1, 1 -> 2
>>> g.edata['y'] = th.ones(2, 1)
>>> # Doubles the edge feature.
>>> def double_feature(edges): return {'y': edges.data['y'] * 2}
>>> g.apply_edges(func=double_feature, edges=0) # Apply func to the first edge.
>>> g.edata
{'y': tensor([[2.], # 2 * 1
[1.]])}
See Also
--------
apply_nodes
""" """
if func == "default": if func == "default":
func = self._apply_edge_func func = self._apply_edge_func
...@@ -1464,21 +1622,41 @@ class DGLGraph(object): ...@@ -1464,21 +1622,41 @@ class DGLGraph(object):
inplace=inplace) inplace=inplace)
Runtime.run(prog) Runtime.run(prog)
def send(self, edges, message_func="default"): def send(self, edges=ALL, message_func="default"):
"""Send messages along the given edges. """Send messages along the given edges.
``edges`` can be any of the following types:
* ``int`` : Specify one edge using its edge id.
* ``pair of int`` : Specify one edge using its endpoints.
* ``int iterable`` / ``tensor`` : Specify multiple edges using their edge ids.
* ``pair of int iterable`` / ``pair of tensors`` :
Specify multiple edges using their endpoints.
The UDF returns messages on the edges and can be later fetched in
the destination node's ``mailbox``. Receiving will consume the messages.
See :func:`recv` for example.
If multiple ``send`` are triggered on the same edge without ``recv``. Messages
generated by the later ``send`` will overwrite previous messages.
Parameters Parameters
---------- ----------
edges : edges, optional edges : valid edges type, optional
Edges can be a pair of endpoint nodes (u, v), or a Edges on which to apply ``message_func``. Default is sending along all
tensor of edge ids. the edges.
message_func : callable message_func : callable
The message function. Message function on the edges. The function should be
an :mod:`Edge UDF <dgl.udf>`.
Notes Notes
----- -----
On multigraphs, if u and v are specified, then the messages will be sent On multigraphs, if :math:`u` and :math:`v` are specified, then the messages will be sent
along all edges between u and v. along all edges between :math:`u` and :math:`v`.
Examples
--------
See the *message passing* example in :class:`DGLGraph` or :func:`recv`.
""" """
if message_func == "default": if message_func == "default":
message_func = self._message_func message_func = self._message_func
...@@ -1505,26 +1683,84 @@ class DGLGraph(object): ...@@ -1505,26 +1683,84 @@ class DGLGraph(object):
self._msg_graph.add_edges(u, v) self._msg_graph.add_edges(u, v)
def recv(self, def recv(self,
v, v=ALL,
reduce_func="default", reduce_func="default",
apply_node_func="default", apply_node_func="default",
inplace=False): inplace=False):
"""Receive and reduce in-coming messages and update representation on node v. """Receive and reduce incoming messages and update the features of node(s) :math:`v`.
Optionally, apply a function to update the node features after receive.
TODO(minjie): document on zero-in-degree case * `reduce_func` will be skipped for nodes with no incoming message.
TODO(minjie): document on how returned new features are merged with the old features * If all ``v`` have no incoming message, this will downgrade to an :func:`apply_nodes`.
TODO(minjie): document on how many times UDFs will be called * If some ``v`` have no incoming message, their new feature value will be calculated
by the column initializer (see :func:`set_n_initializer`). The feature shapes and
dtypes will be inferred.
The node features will be updated by the result of the ``reduce_func``.
Messages are consumed once received.
The provided UDF maybe called multiple times so it is recommended to provide
function with no side effect.
Parameters Parameters
---------- ----------
v : node, container or tensor v : node, container or tensor, optional
The node to be updated. The node to be updated. Default is receiving all the nodes.
reduce_func : callable reduce_func : callable, optional
The reduce function. Reduce function on the node. The function should be
apply_node_func : callable, optional a :mod:`Node UDF <dgl.udf>`.
The update function. apply_node_func : callable
Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
inplace: bool, optional inplace: bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Examples
--------
Create a graph object for demo.
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[1.], [2.], [3.]])
>>> g.add_edges([0, 1], [1, 2])
>>> # Define the function for sending node features as messages.
>>> def send_source(edges): return {'m': edges.src['x']}
>>> # Set the function defined to be the default message function.
>>> g.register_message_func(send_source)
>>> # Sum the messages received and use this to replace the original node feature.
>>> def simple_reduce(nodes): return {'x': nodes.mailbox['m'].sum(1)}
>>> # Set the function defined to be the default message reduce function.
>>> g.register_reduce_func(simple_reduce)
Send and receive messages. Note that although node :math:`0` has no incoming edges,
its feature gets changed from :math:`1` to :math:`0` as it is also included in
``g.nodes()``.
>>> g.send(g.edges())
>>> g.recv(g.nodes())
>>> g.ndata['x']
tensor([[0.],
[1.],
[2.]])
Once messages are received, one will need another call of :func:`send` again before
another call of :func:`recv`. Otherwise, nothing will happen.
>>> g.recv(g.nodes())
>>> g.ndata['x']
tensor([[0.],
[1.],
[2.]])
""" """
if reduce_func == "default": if reduce_func == "default":
reduce_func = self._reduce_func reduce_func = self._reduce_func
...@@ -1536,8 +1772,7 @@ class DGLGraph(object): ...@@ -1536,8 +1772,7 @@ class DGLGraph(object):
# no message has ever been sent # no message has ever been sent
return return
v_is_all = is_all(v) if is_all(v):
if v_is_all:
v = F.arange(0, self.number_of_nodes()) v = F.arange(0, self.number_of_nodes())
elif isinstance(v, int): elif isinstance(v, int):
v = [v] v = [v]
...@@ -1563,29 +1798,76 @@ class DGLGraph(object): ...@@ -1563,29 +1798,76 @@ class DGLGraph(object):
reduce_func="default", reduce_func="default",
apply_node_func="default", apply_node_func="default",
inplace=False): inplace=False):
"""Send messages along edges and receive them on the targets. """Send messages along edges and let destinations receive them.
Optionally, apply a function to update the node features after receive.
This is a convenient combination for performing
``send(self, self.edges, message_func)`` and
``recv(self, dst, reduce_func, apply_node_func)``, where ``dst``
are the destinations of the ``edges``.
Parameters Parameters
---------- ----------
edges : edges edges : valid edges type
Edges can be a pair of endpoint nodes (u, v), or a Edges on which to apply ``func``. See :func:`send` for valid
tensor of edge ids. The default value is all the edges. edges type.
message_func : callable, optional message_func : callable, optional
The message function. Registered function will be used if not Message function on the edges. The function should be
specified. an :mod:`Edge UDF <dgl.udf>`.
reduce_func : callable, optional reduce_func : callable, optional
The reduce function. Registered function will be used if not Reduce function on the node. The function should be
specified. a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional apply_node_func : callable, optional
The update function. Registered function will be used if not Apply function on the nodes. The function should be
specified. a :mod:`Node UDF <dgl.udf>`.
inplace: bool, optional inplace: bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[1.], [2.], [3.]])
>>> g.add_edges([0, 1], [1, 2])
>>> # Define the function for sending node features as messages.
>>> def send_source(edges): return {'m': edges.src['x']}
>>> # Set the function defined to be the default message function.
>>> g.register_message_func(send_source)
>>> # Sum the messages received and use this to replace the original node feature.
>>> def simple_reduce(nodes): return {'x': nodes.mailbox['m'].sum(1)}
>>> # Set the function defined to be the default message reduce function.
>>> g.register_reduce_func(simple_reduce)
Send and receive messages.
>>> g.send_and_recv(g.edges())
>>> g.ndata['x']
tensor([[1.],
[1.],
[2.]])
Note that the feature of node :math:`0` remains the same as it has no
incoming edges.
Notes Notes
----- -----
On multigraphs, if u and v are specified, then the messages will be sent On multigraphs, if u and v are specified, then the messages will be sent
and received along all edges between u and v. and received along all edges between u and v.
See Also
--------
send
recv
""" """
if message_func == "default": if message_func == "default":
message_func = self._message_func message_func = self._message_func
...@@ -1626,20 +1908,81 @@ class DGLGraph(object): ...@@ -1626,20 +1908,81 @@ class DGLGraph(object):
reduce_func="default", reduce_func="default",
apply_node_func="default", apply_node_func="default",
inplace=False): inplace=False):
"""Pull messages from the node's predecessors and then update it. """Pull messages from the node(s)' predecessors and then update their features.
Optionally, apply a function to update the node features after receive.
* `reduce_func` will be skipped for nodes with no incoming message.
* If all ``v`` have no incoming message, this will downgrade to an :func:`apply_nodes`.
* If some ``v`` have no incoming message, their new feature value will be calculated
by the column initializer (see :func:`set_n_initializer`). The feature shapes and
dtypes will be inferred.
Parameters Parameters
---------- ----------
v : node, container or tensor v : int, iterable of int, or tensor
The node to be updated. The node(s) to be updated.
message_func : callable message_func : callable, optional
The message function. Message function on the edges. The function should be
reduce_func : callable an :mod:`Edge UDF <dgl.udf>`.
The reduce function. reduce_func : callable, optional
Reduce function on the node. The function should be
a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional apply_node_func : callable, optional
The update function. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
inplace: bool, optional inplace: bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Create a graph for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[0.], [1.], [2.]])
Use the built-in message function :func:`~dgl.function.copy_src` for copying
node features as the message.
>>> m_func = dgl.function.copy_src('x', 'm')
>>> g.register_message_func(m_func)
Use the built-int message reducing function :func:`~dgl.function.sum`, which
sums the messages received and replace the old node features with it.
>>> m_reduce_func = dgl.function.sum('m', 'x')
>>> g.register_reduce_func(m_reduce_func)
As no edges exist, nothing happens.
>>> g.pull(g.nodes())
>>> g.ndata['x']
tensor([[0.],
[1.],
[2.]])
Add edges ``0 -> 1, 1 -> 2``. Pull messages for the node :math:`2`.
>>> g.add_edges([0, 1], [1, 2])
>>> g.pull(2)
>>> g.ndata['x']
tensor([[0.],
[1.],
[1.]])
The feature of node :math:`2` changes but the feature of node :math:`1`
remains the same as we did not :func:`pull` (and reduce) messages for it.
See Also
--------
push
""" """
if message_func == "default": if message_func == "default":
message_func = self._message_func message_func = self._message_func
...@@ -1669,20 +2012,75 @@ class DGLGraph(object): ...@@ -1669,20 +2012,75 @@ class DGLGraph(object):
reduce_func="default", reduce_func="default",
apply_node_func="default", apply_node_func="default",
inplace=False): inplace=False):
"""Send message from the node to its successors and update them. """Send message from the node(s) to their successors and update them.
Optionally, apply a function to update the node features after receive.
Parameters Parameters
---------- ----------
u : node, container or tensor u : int, iterable of int, or tensor
The node that sends out messages. The node(s) to push messages out.
message_func : callable message_func : callable, optional
The message function. Message function on the edges. The function should be
reduce_func : callable an :mod:`Edge UDF <dgl.udf>`.
The reduce function. reduce_func : callable, optional
apply_node_func : callable Reduce function on the node. The function should be
The update function. a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional
Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
inplace: bool, optional inplace: bool, optional
If True, update will be done in place, but autograd will break. If True, update will be done in place, but autograd will break.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Create a graph for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[1.], [2.], [3.]])
Use the built-in message function :func:`~dgl.function.copy_src` for copying
node features as the message.
>>> m_func = dgl.function.copy_src('x', 'm')
>>> g.register_message_func(m_func)
Use the built-int message reducing function :func:`~dgl.function.sum`, which
sums the messages received and replace the old node features with it.
>>> m_reduce_func = dgl.function.sum('m', 'x')
>>> g.register_reduce_func(m_reduce_func)
As no edges exist, nothing happens.
>>> g.push(g.nodes())
>>> g.ndata['x']
tensor([[1.],
[2.],
[3.]])
Add edges ``0 -> 1, 1 -> 2``. Send messages from the node :math:`1`. and update.
>>> g.add_edges([0, 1], [1, 2])
>>> g.push(1)
>>> g.ndata['x']
tensor([[1.],
[2.],
[2.]])
The feature of node :math:`2` changes but the feature of node :math:`1`
remains the same as we did not :func:`push` for node :math:`0`.
See Also
--------
pull
""" """
if message_func == "default": if message_func == "default":
message_func = self._message_func message_func = self._message_func
...@@ -1710,16 +2108,30 @@ class DGLGraph(object): ...@@ -1710,16 +2108,30 @@ class DGLGraph(object):
message_func="default", message_func="default",
reduce_func="default", reduce_func="default",
apply_node_func="default"): apply_node_func="default"):
"""Send messages through all the edges and update all nodes. """Send messages through all edges and update all nodes.
Optionally, apply a function to update the node features after receive.
This is a convenient combination for performing
``send(self, self.edges(), message_func)`` and
``recv(self, self.nodes(), reduce_func, apply_node_func)``.
Parameters Parameters
---------- ----------
message_func : callable message_func : callable, optional
The message function. Message function on the edges. The function should be
reduce_func : callable an :mod:`Edge UDF <dgl.udf>`.
The reduce function. reduce_func : callable, optional
Reduce function on the node. The function should be
a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional apply_node_func : callable, optional
The update function. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
See Also
--------
send
recv
""" """
if message_func == "default": if message_func == "default":
message_func = self._message_func message_func = self._message_func
...@@ -1742,7 +2154,8 @@ class DGLGraph(object): ...@@ -1742,7 +2154,8 @@ class DGLGraph(object):
message_func="default", message_func="default",
reduce_func="default", reduce_func="default",
apply_node_func="default"): apply_node_func="default"):
"""Propagate messages using graph traversal by triggering pull() on nodes. """Propagate messages using graph traversal by triggering
:func:`pull()` on nodes.
The traversal order is specified by the ``nodes_generator``. It generates The traversal order is specified by the ``nodes_generator``. It generates
node frontiers, which is a list or a tensor of nodes. The nodes in the node frontiers, which is a list or a tensor of nodes. The nodes in the
...@@ -1751,14 +2164,64 @@ class DGLGraph(object): ...@@ -1751,14 +2164,64 @@ class DGLGraph(object):
Parameters Parameters
---------- ----------
node_generators : generator node_generators : iterable, each element is a list or a tensor of node ids
The generator of node frontiers. The generator of node frontiers. It specifies which nodes perform
:func:`pull` at each timestep.
message_func : callable, optional message_func : callable, optional
The message function. Message function on the edges. The function should be
an :mod:`Edge UDF <dgl.udf>`.
reduce_func : callable, optional reduce_func : callable, optional
The reduce function. Reduce function on the node. The function should be
a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional apply_node_func : callable, optional
The update function. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Create a graph for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(4)
>>> g.ndata['x'] = th.tensor([[1.], [2.], [3.], [4.]])
>>> g.add_edges([0, 1, 1, 2], [1, 2, 3, 3])
Prepare message function and message reduce function for demo.
>>> def send_source(edges): return {'m': edges.src['x']}
>>> g.register_message_func(send_source)
>>> def simple_reduce(nodes): return {'x': nodes.mailbox['m'].sum(1)}
>>> g.register_reduce_func(simple_reduce)
First pull messages for nodes :math:`1, 2` with edges ``0 -> 1`` and
``1 -> 2``; and then pull messages for node :math:`3` with edges
``1 -> 3`` and ``2 -> 3``.
>>> g.prop_nodes([[1, 2], [3]])
>>> g.ndata['x']
tensor([[1.],
[1.],
[2.],
[3.]])
In the first stage, we pull messages for nodes :math:`1, 2`.
The feature of node :math:`1` is replaced by that of node :math:`0`, i.e. 1
The feature of node :math:`2` is replaced by that of node :math:`1`, i.e. 2.
Both of the replacement happen simultaneously.
In the second stage, we pull messages for node :math:`3`.
The feature of node :math:`3` becomes the sum of node :math:`1`'s feature and
:math:`2`'s feature, i.e. 1 + 2 = 3.
See Also
--------
prop_edges
""" """
for node_frontier in nodes_generator: for node_frontier in nodes_generator:
self.pull(node_frontier, self.pull(node_frontier,
...@@ -1769,24 +2232,79 @@ class DGLGraph(object): ...@@ -1769,24 +2232,79 @@ class DGLGraph(object):
message_func="default", message_func="default",
reduce_func="default", reduce_func="default",
apply_node_func="default"): apply_node_func="default"):
"""Propagate messages using graph traversal by triggering send_and_recv() on edges. """Propagate messages using graph traversal by triggering
:func:`send_and_recv()` on edges.
The traversal order is specified by the ``edges_generator``. It generates
edge frontiers. The edge frontiers should be of *valid edges type*.
See :func:`send` for more details.
The traversal order is specified by the ``edges_generator``. It Edges in the same frontier will be triggered together, while edges in
generates edge frontiers, which is a list or a tensor of edge ids or different frontiers will be triggered according to the generating order.
end points. The edges in the same frontier will be triggered together,
while edges in different frontiers will be triggered according to the
generating order.
Parameters Parameters
---------- ----------
edges_generator : generator edges_generator : generator
The generator of edge frontiers. The generator of edge frontiers.
message_func : callable, optional message_func : callable, optional
The message function. Message function on the edges. The function should be
an :mod:`Edge UDF <dgl.udf>`.
reduce_func : callable, optional reduce_func : callable, optional
The reduce function. Reduce function on the node. The function should be
a :mod:`Node UDF <dgl.udf>`.
apply_node_func : callable, optional apply_node_func : callable, optional
The update function. Apply function on the nodes. The function should be
a :mod:`Node UDF <dgl.udf>`.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Create a graph for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(4)
>>> g.ndata['x'] = th.tensor([[1.], [2.], [3.], [4.]])
>>> g.add_edges([0, 1, 1, 2], [1, 2, 3, 3])
Prepare message function and message reduce function for demo.
>>> def send_source(edges): return {'m': edges.src['x']}
>>> g.register_message_func(send_source)
>>> def simple_reduce(nodes): return {'x': nodes.mailbox['m'].sum(1)}
>>> g.register_reduce_func(simple_reduce)
First propagate messages for edges ``0 -> 1``, ``1 -> 3`` and then
propagate messages for edges ``1 -> 2``, ``2 -> 3``.
>>> g.prop_edges([([0, 1], [1, 3]), ([1, 2], [2, 3])])
>>> g.ndata['x']
tensor([[1.],
[1.],
[1.],
[3.]])
In the first stage, the following happens simultaneously.
- The feature of node :math:`1` is replaced by that of
node :math:`0`, i.e. 1.
- The feature of node :math:`3` is replaced by that of
node :math:`1`, i.e. 2.
In the second stage, the following happens simultaneously.
- The feature of node :math:`2` is replaced by that of
node :math:`1`, i.e. 1.
- The feature of node :math:`3` is replaced by that of
node :math:`2`, i.e. 3.
See Also
--------
prop_nodes
""" """
for edge_frontier in edges_generator: for edge_frontier in edges_generator:
self.send_and_recv(edge_frontier, self.send_and_recv(edge_frontier,
...@@ -1843,7 +2361,9 @@ class DGLGraph(object): ...@@ -1843,7 +2361,9 @@ class DGLGraph(object):
nodes in the list. nodes in the list.
Equivalent to Equivalent to
``
[self.subgraph(nodes_list) for nodes_list in nodes] [self.subgraph(nodes_list) for nodes_list in nodes]
``
Parameters Parameters
---------- ----------
...@@ -1943,27 +2463,29 @@ class DGLGraph(object): ...@@ -1943,27 +2463,29 @@ class DGLGraph(object):
or not. or not.
There are three types of an incidence matrix :math:`I`: There are three types of an incidence matrix :math:`I`:
* "in": * ``in``:
- :math:`I[v, e] = 1` if e is the in-edge of v (or v is the dst node of e); - :math:`I[v, e] = 1` if :math:`e` is the in-edge of :math:`v`
- :math:`I[v, e] = 0` otherwise. (or :math:`v` is the dst node of :math:`e`);
- :math:`I[v, e] = 0` otherwise.
* "out":
* ``out``:
- :math:`I[v, e] = 1` if e is the out-edge of v (or v is the src node of e);
- :math:`I[v, e] = 0` otherwise. - :math:`I[v, e] = 1` if :math:`e` is the out-edge of :math:`v`
(or :math:`v` is the src node of :math:`e`);
* "both": - :math:`I[v, e] = 0` otherwise.
- :math:`I[v, e] = 1` if e is the in-edge of v; * ``both``:
- :math:`I[v, e] = -1` if e is the out-edge of v;
- :math:`I[v, e] = 0` otherwise (including self-loop). - :math:`I[v, e] = 1` if :math:`e` is the in-edge of :math:`v`;
- :math:`I[v, e] = -1` if :math:`e` is the out-edge of :math:`v`;
- :math:`I[v, e] = 0` otherwise (including self-loop).
Parameters Parameters
---------- ----------
type : str type : str
Can be either "in", "out" or "both" Can be either ``in``, ``out`` or ``both``
ctx : context, optional (default=cpu) ctx : context, optional (default=cpu)
The context of returned incidence matrix. The context of returned incidence matrix.
...@@ -2000,16 +2522,45 @@ class DGLGraph(object): ...@@ -2000,16 +2522,45 @@ class DGLGraph(object):
Parameters Parameters
---------- ----------
predicate : callable predicate : callable
The predicate should take in a NodeBatch object, and return a A function of signature ``func(nodes) -> tensor``.
boolean tensor with N elements indicating which node satisfy ``nodes`` are :class:`NodeBatch` objects as in :mod:`~dgl.udf`.
the predicate. The ``tensor`` returned should be a 1-D boolean tensor with
nodes : container or tensor each element indicating whether the corresponding node in
The nodes to filter on the batch satisfies the predicate.
nodes : int, iterable or tensor of ints
The nodes to filter on. Default value is all the nodes.
Returns Returns
------- -------
tensor tensor
The filtered nodes The filtered nodes.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Construct a graph object for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[1.], [-1.], [1.]])
Define a function for filtering nodes with feature :math:`1`.
>>> def has_feature_one(nodes): return (nodes.data['x'] == 1).squeeze(1)
Filter the nodes with feature :math:`1`.
>>> g.filter_nodes(has_feature_one)
tensor([0, 2])
See Also
--------
filter_edges
""" """
if is_all(nodes): if is_all(nodes):
v = utils.toindex(slice(0, self.number_of_nodes())) v = utils.toindex(slice(0, self.number_of_nodes()))
...@@ -2032,17 +2583,48 @@ class DGLGraph(object): ...@@ -2032,17 +2583,48 @@ class DGLGraph(object):
Parameters Parameters
---------- ----------
predicate : callable predicate : callable
The predicate should take in an EdgeBatch object, and return a A function of signature ``func(edges) -> tensor``.
boolean tensor with E elements indicating which edge satisfy ``edges`` are :class:`EdgeBatch` objects as in :mod:`~dgl.udf`.
the predicate. The ``tensor`` returned should be a 1-D boolean tensor with
edges : edges each element indicating whether the corresponding edge in
Edges can be a pair of endpoint nodes (u, v), or a the batch satisfies the predicate.
tensor of edge ids. The default value is all the edges. edges : valid edges type
Edges on which to apply ``func``. See :func:`send` for valid
edges type. Default value is all the edges.
Returns Returns
------- -------
tensor tensor
The filtered edges The filtered edges represented by their ids.
Examples
--------
.. note:: Here we use pytorch syntax for demo. The general idea applies
to other frameworks with minor syntax change (e.g. replace
``torch.tensor`` with ``mxnet.ndarray``).
Construct a graph object for demo.
>>> import dgl
>>> import torch as th
>>> g = dgl.DGLGraph()
>>> g.add_nodes(3)
>>> g.ndata['x'] = th.tensor([[1.], [-1.], [1.]])
>>> g.add_edges([0, 1, 2], [2, 2, 1])
Define a function for filtering edges whose destinations have
node feature :math:`1`.
>>> def has_dst_one(edges): return (edges.dst['x'] == 1).squeeze(1)
Filter the edges whose destination nodes have feature :math:`1`.
>>> g.filter_edges(has_dst_one)
tensor([0, 1])
See Also
--------
filter_nodes
""" """
if is_all(edges): if is_all(edges):
eid = ALL eid = ALL
......
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