Skip to content

Commit

Permalink
Rework interface to be more consistent
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mtreinish committed Sep 18, 2023
1 parent 8546928 commit 3f2542b
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 41 deletions.
127 changes: 102 additions & 25 deletions qiskit/transpiler/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,13 @@ 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`. 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:
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.
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:
Expand All @@ -406,7 +410,7 @@ class TranspileLayout:
qc.cx(2, 0)
qc.draw("mpl")
then the :attr:`initial_layout` for the :class:`.TranspileLayout` would be
then the output of the :meth:`.initial_virtual_layout` would be
equivalent to::
Layout({
Expand Down Expand Up @@ -434,19 +438,16 @@ class TranspileLayout:
qc.cx(2, 1)
qc.draw("mpl")
then the final layout of this circuit would be::
then the output of the :meth:`routing_permutation` method would be::
Layout({
qc.qubits[0]: 1,
qc.qubits[1]: 0,
qc.qubits[2]: 2,
})
[1, 0, 2]
which maps the qubits at the beginning of the circuit to their final position
after any swap insertions caused by routing. If ``final_layout`` is ``None``
this implies that no routing was performed and there is no output permutation.
which maps the qubits at each position to their final position after any swap
insertions caused by routing.
There are three attributes associated with the class:
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
Expand All @@ -468,11 +469,10 @@ class TranspileLayout:
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:`.full_layout` to generate this.
Additionally, this class provides several methods to return alternative views of
the layout generated by the transpiler to help working with the permutation the
transpiler might cause.
:meth:`.full_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
Expand All @@ -481,7 +481,32 @@ class TranspileLayout:
_input_qubit_count: int | None = None
_output_qubit_list: List[Qubit] | None = None

def initial_layout_list(self, filter_ancillas: bool = False) -> List[int]:
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 analgous
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 a
Args:
Expand All @@ -505,7 +530,7 @@ def initial_layout_list(self, filter_ancillas: bool = False) -> List[int]:
output[pos] = phys
return output

def final_layout_list(self) -> List[int]:
def routing_permutation(self) -> List[int]:
"""Generate a final layout as a an array of integers
If there is no :attr:`.final_layout` attribute present then that indicates
Expand All @@ -522,8 +547,8 @@ def final_layout_list(self) -> List[int]:
virtual_map = self.final_layout.get_virtual_bits()
return [virtual_map[virt] for virt in self._output_qubit_list]

def full_layout(self) -> List[int]:
"""Generate the full layout as a list of integers
def final_index_layout(self, filter_ancillas: bool = True) -> List[int]:
"""Generate the final layout as a list 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::
Expand Down Expand Up @@ -551,6 +576,10 @@ def full_layout(self) -> List[int]:
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
"""
Expand All @@ -573,13 +602,61 @@ def full_layout(self) -> List[int]:

pos_to_virt = {v: k for k, v in self.input_qubit_mapping.items()}
qubit_indices = []
for index in range(num_source_qubits):
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)})

def permute_sparse_pauli_op(self, operator: SparsePauliOp) -> SparsePauliOp:
"""Permute an operator based on a transpiled circuit's layout
Expand All @@ -591,5 +668,5 @@ def permute_sparse_pauli_op(self, operator: SparsePauliOp) -> SparsePauliOp:
A new sparse Pauli op which has been permuted according to the output of the transpiler
"""
identity = SparsePauliOp("I" * len(self._output_qubit_list))
qargs = self.full_layout()
qargs = self.final_index_layout()
return identity.compose(operator, qargs=qargs)
Loading

0 comments on commit 3f2542b

Please sign in to comment.