Repository: Qiskit/qiskit

Issue:
block collection pass reorders measurement
```python
from qiskit import *

qasm = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q3516[1];
qreg q3517[1];
creg c2687[1];
id q3516[0];
measure q3517[0] -> c2687[0];
cx q3517[0],q3516[0];
u2(0.0,0.0) q3517[0];
"""
circ = QuantumCircuit.from_qasm_str(qasm)
print(circ)
new_circ = transpile(circ, optimization_level=3)
print(new_circ)
```

```
            ┌────┐┌───┐
q3516_0: |0>┤ Id ├┤ X ├───────────
            └┬─┬─┘└─┬─┘┌─────────┐
q3517_0: |0>─┤M├────■──┤ U2(0,0) ├
             └╥┘       └─────────┘
 c2687_0: 0 ══╩═══════════════════


            ┌────┐┌───┐
q3516_0: |0>┤ Id ├┤ X ├──────────────
            └────┘└─┬─┘┌─────────┐┌─┐
q3517_0: |0>────────■──┤ U2(0,0) ├┤M├
                       └─────────┘└╥┘
 c2687_0: 0 ═══════════════════════╩═
```
block collection failure on block interrupted by measure
possibly related to #2737 

```python
from qiskit import *
from qiskit.test.mock import FakeTenerife
qasm = """OPENQASM 2.0;
include "qelib1.inc";
qreg q8602[2];
creg c1266[1];
cx q8602[1],q8602[0];
measure q8602[1] -> c1266[0];
cx q8602[1],q8602[0];
"""
circ = QuantumCircuit.from_qasm_str(qasm)
print(circ)
new_circ = transpile(circ, FakeTenerife(), optimization_level=3)
new_circ.draw(line_length=100)
```

```
            ┌───┐   ┌───┐
q8602_0: |0>┤ X ├───┤ X ├
            └─┬─┘┌─┐└─┬─┘
q8602_1: |0>──■──┤M├──■──
                 └╥┘     
 c1266_0: 0 ══════╩══════
                         
             ┌───────────────────┐    ┌───┐┌─┐                             ┌──────────────┐
   q_0: |0>───┤ U2(4.7124,6.2832) ├────┤ X ├┤M├─────────────────────────────┤ U2(0,3.1416) ├
           ┌──┴───────────────────┴───┐└─┬─┘└╥┘┌───────────────────────────┐└──────────────┘
   q_1: |0>┤ U3(1.5708,1.5708,6.2832) ├──■───╫─┤ U3(1.5708,-3.1416,3.1416) ├────────────────
           └──────────────────────────┘      ║ └───────────────────────────┘                
   q_2: |0>──────────────────────────────────╫──────────────────────────────────────────────
                                             ║                                              
   q_3: |0>──────────────────────────────────╫──────────────────────────────────────────────
                                             ║                                              
   q_4: |0>──────────────────────────────────╫──────────────────────────────────────────────
                                             ║                                              
c1266_0: 0 ══════════════════════════════════╩══════════════════════════════════════════════
                                                                                            
```


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/collect_2q_blocks.py b/qiskit/transpiler/passes/collect_2q_blocks.py
--- a/qiskit/transpiler/passes/collect_2q_blocks.py
+++ b/qiskit/transpiler/passes/collect_2q_blocks.py
@@ -53,6 +53,7 @@ def run(self, dag):
         nodes = list(dag.topological_nodes())
         nodes_seen = dict(zip(nodes, [False] * len(nodes)))
         for nd in dag.topological_op_nodes():
+
             group = []
             # Explore predecessors and successors of cx gates
             if nd.name == "cx" and nd.condition is None and not nodes_seen[nd]:
@@ -94,6 +95,10 @@ def run(self, dag):
                         # Examine each predecessor
                         for pnd in sorted_pred:
                             if pnd.name not in good_names:
+                                # remove any qubits that are interrupted by a gate
+                                # e.g. a measure in the middle of the circuit
+                                these_qubits = list(set(these_qubits) -
+                                                    set(pnd.qargs))
                                 continue
                             # If a predecessor is a single qubit gate, add it
                             if pnd.name != "cx":
@@ -163,8 +168,15 @@ def run(self, dag):
                         # Examine each successor
                         for snd in sorted_succ:
                             if snd.name not in good_names:
+                                # remove qubits from consideration if interrupted
+                                # by a gate e.g. a measure in the middle of the circuit
+                                these_qubits = list(set(these_qubits) -
+                                                    set(snd.qargs))
                                 continue
+
                             # If a successor is a single qubit gate, add it
+                            # NB as we have eliminated all gates with names not in
+                            # good_names, this check guarantees they are single qubit
                             if snd.name != "cx":
                                 if not nodes_seen[snd]:
                                     group.append(snd)
diff --git a/qiskit/transpiler/passes/consolidate_blocks.py b/qiskit/transpiler/passes/consolidate_blocks.py
--- a/qiskit/transpiler/passes/consolidate_blocks.py
+++ b/qiskit/transpiler/passes/consolidate_blocks.py
@@ -25,6 +25,7 @@
 from qiskit.quantum_info.synthesis import TwoQubitBasisDecomposer
 from qiskit.extensions import UnitaryGate, CnotGate
 from qiskit.transpiler.basepasses import TransformationPass
+from qiskit.transpiler.exceptions import TranspilerError
 
 
 class ConsolidateBlocks(TransformationPass):
@@ -64,17 +65,42 @@ def run(self, dag):
             global_index_map[wire] = global_qregs.index(wire.register) + wire.index
 
         blocks = self.property_set['block_list']
-        nodes_seen = set()
+        # just to make checking if a node is in any block easier
+        all_block_nodes = {nd for bl in blocks for nd in bl}
 
+        for node in dag.topological_op_nodes():
+            if node not in all_block_nodes:
+                # need to add this node to find out where in the list it goes
+                preds = [nd for nd in dag.predecessors(node) if nd.type == 'op']
+
+                block_count = 0
+                while preds:
+                    if block_count < len(blocks):
+                        block = blocks[block_count]
+
+                        # if any of the predecessors are in the block, remove them
+                        preds = [p for p in preds if p not in block]
+                    else:
+                        # should never occur as this would mean not all
+                        # nodes before this one topologically had been added
+                        # so not all predecessors were removed
+                        raise TranspilerError("Not all predecessors removed due to error"
+                                              " in topological order")
+
+                    block_count += 1
+
+                # we have now seen all predecessors
+                # so update the blocks list to include this block
+                blocks = blocks[:block_count] + [[node]] + blocks[block_count:]
+
+        # create the dag from the updated list of blocks
         basis_gate_name = self.decomposer.gate.name
+        for block in blocks:
 
-        for node in dag.topological_op_nodes():
-            # skip already-visited nodes or input/output nodes
-            if node in nodes_seen or node.type == 'in' or node.type == 'out':
-                continue
-            # check if the node belongs to the next block
-            if blocks and node in blocks[0]:
-                block = blocks[0]
+            if len(block) == 1 and block[0].name != 'cx':
+                # an intermediate node that was added into the overall list
+                new_dag.apply_operation_back(block[0].op, block[0].qargs, block[0].cargs)
+            else:
                 # find the qubits involved in this block
                 block_qargs = set()
                 for nd in block:
@@ -89,7 +115,6 @@ def run(self, dag):
                 for nd in block:
                     if nd.op.name == basis_gate_name:
                         basis_count += 1
-                    nodes_seen.add(nd)
                     subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs])
                 unitary = UnitaryGate(Operator(subcirc))  # simulates the circuit
                 if self.force_consolidate or unitary.num_qubits > 2 or \
@@ -99,22 +124,8 @@ def run(self, dag):
                         unitary, sorted(block_qargs, key=lambda x: block_index_map[x]))
                 else:
                     for nd in block:
-                        nodes_seen.add(nd)
                         new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs)
 
-                del blocks[0]
-            else:
-                # the node could belong to some future block, but in that case
-                # we simply skip it. It is guaranteed that we will revisit that
-                # future block, via its other nodes
-                for block in blocks[1:]:
-                    if node in block:
-                        break
-                # freestanding nodes can just be added
-                else:
-                    nodes_seen.add(node)
-                    new_dag.apply_operation_back(node.op, node.qargs, node.cargs)
-
         return new_dag
 
     def _block_qargs_to_indices(self, block_qargs, global_index_map):


Test patch:
diff --git a/test/python/transpiler/test_collect_2q_blocks.py b/test/python/transpiler/test_collect_2q_blocks.py
--- a/test/python/transpiler/test_collect_2q_blocks.py
+++ b/test/python/transpiler/test_collect_2q_blocks.py
@@ -54,6 +54,49 @@ def test_blocks_in_topological_order(self):
         pass_.run(dag)
         self.assertTrue(pass_.property_set['block_list'], [block_1, block_2])
 
+    def test_block_interrupted_by_gate(self):
+        """Test that blocks interrupted by a gate that can't be added
+        to the block can be collected correctly
+
+        This was raised in #2775 where a measure in the middle of a block
+        stopped the block collection from working properly. This was because
+        the pass didn't expect to have measures in the middle of the circuit.
+
+        blocks : [['cx', 'id', 'id', 'id'], ['id', 'cx']]
+
+                ┌───┐┌────┐┌─┐      ┌────┐┌───┐
+        q_0: |0>┤ X ├┤ Id ├┤M├──────┤ Id ├┤ X ├
+                └─┬─┘├────┤└╥┘┌────┐└────┘└─┬─┘
+        q_1: |0>──■──┤ Id ├─╫─┤ Id ├────────■──
+                     └────┘ ║ └────┘
+         c_0: 0 ════════════╩══════════════════
+
+        """
+        qc = QuantumCircuit(2, 1)
+        qc.cx(1, 0)
+        qc.iden(0)
+        qc.iden(1)
+        qc.measure(0, 0)
+        qc.iden(0)
+        qc.iden(1)
+        qc.cx(1, 0)
+
+        dag = circuit_to_dag(qc)
+        pass_ = Collect2qBlocks()
+        pass_.run(dag)
+
+        # list from Collect2QBlocks of nodes that it should have put into blocks
+        good_names = ["cx", "u1", "u2", "u3", "id"]
+        dag_nodes = [node for node in dag.topological_op_nodes() if node.name in good_names]
+
+        # we have to convert them to sets as the ordering can be different
+        # but equivalent between python 3.5 and 3.7
+        # there is no implied topology in a block, so this isn't an issue
+        dag_nodes = [set(dag_nodes[:4]), set(dag_nodes[4:])]
+        pass_nodes = [set(bl) for bl in pass_.property_set['block_list']]
+
+        self.assertEqual(dag_nodes, pass_nodes)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py
--- a/test/python/transpiler/test_consolidate_blocks.py
+++ b/test/python/transpiler/test_consolidate_blocks.py
@@ -19,6 +19,7 @@
 import unittest
 import numpy as np
 
+from qiskit import transpile
 from qiskit.circuit import QuantumCircuit, QuantumRegister
 from qiskit.extensions import UnitaryGate
 from qiskit.converters import circuit_to_dag
@@ -27,6 +28,8 @@
 from qiskit.providers.basicaer import UnitarySimulatorPy
 from qiskit.quantum_info.operators.measures import process_fidelity
 from qiskit.test import QiskitTestCase
+from qiskit.transpiler import PassManager
+from qiskit.transpiler.passes import Collect2qBlocks
 
 
 class TestConsolidateBlocks(QiskitTestCase):
@@ -146,6 +149,105 @@ def test_block_spanning_two_regs(self):
         fidelity = process_fidelity(new_dag.op_nodes()[0].op.to_matrix(), unitary.to_matrix())
         self.assertAlmostEqual(fidelity, 1.0, places=7)
 
+    def test_node_added_before_block(self):
+        """Test that a node before a block remains before the block
+
+        This issue was raised in #2737 where the measure was moved
+        to be after the 2nd ID gate, as the block was added when the
+        first node in the block was seen.
+
+        blocks = [['id', 'cx', 'id']]
+
+                ┌────┐┌───┐
+        q_0: |0>┤ Id ├┤ X ├──────
+                └┬─┬─┘└─┬─┘┌────┐
+        q_1: |0>─┤M├────■──┤ Id ├
+                 └╥┘       └────┘
+        c_0:  0 ══╩══════════════
+        """
+        qc = QuantumCircuit(2, 1)
+        qc.iden(0)
+        qc.measure(1, 0)
+        qc.cx(1, 0)
+        qc.iden(1)
+
+        # can't just add all the nodes to one block as in other tests
+        # as we are trying to test the block gets added in the correct place
+        # so use a pass to collect the blocks instead
+        pass_manager = PassManager()
+        pass_manager.append(Collect2qBlocks())
+        pass_manager.append(ConsolidateBlocks())
+        qc1 = transpile(qc, pass_manager=pass_manager)
+
+        self.assertEqual(qc, qc1)
+
+    def test_node_added_after_block(self):
+        """Test that a node after the block remains after the block
+
+        This example was raised in #2764, and checks that the final CX
+        stays after the main block, even though one of the nodes in the
+        block was declared after it. This occured when the block was
+        added when the last node in the block was seen.
+
+        blocks = [['cx', 'id', 'id']]
+
+        q_0: |0>─────────────■──
+                     ┌────┐┌─┴─┐
+        q_1: |0>──■──┤ Id ├┤ X ├
+                ┌─┴─┐├────┤└───┘
+        q_2: |0>┤ X ├┤ Id ├─────
+                └───┘└────┘
+        """
+        qc = QuantumCircuit(3)
+        qc.cx(1, 2)
+        qc.iden(1)
+        qc.cx(0, 1)
+        qc.iden(2)
+
+        pass_manager = PassManager()
+        pass_manager.append(Collect2qBlocks())
+        pass_manager.append(ConsolidateBlocks())
+        qc1 = transpile(qc, pass_manager=pass_manager)
+
+        self.assertEqual(qc, qc1)
+
+    def test_node_middle_of_blocks(self):
+        """Test that a node surrounded by blocks stays in the same place
+
+        This is a larger test to ensure multiple blocks can all be collected
+        and added back in the correct order.
+
+        blocks = [['cx', 'id'], ['cx', 'id'], ['id', 'cx'], ['id', 'cx']]
+
+        q_0: |0>──■───────────────────■──
+                ┌─┴─┐┌────┐   ┌────┐┌─┴─┐
+        q_1: |0>┤ X ├┤ Id ├─X─┤ Id ├┤ X ├
+                ├───┤├────┤ │ ├────┤├───┤
+        q_2: |0>┤ X ├┤ Id ├─X─┤ Id ├┤ X ├
+                └─┬─┘└────┘   └────┘└─┬─┘
+        q_3: |0>──■───────────────────■──
+
+        """
+        qc = QuantumCircuit(4)
+        qc.cx(0, 1)
+        qc.cx(3, 2)
+        qc.iden(1)
+        qc.iden(2)
+
+        qc.swap(1, 2)
+
+        qc.iden(1)
+        qc.iden(2)
+        qc.cx(0, 1)
+        qc.cx(3, 2)
+
+        pass_manager = PassManager()
+        pass_manager.append(Collect2qBlocks())
+        pass_manager.append(ConsolidateBlocks())
+        qc1 = transpile(qc, pass_manager=pass_manager)
+
+        self.assertEqual(qc, qc1)
+
 
 if __name__ == '__main__':
     unittest.main()