mirror of https://github.com/Qiskit/qiskit.git
Improve the ergonomics of the TranspileLayout class (#10835)
* Improve the ergonomics of the TranspileLayout class This commit updates the TranspileLayout class to make it easier to work with. The TranspileLayout class was orginally just an a container class that took the information returned from the transpiler needed to reverse the transpiler layout permutation (in the absence of ancillas) so that `QuantumCircuit.from_circuit()` could compare Operator instances across a `transpile()`. However, the internal data structures used by the transpiler are hard to work with, and don't map well to the normal reasoning around how the transpiler transforms the circuit. To improve the usability of the class this commit adds 4 new methods to the class: - initial_layout_list() and final_layout_list() which compute a list form of the initial_layout and final_layout respectively - full_layout() which generates a list that maps the input circuits qubit positions in the input circuit to the final position at the end of the circuit (which is what most people think of when they hear "final layout") - permute_sparse_pauli_op() which takes in a SparsePauliOp object and permutes it based on the full layout. This is especially useful when combined with the Estimator primitive To implement these functions a few extra pieces of information are needed to fully implement them. The first is we need to know the number of original circuit qubits. This is used to account for any ancillas that are added in the circuit, once we have the input circuit count we can use the original_qubit_indices attribute to discern which qubits in the initial layout are from the original circuit and which are ancillas. The second piece of information needed is the list of qubits in the output circuit. as this is needed to look up the position of the virtual qubit in the final_layout. These are both added as optional private attributes to the TranspileLayout class and there are some small changes to the pass manager and QPY to accomodate them. Similarly the change in the TranspileLayout class requires a new QPY version to include the missing details that were not being serialized in QPY and weren't representable in the previous payload format. Fixes #10826 Fixes #10818 * Handle None for the private TranspileLayout fields in QPY * Add test for full_layout with ancillas * Rework interface to be more consistent This commit reworks the names of the new methods added in this API to be more self consistent. It uses the format '*_index_layout' to replace the `*_layout_list` name. Similarly new methods `*_virtual_layout` are added for methods that return layout objects. * Apply suggestions from code review Co-authored-by: Jake Lishman <jake@binhbar.com> * Remove permute_sparse_pauli_op() This commit remove the TranspileLayout.permute_sparse_pauli_op() method. This was a bit out of place on the TranspileLayout class, and it will instead be replaced in a follow-up PR by a apply_layout() method on the SparePauliOp class that takes in a TranspileLayout object. * Update docs * Fix docs build post-rebase * Fix docs issues --------- Co-authored-by: Jake Lishman <jake@binhbar.com>
This commit is contained in:
parent
81e28dc2a6
commit
19854d193a
|
@ -88,11 +88,14 @@ class BasePassRunner(ABC):
|
|||
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 @@ class BasePassRunner(ABC):
|
|||
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 @@ class BasePassRunner(ABC):
|
|||
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(
|
||||
|
|
|
@ -149,7 +149,9 @@ Version 10
|
|||
==========
|
||||
|
||||
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 @@ The contents of FILE_HEADER after V10 are defined as a C struct as:
|
|||
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:
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -278,6 +278,21 @@ MAP_ITEM = namedtuple("MAP_ITEM", ["key_size", "type", "size"])
|
|||
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"],
|
||||
|
|
|
@ -93,6 +93,8 @@ class BasePass(GenericPass):
|
|||
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"]
|
||||
|
|
|
@ -18,6 +18,7 @@ Virtual (qu)bits are tuples, e.g. `(QuantumRegister(3, 'qr'), 2)` or simply `qr[
|
|||
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)})
|
||||
|
|
|
@ -100,7 +100,7 @@ class RunningPassManager(BasePassRunner):
|
|||
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 @@ class RunningPassManager(BasePassRunner):
|
|||
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"]
|
||||
|
|
|
@ -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,
|
||||
})
|
|
@ -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,
|
||||
}
|
||||
),
|
||||
)
|
Loading…
Reference in New Issue