Repository: dmlc/dgl

Issue:
Double edges in some TUDatasets
## 🐛 Bug

The adjacency matrix of some TUDatasets such as IMDB-BINARY is made up of 0s and 2s rather than 0s and 1s. I believe this to be because the original files have multiple entries of the edges. 

## To Reproduce

Steps to reproduce the behavior:

I wrote the following script to show the behaviour 

```
from io import BytesIO
from urllib.request import urlopen
from zipfile import ZipFile

import dgl
import networkx as nx
import numpy as np
import scipy.sparse as sp
import torch_geometric


def get_tudataset(name):
    """Returns a list of adj matrices."""
    # files
    resp = urlopen(f'https://www.chrsmrrs.com/graphkerneldatasets/{name}.zip')
    zipfile = ZipFile(BytesIO(resp.read()))
    graphs_fname=f'{name}/{name}_A.txt'
    graph_indicator_fname=f'{name}/{name}_graph_indicator.txt'

    # map graph_id to set of nodes
    graphs = {}
    for node, line in enumerate(zipfile.open(graph_indicator_fname).readlines()):
        graph_id = int(line.decode('utf-8').strip('\n'))-1
        if graph_id in graphs:
            graphs[graph_id].add(node)
        else:
            graphs[graph_id] = {node}

    # construct adjacent matrices
    number_of_graphs = len(graphs)
    adj_matrices = [sp.lil_matrix((len(graphs[i]), len(graphs[i])), dtype=np.float32) for i in range(number_of_graphs)]
    for line in zipfile.open(graphs_fname).readlines():
        u, v = line.decode('utf-8').strip('\n').split(',')
        u, v = int(u)-1, int(v)-1

        for graph_id in graphs:
            if u in graphs[graph_id]:
                u = u - min(graphs[graph_id])
                v = v - min(graphs[graph_id])
                adj_matrices[graph_id][u, v] += 1

    return adj_matrices

def unique_elements(sparse_adj):
    return set(np.unique(np.array(sparse_adj.todense())))

def adj_elements(dataset):
    adj_matrices = get_tudataset(dataset)
    dataset_pg = torch_geometric.datasets.TUDataset('tmp/torch_geometric', dataset, use_node_attr=True)
    dataset_dgl = dgl.data.TUDataset(dataset)
    number_of_graphs = len(dataset_dgl)

    my_unique_elements = set()
    for i in range(number_of_graphs):
        graph = adj_matrices[i]
        my_unique_elements = my_unique_elements.union(unique_elements(graph))

    pg_unique_elements = set()
    for i in range(number_of_graphs):
        graph = nx.adj_matrix(torch_geometric.utils.to_networkx(dataset_pg[0]))
        pg_unique_elements = pg_unique_elements.union(unique_elements(graph))

    dgl_unique_elements = set()
    for i in range(number_of_graphs):
        graph = dataset_dgl[i][0].adjacency_matrix(scipy_fmt='csr')
        dgl_unique_elements = dgl_unique_elements.union(unique_elements(graph))

    print(dataset)
    print('My implementation', my_unique_elements)
    print('PG implementation', pg_unique_elements)
    print('DGL implementation', dgl_unique_elements)
    print()


if __name__ == '__main__':
    adj_elements('ENZYMES')
    adj_elements('IMDB-BINARY')
```

The function `get_tudataset` is a naive implementation to read the adjacency matrices. The function `unique_elements` takes a sparse adjacency matrix and returns a set containing the unique elements. The function `adj_elements` looks at every adjacency matrix in a dataset and prints the unique elements found using three interfaces to the TUDatasets (my implementation, pytorch_geometric and DGL).

The output of this script is
```
Using backend: pytorch
ENZYMES
My implementation {0.0, 1.0}
PG implementation {0, 1}
DGL implementation {0, 1}

IMDB-BINARY
My implementation {0.0, 2.0}
PG implementation {0, 1}
DGL implementation {0, 2}
```

## Expected behaviour

The datasets `ENZYMES` and `IMDB-BINARY` consist of binary graphs, so the adjacency matrices should be binary. The DGL implementation suffers the same error as my implementation, which is that multi edges are added.  The expected behaviour should be like pytorch_geometric, where the multiplicity (which I think is an inconsistency in the original data) is ignored.

## Environment

 - DGL Version (e.g., 1.0): 0.6.1
 - Backend Library & Version (e.g., PyTorch 0.4.1, MXNet/Gluon 1.3): PyTorch 0.6.1
 - OS (e.g., Linux): macOS Catalina (10.15.7)
 - How you installed DGL (`conda`, `pip`, source): conda
 - Python version: 3.7.9
 - CUDA/cuDNN version (if applicable): N/A
 - GPU models and configuration (e.g. V100): N/A
 - Any other relevant information: N/A


Discussion:
The original data file of IMDB-BINARY consists of duplicate edges, therefore dgl faithfully preserve those in the processing process. You can call `dgl.to_simple(g)` to remove the duplicate edges in the graph. I haven't come a good idea whether we should call `to_simple` graph for all the dataset. Let me know if you have better suggestions

Resolving patch:
diff --git a/python/dgl/data/tu.py b/python/dgl/data/tu.py
--- a/python/dgl/data/tu.py
+++ b/python/dgl/data/tu.py
@@ -252,6 +252,13 @@ class TUDataset(DGLBuiltinDataset):
     num_labels : int
         Number of classes
 
+    Notes
+    -----
+    **IMPORTANT:** Some of the datasets have duplicate edges exist in the graphs, e.g.
+    the edges in ``IMDB-BINARY`` are all duplicated.  DGL faithfully keeps the duplicates
+    as per the original data.  Other frameworks such as PyTorch Geometric removes the
+    duplicates by default.  You can remove the duplicate edges with :func:`dgl.to_simple`.
+
     Examples
     --------
     >>> data = TUDataset('DD')


Test patch: