Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] adding "final_permutation" attribute to pass manager's property set #12206

Closed
wants to merge 8 commits into from
32 changes: 29 additions & 3 deletions qiskit/transpiler/basepasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from qiskit.passmanager.compilation_status import PropertySet, RunState, PassManagerState

from .exceptions import TranspilerError
from .layout import TranspileLayout
from .layout import TranspileLayout, Layout


class MetaPass(abc.ABCMeta):
Expand Down Expand Up @@ -154,14 +154,40 @@ def __call__(
elif result is None:
result_circuit = circuit.copy()

if self.property_set["layout"]:
if self.property_set["layout"] is not None:
# compute final_layout from "final_permutation" attribute (the new way) and
# "final_layout" attribute (for backward-consistency).
if (
self.property_set["final_layout"] is None
and self.property_set["final_permutation"] is None
):
final_layout = None
elif self.property_set["final_layout"] is None:
final_layout = Layout(
dict(zip(result_circuit.qubits, self.property_set["final_permutation"]))
)
elif self.property_set["final_permutation"] is None:
final_layout = self.property_set["final_layout"]
else:
# pylint: disable=cyclic-import
from .passes.utils import _compose_permutations

virtual_map = self.property_set["final_layout"].get_virtual_bits()
final_layout_permutation = [virtual_map[virt] for virt in result_circuit.qubits]

composed_final_permutation = _compose_permutations(
self.property_set["final_permutation"], final_layout_permutation
)
final_layout = Layout(dict(zip(result_circuit.qubits, composed_final_permutation)))

result_circuit._layout = TranspileLayout(
initial_layout=self.property_set["layout"],
input_qubit_mapping=self.property_set["original_qubit_indices"],
final_layout=self.property_set["final_layout"],
final_layout=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"]
if self.property_set["conditional_latency"] is not None:
Expand Down
25 changes: 17 additions & 8 deletions qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"""Transform a circuit with virtual qubits into a circuit with physical qubits."""

from qiskit.circuit import QuantumRegister
from qiskit.dagcircuit import DAGCircuit
from qiskit.dagcircuit import DAGCircuit, DAGCircuitError
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.exceptions import TranspilerError
from qiskit.transpiler.layout import Layout
Expand Down Expand Up @@ -75,6 +75,15 @@ def run(self, dag):
for node in dag.topological_op_nodes():
qargs = [q[virtual_physical_map[qarg]] for qarg in node.qargs]
new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False)

phys_map = list(range(len(new_dag.qubits)))
for virt, phys in virtual_physical_map.items():
try:
index = dag.find_bit(virt).index
except DAGCircuitError:
index = phys
phys_map[index] = phys

else:
# First build a new layout object going from:
# old virtual -> old physical -> new virtual -> new physical
Expand All @@ -96,13 +105,13 @@ def run(self, dag):
qargs = [q[new_virtual_to_physical[qarg]] for qarg in node.qargs]
new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False)
self.property_set["layout"] = full_layout
if (final_layout := self.property_set["final_layout"]) is not None:
final_layout_mapping = {
new_dag.qubits[phys_map[dag.find_bit(old_virt).index]]: phys_map[old_phys]
for old_virt, old_phys in final_layout.get_virtual_bits().items()
}
out_layout = Layout(final_layout_mapping)
self.property_set["final_layout"] = out_layout

if (final_virtual_permutation := self.property_set["final_permutation"]) is not None:
final_physical_permutation = list(range(len(new_dag.qubits)))
for inp, out in enumerate(final_virtual_permutation):
final_physical_permutation[phys_map[inp]] = phys_map[out]
self.property_set["final_permutation"] = final_physical_permutation

new_dag._global_phase = dag._global_phase

return new_dag
10 changes: 10 additions & 0 deletions qiskit/transpiler/passes/layout/sabre_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,16 @@ def run(self, dag):
],
component.circuit_to_dag_dict,
)

# convert to final_permutation instead
final_layout = self.property_set["final_layout"]
final_permutation = [None] * len(mapped_dag.qubits)
for i, q in enumerate(mapped_dag.qubits):
pos = final_layout._v2p[q]
final_permutation[i] = pos
self.property_set["final_layout"] = None
self.property_set["final_permutation"] = final_permutation

disjoint_utils.combine_barriers(mapped_dag, retain_uuid=False)
return mapped_dag

Expand Down
13 changes: 8 additions & 5 deletions qiskit/transpiler/passes/routing/sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from qiskit.transpiler.passes.layout import disjoint_utils
from qiskit.dagcircuit import DAGCircuit
from qiskit.utils.parallel import CPU_COUNT
from qiskit.transpiler.passes.utils import _compose_permutations

from qiskit._accelerate.sabre import (
sabre_routing,
Expand Down Expand Up @@ -250,13 +251,15 @@ def run(self, dag):
)
sabre_stop = time.perf_counter()
logging.debug("Sabre swap algorithm execution complete in: %s", sabre_stop - sabre_start)
final_layout = Layout(dict(zip(dag.qubits, final_permutation)))
if self.property_set["final_layout"] is None:
self.property_set["final_layout"] = final_layout

if self.property_set["final_permutation"] is None:
self.property_set["final_permutation"] = final_permutation
else:
self.property_set["final_layout"] = final_layout.compose(
self.property_set["final_layout"], dag.qubits
# The current permutation is applied before the already existing one
self.property_set["final_permutation"] = _compose_permutations(
final_permutation, self.property_set["final_permutation"]
)

if self.fake_run:
return dag
return _apply_sabre_result(
Expand Down
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@
# Utility functions
from . import control_flow
from .block_to_matrix import _block_to_matrix
from .permutations import _invert_permutation, _compose_permutations
43 changes: 43 additions & 0 deletions qiskit/transpiler/passes/utils/permutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2024.
#
# 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.

r"""
Utilities to invert and compose permutations.

A permutation :math:`\sigma` is represented as a list of integers, with the integer
at position ``i`` corresponding to :math:`\sigma(i)`. As an example, ``[2, 4, 3, 0, 1]``
represents the permutation that maps ``0`` to ``2``, ``1`` to ``4``, ``2`` to ``3``,
``3`` to ``0`` and ``4`` to ``1``.

This notation is the same as used in routing passes. However, it's the inverse of the
notation used in ``PermutationGate``.
"""


def _invert_permutation(perm):
"""Finds inverse of a permutation."""
inverse_map = {inp: out for out, inp in enumerate(perm)}
return [inverse_map[inp] for inp in range(len(perm))]


def _compose_permutations(*perms):
"""Compose multiple permutations, with the permutations applied in the
order they appear in the list. For convenience, we allow some permutations
to be ``None`` which represents identity permutations.
"""
perms = [perm for perm in perms if perm is not None]
if not perms:
return None
out = range(len(perms[0]))
for perm in perms:
out = [perm[i] for i in out]
return out
29 changes: 27 additions & 2 deletions qiskit/transpiler/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from qiskit.passmanager.exceptions import PassManagerError
from .basepasses import BasePass
from .exceptions import TranspilerError
from .layout import TranspileLayout
from .layout import TranspileLayout, Layout

_CircuitsT = Union[List[QuantumCircuit], QuantumCircuit]

Expand Down Expand Up @@ -74,10 +74,35 @@ def _passmanager_backend(
out_program.name = out_name

if self.property_set["layout"] is not None:
# compute final_layout from "final_permutation" attribute (the new way) and
# "final_layout" attribute (for backward-consistency).
if (
self.property_set["final_layout"] is None
and self.property_set["final_permutation"] is None
):
final_layout = None
elif self.property_set["final_layout"] is None:
final_layout = Layout(
dict(zip(out_program.qubits, self.property_set["final_permutation"]))
)
elif self.property_set["final_permutation"] is None:
final_layout = self.property_set["final_layout"]
else:
# pylint: disable=cyclic-import
from .passes.utils import _compose_permutations

virtual_map = self.property_set["final_layout"].get_virtual_bits()
final_layout_permutation = [virtual_map[virt] for virt in out_program.qubits]

composed_final_permutation = _compose_permutations(
self.property_set["final_permutation"], final_layout_permutation
)
final_layout = Layout(dict(zip(out_program.qubits, composed_final_permutation)))

out_program._layout = TranspileLayout(
initial_layout=self.property_set["layout"],
input_qubit_mapping=self.property_set["original_qubit_indices"],
final_layout=self.property_set["final_layout"],
final_layout=final_layout,
_input_qubit_count=len(in_program.qubits),
_output_qubit_list=out_program.qubits,
)
Expand Down
8 changes: 6 additions & 2 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,9 @@ def _vf2_match_not_found(property_set):
)

def _swap_mapped(property_set):
return property_set["final_layout"] is None
return (
property_set["final_layout"] is None and property_set["final_permutation"] is None
)

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
Expand Down Expand Up @@ -877,7 +879,9 @@ def _choose_layout_condition(property_set):
return not property_set["layout"]

def _swap_mapped(property_set):
return property_set["final_layout"] is None
return (
property_set["final_layout"] is None and property_set["final_permutation"] is None
)

if pass_manager_config.target is None:
coupling_map = pass_manager_config.coupling_map
Expand Down
62 changes: 38 additions & 24 deletions test/python/transpiler/test_apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import unittest

from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister
from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister, Qubit
from qiskit.converters import circuit_to_dag
from qiskit.transpiler.layout import Layout
from qiskit.transpiler.passes import ApplyLayout, SetLayout
Expand Down Expand Up @@ -119,8 +119,39 @@ def test_circuit_with_swap_gate(self):

self.assertEqual(circuit_to_dag(expected), after)

def test_final_layout_is_updated(self):
"""Test that if vf2postlayout runs that we've updated the final layout."""
def test_final_permutation(self):
"""Test that final permutation is updated."""

q = QuantumRegister(5, "q")

# note this layout reverses the order of qubits
initial_layout = Layout(
{
Qubit(q, 0): 4,
Qubit(q, 1): 3,
Qubit(q, 2): 2,
Qubit(q, 3): 1,
Qubit(q, 4): 0,
}
)

qc = QuantumCircuit(5)
qc.cx(Qubit(q, 0), Qubit(q, 2))

out_pass = ApplyLayout()
out_pass.property_set["layout"] = initial_layout
out_pass.property_set["final_permutation"] = [1, 2, 3, 4, 0]

out_pass(qc)

# The final permutation before applying the pass was 0->1, 1->2, 2->3, 3->4, 4->0.
# With the given layout (0->4, 1->3, 2->2, 3->1, 4->0), the final permutation
# should become 4->3, 3->2, 2->1, 1->0, 0->4.
new_final_permutation = out_pass.property_set["final_permutation"]
self.assertEqual(new_final_permutation, [4, 0, 1, 2, 3])

def test_final_permutation_with_postlayout(self):
"""Test that if vf2postlayout runs that we've updated the final permutation."""
qubits = 3
qc = QuantumCircuit(qubits)
for i in range(5):
Expand All @@ -134,15 +165,8 @@ def test_final_layout_is_updated(self):
out_pass.property_set["original_qubit_indices"] = (
first_layout_circ.layout.input_qubit_mapping
)
out_pass.property_set["final_layout"] = Layout(
{
first_layout_circ.qubits[0]: 0,
first_layout_circ.qubits[1]: 3,
first_layout_circ.qubits[2]: 2,
first_layout_circ.qubits[3]: 4,
first_layout_circ.qubits[4]: 1,
}
)
out_pass.property_set["final_permutation"] = [0, 3, 2, 4, 1]

# Set a post layout like vf2postlayout would:
out_pass.property_set["post_layout"] = Layout(
{
Expand All @@ -153,19 +177,9 @@ def test_final_layout_is_updated(self):
first_layout_circ.qubits[4]: 3,
}
)

out_pass(first_layout_circ)
self.assertEqual(
out_pass.property_set["final_layout"],
Layout(
{
first_layout_circ.qubits[0]: 0,
first_layout_circ.qubits[2]: 1,
first_layout_circ.qubits[4]: 4,
first_layout_circ.qubits[1]: 3,
first_layout_circ.qubits[3]: 2,
}
),
)
self.assertEqual(out_pass.property_set["final_permutation"], [0, 3, 1, 2, 4])


if __name__ == "__main__":
Expand Down
5 changes: 3 additions & 2 deletions test/python/transpiler/test_sabre_swap.py
Original file line number Diff line number Diff line change
Expand Up @@ -1307,10 +1307,11 @@ def test_if_no_else_restores_layout(self):
transpiled = pass_(qc)

# Check the pass claims to have done things right.
initial_layout = Layout.generate_trivial_layout(*qc.qubits)
self.assertEqual(initial_layout, pass_.property_set["final_layout"])
final_permutation = pass_.property_set["final_permutation"]
self.assertTrue((final_permutation == [0, 1, 2, 3, 4, 5, 6, 7]).all())

# Check that pass really did do it right.
initial_layout = Layout.generate_trivial_layout(*qc.qubits)
inner_block = transpiled.data[0].operation.blocks[0]
running_layout = initial_layout.copy()
for instruction in inner_block:
Expand Down