diff --git a/qiskit/passmanager/passrunner.py b/qiskit/passmanager/passrunner.py index 9ce983ce6591..16126b02fe18 100644 --- a/qiskit/passmanager/passrunner.py +++ b/qiskit/passmanager/passrunner.py @@ -88,11 +88,14 @@ def _to_passmanager_ir(self, in_program): pass @abstractmethod - def _to_target(self, passmanager_ir): + def _to_target(self, passmanager_ir, in_program): """Convert pass manager IR into output program. Args: passmanager_ir: Pass manager IR after optimization. + in_program: The input program, this can be used if you need + any metadata about the original input for the output. It + should not be mutated. Returns: Output program. @@ -229,7 +232,6 @@ def run( self.metadata = metadata passmanager_ir = self._to_passmanager_ir(in_program) - del in_program for controller in self.working_list: passmanager_ir = self._run_pass_generic( @@ -237,7 +239,8 @@ def run( passmanager_ir=passmanager_ir, options=self.passmanager_options, ) - out_program = self._to_target(passmanager_ir) + out_program = self._to_target(passmanager_ir, in_program) + del in_program if not isinstance(out_program, self.OUT_PROGRAM_TYPE): raise TypeError( diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 5824d11de1a9..d2fd4db57f40 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -149,7 +149,9 @@ ========== Version 10 adds support for symengine-native serialization for objects of type -:class:`~.ParameterExpression` as well as symbolic expressions in Pulse schedule blocks. +:class:`~.ParameterExpression` as well as symbolic expressions in Pulse schedule blocks. Version +10 also adds support for new fields in the :class:`~.TranspileLayout` class added in the Qiskit +0.45.0 release. The symbolic_encoding field is added to the file header, and a new encoding type char is introduced, mapped to each symbolic library as follows: ``p`` refers to sympy @@ -171,6 +173,26 @@ char symbolic_encoding; } +LAYOUT +------ + +The ``LAYOUT`` struct is updated to have an additional ``input_qubit_count`` field. +With version 10 the ``LAYOUT`` struct is now: + +.. code-block:: c + + struct { + char exists; + int32_t initial_layout_size; + int32_t input_mapping_size; + int32_t final_layout_size; + uint32_t extra_registers; + int32_t input_qubit_count; + } + +The rest of the layout data after the ``LAYOUT`` struct is represented as in previous versions. If +``input qubit_count`` is < 0 that indicates that both ``_input_qubit_count`` +and ``_output_qubit_list`` in the :class:`~.TranspileLayout` object are ``None``. .. _qpy_version_9: diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 47ca333fc621..2e099125363e 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -831,7 +831,7 @@ def _write_registers(file_obj, in_circ_regs, full_bits): def _write_layout(file_obj, circuit): if circuit.layout is None: # Write a null header if there is no layout present - file_obj.write(struct.pack(formats.LAYOUT_PACK, False, -1, -1, -1, 0)) + file_obj.write(struct.pack(formats.LAYOUT_V2_PACK, False, -1, -1, -1, 0, 0)) return initial_size = -1 input_qubit_mapping = {} @@ -874,14 +874,18 @@ def _write_layout(file_obj, circuit): virtual_bit = final_layout_physical[i] final_layout_array.append(circuit.find_bit(virtual_bit).index) + input_qubit_count = circuit._layout._input_qubit_count + if input_qubit_count is None: + input_qubit_count = -1 file_obj.write( struct.pack( - formats.LAYOUT_PACK, + formats.LAYOUT_V2_PACK, True, initial_size, input_qubit_size, final_layout_size, len(extra_registers), + input_qubit_count, ) ) _write_registers( @@ -910,6 +914,10 @@ def _read_layout(file_obj, circuit): ) if not header.exists: return + _read_common_layout(file_obj, header, circuit) + + +def _read_common_layout(file_obj, header, circuit): registers = { name: QuantumRegister(len(v[1]), name) for name, v in _read_registers_v4(file_obj, header.extra_registers)["q"].items() @@ -958,6 +966,18 @@ def _read_layout(file_obj, circuit): circuit._layout = TranspileLayout(initial_layout, input_qubit_mapping, final_layout) +def _read_layout_v2(file_obj, circuit): + header = formats.LAYOUT_V2._make( + struct.unpack(formats.LAYOUT_V2_PACK, file_obj.read(formats.LAYOUT_V2_SIZE)) + ) + if not header.exists: + return + _read_common_layout(file_obj, header, circuit) + if header.input_qubit_count >= 0: + circuit._layout._input_qubit_count = header.input_qubit_count + circuit._layout._output_qubit_list = circuit.qubits + + def write_circuit(file_obj, circuit, metadata_serializer=None, use_symengine=False): """Write a single QuantumCircuit object in the file like object. @@ -1161,5 +1181,8 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa UserWarning, ) if version >= 8: - _read_layout(file_obj, circ) + if version >= 10: + _read_layout_v2(file_obj, circ) + else: + _read_layout(file_obj, circ) return circ diff --git a/qiskit/qpy/formats.py b/qiskit/qpy/formats.py index d3da8c5bed7e..7bc1bc9410e5 100644 --- a/qiskit/qpy/formats.py +++ b/qiskit/qpy/formats.py @@ -278,6 +278,21 @@ MAP_ITEM_PACK = "!H1cH" MAP_ITEM_SIZE = struct.calcsize(MAP_ITEM_PACK) +LAYOUT_V2 = namedtuple( + "LAYOUT", + [ + "exists", + "initial_layout_size", + "input_mapping_size", + "final_layout_size", + "extra_registers", + "input_qubit_count", + ], +) +LAYOUT_V2_PACK = "!?iiiIi" +LAYOUT_V2_SIZE = struct.calcsize(LAYOUT_V2_PACK) + + LAYOUT = namedtuple( "LAYOUT", ["exists", "initial_layout_size", "input_mapping_size", "final_layout_size", "extra_registers"], diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index 46b10b6510da..646bc836f130 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -93,6 +93,8 @@ def __call__(self, circuit, property_set=None): initial_layout=self.property_set["layout"], input_qubit_mapping=self.property_set["original_qubit_indices"], final_layout=self.property_set["final_layout"], + _input_qubit_count=len(circuit.qubits), + _output_qubit_list=result_circuit.qubits, ) if self.property_set["clbit_write_latency"] is not None: result_circuit._clbit_write_latency = self.property_set["clbit_write_latency"] diff --git a/qiskit/transpiler/layout.py b/qiskit/transpiler/layout.py index 0e318e6a8db1..b9ef2156a551 100644 --- a/qiskit/transpiler/layout.py +++ b/qiskit/transpiler/layout.py @@ -18,6 +18,7 @@ Physical (qu)bits are integers. """ from __future__ import annotations +from typing import List from dataclasses import dataclass from qiskit.circuit.quantumregister import Qubit, QuantumRegister @@ -375,9 +376,77 @@ class TranspileLayout: 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`. + the :mod:`~qiskit.transpiler`. In general the normal interface to access + and reason about the layout transformations made by the transpiler is to + use the helper methods defined on this class. - There are three attributes associated with the class: + For example, looking at the initial layout, the transpiler can potentially + remap the order of the qubits in your circuit as it fits the circuit to + the target backend. If the input circuit was: + + .. plot: + :include-source: + + from qiskit.circuit import QuantumCircuit, QuantumRegister + + qr = QuantumRegister(3, name="MyReg") + qc = QuantumCircuit(qr) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.draw("mpl") + + Then during the layout stage the transpiler reorders the qubits to be: + + .. plot: + :include-source: + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(3) + qc.h(2) + qc.cx(2, 1) + qc.cx(2, 0) + qc.draw("mpl") + + then the output of the :meth:`.initial_virtual_layout` would be + equivalent to:: + + Layout({ + qr[0]: 2, + qr[1]: 1, + qr[2]: 0, + }) + + (it is also this attribute in the :meth:`.QuantumCircuit.draw` and + :func:`.circuit_drawer` which is used to display the mapping of qubits to + positions in circuit visualizations post-transpilation) + + Building on this above example for final layout, if the transpiler needed to + insert swap gates during routing so the output circuit became: + + .. plot: + :include-source: + + from qiskit import QuantumCircuit + + qc = QuantumCircuit(3) + qc.h(2) + qc.cx(2, 1) + qc.swap(0, 1) + qc.cx(2, 1) + qc.draw("mpl") + + then the output of the :meth:`routing_permutation` method would be:: + + [1, 0, 2] + + which maps the qubits at each position to their final position after any swap + insertions caused by routing. + + There are three public attributes associated with the class, however these + are mostly provided for backwards compatibility and represent the internal + state from the transpiler. They are defined as: * :attr:`initial_layout` - This attribute is used to model the permutation caused by the :ref:`layout_stage` it contains a @@ -393,12 +462,196 @@ class TranspileLayout: 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 + 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. + :class:`.QuantumCircuit.qubits` in the output circuit to the final + position after routing. It is **not** a mapping from the original + input circuit's position to the final position at the end of the + transpiled circuit. If you need this you can use the + :meth:`.final_index_layout` to generate this. If this is set to ``None`` + this indicates that routing was not run and it can be considered + equivalent to a trivial layout with the qubits from the output circuit's + :attr:`~.QuantumCircuit.qubits` list. """ initial_layout: Layout input_qubit_mapping: dict[Qubit, int] final_layout: Layout | None = None + _input_qubit_count: int | None = None + _output_qubit_list: List[Qubit] | None = None + + def initial_virtual_layout(self, filter_ancillas: bool = False) -> Layout: + """Return a :class:`.Layout` object for the initial layout. + + This returns a mapping of virtual :class:`~.Qubit` objects in the input + circuit to the physical qubit selected during layout. This is analogous + to the :attr:`.initial_layout` attribute. + + Args: + filter_ancillas: If set to ``True`` only qubits in the input circuit + will be in the returned layout. Any ancilla qubits added to the + output circuit will be filtered from the returned object. + Returns: + A layout object mapping the input circuit's :class:`~.Qubit` + objects to the selected physical qubits. + """ + if not filter_ancillas: + return self.initial_layout + return Layout( + { + k: v + for k, v in self.initial_layout.get_virtual_bits().items() + if self.input_qubit_mapping[k] < self._input_qubit_count + } + ) + + def initial_index_layout(self, filter_ancillas: bool = False) -> List[int]: + """Generate an initial layout as an array of integers + + Args: + filter_ancillas: If set to ``True`` any ancilla qubits added + to the transpiler will not be included in the output. + + Return: + A layout array that maps a position in the array to its new position in the output + circuit. + """ + + virtual_map = self.initial_layout.get_virtual_bits() + if filter_ancillas: + output = [None] * self._input_qubit_count + else: + output = [None] * len(virtual_map) + for index, (virt, phys) in enumerate(virtual_map.items()): + if filter_ancillas and index >= self._input_qubit_count: + break + pos = self.input_qubit_mapping[virt] + output[pos] = phys + return output + + def routing_permutation(self) -> List[int]: + """Generate a final layout as an array of integers + + If there is no :attr:`.final_layout` attribute present then that indicates + there was no output permutation caused by routing or other transpiler + transforms. In this case the function will return a list of ``[0, 1, 2, .., n]`` + to indicate this + + Returns: + A layout array that maps a position in the array to its new position in the output + circuit + """ + if self.final_layout is None: + return list(range(len(self._output_qubit_list))) + virtual_map = self.final_layout.get_virtual_bits() + return [virtual_map[virt] for virt in self._output_qubit_list] + + def final_index_layout(self, filter_ancillas: bool = True) -> List[int]: + """Generate the final layout as an array of integers + + This method will generate an array of final positions for each qubit in the output circuit. + For example, if you had an input circuit like:: + + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + + and the output from the transpiler was:: + + tqc = QuantumCircuit(3) + qc.h(2) + qc.cx(2, 1) + qc.swap(0, 1) + qc.cx(2, 1) + + then the return from this function would be a list of:: + + [2, 0, 1] + + because qubit 0 in the original circuit's final state is on qubit 3 in the output circuit, + qubit 1 in the original circuit's final state is on qubit 0, and qubit 2's final state is + on qubit. The output list length will be as wide as the input circuit's number of qubits, + as the output list from this method is for tracking the permutation of qubits in the + original circuit caused by the transpiler. + + Args: + filter_ancillas: If set to ``False`` any ancillas allocated in the output circuit will be + included in the layout. + + Returns: + A list of final positions for each input circuit qubit + """ + if self._input_qubit_count is None: + # TODO: After there is a way to differentiate the ancilla qubits added by the transpiler + # don't use the ancilla name anymore.See #10817 for discussion on this. + num_source_qubits = len( + [ + x + for x in self.input_qubit_mapping + if getattr(x, "_register", "").startswith("ancilla") + ] + ) + else: + num_source_qubits = self._input_qubit_count + if self._output_qubit_list is None: + circuit_qubits = list(self.final_layout.get_virtual_bits()) + else: + circuit_qubits = self._output_qubit_list + + pos_to_virt = {v: k for k, v in self.input_qubit_mapping.items()} + qubit_indices = [] + if filter_ancillas: + num_qubits = num_source_qubits + else: + num_qubits = len(self._output_qubit_list) + for index in range(num_qubits): + qubit_idx = self.initial_layout[pos_to_virt[index]] + if self.final_layout is not None: + qubit_idx = self.final_layout[circuit_qubits[qubit_idx]] + qubit_indices.append(qubit_idx) + return qubit_indices + + def final_virtual_layout(self, filter_ancillas: bool = True) -> Layout: + """Generate the final layout as a :class:`.Layout` object + + This method will generate an array of final positions for each qubit in the output circuit. + For example, if you had an input circuit like:: + + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + + and the output from the transpiler was:: + + tqc = QuantumCircuit(3) + qc.h(2) + qc.cx(2, 1) + qc.swap(0, 1) + qc.cx(2, 1) + + then the return from this function would be a layout object:: + + Layout({ + qc.qubits[0]: 2, + qc.qubits[1]: 0, + qc.qubits[2]: 1, + }) + + because qubit 0 in the original circuit's final state is on qubit 3 in the output circuit, + qubit 1 in the original circuit's final state is on qubit 0, and qubit 2's final state is + on qubit. The output list length will be as wide as the input circuit's number of qubits, + as the output list from this method is for tracking the permutation of qubits in the + original circuit caused by the transpiler. + + Args: + filter_ancillas: If set to ``False`` any ancillas allocated in the output circuit will be + included in the layout. + + Returns: + A layout object mapping to the final positions for each qubit + """ + res = self.final_index_layout(filter_ancillas=filter_ancillas) + pos_to_virt = {v: k for k, v in self.input_qubit_mapping.items()} + return Layout({pos_to_virt[index]: phys for index, phys in enumerate(res)}) diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index aa574414667a..7962551e02d9 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -100,7 +100,7 @@ def _to_passmanager_ir(self, in_program: QuantumCircuit) -> DAGCircuit: raise TranspilerError(f"Input {in_program.__class__} is not QuantumCircuit.") return circuit_to_dag(in_program) - def _to_target(self, passmanager_ir: DAGCircuit) -> QuantumCircuit: + def _to_target(self, passmanager_ir: DAGCircuit, in_program: QuantumCircuit) -> QuantumCircuit: if not isinstance(passmanager_ir, DAGCircuit): raise TranspilerError(f"Input {passmanager_ir.__class__} is not DAGCircuit.") @@ -112,6 +112,8 @@ def _to_target(self, passmanager_ir: DAGCircuit) -> QuantumCircuit: initial_layout=self.property_set["layout"], input_qubit_mapping=self.property_set["original_qubit_indices"], final_layout=self.property_set["final_layout"], + _input_qubit_count=len(in_program.qubits), + _output_qubit_list=circuit.qubits, ) circuit._clbit_write_latency = self.property_set["clbit_write_latency"] circuit._conditional_latency = self.property_set["conditional_latency"] diff --git a/releasenotes/notes/transpile-layout-improvements-118dd902d93e5b96.yaml b/releasenotes/notes/transpile-layout-improvements-118dd902d93e5b96.yaml new file mode 100644 index 000000000000..ed6761ccd0d2 --- /dev/null +++ b/releasenotes/notes/transpile-layout-improvements-118dd902d93e5b96.yaml @@ -0,0 +1,59 @@ +--- +features: + - | + Added new methods to :class:`.TranspileLayout`, :meth:`~.TranspileLayout.initial_index_layout` + and :meth:`~.TranspileLayout.routing_permutation`, which are used to generate a list view of + the :attr:`.TranspileLayout.initial_layout` and + :attr:`.TranspileLayout.final_layout` attributes respectively. For example, + if the :attr:`~.TranspileLayout.final_layout` attribute was:: + + Layout({ + qr[0]: 2, + qr[1]: 3, + qr[2]: 0, + qr[3]: 1, + }) + + then :meth:`~.TranspileLayout.routing_permutation` will return:: + + [2, 3, 0, 1] + - | + Added a new method to :class:`.TranspileLayout`, :meth:`~.TranspileLayout.initial_virtual_layout`, + which is equivalent to the :attr:`.TranspileLayout.initial_layout` attribute but gives the option + to filter ancilla qubits that were added to the circuit. By default the :attr:`.TranspileLayout.initial_layout` + will typically include any ancillas added by the transpiler. + - | + Added a new methods, :meth:`~.TranspileLayout.final_index_layout` and :meth:`~.TranspileLayout.final_virtual_layout` + to the :class:`~.TranspileLayout` class. These methods are used to return a final layout + (the mapping of input circuit qubits to the final position in the output). This is distinct + from the :attr:`~.TranspileLayout.final_layout` attribute which is the permutation caused by + routing as a :class:`.Layout` object. The :meth:`~.TranspileLayout.final_index_layout` method + returns a list to showthe output position for each qubit in the input circuit to the transpiler. + For example, with an original circuit:: + + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + + and the output from the transpiler was:: + + tqc = QuantumCircuit(3) + tqc.h(2) + tqc.cx(2, 1) + tqc.swap(0, 1) + tqc.cx(2, 1) + + then the output from :meth:`~.TranspileLayout.final_index_layout` would return a + list of:: + + [2, 0, 1] + + The :meth:`~.TranspileLayout.final_virtual_layout` returns this as a :class:`.Layout` object, + so the return from the above example would be:: + + Layout({ + qc.qubits[0]: 2, + qc.qubits[1]: 0, + qc.qubits[2]: 1, + }) diff --git a/test/python/transpiler/test_transpile_layout.py b/test/python/transpiler/test_transpile_layout.py new file mode 100644 index 000000000000..b966a0af6b5e --- /dev/null +++ b/test/python/transpiler/test_transpile_layout.py @@ -0,0 +1,296 @@ +# 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. + +# pylint: disable=missing-function-docstring + +"""Tests the layout object""" + +from qiskit.circuit import QuantumCircuit, QuantumRegister +from qiskit.transpiler.layout import Layout, TranspileLayout +from qiskit.transpiler.coupling import CouplingMap +from qiskit.compiler import transpile +from qiskit.test import QiskitTestCase + + +class TranspileLayoutTest(QiskitTestCase): + """Test the methods in the TranspileLayout object.""" + + def test_final_index_layout_full_path(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(3, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + res = tqc.layout.final_index_layout() + self.assertEqual(res, [2, 0, 1]) + + def test_final_virtual_layout_full_path(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(3, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + res = tqc.layout.final_virtual_layout() + self.assertEqual(res, Layout({qc.qubits[0]: 2, qc.qubits[1]: 0, qc.qubits[2]: 1})) + + def test_final_index_layout_full_path_with_ancilla(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(10, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[9, 4, 0], seed_transpiler=42) + # tqc: + # q_2 -> 0 ──X───────────────────────────────────────────────── + # │ + # ancilla_0 -> 1 ──X───X───────────────────────────────────────────── + # │ + # ancilla_1 -> 2 ──────X──X────────────────────────────────────────── + # │ ┌───┐ ┌───┐ + # ancilla_2 -> 3 ─────────X─┤ H ├────────────────────────────■──┤ H ├ + # ┌───┐ └───┘ ┌───┐ ┌───┐┌─┴─┐├───┤ + # q_1 -> 4 ┤ H ├─────────────────────■──┤ H ├─X─┤ H ├┤ X ├┤ H ├ + # └───┘ ┌───┐┌─┴─┐├───┤ │ └───┘└───┘└───┘ + # ancilla_3 -> 5 ─────────────────X─┤ H ├┤ X ├┤ H ├─X──────────────── + # │ └───┘└───┘└───┘ + # ancilla_4 -> 6 ─────────────X───X────────────────────────────────── + # │ + # ancilla_5 -> 7 ─────────X───X────────────────────────────────────── + # │ + # ancilla_6 -> 8 ──────X──X────────────────────────────────────────── + # ┌───┐ │ + # q_0 -> 9 ┤ H ├─X───────────────────────────────────────────── + # └───┘ + res = tqc.layout.final_index_layout() + self.assertEqual(res, [4, 5, 3]) + + def test_final_index_layout_full_path_with_ancilla_no_filter(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(10, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[9, 4, 0], seed_transpiler=42) + # tqc: + # q_2 -> 0 ──X───────────────────────────────────────────────── + # │ + # ancilla_0 -> 1 ──X───X───────────────────────────────────────────── + # │ + # ancilla_1 -> 2 ──────X──X────────────────────────────────────────── + # │ ┌───┐ ┌───┐ + # ancilla_2 -> 3 ─────────X─┤ H ├────────────────────────────■──┤ H ├ + # ┌───┐ └───┘ ┌───┐ ┌───┐┌─┴─┐├───┤ + # q_1 -> 4 ┤ H ├─────────────────────■──┤ H ├─X─┤ H ├┤ X ├┤ H ├ + # └───┘ ┌───┐┌─┴─┐├───┤ │ └───┘└───┘└───┘ + # ancilla_3 -> 5 ─────────────────X─┤ H ├┤ X ├┤ H ├─X──────────────── + # │ └───┘└───┘└───┘ + # ancilla_4 -> 6 ─────────────X───X────────────────────────────────── + # │ + # ancilla_5 -> 7 ─────────X───X────────────────────────────────────── + # │ + # ancilla_6 -> 8 ──────X──X────────────────────────────────────────── + # ┌───┐ │ + # q_0 -> 9 ┤ H ├─X───────────────────────────────────────────── + # └───┘ + res = tqc.layout.final_index_layout(filter_ancillas=False) + self.assertEqual(res, [4, 5, 3, 0, 1, 2, 6, 7, 8, 9]) + + def test_final_virtual_layout_full_path_with_ancilla(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(10, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[9, 4, 0], seed_transpiler=42) + # tqc: + # q_2 -> 0 ──X───────────────────────────────────────────────── + # │ + # ancilla_0 -> 1 ──X───X───────────────────────────────────────────── + # │ + # ancilla_1 -> 2 ──────X──X────────────────────────────────────────── + # │ ┌───┐ ┌───┐ + # ancilla_2 -> 3 ─────────X─┤ H ├────────────────────────────■──┤ H ├ + # ┌───┐ └───┘ ┌───┐ ┌───┐┌─┴─┐├───┤ + # q_1 -> 4 ┤ H ├─────────────────────■──┤ H ├─X─┤ H ├┤ X ├┤ H ├ + # └───┘ ┌───┐┌─┴─┐├───┤ │ └───┘└───┘└───┘ + # ancilla_3 -> 5 ─────────────────X─┤ H ├┤ X ├┤ H ├─X──────────────── + # │ └───┘└───┘└───┘ + # ancilla_4 -> 6 ─────────────X───X────────────────────────────────── + # │ + # ancilla_5 -> 7 ─────────X───X────────────────────────────────────── + # │ + # ancilla_6 -> 8 ──────X──X────────────────────────────────────────── + # ┌───┐ │ + # q_0 -> 9 ┤ H ├─X───────────────────────────────────────────── + # └───┘ + res = tqc.layout.final_virtual_layout() + self.assertEqual(res, Layout({qc.qubits[0]: 4, qc.qubits[1]: 5, qc.qubits[2]: 3})) + + def test_final_virtual_layout_full_path_with_ancilla_no_filter(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(10, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[9, 4, 0], seed_transpiler=42) + # tqc: + # q_2 -> 0 ──X───────────────────────────────────────────────── + # │ + # ancilla_0 -> 1 ──X───X───────────────────────────────────────────── + # │ + # ancilla_1 -> 2 ──────X──X────────────────────────────────────────── + # │ ┌───┐ ┌───┐ + # ancilla_2 -> 3 ─────────X─┤ H ├────────────────────────────■──┤ H ├ + # ┌───┐ └───┘ ┌───┐ ┌───┐┌─┴─┐├───┤ + # q_1 -> 4 ┤ H ├─────────────────────■──┤ H ├─X─┤ H ├┤ X ├┤ H ├ + # └───┘ ┌───┐┌─┴─┐├───┤ │ └───┘└───┘└───┘ + # ancilla_3 -> 5 ─────────────────X─┤ H ├┤ X ├┤ H ├─X──────────────── + # │ └───┘└───┘└───┘ + # ancilla_4 -> 6 ─────────────X───X────────────────────────────────── + # │ + # ancilla_5 -> 7 ─────────X───X────────────────────────────────────── + # │ + # ancilla_6 -> 8 ──────X──X────────────────────────────────────────── + # ┌───┐ │ + # q_0 -> 9 ┤ H ├─X───────────────────────────────────────────── + # └───┘ + res = tqc.layout.final_virtual_layout(filter_ancillas=False) + pos_to_virt = {v: k for k, v in tqc.layout.input_qubit_mapping.items()} + expected = Layout( + { + pos_to_virt[0]: 4, + pos_to_virt[1]: 5, + pos_to_virt[2]: 3, + pos_to_virt[3]: 0, + pos_to_virt[4]: 1, + pos_to_virt[5]: 2, + pos_to_virt[6]: 6, + pos_to_virt[7]: 7, + pos_to_virt[8]: 8, + pos_to_virt[9]: 9, + } + ) + self.assertEqual(res, expected) + + def test_routing_permutation(self): + qr = QuantumRegister(5) + final_layout = Layout( + { + qr[0]: 2, + qr[1]: 4, + qr[2]: 1, + qr[3]: 0, + qr[4]: 3, + } + ) + layout_obj = TranspileLayout( + initial_layout=Layout.generate_trivial_layout(qr), + input_qubit_mapping={v: k for k, v in enumerate(qr)}, + final_layout=final_layout, + _input_qubit_count=5, + _output_qubit_list=list(qr), + ) + res = layout_obj.routing_permutation() + self.assertEqual(res, [2, 4, 1, 0, 3]) + + def test_routing_permutation_no_final_layout(self): + qr = QuantumRegister(5) + layout_obj = TranspileLayout( + initial_layout=Layout.generate_trivial_layout(qr), + input_qubit_mapping={v: k for k, v in enumerate(qr)}, + final_layout=None, + _input_qubit_count=5, + _output_qubit_list=list(qr), + ) + res = layout_obj.routing_permutation() + self.assertEqual(res, list(range(5))) + + def test_initial_index_layout(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(3, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + self.assertEqual(tqc.layout.initial_index_layout(), [2, 1, 0]) + + def test_initial_index_layout_with_ancillas(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(6, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + self.assertEqual(tqc.layout.initial_index_layout(), [2, 1, 0, 3, 4, 5]) + + def test_initial_index_layout_filter_ancillas(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(6, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[5, 2, 1], seed_transpiler=42) + self.assertEqual(tqc.layout.initial_index_layout(True), [5, 2, 1]) + + def test_initial_virtual_layout(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(3, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + self.assertEqual( + tqc.layout.initial_virtual_layout(), + Layout.from_qubit_list([qc.qubits[2], qc.qubits[1], qc.qubits[0]]), + ) + + def test_initial_virtual_layout_with_ancillas(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(6, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[2, 1, 0], seed_transpiler=42) + reverse_pos_map = {v: k for k, v in tqc.layout.input_qubit_mapping.items()} + self.assertEqual( + tqc.layout.initial_virtual_layout(), + Layout.from_qubit_list( + [ + reverse_pos_map[2], + reverse_pos_map[1], + reverse_pos_map[0], + reverse_pos_map[3], + reverse_pos_map[4], + reverse_pos_map[5], + ] + ), + ) + + def test_initial_virtual_layout_filter_ancillas(self): + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + cmap = CouplingMap.from_line(6, bidirectional=False) + tqc = transpile(qc, coupling_map=cmap, initial_layout=[5, 2, 1], seed_transpiler=42) + self.assertEqual( + tqc.layout.initial_virtual_layout(True), + Layout( + { + qc.qubits[0]: 5, + qc.qubits[1]: 2, + qc.qubits[2]: 1, + } + ), + )