Repository: Qiskit/qiskit

Issue:
DAGCircuit.apply_operation_back sometimes applies operation front
From discussions with @gadial , there seems to be some issues with the `DAGCircuit`s returned by `DAGCircuit.layers` which can lead to inconsistent behavior, e.g. they cannot be drawn with the `dag_drawer` and calls to `apply_operation_back` will not consistently add gates to the end of the dag. e.g.

```
for i in range(10):
    qc = QuantumCircuit(qr)
    qc.x(0)
    dag = circuit_to_dag(qc)
    d1 = list(dag.layers())[0]['graph']
    #dag_drawer(d1)  # <= raises with 'KeyError: 'name''
    d1.apply_operation_back(IdGate(), [qr[0]], [])
    print(dag_to_circuit(d1))
```
will generate
```
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌────┐┌───┐
qr_0: |0>┤ Id ├┤ X ├
         └────┘└───┘
         ┌────┐┌───┐
qr_0: |0>┤ Id ├┤ X ├
         └────┘└───┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌───┐┌────┐
qr_0: |0>┤ X ├┤ Id ├
         └───┘└────┘
         ┌────┐┌───┐
qr_0: |0>┤ Id ├┤ X ├
         └────┘└───┘
```

This seems to be constrained to the `DAGCircuit`s built by `dag.layers()`, repeating the above on the original dag without going through `layers` consistently generates `X-Id`.


Discussion:

Resolving patch:
diff --git a/qiskit/dagcircuit/dagcircuit.py b/qiskit/dagcircuit/dagcircuit.py
--- a/qiskit/dagcircuit/dagcircuit.py
+++ b/qiskit/dagcircuit/dagcircuit.py
@@ -1015,6 +1015,10 @@ def layers(self):
         greedy algorithm. Each returned layer is a dict containing
         {"graph": circuit graph, "partition": list of qubit lists}.
 
+        New but semantically equivalent DAGNodes will be included in the returned layers,
+        NOT the DAGNodes from the original DAG. The original vs new nodes can be compared using
+        DAGNode.semantic_eq(node1, node2)
+
         TODO: Gates that use the same cbits will end up in different
         layers as this is currently implemented. This may not be
         the desired behavior.
@@ -1025,16 +1029,18 @@ def layers(self):
         except StopIteration:
             return
 
-        def add_nodes_from(layer, nodes):
-            """ Convert DAGNodes into a format that can be added to a
-             multigraph and then add to graph"""
-            layer._multi_graph.add_nodes_from(nodes)
-
         for graph_layer in graph_layers:
 
             # Get the op nodes from the layer, removing any input and output nodes.
             op_nodes = [node for node in graph_layer if node.type == "op"]
 
+            # Sort to make sure they are in the order they were added to the original DAG
+            # It has to be done by node_id as graph_layer is just a list of nodes
+            # with no implied topology
+            # Drawing tools that rely on _node_id to infer order of node creation
+            # so we need this to be preserved by layers()
+            op_nodes.sort(key=lambda nd: nd._node_id)
+
             # Stop yielding once there are no more op_nodes in a layer.
             if not op_nodes:
                 return
@@ -1043,36 +1049,26 @@ def add_nodes_from(layer, nodes):
             new_layer = DAGCircuit()
             new_layer.name = self.name
 
+            # add in the registers - this adds the input/output nodes
             for creg in self.cregs.values():
                 new_layer.add_creg(creg)
             for qreg in self.qregs.values():
                 new_layer.add_qreg(qreg)
 
-            add_nodes_from(new_layer, self.input_map.values())
-            add_nodes_from(new_layer, self.output_map.values())
-            add_nodes_from(new_layer, op_nodes)
+            for node in op_nodes:
+                # this creates new DAGNodes in the new_layer
+                new_layer.apply_operation_back(node.op,
+                                               node.qargs,
+                                               node.cargs,
+                                               node.condition)
 
             # The quantum registers that have an operation in this layer.
             support_list = [
                 op_node.qargs
-                for op_node in op_nodes
+                for op_node in new_layer.op_nodes()
                 if op_node.name not in {"barrier", "snapshot", "save", "load", "noise"}
             ]
 
-            # Now add the edges to the multi_graph
-            # By default we just wire inputs to the outputs.
-            wires = {self.input_map[wire]: self.output_map[wire]
-                     for wire in self.wires}
-            # Wire inputs to op nodes, and op nodes to outputs.
-            for op_node in op_nodes:
-                args = self._bits_in_condition(op_node.condition) \
-                       + op_node.cargs + op_node.qargs
-                arg_ids = (self.input_map[(arg.register, arg.index)] for arg in args)
-                for arg_id in arg_ids:
-                    wires[arg_id], wires[op_node] = op_node, wires[arg_id]
-
-            # Add wiring to/from the operations and between unused inputs & outputs.
-            new_layer._multi_graph.add_edges_from(wires.items())
             yield {"graph": new_layer, "partition": support_list}
 
     def serial_layers(self):


Test patch:
diff --git a/test/python/test_dagcircuit.py b/test/python/test_dagcircuit.py
--- a/test/python/test_dagcircuit.py
+++ b/test/python/test_dagcircuit.py
@@ -24,6 +24,7 @@
 from qiskit.circuit import Measure
 from qiskit.circuit import Reset
 from qiskit.circuit import Gate, Instruction
+from qiskit.extensions.standard.iden import IdGate
 from qiskit.extensions.standard.h import HGate
 from qiskit.extensions.standard.cx import CnotGate
 from qiskit.extensions.standard.x import XGate
@@ -449,6 +450,26 @@ def test_layers_basic(self):
             ['measure', 'measure']
         ], name_layers)
 
+    def test_layers_maintains_order(self):
+        """Test that the layers method doesn't mess up the order of the DAG as
+         reported in #2698"""
+        qr = QuantumRegister(1, 'q0')
+
+        # the order the nodes should be in
+        truth = [('in', 'q0[0]', 1), ('op', 'x', 3), ('op', 'id', 4), ('out', 'q0[0]', 2)]
+
+        # this only occurred sometimes so has to be run more than once
+        # (10 times seemed to always be enough for this bug to show at least once)
+        for _ in range(10):
+            qc = QuantumCircuit(qr)
+            qc.x(0)
+            dag = circuit_to_dag(qc)
+            d1 = list(dag.layers())[0]['graph']
+            d1.apply_operation_back(IdGate(), [qr[0]], [])
+
+            comp = [(nd.type, nd.name, nd._node_id) for nd in d1.topological_nodes()]
+            self.assertEqual(comp, truth)
+
 
 class TestCircuitProperties(QiskitTestCase):
     """DAGCircuit properties test."""