Repository: Qiskit/qiskit

Issue:
Transpiler reorders measure through barriers
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations

- **Qiskit Terra version**: master
- **Python version**: 3.6
- **Operating system**: MacOS

### What is the current behavior?

A recent merge into master (I think probably #1705) is `qiskit-aer` master to fail because it reorders measurements past barriers.

### Steps to reproduce the problem

One of the Aer tests is a non-local measure noise: if I measure qubit 0, apply a noise operator on qubit 1. To test this the circuit has to ensure measure of qubit 0 happens before measure of qubit 1 and looks like

```
qr = QuantumRegister(2)
cr = ClassicalRegister(2)
circ = QuantumCircuit(qr, cr)
circ.measure(qr[0], cr[0])
circ.barrier(qr)
circ.measure(qr[1], cr[1])
```

Before this would ensure the output qobj had instructions:

```
[QobjInstruction(memory=[0], name='measure', qubits=[0]),
 QobjInstruction(memory=[1], name='measure', qubits=[1])]
```

But now it reorders the measures past the barrier returning:
```
[QobjInstruction(memory=[1], name='measure', qubits=[1]),
 QobjInstruction(memory=[0], name='measure', qubits=[0])]
```

### What is the expected behavior?

Measure instructions should respect barrier placement

### Suggested solutions





Discussion:
The full failing test is:

```python
# test circuit
qr = QuantumRegister(2, 'qr')
cr = ClassicalRegister(2, 'cr')
circuit = QuantumCircuit(qr, cr)
 # use barrier to ensure measure qubit 0 is before qubit 1
circuit.barrier(qr)
circuit.measure(qr[0], cr[0])
circuit.barrier(qr)
 circuit.measure(qr[1], cr[1])



 # test noise model
error = pauli_error([('X', 0.25), ('I', 0.75)])
noise_model = NoiseModel()
noise_model.add_nonlocal_quantum_error(error, 'measure', [0], [1])

# Execute
backend = QasmSimulator()
shots = 2000
target = {'0x0': 3 * shots / 4, '0x2': shots / 4}
qobj = compile([circuit], backend, shots=shots,
                          basis_gates=noise_model.basis_gates)
result = backend.run(qobj, noise_model=noise_model).result()
counts = result.data(0)['counts']
```

And it should have `target == counts` up to statistical error on the counts. Instead, because measure of qubit-1 is being put before measure of qubit-0, the noise to qubit 1 is being applied after it has already been measured returning final counts `{'0x0': 2000}`.

Resolving patch:
diff --git a/qiskit/dagcircuit/_dagcircuit.py b/qiskit/dagcircuit/_dagcircuit.py
--- a/qiskit/dagcircuit/_dagcircuit.py
+++ b/qiskit/dagcircuit/_dagcircuit.py
@@ -914,7 +914,7 @@ def _make_pred_succ_maps(self, n):
             n (int): reference to self.multi_graph node id
 
         Returns:
-            set(dict): set({predecessor_map, successor_map})
+            tuple(dict): tuple(predecessor_map, successor_map)
                 These map from wire (Register, int) to predecessor (successor)
                 nodes of n.
         """
@@ -975,6 +975,15 @@ def _match_dag_nodes(node1, node2):
         """
         copy_node1 = {k: v for (k, v) in node1.items()}
         copy_node2 = {k: v for (k, v) in node2.items()}
+
+        # For barriers, qarg order is not significant so compare as sets
+        if 'barrier' == copy_node1['name'] == copy_node2['name']:
+            node1_qargs = set(copy_node1.pop('qargs', []))
+            node2_qargs = set(copy_node2.pop('qargs', []))
+
+            if node1_qargs != node2_qargs:
+                return False
+
         return copy_node1 == copy_node2
 
     def __eq__(self, other):
@@ -1278,6 +1287,14 @@ def successors(self, node):
         """Returns the successors of a node."""
         return self.multi_graph.successors(node)
 
+    def ancestors(self, node):
+        """Returns the ancestors of a node."""
+        return nx.ancestors(self.multi_graph, node)
+
+    def descendants(self, node):
+        """Returns the descendants of a node."""
+        return nx.descendants(self.multi_graph, node)
+
     def quantum_successors(self, node):
         """Returns the successors of a node that are connected by a quantum edge"""
         successors = []
diff --git a/qiskit/transpiler/passes/mapping/barrier_before_final_measurements.py b/qiskit/transpiler/passes/mapping/barrier_before_final_measurements.py
--- a/qiskit/transpiler/passes/mapping/barrier_before_final_measurements.py
+++ b/qiskit/transpiler/passes/mapping/barrier_before_final_measurements.py
@@ -7,7 +7,11 @@
 
 
 """
-This pass adds a barrier before the final measurements.
+This pass adds a barrier before the set of final measurements. Measurements
+are considered final if they are followed by no other operations (aside from
+other measurements or barriers.)
+
+A new barrier will not be added if an equivalent barrier is already present.
 """
 
 from qiskit.extensions.standard.barrier import Barrier
@@ -20,47 +24,79 @@ class BarrierBeforeFinalMeasurements(TransformationPass):
 
     def run(self, dag):
         """Return a circuit with a barrier before last measurments."""
-        last_measures = []
-        for measure in dag.get_named_nodes('measure'):
-            is_last_measurement = all([after_measure in dag.output_map.values() for after_measure in
-                                       dag.successors(measure)])
-            if is_last_measurement:
-                last_measures.append(measure)
-
-        if not last_measures:
-            return dag
 
-        # create a laywer with the barrier and the measurements in last_measures operation
-        dag.add_basis_element('barrier', len(last_measures), 0, 0)
-        barried_layer = DAGCircuit()
-        last_measures_nodes = [dag.multi_graph.node[node] for node in last_measures]
-        last_measures_qubits = [node['qargs'][0] for node in last_measures_nodes]
+        # Collect DAG nodes which are followed only by barriers or other measures.
+        final_op_types = ['measure', 'barrier']
+        final_ops = []
+        for candidate_op in dag.get_named_nodes(*final_op_types):
+            nodes_after_candidate = [dag.multi_graph.nodes[node_id]
+                                     for node_id in dag.descendants(candidate_op)]
+            is_final_op = all([node['type'] == 'out'
+                               or (node['type'] == 'op' and node['op'].name in final_op_types)
+                               for node in nodes_after_candidate])
 
-        # Add registers from the original dag.
+            if is_final_op:
+                final_ops.append(candidate_op)
+
+        if not final_ops:
+            return dag
+
+        # Create a layer with the barrier and add registers from the original dag.
+        barrier_layer = DAGCircuit()
         for qreg in dag.qregs.values():
-            barried_layer.add_qreg(qreg)
+            barrier_layer.add_qreg(qreg)
         for creg in dag.cregs.values():
-            barried_layer.add_creg(creg)
+            barrier_layer.add_creg(creg)
+
+        final_qubits = set(dag.multi_graph.node[final_op]['qargs'][0]
+                           for final_op in final_ops)
+
+        barrier_layer.apply_operation_back(Barrier(qubits=final_qubits))
+        new_barrier_id = barrier_layer.node_counter
+
+        # Preserve order of final ops collected earlier from the original DAG.
+        ordered_node_ids = [node_id for node_id in dag.node_nums_in_topological_order()
+                            if node_id in set(final_ops)]
+        ordered_final_nodes = [dag.multi_graph.node[node] for node in ordered_node_ids]
+
+        # Move final ops to the new layer and append the new layer to the DAG.
+        for final_node in ordered_final_nodes:
+            barrier_layer.apply_operation_back(final_node['op'])
+
+        for final_op in final_ops:
+            dag._remove_op_node(final_op)
+
+        # Check to see if the new barrier added to the DAG is equivalent to any
+        # existing barriers, and if so, consolidate the two.
+        our_ancestors = barrier_layer.ancestors(new_barrier_id)
+        our_descendants = barrier_layer.descendants(new_barrier_id)
+        our_qubits = final_qubits
+
+        existing_barriers = barrier_layer.get_named_nodes('barrier')
+        existing_barriers.remove(new_barrier_id)
+
+        for candidate_barrier in existing_barriers:
+            their_ancestors = barrier_layer.ancestors(candidate_barrier)
+            their_descendants = barrier_layer.descendants(candidate_barrier)
+            their_qubits = set(barrier_layer.multi_graph.nodes[candidate_barrier]['op'].qargs)
 
-        # Add the barrier operation
-        barried_layer.apply_operation_back(Barrier(qubits=last_measures_qubits))
+            if (
+                    not our_qubits.isdisjoint(their_qubits)
+                    and our_ancestors.isdisjoint(their_descendants)
+                    and our_descendants.isdisjoint(their_ancestors)
+            ):
+                merge_barrier = Barrier(qubits=(our_qubits | their_qubits))
+                barrier_layer.apply_operation_front(merge_barrier)
 
-        # Add the measurements to the behind the barrier
-        for last_measures_node in last_measures_nodes:
-            barried_layer.apply_operation_back(last_measures_node['op'])
+                merge_barrier_id = barrier_layer.node_counter
+                our_ancestors = our_ancestors | their_ancestors
+                our_descendants = our_descendants | their_descendants
 
-        # Remove barriers in front the measurements in the original dag
-        for last_measure in last_measures:
-            for predecesor in dag.multi_graph.predecessors(last_measure):
-                if dag.multi_graph.nodes[predecesor]['type'] == 'op' and \
-                        isinstance(dag.multi_graph.nodes[predecesor]['op'], Barrier):
-                    dag._remove_op_node(predecesor)
+                barrier_layer._remove_op_node(candidate_barrier)
+                barrier_layer._remove_op_node(new_barrier_id)
 
-        # Remove the measurements from the original dag
-        for last_measure in last_measures:
-            dag._remove_op_node(last_measure)
+                new_barrier_id = merge_barrier_id
 
-        # Extend the original dag with the barried layer
-        dag.extend_back(barried_layer)
+        dag.extend_back(barrier_layer)
 
         return dag


Test patch:
diff --git a/test/python/transpiler/test_add_barrier_before_measuremets.py b/test/python/transpiler/test_add_barrier_before_measuremets.py
--- a/test/python/transpiler/test_add_barrier_before_measuremets.py
+++ b/test/python/transpiler/test_add_barrier_before_measuremets.py
@@ -95,6 +95,7 @@ def test_two_qregs(self):
          q1:--------|--[m]--  -> q1:-------|---|--[m]--
                     |   |                  |   |   |
          c0:--------.---|---      c0:----------.---|---
+                        |                          |
          c1:------------.---      c0:--------------.---
         """
         qr0 = QuantumRegister(1, 'q0')
@@ -151,8 +152,8 @@ def test_two_qregs_to_a_single_creg(self):
     def test_preserve_measure_for_conditional(self):
         """ Test barrier is inserted after any measurements used for conditionals
 
-         q0:--[H]--[m]------------     q0:--[H]--[m]-------|-------
-                    |                             |        |
+         q0:--[H]--[m]------------     q0:--[H]--[m]---------------
+                    |                             |
          q1:--------|--[ z]--[m]--  -> q1:--------|--[ z]--|--[m]--
                     |    |    |                   |    |       |
          c0:--------.--[=1]---|---     c0:--------.--[=1]------|---
@@ -211,11 +212,11 @@ def test_handle_redundancy(self):
 
     def test_remove_barrier_in_different_qregs(self):
         """ Two measurements in different qregs to the same creg
-                                     |
+
          q0:--|--[m]------     q0:---|--[m]------
                   |                  |   |
          q1:--|---|--[m]--  -> q1:---|---|--[m]--
-                  |   |              |   |   |
+                  |   |                  |   |
          c0:------.---|---     c0:-------.---|---
             ----------.---        -----------.---
         """
@@ -239,6 +240,121 @@ def test_remove_barrier_in_different_qregs(self):
 
         self.assertEqual(result, circuit_to_dag(expected))
 
+    def test_preserve_barriers_for_measurement_ordering(self):
+        """ If the circuit has a barrier to enforce a measurement order, preserve it in the output.
+
+         q:---[m]--|-------     q:---|--[m]--|-------
+           ----|---|--[m]--  ->   ---|---|---|--[m]--
+               |       |                 |       |
+         c:----.-------|---     c:-------.-------|---
+           ------------.---       ---------------.---
+        """
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(2, 'c')
+
+        circuit = QuantumCircuit(qr, cr)
+        circuit.measure(qr[0], cr[0])
+        circuit.barrier(qr)
+        circuit.measure(qr[1], cr[1])
+
+        expected = QuantumCircuit(qr, cr)
+        expected.barrier(qr)
+        expected.measure(qr[0], cr[0])
+        expected.barrier(qr)
+        expected.measure(qr[1], cr[1])
+
+        pass_ = BarrierBeforeFinalMeasurements()
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
+    def test_measures_followed_by_barriers_should_be_final(self):
+        """ If a measurement is followed only by a barrier, insert the barrier before it.
+
+         q:---[H]--|--[m]--|-------     q:---[H]--|--[m]-|-------
+           ---[H]--|---|---|--[m]--  ->   ---[H]--|---|--|--[m]--
+                       |       |                      |      |
+         c:------------.-------|---     c:------------.------|---
+           --------------------.---       -------------------.---
+        """
+        qr = QuantumRegister(2, 'q')
+        cr = ClassicalRegister(2, 'c')
+
+        circuit = QuantumCircuit(qr, cr)
+        circuit.h(qr)
+        circuit.barrier(qr)
+        circuit.measure(qr[0], cr[0])
+        circuit.barrier(qr)
+        circuit.measure(qr[1], cr[1])
+
+        expected = QuantumCircuit(qr, cr)
+        expected.h(qr)
+        expected.barrier(qr)
+        expected.measure(qr[0], cr[0])
+        expected.barrier(qr)
+        expected.measure(qr[1], cr[1])
+
+        pass_ = BarrierBeforeFinalMeasurements()
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
+    def test_should_merge_with_smaller_duplicate_barrier(self):
+        """ If an equivalent barrier exists covering a subset of the qubits
+        covered by the new barrier, it should be replaced.
+
+         q:---|--[m]-------------     q:---|--[m]-------------
+           ---|---|---[m]--------  ->   ---|---|---[m]--------
+           -------|----|---[m]---       ---|---|----|---[m]---
+                  |    |    |                  |    |    |
+         c:-------.----|----|----     c:-------.----|----|----
+           ------------.----|----       ------------.----|----
+           -----------------.----       -----------------.----
+        """
+        qr = QuantumRegister(3, 'q')
+        cr = ClassicalRegister(3, 'c')
+
+        circuit = QuantumCircuit(qr, cr)
+        circuit.barrier(qr[0], qr[1])
+        circuit.measure(qr, cr)
+
+        expected = QuantumCircuit(qr, cr)
+        expected.barrier(qr)
+        expected.measure(qr, cr)
+
+        pass_ = BarrierBeforeFinalMeasurements()
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
+    def test_should_merge_with_larger_duplicate_barrier(self):
+        """ If a barrier exists and is stronger than the barrier to be inserted,
+        preserve the existing barrier and do not insert a new barrier.
+
+         q:---|--[m]--|-------     q:---|--[m]-|-------
+           ---|---|---|--[m]--  ->   ---|---|--|--[m]--
+           ---|---|---|---|---       ---|---|--|---|---
+                  |       |                 |      |
+         c:-------.-------|---     c:-------.------|---
+           ---------------.---       --------------.---
+           -------------------       ------------------
+        """
+        qr = QuantumRegister(3, 'q')
+        cr = ClassicalRegister(3, 'c')
+
+        circuit = QuantumCircuit(qr, cr)
+        circuit.barrier(qr)
+        circuit.measure(qr[0], cr[0])
+        circuit.barrier(qr)
+        circuit.measure(qr[1], cr[1])
+
+        expected = circuit
+
+        pass_ = BarrierBeforeFinalMeasurements()
+        result = pass_.run(circuit_to_dag(circuit))
+
+        self.assertEqual(result, circuit_to_dag(expected))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/python/transpiler/test_lookahead_swap.py b/test/python/transpiler/test_lookahead_swap.py
--- a/test/python/transpiler/test_lookahead_swap.py
+++ b/test/python/transpiler/test_lookahead_swap.py
@@ -12,7 +12,6 @@
 from qiskit.mapper import CouplingMap
 from qiskit.converters import circuit_to_dag
 from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
-from qiskit.transpiler import PassManager, transpile_dag
 from qiskit.test import QiskitTestCase
 
 
@@ -35,15 +34,11 @@ def test_lookahead_swap_doesnt_modify_mapped_circuit(self):
         # Create coupling map which contains all two-qubit gates in the circuit.
         coupling_map = CouplingMap([[0, 1], [0, 2]])
 
-        pass_manager = PassManager()
-        pass_manager.append(LookaheadSwap(coupling_map))
-        mapped_dag = transpile_dag(original_dag, pass_manager=pass_manager)
+        mapped_dag = LookaheadSwap(coupling_map).run(original_dag)
 
         self.assertEqual(original_dag, mapped_dag)
 
-        second_pass_manager = PassManager()
-        second_pass_manager.append(LookaheadSwap(coupling_map))
-        remapped_dag = transpile_dag(mapped_dag, pass_manager=second_pass_manager)
+        remapped_dag = LookaheadSwap(coupling_map).run(mapped_dag)
 
         self.assertEqual(mapped_dag, remapped_dag)
 
@@ -61,9 +56,7 @@ def test_lookahead_swap_should_add_a_single_swap(self):
 
         coupling_map = CouplingMap([[0, 1], [1, 2]])
 
-        pass_manager = PassManager()
-        pass_manager.append([LookaheadSwap(coupling_map)])
-        mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager)
+        mapped_dag = LookaheadSwap(coupling_map).run(dag_circuit)
 
         self.assertEqual(mapped_dag.count_ops().get('swap', 0),
                          dag_circuit.count_ops().get('swap', 0) + 1)
@@ -89,9 +82,7 @@ def test_lookahead_swap_finds_minimal_swap_solution(self):
 
         coupling_map = CouplingMap([[0, 1], [1, 2]])
 
-        pass_manager = PassManager()
-        pass_manager.append([LookaheadSwap(coupling_map)])
-        mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager)
+        mapped_dag = LookaheadSwap(coupling_map).run(dag_circuit)
 
         self.assertEqual(mapped_dag.count_ops().get('swap', 0),
                          dag_circuit.count_ops().get('swap', 0) + 1)
@@ -118,9 +109,7 @@ def test_lookahead_swap_maps_measurements(self):
 
         coupling_map = CouplingMap([[0, 1], [1, 2]])
 
-        pass_manager = PassManager()
-        pass_manager.append([LookaheadSwap(coupling_map)])
-        mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager)
+        mapped_dag = LookaheadSwap(coupling_map).run(dag_circuit)
 
         mapped_measure_qargs = set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs'][0]
                                    for op in mapped_dag.get_named_nodes('measure'))
@@ -150,9 +139,7 @@ def test_lookahead_swap_maps_barriers(self):
 
         coupling_map = CouplingMap([[0, 1], [1, 2]])
 
-        pass_manager = PassManager()
-        pass_manager.append([LookaheadSwap(coupling_map)])
-        mapped_dag = transpile_dag(dag_circuit, pass_manager=pass_manager)
+        mapped_dag = LookaheadSwap(coupling_map).run(dag_circuit)
 
         mapped_barrier_qargs = [set(mapped_dag.multi_graph.nodes(data=True)[op]['qargs'])
                                 for op in mapped_dag.get_named_nodes('barrier')][0]