Repository: Qiskit/qiskit

Issue:
Swap Mapper Uses Qubits After Measurement
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

- **Qiskit (Python SDK) version**: Latest
- **Python version**: 3.6
- **Operating system**: OSX

### What is the current behavior?
The Qobj / QASM generated after doing the swap mapping can be invalid due to the use of already measured qubits by the swap mapper.  In addition, the measured qubits can be swapped in such a way that measuring multiple qubits gets mapped onto multiple measurements of the same qubit.

### Steps to reproduce the problem

```python
from qiskit import ClassicalRegister, QuantumRegister
from qiskit import QuantumCircuit, compile, register
register()

q = QuantumRegister(5)
c = ClassicalRegister(2)
qc = QuantumCircuit(q, c)

qc.h(q[0])
qc.h(q[1])
qc.h(q[2])
qc.h(q[3])
qc.h(q[4])
qc.cx(q[0], q[1])
qc.cx(q[1], q[3])
qc.cx(q[1], q[4])
qc.cx(q[1], q[3])
qc.measure(q[4], c[1])
qc.measure(q[0], c[0])

seed = 213
qobj = compile(qc, 'ibmqx5', seed=seed)
```

The output circuit is:

![mapper_bug_qasm](https://user-images.githubusercontent.com/1249193/43070970-48196d06-8e3f-11e8-86bc-6356464cc9e3.png)

showing that the measurements are no longer last, and the measurements of `q[0]` and `q[4]` become repeated measurements of `q[0]`:

```python
['OPENQASM 2.0',
 '\ninclude "qelib1.inc"',
 '\nqreg q[16]',
 '\ncreg c3[2]',
 '\nu2(0,3.14159265358979) q[15]',
 '\nu2(0,3.14159265358979) q[14]',
 '\nu2(0,3.14159265358979) q[2]',
 '\ncx q[15],q[14]',
 '\ncx q[1],q[0]',
 '\nu2(0,3.14159265358979) q[14]',
 '\nu2(0,3.14159265358979) q[15]',
 '\ncx q[15],q[14]',
 '\nu2(0,3.14159265358979) q[14]',
 '\nu2(0,3.14159265358979) q[15]',
 '\ncx q[15],q[14]',
 '\nu2(0,3.14159265358979) q[15]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nmeasure q[0] -> c3[0]',
 '\nu2(0,3.14159265358979) q[1]',
 '\ncx q[1],q[2]',
 '\ncx q[1],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nu2(0,3.14159265358979) q[1]',
 '\ncx q[1],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nu2(0,3.14159265358979) q[1]',
 '\ncx q[1],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\ncx q[15],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nu2(0,3.14159265358979) q[15]',
 '\ncx q[15],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nu2(0,3.14159265358979) q[15]',
 '\ncx q[15],q[0]',
 '\nu2(0,3.14159265358979) q[0]',
 '\nu2(0,3.14159265358979) q[15]',
 '\ncx q[15],q[0]',
 '\ncx q[15],q[2]',
 '\nmeasure q[0] -> c3[1]',
 '\n']
```


### What is the expected behavior?
The hardware requires that no gates be done after a measurement.  In addition, this blocks the possibility of swapping qubits so that the same one is measured twice.

### Suggested solutions
Working on it.





Discussion:

Resolving patch:
diff --git a/qiskit/mapper/__init__.py b/qiskit/mapper/__init__.py
--- a/qiskit/mapper/__init__.py
+++ b/qiskit/mapper/__init__.py
@@ -10,4 +10,6 @@
 from ._compiling import two_qubit_kak, euler_angles_1q
 from ._coupling import Coupling, coupling_dict2list, coupling_list2dict
 from ._mappererror import MapperError
-from ._mapping import swap_mapper, direction_mapper, cx_cancellation, optimize_1q_gates
+from ._mapping import (swap_mapper, direction_mapper, cx_cancellation,
+                       optimize_1q_gates, remove_last_measurements,
+                       return_last_measurements)
diff --git a/qiskit/mapper/_mapping.py b/qiskit/mapper/_mapping.py
--- a/qiskit/mapper/_mapping.py
+++ b/qiskit/mapper/_mapping.py
@@ -22,6 +22,7 @@
 from qiskit.qasm import _node as node
 from qiskit.mapper import MapperError
 from qiskit.dagcircuit import DAGCircuit
+from qiskit.dagcircuit._dagcircuiterror import DAGCircuitError
 from qiskit.unroll import DagUnroller, DAGBackend
 from qiskit.mapper._quaternion import quaternion_from_euler
 
@@ -437,7 +438,9 @@ def swap_mapper(circuit_graph, coupling_graph,
         a layout dict mapping qubits of circuit_graph into qubits
         of coupling_graph. The layout may differ from the initial_layout
         if the first layer of gates cannot be executed on the
-        initial_layout.
+        initial_layout. Finally, returned is the final layer qubit
+        permutation that is needed to add measurements back in.
+
     Raises:
         MapperError: if there was any error during the mapping or with the
             parameters.
@@ -574,6 +577,10 @@ def swap_mapper(circuit_graph, coupling_graph,
                 initial_layout = layout
                 first_layer = False
 
+    # This is the final layout that we need to correctly replace
+    # any measurements that needed to be removed before the swap
+    last_layout = layout
+
     # If first_layer is still set, the circuit only has single-qubit gates
     # so we can use the initial layout to output the entire circuit
     if first_layer:
@@ -585,7 +592,7 @@ def swap_mapper(circuit_graph, coupling_graph,
     dag_unrrolled = DagUnroller(dagcircuit_output,
                                 DAGBackend(basis.split(",")))
     dagcircuit_output = dag_unrrolled.expand_gates()
-    return dagcircuit_output, initial_layout
+    return dagcircuit_output, initial_layout, last_layout
 
 
 def yzy_to_zyz(xi, theta1, theta2, eps=1e-9):
@@ -815,3 +822,54 @@ def optimize_1q_gates(circuit):
         if right_name == "nop":
             unrolled._remove_op_node(run[0])
     return unrolled
+
+
+def remove_last_measurements(dag_circuit, perform_remove=True):
+    """Removes all measurements that occur as the last operation
+    on a given qubit for a DAG circuit.  Measurements that are followed by
+    additional gates are untouched.
+
+    This operation is done in-place on the input DAG circuit if perform_pop=True.
+
+    Parameters:
+        dag_circuit (qiskit.dagcircuit._dagcircuit.DAGCircuit): DAG circuit.
+        perform_remove (bool): Whether to perform removal, or just return node list.
+
+    Returns:
+        list: List of all measurements that were removed.
+    """
+    removed_meas = []
+    try:
+        meas_nodes = dag_circuit.get_named_nodes('measure')
+    except DAGCircuitError:
+        return removed_meas
+
+    for idx in meas_nodes:
+        _, succ_map = dag_circuit._make_pred_succ_maps(idx)
+        if len(succ_map) == 2:
+            # All succesors of the measurement are outputs, one for qubit and one for cbit
+            # (As opposed to more gates being applied), and it is safe to remove the
+            # measurement node and add it back after the swap mapper is done.
+            removed_meas.append(dag_circuit.multi_graph.node[idx])
+            if perform_remove:
+                dag_circuit._remove_op_node(idx)
+    return removed_meas
+
+
+def return_last_measurements(dag_circuit, removed_meas, final_layout):
+    """Returns the measurements to a quantum circuit, removed by
+    `remove_last_measurements` after the swap mapper is finished.
+
+    This operation is done in-place on the input DAG circuit.
+
+    Parameters:
+        dag_circuit (qiskit.dagcircuit._dagcircuit.DAGCircuit): DAG circuit.
+        removed_meas (list): List of measurements previously removed.
+        final_layout (dict): Qubit layout after swap mapping.
+    """
+    if any(removed_meas) and 'measure' not in dag_circuit.basis.keys():
+        dag_circuit.add_basis_element("measure", 1, 1, 0)
+    for meas in removed_meas:
+        new_q_label = final_layout[meas['qargs'][0]]
+        dag_circuit.apply_operation_back(name='measure', qargs=[new_q_label],
+                                         cargs=meas['cargs'])
diff --git a/qiskit/transpiler/_transpiler.py b/qiskit/transpiler/_transpiler.py
--- a/qiskit/transpiler/_transpiler.py
+++ b/qiskit/transpiler/_transpiler.py
@@ -21,7 +21,8 @@
 from qiskit.dagcircuit import DAGCircuit
 from qiskit.unroll import DagUnroller, DAGBackend, JsonBackend
 from qiskit.mapper import (Coupling, optimize_1q_gates, coupling_list2dict, swap_mapper,
-                           cx_cancellation, direction_mapper)
+                           cx_cancellation, direction_mapper,
+                           remove_last_measurements, return_last_measurements)
 from qiskit._gate import Gate
 from qiskit.qobj import Qobj, QobjConfig, QobjExperiment, QobjItem
 
@@ -243,8 +244,10 @@ def transpile(dag_circuit, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
                         dag_circuit.property_summary())
             # Insert swap gates
             coupling = Coupling(coupling_list2dict(coupling_map))
+            removed_meas = remove_last_measurements(dag_circuit)
+            logger.info("measurements moved: %s", removed_meas)
             logger.info("initial layout: %s", initial_layout)
-            dag_circuit, final_layout = swap_mapper(
+            dag_circuit, final_layout, last_layout = swap_mapper(
                 dag_circuit, coupling, initial_layout, trials=20, seed=seed)
             logger.info("final layout: %s", final_layout)
             # Expand swaps
@@ -256,6 +259,8 @@ def transpile(dag_circuit, basis_gates='u1,u2,u3,cx,id', coupling_map=None,
             cx_cancellation(dag_circuit)
             # Simplify single qubit gates
             dag_circuit = optimize_1q_gates(dag_circuit)
+            return_last_measurements(dag_circuit, removed_meas,
+                                     last_layout)
             logger.info("post-mapping properties: %s",
                         dag_circuit.property_summary())
 


Test patch:
diff --git a/test/python/qasm/move_measurements.qasm b/test/python/qasm/move_measurements.qasm
new file mode 100644
--- /dev/null
+++ b/test/python/qasm/move_measurements.qasm
@@ -0,0 +1,212 @@
+OPENQASM 2.0;
+include "qelib1.inc";
+qreg qa[2];
+qreg qb[3];
+qreg qN[3];
+qreg qc[1];
+qreg qNt[2];
+qreg qt[1];
+creg c[3];
+x qa[1];
+x qb[0];
+x qb[1];
+x qN[0];
+x qN[1];
+x qNt[0];
+x qNt[1];
+ccx qa[0],qb[0],qc[0];
+cx qa[0],qb[0];
+ccx qa[1],qb[1],qb[2];
+cx qa[1],qb[1];
+ccx qc[0],qb[1],qb[2];
+cx qa[1],qb[1];
+cx qa[1],qb[1];
+cx qc[0],qb[1];
+cx qa[0],qb[0];
+ccx qa[0],qb[0],qc[0];
+cx qa[0],qb[0];
+cx qN[0],qb[0];
+ccx qN[0],qb[0],qc[0];
+cx qN[0],qb[0];
+cx qc[0],qb[1];
+cx qN[1],qb[1];
+cx qN[1],qb[1];
+ccx qc[0],qb[1],qb[2];
+cx qN[1],qb[1];
+ccx qN[1],qb[1],qb[2];
+cx qN[0],qb[0];
+ccx qN[0],qb[0],qc[0];
+x qb[2];
+cx qb[2],qt[0];
+x qb[2];
+ccx qt[0],qNt[0],qN[0];
+h qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+h qc[0];
+t qN[0];
+ccx qt[0],qNt[0],qN[0];
+t qNt[0];
+tdg qN[0];
+ccx qt[0],qNt[0],qN[0];
+ccx qt[0],qNt[0],qN[0];
+ccx qt[0],qc[0],qN[1];
+ccx qt[0],qNt[1],qN[1];
+ccx qt[0],qNt[1],qN[1];
+h qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qc[0],qN[2];
+t qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qc[0],qN[2];
+t qN[2];
+h qN[2];
+t qN[1];
+ccx qt[0],qc[0],qN[1];
+t qc[0];
+tdg qN[1];
+ccx qt[0],qc[0],qN[1];
+ccx qt[0],qNt[1],qN[1];
+h qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qNt[1],qN[2];
+t qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qNt[1],qN[2];
+t qN[2];
+h qN[2];
+t qN[1];
+ccx qt[0],qNt[1],qN[1];
+t qNt[1];
+tdg qN[1];
+ccx qt[0],qNt[1],qN[1];
+ccx qt[0],qNt[0],qN[0];
+h qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+h qc[0];
+t qN[0];
+ccx qt[0],qNt[0],qN[0];
+t qNt[0];
+tdg qN[0];
+ccx qt[0],qNt[0],qN[0];
+ccx qN[0],qb[0],qc[0];
+cx qN[0],qb[0];
+ccx qN[1],qb[1],qb[2];
+cx qN[1],qb[1];
+ccx qc[0],qb[1],qb[2];
+cx qN[1],qb[1];
+cx qN[1],qb[1];
+cx qc[0],qb[1];
+cx qN[0],qb[0];
+ccx qN[0],qb[0],qc[0];
+cx qN[0],qb[0];
+h qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+h qc[0];
+t qN[0];
+ccx qt[0],qNt[0],qN[0];
+t qNt[0];
+tdg qN[0];
+ccx qt[0],qNt[0],qN[0];
+ccx qt[0],qNt[0],qN[0];
+h qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qNt[1],qN[2];
+t qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qNt[1],qN[2];
+t qN[2];
+h qN[2];
+t qN[1];
+ccx qt[0],qNt[1],qN[1];
+t qNt[1];
+tdg qN[1];
+ccx qt[0],qNt[1],qN[1];
+ccx qt[0],qNt[1],qN[1];
+h qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qc[0],qN[2];
+t qN[2];
+ccx qt[0],qN[1],qN[2];
+tdg qN[2];
+ccx qt[0],qc[0],qN[2];
+t qN[2];
+h qN[2];
+t qN[1];
+ccx qt[0],qc[0],qN[1];
+t qc[0];
+tdg qN[1];
+ccx qt[0],qc[0],qN[1];
+ccx qt[0],qNt[1],qN[1];
+ccx qt[0],qNt[1],qN[1];
+ccx qt[0],qc[0],qN[1];
+ccx qt[0],qNt[0],qN[0];
+h qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+ccx qt[0],qN[0],qc[0];
+tdg qc[0];
+ccx qt[0],qNt[0],qc[0];
+t qc[0];
+h qc[0];
+t qN[0];
+ccx qt[0],qNt[0],qN[0];
+t qNt[0];
+tdg qN[0];
+ccx qt[0],qNt[0],qN[0];
+ccx qt[0],qNt[0],qN[0];
+cx qa[0],qb[0];
+ccx qa[0],qb[0],qc[0];
+cx qa[0],qb[0];
+cx qc[0],qb[1];
+cx qa[1],qb[1];
+cx qa[1],qb[1];
+ccx qc[0],qb[1],qb[2];
+cx qa[1],qb[1];
+ccx qa[1],qb[1],qb[2];
+cx qa[0],qb[0];
+ccx qa[0],qb[0],qc[0];
+cx qb[2],qt[0];
+ccx qa[0],qb[0],qc[0];
+cx qa[0],qb[0];
+ccx qa[1],qb[1],qb[2];
+cx qa[1],qb[1];
+ccx qc[0],qb[1],qb[2];
+cx qa[1],qb[1];
+cx qa[1],qb[1];
+cx qc[0],qb[1];
+cx qa[0],qb[0];
+ccx qa[0],qb[0],qc[0];
+cx qa[0],qb[0];
+measure qb[0] -> c[0];
+measure qb[1] -> c[1];
+measure qb[2] -> c[2];
\ No newline at end of file
diff --git a/test/python/test_mapper.py b/test/python/test_mapper.py
--- a/test/python/test_mapper.py
+++ b/test/python/test_mapper.py
@@ -12,6 +12,9 @@
 import qiskit.wrapper
 from qiskit import (QuantumProgram, load_qasm_string, mapper, qasm, unroll)
 from qiskit.qobj import Qobj
+from qiskit.transpiler._transpiler import transpile
+from qiskit.dagcircuit._dagcircuit import DAGCircuit
+from qiskit.mapper._mapping import remove_last_measurements
 from .common import QiskitTestCase
 
 
@@ -25,6 +28,19 @@ def __init__(self):
                               'coupling_map': qx4_cmap}
 
 
+class FakeQX5BackEnd(object):
+    """A fake QX5 backend.
+    """
+    def __init__(self):
+        qx5_cmap = [[1, 0], [1, 2], [2, 3], [3, 4], [3, 14], [5, 4], [6, 5],
+                    [6, 7], [6, 11], [7, 10], [8, 7], [9, 8], [9, 10],
+                    [11, 10], [12, 5], [12, 11], [12, 13], [13, 4],
+                    [13, 14], [15, 0], [15, 2], [15, 14]]
+        self.configuration = {'name': 'fake_qx5', 'basis_gates': 'u1,u2,u3,cx,id',
+                              'simulator': False, 'n_qubits': 16,
+                              'coupling_map': qx5_cmap}
+
+
 class MapperTest(QiskitTestCase):
     """Test the mapper."""
 
@@ -224,6 +240,24 @@ def test_yzy_zyz_cases(self):
         qobj2 = qiskit.wrapper.compile(circ2, backend)
         self.assertIsInstance(qobj2, Qobj)
 
+    def test_move_measurements(self):
+        """Measurements applied AFTER swap mapping.
+        """
+        backend = FakeQX5BackEnd()
+        cmap = backend.configuration['coupling_map']
+        circ = qiskit.load_qasm_file(self._get_resource_path('qasm/move_measurements.qasm'),
+                                     name='move')
+        dag_circuit = DAGCircuit.fromQuantumCircuit(circ)
+        lay = {('qa', 0): ('q', 0), ('qa', 1): ('q', 1), ('qb', 0): ('q', 15),
+               ('qb', 1): ('q', 2), ('qb', 2): ('q', 14), ('qN', 0): ('q', 3),
+               ('qN', 1): ('q', 13), ('qN', 2): ('q', 4), ('qc', 0): ('q', 12),
+               ('qNt', 0): ('q', 5), ('qNt', 1): ('q', 11), ('qt', 0): ('q', 6)}
+        out_dag = transpile(dag_circuit, initial_layout=lay,
+                            coupling_map=cmap, format='dag')
+        moved_meas = remove_last_measurements(out_dag, perform_remove=False)
+        meas_nodes = out_dag.get_named_nodes('measure')
+        self.assertEqual(len(moved_meas), len(meas_nodes))
+
 
 # QASMs expected by the tests.
 EXPECTED_QASM_SYMBOLIC_BINARY = """OPENQASM 2.0;
@@ -278,9 +312,9 @@ def test_yzy_zyz_cases(self):
 cx q[1],q[0];
 cx q[1],q[0];
 u2(0,3.14159265358979) q[0];
-measure q[0] -> cr[1];
 u2(0,3.14159265358979) q[1];
-measure q[1] -> cr[0];\n"""
+measure q[1] -> cr[0];
+measure q[0] -> cr[1];\n"""
 
 QASM_SYMBOLIC_POWER = """OPENQASM 2.0;
 include "qelib1.inc";