Repository: Qiskit/qiskit

Issue:
ConsolidateBlocks drops classical conditional
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

From https://travis-ci.com/Qiskit/qiskit-terra/jobs/242965523#L6482 :

### Information

- **Qiskit Terra version**: master
- **Python version**: 3.5
- **Operating system**: Mac
```
>>> import qiskit as qk
>>> from qiskit.test.mock import FakeMelbourne
>>> qc = qk.QuantumCircuit.from_qasm_str('''
... OPENQASM 2.0;
... include "qelib1.inc";
... qreg q6476[8];
... qreg q6477[2];
... creg c1144[1];
... crz(0.0) q6476[7],q6476[0];
... if(c1144==1) u2(0.0,0.0) q6476[0];
... measure q6476[0] -> c1144[0];
... ''')
/Users/kevin.krsulichibm.com/q/qiskit-terra/qiskit/circuit/instruction.py:356: UserWarning: The instruction attribute, "control", will be renamed to "condition". The "control" method will later be used to create quantum controlled gates
  warnings.warn('The instruction attribute, "control", will be renamed '
>>> print(qc)
            ┌───────┐┌─────────┐┌─┐
q6476_0: |0>┤ Rz(0) ├┤ U2(0,0) ├┤M├
            └───┬───┘└────┬────┘└╥┘
q6476_1: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_2: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_3: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_4: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_5: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_6: |0>────┼─────────┼──────╫─
                │         │      ║
q6476_7: |0>────■─────────┼──────╫─
                          │      ║
q6477_0: |0>──────────────┼──────╫─
                          │      ║
q6477_1: |0>──────────────┼──────╫─
                       ┌──┴──┐   ║
 c1144_0: 0 ═══════════╡ = 1 ╞═══╩═
                       └─────┘
>>> qk.execute(qc, qk.Aer.get_backend('qasm_simulator')).result().get_counts()
{'0': 1024}
>>> qk.execute(qc, qk.Aer.get_backend('qasm_simulator'), optimization_level=3).result().get_counts()
{'0': 506, '1': 518}
>>> print(qk.transpile(qc, qk.Aer.get_backend('qasm_simulator'), optimization_level=3))
            ┌──────────┐┌─┐
q6476_0: |0>┤0         ├┤M├
            │          │└╥┘
q6476_1: |0>┤          ├─╫─
            │          │ ║
q6476_2: |0>┤          ├─╫─
            │          │ ║
q6476_3: |0>┤          ├─╫─
            │  unitary │ ║
q6476_4: |0>┤          ├─╫─
            │          │ ║
q6476_5: |0>┤          ├─╫─
            │          │ ║
q6476_6: |0>┤          ├─╫─
            │          │ ║
q6476_7: |0>┤1         ├─╫─
            └──────────┘ ║
q6477_0: |0>─────────────╫─
                         ║
q6477_1: |0>─────────────╫─
                         ║
 c1144_0: 0 ═════════════╩═
```

Attaching a callback to print the pass name and output dag, it looks like the conditional is lost in ConsolidateBlocks.

```
RemoveResetInZeroState
            ┌───────┐┌───┐┌───────┐┌───┐┌─────────┐┌─┐
q6476_0: |0>┤ U1(0) ├┤ X ├┤ U1(0) ├┤ X ├┤ U2(0,0) ├┤M├
            └───────┘└─┬─┘└───────┘└─┬─┘└────┬────┘└╥┘
q6476_1: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_2: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_3: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_4: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_5: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_6: |0>───────────┼─────────────┼───────┼──────╫─
                       │             │       │      ║
q6476_7: |0>───────────■─────────────■───────┼──────╫─
                                             │      ║
q6477_0: |0>─────────────────────────────────┼──────╫─
                                             │      ║
q6477_1: |0>─────────────────────────────────┼──────╫─
                                          ┌──┴──┐   ║
 c1144_0: 0 ══════════════════════════════╡ = 1 ╞═══╩═
                                          └─────┘
ConsolidateBlocks
            ┌──────────┐┌─┐
q6476_0: |0>┤0         ├┤M├
            │          │└╥┘
q6476_1: |0>┤          ├─╫─
            │          │ ║
q6476_2: |0>┤          ├─╫─
            │          │ ║
q6476_3: |0>┤          ├─╫─
            │  unitary │ ║
q6476_4: |0>┤          ├─╫─
            │          │ ║
q6476_5: |0>┤          ├─╫─
            │          │ ║
q6476_6: |0>┤          ├─╫─
            │          │ ║
q6476_7: |0>┤1         ├─╫─
            └──────────┘ ║
q6477_0: |0>─────────────╫─
                         ║
q6477_1: |0>─────────────╫─
                         ║
 c1144_0: 0 ═════════════╩═
```



Discussion:
`Collect2qBlocks` includes multiple gates with conditionals into the same block. For example, the circuit 
```
            ┌─────────┐┌───────┐┌───┐┌───────┐┌───┐┌─────────┐┌─┐
q6476_0: |0>┤ U2(0,0) ├┤ U1(0) ├┤ X ├┤ U1(0) ├┤ X ├┤ U2(0,0) ├┤M├
            └────┬────┘└───────┘└─┬─┘└───────┘└─┬─┘└────┬────┘└╥┘
q6476_1: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_2: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_3: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_4: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_5: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_6: |0>─────┼────────────────┼─────────────┼───────┼──────╫─
                 │                │             │       │      ║ 
q6476_7: |0>─────┼────────────────■─────────────■───────┼──────╫─
                 │                                      │      ║ 
q6477_0: |0>─────┼──────────────────────────────────────┼──────╫─
                 │                                      │      ║ 
q6477_1: |0>─────┼──────────────────────────────────────┼──────╫─
              ┌──┴──┐                                ┌──┴──┐   ║ 
 c1144_0: 0 ══╡ = 0 ╞════════════════════════════════╡ = 1 ╞═══╩═
              └─────┘                                └─────┘     

```
is collected into the blocks`[['u2', 'u1', 'cx', 'u1', 'cx', 'u2'], ['measure']]` which `ConsolidateBlocks` then joins together.

I don't think that this  grouping is correct as I cannot see a logical value that the condition on the unitary could hold in this instance. Therefore, I think that the update that is needed is to refine how `Collect2QBlocks` deals with gates which contain conditions.

Resolving patch:
diff --git a/qiskit/transpiler/passes/optimization/collect_2q_blocks.py b/qiskit/transpiler/passes/optimization/collect_2q_blocks.py
--- a/qiskit/transpiler/passes/optimization/collect_2q_blocks.py
+++ b/qiskit/transpiler/passes/optimization/collect_2q_blocks.py
@@ -68,7 +68,7 @@ def run(self, dag):
                     # If there is one predecessor, add it if it's on the right qubits
                     if len(pred) == 1 and not nodes_seen[pred[0]]:
                         pnd = pred[0]
-                        if pnd.name in good_names:
+                        if pnd.name in good_names and pnd.condition is None:
                             if (pnd.name == "cx" and set(pnd.qargs) == these_qubits) or \
                                     pnd.name != "cx" and not pnd.op.is_parameterized():
                                 group.append(pnd)
@@ -96,7 +96,7 @@ def run(self, dag):
                             break  # stop immediately if we hit a pair of cx
                         # Examine each predecessor
                         for pnd in sorted_pred:
-                            if pnd.name not in good_names:
+                            if pnd.name not in good_names or pnd.condition is not None:
                                 # 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) -
@@ -111,7 +111,7 @@ def run(self, dag):
                             # If cx, check qubits
                             else:
                                 pred_qubits = set(pnd.qargs)
-                                if pred_qubits == these_qubits:
+                                if pred_qubits == these_qubits and pnd.condition is None:
                                     # add if on same qubits
                                     if not nodes_seen[pnd]:
                                         group.append(pnd)
@@ -140,7 +140,7 @@ def run(self, dag):
                     # If there is one successor, add it if its on the right qubits
                     if len(succ) == 1 and not nodes_seen[succ[0]]:
                         snd = succ[0]
-                        if snd.name in good_names:
+                        if snd.name in good_names and snd.condition is None:
                             if (snd.name == "cx" and set(snd.qargs) == these_qubits) or \
                                     snd.name != "cx" and not snd.op.is_parameterized():
                                 group.append(snd)
@@ -169,7 +169,7 @@ def run(self, dag):
                             break  # stop immediately if we hit a pair of cx
                         # Examine each successor
                         for snd in sorted_succ:
-                            if snd.name not in good_names:
+                            if snd.name not in good_names or snd.condition is not None:
                                 # 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) -
@@ -187,7 +187,7 @@ def run(self, dag):
                             else:
                                 # If cx, check qubits
                                 succ_qubits = set(snd.qargs)
-                                if succ_qubits == these_qubits:
+                                if succ_qubits == these_qubits and snd.condition is None:
                                     # add if on same qubits
                                     if not nodes_seen[snd]:
                                         group.append(snd)


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
@@ -18,7 +18,7 @@
 
 import unittest
 
-from qiskit.circuit import QuantumCircuit, QuantumRegister
+from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
 from qiskit.converters import circuit_to_dag
 from qiskit.compiler import transpile
 from qiskit.transpiler import PassManager
@@ -137,7 +137,49 @@ def test_block_with_classical_register(self):
 
         transpile(qc, backend, pass_manager=pass_manager)
 
-        self.assertEqual([['cx', 'u1']],
+        self.assertEqual([['cx']],
+                         [[n.name for n in block]
+                          for block in pass_manager.property_set['block_list']])
+
+    def test_do_not_merge_conditioned_gates(self):
+        """Validate that classically conditioned gates are never considered for
+        inclusion in a block. Note that there are cases where gates conditioned
+        on the same (register, value) pair could be correctly merged, but this is
+        not yet implemented.
+
+                 ┌─────────┐┌─────────┐┌─────────┐      ┌───┐
+        qr_0: |0>┤ U1(0.1) ├┤ U1(0.2) ├┤ U1(0.3) ├──■───┤ X ├────■───
+                 └─────────┘└────┬────┘└────┬────┘┌─┴─┐ └─┬─┘  ┌─┴─┐
+        qr_1: |0>────────────────┼──────────┼─────┤ X ├───■────┤ X ├─
+                                 │          │     └───┘   │    └─┬─┘
+        qr_2: |0>────────────────┼──────────┼─────────────┼──────┼───
+                              ┌──┴──┐    ┌──┴──┐       ┌──┴──┐┌──┴──┐
+         cr_0: 0 ═════════════╡     ╞════╡     ╞═══════╡     ╞╡     ╞
+                              │ = 0 │    │ = 0 │       │ = 0 ││ = 1 │
+         cr_1: 0 ═════════════╡     ╞════╡     ╞═══════╡     ╞╡     ╞
+                              └─────┘    └─────┘       └─────┘└─────┘
+
+        Previously the blocks collected were : [['u1', 'u1', 'u1', 'cx', 'cx', 'cx']]
+        This is now corrected to : [['cx']]
+        """
+        # ref: https://github.com/Qiskit/qiskit-terra/issues/3215
+
+        qr = QuantumRegister(3, 'qr')
+        cr = ClassicalRegister(2, 'cr')
+
+        qc = QuantumCircuit(qr, cr)
+        qc.u1(0.1, 0)
+        qc.u1(0.2, 0).c_if(cr, 0)
+        qc.u1(0.3, 0).c_if(cr, 0)
+        qc.cx(0, 1)
+        qc.cx(1, 0).c_if(cr, 0)
+        qc.cx(0, 1).c_if(cr, 1)
+
+        pass_manager = PassManager()
+        pass_manager.append(Collect2qBlocks())
+
+        transpile(qc, pass_manager=pass_manager)
+        self.assertEqual([['cx']],
                          [[n.name for n in block]
                           for block in pass_manager.property_set['block_list']])