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 pass to optimization level 3 #12111

Merged
merged 57 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
57 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
7a90b70
adding ElidePermutations and FinalizeLayouts to level 3
alexanderivrii Apr 1, 2024
9edf432
fixing tests
alexanderivrii Apr 1, 2024
c935fea
release notes
alexanderivrii Apr 1, 2024
6649551
Merge branch 'main' into elide-swaps-in-transpile
alexanderivrii May 2, 2024
76ee567
Merge branch 'main' into elide-swaps-in-transpile
alexanderivrii May 2, 2024
0c6eece
Merge branch 'main' into elide-swaps-in-transpile
alexanderivrii May 2, 2024
032039c
properly merging with main (now that ElidePermutations is merged)
alexanderivrii May 2, 2024
a2de408
and also deleting finalize_layouts
alexanderivrii May 2, 2024
efe602d
Update releasenotes/notes/add-elide-permutations-to-pipeline-077dad03…
mtreinish May 2, 2024
90f20e7
updating code comment
alexanderivrii May 2, 2024
83e52ff
Fixing failing test: now that ElidePermutations pass runs with optimi…
alexanderivrii May 2, 2024
b4ced45
Merge branch 'elide-swaps-in-transpile' of github.com:alexanderivrii/…
alexanderivrii 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
49 changes: 31 additions & 18 deletions qiskit/quantum_info/operators/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ def __init__(
a Numpy array of shape (2**N, 2**N) qubit systems will be used. If
the input operator is not an N-qubit operator, it will assign a
single subsystem with dimension specified by the shape of the input.
Note that two operators initialized via this method are only considered equivalent if they
match up to their canonical qubit order (or: permutation). See :meth:`.Operator.from_circuit`
to specify a different qubit permutation.
"""
op_shape = None
if isinstance(data, (list, np.ndarray)):
Expand Down Expand Up @@ -391,8 +394,7 @@ def from_circuit(
Returns:
Operator: An operator representing the input circuit
"""
dimension = 2**circuit.num_qubits
op = cls(np.eye(dimension))

if layout is None:
if not ignore_set_layout:
layout = getattr(circuit, "_layout", None)
Expand All @@ -403,27 +405,38 @@ def from_circuit(
initial_layout=layout,
input_qubit_mapping={qubit: index for index, qubit in enumerate(circuit.qubits)},
)

initial_layout = layout.initial_layout if layout is not None else None

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can rewrite this as inital_layout = layout and layout.initial_layout

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your way is also valid, so it's mostly just a question of preference. is not None checks are very explicit about what's being expected, whereas the implicit truthiness and pass-through of Python's Boolean expressions is maybe a little less well-known (and technically allows more things to pass through the false-y test without an error).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats understandable. Fine with keeping it this way for code readability 👍


if final_layout is None:
if not ignore_set_layout and layout is not None:
final_layout = getattr(layout, "final_layout", None)

qargs = None
# If there was a layout specified (either from the circuit
# or via user input) use that to set qargs to permute qubits
# based on that layout
if layout is not None:
physical_to_virtual = layout.initial_layout.get_physical_bits()
qargs = [
layout.input_qubit_mapping[physical_to_virtual[physical_bit]]
for physical_bit in range(len(physical_to_virtual))
]
# Convert circuit to an instruction
instruction = circuit.to_instruction()
op._append_instruction(instruction, qargs=qargs)
# If final layout is set permute output indices based on layout
from qiskit.synthesis.permutation.permutation_utils import _inverse_pattern

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kind of nit: Since we are always going to execute this line of code, shouldn't this be at the top of the function? Right after the docstring?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qiskit.synthesis is a logically higher part of the package hierarchy than qiskit.quantum_info, but this part of Operator is acting above its position, so we use a scoped import to avoid import cycles.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see. But my concern is more towards moving this to top of the function from_circuit and not the top of the file (which is where I think it could run into the circular import error). I might be wrong, can you please clarify?


if initial_layout is not None:
input_qubits = [None] * len(layout.input_qubit_mapping)
for q, p in layout.input_qubit_mapping.items():
input_qubits[p] = q

initial_permutation = initial_layout.to_permutation(input_qubits)
initial_permutation_inverse = _inverse_pattern(initial_permutation)

if final_layout is not None:
perm_pattern = [final_layout._v2p[v] for v in circuit.qubits]
op = op.apply_permutation(perm_pattern, front=False)
final_permutation = final_layout.to_permutation(circuit.qubits)
final_permutation_inverse = _inverse_pattern(final_permutation)

op = Operator(circuit)

if initial_layout:
op = op.apply_permutation(initial_permutation, True)

if final_layout:
op = op.apply_permutation(final_permutation_inverse, False)

if initial_layout:
op = op.apply_permutation(initial_permutation_inverse, False)

return op

def is_unitary(self, atol=None, rtol=None):
Expand Down
4 changes: 4 additions & 0 deletions qiskit/transpiler/passes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@
EchoRZXWeylDecomposition
ResetAfterMeasureSimplification
OptimizeCliffords
ElidePermutations
NormalizeRXAngle
OptimizeAnnotated
FinalizeLayouts

Calibration
=============
Expand Down Expand Up @@ -236,8 +238,10 @@
from .optimization import CollectCliffords
from .optimization import ResetAfterMeasureSimplification
from .optimization import OptimizeCliffords
from .optimization import ElidePermutations
from .optimization import NormalizeRXAngle
from .optimization import OptimizeAnnotated
from .optimization import FinalizeLayouts

# circuit analysis
from .analysis import ResourceEstimation
Expand Down
5 changes: 2 additions & 3 deletions 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 All @@ -71,9 +70,9 @@ def run(self, dag):
}
for qreg in dag.qregs.values():
self.property_set["layout"].add_register(qreg)
virtual_phsyical_map = layout.get_virtual_bits()
virtual_physical_map = layout.get_virtual_bits()
for node in dag.topological_op_nodes():
qargs = [q[virtual_phsyical_map[qarg]] for qarg in node.qargs]
qargs = [q[virtual_physical_map[qarg]] for qarg in node.qargs]
new_dag.apply_operation_back(node.op, qargs, node.cargs, check=False)
else:
# First build a new layout object going from:
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
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/optimization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,7 @@
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
from .finalize_layouts import FinalizeLayouts
106 changes: 106 additions & 0 deletions qiskit/transpiler/passes/optimization/elide_permutations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# 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()
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)
elif getattr(node.op, "condition", None) is not None:
new_dag.apply_operation_back(node.op, _apply_mapping(node.qargs), node.cargs)
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
# ToDo: check if this exists; then compose
self.property_set["virtual_permutation_layout"] = Layout(
{dag.qubits[out]: idx for idx, out in enumerate(qubit_mapping)}
)
return new_dag
77 changes: 77 additions & 0 deletions qiskit/transpiler/passes/optimization/finalize_layouts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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.


"""Finalize layout-related attributes."""

import logging

from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.transpiler.layout import Layout

logger = logging.getLogger(__name__)


class FinalizeLayouts(AnalysisPass):
"""Finalize 'layout' and 'final_layout' attributes, taking 'virtual_permutation_layout'
into account when exists.
"""

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

Args:
dag (DAGCircuit): the DAG circuit.
"""

if (
virtual_permutation_layout := self.property_set.get("virtual_permutation_layout", None)
) 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
5 changes: 2 additions & 3 deletions qiskit/transpiler/preset_passmanagers/builtin_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from qiskit.transpiler.passes import TrivialLayout
from qiskit.transpiler.passes import CheckMap
from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements
from qiskit.transpiler.passes import OptimizeSwapBeforeMeasure
from qiskit.transpiler.passes import ElidePermutations
from qiskit.transpiler.passes import RemoveDiagonalGatesBeforeMeasure
from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.preset_passmanagers.plugin import (
Expand Down Expand Up @@ -135,7 +135,7 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
pass_manager_config.unitary_synthesis_plugin_config,
pass_manager_config.hls_config,
)
init.append(OptimizeSwapBeforeMeasure())
init.append(ElidePermutations())
init.append(RemoveDiagonalGatesBeforeMeasure())
init.append(
InverseCancellation(
Expand All @@ -156,7 +156,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana
)
)
init.append(CommutativeCancellation())

else:
return TranspilerError(f"Invalid optimization level {optimization_level}")
return init
Expand Down
8 changes: 8 additions & 0 deletions qiskit/transpiler/preset_passmanagers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from qiskit.transpiler.passes import PulseGates
from qiskit.transpiler.passes import ContainsInstruction
from qiskit.transpiler.passes import VF2PostLayout
from qiskit.transpiler.passes import FinalizeLayouts
from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason
from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason
from qiskit.transpiler.exceptions import TranspilerError
Expand Down Expand Up @@ -407,6 +408,13 @@ def _direction_condition(property_set):
return pre_opt


def generate_post_op_passmanager():
"""Generates the post-optimization pass manager."""
out = PassManager()
out.append(FinalizeLayouts())
return out
Copy link
Member

@jakelishman jakelishman May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Highly unimportant, but just to point out that this can also be spelled return PassManager([FinalizeLayouts()]).

(No need to change it, just commenting in case it's shorter for you in the future.)



def generate_translation_passmanager(
target,
basis_gates=None,
Expand Down
3 changes: 3 additions & 0 deletions qiskit/transpiler/preset_passmanagers/level3.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
"scheduling", scheduling_method, pass_manager_config, optimization_level=3
)

post_optimization = common.generate_post_op_passmanager()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we ought to include the layout finalisation in every optimisation level, even if by our presets it won't have any work to do. It just helps catch the case of somebody using (say) an O1 pass manager but injecting ElideSwaps into pre-op, which is a totally valid use of StagedPassManager.

Since we don't have final_permutation and its fixing of the update responsibilities to be local to the modifying pass, I think we need to treat FinalizeLayouts as a required part of the transpiler pipeline at all optimisation levels, or shift the responsibility of FinalizeLayouts into PassManager._passmanager_backend (#9523 (comment)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since #9523 moved the responsibility of FinalizeLayouts into PassManager._passmanager_backend, now we don't need the post-op stage at all, rather than needing it everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, this is much-much cleaner than having a separate FinalizeLayouts pass.

return StagedPassManager(
pre_init=pre_init,
init=init,
Expand All @@ -115,5 +117,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa
translation=translation,
pre_optimization=pre_optimization,
optimization=optimization,
post_optimization=post_optimization,
scheduling=sched,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
features:
- |
The transpiler passes :class:`~.ElidePermutations` and :class:`~.FinalizeLayouts`
run by default with optimization level 3. Intuitively, removing
:class:`~.SwapGate`\s and :class:`~qiskit.circuit.library.PermutationGate`\s
in a virtual circuit is almost always beneficial, as it makes the circuit shorter
and easier to route. As :class:`~.OptimizeSwapBeforeMeasure` is a special case
of :class:`~.ElidePermutations`, it has been removed from optimization level 3.
Loading
Loading