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

Add ElidePermutations transpiler pass #9523

Merged
merged 55 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e90bdc7
Add ElideSwaps transpiler pass
mtreinish Feb 2, 2023
caa6a73
Update tests with optimization level 3
mtreinish Feb 2, 2023
e757164
Pass final layout from ElideSwap to output
mtreinish Feb 2, 2023
438e0a9
Move pass in opt level 3 earlier in stage and skip with explicit layout
mtreinish Feb 2, 2023
8f699e8
Doc and copy paste fixes
mtreinish Feb 2, 2023
8dfd4f7
Expand test coverage
mtreinish Feb 2, 2023
3b28f06
Update permutation tracking
mtreinish Feb 2, 2023
ec8af2d
Merge branch 'main' into elide-swaps
mtreinish Feb 2, 2023
599bc67
Generalize pass to support PermutationGate too
mtreinish Feb 9, 2023
ad3697c
Merge remote-tracking branch 'origin/main' into elide-swaps
mtreinish Aug 22, 2023
1d30a65
Fix permutation handling
mtreinish Aug 22, 2023
2f13d9d
Fix formatting
mtreinish Aug 22, 2023
5688293
Fix final layout handling for no initial layout
mtreinish Aug 22, 2023
955dec3
Improve documentation and log a warning if run post layout
mtreinish Aug 22, 2023
a364c08
Fix final layout handling with no ElideSwaps being run
mtreinish Aug 22, 2023
695f35b
Fix docs build
mtreinish Aug 23, 2023
39fd3bb
Merge branch 'main' into elide-swaps
mtreinish Sep 20, 2023
1ea19ee
Merge branch 'main' into elide-swaps
mtreinish Oct 5, 2023
95e7c11
Merge remote-tracking branch 'origin/main' into elide-swaps
mtreinish Oct 9, 2023
1d37611
Fix release note
mtreinish Oct 9, 2023
b27e52e
Fix typo
mtreinish Oct 9, 2023
9ece52c
Merge remote-tracking branch 'origin/main' into elide-swaps
mtreinish Oct 13, 2023
0f1df15
Add test for routing and elide permutations
mtreinish Oct 13, 2023
1aba9fc
Apply suggestions from code review
mtreinish Oct 16, 2023
723fd42
Rename test file to test_elide_permutations.py
mtreinish Oct 16, 2023
11b4156
Merge remote-tracking branch 'origin/main' into elide-swaps
mtreinish Oct 16, 2023
8ff19a6
Apply suggestions from code review
mtreinish Oct 17, 2023
40f4c4e
Merge branch 'main' into elide-swaps
mtreinish Dec 11, 2023
87ce691
Merge branch 'main' into elide-swaps
mtreinish Jan 17, 2024
c1fd952
Merge remote-tracking branch 'origin/main' into elide-swaps
mtreinish Mar 21, 2024
33e867d
Fix test import after rebase
mtreinish Mar 21, 2024
6f0f679
fixing failing test cases
sbrandhsn Mar 21, 2024
f698977
addresses kehas comments - thx
sbrandhsn Mar 21, 2024
31b23f2
Adding FinalyzeLayouts pass to pull the virtual circuit permutation f…
alexanderivrii Mar 26, 2024
32cc904
formatting
alexanderivrii Mar 26, 2024
960f592
Merge branch 'main' into elide-swaps
alexanderivrii Mar 27, 2024
e978874
Merge branch 'main' into elide-swaps
alexanderivrii Mar 28, 2024
b3c6252
partial rebase on top of 12057 + tests
alexanderivrii Mar 28, 2024
80cd91a
also need test_operator for partial rebase
alexanderivrii Mar 28, 2024
cc621f1
Merge branch 'main' into elide-swaps
alexanderivrii Mar 31, 2024
33ac77f
removing from transpiler flow for now; reworking elide tests
alexanderivrii Mar 31, 2024
ddfde6a
also adding permutation gate to the example
alexanderivrii Mar 31, 2024
aaa3365
also temporarily reverting test_transpiler.py
alexanderivrii Mar 31, 2024
15e9809
update to release notes
alexanderivrii Mar 31, 2024
2b203e1
minor fixes
alexanderivrii Mar 31, 2024
ba49503
Merge branch 'main' into elide-swaps
ElePT Apr 4, 2024
bb80234
Merge branch 'main' into elide-swaps
mtreinish May 1, 2024
5888c70
Apply suggestions from code review
mtreinish May 1, 2024
07c2966
Fix lint
mtreinish May 1, 2024
e270aa8
Update qiskit/transpiler/passes/optimization/elide_permutations.py
mtreinish May 1, 2024
f5b6778
Add test to test we skip after layout
mtreinish May 1, 2024
5cbc67d
Merge branch 'main' into elide-swaps
mtreinish May 1, 2024
e755bcd
Integrate FinalizeLayouts into the PassManager harness
mtreinish May 2, 2024
28263bf
Compose a potential existing virtual_permutation_layout
mtreinish May 2, 2024
3c967c3
Remove unused import
mtreinish May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
EchoRZXWeylDecomposition
ResetAfterMeasureSimplification
OptimizeCliffords
ElidePermutations
NormalizeRXAngle
OptimizeAnnotated

Expand Down Expand Up @@ -236,6 +237,7 @@
from .optimization import CollectCliffords
from .optimization import ResetAfterMeasureSimplification
from .optimization import OptimizeCliffords
from .optimization import ElidePermutations
from .optimization import NormalizeRXAngle
from .optimization import OptimizeAnnotated

Expand Down
1 change: 0 additions & 1 deletion qiskit/transpiler/passes/layout/apply_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ def run(self, dag):
raise TranspilerError("The 'layout' must be full (with ancilla).")

post_layout = self.property_set["post_layout"]

q = QuantumRegister(len(layout), "q")

new_dag = DAGCircuit()
Expand Down
2 changes: 1 addition & 1 deletion qiskit/transpiler/passes/layout/set_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def run(self, dag):
layout = None
else:
raise InvalidLayoutError(
f"SetLayout was intialized with the layout type: {type(self.layout)}"
f"SetLayout was initialized with the layout type: {type(self.layout)}"
)
self.property_set["layout"] = layout
return dag
1 change: 1 addition & 0 deletions qiskit/transpiler/passes/optimization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@
from .reset_after_measure_simplification import ResetAfterMeasureSimplification
from .optimize_cliffords import OptimizeCliffords
from .collect_cliffords import CollectCliffords
from .elide_permutations import ElidePermutations
from .normalize_rx_angle import NormalizeRXAngle
from .optimize_annotated import OptimizeAnnotated
112 changes: 112 additions & 0 deletions qiskit/transpiler/passes/optimization/elide_permutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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.


"""Remove any swap gates in the circuit by pushing it through into a qubit permutation."""

import logging

from qiskit.circuit.library.standard_gates import SwapGate
from qiskit.circuit.library.generalized_gates import PermutationGate
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.layout import Layout

logger = logging.getLogger(__name__)


class ElidePermutations(TransformationPass):
r"""Remove permutation operations from a pre-layout circuit

This pass is intended to be run before a layout (mapping virtual qubits
to physical qubits) is set during the transpilation pipeline. This
pass iterates over the :class:`~.DAGCircuit` and when a :class:`~.SwapGate`
or :class:`~.PermutationGate` are encountered it permutes the virtual qubits in
the circuit and removes the swap gate. This will effectively remove any
:class:`~SwapGate`\s or :class:`~PermutationGate` in the circuit prior to running
layout. If this pass is run after a layout has been set it will become a no-op
(and log a warning) as this optimization is not sound after physical qubits are
selected and there are connectivity constraints to adhere to.

For tracking purposes this pass sets 3 values in the property set if there
are any :class:`~.SwapGate` or :class:`~.PermutationGate` objects in the circuit
and the pass isn't a no-op.

* ``original_layout``: The trivial :class:`~.Layout` for the input to this pass being run
* ``original_qubit_indices``: The mapping of qubit objects to positional indices for the state
of the circuit as input to this pass.
* ``virtual_permutation_layout``: A :class:`~.Layout` object mapping input qubits to the output
state after eliding permutations.

These three properties are needed for the transpiler to track the permutations in the out
:attr:`.QuantumCircuit.layout` attribute. The elision of permutations is equivalent to a
``final_layout`` set by routing and all three of these attributes are needed in the case
"""

def run(self, dag):
"""Run the ElidePermutations pass on ``dag``.

Args:
dag (DAGCircuit): the DAG to be optimized.

Returns:
DAGCircuit: the optimized DAG.
"""
if self.property_set["layout"] is not None:
logger.warning(
"ElidePermutations is not valid after a layout has been set. This indicates "
"an invalid pass manager construction."
)
return dag

op_count = dag.count_ops(recurse=False)
if op_count.get("swap", 0) == 0 and op_count.get("permutation", 0) == 0:
return dag

new_dag = dag.copy_empty_like()
qubit_mapping = list(range(len(dag.qubits)))

def _apply_mapping(qargs):
return tuple(dag.qubits[qubit_mapping[dag.find_bit(qubit).index]] for qubit in qargs)

for node in dag.topological_op_nodes():
if not isinstance(node.op, (SwapGate, PermutationGate)):
new_dag.apply_operation_back(
node.op, _apply_mapping(node.qargs), node.cargs, check=False
)
elif getattr(node.op, "condition", None) is not None:
new_dag.apply_operation_back(
node.op, _apply_mapping(node.qargs), node.cargs, check=False
)
elif isinstance(node.op, SwapGate):
index_0 = dag.find_bit(node.qargs[0]).index
index_1 = dag.find_bit(node.qargs[1]).index
qubit_mapping[index_1], qubit_mapping[index_0] = (
qubit_mapping[index_0],
qubit_mapping[index_1],
)
elif isinstance(node.op, PermutationGate):
starting_indices = [qubit_mapping[dag.find_bit(qarg).index] for qarg in node.qargs]
pattern = node.op.params[0]
pattern_indices = [qubit_mapping[idx] for idx in pattern]
for i, j in zip(starting_indices, pattern_indices):
qubit_mapping[i] = j
input_qubit_mapping = {qubit: index for index, qubit in enumerate(dag.qubits)}
self.property_set["original_layout"] = Layout(input_qubit_mapping)
if self.property_set["original_qubit_indices"] is None:
self.property_set["original_qubit_indices"] = input_qubit_mapping

new_layout = Layout({dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)})
if current_layout := self.property_set["virtual_permutation_layout"] is not None:
self.property_set["virtual_permutation_layout"] = current_layout.compose(new_layout)
else:
self.property_set["virtual_permutation_layout"] = new_layout
return new_dag
45 changes: 44 additions & 1 deletion 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 @@ -69,6 +69,7 @@ def _passmanager_backend(
) -> QuantumCircuit:
out_program = dag_to_circuit(passmanager_ir, copy_operations=False)

self._finalize_layouts(passmanager_ir)
out_name = kwargs.get("output_name", None)
if out_name is not None:
out_program.name = out_name
Expand Down Expand Up @@ -96,6 +97,48 @@ def _passmanager_backend(

return out_program

def _finalize_layouts(self, dag):
if (virtual_permutation_layout := self.property_set["virtual_permutation_layout"]) is None:
return

self.property_set.pop("virtual_permutation_layout")

# virtual_permutation_layout is usually created before extending the layout with ancillas,
# so we extend the permutation to be identity on ancilla qubits
original_qubit_indices = self.property_set.get("original_qubit_indices", None)
for oq in original_qubit_indices:
if oq not in virtual_permutation_layout:
virtual_permutation_layout[oq] = original_qubit_indices[oq]

t_qubits = dag.qubits

if (t_initial_layout := self.property_set.get("layout", None)) is None:
t_initial_layout = Layout(dict(enumerate(t_qubits)))

if (t_final_layout := self.property_set.get("final_layout", None)) is None:
t_final_layout = Layout(dict(enumerate(t_qubits)))

# Ordered list of original qubits
original_qubits_reverse = {v: k for k, v in original_qubit_indices.items()}
original_qubits = []
for i in range(len(original_qubits_reverse)):
original_qubits.append(original_qubits_reverse[i])

virtual_permutation_layout_inv = virtual_permutation_layout.inverse(
original_qubits, original_qubits
)

t_initial_layout_inv = t_initial_layout.inverse(original_qubits, t_qubits)

# ToDo: this can possibly be made simpler
new_final_layout = t_initial_layout_inv
new_final_layout = new_final_layout.compose(virtual_permutation_layout_inv, original_qubits)
new_final_layout = new_final_layout.compose(t_initial_layout, original_qubits)
new_final_layout = new_final_layout.compose(t_final_layout, t_qubits)

self.property_set["layout"] = t_initial_layout
self.property_set["final_layout"] = new_final_layout

def append(
self,
passes: Task | list[Task],
Expand Down
1 change: 0 additions & 1 deletion qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
)
)
init.append(CommutativeCancellation())

else:
raise TranspilerError(f"Invalid optimization level {optimization_level}")
return init
Expand Down
41 changes: 41 additions & 0 deletions releasenotes/notes/add-elide-swaps-b0a4c373c9af1efd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
features:
- |
Added a new optimization transpiler pass, :class:`~.ElidePermutations`,
which is designed to run prior to the :ref:`layout_stage` and will
optimize away any :class:`~.SwapGate`\s and
:class:`~qiskit.circuit.library.PermutationGate`\s
in a circuit by permuting virtual
qubits. For example, taking a circuit with :class:`~.SwapGate`\s:

.. plot::

mtreinish marked this conversation as resolved.
Show resolved Hide resolved
from qiskit.circuit import QuantumCircuit

qc = QuantumCircuit(3)
qc.h(0)
qc.swap(0, 1)
qc.swap(2, 0)
qc.cx(1, 0)
qc.measure_all()
qc.draw("mpl")

will remove the swaps when the pass is run:

.. plot::
:include-source:

from qiskit.transpiler.passes import ElidePermutations
from qiskit.circuit import QuantumCircuit

qc = QuantumCircuit(3)
qc.h(0)
qc.swap(0, 1)
qc.swap(2, 0)
qc.cx(1, 0)
qc.measure_all()

ElidePermutations()(qc).draw("mpl")

The pass also sets the ``virtual_permutation_layout`` property set, storing
the permutation of the virtual qubits that was optimized away.
Loading
Loading