Repository: Qiskit/qiskit

Issue:
DAGs imported from networkx cannot be drawn
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

- **Qiskit version**: 0.19.2
- **Python version**: 3.8.12
- **Operating system**: Windows 10

### What is the current behavior?
I can successfully create `DAGCircuit` objects by importing them from networkx graphs. Those objects seem to behave correctly, but whenever I try to draw them using the `dag_drawer` function, I get an error. I verified that the error only occurs for DAGCircuit objects imported from networkx graphs - in fact, I can draw a dag correctly, but if I convert it to networkx and back (`new_dag = from_networkx(dag.to_networkx())`), then new_dag can no longer be drawn.

### Steps to reproduce the problem
```
import qiskit
from qiskit.circuit.random import random_circuit
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.tools.visualization import dag_drawer
from qiskit.dagcircuit import DAGCircuit

in_circ = random_circuit(1,1)
in_dag = circuit_to_dag(in_circ)
# dag_drawer(in_dag) # if uncommented, draws the DAG correctly
graph = in_dag.to_networkx()
out_dag = DAGCircuit.from_networkx(graph)
dag_drawer(out_dag) # throws an error
```

outputs

```
KeyError                                  Traceback (most recent call last)
---> [12]() dag_drawer(out_dag)


--> [224]() dot_str = dag._multi_graph.to_dot(node_attr_func, edge_attr_func, graph_attrs)

--> [206]()     n["label"] = bit_labels[node.wire]
 
KeyError: Qubit(QuantumRegister(1, 'q'), 0)
```

### What is the expected behavior?

That DAG objects created from networkx graphs have the same properties as those created from quantum circuits.

### Suggested solutions



Discussion:
Honestly, I'm thinking it's time that we deprecate and prepare to remove the networkx converters for the dagcircuit. We originally had them when we first switched to use retworkx because the api functionality was more limited. But, I'm not sure there is much value in having them anymore as retworkx can do a lot more than 2 years ago and we can quickly add any functionality its missing. Is there a particular function or use case you have for converting to networkx graphs?

That being said I think the issue is in the `from_networkx` has no context about registers and the `dag_drawer` only works with registers for edge labels and in and out node labels. The graph representation can't really know about any registers with the circuit generally because that's metadata from a circuit . It might be a good use case for a networkx graph attribute to store the registers list, but I think given the pending deprecation I mentioned above it's not worth doing that. The `dag_drawer` method needs to be updated to work with bits directly and not only work with registers. I'll push up a fix to do this shortly
Hi. Yeah, I see where it's coming from. In my view, networkx is a handy tool for interacting with DAGs flexibly. I'm playing around with some custom transpilation methods, and as a beginner, using networkx means I can find a lot of examples / tutorials / discussion online. But I see where you're coming from, and you might not want to support this kind of usecases.

From my perspective, it would be most flexible to set things up such that if a DAGCircuit can be initialised without some metadata, then all in-built functions that operate on DAGCircuits don't crash even if the metadata is not provided. Alternatively, some metadata could be added by default if not explicitly specified?

Resolving patch:
diff --git a/qiskit/visualization/dag_visualization.py b/qiskit/visualization/dag_visualization.py
--- a/qiskit/visualization/dag_visualization.py
+++ b/qiskit/visualization/dag_visualization.py
@@ -21,6 +21,7 @@
 import tempfile
 
 from qiskit.dagcircuit.dagnode import DAGOpNode, DAGInNode, DAGOutNode
+from qiskit.circuit import Qubit
 from qiskit.utils import optionals as _optionals
 from qiskit.exceptions import InvalidFileError
 from .exceptions import VisualizationError
@@ -173,7 +174,9 @@ def node_attr_func(node):
         edge_attr_func = None
 
     else:
-        bit_labels = {
+        qubit_indices = {bit: index for index, bit in enumerate(dag.qubits)}
+        clbit_indices = {bit: index for index, bit in enumerate(dag.clbits)}
+        register_bit_labels = {
             bit: f"{reg.name}[{idx}]"
             for reg in list(dag.qregs.values()) + list(dag.cregs.values())
             for (idx, bit) in enumerate(reg)
@@ -192,12 +195,20 @@ def node_attr_func(node):
                     n["style"] = "filled"
                     n["fillcolor"] = "lightblue"
                 if isinstance(node, DAGInNode):
-                    n["label"] = bit_labels[node.wire]
+                    if isinstance(node.wire, Qubit):
+                        label = register_bit_labels.get(node.wire, f"q_{qubit_indices[node.wire]}")
+                    else:
+                        label = register_bit_labels.get(node.wire, f"c_{clbit_indices[node.wire]}")
+                    n["label"] = label
                     n["color"] = "black"
                     n["style"] = "filled"
                     n["fillcolor"] = "green"
                 if isinstance(node, DAGOutNode):
-                    n["label"] = bit_labels[node.wire]
+                    if isinstance(node.wire, Qubit):
+                        label = register_bit_labels.get(node.wire, f"q[{qubit_indices[node.wire]}]")
+                    else:
+                        label = register_bit_labels.get(node.wire, f"c[{clbit_indices[node.wire]}]")
+                    n["label"] = label
                     n["color"] = "black"
                     n["style"] = "filled"
                     n["fillcolor"] = "red"
@@ -207,7 +218,11 @@ def node_attr_func(node):
 
         def edge_attr_func(edge):
             e = {}
-            e["label"] = bit_labels[edge]
+            if isinstance(edge, Qubit):
+                label = register_bit_labels.get(edge, f"q_{qubit_indices[edge]}")
+            else:
+                label = register_bit_labels.get(edge, f"c_{clbit_indices[edge]}")
+            e["label"] = label
             return e
 
     dot_str = dag._multi_graph.to_dot(node_attr_func, edge_attr_func, graph_attrs)


Test patch:
diff --git a/test/python/visualization/references/dag_no_reg.png b/test/python/visualization/references/dag_no_reg.png
new file mode 100644
Binary files /dev/null and b/test/python/visualization/references/dag_no_reg.png differ
diff --git a/test/python/visualization/test_dag_drawer.py b/test/python/visualization/test_dag_drawer.py
--- a/test/python/visualization/test_dag_drawer.py
+++ b/test/python/visualization/test_dag_drawer.py
@@ -12,17 +12,22 @@
 
 """Tests for DAG visualization tool."""
 
+import os
+import tempfile
 import unittest
 
-from qiskit import QuantumRegister, QuantumCircuit
-from qiskit.test import QiskitTestCase
+from PIL import Image
+
+from qiskit.circuit import QuantumRegister, QuantumCircuit, Qubit, Clbit
 from qiskit.tools.visualization import dag_drawer
 from qiskit.exceptions import InvalidFileError
 from qiskit.visualization.exceptions import VisualizationError
 from qiskit.converters import circuit_to_dag
+from qiskit.utils import optionals as _optionals
+from .visualization import path_to_diagram_reference, QiskitVisualizationTestCase
 
 
-class TestDagDrawer(QiskitTestCase):
+class TestDagDrawer(QiskitVisualizationTestCase):
     """Qiskit DAG drawer tests."""
 
     def setUp(self):
@@ -49,6 +54,22 @@ def test_dag_drawer_checks_filename_extension(self):
         with self.assertRaisesRegex(InvalidFileError, "Filename extension must be one of: .*"):
             dag_drawer(self.dag, filename="aa.abc")
 
+    @unittest.skipUnless(_optionals.HAS_GRAPHVIZ, "Graphviz not installed")
+    def test_dag_drawer_no_register(self):
+        """Test dag visualization with a circuit with no registers."""
+        qubit = Qubit()
+        clbit = Clbit()
+        qc = QuantumCircuit([qubit, clbit])
+        qc.h(0)
+        qc.measure(0, 0)
+        dag = circuit_to_dag(qc)
+        with tempfile.TemporaryDirectory() as tmpdirname:
+            tmp_path = os.path.join(tmpdirname, "dag.png")
+            dag_drawer(dag, filename=tmp_path)
+            image_ref = path_to_diagram_reference("dag_no_reg.png")
+            image = Image.open(tmp_path)
+            self.assertImagesAreEqual(image, image_ref, 0.2)
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)