Skip to content

Commit 5c78e3e

Browse files
committed
Increased version to 2.0.1. Added functions for GraphDict to interface networkx. Added networkx to requirements in setup.py and added a notebook for tutorial.
1 parent 0ab9440 commit 5c78e3e

File tree

4 files changed

+1072
-5
lines changed

4 files changed

+1072
-5
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ A major issue for graphs is their flexible size and shape, when using mini-batch
8484

8585
Graph tensors for edge-indices or attributes for multiple graphs is passed to the model in form of ragged tensors
8686
of shape `(batch, None, Dim)` where `Dim` denotes a fixed feature or index dimension.
87-
Such a ragged tensor has `ragged_rank=1` with one ragged dimension indicated by `None` and is build from a value and partition tensor.
87+
Such a ragged tensor has `ragged_rank=1` with one ragged dimension indicated by `None` and is build from a value plus partition tensor.
8888
For example, the graph structure is represented by an index-list of shape `(batch, None, 2)` with index of incoming or receiving node `i` and outgoing or sending node `j` as `(i, j)`.
8989
Note, an additional edge with `(j, i)` is required for undirected graphs.
9090
A ragged constant can be easily created and passed to a model:
@@ -145,7 +145,7 @@ class MyMessageNN(MessagePassingBase):
145145

146146
<a name="literature"></a>
147147
# Literature
148-
A version of the following models are implemented in [literature](kgcnn/literature):
148+
A version of the following models is implemented in [literature](kgcnn/literature):
149149
* **[GCN](kgcnn/literature/GCN.py)**: [Semi-Supervised Classification with Graph Convolutional Networks](https://arxiv.org/abs/1609.02907) by Kipf et al. (2016)
150150
* **[INorp](kgcnn/literature/INorp.py)**: [Interaction Networks for Learning about Objects,Relations and Physics](https://arxiv.org/abs/1612.00222) by Battaglia et al. (2016)
151151
* **[Megnet](kgcnn/literature/Megnet.py)**: [Graph Networks as a Universal Machine Learning Framework for Molecules and Crystals](https://doi.org/10.1021/acs.chemmater.9b01294) by Chen et al. (2019)

kgcnn/data/base.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import numpy as np
33
import tensorflow as tf
4+
import networkx as nx
45
import pandas as pd
56
import os
67
import re
@@ -52,13 +53,94 @@ def __init__(self, sub_dict: dict = None):
5253
super(GraphDict, self).__init__(sub_dict)
5354

5455
def to_dict(self) -> dict:
55-
"""Returns a python-dictionary of self.
56+
"""Returns a python-dictionary of self. Does not copy values.
5657
5758
Returns:
5859
dict: Dictionary of graph tensor objects.
5960
"""
6061
return {key: value for key, value in self.items()}
6162

63+
def from_networkx(self, graph,
64+
node_number: str = "node_number",
65+
edge_indices: str = "edge_indices",
66+
node_attributes: str = None,
67+
edge_attributes: str = None,
68+
node_labels: str = None):
69+
r"""Convert a networkx graph instance into a dictionary of graph-tensors. The networkx graph is always converted
70+
into integer node labels. The former node IDs can be hold in :obj:`node_labels`. Furthermore, node or edge
71+
data can be cast into attributes via :obj:`node_attributes` and :obj:`edge_attributes`.
72+
73+
Args:
74+
graph (nx.Graph): A networkx graph instance to convert.
75+
node_number (str): The name that the node numbers are assigned to. Default is "node_number".
76+
edge_indices (str): The name that the edge indices are assigned to. Default is "edge_indices".
77+
node_attributes (str, list): Name of node attributes to add from node data. Can also be a list of names.
78+
Default is None.
79+
edge_attributes (str, list): Name of edge attributes to add from edge data. Can also be a list of names.
80+
Default is None.
81+
node_labels (str): Name of the labels of nodes to store former node IDs into. Default is None.
82+
83+
Returns:
84+
self.
85+
"""
86+
assert node_labels is None or isinstance(node_labels, str), "Please provide name of node labels or `None`"
87+
graph_int = nx.convert_node_labels_to_integers(graph, label_attribute=node_labels)
88+
graph_size = len(graph_int)
89+
90+
def _attr_to_list(attr):
91+
if attr is None:
92+
attr = []
93+
elif isinstance(attr, str):
94+
attr = [attr]
95+
if not isinstance(attr, list):
96+
raise TypeError("Attribute name is neither list or string.")
97+
return attr
98+
99+
# Loop over nodes in graph.
100+
node_attr = _attr_to_list(node_attributes)
101+
if node_labels is not None:
102+
node_attr += [node_labels]
103+
node_attr_dict = {x: [None]*graph_size for x in node_attr}
104+
nodes_id = []
105+
for i, x in enumerate(graph_int.nodes.data()):
106+
nodes_id.append(x[0])
107+
for d in node_attr:
108+
if d not in x[1]:
109+
raise KeyError("Node does not have property %s" % d)
110+
node_attr_dict[d][i] = x[1][d]
111+
112+
edge_id = []
113+
edges_attr = _attr_to_list(edge_attributes)
114+
edges_attr_dict = {x: [None]*graph_size for x in edges_attr}
115+
for i, x in enumerate(graph_int.edges.data()):
116+
edge_id.append(x[:2])
117+
for d in edges_attr:
118+
if d not in x[2]:
119+
raise KeyError("Edge does not have property %s" % d)
120+
edges_attr_dict[d][i] = x[2][d]
121+
122+
# Storing graph tensors in self.
123+
self.assign_property(node_number, self._tensor_conversion(nodes_id))
124+
self.assign_property(edge_indices, self._tensor_conversion(edge_id))
125+
for key, value in node_attr_dict.items():
126+
self.assign_property(key, self._tensor_conversion(value))
127+
for key, value in edges_attr_dict.items():
128+
self.assign_property(key, self._tensor_conversion(value))
129+
return self
130+
131+
def to_networkx(self, edge_indices="edge_indices"):
132+
"""Function draft to make a networkx graph. No attributes or data is supported at the moment.
133+
134+
Args:
135+
edge_indices (str): Name of edge index tensors to make graph with. Default is "edge_indices".
136+
137+
Returns:
138+
nx.DiGraph: Directed networkx graph instance.
139+
"""
140+
graph = nx.DiGraph()
141+
graph.add_edges_from(self.obtain_property(edge_indices))
142+
return graph
143+
62144
def assign_property(self, key: str, value):
63145
r"""Add a named property as key, value pair to self. If the value is `None`, nothing is done.
64146
Similar to assign-item default method :obj:`__setitem__`, but ignores `None` values and casts to tensor.

notebooks/tutorial_graph_dict.ipynb

Lines changed: 985 additions & 0 deletions
Large diffs are not rendered by default.

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
setup(
88
name="kgcnn",
9-
version="2.0.0",
9+
version="2.0.1",
1010
author="Patrick Reiser",
1111
author_email="patrick.reiser@kit.edu",
1212
description="General Base Layers for Graph Convolutions with tensorflow.keras",
1313
long_description=long_description,
1414
long_description_content_type="text/markdown",
1515
url="https://github.com/aimat-lab/gcnn_keras",
1616
install_requires=['numpy', "scikit-learn", "pandas", "scipy", "requests", "matplotlib", "networkx", "sympy",
17-
"requests", "tensorflow-addons", "keras-tuner", "pyyaml"],
17+
"requests", "tensorflow-addons", "keras-tuner", "pyyaml", "networkx"],
1818
extras_require={
1919
"tf": ["tensorflow>=2.4.0"],
2020
"tf_gpu": ["tensorflow-gpu>=2.4.0"],

0 commit comments

Comments
 (0)