Repository: Qiskit/qiskit

Issue:
Publically expose `QuantumCircuit._layout` and add helper functions for working with embedded layout
### What should we add?

As discussed in #8800 the way we're embedding the `Layout` from the transpiler into the output quantum circuit is a bit of a weird place right now. We have a private attribute `_layout` which is used widely across the qiskit code but we provide no public or supported interface for working with this. This came to be an issue for fixing #8800 because to fix the issue we changed what was stored in the `_layout` attribute, and while it is a private layout external users depend on it because it was the only way to reason about the permutation caused by initial layout in the transpiler. To address this gap in 0.23.0 we should introduce a public `QuantumCircuit.layout` attribute and/or also have methods to work with the embedded layout. Like something to return the qargs for the circuit.


Discussion:
I think `QuantumCircuit.metadata` is a more natural place to include this information (since a `Layout` itself isn't strictly a property of a single circuit, it's a mapping of (virtual) qubits in one circuit to (physical) qubits in another). 

Another piece it would be good to discuss before we finalize this direction is whether or not to include pre/post `Permutation`s explicitly as a result of the layout and routing passes (with an optimization pass to move them after of qubit initialization and before qubit measurement). This had been discussed in the past and had some advantages in e.g. transpiling subcircuits, but I don't think there was ever a concrete plan of implementation.
I'm not convinced that `metadata` is right, and I think some of that is that I don't entirely agree with the idea that the "physical" qubits are in a different circuit - they're specific to a machine, but then so are the operations that are contained within the circuit.  I do buy the idea that the mapping isn't really an property of the circuit in a vacuum, but a transpiled circuit already doesn't exist in one.

I'm nervous about us suddenly applying important meaning to `metadata`, because the getter documentation for it says:
> The metadata for the circuit is a user provided ``dict`` of metadata for the circuit. It will not be used to influence the execution or operation of the circuit, but it is expected to be passed between all transforms of the circuit (ie transpilation) and that providers will associate any circuit metadata with the results it returns from execution of that circuit.

Putting the mapping to hardware qubits in that dictionary seems to me to be a violation of the idea that `metadata` doesn't influence execution or operation of the circuit.  We'd also be newly adding a restriction on the keys available for users to assign with arbitrary data, and I really don't like how using `dict` directly masks the typing information that IDEs / linters would be able to use if it's a regular attribute.

(Technically there's typing syntax to mitigate some of that last point, if you're prepared to reserve a key in `metadata` that users can _never_ use, but I personally think that's a hack around bad coding practices in Python.)
All that's to say that my vote is to use a public `.layout` attribute.
I would like to add a comment here to explain an observation that I made a few weeks ago. Specifically, I was working with the primitives and wanted to skip transpilation on the server side. This meant, that I had to transpile my circuit locally and set the corresponding option in my primitive job.

However, this immediately led to the problem that my (now physical) circuit contained a different number of qubits than my operator resulting in errors (obviously). I could not find a builtin way of how to deal with this situation (that is to say, I did not find a transformation method which would map my qubit operator to the correct set of physical qubits) so I ended up using the private `_layout` attribute of my QuantumCircuit to build a simple expansion routine for my operator.

There _must_ be some similar code running somewhere on the server side but I did not find that.


All that is to say:
- I would also be in favor of some public layout information
- I would also argue a builtin way for what I had to do above should be provided based on the public layout information
Max: can you make the operator form before the transpilation?  Seems like that'd save you both simulation cost, and solve your problem.  Transpilation shouldn't affect the matrix representation (including global phase, although I know some of the higher-level synthesis stuff might be missing that right now), except for re-mapping some of the qubits, and that can be fixed on the `Operator` by applying permutations to the rows and columns for input and final layouts.

*edit*: I misunderstood what was going on a little bit - I was thinking needing matrix representations of the circuit and the operator, rather than the operator still being in a circuit or `SparsePauliOp` representation.
@mrossinek fwiw, this kind of thing is the intent of the `Operator.from_circuit` constructor method: https://qiskit.org/documentation/stubs/qiskit.quantum_info.Operator.from_circuit.html#qiskit.quantum_info.Operator.from_circuit which already factors in the initial layout and final, layout if it's present. Although, it currently does not factor in ancilla expansion reversal that you described, which is something is something @alexanderivrii brought up recently as well. ~We should open another issue to track that.~ Done in: #9476 

But, yeah I think we all agree that the point here is that the `_layout` attribute has been semi-public for a long time because it's the only way users have to reason about the permutation caused by compilation. So we should promote it to be an actual public documented interface
I'm also in favor of using a `.layout` attribute for the reasons @jakelishman outlined above along with two other reasons, for QPY `metadata`  gets serialized by default by passing it to the default stdlib json serializer and deserializer. The interface we've defined for that is if users have anything outside what stdlib json can handle they should provide their own `JSONEncoder` and `JSONDecoder` subclasses to the qpy `dump` and `load` functions to be able to handle custom types. If we put the layout information in `metadata` we'd have to update the default serializers in qpy to be able to handle `Layout`  and `Qubit` objects, but that would result in breaking any users that were using our interface as described, because if they expanded the `metadata` with their own custom types pre-transpilation then what previously worked for serializing a post-transpiled circuit would no longer work because their custom encoder and decoder would not handle the layout anymore unless they subclassed our custom encoder.

The other reason is having a separate attribute will let us clearly document the attribute. We can write a detailed description of the what the layout (as at this point it's really initial layout, input qubit mapping, and final layout) is and examples on how to use it. Having a place we can point people to to explain all of this I think will be quite valuable because explaining layout and routing as permutations can sometimes be confusing.
See my comment from May of last year that touches on the same issue:

https://github.com/Qiskit/qiskit-ibm-runtime/issues/338

Resolving patch:
diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py
--- a/qiskit/circuit/quantumcircuit.py
+++ b/qiskit/circuit/quantumcircuit.py
@@ -73,6 +73,7 @@
 
 if typing.TYPE_CHECKING:
     import qiskit  # pylint: disable=cyclic-import
+    from qiskit.transpiler.layout import TranspileLayout  # pylint: disable=cyclic-import
 
 BitLocations = namedtuple("BitLocations", ("index", "registers"))
 
@@ -355,6 +356,23 @@ def from_instructions(
             circuit._append(instruction)
         return circuit
 
+    @property
+    def layout(self) -> Optional[TranspileLayout]:
+        r"""Return any associated layout information anout the circuit
+
+        This attribute contains an optional :class:`~.TranspileLayout`
+        object. This is typically set on the output from :func:`~.transpile`
+        or :meth:`.PassManager.run` to retain information about the
+        permutations caused on the input circuit by transpilation.
+
+        There are two types of permutations caused by the :func:`~.transpile`
+        function, an initial layout which permutes the qubits based on the
+        selected physical qubits on the :class:`~.Target`, and a final layout
+        which is an output permutation caused by :class:`~.SwapGate`\s
+        inserted during routing.
+        """
+        return self._layout
+
     @property
     def data(self) -> QuantumCircuitData:
         """Return the circuit data (instructions and context).
diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py
--- a/qiskit/transpiler/__init__.py
+++ b/qiskit/transpiler/__init__.py
@@ -1210,6 +1210,7 @@
 
    Layout
    CouplingMap
+   TranspileLayout
 
 Scheduling
 ----------
@@ -1256,7 +1257,7 @@
 from .fencedobjs import FencedDAGCircuit, FencedPropertySet
 from .basepasses import AnalysisPass, TransformationPass
 from .coupling import CouplingMap
-from .layout import Layout
+from .layout import Layout, TranspileLayout
 from .instruction_durations import InstructionDurations
 from .target import Target
 from .target import InstructionProperties
diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py
--- a/qiskit/transpiler/layout.py
+++ b/qiskit/transpiler/layout.py
@@ -370,7 +370,35 @@ def from_qubit_list(qubit_list, *qregs):
 
 @dataclass
 class TranspileLayout:
-    """Layout attributes from output circuit from transpiler."""
+    r"""Layout attributes from output circuit from transpiler.
+
+    The transpiler in general is unitary-perserving up to permutations caused
+    by setting and applying initial layout during the :ref:`layout_stage`
+    and :class:`~.SwapGate` insertion during the :ref:`routing_stage`. To
+    provide an interface to reason about these permutations caused by
+    the :mod:`~qiskit.transpiler`.
+
+    There are three attributes associated with the class:
+
+      * :attr:`initial_layout` - This attribute is used to model the
+        permutation caused by the :ref:`layout_stage` it contains a
+        :class:`~.Layout` object that maps the input :class:`~.QuantumCircuit`\s
+        :class:`~.Qubit` objects to the position in the output
+        :class:`.QuantumCircuit.qubits` list.
+      * :attr:`input_qubit_mapping` - This attribute is used to retain
+        input ordering of the original :class:`~.QuantumCircuit` object. It
+        maps the virtual :class:`~.Qubit` object from the original circuit
+        (and :attr:`initial_layout`) to its corresponding position in
+        :attr:`.QuantumCircuit.qubits` in the original circuit. This
+        is needed when computing the permutation of the :class:`Operator` of
+        the circuit (and used by :meth:`.Operator.from_circuit`).
+      * :attr:`final_layout` - This is a :class:`~.Layout` object used to
+        model the output permutation caused ny any :class:`~.SwapGate`\s
+        inserted into the :class:~.QuantumCircuit` during the
+        :ref:`routing_stage`. It maps the output circuit's qubits from
+        :class:`.QuantumCircuit.qubits` to the final position after
+        routing.
+    """
 
     initial_layout: Layout
     input_qubit_mapping: Dict[Qubit, int]


Test patch: