From 71495591d739f7c7778ad70207c5e34111af6505 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 21 Jun 2022 14:57:04 -0400 Subject: [PATCH] Add StagedPassManager class for pass manager with defined stages (#6403) * Add FullPassManager class for pass manager with defined stages This commit adds a new PassManager subclass, FullPassManager. This class is used to have a PassManager with a defined structure and stages for the normal transpile workflow. The preset pass managers are then updated to be FullPassManager objects they conform to the fixed structure. Having a class with defined phases gives us flexibility in the future for making the transpiler pluggable with external plugins (similar to what's done in PR #6124) and also have backend hook points before or after different phases of the transpile. Fixes #5978 * Add docs * Deduplicate preset passmanager construction * Update docs * Add dedicated scheduling stage to FullPassManager * Add missing new UnitarySynthesis kwargs after rebase * Use basis_translator as default method instead of basis Co-authored-by: Kevin Krsulich * Rename FullPassManager StructuredPassManager * Rename generate_scheduling_post_opt() generate_scheduling() * Fix missing and incorrect arguments * Fix more rebase issues * Fix even more rebase issues * Only run unroll3q on level 0-2 if coupling map is set To preserve the behavior prior to this reorganization this commit makes the Unroll3qorMore pass run if we have a coupling map set. This is because this pass is only needed to be run for these optimization levels so that the layout and routing passes can function (as they only work with 2q gates). If we're not running these passes we shouldn't be unrolling gates. This will fix the last 4 QAOA test failures post the recent rebase as that was failing because we were trying to unroll an unbound 4q hamiltonian gate when weren't before. * Rework StructuredPassManager as a more dynamic StagedPassManager This commit redefines the previous StructuredPassManager class into a more dynamic StagedPassmanager. The StagedPassManager doesn't have fixed hard coded stages anymore but instead lets users define their own stages. It also adds implicit 'pre_' and 'post_' hook points for each listed stage. This lets users dynamically define the stages based on a particular use case. * Fix docs * Update internal pass set on each access This commit updates the api for the StagedPassManager to refresh the internal pass list on each access. This adds a little overhead but with the tradeoff of making in place modifications to a stage's pass manager reflected without needing to manually call an update method. * Rename phases attribute to stages * Fix lint * Explicitly set name in qpy compat tests The rework of the preset passmanager construction changes the import order slightly and the number of circuits constructed in a session prior to the qpy compat deserialization side generating equivalent circuits for comparision has changed. This is causing all the deserialization side numbers to change and the tests are now failing because the circuit names are not equivalent. Since the exact name is a side effect of the import order (based on the number of unnamed circuits created in the session priort) it's not part of what the qpy tests are validating. We're trying to assert that the naem is preserved loading a qpy file across a version boundary. To fix this issues this commit adds an explicit name to the generation for all the circuits to ensure that we have a deterministic name for each circuit. * Apply suggestions from code review Co-authored-by: Luciano Bello * Run black * Update type hint * Remove out of date docstring note * Update copyright header date in qiskit/transpiler/preset_passmanagers/common.py Co-authored-by: Luciano Bello * Add check for invalid stage names * Add backwards compatibility note * Add docs on using StagedPassManager features with preset passmanagers Co-authored-by: Kevin Krsulich Co-authored-by: Luciano Bello Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/__init__.py | 70 ++++ .../passes/optimization/consolidate_blocks.py | 7 +- qiskit/transpiler/passmanager.py | 184 +++++++++ .../preset_passmanagers/__init__.py | 2 +- .../transpiler/preset_passmanagers/common.py | 388 ++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 287 ++++--------- .../transpiler/preset_passmanagers/level1.py | 355 +++++----------- .../transpiler/preset_passmanagers/level2.py | 353 ++++------------ .../transpiler/preset_passmanagers/level3.py | 354 +++++----------- ...add-full-passmanager-5a377f1b71480f72.yaml | 19 + .../transpiler/test_staged_passmanager.py | 94 +++++ test/qpy_compat/test_qpy.py | 42 +- 12 files changed, 1136 insertions(+), 1019 deletions(-) create mode 100644 qiskit/transpiler/preset_passmanagers/common.py create mode 100644 releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml create mode 100644 test/python/transpiler/test_staged_passmanager.py diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index d6d584de811e..ccaec1f4d224 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -595,6 +595,74 @@
+.. dropdown:: Working with preset :class:`~.PassManager` + :animate: fade-in-slide-down + + By default Qiskit includes functions to build preset :class:`~.PassManager` objects. + These preset passmangers are what get used by the :func:`~.transpile` function + for each optimization level. If you'd like to work directly with a + preset pass manager you can use the :func:`~.generate_preset_pass_manager` + function to easily generate one. For example: + + .. code-block:: python + + from qiskit.transpiler.preset_passmanager import generate_preset_pass_manager + from qiskit.providers.fake_provider import FakeLagosV2 + + backend = FakeLagosV2() + pass_manager = generate_preset_pass_manager(3, backend) + + which will generate a :class:`~.StagedPassManager` object for optimization level 3 + targetting the :class:`~.FakeLagosV2` backend (equivalent to what is used internally + by :func:`~.transpile` with ``backend=FakeLagosV2()`` and ``optimization_level=3``). + You can use this just like working with any other :class:`~.PassManager`. However, + because it is a :class:`~.StagedPassManager` it also makes it easy to compose and/or + replace stages of the pipeline. For example, if you wanted to run a custom scheduling + stage using dynamical decoupling (via the :class:`~.PadDynamicalDecoupling` pass) and + also add initial logical optimization prior to routing you would do something like + (building off the previous example): + + .. code-block:: python + + from qiskit.circuit.library import XGate, HGate, RXGate, PhaseGate, TGate, TdgGate + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling + from qiskit.transpiler.passes import CXCancellation, InverseCancellation + + backend_durations = backend.target.durations() + dd_sequence = [XGate(), XGate()] + scheduling_pm = PassManager([ + ALAPScheduleAnalysis(backend_durations), + PadDynamicalDecoupling(backend_durations, dd_sequence), + ]) + inverse_gate_list = [ + HGate(), + (RXGate(np.pi / 4), RXGate(-np.pi / 4)), + (PhaseGate(np.pi / 4), PhaseGate(-np.pi / 4)), + (TGate(), TdgGate()), + + ]) + logical_opt = PassManager([ + CXCancellation(), + InverseCancellation([HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)) + ]) + + + # Add pre-layout stage to run extra logical optimization + pass_manager.pre_layout = logical_opt + # Set scheduling stage to custom pass manager + pass_manager.scheduling = scheduling_pm + + + Then when :meth:`~.StagedPassManager.run` is called on ``pass_manager`` the + ``logical_opt`` :class:`~.PassManager` will be called prior to the ``layout`` stage + and for the ``scheduling`` stage our custom :class:`~.PassManager` + ``scheduling_pm`` will be used. + + .. raw:: html + +
+ Transpiler API ============== @@ -614,6 +682,7 @@ .. autosummary:: :toctree: ../stubs/ + StagedPassManager PassManager PassManagerConfig PropertySet @@ -669,6 +738,7 @@ from .runningpassmanager import FlowController, ConditionalController, DoWhileController from .passmanager import PassManager from .passmanager_config import PassManagerConfig +from .passmanager import StagedPassManager from .propertyset import PropertySet from .exceptions import TranspilerError, TranspilerAccessError from .fencedobjs import FencedDAGCircuit, FencedPropertySet diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 90c0858705bb..c9f2d540b60e 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -117,7 +117,7 @@ def run(self, dag): # If 1q runs are collected before consolidate those too runs = self.property_set["run_list"] or [] for run in runs: - if run[0] in all_block_gates: + if any(gate in all_block_gates for gate in run): continue if len(run) == 1 and not self._check_not_in_basis( run[0].name, run[0].qargs, global_index_map @@ -135,6 +135,11 @@ def run(self, dag): continue unitary = UnitaryGate(operator) dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False) + # Clear collected blocks and runs as they are no longer valid after consolidation + if "run_list" in self.property_set: + del self.property_set["run_list"] + if "block_list" in self.property_set: + del self.property_set["block_list"] return dag def _check_not_in_basis(self, gate_name, qargs, global_index_map): diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index a206af3cf0ab..064579624c91 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -12,6 +12,8 @@ """Manager for a set of Passes and their scheduling during transpilation.""" +import io +import re from typing import Union, List, Callable, Dict, Any import dill @@ -319,3 +321,185 @@ def passes(self) -> List[Dict[str, BasePass]]: item["flow_controllers"] = {} ret.append(item) return ret + + +class StagedPassManager(PassManager): + """A Pass manager pipeline built up of individual stages + + This class enables building a compilation pipeline out of fixed stages. + Each ``StagedPassManager`` defines a list of stages which are executed in + a fixed order, and each stage is defined as a standalone :class:`~.PassManager` + instance. There are also ``pre_`` and ``post_`` stages for each defined stage. + This enables easily composing and replacing different stages and also adding + hook points to enable programmtic modifications to a pipeline. When using a staged + pass manager you are not able to modify the individual passes and are only able + to modify stages. + + By default instances of StagedPassManager define a typical full compilation + pipeline from an abstract virtual circuit to one that is optimized and + capable of running on the specified backend. The default pre-defined stages are: + + #. ``init`` - any initial passes that are run before we start embedding the circuit to the backend + #. ``layout`` - This stage runs layout and maps the virtual qubits in the + circuit to the physical qubits on a backend + #. ``routing`` - This stage runs after a layout has been run and will insert any + necessary gates to move the qubit states around until it can be run on + backend's compuling map. + #. ``translation`` - Perform the basis gate translation, in other words translate the gates + in the circuit to the target backend's basis set + #. ``optimization`` - The main optimization loop, this will typically run in a loop trying to + optimize the circuit until a condtion (such as fixed depth) is reached. + #. ``scheduling`` - Any hardware aware scheduling passes + + .. note:: + + For backwards compatibility the relative positioning of these default + stages will remain stable moving forward. However, new stages may be + added to the default stage list in between current stages. For example, + in a future release a new phase, something like ``logical_optimization``, could be added + immediately after the existing ``init`` stage in the default stage list. + This would preserve compatibility for pre-existing ``StagedPassManager`` + users as the relative positions of the stage are preserved so the behavior + will not change between releases. + + These stages will be executed in order and any stage set to ``None`` will be skipped. If + a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here + (for example in the case of a :class:`~.Pass` that covers both Layout and Routing) you will want + to set that to the earliest stage in sequence that it covers. + """ + + invalid_stage_regex = re.compile( + r"\s|\+|\-|\*|\/|\\|\%|\<|\>|\@|\!|\~|\^|\&|\:|\[|\]|\{|\}|\(|\)" + ) + + def __init__(self, stages=None, **kwargs): + """Initialize a new StagedPassManager object + + Args: + stages (List[str]): An optional list of stages to use for this + instance. If this is not specified the default stages list + ``['init', 'layout', 'routing', 'translation', 'optimization', 'scheduling']`` is + used + kwargs: The initial :class:`~.PassManager` values for any stages + defined in ``stages``. If a argument is not defined the + stages will default to ``None`` indicating an empty/undefined + stage. + + Raises: + AttributeError: If a stage in the input keyword arguments is not defined. + ValueError: If an invalid stage name is specified. + """ + if stages is None: + self.stages = [ + "init", + "layout", + "routing", + "translation", + "optimization", + "scheduling", + ] + else: + invalid_stages = [ + stage for stage in stages if self.invalid_stage_regex.search(stage) is not None + ] + if invalid_stages: + with io.StringIO() as msg: + msg.write(f"The following stage names are not valid: {invalid_stages[0]}") + for invalid_stage in invalid_stages[1:]: + msg.write(f", {invalid_stage}") + raise ValueError(msg.getvalue()) + + self.stages = stages + super().__init__() + for stage in self.stages: + pre_stage = "pre_" + stage + post_stage = "post_" + stage + setattr(self, pre_stage, None) + setattr(self, stage, None) + setattr(self, post_stage, None) + for stage, pm in kwargs.items(): + if ( + stage not in self.stages + and not (stage.startswith("pre_") and stage[4:] in self.stages) + and not (stage.startswith("post_") and stage[5:] in self.stages) + ): + raise AttributeError(f"{stage} is not a valid stage.") + setattr(self, stage, pm) + self._update_passmanager() + + def _update_passmanager(self): + self._pass_sets = [] + for stage in self.stages: + # Add pre-stage PM + pre_stage_pm = getattr(self, "pre_" + stage, None) + if pre_stage_pm is not None: + self._pass_sets.extend(pre_stage_pm._pass_sets) + # Add stage PM + stage_pm = getattr(self, stage, None) + if stage_pm is not None: + self._pass_sets.extend(stage_pm._pass_sets) + # Add post-stage PM + post_stage_pm = getattr(self, "post_" + stage, None) + if post_stage_pm is not None: + self._pass_sets.extend(post_stage_pm._pass_sets) + + def __setattr__(self, attr, value): + super().__setattr__(attr, value) + if ( + attr in self.stages + or (attr.startswith("pre_") and attr[4:] not in self.stages) + or (attr.startswith("post_") and attr[5:] not in self.stages) + ): + self._update_passmanager() + + def append( + self, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any, + ) -> None: + raise NotImplementedError + + def replace( + self, + index: int, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any, + ) -> None: + raise NotImplementedError + + # Raise NotImplemntedError on individual pass manipulation + def remove(self, index: int) -> None: + raise NotImplementedError + + def __getitem(self, index): + self._update_passmanager() + return super().__getitem__(index) + + def __len__(self): + self._update_passmanager() + return super().__len__() + + def __setitem__(self, index, item): + raise NotImplementedError + + def __add__(self, other): + raise NotImplementedError + + def _create_running_passmanager(self) -> RunningPassManager: + self._update_passmanager() + return super()._create_running_passmanager() + + def passes(self) -> List[Dict[str, BasePass]]: + self._update_passmanager() + return super().passes() + + def run( + self, + circuits: Union[QuantumCircuit, List[QuantumCircuit]], + output_name: str = None, + callback: Callable = None, + ) -> Union[QuantumCircuit, List[QuantumCircuit]]: + self._update_passmanager() + return super().run(circuits, output_name, callback) diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index 45ddada26c71..eb2f0d2aed39 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -136,7 +136,7 @@ def generate_preset_pass_manager( to use this option. Returns: - PassManager: The preset pass manager for the given options + StagedPassManager: The preset pass manager for the given options Raises: ValueError: if an invalid value for ``optimization_level`` is passed in. diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py new file mode 100644 index 000000000000..ce01dce6a26e --- /dev/null +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -0,0 +1,388 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# 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=invalid-name + +"""Common preset passmanager generators.""" + +from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel + +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passes import Unroller +from qiskit.transpiler.passes import BasisTranslator +from qiskit.transpiler.passes import UnrollCustomDefinitions +from qiskit.transpiler.passes import Unroll3qOrMore +from qiskit.transpiler.passes import Collect2qBlocks +from qiskit.transpiler.passes import Collect1qRuns +from qiskit.transpiler.passes import ConsolidateBlocks +from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import CheckMap +from qiskit.transpiler.passes import GateDirection +from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import CheckGateDirection +from qiskit.transpiler.passes import TimeUnitConversion +from qiskit.transpiler.passes import ALAPScheduleAnalysis +from qiskit.transpiler.passes import ASAPScheduleAnalysis +from qiskit.transpiler.passes import FullAncillaAllocation +from qiskit.transpiler.passes import EnlargeWithAncilla +from qiskit.transpiler.passes import ApplyLayout +from qiskit.transpiler.passes import RemoveResetInZeroState +from qiskit.transpiler.passes import ValidatePulseGates +from qiskit.transpiler.passes import PadDelay +from qiskit.transpiler.passes import InstructionDurationCheck +from qiskit.transpiler.passes import ConstrainedReschedule +from qiskit.transpiler.passes import PulseGates +from qiskit.transpiler.passes import ContainsInstruction +from qiskit.transpiler.passes import VF2PostLayout +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 + + +def generate_unroll_3q( + target, + basis_gates=None, + approximation_degree=None, + unitary_synthesis_method="default", + unitary_synthesis_plugin_config=None, +): + """Generate an unroll >3q :class:`~qiskit.transpiler.PassManager` + + Args: + target (Target): the :class:`~.Target` object representing the backend + basis_gates (list): A list of str gate names that represent the basis + gates on the backend target + approximation_degree (float): The heuristic approximation degree to + use. Can be between 0 and 1. + unitary_synthesis_method (str): The unitary synthesis method to use + unitary_synthesis_plugin_config (dict): The optional dictionary plugin + configuration, this is plugin specific refer to the specified plugin's + documenation for how to use. + + Returns: + PassManager: The unroll 3q or more pass manager + """ + unroll_3q = PassManager() + unroll_3q.append( + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + method=unitary_synthesis_method, + min_qubits=3, + plugin_config=unitary_synthesis_plugin_config, + target=target, + ) + ) + unroll_3q.append(Unroll3qOrMore(target=target, basis_gates=basis_gates)) + return unroll_3q + + +def generate_embed_passmanager(coupling_map): + """Generate a layout embedding :class:`~qiskit.transpiler.PassManager` + + This is used to generate a :class:`~qiskit.transpiler.PassManager` object + that can be used to expand and apply an initial layout to a circuit + + Args: + coupling_map (CouplingMap): The coupling map for the backend to embed + the circuit to. + Returns: + PassManager: The embedding passmanager that assumes the layout property + set has been set in earlier stages + """ + return PassManager([FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]) + + +def _trivial_not_perfect(property_set): + # Verify that a trivial layout is perfect. If trivial_layout_score > 0 + # the layout is not perfect. The layout is unconditionally set by trivial + # layout so we need to clear it before contuing. + return ( + property_set["trivial_layout_score"] is not None + and property_set["trivial_layout_score"] != 0 + ) + + +def _apply_post_layout_condition(property_set): + # if VF2 Post layout found a solution we need to re-apply the better + # layout. Otherwise we can skip apply layout. + return ( + property_set["VF2PostLayout_stop_reason"] is not None + and property_set["VF2PostLayout_stop_reason"] is VF2PostLayoutStopReason.SOLUTION_FOUND + ) + + +def generate_routing_passmanager( + routing_pass, + target, + coupling_map=None, + vf2_call_limit=None, + backend_properties=None, + seed_transpiler=None, + check_trivial=False, + use_barrier_before_measurement=True, +): + """Generate a routing :class:`~qiskit.transpiler.PassManager` + + Args: + routing_pass (TransformationPass): The pass which will perform the + routing + target (Target): the :class:`~.Target` object representing the backend + coupling_map (CouplingMap): The coupling map of the backend to route + for + vf2_call_limit (int): The internal call limit for the vf2 post layout + pass. If this is ``None`` the vf2 post layout will not be run. + backend_properties (BackendProperties): Properties of a backend to + synthesize for (e.g. gate fidelities). + seed_transpiler (int): Sets random seed for the stochastic parts of + the transpiler. + check_trivial (bool): If set to true this will condition running the + :class:`~.VF2PostLayout` pass after routing on whether a trivial + layout was tried and was found to not be perfect. This is only + needed if the constructed pass manager runs :class:`~.TrivialLayout` + as a first layout attempt and uses it if it's a perfect layout + (as is the case with preset pass manager level 1). + use_barrier_before_measurement (bool): If true (the default) the + :class:`~.BarrierBeforeFinalMeasurements` transpiler pass will be run prior to the + specified pass in the ``routing_pass`` argument. + Returns: + PassManager: The routing pass manager + """ + + def _run_post_layout_condition(property_set): + # If we check trivial layout and the found trivial layout was not perfect also + # ensure VF2 initial layout was not used before running vf2 post layout + if not check_trivial or _trivial_not_perfect(property_set): + vf2_stop_reason = property_set["VF2Layout_stop_reason"] + if vf2_stop_reason is None or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND: + return True + return False + + routing = PassManager() + routing.append(CheckMap(coupling_map)) + + def _swap_condition(property_set): + return not property_set["is_swap_mapped"] + + if use_barrier_before_measurement: + routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition) + else: + routing.append([routing_pass], condition=_swap_condition) + + if (target is not None or backend_properties is not None) and vf2_call_limit is not None: + routing.append( + VF2PostLayout( + target, + coupling_map, + backend_properties, + seed_transpiler, + call_limit=vf2_call_limit, + strict_direction=False, + ), + condition=_run_post_layout_condition, + ) + routing.append(ApplyLayout(), condition=_apply_post_layout_condition) + + return routing + + +def generate_pre_op_passmanager(target=None, coupling_map=None, remove_reset_in_zero=False): + """Generate a pre-optimization loop :class:`~qiskit.transpiler.PassManager` + + This pass manager will check to ensure that directionality from the coupling + map is respected + + Args: + target (Target): the :class:`~.Target` object representing the backend + coupling_map (CouplingMap): The coupling map to use + remove_reset_in_zero (bool): If ``True`` include the remove reset in + zero pass in the generated PassManager + Returns: + PassManager: The pass manager + + """ + pre_opt = PassManager() + if coupling_map: + pre_opt.append(CheckGateDirection(coupling_map, target=target)) + + def _direction_condition(property_set): + return not property_set["is_direction_mapped"] + + pre_opt.append([GateDirection(coupling_map, target=target)], condition=_direction_condition) + if remove_reset_in_zero: + pre_opt.append(RemoveResetInZeroState()) + return pre_opt + + +def generate_translation_passmanager( + target, + basis_gates=None, + method="translator", + approximation_degree=None, + coupling_map=None, + backend_props=None, + unitary_synthesis_method="default", + unitary_synthesis_plugin_config=None, +): + """Generate a basis translation :class:`~qiskit.transpiler.PassManager` + + Args: + target (Target): the :class:`~.Target` object representing the backend + basis_gates (list): A list of str gate names that represent the basis + gates on the backend target + method (str): The basis translation method to use + approximation_degree (float): The heuristic approximation degree to + use. Can be between 0 and 1. + coupling_map (CouplingMap): the coupling map of the backend + in case synthesis is done on a physical circuit. The + directionality of the coupling_map will be taken into + account if pulse_optimize is True/None and natural_direction + is True/None. + unitary_synthesis_plugin_config (dict): The optional dictionary plugin + configuration, this is plugin specific refer to the specified plugin's + documenation for how to use. + backend_props (BackendProperties): Properties of a backend to + synthesize for (e.g. gate fidelities). + unitary_synthesis_method (str): The unitary synthesis method to use + + Returns: + PassManager: The basis translation pass manager + + Raises: + TranspilerError: If the ``method`` kwarg is not a valid value + """ + if method == "unroller": + unroll = [Unroller(basis_gates)] + elif method == "translator": + unroll = [ + # Use unitary synthesis for basis aware decomposition of + # UnitaryGates before custom unrolling + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + UnrollCustomDefinitions(sel, basis_gates), + BasisTranslator(sel, basis_gates, target), + ] + elif method == "synthesis": + unroll = [ + # # Use unitary synthesis for basis aware decomposition of + # UnitaryGates > 2q before collection + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + min_qubits=3, + target=target, + ), + Unroll3qOrMore(target=target, basis_gates=basis_gates), + Collect2qBlocks(), + Collect1qRuns(), + ConsolidateBlocks(basis_gates=basis_gates, target=target), + UnitarySynthesis( + basis_gates=basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + plugin_config=unitary_synthesis_plugin_config, + method=unitary_synthesis_method, + target=target, + ), + ] + else: + raise TranspilerError("Invalid translation method %s." % method) + return PassManager(unroll) + + +def generate_scheduling(instruction_durations, scheduling_method, timing_constraints, inst_map): + """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager` + + Args: + instruction_durations (dict): The dictionary of instruction durations + scheduling_method (str): The scheduling method to use, can either be + ``'asap'``/``'as_soon_as_possible'`` or + ``'alap'``/``'as_late_as_possible'`` + timing_constraints (TimingConstraints): Hardware time alignment restrictions. + inst_map (InstructionScheduleMap): Mapping object that maps gate to schedule. + + Returns: + PassManager: The scheduling pass manager + + Raises: + TranspilerError: If the ``scheduling_method`` kwarg is not a valid value + """ + scheduling = PassManager() + if inst_map and inst_map.has_custom_gate(): + scheduling.append(PulseGates(inst_map=inst_map)) + if scheduling_method: + # Do scheduling after unit conversion. + scheduler = { + "alap": ALAPScheduleAnalysis, + "as_late_as_possible": ALAPScheduleAnalysis, + "asap": ASAPScheduleAnalysis, + "as_soon_as_possible": ASAPScheduleAnalysis, + } + scheduling.append(TimeUnitConversion(instruction_durations)) + try: + scheduling.append(scheduler[scheduling_method](instruction_durations)) + except KeyError as ex: + raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex + elif instruction_durations: + # No scheduling. But do unit conversion for delays. + def _contains_delay(property_set): + return property_set["contains_delay"] + + scheduling.append(ContainsInstruction("delay")) + scheduling.append(TimeUnitConversion(instruction_durations), condition=_contains_delay) + if ( + timing_constraints.granularity != 1 + or timing_constraints.min_length != 1 + or timing_constraints.acquire_alignment != 1 + or timing_constraints.pulse_alignment != 1 + ): + # Run alignment analysis regardless of scheduling. + + def _require_alignment(property_set): + return property_set["reschedule_required"] + + scheduling.append( + InstructionDurationCheck( + acquire_alignment=timing_constraints.acquire_alignment, + pulse_alignment=timing_constraints.pulse_alignment, + ) + ) + scheduling.append( + ConstrainedReschedule( + acquire_alignment=timing_constraints.acquire_alignment, + pulse_alignment=timing_constraints.pulse_alignment, + ), + condition=_require_alignment, + ) + scheduling.append( + ValidatePulseGates( + granularity=timing_constraints.granularity, + min_length=timing_constraints.min_length, + ) + ) + if scheduling_method: + # Call padding pass if circuit is scheduled + scheduling.append(PadDelay()) + + return scheduling diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 3c881cc1b306..37aadc1c4452 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -18,47 +18,24 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import Collect1qRuns -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPScheduleAnalysis -from qiskit.transpiler.passes import ASAPScheduleAnalysis -from qiskit.transpiler.passes import ConstrainedReschedule -from qiskit.transpiler.passes import InstructionDurationCheck -from qiskit.transpiler.passes import ValidatePulseGates -from qiskit.transpiler.passes import PulseGates -from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error -from qiskit.transpiler.passes import ContainsInstruction - +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM -def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 0 pass manager: no explicit optimization other than mapping to backend. This pass manager applies the user-given initial layout. If none is given, a trivial @@ -68,10 +45,6 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: The pass manager then unrolls the circuit to the desired basis, and transforms the circuit to match the coupling map. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. @@ -98,21 +71,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config target = pass_manager_config.target - # 1. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - ] - - # 2. Choose an initial layout if not set by user (default: trivial layout) + # Choose an initial layout if not set by user (default: trivial layout) _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -129,27 +88,16 @@ def _choose_layout_condition(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - def _swap_needs_basis(property_set): - return _swap_condition(property_set) and routing_method == "toqm" - - _swap = [BarrierBeforeFinalMeasurements()] + toqm_pass = False + # Choose routing pass if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)] + routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler) elif routing_method == "toqm": HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO0, latencies_from_target @@ -157,162 +105,81 @@ def _swap_needs_basis(property_set): if initial_layout: raise TranspilerError("Initial layouts are not supported with TOQM-based routing.") + toqm_pass = True # Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap # does not yet support barriers. - _swap = [ - ToqmSwap( - coupling_map, - strategy=ToqmStrategyO0( - latencies_from_target( - coupling_map, instruction_durations, basis_gates, backend_properties, target - ) - ), - ) - ] + routing_pass = ToqmSwap( + coupling_map, + strategy=ToqmStrategyO0( + latencies_from_target( + coupling_map, instruction_durations, basis_gates, backend_properties, target + ) + ), + ) elif routing_method == "none": - _swap += [ - Error( - msg=( - "No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}" - ), - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - UnrollCustomDefinitions(sel, basis_gates), - BasisTranslator(sel, basis_gates, target), - ] - elif translation_method == "synthesis": - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - Collect1qRuns(), - ConsolidateBlocks(basis_gates=basis_gates, target=target), - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map, target)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map, target)] - + unroll_3q = None # Build pass manager - pm0 = PassManager() if coupling_map or initial_layout: - pm0.append(_given_layout) - pm0.append(_unroll3q) - pm0.append(_choose_layout, condition=_choose_layout_condition) - pm0.append(_embed) - pm0.append(_swap_check) - pm0.append(_unroll, condition=_swap_needs_basis) - pm0.append(_swap, condition=_swap_condition) - pm0.append(_unroll) - if (coupling_map and not coupling_map.is_symmetric) or ( - target is not None and target.get_non_global_operation_names(strict_direction=True) - ): - pm0.append(_direction_check) - pm0.append(_direction, condition=_direction_condition) - pm0.append(_unroll) - if inst_map and inst_map.has_custom_gate(): - pm0.append(PulseGates(inst_map=inst_map)) - - # 7. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - # Apply alignment analysis regardless of scheduling for delay validation. - if scheduling_method: - # Do scheduling after unit conversion. - scheduler = { - "alap": ALAPScheduleAnalysis, - "as_late_as_possible": ALAPScheduleAnalysis, - "asap": ASAPScheduleAnalysis, - "as_soon_as_possible": ASAPScheduleAnalysis, - } - pm0.append(TimeUnitConversion(instruction_durations)) - try: - pm0.append(scheduler[scheduling_method](instruction_durations)) - except KeyError as ex: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex - elif instruction_durations: - # No scheduling. But do unit conversion for delays. - def _contains_delay(property_set): - return property_set["contains_delay"] - - pm0.append(ContainsInstruction("delay")) - pm0.append(TimeUnitConversion(instruction_durations), condition=_contains_delay) - if ( - timing_constraints.granularity != 1 - or timing_constraints.min_length != 1 - or timing_constraints.acquire_alignment != 1 - or timing_constraints.pulse_alignment != 1 - ): - # Run alignment analysis regardless of scheduling. - - def _require_alignment(property_set): - return property_set["reschedule_required"] - - pm0.append( - InstructionDurationCheck( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ) - ) - pm0.append( - ConstrainedReschedule( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ), - condition=_require_alignment, + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, ) - pm0.append( - ValidatePulseGates( - granularity=timing_constraints.granularity, - min_length=timing_constraints.min_length, - ) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout, condition=_choose_layout_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=not toqm_pass, ) - if scheduling_method: - # Call padding pass if circuit is scheduled - pm0.append(PadDelay()) + else: + layout = None + routing = None + translation = common.generate_translation_passmanager( + target, + basis_gates, + translation_method, + approximation_degree, + coupling_map, + backend_properties, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + pre_routing = None + if toqm_pass: + pre_routing = translation - return pm0 + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None and target.get_non_global_operation_names(strict_direction=True) + ): + pre_opt = common.generate_pre_op_passmanager(target, coupling_map) + pre_opt += translation + else: + pre_opt = None + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + + return StagedPassManager( + init=unroll_3q, + layout=layout, + pre_routing=pre_routing, + routing=routing, + translation=translation, + pre_optimization=pre_opt, + scheduling=sched, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index e24b40f7d875..5da52fb23052 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -18,57 +18,33 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore from qiskit.transpiler.passes import CXCancellation -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout -from qiskit.transpiler.passes import VF2PostLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements -from qiskit.transpiler.passes import Layout2qDistance from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size -from qiskit.transpiler.passes import RemoveResetInZeroState from qiskit.transpiler.passes import Optimize1qGatesDecomposition -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPScheduleAnalysis -from qiskit.transpiler.passes import ASAPScheduleAnalysis -from qiskit.transpiler.passes import ConstrainedReschedule -from qiskit.transpiler.passes import InstructionDurationCheck -from qiskit.transpiler.passes import ValidatePulseGates -from qiskit.transpiler.passes import PulseGates -from qiskit.transpiler.passes import PadDelay +from qiskit.transpiler.passes import Layout2qDistance from qiskit.transpiler.passes import Error -from qiskit.transpiler.passes import ContainsInstruction +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason -from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM -def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 1 pass manager: light optimization by simple adjacent gate collapsing. This pass manager applies the user-given initial layout. If none is given, @@ -80,10 +56,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: circuit to match the coupling map. Finally, optimizations in the form of adjacent gate collapse and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. @@ -110,7 +82,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: timing_constraints = pass_manager_config.timing_constraints or TimingConstraints() target = pass_manager_config.target - # 1. Use trivial layout if no layout given if that isn't perfect use vf2 layout + # Use trivial layout if no layout given _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -127,6 +99,7 @@ def _trivial_not_perfect(property_set): return True return False + # Use a better layout on densely connected qubits, if circuit needs swaps def _vf2_match_not_found(property_set): # If a layout hasn't been set by the time we run vf2 layout we need to # run layout @@ -162,21 +135,6 @@ def _vf2_match_not_found(property_set): ) ) - # 2. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - ] - - # 3. Use a better layout on densely connected qubits, if circuit needs swaps if layout_method == "trivial": _improve_layout = TrivialLayout(coupling_map) elif layout_method == "dense": @@ -188,27 +146,15 @@ def _vf2_match_not_found(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 4. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 5. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - def _swap_needs_basis(property_set): - return _swap_condition(property_set) and routing_method == "toqm" - - _swap = [BarrierBeforeFinalMeasurements()] + toqm_pass = False if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)] + routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler) elif routing_method == "toqm": HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO1, latencies_from_target @@ -216,94 +162,28 @@ def _swap_needs_basis(property_set): if initial_layout: raise TranspilerError("Initial layouts are not supported with TOQM-based routing.") + toqm_pass = True # Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap # does not yet support barriers. - _swap = [ - ToqmSwap( - coupling_map, - strategy=ToqmStrategyO1( - latencies_from_target( - coupling_map, instruction_durations, basis_gates, backend_properties, target - ) - ), - ) - ] + routing_pass = ToqmSwap( + coupling_map, + strategy=ToqmStrategyO1( + latencies_from_target( + coupling_map, instruction_durations, basis_gates, backend_properties, target + ) + ), + ) elif routing_method == "none": - _swap += [ - Error( - msg=( - "No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}" - ), - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 6. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates before - # custom unrolling - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method, - backend_props=backend_properties, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - UnrollCustomDefinitions(sel, basis_gates), - BasisTranslator(sel, basis_gates, target), - ] - elif translation_method == "synthesis": - _unroll = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates before - # collection - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method, - backend_props=backend_properties, - min_qubits=3, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates, target=target), - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - method=unitary_synthesis_method, - backend_props=backend_properties, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 7. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map, target)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map, target)] - - # 8. Remove zero-state reset - _reset = RemoveResetInZeroState() - - # 9. Merge 1q rotations and cancel CNOT gates iteratively until no more change in depth - # or size of circuit + # Build optimization loop: merge 1q rotations and cancel CNOT gates iteratively + # until no more change in depth _depth_check = [Depth(), FixedPoint("depth")] _size_check = [Size(), FixedPoint("size")] @@ -312,125 +192,74 @@ def _opt_control(property_set): _opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()] - # Build pass manager - pm1 = PassManager() + unroll_3q = None + # Build full pass manager if coupling_map or initial_layout: - pm1.append(_given_layout) - pm1.append(_unroll3q) - pm1.append(_choose_layout_0, condition=_choose_layout_condition) - pm1.append(_choose_layout_1, condition=_trivial_not_perfect) - pm1.append(_improve_layout, condition=_vf2_match_not_found) - pm1.append(_embed) - pm1.append(_swap_check) - pm1.append(_unroll, condition=_swap_needs_basis) - pm1.append(_swap, condition=_swap_condition) - if ( - (coupling_map and backend_properties) - and initial_layout is None - and pass_manager_config.layout_method is None - ): - - def _run_post_layout_condition(property_set): - if _trivial_not_perfect(property_set): - vf2_stop_reason = property_set["VF2Layout_stop_reason"] - if ( - vf2_stop_reason is None - or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND - ): - return True - return False - - def _apply_post_layout_condition(property_set): - # if VF2 Post layout found a solution we need to re-apply the better - # layout. Otherwise we can skip apply layout. - if ( - property_set["VF2PostLayout_stop_reason"] is not None - and property_set["VF2PostLayout_stop_reason"] - is VF2PostLayoutStopReason.SOLUTION_FOUND - ): - return True - return False + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout_0, condition=_choose_layout_condition) + layout.append(_choose_layout_1, condition=_trivial_not_perfect) + layout.append(_improve_layout, condition=_vf2_match_not_found) + layout += common.generate_embed_passmanager(coupling_map) + vf2_call_limit = None + if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None: + vf2_call_limit = int(5e4) # Set call limit to ~100ms with retworkx 0.10.2 + routing = common.generate_routing_passmanager( + routing_pass, + target, + coupling_map, + vf2_call_limit=vf2_call_limit, + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, + check_trivial=True, + use_barrier_before_measurement=not toqm_pass, + ) + else: + layout = None + routing = None + translation = common.generate_translation_passmanager( + target, + basis_gates, + translation_method, + approximation_degree, + coupling_map, + backend_properties, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + pre_routing = None + if toqm_pass: + pre_routing = translation - pm1.append( - VF2PostLayout( - target, - coupling_map, - backend_properties, - seed_transpiler, - call_limit=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2 - strict_direction=False, - ), - condition=_run_post_layout_condition, - ) - pm1.append(ApplyLayout(), condition=_apply_post_layout_condition) - pm1.append(_unroll) if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) ): - pm1.append(_direction_check) - pm1.append(_direction, condition=_direction_condition) - pm1.append(_reset) - pm1.append(_depth_check + _size_check) - pm1.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control) - - if inst_map and inst_map.has_custom_gate(): - pm1.append(PulseGates(inst_map=inst_map)) - - # 10. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - # Apply alignment analysis regardless of scheduling for delay validation. - if scheduling_method: - # Do scheduling after unit conversion. - scheduler = { - "alap": ALAPScheduleAnalysis, - "as_late_as_possible": ALAPScheduleAnalysis, - "asap": ASAPScheduleAnalysis, - "as_soon_as_possible": ASAPScheduleAnalysis, - } - pm1.append(TimeUnitConversion(instruction_durations)) - try: - pm1.append(scheduler[scheduling_method](instruction_durations)) - except KeyError as ex: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex - elif instruction_durations: - # No scheduling. But do unit conversion for delays. - def _contains_delay(property_set): - return property_set["contains_delay"] - - pm1.append(ContainsInstruction("delay")) - pm1.append(TimeUnitConversion(instruction_durations), condition=_contains_delay) - if ( - timing_constraints.granularity != 1 - or timing_constraints.min_length != 1 - or timing_constraints.acquire_alignment != 1 - or timing_constraints.pulse_alignment != 1 - ): - # Run alignment analysis regardless of scheduling. - - def _require_alignment(property_set): - return property_set["reschedule_required"] - - pm1.append( - InstructionDurationCheck( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ) - ) - pm1.append( - ConstrainedReschedule( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ), - condition=_require_alignment, - ) - pm1.append( - ValidatePulseGates( - granularity=timing_constraints.granularity, - min_length=timing_constraints.min_length, - ) - ) - if scheduling_method: - # Call padding pass if circuit is scheduled - pm1.append(PadDelay()) + pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) + optimization = PassManager() + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + optimization.append(_depth_check + _size_check) + opt_loop = _opt + unroll + _depth_check + _size_check + optimization.append(opt_loop, do_while=_opt_control) + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) - return pm1 + return StagedPassManager( + init=unroll_3q, + layout=layout, + pre_routing=pre_routing, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + scheduling=sched, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 5c0705745584..c2ee13ad8500 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -19,56 +19,32 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout -from qiskit.transpiler.passes import VF2PostLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size -from qiskit.transpiler.passes import RemoveResetInZeroState from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import CommutativeCancellation -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPScheduleAnalysis -from qiskit.transpiler.passes import ASAPScheduleAnalysis -from qiskit.transpiler.passes import ConstrainedReschedule -from qiskit.transpiler.passes import InstructionDurationCheck -from qiskit.transpiler.passes import ValidatePulseGates -from qiskit.transpiler.passes import PulseGates -from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error -from qiskit.transpiler.passes import ContainsInstruction +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason -from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM -def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 2 pass manager: medium optimization by initial layout selection and gate cancellation using commutativity rules. @@ -82,10 +58,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Finally, optimizations in the form of commutative gate cancellation and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. @@ -112,21 +84,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config target = pass_manager_config.target - # 1. Unroll to 1q or 2q gates - _unroll3q = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - min_qubits=3, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - ] - - # 2. Search for a perfect layout, or choose a dense layout, if no layout given + # Search for a perfect layout, or choose a dense layout, if no layout given _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -147,7 +105,7 @@ def _vf2_match_not_found(property_set): return True return False - # 2a. Try using VF2 layout to find a perfect layout + # Try using VF2 layout to find a perfect layout _choose_layout_0 = ( [] if pass_manager_config.layout_method @@ -160,7 +118,6 @@ def _vf2_match_not_found(property_set): ) ) - # 2b. if VF2 layout doesn't converge on a solution use layout_method (dense) to get a layout if layout_method == "trivial": _choose_layout_1 = TrivialLayout(coupling_map) elif layout_method == "dense": @@ -172,123 +129,44 @@ def _vf2_match_not_found(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - def _swap_needs_basis(property_set): - return _swap_condition(property_set) and routing_method == "toqm" - - _swap = [BarrierBeforeFinalMeasurements()] + toqm_pass = False if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)] + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=5) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) elif routing_method == "toqm": HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO2, latencies_from_target if initial_layout: raise TranspilerError("Initial layouts are not supported with TOQM-based routing.") + toqm_pass = True # Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap # does not yet support barriers. - _swap = [ - ToqmSwap( - coupling_map, - strategy=ToqmStrategyO2( - latencies_from_target( - coupling_map, instruction_durations, basis_gates, backend_properties, target - ) - ), - ) - ] + routing_pass = ToqmSwap( + coupling_map, + strategy=ToqmStrategyO2( + latencies_from_target( + coupling_map, instruction_durations, basis_gates, backend_properties, target + ) + ), + ) elif routing_method == "none": - _swap += [ - Error( - msg=( - "No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}" - ), - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates before - # custom unrolling - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - UnrollCustomDefinitions(sel, basis_gates), - BasisTranslator(sel, basis_gates, target), - ] - elif translation_method == "synthesis": - _unroll = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates before - # collection - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - min_qubits=3, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates, target=target), - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map, target)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map, target)] - - # 7. Remove zero-state reset - _reset = RemoveResetInZeroState() - - # 8. 1q rotation merge and commutative cancellation iteratively until no more change in depth - # and size + # Build optimization loop: 1q rotation merge and commutative cancellation iteratively until + # no more change in depth _depth_check = [Depth(), FixedPoint("depth")] _size_check = [Size(), FixedPoint("size")] @@ -300,125 +178,70 @@ def _opt_control(property_set): CommutativeCancellation(basis_gates=basis_gates), ] + unroll_3q = None # Build pass manager - pm2 = PassManager() if coupling_map or initial_layout: - pm2.append(_given_layout) - pm2.append(_unroll3q) - pm2.append(_choose_layout_0, condition=_choose_layout_condition) - pm2.append(_choose_layout_1, condition=_vf2_match_not_found) - pm2.append(_embed) - pm2.append(_swap_check) - pm2.append(_unroll, condition=_swap_needs_basis) - pm2.append(_swap, condition=_swap_condition) - if ( - (coupling_map and backend_properties) - and initial_layout is None - and pass_manager_config.layout_method is None - ): - - def _run_post_layout_condition(property_set): - vf2_stop_reason = property_set["VF2Layout_stop_reason"] - if ( - vf2_stop_reason is not None - and vf2_stop_reason == VF2LayoutStopReason.SOLUTION_FOUND - ): - return False - return True - - def _apply_post_layout_condition(property_set): - # if VF2 Post layout found a solution we need to re-apply the better - # layout. Otherwise we can skip apply layout. - if ( - property_set["VF2PostLayout_stop_reason"] is not None - and property_set["VF2PostLayout_stop_reason"] - is VF2PostLayoutStopReason.SOLUTION_FOUND - ): - return True - return False - - pm2.append( - VF2PostLayout( - target, - coupling_map, - backend_properties, - seed_transpiler, - call_limit=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2 - strict_direction=False, - ), - condition=_run_post_layout_condition, - ) - pm2.append(ApplyLayout(), condition=_apply_post_layout_condition) - - pm2.append(_unroll) + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout_0, condition=_choose_layout_condition) + layout.append(_choose_layout_1, condition=_vf2_match_not_found) + layout += common.generate_embed_passmanager(coupling_map) + vf2_call_limit = None + if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None: + vf2_call_limit = int(5e6) # Set call limit to ~10 sec with retworkx 0.10.2 + routing = common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + vf2_call_limit=vf2_call_limit, + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=not toqm_pass, + ) + else: + layout = None + routing = None + translation = common.generate_translation_passmanager( + target, + basis_gates, + translation_method, + approximation_degree, + coupling_map, + backend_properties, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + pre_routing = None + if toqm_pass: + pre_routing = translation if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) ): - pm2.append(_direction_check) - pm2.append(_direction, condition=_direction_condition) - pm2.append(_reset) - - pm2.append(_depth_check + _size_check) - pm2.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control) - - if inst_map and inst_map.has_custom_gate(): - pm2.append(PulseGates(inst_map=inst_map)) - - # 9. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - # Apply alignment analysis regardless of scheduling for delay validation. - if scheduling_method: - # Do scheduling after unit conversion. - scheduler = { - "alap": ALAPScheduleAnalysis, - "as_late_as_possible": ALAPScheduleAnalysis, - "asap": ASAPScheduleAnalysis, - "as_soon_as_possible": ASAPScheduleAnalysis, - } - pm2.append(TimeUnitConversion(instruction_durations)) - try: - pm2.append(scheduler[scheduling_method](instruction_durations)) - except KeyError as ex: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex - elif instruction_durations: - # No scheduling. But do unit conversion for delays. - def _contains_delay(property_set): - return property_set["contains_delay"] - - pm2.append(ContainsInstruction("delay")) - pm2.append(TimeUnitConversion(instruction_durations), condition=_contains_delay) - if ( - timing_constraints.granularity != 1 - or timing_constraints.min_length != 1 - or timing_constraints.acquire_alignment != 1 - or timing_constraints.pulse_alignment != 1 - ): - # Run alignment analysis regardless of scheduling. - - def _require_alignment(property_set): - return property_set["reschedule_required"] - - pm2.append( - InstructionDurationCheck( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ) - ) - pm2.append( - ConstrainedReschedule( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ), - condition=_require_alignment, - ) - pm2.append( - ValidatePulseGates( - granularity=timing_constraints.granularity, - min_length=timing_constraints.min_length, - ) - ) - if scheduling_method: - # Call padding pass if circuit is scheduled - pm2.append(PadDelay()) - - return pm2 + pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) + optimization = PassManager() + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + optimization.append(_depth_check + _size_check) + opt_loop = _opt + unroll + _depth_check + _size_check + optimization.append(opt_loop, do_while=_opt_control) + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + return StagedPassManager( + init=unroll_3q, + layout=layout, + pre_routing=pre_routing, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + scheduling=sched, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 0f678c902cd2..7a0f22319ecb 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -20,27 +20,18 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout -from qiskit.transpiler.passes import VF2PostLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size @@ -52,26 +43,15 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPScheduleAnalysis -from qiskit.transpiler.passes import ASAPScheduleAnalysis -from qiskit.transpiler.passes import ConstrainedReschedule -from qiskit.transpiler.passes import InstructionDurationCheck -from qiskit.transpiler.passes import ValidatePulseGates -from qiskit.transpiler.passes import PulseGates -from qiskit.transpiler.passes import PadDelay from qiskit.transpiler.passes import Error -from qiskit.transpiler.passes import ContainsInstruction +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason -from qiskit.transpiler.passes.layout.vf2_post_layout import VF2PostLayoutStopReason from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM -def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules and unitary synthesis. @@ -85,10 +65,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: Finally, optimizations in the form of commutative gate cancellation, resynthesis of two-qubit unitary blocks, and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. @@ -115,21 +91,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config target = pass_manager_config.target - # 1. Unroll to 1q or 2q gates - _unroll3q = [ - # Use unitary synthesis for basis aware decomposition of UnitaryGates - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - min_qubits=3, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - ] - - # 2. Layout on good qubits if calibration info available, otherwise on dense links + # Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -174,27 +136,15 @@ def _vf2_match_not_found(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - def _swap_needs_basis(property_set): - return _swap_condition(property_set) and routing_method == "toqm" - - _swap = [BarrierBeforeFinalMeasurements()] + toqm_pass = False if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) elif routing_method == "toqm": HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO3, latencies_from_target @@ -202,86 +152,26 @@ def _swap_needs_basis(property_set): if initial_layout: raise TranspilerError("Initial layouts are not supported with TOQM-based routing.") + toqm_pass = True # Note: BarrierBeforeFinalMeasurements is skipped intentionally since ToqmSwap # does not yet support barriers. - _swap = [ - ToqmSwap( - coupling_map, - strategy=ToqmStrategyO3( - latencies_from_target( - coupling_map, instruction_durations, basis_gates, backend_properties, target - ) - ), - ) - ] + routing_pass = ToqmSwap( + coupling_map, + strategy=ToqmStrategyO3( + latencies_from_target( + coupling_map, instruction_durations, basis_gates, backend_properties, target + ) + ), + ) elif routing_method == "none": - _swap += [ - Error( - msg=( - "No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}" - ), - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - plugin_config=unitary_synthesis_plugin_config, - method=unitary_synthesis_method, - target=target, - ), - UnrollCustomDefinitions(sel, basis_gates), - BasisTranslator(sel, basis_gates, target), - ] - elif translation_method == "synthesis": - _unroll = [ - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - min_qubits=3, - target=target, - ), - Unroll3qOrMore(target=target, basis_gates=basis_gates), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates, target=target), - UnitarySynthesis( - basis_gates, - approximation_degree=approximation_degree, - coupling_map=coupling_map, - backend_props=backend_properties, - method=unitary_synthesis_method, - plugin_config=unitary_synthesis_plugin_config, - target=target, - ), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any CX direction mismatch - _direction_check = [CheckGateDirection(coupling_map, target)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map, target)] - # 8. Optimize iteratively until no more change in depth. Removes useless gates # after reset and before measure, commutes gates and optimizes contiguous blocks. _depth_check = [Depth(), FixedPoint("depth")] @@ -290,10 +180,6 @@ def _direction_condition(property_set): def _opt_control(property_set): return (not property_set["depth_fixed_point"]) or (not property_set["size_fixed_point"]) - _reset = [RemoveResetInZeroState()] - - _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] - _opt = [ Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates, target=target), @@ -311,137 +197,87 @@ def _opt_control(property_set): ] # Build pass manager - pm3 = PassManager() - pm3.append(_unroll3q) - pm3.append(_reset + _meas) + init = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + init.append(RemoveResetInZeroState()) + init.append(OptimizeSwapBeforeMeasure()) + init.append(RemoveDiagonalGatesBeforeMeasure()) if coupling_map or initial_layout: - pm3.append(_given_layout) - pm3.append(_choose_layout_0, condition=_choose_layout_condition) - pm3.append(_choose_layout_1, condition=_vf2_match_not_found) - pm3.append(_embed) - pm3.append(_swap_check) - pm3.append(_unroll, condition=_swap_needs_basis) - pm3.append(_swap, condition=_swap_condition) - if ( - (coupling_map and backend_properties) - and initial_layout is None - and pass_manager_config.layout_method is None - ): - - def _run_post_layout_condition(property_set): - vf2_stop_reason = property_set["VF2Layout_stop_reason"] - if ( - vf2_stop_reason is not None - and vf2_stop_reason == VF2LayoutStopReason.SOLUTION_FOUND - ): - return False - return True - - def _apply_post_layout_condition(property_set): - # if VF2 Post layout found a solution we need to re-apply the better - # layout. Otherwise we can skip apply layout. - if ( - property_set["VF2PostLayout_stop_reason"] is not None - and property_set["VF2PostLayout_stop_reason"] - is VF2PostLayoutStopReason.SOLUTION_FOUND - ): - return True - return False - - pm3.append( - VF2PostLayout( - target, - coupling_map, - backend_properties, - seed_transpiler, - call_limit=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2 - strict_direction=False, - ), - condition=_run_post_layout_condition, - ) - pm3.append(ApplyLayout(), condition=_apply_post_layout_condition) - - pm3.append(_unroll) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout_0, condition=_choose_layout_condition) + layout.append(_choose_layout_1, condition=_vf2_match_not_found) + layout += common.generate_embed_passmanager(coupling_map) + vf2_call_limit = None + if pass_manager_config.layout_method is None and pass_manager_config.initial_layout is None: + vf2_call_limit = int(3e7) # Set call limit to ~60 sec with retworkx 0.10.2 + routing = common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + vf2_call_limit=vf2_call_limit, + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=not toqm_pass, + ) + else: + layout = None + routing = None + translation = common.generate_translation_passmanager( + target, + basis_gates, + translation_method, + approximation_degree, + coupling_map, + backend_properties, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) + pre_routing = None + if toqm_pass: + pre_routing = translation + optimization = PassManager() + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + optimization.append(_depth_check + _size_check) if (coupling_map and not coupling_map.is_symmetric) or ( target is not None and target.get_non_global_operation_names(strict_direction=True) ): - pm3.append(_direction_check) - pm3.append(_direction, condition=_direction_condition) - pm3.append(_reset) - pm3.append(_depth_check + _size_check) + pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) + _direction = [ + pass_ + for x in common.generate_pre_op_passmanager(target, coupling_map).passes() + for pass_ in x["passes"] + ] # For transpiling to a target we need to run GateDirection in the # optimization loop to correct for incorrect directions that might be # inserted by UnitarySynthesis which is direction aware but only via # the coupling map which with a target doesn't give a full picture if target is not None: - pm3.append( - _opt + _unroll + _depth_check + _size_check + _direction, do_while=_opt_control + optimization.append( + _opt + unroll + _depth_check + _size_check + _direction, do_while=_opt_control ) else: - pm3.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control) + optimization.append(_opt + unroll + _depth_check + _size_check, do_while=_opt_control) else: - pm3.append(_reset) - pm3.append(_depth_check + _size_check) - pm3.append(_opt + _unroll + _depth_check + _size_check, do_while=_opt_control) - - if inst_map and inst_map.has_custom_gate(): - pm3.append(PulseGates(inst_map=inst_map)) - - # 9. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - # Apply alignment analysis regardless of scheduling for delay validation. - if scheduling_method: - # Do scheduling after unit conversion. - scheduler = { - "alap": ALAPScheduleAnalysis, - "as_late_as_possible": ALAPScheduleAnalysis, - "asap": ASAPScheduleAnalysis, - "as_soon_as_possible": ASAPScheduleAnalysis, - } - pm3.append(TimeUnitConversion(instruction_durations)) - try: - pm3.append(scheduler[scheduling_method](instruction_durations)) - except KeyError as ex: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) from ex - elif instruction_durations: - # No scheduling. But do unit conversion for delays. - def _contains_delay(property_set): - return property_set["contains_delay"] - - pm3.append(ContainsInstruction("delay")) - pm3.append(TimeUnitConversion(instruction_durations), condition=_contains_delay) - if ( - timing_constraints.granularity != 1 - or timing_constraints.min_length != 1 - or timing_constraints.acquire_alignment != 1 - or timing_constraints.pulse_alignment != 1 - ): - # Run alignment analysis regardless of scheduling. - - def _require_alignment(property_set): - return property_set["reschedule_required"] - - pm3.append( - InstructionDurationCheck( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ) - ) - pm3.append( - ConstrainedReschedule( - acquire_alignment=timing_constraints.acquire_alignment, - pulse_alignment=timing_constraints.pulse_alignment, - ), - condition=_require_alignment, - ) - pm3.append( - ValidatePulseGates( - granularity=timing_constraints.granularity, - min_length=timing_constraints.min_length, - ) - ) - if scheduling_method: - # Call padding pass if circuit is scheduled - pm3.append(PadDelay()) - - return pm3 + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) + optimization.append(_opt + unroll + _depth_check + _size_check, do_while=_opt_control) + opt_loop = _depth_check + _opt + unroll + optimization.append(opt_loop, do_while=_opt_control) + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + return StagedPassManager( + init=init, + layout=layout, + pre_routing=pre_routing, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + scheduling=sched, + ) diff --git a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml new file mode 100644 index 000000000000..be59f4545743 --- /dev/null +++ b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Added a new class, :class:`qiskit.transpiler.StagedPassManager`, which is + a :class:`~qiskit.transpiler.PassManager` subclass that has a pipeline + with defined phases to perform circuit compilation. Each phase is a + :class:`~qiskit.transpiler.PassManager` object that will get executed + in a fixed order. For example:: + + from qiskit.transpiler.passes import * + from qiskit.transpiler import PassManager, StagedPassManager + + basis_gates = ['rx', 'ry', 'rxx'] + init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()]) + translate = PassManager([Collect2qBlocks(), + ConsolidateBlocks(basis_gates=basis_gates), + UnitarySynthesis(basis_gates)]) + + staged_pm = StagedPassManager(stages=['init', 'translation'], init=init, translation=translate) diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py new file mode 100644 index 000000000000..79045b2cd86d --- /dev/null +++ b/test/python/transpiler/test_staged_passmanager.py @@ -0,0 +1,94 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022 +# +# 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,missing-class-docstring + +"""Test the staged passmanager logic""" + +from qiskit.transpiler import PassManager, StagedPassManager +from qiskit.transpiler.passes import Optimize1qGates, Unroller, Depth +from qiskit.test import QiskitTestCase + + +class TestStagedPassManager(QiskitTestCase): + def test_default_stages(self): + spm = StagedPassManager() + self.assertEqual( + spm.stages, ["init", "layout", "routing", "translation", "optimization", "scheduling"] + ) + spm = StagedPassManager( + init=PassManager([Optimize1qGates()]), + routing=PassManager([Unroller(["u", "cx"])]), + scheduling=PassManager([Depth()]), + ) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Unroller", "Depth"], + ) + + def test_inplace_edit(self): + spm = StagedPassManager(stages=["single_stage"]) + spm.single_stage = PassManager([Optimize1qGates(), Depth()]) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Depth"], + ) + spm.single_stage.append(Unroller(["u"])) + spm.single_stage.append(Depth()) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Depth", "Unroller", "Depth"], + ) + + def test_invalid_stage(self): + with self.assertRaises(AttributeError): + StagedPassManager(stages=["init"], translation=PassManager()) + + def test_pre_phase_is_valid_stage(self): + spm = StagedPassManager(stages=["init"], pre_init=PassManager([Depth()])) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Depth"], + ) + + def test_append_extend_not_implemented(self): + spm = StagedPassManager() + with self.assertRaises(NotImplementedError): + spm.append(Depth()) + with self.assertRaises(NotImplementedError): + spm += PassManager() + + def test_invalid_stages(self): + invalid_stages = [ + "two words", + "two-words", + "two+words", + "two&words", + "[two_words]", + "", + "{two_words}", + "(two_words)", + "two^words", + "two_words!", + "^two_words", + "@two_words", + "two~words", + r"two\words", + "two/words", + ] + all_stages = invalid_stages + ["two_words", "init"] + + with self.assertRaises(ValueError) as err: + StagedPassManager(all_stages) + message = str(err.exception) + for stage in invalid_stages: + self.assertIn(stage, message) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 3956da41737f..4a2f47bcdd91 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -57,7 +57,7 @@ def generate_full_circuit(): def generate_unitary_gate_circuit(): """Generate a circuit with a unitary gate.""" - unitary_circuit = QuantumCircuit(5) + unitary_circuit = QuantumCircuit(5, name="unitary_circuit") unitary_circuit.unitary(random_unitary(32, seed=100), [0, 1, 2, 3, 4]) unitary_circuit.measure_all() return unitary_circuit @@ -67,7 +67,7 @@ def generate_random_circuits(): """Generate multiple random circuits.""" random_circuits = [] for i in range(1, 15): - qc = QuantumCircuit(i) + qc = QuantumCircuit(i, name=f"random_circuit-{i}") qc.h(0) if i > 1: for j in range(i - 1): @@ -84,7 +84,9 @@ def generate_random_circuits(): def generate_string_parameters(): """Generate a circuit from pauli tensor opflow.""" - return (X ^ Y ^ Z).to_circuit_op().to_circuit() + op_circuit = (X ^ Y ^ Z).to_circuit_op().to_circuit() + op_circuit.name = "X^Y^Z" + return op_circuit def generate_register_edge_cases(): @@ -92,7 +94,7 @@ def generate_register_edge_cases(): register_edge_cases = [] # Circuit with shared bits in a register qubits = [Qubit() for _ in range(5)] - shared_qc = QuantumCircuit() + shared_qc = QuantumCircuit(name="shared_bits") shared_qc.add_bits(qubits) shared_qr = QuantumRegister(bits=qubits) shared_qc.add_register(shared_qr) @@ -109,7 +111,7 @@ def generate_register_edge_cases(): qr = QuantumRegister(name="bar", bits=qr[:3] + [Qubit(), Qubit()]) cr = ClassicalRegister(5, "foo") cr = ClassicalRegister(name="classical_bar", bits=cr[:3] + [Clbit(), Clbit()]) - hybrid_qc = QuantumCircuit(qr, cr) + hybrid_qc = QuantumCircuit(qr, cr, name="mix_standalone_bits_registers") hybrid_qc.h(0) hybrid_qc.cx(0, 1) hybrid_qc.cx(0, 2) @@ -120,7 +122,7 @@ def generate_register_edge_cases(): # Circuit with mixed standalone and shared registers qubits = [Qubit() for _ in range(5)] clbits = [Clbit() for _ in range(5)] - mixed_qc = QuantumCircuit() + mixed_qc = QuantumCircuit(name="mix_standalone_bits_with_registers") mixed_qc.add_bits(qubits) mixed_qc.add_bits(clbits) qr = QuantumRegister(bits=qubits) @@ -140,7 +142,7 @@ def generate_register_edge_cases(): qr_standalone = QuantumRegister(2, "standalone") qubits = [Qubit() for _ in range(5)] clbits = [Clbit() for _ in range(5)] - ooo_qc = QuantumCircuit() + ooo_qc = QuantumCircuit(name="out_of_order_bits") ooo_qc.add_bits(qubits) ooo_qc.add_bits(clbits) random.seed(42) @@ -166,7 +168,7 @@ def generate_register_edge_cases(): def generate_parameterized_circuit(): """Generate a circuit with parameters and parameter expressions.""" - param_circuit = QuantumCircuit(1) + param_circuit = QuantumCircuit(1, name="parameterized") theta = Parameter("theta") lam = Parameter("λ") theta_pi = 3.14159 * theta @@ -194,7 +196,7 @@ def generate_qft_circuit(): ) qubits = 3 - qft_circ = QuantumCircuit(qubits, qubits) + qft_circ = QuantumCircuit(qubits, qubits, name="QFT") qft_circ.initialize(state) qft_circ.append(QFT(qubits), range(qubits)) qft_circ.measure(range(qubits), range(qubits)) @@ -208,7 +210,7 @@ def generate_param_phase(): theta = Parameter("theta") phi = Parameter("phi") sum_param = theta + phi - qc = QuantumCircuit(5, 1, global_phase=sum_param) + qc = QuantumCircuit(5, 1, global_phase=sum_param, name="parameter_phase") qc.h(0) for i in range(4): qc.cx(i, i + 1) @@ -224,7 +226,7 @@ def generate_param_phase(): output_circuits.append(qc) # Generate circuit with Parameter global phase theta = Parameter("theta") - bell_qc = QuantumCircuit(2, global_phase=theta) + bell_qc = QuantumCircuit(2, global_phase=theta, name="bell_param_global_phase") bell_qc.h(0) bell_qc.cx(0, 1) bell_qc.measure_all() @@ -246,7 +248,7 @@ def generate_single_clbit_condition_teleportation(): # pylint: disable=invalid- def generate_parameter_vector(): """Generate tests for parameter vector element ordering.""" - qc = QuantumCircuit(11) + qc = QuantumCircuit(11, name="parameter_vector") input_params = ParameterVector("x_par", 11) user_params = ParameterVector("θ_par", 11) for i, param in enumerate(user_params): @@ -258,7 +260,7 @@ def generate_parameter_vector(): def generate_parameter_vector_expression(): # pylint: disable=invalid-name """Generate tests for parameter vector element ordering.""" - qc = QuantumCircuit(7) + qc = QuantumCircuit(7, name="vector_expansion") entanglement = [[i, i + 1] for i in range(7 - 1)] input_params = ParameterVector("x_par", 14) user_params = ParameterVector("\u03B8_par", 1) @@ -284,7 +286,7 @@ def generate_evolution_gate(): synthesis = SuzukiTrotter() evo = PauliEvolutionGate([(Z ^ I) + (I ^ Z)] * 5, time=2.0, synthesis=synthesis) - qc = QuantumCircuit(2) + qc = QuantumCircuit(2, name="pauli_evolution_circuit") qc.append(evo, range(2)) return qc @@ -295,7 +297,7 @@ def generate_control_flow_circuits(): # If instruction circuits = [] - qc = QuantumCircuit(2, 2) + qc = QuantumCircuit(2, 2, name="control_flow") qc.h(0) qc.measure(0, 0) true_body = QuantumCircuit(1) @@ -305,7 +307,7 @@ def generate_control_flow_circuits(): qc.measure(1, 1) circuits.append(qc) # If else instruction - qc = QuantumCircuit(2, 2) + qc = QuantumCircuit(2, 2, name="if_else") qc.h(0) qc.measure(0, 0) false_body = QuantumCircuit(1) @@ -315,7 +317,7 @@ def generate_control_flow_circuits(): qc.measure(1, 1) circuits.append(qc) # While loop - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="while_loop") block = QuantumCircuit(2, 1) block.h(0) block.cx(0, 1) @@ -324,7 +326,7 @@ def generate_control_flow_circuits(): qc.append(while_loop, [0, 1], [0]) circuits.append(qc) # for loop range - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="for_loop") body = QuantumCircuit(2, 1) body.h(0) body.cx(0, 1) @@ -334,7 +336,7 @@ def generate_control_flow_circuits(): qc.append(for_loop_op, [0, 1], [0]) circuits.append(qc) # For loop iterator - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="for_loop_iterator") for_loop_op = ForLoopOp(iter(range(5)), None, body=body) qc.append(for_loop_op, [0, 1], [0]) circuits.append(qc) @@ -427,7 +429,7 @@ def assert_equal(reference, qpy, count, bind=None): sys.exit(1) # Don't compare name on bound circuits if bind is None and reference.name != qpy.name: - msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}" + msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}\n{reference}\n{qpy}" sys.stderr.write(msg) sys.exit(2) if reference.metadata != qpy.metadata: