|
1 | 1 | import logging
|
2 | 2 | import numpy as np
|
3 | 3 | import tensorflow as tf
|
| 4 | +import networkx as nx |
4 | 5 | import pandas as pd
|
5 | 6 | import os
|
6 | 7 | import re
|
@@ -52,13 +53,94 @@ def __init__(self, sub_dict: dict = None):
|
52 | 53 | super(GraphDict, self).__init__(sub_dict)
|
53 | 54 |
|
54 | 55 | def to_dict(self) -> dict:
|
55 |
| - """Returns a python-dictionary of self. |
| 56 | + """Returns a python-dictionary of self. Does not copy values. |
56 | 57 |
|
57 | 58 | Returns:
|
58 | 59 | dict: Dictionary of graph tensor objects.
|
59 | 60 | """
|
60 | 61 | return {key: value for key, value in self.items()}
|
61 | 62 |
|
| 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 | + |
62 | 144 | def assign_property(self, key: str, value):
|
63 | 145 | r"""Add a named property as key, value pair to self. If the value is `None`, nothing is done.
|
64 | 146 | Similar to assign-item default method :obj:`__setitem__`, but ignores `None` values and casts to tensor.
|
|
0 commit comments