Repository: Qiskit/qiskit

Issue:
Creating Layout from a list of [int] or a map of {int: int}
### What should we add?

A Layout object can only be instantiated from the `Qubit`s of a particular circuit. i.e. if the intention is to lay the i'th qubit of the circuit on the j'th qubit of a coupling_map, this would be written as `qr[i] -> j`

However the qubits in a circuit are completely ordered (regardless of how many registers there are). So the Layout should not necessarily depend on the Qubit within that circuit --- just an integer should exactly specify that qubit.

The reason this is important is that the SetLayout transpiler pass takes a Layout which in turn requires the circuit Qubits. So a PassManager cannot be created independent of the circuit. This is against the design principle of PassManager which is supposed to be created once and run on many circuits.

Instead one should be able to create SetLayout like this:
```py
SetLayout(Layout([4, 3, 8, 6, 0]))   # this means a 5-qubit circuit would be laid out as `0: 4, 1: 3, 2: 8, 3: 6, 4: 0`
SetLayout(Layout({4: 0, 3: 1, 8: 2, 6: 3, 0: 4]))   # this means the same. Should also work if key/value are reversed.
```


Discussion:
I completely agree that `Layout` should be constructed like this, and the times we now use Rust in layout and routing passes e.g. `StochasticSwap`, the Rust struct we use is two arrays of integers.  We need to change the API a little to have this work right, though - currently `Layout.__getitem__` (and a host of other methods) infer whether you passed a virtual or physical qubit based on the input type.  We should get rid of that anyway, because it's _really_ slow for such an inner-loop structure (to the degree that it's faster to completely copy the structure than to swap two elements), but this is one more reason to do so.
> The reason this is important is that the SetLayout transpiler pass takes a Layout which in turn requires the circuit Qubits. So a PassManager cannot be created independent of the circuit. This is against the design principle of PassManager which is supposed to be created once and run on many circuits.

I agree this is something to fix. It seems to me that `SetLayout` should only be called only when `initial_layout` is provided to the level. That should make the PassManager independent, right?
> It seems to me that SetLayout should only be called only when initial_layout is provided to the level. That should make the PassManager independent, right?

not sure what you mean. By "level" if you mean preset passmanager levels, I think that's not the issue. I think SetLayout should be constructible from a simple integer list for the layout, independent of whether it's in a preset or custom passmanager.
Its been close to a year, are there any updates on this issue? This expect issue is a blocker for qiskit-experiments for switching to using pass managers, since an initial layout needs to be specified as an integer list to the `generate_preset_pass_manager` function.

To get pass managers to work with int list initial_layouts in the current form I hacked the `SetLayout` pass like this to borrow the `transpile` arg parsing and use it to generate the Layout on the fly based on dag registers:

```python
class SetLayout(AnalysisPass):

    def __init__(self, layout):
        super().__init__()
        self.layout = Layout(layout) if isinstance(layout, dict) else layout

        if layout is None or isinstance(layout, Layout):
            self._get_layout = lambda _: self.layout
        elif isinstancelist(layout):
            if all(isinstanceint(elem) for elem in layout):
                self._get_layout = lambda dag: Layout.from_intlist(
                    self.layout, *list(dag.qregs.values())
                )
            else:
                self._get_layout = lambda dag: Layout.from_qubit_list(
                    self.layout, *list(dag.qregs.values())
                )
        else:
            raise TranspilerError("The initial_layout parameter could not be parsed")

    def run(self, dag):
        self.property_set["layout"] = self._get_layout(dag)
        return dag
```
Just for reference I hit this in trying to slim down the transpile() function in: https://github.com/Qiskit/qiskit-terra/pull/10291 My workaround there was to do something similar to @chriseclectic's suggestion (just a bit smaller in scope) and deferred the int list initialization until `SetLayout`.

Resolving patch:
diff --git a/qiskit/transpiler/passes/layout/set_layout.py b/qiskit/transpiler/passes/layout/set_layout.py
--- a/qiskit/transpiler/passes/layout/set_layout.py
+++ b/qiskit/transpiler/passes/layout/set_layout.py
@@ -11,8 +11,7 @@
 # that they have been altered from the originals.
 
 """Set the ``layout`` property to the given layout."""
-
-
+from qiskit.transpiler import Layout, TranspilerError
 from qiskit.transpiler.basepasses import AnalysisPass
 
 
@@ -27,13 +26,18 @@ def __init__(self, layout):
         """SetLayout initializer.
 
         Args:
-            layout (Layout): the layout to set.
+            layout (Layout or List[int]): the layout to set. It can be:
+
+                * a :class:`Layout` instance: sets that layout.
+                * a list of integers: takes the index in the list as the physical position in which the
+                  virtual qubit is going to be mapped.
+
         """
         super().__init__()
         self.layout = layout
 
     def run(self, dag):
-        """Run the SetLayout pass on `dag`.
+        """Run the SetLayout pass on ``dag``.
 
         Args:
             dag (DAGCircuit): DAG to map.
@@ -41,5 +45,20 @@ def run(self, dag):
         Returns:
             DAGCircuit: the original DAG.
         """
-        self.property_set["layout"] = None if self.layout is None else self.layout.copy()
+        if isinstance(self.layout, list):
+            if len(self.layout) != len(dag.qubits):
+                raise TranspilerError(
+                    "The length of the layout is different than the size of the "
+                    f"circuit: {len(self.layout)} <> {len(dag.qubits)}"
+                )
+            layout = Layout({phys: dag.qubits[i] for i, phys in enumerate(self.layout)})
+        elif isinstance(self.layout, Layout):
+            layout = self.layout.copy()
+        elif self.layout is None:
+            layout = None
+        else:
+            raise TranspilerError(
+                f"SetLayout was intialized with the layout type: {type(self.layout)}"
+            )
+        self.property_set["layout"] = layout
         return dag


Test patch:
diff --git a/test/python/transpiler/test_setlayout.py b/test/python/transpiler/test_setlayout.py
new file mode 100644
--- /dev/null
+++ b/test/python/transpiler/test_setlayout.py
@@ -0,0 +1,114 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2023.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Test the SetLayout pass"""
+
+import unittest
+
+from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister
+from qiskit.transpiler import CouplingMap, Layout
+from qiskit.transpiler.passes import SetLayout, ApplyLayout, FullAncillaAllocation
+from qiskit.test import QiskitTestCase
+from qiskit.transpiler import PassManager, TranspilerError
+
+
+class TestSetLayout(QiskitTestCase):
+    """Tests the SetLayout pass"""
+
+    def assertEqualToReference(self, result_to_compare):
+        """Compare result_to_compare to a reference
+
+                       ┌───┐ ░ ┌─┐
+              q_0 -> 0 ┤ H ├─░─┤M├───────────────
+                       ├───┤ ░ └╥┘┌─┐
+              q_1 -> 1 ┤ H ├─░──╫─┤M├────────────
+                       ├───┤ ░  ║ └╥┘      ┌─┐
+              q_4 -> 2 ┤ H ├─░──╫──╫───────┤M├───
+                       ├───┤ ░  ║  ║ ┌─┐   └╥┘
+              q_2 -> 3 ┤ H ├─░──╫──╫─┤M├────╫────
+                       └───┘ ░  ║  ║ └╥┘    ║
+        ancilla_0 -> 4 ─────────╫──╫──╫─────╫────
+                       ┌───┐ ░  ║  ║  ║ ┌─┐ ║
+              q_3 -> 5 ┤ H ├─░──╫──╫──╫─┤M├─╫────
+                       ├───┤ ░  ║  ║  ║ └╥┘ ║ ┌─┐
+              q_5 -> 6 ┤ H ├─░──╫──╫──╫──╫──╫─┤M├
+                       └───┘ ░  ║  ║  ║  ║  ║ └╥┘
+               meas: 6/═════════╩══╩══╩══╩══╩══╩═
+                                0  1  2  3  4  5
+        """
+        qr = QuantumRegister(6, "q")
+        ancilla = QuantumRegister(1, "ancilla")
+        cl = ClassicalRegister(6, "meas")
+        reference = QuantumCircuit(qr, ancilla, cl)
+        reference.h(qr)
+        reference.barrier(qr)
+        reference.measure(qr, cl)
+        pass_manager = PassManager()
+        pass_manager.append(
+            SetLayout(
+                Layout({qr[0]: 0, qr[1]: 1, qr[4]: 2, qr[2]: 3, ancilla[0]: 4, qr[3]: 5, qr[5]: 6})
+            )
+        )
+        pass_manager.append(ApplyLayout())
+        self.assertEqual(result_to_compare, pass_manager.run(reference))
+
+    def test_setlayout_as_Layout(self):
+        """Construct SetLayout with a Layout."""
+
+        qr = QuantumRegister(6, "q")
+        circuit = QuantumCircuit(qr)
+        circuit.h(qr)
+        circuit.measure_all()
+
+        pass_manager = PassManager()
+        pass_manager.append(
+            SetLayout(Layout.from_intlist([0, 1, 3, 5, 2, 6], QuantumRegister(6, "q")))
+        )
+        pass_manager.append(FullAncillaAllocation(CouplingMap.from_line(7)))
+        pass_manager.append(ApplyLayout())
+        result = pass_manager.run(circuit)
+
+        self.assertEqualToReference(result)
+
+    def test_setlayout_as_list(self):
+        """Construct SetLayout with a list."""
+
+        qr = QuantumRegister(6, "q")
+        circuit = QuantumCircuit(qr)
+        circuit.h(qr)
+        circuit.measure_all()
+
+        pass_manager = PassManager()
+        pass_manager.append(SetLayout([0, 1, 3, 5, 2, 6]))
+        pass_manager.append(FullAncillaAllocation(CouplingMap.from_line(7)))
+        pass_manager.append(ApplyLayout())
+        result = pass_manager.run(circuit)
+
+        self.assertEqualToReference(result)
+
+    def test_raise_when_layout_len_does_not_match(self):
+        """Test error is raised if layout defined as list does not match the circuit size."""
+
+        qr = QuantumRegister(42, "q")
+        circuit = QuantumCircuit(qr)
+
+        pass_manager = PassManager()
+        pass_manager.append(SetLayout([0, 1, 3, 5, 2, 6]))
+        pass_manager.append(FullAncillaAllocation(CouplingMap.from_line(7)))
+        pass_manager.append(ApplyLayout())
+
+        with self.assertRaises(TranspilerError):
+            pass_manager.run(circuit)
+
+
+if __name__ == "__main__":
+    unittest.main()