Repository: Qiskit/qiskit

Issue:
Extract layout from mapping passes
Layout should be established during the embed stage of the transpiler, but several mapping passes have logic to establish their own layouts or change the layout provided. These should be extracted into their own passes and mappers should instead rely on the layouts set in the property_set.



Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/passes/mapping/stochastic_swap.py b/qiskit/transpiler/passes/mapping/stochastic_swap.py
--- a/qiskit/transpiler/passes/mapping/stochastic_swap.py
+++ b/qiskit/transpiler/passes/mapping/stochastic_swap.py
@@ -78,7 +78,6 @@ def __init__(self, coupling_map, initial_layout=None,
         super().__init__()
         self.coupling_map = coupling_map
         self.initial_layout = initial_layout
-        self.input_layout = None
         self.trials = trials
         self.seed = seed
         self.qregs = None
@@ -112,8 +111,6 @@ def run(self, dag):
             raise TranspilerError(
                 "Mappers require to have the layout to be the same size as the coupling map")
 
-        self.input_layout = self.initial_layout.copy()
-
         self.qregs = dag.qregs
         if self.seed is None:
             self.seed = np.random.randint(0, np.iinfo(np.int32).max)
@@ -121,7 +118,6 @@ def run(self, dag):
         logger.debug("StochasticSwap RandomState seeded with seed=%s", self.seed)
 
         new_dag = self._mapper(dag, self.coupling_map, trials=self.trials)
-        # self.property_set["layout"] = self.initial_layout
         return new_dag
 
     def _layer_permutation(self, layer_partition, layout, qubit_subset,
@@ -163,13 +159,11 @@ def _layer_permutation(self, layer_partition, layout, qubit_subset,
                                   coupling, trials,
                                   self.qregs, self.rng)
 
-    def _layer_update(self, i, first_layer, best_layout, best_depth,
+    def _layer_update(self, i, best_layout, best_depth,
                       best_circuit, layer_list):
         """Provide a DAGCircuit for a new mapped layer.
 
         i (int) = layer number
-        first_layer (bool) = True if this is the first layer in the
-            circuit with any multi-qubit gates
         best_layout (Layout) = layout returned from _layer_permutation
         best_depth (int) = depth returned from _layer_permutation
         best_circuit (DAGCircuit) = swap circuit returned
@@ -188,38 +182,23 @@ def _layer_update(self, i, first_layer, best_layout, best_depth,
             if qubit.register not in dagcircuit_output.qregs.values():
                 dagcircuit_output.add_qreg(qubit.register)
 
-        # If this is the first layer with multi-qubit gates,
-        # output all layers up to this point and ignore any
-        # swap gates. Set the initial layout.
-        if first_layer:
-            logger.debug("layer_update: first multi-qubit gate layer")
-            # Output all layers up to this point
-            for j in range(i + 1):
-                # Make qubit edge map and extend by classical bits
-                edge_map = layout.combine_into_edge_map(self.initial_layout)
-                for bit in dagcircuit_output.clbits():
-                    edge_map[bit] = bit
-                dagcircuit_output.compose_back(layer_list[j]["graph"], edge_map)
-        # Otherwise, we output the current layer and the associated swap gates.
+        # Output any swaps
+        if best_depth > 0:
+            logger.debug("layer_update: there are swaps in this layer, "
+                         "depth %d", best_depth)
+            dagcircuit_output.extend_back(best_circuit)
         else:
-            # Output any swaps
-            if best_depth > 0:
-                logger.debug("layer_update: there are swaps in this layer, "
-                             "depth %d", best_depth)
-                dagcircuit_output.extend_back(best_circuit)
-            else:
-                logger.debug("layer_update: there are no swaps in this layer")
-            # Make qubit edge map and extend by classical bits
-            edge_map = layout.combine_into_edge_map(self.initial_layout)
-            for bit in dagcircuit_output.clbits():
-                edge_map[bit] = bit
-            # Output this layer
-            dagcircuit_output.compose_back(layer_list[i]["graph"], edge_map)
+            logger.debug("layer_update: there are no swaps in this layer")
+        # Make qubit edge map and extend by classical bits
+        edge_map = layout.combine_into_edge_map(self.initial_layout)
+        for bit in dagcircuit_output.clbits():
+            edge_map[bit] = bit
+        # Output this layer
+        dagcircuit_output.compose_back(layer_list[i]["graph"], edge_map)
 
         return dagcircuit_output
 
-    def _mapper(self, circuit_graph, coupling_graph,
-                trials=20):
+    def _mapper(self, circuit_graph, coupling_graph, trials=20):
         """Map a DAGCircuit onto a CouplingMap using swap gates.
 
         Use self.initial_layout for the initial layout.
@@ -249,25 +228,9 @@ def _mapper(self, circuit_graph, coupling_graph,
         for i, v in enumerate(layerlist):
             logger.debug("    %d: %s", i, v["partition"])
 
-        if self.initial_layout is not None:
-            qubit_subset = self.initial_layout.get_virtual_bits().keys()
-        else:
-            # Supply a default layout for this dag
-            self.initial_layout = Layout()
-            physical_qubit = 0
-            for qreg in circuit_graph.qregs.values():
-                for index in range(qreg.size):
-                    self.initial_layout[(qreg, index)] = physical_qubit
-                    physical_qubit += 1
-            qubit_subset = self.initial_layout.get_virtual_bits().keys()
-            # Restrict the coupling map to the image of the layout
-            coupling_graph = coupling_graph.subgraph(
-                self.initial_layout.get_physical_bits().keys())
-            if coupling_graph.size() < len(self.initial_layout):
-                raise TranspilerError("Coupling map too small for default layout")
-            self.input_layout = self.initial_layout.copy()
-
-        # Find swap circuit to preceed to each layer of input circuit
+        qubit_subset = self.initial_layout.get_virtual_bits().keys()
+
+        # Find swap circuit to precede each layer of input circuit
         layout = self.initial_layout.copy()
 
         # Construct an empty DAGCircuit with the same set of
@@ -287,7 +250,6 @@ def _mapper(self, circuit_graph, coupling_graph,
         for bit in circuit_graph.clbits():
             identity_wire_map[bit] = bit
 
-        first_layer = True  # True until first layer is output
         logger.debug("initial_layout = %s", layout)
 
         # Iterate over layers
@@ -330,48 +292,35 @@ def _mapper(self, circuit_graph, coupling_graph,
                     # If this layer is only single-qubit gates,
                     # and we have yet to see multi-qubit gates,
                     # continue to the next inner iteration
-                    if trivial_flag and first_layer:
+                    if trivial_flag:
                         logger.debug("mapper: skip to next sublayer")
                         continue
 
-                    if first_layer:
-                        self.initial_layout = layout
-
                     # Update the record of qubit positions
                     # for each inner iteration
                     layout = best_layout
                     # Update the DAG
                     dagcircuit_output.extend_back(
                         self._layer_update(j,
-                                           first_layer,
                                            best_layout,
                                            best_depth,
                                            best_circuit,
                                            serial_layerlist),
                         identity_wire_map)
-                    if first_layer:
-                        first_layer = False
 
             else:
                 # Update the record of qubit positions for each iteration
                 layout = best_layout
 
-                if first_layer:
-                    self.initial_layout = layout
-
                 # Update the DAG
                 dagcircuit_output.extend_back(
                     self._layer_update(i,
-                                       first_layer,
                                        best_layout,
                                        best_depth,
                                        best_circuit,
                                        layerlist),
                     identity_wire_map)
 
-                if first_layer:
-                    first_layer = False
-
         # This is the final edgemap. We might use it to correctly replace
         # any measurements that needed to be removed earlier.
         logger.debug("mapper: self.initial_layout = %s", pformat(self.initial_layout))
@@ -379,16 +328,6 @@ def _mapper(self, circuit_graph, coupling_graph,
         last_edgemap = layout.combine_into_edge_map(self.initial_layout)
         logger.debug("mapper: last_edgemap = %s", pformat(last_edgemap))
 
-        # 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
-        # This code is dead due to changes to first_layer above.
-        if first_layer:
-            logger.debug("mapper: first_layer flag still set")
-            layout = self.initial_layout
-            for i, layer in enumerate(layerlist):
-                edge_map = layout.combine_into_edge_map(self.initial_layout)
-                dagcircuit_output.compose_back(layer["graph"], edge_map)
-
         return dagcircuit_output
 
 


Test patch:
diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py
--- a/test/python/transpiler/test_stochastic_swap.py
+++ b/test/python/transpiler/test_stochastic_swap.py
@@ -49,7 +49,7 @@ def test_multiple_registers_with_layout_adjust(self):
         circ.measure(qr_a[0], cr_c[2])
         dag = circuit_to_dag(circ)
 
-        layout = Layout({qr_q[0]: 0, qr_q[1]: 1, qr_a[0]: 2})
+        layout = Layout({qr_q[0]: 0, qr_q[1]: 2, qr_a[0]: 1})
 
         pass_ = StochasticSwap(coupling, layout, 20, 13)
         after = pass_.run(dag)
@@ -81,33 +81,6 @@ def test_multiple_registers_with_good_layout(self):
 
         self.assertEqual(dag, after)
 
-    def test_multiple_registers_with_default_layout(self):
-        """
-        Test two registers + measurements using no layout.
-        The default layout will be adjusted to all gates
-        become nearest neighbor. The pass has the layout
-        in pass_.initial_layout.
-        """
-        coupling = CouplingMap([[0, 1], [1, 2]])
-
-        qr_q = QuantumRegister(2, 'q')
-        qr_a = QuantumRegister(1, 'a')
-        cr_c = ClassicalRegister(3, 'c')
-        circ = QuantumCircuit(qr_q, qr_a, cr_c)
-        circ.cx(qr_q[0], qr_a[0])
-        circ.cx(qr_q[1], qr_a[0])
-        circ.measure(qr_q[0], cr_c[0])
-        circ.measure(qr_q[1], cr_c[1])
-        circ.measure(qr_a[0], cr_c[2])
-        dag = circuit_to_dag(circ)
-
-        layout = None
-
-        pass_ = StochasticSwap(coupling, layout, 20, 13)
-        after = pass_.run(dag)
-
-        self.assertEqual(dag, after)
-
     def test_trivial_case(self):
         """
          q0:--(+)-[H]-(+)-
@@ -155,17 +128,18 @@ def test_trivial_in_same_layer(self):
         self.assertEqual(dag, after)
 
     def test_permute_wires_1(self):
-        """All of the test_permute_wires tests are derived
-        from the basic mapper tests. In this case, the
-        stochastic mapper handles a single
-        layer by qubit label permutations so as not to
-        introduce additional swap gates. The new
-        initial layout is found in pass_.initial_layout.
-         q0:-------
-         q1:--(+)--
+        """
+         q0:--------
+
+         q1:---.----
                |
-         q2:---.---
+         q2:--(+)---
          Coupling map: [1]--[0]--[2]
+         q0:--x-(+)-
+              |  |
+         q1:--|--.--
+              |
+         q2:--x-----
         """
         coupling = CouplingMap([[0, 1], [0, 2]])
 
@@ -177,7 +151,11 @@ def test_permute_wires_1(self):
         pass_ = StochasticSwap(coupling, None, 20, 13)
         after = pass_.run(dag)
 
-        self.assertEqual(dag, after)
+        expected = QuantumCircuit(qr)
+        expected.swap(qr[0], qr[2])
+        expected.cx(qr[1], qr[0])
+
+        self.assertEqual(circuit_to_dag(expected), after)
 
     def test_permute_wires_2(self):
         """
@@ -187,6 +165,11 @@ def test_permute_wires_2(self):
                 |
          qr2:--(+)-------
          Coupling map: [0]--[1]--[2]
+         qr0:----.---[H]-
+                 |
+         qr1:-x-(+)------
+              |
+         qr2:-x----------
         """
         coupling = CouplingMap([[1, 0], [1, 2]])
 
@@ -199,7 +182,12 @@ def test_permute_wires_2(self):
         pass_ = StochasticSwap(coupling, None, 20, 13)
         after = pass_.run(dag)
 
-        self.assertEqual(dag, after)
+        expected = QuantumCircuit(qr)
+        expected.swap(qr[1], qr[2])
+        expected.cx(qr[0], qr[1])
+        expected.h(qr[0])
+
+        self.assertEqual(circuit_to_dag(expected), after)
 
     def test_permute_wires_3(self):
         """
@@ -211,6 +199,13 @@ def test_permute_wires_3(self):
                 |    |
          qr3:---.---(+)-
          Coupling map: [0]--[1]--[2]--[3]
+         qr0:-x------------
+              |
+         qr1:-x--(+)---.---
+                  |    |
+         qr2:-x---.---(+)--
+              |
+         qr3:-x------------
         """
         coupling = CouplingMap([[0, 1], [1, 2], [2, 3]])
 
@@ -223,7 +218,13 @@ def test_permute_wires_3(self):
         pass_ = StochasticSwap(coupling, None, 20, 13)
         after = pass_.run(dag)
 
-        self.assertEqual(dag, after)
+        expected = QuantumCircuit(qr)
+        expected.swap(qr[0], qr[1])
+        expected.swap(qr[2], qr[3])
+        expected.cx(qr[1], qr[2])
+        expected.cx(qr[2], qr[1])
+
+        self.assertEqual(circuit_to_dag(expected), after)
 
     def test_permute_wires_4(self):
         """No qubit label permutation occurs if the first
@@ -253,15 +254,15 @@ def test_permute_wires_4(self):
         circuit.cx(qr[3], qr[0])
         dag = circuit_to_dag(circuit)
 
+        pass_ = StochasticSwap(coupling, None, 20, 13)
+        after = pass_.run(dag)
+
         expected = QuantumCircuit(qr)
         expected.h(qr[3])
         expected.swap(qr[2], qr[3])
         expected.swap(qr[0], qr[1])
         expected.cx(qr[2], qr[1])
 
-        pass_ = StochasticSwap(coupling, None, 20, 13)
-        after = pass_.run(dag)
-
         self.assertEqual(circuit_to_dag(expected), after)
 
     def test_permute_wires_5(self):
@@ -276,6 +277,13 @@ def test_permute_wires_5(self):
                 |
          qr3:---.--[H]--
          Coupling map: [0]--[1]--[2]--[3]
+         qr0:-x-----------
+              |
+         qr1:-x--(+)------
+                  |
+         qr2:-x---.--[H]--
+              |
+         qr3:-x-----------
         """
         coupling = CouplingMap([[0, 1], [1, 2], [2, 3]])
 
@@ -288,32 +296,13 @@ def test_permute_wires_5(self):
         pass_ = StochasticSwap(coupling, None, 20, 13)
         after = pass_.run(dag)
 
-        self.assertEqual(dag, after)
-
-    def test_permute_wires_6(self):
-        """
-         qr0:--(+)-------.--
-                |        |
-         qr1:---|--------|--
-                |
-         qr2:---|--------|--
-                |        |
-         qr3:---.--[H]--(+)-
-         Coupling map: [0]--[1]--[2]--[3]
-        """
-        coupling = CouplingMap([[0, 1], [1, 2], [2, 3]])
-
-        qr = QuantumRegister(4, 'q')
-        circuit = QuantumCircuit(qr)
-        circuit.cx(qr[3], qr[0])
-        circuit.h(qr[3])
-        circuit.cx(qr[0], qr[3])
-        dag = circuit_to_dag(circuit)
-
-        pass_ = StochasticSwap(coupling, None, 20, 13)
-        after = pass_.run(dag)
+        expected = QuantumCircuit(qr)
+        expected.swap(qr[0], qr[1])
+        expected.swap(qr[2], qr[3])
+        expected.cx(qr[2], qr[1])
+        expected.h(qr[2])
 
-        self.assertEqual(dag, after)
+        self.assertEqual(circuit_to_dag(expected), after)
 
     def test_overoptimization_case(self):
         """Check mapper overoptimization.
@@ -444,7 +433,7 @@ def test_map_with_layout(self):
         circ.measure(qrb[0], cr[2])
         dag = circuit_to_dag(circ)
 
-        layout = Layout({qra[0]: 0, qra[1]: 1, qrb[0]: 2})
+        layout = Layout({qra[0]: 0, qra[1]: 2, qrb[0]: 1})
 
         pass_ = StochasticSwap(coupling, layout, 20, 13)
         after = pass_.run(dag)
@@ -487,23 +476,22 @@ def test_congestion(self):
         #                                        ║
         #  c_3: 0 ═══════════════════════════════╩═══════════════════════
         #
-        #                                ┌─┐┌───┐                     ┌─┐
-        # q_0: |0>────────────────────■──┤M├┤ H ├──────────────────■──┤M├──────
-        #                           ┌─┴─┐└╥┘└───┘┌───┐┌───┐      ┌─┴─┐└╥┘┌─┐
-        # q_1: |0>──■───X───────────┤ X ├─╫──────┤ H ├┤ X ├─X────┤ X ├─╫─┤M├───
-        #         ┌─┴─┐ │      ┌───┐└───┘ ║      └───┘└─┬─┘ │    └───┘ ║ └╥┘┌─┐
-        # a_0: |0>┤ X ├─┼──────┤ H ├──────╫─────────────■───┼──────────╫──╫─┤M├
-        #         └───┘ │ ┌───┐└───┘      ║                 │ ┌─┐      ║  ║ └╥┘
-        # a_1: |0>──────X─┤ H ├───────────╫─────────────────X─┤M├──────╫──╫──╫─
-        #                 └───┘           ║                   └╥┘      ║  ║  ║
-        #  c_0: 0 ════════════════════════╩════════════════════╬═══════╩══╬══╬═
-        #                                                      ║          ║  ║
-        #  c_1: 0 ═════════════════════════════════════════════╬══════════╩══╬═
-        #                                                      ║             ║
-        #  c_2: 0 ═════════════════════════════════════════════╬═════════════╩═
-        #                                                      ║
-        #  c_3: 0 ═════════════════════════════════════════════╩═══════════════
-        #
+        #                    ┌───┐                      ┌───┐   ┌─┐
+        #  q_0: |0>───────X──┤ H ├──────────────────────┤ X ├───┤M├
+        #                 │  └───┘┌─┐        ┌───┐      └─┬─┘┌─┐└╥┘
+        #  q_1: |0>──■────X────■──┤M├──────X─┤ X ├─X──────■──┤M├─╫─
+        #          ┌─┴─┐┌───┐  │  └╥┘      │ └─┬─┘ │ ┌─┐     └╥┘ ║
+        #  a_0: |0>┤ X ├┤ H ├──┼───╫───────┼───■───┼─┤M├──────╫──╫─
+        #          └───┘└───┘┌─┴─┐ ║ ┌───┐ │ ┌───┐ │ └╥┘ ┌─┐  ║  ║
+        #  a_1: |0>──────────┤ X ├─╫─┤ H ├─X─┤ H ├─X──╫──┤M├──╫──╫─
+        #                    └───┘ ║ └───┘   └───┘    ║  └╥┘  ║  ║
+        #   c_0: 0 ════════════════╩══════════════════╬═══╬═══╩══╬═
+        #                                             ║   ║      ║
+        #   c_1: 0 ═══════════════════════════════════╬═══╬══════╩═
+        #                                             ║   ║
+        #   c_2: 0 ═══════════════════════════════════╩═══╬════════
+        #                                                 ║
+        #   c_3: 0 ═══════════════════════════════════════╩════════
         # Layout from mapper:
         # {qr[0]: 0,
         #  qr[1]: 1,
@@ -513,19 +501,20 @@ def test_congestion(self):
         #     2
         #     |
         # 0 - 1 - 3
+
         expected = QuantumCircuit(qr, ar, cr)
         expected.cx(qr[1], ar[0])
+        expected.h(ar[0])
         expected.swap(qr[0], qr[1])
+        expected.h(qr[0])
         expected.cx(qr[1], ar[1])
-        expected.h(ar[1])
-        expected.h(ar[0])
         expected.measure(qr[1], cr[0])
-        expected.h(qr[0])
-        expected.swap(qr[1], ar[1])
         expected.h(ar[1])
+        expected.swap(qr[1], ar[1])
         expected.cx(ar[0], qr[1])
-        expected.measure(ar[0], cr[2])
+        expected.h(ar[1])
         expected.swap(qr[1], ar[1])
+        expected.measure(ar[0], cr[2])
         expected.measure(ar[1], cr[3])
         expected.cx(qr[1], qr[0])
         expected.measure(qr[1], cr[0])
@@ -536,6 +525,7 @@ def test_congestion(self):
 
         pass_ = StochasticSwap(coupling, layout, 20, 13)
         after = pass_.run(dag)
+
         self.assertEqual(expected_dag, after)
 
     def test_all_single_qubit(self):