Repository: dmlc/dgl

Issue:
dgl.prop_nodes_topo Message Passing Error
## 🐛 Bug

<!-- A clear and concise description of what the bug is. -->
Clearly, there is something wrong with the message passing in the following example, either because source nodes' features are passed to the wrong dst nodes or the final updated features are written to different nodes.

Basically, I use this function to aggregate the non-negative predictions (or sum the upstreaming features including the current node's feature) from the root to the leaves in a tree. As the graph is tree-structured and the predictions are non-negative, the aggregated value should monotonically increase from the root to the leaves. The following shows the result of an 8-path tree using DGL v0.4.3. x-axis means the indices in each path. y-axis is the aggregated value.
![v4](https://user-images.githubusercontent.com/61814189/103498209-ca86eb80-4df8-11eb-8a4b-5cae38ac4860.png)

However, when I switched from v0.4.3 to v0.5.3, the aggregated values are not monotonically increasing anymore. The following shows the result using DGL v0.5.3. Apparently, there is something wrong with this function. Tried both the built-in message passing functions and writing by myself, the results are inconsistent for these versions.
![v5](https://user-images.githubusercontent.com/61814189/103498273-002bd480-4df9-11eb-8573-a8ad60098dd6.png)

## To Reproduce

Steps to reproduce the behavior:
1. Create a large tree-structured graph (the nodes have either one or zero in degrees).
2. Write non-negative feature into the node data.
3. Propagate the messages among the graph as follows:
```
dgl.prop_nodes_topo(
      graph,
      message_func=dgl.function.u_add_v('data', 'data', 'data'),
      reduce_func=dgl.function.sum('data', 'data'))
```

The synthetic data and script to reproduce this issue can be found in the attached zip file.

[reproduce.zip](https://github.com/dmlc/dgl/files/5762879/reproduce.zip)

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

the aggregated values should be monotonically increasing from the root to the leaves.
## Environment

 - DGL Version: 0.5.3
 - Backend Library & Version: PyTorch 1.7.0
 - OS (Linux): Linux
 - How you installed DGL (`conda`): conda install -c dglteam dgl-cuda10.1
 - Python version: 3.7.4
 - CUDA/cuDNN version (if applicable): 10.1
 - GPU models and configuration (e.g. V100): Tesla P40
 - Any other relevant information: The synthetic data and script to reproduce this issue can be found in the zip file.



Discussion:
I guess you need a large enough tree. I have already attached the data and
the script to reproduce it in the issue:
https://github.com/dmlc/dgl/files/5762879/reproduce.zip

Best

On Tue, Jan 5, 2021 at 11:07 PM Quan (Andy) Gan <notifications@github.com>
wrote:

> Seems to work for me. Although I'm not sure if it matches your case:
>
> import dglimport torchg = dgl.graph(([0,0,1,1,2,2,3,3,4,4,5,5,6,6],[1,2,3,4,5,6,7,8,9,10,11,12,13,14]))x = torch.rand(15)g.ndata['x'] = xdgl.prop_nodes_topo(g, dgl.function.u_add_v('x', 'x', 'm'), dgl.function.sum('m', 'x'))print(g.ndata['x'])for i in range(1, 15):
>     assert g.ndata['x'][i] > g.ndata['x'][(i - 1) // 2]
>
> Could you give an example code so that I can reproduce your problem?
> Thanks.
>
> —
> You are receiving this because you authored the thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/dmlc/dgl/issues/2484#issuecomment-755126833>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AOXTLLMP3JXG53A2TU5AANTSYQD4LANCNFSM4VSNTHJA>
> .
>

Yeah I noticed that.  Bug confirmed.  Seems that there is a mistake in message passing graph generation.

EDIT: a minimal example reproducing the same issue with `pull`:
```python
g = dgl.graph(([0,1,2],[1,2,3]))
x = torch.rand(4)
x
# tensor([0.4403, 0.7392, 0.1357, 0.9197])

g.ndata['x'] = x
g.pull([1, 2], dgl.function.u_add_v('x', 'x', 'm'), dgl.function.sum('m', 'x'))
g.ndata['x']
# tensor([0.4403, 1.1795, 0.8749, 0.9197])   <-- right

g.ndata['x'] = x
g.pull([2, 1], dgl.function.u_add_v('x', 'x', 'm'), dgl.function.sum('m', 'x'))
g.ndata['x']
# tensor([0.4403, 0.8749, 1.1795, 0.9197])   <-- wrong
```

Resolving patch:
diff --git a/python/dgl/heterograph.py b/python/dgl/heterograph.py
--- a/python/dgl/heterograph.py
+++ b/python/dgl/heterograph.py
@@ -4385,9 +4385,9 @@ def send_and_recv(self,
         u, v = self.find_edges(eid, etype=etype)
         # call message passing onsubgraph
         g = self if etype is None else self[etype]
-        ndata = core.message_passing(_create_compute_graph(g, u, v, eid),
-                                     message_func, reduce_func, apply_node_func)
-        dstnodes = F.unique(v)
+        compute_graph, _, dstnodes, _ = _create_compute_graph(g, u, v, eid)
+        ndata = core.message_passing(
+            compute_graph, message_func, reduce_func, apply_node_func)
         self._set_n_repr(dtid, dstnodes, ndata)
 
     def pull(self,
@@ -4489,9 +4489,10 @@ def pull(self,
         g = self if etype is None else self[etype]
         # call message passing on subgraph
         src, dst, eid = g.in_edges(v, form='all')
-        ndata = core.message_passing(_create_compute_graph(g, src, dst, eid, v),
-                                     message_func, reduce_func, apply_node_func)
-        self._set_n_repr(dtid, v, ndata)
+        compute_graph, _, dstnodes, _ = _create_compute_graph(g, src, dst, eid, v)
+        ndata = core.message_passing(
+            compute_graph, message_func, reduce_func, apply_node_func)
+        self._set_n_repr(dtid, dstnodes, ndata)
 
     def push(self,
              u,
@@ -6060,6 +6061,6 @@ def _create_compute_graph(graph, u, v, eid, recv_nodes=None):
 
     return DGLHeteroGraph(hgidx, ([srctype], [dsttype]), [etype],
                           node_frames=[srcframe, dstframe],
-                          edge_frames=[eframe])
+                          edge_frames=[eframe]), unique_src, unique_dst, eid
 
 _init_api("dgl.heterograph")


Test patch:
diff --git a/tests/compute/test_basics.py b/tests/compute/test_basics.py
--- a/tests/compute/test_basics.py
+++ b/tests/compute/test_basics.py
@@ -657,3 +657,18 @@ def reducer(nodes):
         assert np.array_equal(eid, np.sort(eid, 1))
         return {'n': F.sum(nodes.mailbox['eid'], 1)}
     g.update_all(fn.copy_e('eid', 'eid'), reducer)
+
+@parametrize_dtype
+def test_issue_2484(idtype):
+    import dgl.function as fn
+    g = dgl.graph(([0, 1, 2], [1, 2, 3]), idtype=idtype, device=F.ctx())
+    x = F.copy_to(F.randn((4,)), F.ctx())
+    g.ndata['x'] = x
+    g.pull([2, 1], fn.u_add_v('x', 'x', 'm'), fn.sum('m', 'x'))
+    y1 = g.ndata['x']
+
+    g.ndata['x'] = x
+    g.pull([1, 2], fn.u_add_v('x', 'x', 'm'), fn.sum('m', 'x'))
+    y2 = g.ndata['x']
+
+    assert F.allclose(y1, y2)