diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index f1a572c0247f..87ba67bfa9db 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -277,6 +277,14 @@ def callback_func(**kwargs): scheduling_method = backend.get_scheduling_stage_plugin() if translation_method is None and hasattr(backend, "get_translation_stage_plugin"): translation_method = backend.get_translation_stage_plugin() + if init_method is None and hasattr(backend, "get_init_stage_plugin"): + init_method = backend.get_init_stage_plugin() + if layout_method is None and hasattr(backend, "get_layout_stage_plugin"): + layout_method = backend.get_layout_stage_plugin() + if routing_method is None and hasattr(backend, "get_routing_stage_plugin"): + routing_method = backend.get_routing_stage_plugin() + if optimization_method is None and hasattr(backend, "get_optimization_stage_plugin"): + optimization_method = backend.get_optimization_stage_plugin() output_name = _parse_output_name(output_name, circuits) coupling_map = _parse_coupling_map(coupling_map) diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 304d83268626..e1a7258399ef 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -51,23 +51,26 @@ class BackendV2(Backend, ABC): something like a ``shots`` field for a backend that runs experiments which would contain an int for how many shots to execute. - A backend object can optionally contain methods named - ``get_translation_stage_plugin`` and ``get_scheduling_stage_plugin``. If these - methods are present on a backend object and this object is used for + A backend object can optionally contain methods named: + * ``get_init_stage_plugin`` + * ``get_layout_stage_plugin`` + * ``get_routing_stage_plugin`` + * ``get_translation_stage_plugin`` + * ``get_scheduling_stage_plugin``. + If these methods are present on a backend object and this object is used for :func:`~.transpile` or :func:`~.generate_preset_pass_manager` the transpilation process will default to using the output from those methods - as the scheduling stage and the translation compilation stage. This - enables a backend which has custom requirements for compilation to specify a - stage plugin for these stages to enable custom transformation of - the circuit to ensure it is runnable on the backend. These hooks are enabled - by default and should only be used to enable extra compilation steps - if they are **required** to ensure a circuit is executable on the backend or - have the expected level of performance. These methods are passed no input - arguments and are expected to return a ``str`` representing the method name - which should be a stage plugin (see: :mod:`qiskit.transpiler.preset_passmanagers.plugin` - for more details on plugins). The typical expected use case is for a backend - provider to implement a stage plugin for ``translation`` or ``scheduling`` - that contains the custom compilation passes and then for the hook methods on + as the respective compilation stage. This enables a backend which has custom + requirements or bespoke methods for compilation to specify a stage plugin for + these stages to enable custom compilation of the circuit to ensure it is + runnable on the backend. These hooks are enabled by default and should only + be used to enable extra compilation steps if they are **required** to ensure + a circuit is executable on the backend or have the expected level of performance. + These methods are passed no input arguments and are expected to return a ``str`` + representing the method name which should be a stage plugin (see: + :mod:`qiskit.transpiler.preset_passmanagers.plugin` for more details on plugins). + The typical expected use case is for a backend provider to implement a stage + plugin that contains the custom compilation passes and then for the hook methods on the backend object to return the plugin name so that :func:`~.transpile` will use it by default when targeting the backend. diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 47fcdce2f6d4..d79171e369c3 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -138,10 +138,11 @@ def from_backend(cls, backend, _skip_target=False, **pass_manager_options): res.instruction_durations = backend.instruction_durations if res.target is None and not _skip_target: res.target = backend.target - if res.scheduling_method is None and hasattr(backend, "get_scheduling_stage_plugin"): - res.scheduling_method = backend.get_scheduling_stage_plugin() - if res.translation_method is None and hasattr(backend, "get_translation_stage_plugin"): - res.translation_method = backend.get_translation_stage_plugin() + stages = ["init", "layout", "routing", "translation", "optimization", "scheduling"] + for stage in stages: + if getattr(res, f"{stage}_method", None) is None: + if plugin_meth := getattr(backend, f"get_{stage}_stage_plugin", None): + setattr(res, f"{stage}_method", plugin_meth()) return res def __str__(self): diff --git a/releasenotes/notes/expand_plugins_backend-e731bba7ef87ad2f.yaml b/releasenotes/notes/expand_plugins_backend-e731bba7ef87ad2f.yaml new file mode 100644 index 000000000000..1d21e7a9fd2e --- /dev/null +++ b/releasenotes/notes/expand_plugins_backend-e731bba7ef87ad2f.yaml @@ -0,0 +1,11 @@ +--- +features_providers: + - Implementations of the :class:`.BackendV2` abstract class can now define the optional + methods ``get_init_stage_plugin``, ``get_layout_stage_plugin``, + ``get_routing_stage_plugin``, and ``get_optimization_stage_plugin`` which can be used + to specify alternative default plugins to use when invoking the transpiler for that + backend. When defined these methods should return the name of the plugin + to use by default when running the corresponding :ref:`transpiler-preset` stages when + targeting this backend object. These methods work in the same as the existing :class:`.BackendV2` methods + ``get_translation_stage_plugin`` and ``get_scheduling_stage_plugin`` but for the other + stages of the :ref:`transpiler-preset`. diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index fcf0df5dd4c2..a30b2c4ff942 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -44,6 +44,10 @@ ALAPScheduleAnalysis, PadDynamicalDecoupling, RemoveResetInZeroState, + RemoveIdentityEquivalent, + TrivialLayout, + BasicSwap, + Optimize1qGates, ) from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.converters import circuit_to_dag @@ -81,13 +85,25 @@ def mock_get_passmanager_stage( ) return pm elif stage_name == "init": - return PassManager([]) + if plugin_name == "custom_stage_for_test": + return PassManager([RemoveIdentityEquivalent()]) + else: + return PassManager([]) elif stage_name == "routing": - return PassManager([]) + if plugin_name == "custom_stage_for_test": + return PassManager([BasicSwap(pm_config.coupling_map)]) + else: + return PassManager([]) elif stage_name == "optimization": - return OptimizationPassManager().pass_manager(pm_config, optimization_level) + if plugin_name == "custom_stage_for_test": + return PassManager([Optimize1qGates()]) + else: + return OptimizationPassManager().pass_manager(pm_config, optimization_level) elif stage_name == "layout": - return PassManager([]) + if plugin_name == "custom_stage_for_test": + return PassManager([TrivialLayout(pm_config.coupling_map)]) + else: + return PassManager([]) else: raise RuntimeError("Failure, unexpected stage plugin combo for test") @@ -1381,13 +1397,39 @@ def get_translation_stage_plugin(self): """Custom post translation stage.""" return "custom_stage_for_test" + def get_init_stage_plugin(self): + """Custom init stage.""" + return "custom_stage_for_test" + + def get_layout_stage_plugin(self): + """Custom layout stage.""" + return "custom_stage_for_test" + + def get_routing_stage_plugin(self): + """Custom routing stage.""" + return "custom_stage_for_test" + + def get_optimization_stage_plugin(self): + """Custom optimization stage.""" + return "custom_stage_for_test" + target = TargetBackend(num_qubits=7, coupling_map=LAGOS_CMAP, seed=42) pm = generate_preset_pass_manager(optimization_level, backend=target) self.assertIsInstance(pm, PassManager) pass_list = [x.__class__.__name__ for x in pm.to_flow_controller().tasks] - self.assertIn("PadDynamicalDecoupling", pass_list) - self.assertIn("ALAPScheduleAnalysis", pass_list) + expected = [ + "ContainsInstruction", + "ConditionalController", + "RemoveIdentityEquivalent", + "TrivialLayout", + "BasicSwap", + "RemoveResetInZeroState", + "Optimize1qGates", + "ALAPScheduleAnalysis", + "PadDynamicalDecoupling", + ] + self.assertEqual(pass_list, expected) post_translation_pass_list = [ x.__class__.__name__ for x in pm.translation.to_flow_controller().tasks ]