Repository: Qiskit/retworkx
Pull request #548 (2022-01-11 20:44:27)

Add flag for attribute conversion when converting from networkx
- [x] I ran rustfmt locally
- [x] I have added the tests to cover my changes.
- [x] I have updated the documentation accordingly.
- [x] I have read the CONTRIBUTING document.
---

Currently, converting from a native `networkx` Graph to a `retworkx` PyGraph only preserves adjacency. This PR adds an *optional (and by default disabled) flag* to copy over the node attributes, thereby allowing for a more faithful conversion/translation.

After this PR, it will be possible to do something like this:

```python
In [1]: import networkx as nx

In [2]: import retworkx

In [3]: g = nx.Graph()

In [4]: g.add_node("1", test = True)

In [5]: g.add_node("2", test = True)

In [6]: g.nodes["1"]["test"]
Out[6]: True

In [7]: pygraph = retworkx.networkx_converter(g, keep_attributes=True)

In [8]: pygraph[1]["test"]
Out[8]: True

In [9]: pygraph[1]
Out[9]: {'test': True, '__networkx_name__': '1'}

In [10]: pygraph[0]  # indicies are not always preserved, so it's useful to have the name
Out[10]: {'test': True, '__networkx_name__': '2'}
```

For some context, I am considering adding a `retworkx` backend to a library that I contribute to ([GerryChain](https://github.com/mggg/GerryChain)) to replace our `networkx` usage. `retworkx` seems like an exciting project and may be a viable choice (as opposed to graph-tool or something similar).

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/Qiskit/retworkx?pullRequest=548) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/Qiskit/retworkx?pullRequest=548) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/Qiskit/retworkx?pullRequest=548) it.</sub>
coveralls: ## Pull Request Test Coverage Report for [Build 1684665606](https://coveralls.io/builds/45547353)

* **0** of **0**   changed or added relevant lines in **0** files are covered.
* **2** unchanged lines in **1** file lost coverage.
* Overall coverage increased (+**0.003%**) to **98.493%**

---


|  Files with Coverage Reduction | New Missed Lines | % |
| :-----|--------------|--: |
| [src/shortest_path/all_pairs_dijkstra.rs](https://coveralls.io/builds/45547353/source?filename=src%2Fshortest_path%2Fall_pairs_dijkstra.rs#L76) | 2 | 98.71% |
<!-- | **Total:** | **2** |  | -->

|  Totals | [![Coverage Status](https://coveralls.io/builds/45547353/badge)](https://coveralls.io/builds/45547353) |
| :-- | --: |
| Change from base [Build 1684396079](https://coveralls.io/builds/45545903): |  0.003% |
| Covered Lines: | 11502 |
| Relevant Lines: | 11678 |

---
##### 💛  - [Coveralls](https://coveralls.io)


Patch:
diff --git a/retworkx/__init__.py b/retworkx/__init__.py
index 79b62eb8..04958441 100644
--- a/retworkx/__init__.py
+++ b/retworkx/__init__.py
@@ -1292,7 +1292,7 @@ def _graph_spring_layout(
     )
 
 
-def networkx_converter(graph):
+def networkx_converter(graph, keep_attributes: bool = False):
     """Convert a networkx graph object into a retworkx graph object.
 
     .. note::
@@ -1303,6 +1303,12 @@ def networkx_converter(graph):
         independently.
 
     :param networkx.Graph graph: The networkx graph to convert.
+    :param bool keep_attributes: If ``True``, add networkx node attributes to
+        the data payload in the nodes of the output retworkx graph. When set to
+        ``True``, the node data payloads in the output retworkx graph object
+        will be dictionaries with the node attributes from the input networkx
+        graph where the ``"__networkx_node__"`` key contains the node from the
+        input networkx graph.
 
     :returns: A retworkx graph, either a :class:`~retworkx.PyDiGraph` or a
         :class:`~retworkx.PyGraph` based on whether the input graph is directed
@@ -1321,6 +1327,13 @@ def networkx_converter(graph):
             for x in graph.edges(data=True)
         ]
     )
+
+    if keep_attributes:
+        for node, node_index in node_indices.items():
+            attributes = graph.nodes[node]
+            attributes["__networkx_node__"] = node
+            new_graph[node_index] = attributes
+
     return new_graph
 
 


Test patch:
diff --git a/tests/test_converters.py b/tests/test_converters.py
index c3146853..5cd96e83 100644
--- a/tests/test_converters.py
+++ b/tests/test_converters.py
@@ -18,90 +18,136 @@ import networkx
 class TestNetworkxConverter(unittest.TestCase):
     def test_undirected_gnm_graph(self):
         g = networkx.gnm_random_graph(10, 10, seed=42)
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_directed_gnm_graph(self):
         g = networkx.gnm_random_graph(10, 10, seed=42, directed=True)
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyDiGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyDiGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_empty_graph(self):
         g = networkx.Graph()
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_empty_multigraph(self):
         g = networkx.MultiGraph()
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_empty_directed_graph(self):
         g = networkx.DiGraph()
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyDiGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyDiGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_empty_directed_multigraph(self):
         g = networkx.MultiDiGraph()
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyDiGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyDiGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_cubical_graph(self):
         g = networkx.cubical_graph(networkx.Graph)
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_cubical_multigraph(self):
         g = networkx.cubical_graph(networkx.MultiGraph)
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
 
     def test_random_k_out_graph(self):
         g = networkx.random_k_out_graph(100, 50, 3.14159, True, 42)
-        out_graph = retworkx.networkx_converter(g)
-        self.assertIsInstance(out_graph, retworkx.PyDiGraph)
-        self.assertEqual(out_graph.nodes(), list(g.nodes))
-        self.assertEqual(
-            out_graph.weighted_edge_list(), list(g.edges(data=True))
-        )
-        self.assertEqual(out_graph.multigraph, g.is_multigraph())
+        for keep_attributes in [True, False]:
+            with self.subTest(keep_attributes=keep_attributes):
+                out_graph = retworkx.networkx_converter(
+                    g, keep_attributes=keep_attributes
+                )
+                self.assertIsInstance(out_graph, retworkx.PyDiGraph)
+                self.assertEqual(list(out_graph.node_indexes()), list(g.nodes))
+                self.assertEqual(
+                    out_graph.weighted_edge_list(), list(g.edges(data=True))
+                )
+                self.assertEqual(out_graph.multigraph, g.is_multigraph())
+
+    def test_networkx_graph_attributes_are_converted(self):
+        g = networkx.Graph()
+        for node in range(100):
+            g.add_node(str(node), test=True)
+
+        out_graph = retworkx.networkx_converter(g, keep_attributes=True)
+        for node in out_graph.node_indexes():
+            self.assertEqual(out_graph[node]["test"], True)
+            self.assertEqual(out_graph[node]["__networkx_node__"], str(node))