diff --git a/docs/apidocs/terra.rst b/docs/apidocs/terra.rst index 5b765c7c95e7..9154a9420fc6 100644 --- a/docs/apidocs/terra.rst +++ b/docs/apidocs/terra.rst @@ -36,6 +36,7 @@ Qiskit Terra API Reference transpiler_passes transpiler_preset transpiler_plugins + transpiler_synthesis_plugins transpiler_builtin_plugins utils utils_mitigation diff --git a/docs/apidocs/transpiler_plugins.rst b/docs/apidocs/transpiler_plugins.rst index 33646fe2d58f..b5de3efc8ff6 100644 --- a/docs/apidocs/transpiler_plugins.rst +++ b/docs/apidocs/transpiler_plugins.rst @@ -1,6 +1,6 @@ .. _qiskit-transpiler-plugins: -.. automodule:: qiskit.transpiler.passes.synthesis.plugin +.. automodule:: qiskit.transpiler.preset_passmanagers.plugin :no-members: :no-inherited-members: :no-special-members: diff --git a/docs/apidocs/transpiler_synthesis_plugins.rst b/docs/apidocs/transpiler_synthesis_plugins.rst new file mode 100644 index 000000000000..70bef1190f40 --- /dev/null +++ b/docs/apidocs/transpiler_synthesis_plugins.rst @@ -0,0 +1,6 @@ +.. _qiskit-transpiler-synthesis-plugins: + +.. automodule:: qiskit.transpiler.passes.synthesis.plugin + :no-members: + :no-inherited-members: + :no-special-members: diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index ac9ccc7c0db4..d5545e9fdda6 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -80,6 +80,8 @@ def transpile( unitary_synthesis_method: str = "default", unitary_synthesis_plugin_config: dict = None, target: Target = None, + init_method: str = None, + optimization_method: str = None, ) -> Union[QuantumCircuit, List[QuantumCircuit]]: """Transpile one or more circuits, according to some desired transpilation targets. @@ -150,17 +152,29 @@ def transpile( [qr[0], None, None, qr[1], None, qr[2]] - layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre') + layout_method: Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre'). + This can also be the external plugin name to use for the ``layout`` stage. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"layout"`` for the ``stage_name`` argument. routing_method: Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre', 'toqm', 'none'). Note that to use method 'toqm', package 'qiskit-toqm' must be installed. + This can also be the external plugin name to use for the ``routing`` stage. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"routing"`` for the ``stage_name`` argument. translation_method: Name of translation pass ('unroller', 'translator', 'synthesis') + This can also be the external plugin name to use for the ``translation`` stage. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"translation"`` for the ``stage_name`` argument. scheduling_method: Name of scheduling pass. * ``'as_soon_as_possible'``: Schedule instructions greedily, as early as possible on a qubit resource. (alias: ``'asap'``) * ``'as_late_as_possible'``: Schedule instructions late, i.e. keeping qubits in the ground state when possible. (alias: ``'alap'``) - If ``None``, no scheduling will be done. + If ``None``, no scheduling will be done. This can also be the external plugin name + to use for the ``scheduling`` stage. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"scheduling"`` for the ``stage_name`` + argument. instruction_durations: Durations of instructions. Applicable only if scheduling_method is specified. The gate lengths defined in ``backend.properties`` are used as default. @@ -246,6 +260,16 @@ def callback_func(**kwargs): the ``backend`` argument, but if you have manually constructed a :class:`~qiskit.transpiler.Target` object you can specify it manually here. This will override the target from ``backend``. + init_method: The plugin name to use for the ``init`` stage. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"init"`` for the stage + name argument. + optimization_method: The plugin name to use for the + ``optimization`` stage. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"optimization"`` for the + ``stage_name`` argument. + Returns: The transpiled circuit(s). @@ -310,6 +334,8 @@ def callback_func(**kwargs): unitary_synthesis_method, unitary_synthesis_plugin_config, target, + init_method, + optimization_method, ) # Get transpile_args to configure the circuit transpilation job(s) if coupling_map in unique_transpile_args: @@ -392,7 +418,7 @@ def _log_transpile_time(start_time, end_time): def _combine_args(shared_transpiler_args, unique_config): # Pop optimization_level to exclude it from the kwargs when building a # PassManagerConfig - level = shared_transpiler_args.pop("optimization_level") + level = shared_transpiler_args.get("optimization_level") pass_manager_config = shared_transpiler_args pass_manager_config.update(unique_config.pop("pass_manager_config")) pass_manager_config = PassManagerConfig(**pass_manager_config) @@ -560,6 +586,8 @@ def _parse_transpile_args( unitary_synthesis_method, unitary_synthesis_plugin_config, target, + init_method, + optimization_method, ) -> Tuple[List[Dict], Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on @@ -627,6 +655,8 @@ def _parse_transpile_args( shared_dict = { "optimization_level": optimization_level, "basis_gates": basis_gates, + "init_method": init_method, + "optimization_method": optimization_method, } list_transpile_args = [] diff --git a/qiskit/transpiler/passes/synthesis/plugin.py b/qiskit/transpiler/passes/synthesis/plugin.py index 33f168319ced..b199e0ffdbe8 100644 --- a/qiskit/transpiler/passes/synthesis/plugin.py +++ b/qiskit/transpiler/passes/synthesis/plugin.py @@ -27,6 +27,10 @@ which enable packages external to qiskit to advertise they include a synthesis plugin. +See :mod:`qiskit.transpiler.preset_passmanagers.plugin` for details on how +to write plugins for transpiler stages. + + Writing Plugins =============== diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 929d1aa13366..fc412dc4f286 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -39,6 +39,9 @@ def __init__( unitary_synthesis_method="default", unitary_synthesis_plugin_config=None, target=None, + init_method=None, + optimization_method=None, + optimization_level=None, ): """Initialize a PassManagerConfig object @@ -50,12 +53,16 @@ def __init__( coupling_map (CouplingMap): Directed graph represented a coupling map. layout_method (str): the pass to use for choosing initial qubit - placement. + placement. This will be the plugin name if an external layout stage + plugin is being used. routing_method (str): the pass to use for routing qubits on the - architecture. + architecture. This will be a plugin name if an external routing stage + plugin is being used. translation_method (str): the pass to use for translating gates to - basis_gates. - scheduling_method (str): the pass to use for scheduling instructions. + basis_gates. This will be a plugin name if an external translation stage + plugin is being used. + scheduling_method (str): the pass to use for scheduling instructions. This will + be a plugin name if an external scheduling stage plugin is being used. instruction_durations (InstructionDurations): Dictionary of duration (in dt) for each instruction. backend_properties (BackendProperties): Properties returned by a @@ -70,14 +77,20 @@ def __init__( :class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will search installed plugins for a valid method. target (Target): The backend target + init_method (str): The plugin name for the init stage plugin to use + optimization_method (str): The plugin name for the optimization stage plugin + to use. + optimization_level (int): The optimization level being used for compilation. """ self.initial_layout = initial_layout self.basis_gates = basis_gates self.inst_map = inst_map self.coupling_map = coupling_map + self.init_method = init_method self.layout_method = layout_method self.routing_method = routing_method self.translation_method = translation_method + self.optimization_method = optimization_method self.scheduling_method = scheduling_method self.instruction_durations = instruction_durations self.backend_properties = backend_properties @@ -87,6 +100,7 @@ def __init__( self.unitary_synthesis_method = unitary_synthesis_method self.unitary_synthesis_plugin_config = unitary_synthesis_plugin_config self.target = target + self.optimization_level = optimization_level @classmethod def from_backend(cls, backend, **pass_manager_options): diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index eb2f0d2aed39..0af4e40dd031 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -55,6 +55,8 @@ def generate_preset_pass_manager( seed_transpiler=None, unitary_synthesis_method="default", unitary_synthesis_plugin_config=None, + init_method=None, + optimization_method=None, ): """Generate a preset :class:`~.PassManager` @@ -103,18 +105,30 @@ def generate_preset_pass_manager( layout_method (str): The :class:`~.Pass` to use for choosing initial qubit placement. Valid choices are ``'trivial'``, ``'dense'``, ``'noise_adaptive'``, and, ``'sabre'`` repsenting :class:`~.TrivialLayout`, :class:`~DenseLayout`, - :class:`~.NoiseAdaptiveLayout`, :class:`~.SabreLayout` respectively. + :class:`~.NoiseAdaptiveLayout`, :class:`~.SabreLayout` respectively. This can also + be the external plugin name to use for the ``layout`` stage of the output + :class:`~.StagedPassManager`. You can see a list of installed plugins by using + :func:`~.list_stage_plugins` with ``"layout"`` for the ``stage_name`` argument. routing_method (str): The pass to use for routing qubits on the architecture. Valid choices are ``'basic'``, ``'lookahead'``, ``'stochastic'``, ``'sabre'``, and ``'none'`` representing :class:`~.BasicSwap`, :class:`~.LookaheadSwap`, :class:`~.StochasticSwap`, :class:`~.SabreSwap`, and - erroring if routing is required respectively. + erroring if routing is required respectively. This can also be the external plugin + name to use for the ``routing`` stage of the output :class:`~.StagedPassManager`. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"routing"`` for the ``stage_name`` argument. translation_method (str): The method to use for translating gates to basis gates. Valid choices ``'unroller'``, ``'translator'``, ``'synthesis'`` representing :class:`~.Unroller`, :class:`~.BasisTranslator`, and - :class:`~.UnitarySynthesis` respectively. + :class:`~.UnitarySynthesis` respectively. This can also be the external plugin + name to use for the ``translation`` stage of the output :class:`~.StagedPassManager`. + You can see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"translation"`` for the ``stage_name`` argument. scheduling_method (str): The pass to use for scheduling instructions. Valid choices - are ``'alap'`` and ``'asap'``. + are ``'alap'`` and ``'asap'``. This can also be the external plugin name to use + for the ``scheduling`` stage of the output :class:`~.StagedPassManager`. You can + see a list of installed plugins by using :func:`~.list_stage_plugins` with + ``"scheduling"`` for the ``stage_name`` argument. backend_properties (BackendProperties): Properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. @@ -134,6 +148,17 @@ def generate_preset_pass_manager( the ``unitary_synthesis`` argument. As this is custom for each unitary synthesis plugin refer to the plugin documentation for how to use this option. + init_method (str): The plugin name to use for the ``init`` stage of + the output :class:`~.StagedPassManager`. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"init"`` for the stage + name argument. + optimization_method (str): The plugin name to use for the + ``optimization`` stage of the output + :class:`~.StagedPassManager`. By default an external + plugin is not used. You can see a list of installed plugins by + using :func:`~.list_stage_plugins` with ``"optimization"`` for the + ``stage_name`` argument. Returns: StagedPassManager: The preset pass manager for the given options @@ -172,6 +197,9 @@ def generate_preset_pass_manager( unitary_synthesis_method=unitary_synthesis_method, unitary_synthesis_plugin_config=unitary_synthesis_plugin_config, initial_layout=initial_layout, + init_method=init_method, + optimization_method=optimization_method, + optimization_level=optimization_level, ) if backend is not None: diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py new file mode 100644 index 000000000000..d1fc2b5e67c7 --- /dev/null +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -0,0 +1,273 @@ +# 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. + +"""Built-in transpiler stage plugins for preset pass managers.""" + +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.exceptions import TranspilerError +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 Error +from qiskit.transpiler.preset_passmanagers import common +from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin + + +class BasicSwapPassManager(PassManagerStagePlugin): + """Plugin class for routing stage with :class:`~.BasicSwap`""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build routing stage PassManager.""" + seed_transpiler = pass_manager_config.seed_transpiler + target = pass_manager_config.target + coupling_map = pass_manager_config.coupling_map + backend_properties = pass_manager_config.backend_properties + routing_pass = BasicSwap(coupling_map) + vf2_call_limit = common.get_vf2_call_limit( + optimization_level, + pass_manager_config.layout_method, + pass_manager_config.initial_layout, + ) + if optimization_level == 0: + return common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=True, + ) + if optimization_level == 1: + return 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=True, + ) + if optimization_level == 2: + return 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=True, + ) + if optimization_level == 3: + return 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=True, + ) + raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") + + +class StochasticSwapPassManager(PassManagerStagePlugin): + """Plugin class for routing stage with :class:`~.StochasticSwap`""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build routing stage PassManager.""" + seed_transpiler = pass_manager_config.seed_transpiler + target = pass_manager_config.target + coupling_map = pass_manager_config.coupling_map + backend_properties = pass_manager_config.backend_properties + vf2_call_limit = common.get_vf2_call_limit( + optimization_level, + pass_manager_config.layout_method, + pass_manager_config.initial_layout, + ) + if optimization_level == 3: + routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) + else: + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) + + if optimization_level == 0: + return common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=True, + ) + if optimization_level == 1: + return 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=True, + ) + if optimization_level in {2, 3}: + return 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=True, + ) + raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") + + +class LookaheadSwapPassManager(PassManagerStagePlugin): + """Plugin class for routing stage with :class:`~.LookaheadSwap`""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build routing stage PassManager.""" + seed_transpiler = pass_manager_config.seed_transpiler + target = pass_manager_config.target + coupling_map = pass_manager_config.coupling_map + backend_properties = pass_manager_config.backend_properties + vf2_call_limit = common.get_vf2_call_limit( + optimization_level, + pass_manager_config.layout_method, + pass_manager_config.initial_layout, + ) + if optimization_level == 0: + routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) + return common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=True, + ) + if optimization_level == 1: + routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) + return 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=True, + ) + if optimization_level == 2: + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + return 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=True, + ) + if optimization_level == 3: + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) + return 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=True, + ) + raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") + + +class SabreSwapPassManager(PassManagerStagePlugin): + """Plugin class for routing stage with :class:`~.SabreSwap`""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build routing stage PassManager.""" + seed_transpiler = pass_manager_config.seed_transpiler + target = pass_manager_config.target + coupling_map = pass_manager_config.coupling_map + backend_properties = pass_manager_config.backend_properties + vf2_call_limit = common.get_vf2_call_limit( + optimization_level, + pass_manager_config.layout_method, + pass_manager_config.initial_layout, + ) + if optimization_level == 0: + routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler) + return common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=True, + ) + if optimization_level == 1: + routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler) + return 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=True, + ) + if optimization_level == 2: + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) + return 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=True, + ) + if optimization_level == 3: + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) + return 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=True, + ) + raise TranspilerError(f"Invalid optimization level specified: {optimization_level}") + + +class NoneRoutingPassManager(PassManagerStagePlugin): + """Plugin class for routing stage with error on routing.""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build routing stage PassManager.""" + seed_transpiler = pass_manager_config.seed_transpiler + target = pass_manager_config.target + coupling_map = pass_manager_config.coupling_map + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) + return common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=True, + ) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index d66037e616b9..7e5513316042 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -14,6 +14,8 @@ """Common preset passmanager generators.""" +from typing import Optional + from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel from qiskit.transpiler.passmanager import PassManager @@ -47,6 +49,7 @@ 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 +from qiskit.transpiler.layout import Layout def generate_unroll_3q( @@ -391,3 +394,20 @@ def _require_alignment(property_set): scheduling.append(PadDelay()) return scheduling + + +def get_vf2_call_limit( + optimization_level: int, + layout_method: Optional[str] = None, + initial_layout: Optional[Layout] = None, +) -> Optional[int]: + """Get the vf2 call limit for vf2 based layout passes.""" + vf2_call_limit = None + if layout_method is None and initial_layout is None: + if optimization_level == 1: + vf2_call_limit = int(5e4) # Set call limit to ~100ms with retworkx 0.10.2 + elif optimization_level == 2: + vf2_call_limit = int(5e6) # Set call limit to ~10 sec with retworkx 0.10.2 + elif optimization_level == 3: + vf2_call_limit = int(3e7) # Set call limit to ~60 sec with retworkx 0.10.2 + return vf2_call_limit diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 37aadc1c4452..c02c517a23e1 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -25,12 +25,11 @@ from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -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 Error from qiskit.transpiler.preset_passmanagers import common +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePluginManager, + list_stage_plugins, +) from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM @@ -54,13 +53,16 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Raises: TranspilerError: if the passmanager config is invalid. """ + plugin_manager = PassManagerStagePluginManager() basis_gates = pass_manager_config.basis_gates inst_map = pass_manager_config.inst_map coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + init_method = pass_manager_config.init_method layout_method = pass_manager_config.layout_method or "trivial" routing_method = pass_manager_config.routing_method or "stochastic" translation_method = pass_manager_config.translation_method or "translator" + optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -85,20 +87,11 @@ def _choose_layout_condition(property_set): _choose_layout = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _choose_layout = SabreLayout(coupling_map, max_iterations=1, seed=seed_transpiler) - else: - raise TranspilerError("Invalid layout method %s." % layout_method) toqm_pass = False # Choose routing pass - if routing_method == "basic": - routing_pass = BasicSwap(coupling_map) - elif routing_method == "stochastic": - routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) - elif routing_method == "lookahead": - routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) - elif routing_method == "sabre": - routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler) - elif routing_method == "toqm": + # TODO: Remove when qiskit-toqm has it's own plugin and we can rely on just the plugin interface + if routing_method == "toqm" and "toqm" not in list_stage_plugins("routing"): HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO0, latencies_from_target @@ -116,14 +109,17 @@ def _choose_layout_condition(property_set): ) ), ) - elif routing_method == "none": - routing_pass = Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", + routing_pm = common.generate_routing_passmanager( + routing_pass, + target, + coupling_map=coupling_map, + seed_transpiler=seed_transpiler, + use_barrier_before_measurement=not toqm_pass, ) else: - raise TranspilerError("Invalid routing method %s." % routing_method) + routing_pm = plugin_manager.get_passmanager_stage( + "routing", routing_method, pass_manager_config, optimization_level=0 + ) unroll_3q = None # Build pass manager @@ -135,30 +131,34 @@ def _choose_layout_condition(property_set): unitary_synthesis_method, unitary_synthesis_plugin_config, ) - 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 layout_method not in {"trivial", "dense", "noise_adaptive", "sabre"}: + layout = plugin_manager.get_passmanager_stage( + "layout", layout_method, pass_manager_config, optimization_level=0 + ) + else: + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout, condition=_choose_layout_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = routing_pm 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, - ) + if translation_method not in {"translator", "synthesis", "unroller"}: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=0 + ) + else: + 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 @@ -170,16 +170,33 @@ def _choose_layout_condition(property_set): pre_opt += translation else: pre_opt = None - sched = common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map - ) + if scheduling_method is None or scheduling_method in {"alap", "asap"}: + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + else: + sched = plugin_manager.get_passmanager_stage( + "scheduling", scheduling_method, pass_manager_config, optimization_level=0 + ) + if init_method is not None: + init = plugin_manager.get_passmanager_stage( + "init", init_method, pass_manager_config, optimization_level=0 + ) + else: + init = unroll_3q + optimization = None + if optimization_method is not None: + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=0 + ) return StagedPassManager( - init=unroll_3q, + init=init, layout=layout, pre_routing=pre_routing, routing=routing, translation=translation, pre_optimization=pre_opt, + optimization=optimization, scheduling=sched, ) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 5da52fb23052..05e7d56a736e 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -27,21 +27,20 @@ from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -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 FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import Layout2qDistance -from qiskit.transpiler.passes import Error from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePluginManager, + list_stage_plugins, +) def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: @@ -65,13 +64,16 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Raises: TranspilerError: if the passmanager config is invalid. """ + plugin_manager = PassManagerStagePluginManager() basis_gates = pass_manager_config.basis_gates inst_map = pass_manager_config.inst_map coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout layout_method = pass_manager_config.layout_method or "dense" + init_method = pass_manager_config.init_method routing_method = pass_manager_config.routing_method or "stochastic" translation_method = pass_manager_config.translation_method or "translator" + optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -143,19 +145,11 @@ def _vf2_match_not_found(property_set): _improve_layout = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _improve_layout = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) - else: - raise TranspilerError("Invalid layout method %s." % layout_method) toqm_pass = False - if routing_method == "basic": - routing_pass = BasicSwap(coupling_map) - elif routing_method == "stochastic": - routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) - elif routing_method == "lookahead": - routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) - elif routing_method == "sabre": - routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler) - elif routing_method == "toqm": + routing_pm = None + # TODO: Remove when qiskit-toqm has it's own plugin and we can rely on just the plugin interface + if routing_method == "toqm" and "toqm" not in list_stage_plugins("routing"): HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO1, latencies_from_target @@ -173,14 +167,26 @@ def _vf2_match_not_found(property_set): ) ), ) - elif routing_method == "none": - routing_pass = Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", + vf2_call_limit = common.get_vf2_call_limit( + 1, pass_manager_config.layout_method, pass_manager_config.initial_layout + ) + routing_pm = 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: - raise TranspilerError("Invalid routing method %s." % routing_method) + routing_pm = plugin_manager.get_passmanager_stage( + "routing", + routing_method, + pass_manager_config, + optimization_level=1, + ) # Build optimization loop: merge 1q rotations and cancel CNOT gates iteratively # until no more change in depth @@ -202,38 +208,38 @@ def _opt_control(property_set): 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, - ) + if layout_method not in {"trivial", "dense", "noise_adaptive", "sabre"}: + layout = plugin_manager.get_passmanager_stage( + "layout", layout_method, pass_manager_config, optimization_level=1 + ) + else: + 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) + + routing = routing_pm + 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, - ) + if translation_method not in {"translator", "synthesis", "unroller"}: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=1 + ) + else: + 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 @@ -241,20 +247,38 @@ def _opt_control(property_set): 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_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) + pre_optimization = common.generate_pre_op_passmanager( + target, coupling_map, remove_reset_in_zero=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 - ) + if optimization_method is None: + 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) + else: + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=1 + ) + if scheduling_method is None or scheduling_method in {"alap", "asap"}: + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + else: + sched = plugin_manager.get_passmanager_stage( + "scheduling", scheduling_method, pass_manager_config, optimization_level=1 + ) + if init_method is not None: + init = plugin_manager.get_passmanager_stage( + "init", init_method, pass_manager_config, optimization_level=1 + ) + else: + init = unroll_3q return StagedPassManager( - init=unroll_3q, + init=init, layout=layout, pre_routing=pre_routing, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index c2ee13ad8500..5a8dcde691e8 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -27,21 +27,20 @@ from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -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 FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import CommutativeCancellation -from qiskit.transpiler.passes import Error from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePluginManager, + list_stage_plugins, +) def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: @@ -67,13 +66,16 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Raises: TranspilerError: if the passmanager config is invalid. """ + plugin_manager = PassManagerStagePluginManager() basis_gates = pass_manager_config.basis_gates inst_map = pass_manager_config.inst_map coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + init_method = pass_manager_config.init_method layout_method = pass_manager_config.layout_method or "dense" routing_method = pass_manager_config.routing_method or "stochastic" translation_method = pass_manager_config.translation_method or "translator" + optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -126,19 +128,11 @@ def _vf2_match_not_found(property_set): _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _choose_layout_1 = SabreLayout(coupling_map, max_iterations=2, seed=seed_transpiler) - else: - raise TranspilerError("Invalid layout method %s." % layout_method) toqm_pass = False - if routing_method == "basic": - routing_pass = BasicSwap(coupling_map) - elif routing_method == "stochastic": - routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) - elif routing_method == "lookahead": - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=5) - elif routing_method == "sabre": - routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) - elif routing_method == "toqm": + routing_pm = None + # TODO: Remove when qiskit-toqm has it's own plugin and we can rely on just the plugin interface + if routing_method == "toqm" and "toqm" not in list_stage_plugins("routing"): HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO2, latencies_from_target @@ -156,14 +150,22 @@ def _vf2_match_not_found(property_set): ) ), ) - elif routing_method == "none": - routing_pass = Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", + vf2_call_limit = common.get_vf2_call_limit( + 2, pass_manager_config.layout_method, pass_manager_config.initial_layout + ) + routing_pm = 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: - raise TranspilerError("Invalid routing method %s." % routing_method) + routing_pm = plugin_manager.get_passmanager_stage( + "routing", routing_method, pass_manager_config, optimization_level=2 + ) # Build optimization loop: 1q rotation merge and commutative cancellation iteratively until # no more change in depth @@ -188,36 +190,35 @@ def _opt_control(property_set): 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, - ) + if layout_method not in {"trivial", "dense", "noise_adaptive", "sabre"}: + layout = plugin_manager.get_passmanager_stage( + "layout", layout_method, pass_manager_config, optimization_level=2 + ) + else: + 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) + routing = routing_pm 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, - ) + if translation_method not in {"translator", "synthesis", "unroller"}: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=2 + ) + else: + 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 @@ -227,16 +228,33 @@ def _opt_control(property_set): 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 - ) + if optimization_method is None: + 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) + else: + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=2 + ) + if scheduling_method is None or scheduling_method in {"alap", "asap"}: + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + else: + sched = plugin_manager.get_passmanager_stage( + "scheduling", scheduling_method, pass_manager_config, optimization_level=2 + ) + if init_method is not None: + init = plugin_manager.get_passmanager_stage( + "init", init_method, pass_manager_config, optimization_level=2 + ) + else: + init = unroll_3q + return StagedPassManager( - init=unroll_3q, + init=init, layout=layout, pre_routing=pre_routing, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 7a0f22319ecb..0d664f2172a0 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -28,10 +28,6 @@ from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -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 FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import Size @@ -43,10 +39,12 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import Error from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason - +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePluginManager, + list_stage_plugins, +) from qiskit.transpiler import TranspilerError from qiskit.utils.optionals import HAS_TOQM @@ -74,13 +72,16 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Raises: TranspilerError: if the passmanager config is invalid. """ + plugin_manager = PassManagerStagePluginManager() basis_gates = pass_manager_config.basis_gates inst_map = pass_manager_config.inst_map coupling_map = pass_manager_config.coupling_map initial_layout = pass_manager_config.initial_layout + init_method = pass_manager_config.init_method layout_method = pass_manager_config.layout_method or "sabre" routing_method = pass_manager_config.routing_method or "sabre" translation_method = pass_manager_config.translation_method or "translator" + optimization_method = pass_manager_config.optimization_method scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -90,6 +91,11 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa timing_constraints = pass_manager_config.timing_constraints or TimingConstraints() unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config target = pass_manager_config.target + # Override an unset optimization_level for stage plugin use. + # it will be restored to None before this is returned + optimization_level = pass_manager_config.optimization_level + if optimization_level is None: + pass_manager_config.optimization_level = 3 # Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) @@ -133,19 +139,10 @@ def _vf2_match_not_found(property_set): _choose_layout_1 = NoiseAdaptiveLayout(backend_properties) elif layout_method == "sabre": _choose_layout_1 = SabreLayout(coupling_map, max_iterations=4, seed=seed_transpiler) - else: - raise TranspilerError("Invalid layout method %s." % layout_method) toqm_pass = False - if routing_method == "basic": - routing_pass = BasicSwap(coupling_map) - elif routing_method == "stochastic": - routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) - elif routing_method == "lookahead": - routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) - elif routing_method == "sabre": - routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) - elif routing_method == "toqm": + # TODO: Remove when qiskit-toqm has it's own plugin and we can rely on just the plugin interface + if routing_method == "toqm" and "toqm" not in list_stage_plugins("routing"): HAS_TOQM.require_now("TOQM-based routing") from qiskit_toqm import ToqmSwap, ToqmStrategyO3, latencies_from_target @@ -163,14 +160,22 @@ def _vf2_match_not_found(property_set): ) ), ) - elif routing_method == "none": - routing_pass = Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", + vf2_call_limit = common.get_vf2_call_limit( + 3, pass_manager_config.layout_method, pass_manager_config.initial_layout + ) + routing_pm = 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: - raise TranspilerError("Invalid routing method %s." % routing_method) + routing_pm = plugin_manager.get_passmanager_stage( + "routing", routing_method, pass_manager_config, optimization_level=3 + ) # 8. Optimize iteratively until no more change in depth. Removes useless gates # after reset and before measure, commutes gates and optimizes contiguous blocks. @@ -197,80 +202,107 @@ def _opt_control(property_set): ] # Build pass manager - init = common.generate_unroll_3q( - target, - basis_gates, - approximation_degree, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - ) + if init_method is not None: + init = plugin_manager.get_passmanager_stage( + "init", init_method, pass_manager_config, optimization_level=3 + ) + else: + 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: - 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, - ) + if layout_method not in {"trivial", "dense", "noise_adaptive", "sabre"}: + layout = plugin_manager.get_passmanager_stage( + "layout", layout_method, pass_manager_config, optimization_level=3 + ) + else: + 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) + routing = routing_pm 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, - ) + if translation_method not in {"translator", "synthesis", "unroller"}: + translation = plugin_manager.get_passmanager_stage( + "translation", translation_method, pass_manager_config, optimization_level=3 + ) + else: + 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) - ): - 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: - optimization.append( - _opt + unroll + _depth_check + _size_check + _direction, do_while=_opt_control - ) + if optimization_method is None: + 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) + ): + 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 and optimization is not None: + optimization.append( + _opt + unroll + _depth_check + _size_check + _direction, do_while=_opt_control + ) + elif optimization is not None: + optimization.append( + _opt + unroll + _depth_check + _size_check, do_while=_opt_control + ) else: + 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) else: - 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 - ) + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=3 + ) + 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_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) + + if scheduling_method is None or scheduling_method in {"alap", "asap"}: + sched = common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, inst_map + ) + else: + sched = plugin_manager.get_passmanager_stage( + "scheduling", scheduling_method, pass_manager_config, optimization_level=3 + ) + + # Restore PassManagerConfig optimization_level override + pass_manager_config.optimization_level = optimization_level + return StagedPassManager( init=init, layout=layout, diff --git a/qiskit/transpiler/preset_passmanagers/plugin.py b/qiskit/transpiler/preset_passmanagers/plugin.py new file mode 100644 index 000000000000..a442bbb49383 --- /dev/null +++ b/qiskit/transpiler/preset_passmanagers/plugin.py @@ -0,0 +1,299 @@ +# 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. + +""" +======================================================================================= +Transpiler Stage Plugin Interface (:mod:`qiskit.transpiler.preset_passmanagers.plugin`) +======================================================================================= + +.. currentmodule:: qiskit.transpiler.preset_passmanagers.plugin + +This module defines the plugin interface for providing custom stage +implementations for the preset pass managers and the :func:`~.transpile` +function. This enables external Python packages to provide +:class:`~.PassManager` objects that can be used for each stage. + +The plugin interfaces are built using setuptools +`entry points `__ +which enable packages external to Qiskit to advertise they include a transpiler stage. + +See :mod:`qiskit.transpiler.passes.synthesis.plugin` for details on how to +write plugins for synthesis methods which are used by the transpiler. + +.. _stage_table: + +Plugin Stages +============= + +Currently there are 6 stages in the preset pass managers used by and corresponding entrypoints. + +.. list-table:: Stages + :header-rows: 1 + + * - Stage Name + - Entry Point + - Reserved Names + - Description and expectations + * - ``init`` + - ``qiskit.transpiler.init`` + - No reserved names + - This stage runs first and is typically used for any initial logical optimization. Because most + layout and routing algorithms are only designed to work with 1 and 2 qubit gates, this stage + is also used to translate any gates that operate on more than 2 qubits into gates that only + operate on 1 or 2 qubits. + * - ``layout`` + - ``qiskit.transpiler.layout`` + - ``trivial``, ``dense``, ``noise_adaptive``, ``sabre`` + - The output from this stage is expected to have the ``layout`` property + set field set with a :class:`~.Layout` object. Additionally, the circuit is + typically expected to be embedded so that it is expanded to include all + qubits and the :class:`~.ApplyLayout` pass is expected to be run to apply the + layout. The embedding of the :class:`~.Layout` can be generated with + :func:`~.generate_embed_passmanager`. + * - ``routing`` + - ``qiskit.transpiler.routing`` + - ``basic``, ``stochastic``, ``lookahead``, ``sabre``, ``toqm`` + - The output from this stage is expected to have the circuit match the + connectivity constraints of the target backend. This does not necessarily + need to match the directionality of the edges in the target as a later + stage typically will adjust directional gates to match that constraint + (but there is no penalty for doing that in the ``routing`` stage). + * - ``translation`` + - ``qiskit.transpiler.translation`` + - ``translator``, ``synthesis``, ``unroller`` + - The output of this stage is expected to have every operation be a native + instruction on the target backend. + * - ``optimization`` + - ``qiskit.transpiler.optimization`` + - There are no reserved plugin names + - This stage is expected to perform optimization and simplification. + The constraints from earlier stages still apply to the output of this + stage. After the ``optimization`` stage is run we expect the circuit + to still be executable on the target. + * - ``scheduling`` + - ``qiskit.transpiler.scheduling`` + - ``alap``, ``asap`` + - This is the last stage run and it is expected to output a scheduled + circuit such that all idle periods in the circuit are marked by explicit + :class:`~qiskit.circuit.Delay` instructions. + +Writing Plugins +=============== + +To write a pass manager stage plugin there are 2 main steps. The first step is +to create a subclass of the abstract plugin class +:class:`~.PassManagerStagePluginManager` which is used to define how the :class:`~.PassManager` +for the stage will be constructed. For example, to create a ``layout`` stage plugin that just +runs :class:`~.VF2Layout` and will fallback to use :class:`~.TrivialLayout` if +:class:`~VF2Layout` is unable to find a perfect layout:: + + from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin + from qiskit.transpiler.preset_passmanagers import common + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import VF2Layout, TrivialLayout + from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason + + + def _vf2_match_not_found(property_set): + return property_set["layout"] is None or ( + property_set["VF2Layout_stop_reason"] is not None + and property_set["VF2Layout_stop_reason"] is not VF2LayoutStopReason.SOLUTION_FOUND + + + class VF2LayoutPlugin(PassManagerStagePlugin): + + def pass_manager(self, pass_manager_config): + layout_pm = PassManager( + [ + VF2Layout( + coupling_map=pass_manager_config.coupling_map, + properties=pass_manager_config.backend_properties, + target=pass_manager_config.target + ) + ] + ) + layout_pm.append( + TrivialLayout(pass_manager_config.coupling_map), + condition=_vf2_match_not_found, + ) + layout_pm += common.generate_embed_passmanager(pass_manager_config.coupling_map) + return layout_pm + +The second step is to expose the :class:`~.PassManagerStagePluginManager` +subclass as a setuptools entry point in the package metadata. This can be done +by simply adding an ``entry_points`` entry to the ``setuptools.setup`` call in +the ``setup.py`` or the plugin package with the necessary entry points under the +appropriate namespace for the stage your plugin is for. You can see the list +of stages, entrypoints, and expectations from the stage in :ref:`stage_table`. +For example, continuing from the example plugin above:: + + entry_points = { + 'qiskit.transpiler.layout': [ + 'vf2 = qiskit_plugin_pkg.module.plugin:VF2LayoutPlugin', + ] + }, + +(note that the entry point ``name = path`` is a single string not a Python +expression). There isn't a limit to the number of plugins a single package can +include as long as each plugin has a unique name. So a single package can +expose multiple plugins if necessary. Refer to :ref:`stage_table` for a list +of reserved names for each stage. + +Plugin API +========== + +.. autosummary:: + :toctree: ../stubs/ + + PassManagerStagePlugin + PassManagerStagePluginManager + list_stage_plugins +""" + +import abc +from typing import List, Optional + +import stevedore + +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passmanager_config import PassManagerConfig + + +class PassManagerStagePlugin(abc.ABC): + """A ``PassManagerStagePlugin`` is a plugin interface object for using custom + stages in :func:`~.transpile`. + + A ``PassManagerStagePlugin`` object can be added to an external package and + integrated into the :func:`~.transpile` function with an entrypoint. This + will enable users to use the output of :meth:`.pass_manager` to implement + a stage in the compilation process. + """ + + @abc.abstractmethod + def pass_manager( + self, pass_manager_config: PassManagerConfig, optimization_level: Optional[int] = None + ) -> PassManager: + """This method is designed to return a :class:`~.PassManager` for the stage this implements + + Args: + pass_manager_config: A configuration object that defines all the target device + specifications and any user specified options to :func:`~.transpile` or + :func:`~.generate_preset_pass_manager` + optimization_level: The optimization level of the transpilation, if set this + should be used to set values for any tunable parameters to trade off runtime + for potential optimization. Valid values should be ``0``, ``1``, ``2``, or ``3`` + and the higher the number the more optimization is expected. + """ + pass + + +class PassManagerStagePluginManager: + """Manager class for preset pass manager stage plugins.""" + + def __init__(self): + super().__init__() + self.init_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.init", invoke_on_load=True, propagate_map_exceptions=True + ) + self.layout_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.layout", invoke_on_load=True, propagate_map_exceptions=True + ) + self.routing_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.routing", invoke_on_load=True, propagate_map_exceptions=True + ) + self.translation_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.translation", invoke_on_load=True, propagate_map_exceptions=True + ) + self.optimization_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.optimization", invoke_on_load=True, propagate_map_exceptions=True + ) + self.scheduling_plugins = stevedore.ExtensionManager( + "qiskit.transpiler.scheduling", invoke_on_load=True, propagate_map_exceptions=True + ) + + def get_passmanager_stage( + self, + stage_name: str, + plugin_name: str, + pm_config: PassManagerConfig, + optimization_level=None, + ) -> PassManager: + """Get a stage""" + if stage_name == "init": + return self._build_pm( + self.init_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + elif stage_name == "layout": + return self._build_pm( + self.layout_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + elif stage_name == "routing": + return self._build_pm( + self.routing_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + elif stage_name == "translation": + return self._build_pm( + self.translation_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + elif stage_name == "optimization": + return self._build_pm( + self.optimization_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + elif stage_name == "scheduling": + return self._build_pm( + self.scheduling_plugins, stage_name, plugin_name, pm_config, optimization_level + ) + else: + raise TranspilerError(f"Invalid stage name: {stage_name}") + + def _build_pm( + self, + stage_obj: stevedore.ExtensionManager, + stage_name: str, + plugin_name: str, + pm_config: PassManagerConfig, + optimization_level: Optional[int] = None, + ): + if plugin_name not in stage_obj: + raise TranspilerError(f"Invalid plugin name {plugin_name} for stage {stage_name}") + plugin_obj = stage_obj[plugin_name] + return plugin_obj.obj.pass_manager(pm_config, optimization_level) + + +def list_stage_plugins(stage_name: str) -> List[str]: + """Get a list of installed plugins for a stage. + + Args: + stage_name: The stage name to get the plugin names for + + Returns: + plugins: The list of installed plugin names for the specified stages + + Raises: + TranspilerError: If an invalid stage name is specified. + """ + plugin_mgr = PassManagerStagePluginManager() + if stage_name == "init": + return plugin_mgr.init_plugins.names() + elif stage_name == "layout": + return plugin_mgr.layout_plugins.names() + elif stage_name == "routing": + return plugin_mgr.routing_plugins.names() + elif stage_name == "translation": + return plugin_mgr.translation_plugins.names() + elif stage_name == "optimization": + return plugin_mgr.optimization_plugins.names() + elif stage_name == "scheduling": + return plugin_mgr.scheduling_plugins.names() + else: + raise TranspilerError(f"Invalid stage name: {stage_name}") diff --git a/releasenotes/notes/stage-plugin-interface-47daae40f7d0ad3c.yaml b/releasenotes/notes/stage-plugin-interface-47daae40f7d0ad3c.yaml new file mode 100644 index 000000000000..ea4c88c097df --- /dev/null +++ b/releasenotes/notes/stage-plugin-interface-47daae40f7d0ad3c.yaml @@ -0,0 +1,32 @@ +--- +features: + - | + Introduced a new plugin interface for transpiler stages which is used to + enable alternative :class:`~.PassManager` objects from an external package + in a particular stage as part of :func:`~.transpile` or the + :class:`~.StagedPassManager` output from + :func:`~.generate_preset_pass_manager`, :func:`~.level_0_pass_manager`, + :func:`~.level_1_pass_manager`, :func:`~.level_2_pass_manager`, and + :func:`~.level_3_pass_manager`. Users can select a plugin to use for a + transpiler stage with the ``init_method``, ``layout_method``, + ``routing_method``, ``translation_method``, ``optimization_method``, and + ``scheduling_method`` keyword arguments on :func:`~.transpile` and + :func:`~.generate_preset_pass_manager`. A full list of plugin names + currently installed can be found with the :func:`.list_stage_plugins` + function. For creating plugins refer to the + :mod:`qiskit.transpiler.preset_passmanagers.plugin` module documentation + which includes a guide for writing stage plugins. + - | + The :func:`~.transpile` has two new keyword arguments, ``init_method`` and + ``optimization_method`` which are used to specify alternative plugins to + use for the ``init`` stage and ``optimization`` stages respectively. + - | + The :class:`~.PassManagerConfig` class has 3 new attributes, + :attr:`~.PassManagerConfig.init_method`, + :attr:`~.PassManagerConfig.optimization_method`, and + :attr:`~.PassManagerConfig.optimization_level` along with matching keyword + arguments on the constructor methods. The first two attributes represent + the user specified ``init`` and ``optimization`` plugins to use for + compilation. The :attr:`~.PassManagerConfig.optimization_level` attribute + represents the compilations optimization level if specified which can + be used to inform stage plugin behavior. diff --git a/setup.py b/setup.py index 5cdccc12ceea..ed7d0489f8d3 100755 --- a/setup.py +++ b/setup.py @@ -100,6 +100,13 @@ "qiskit.unitary_synthesis": [ "default = qiskit.transpiler.passes.synthesis.unitary_synthesis:DefaultUnitarySynthesis", "aqc = qiskit.transpiler.synthesis.aqc.aqc_plugin:AQCSynthesisPlugin", - ] + ], + "qiskit.transpiler.routing": [ + "basic = qiskit.transpiler.preset_passmanagers.builtin_plugins:BasicSwapPassManager", + "stochastic = qiskit.transpiler.preset_passmanagers.builtin_plugins:StochasticSwapPassManager", + "lookahead = qiskit.transpiler.preset_passmanagers.builtin_plugins:LookaheadSwapPassManager", + "sabre = qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreSwapPassManager", + "none = qiskit.transpiler.preset_passmanagers.builtin_plugins:NoneRoutingPassManager", + ], }, ) diff --git a/test/python/transpiler/test_stage_plugin.py b/test/python/transpiler/test_stage_plugin.py new file mode 100644 index 000000000000..1242905e9f4e --- /dev/null +++ b/test/python/transpiler/test_stage_plugin.py @@ -0,0 +1,103 @@ +# 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. + +""" +Tests for the staged transpiler plugins. +""" + +from test import combine + +import ddt + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.compiler.transpiler import transpile +from qiskit.test import QiskitTestCase +from qiskit.transpiler import PassManager, PassManagerConfig, CouplingMap +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePluginManager, + list_stage_plugins, +) +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.providers.basicaer import QasmSimulatorPy + + +class TestStagePassManagerPlugin(QiskitTestCase): + """Tests for the transpiler stage plugin interface.""" + + def test_list_stage_plugins(self): + """Test list stage plugin function.""" + routing_passes = list_stage_plugins("routing") + self.assertIn("basic", routing_passes) + self.assertIn("sabre", routing_passes) + self.assertIn("lookahead", routing_passes) + self.assertIn("stochastic", routing_passes) + self.assertIsInstance(list_stage_plugins("init"), list) + self.assertIsInstance(list_stage_plugins("layout"), list) + self.assertIsInstance(list_stage_plugins("translation"), list) + self.assertIsInstance(list_stage_plugins("optimization"), list) + self.assertIsInstance(list_stage_plugins("scheduling"), list) + + def test_list_stage_plugins_invalid_stage_name(self): + """Test list stage plugin function with invalid stage name.""" + with self.assertRaises(TranspilerError): + list_stage_plugins("not_a_stage") + + def test_build_pm_invalid_plugin_name_valid_stage(self): + """Test get pm from plugin with invalid plugin name and valid stage.""" + plugin_manager = PassManagerStagePluginManager() + with self.assertRaises(TranspilerError): + plugin_manager.get_passmanager_stage("init", "empty_plugin", PassManagerConfig()) + + def test_build_pm_invalid_stage(self): + """Test get pm from plugin with invalid stage.""" + plugin_manager = PassManagerStagePluginManager() + with self.assertRaises(TranspilerError): + plugin_manager.get_passmanager_stage( + "not_a_sage", "fake_plugin_not_real", PassManagerConfig() + ) + + def test_build_pm(self): + """Test get pm from plugin.""" + plugin_manager = PassManagerStagePluginManager() + pm_config = PassManagerConfig() + pm = plugin_manager.get_passmanager_stage( + "routing", "sabre", pm_config, optimization_level=3 + ) + self.assertIsInstance(pm, PassManager) + + +@ddt.ddt +class TestBuiltinPlugins(QiskitTestCase): + """Test that all built-in plugins work in transpile().""" + + @combine( + optimization_level=list(range(4)), + routing_method=["basic", "lookahead", "sabre", "stochastic"], + ) + def test_routing_plugins(self, optimization_level, routing_method): + """Test all routing plugins (excluding error).""" + qc = QuantumCircuit(4) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.measure_all() + tqc = transpile( + qc, + basis_gates=["cx", "sx", "x", "rz"], + coupling_map=CouplingMap.from_line(4), + optimization_level=optimization_level, + routing_method=routing_method, + ) + backend = QasmSimulatorPy() + counts = backend.run(tqc, shots=1000).result().get_counts() + self.assertDictAlmostEqual(counts, {"0000": 500, "1111": 500}, delta=100)