From e7808e7b058e7d05e3e3abb18738f3a9a57d941f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Tue, 22 Aug 2023 12:06:38 -0400 Subject: [PATCH] Optimization stage as a plugin. (#10581) * Initial: Optimization stage as a plugin. * CI: Added fix to preset_passmanager test * Simplify inner translation stage creation logic This commit changes the plugin construction logic slightly to adjust how the embedded translation stage is created. After #10621 merged we only need to use the plugin interface to get the translation stage pass manager. * Update documentation --------- Co-authored-by: Matthew Treinish --- .../preset_passmanagers/builtin_plugins.py | 121 +++++++++++++++++- .../transpiler/preset_passmanagers/level0.py | 10 +- .../transpiler/preset_passmanagers/level1.py | 45 +------ .../transpiler/preset_passmanagers/level2.py | 47 +------ .../transpiler/preset_passmanagers/level3.py | 98 ++------------ .../transpiler/preset_passmanagers/plugin.py | 2 +- ...reserved-plugin-name-3825c2000a579e38.yaml | 10 +- setup.py | 3 + .../transpiler/test_preset_passmanagers.py | 3 + 9 files changed, 160 insertions(+), 179 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 81475813e29c..2ecd4653707b 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -28,7 +28,21 @@ from qiskit.transpiler.passes import CheckMap from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.preset_passmanagers import common -from qiskit.transpiler.preset_passmanagers.plugin import PassManagerStagePlugin +from qiskit.transpiler.preset_passmanagers.plugin import ( + PassManagerStagePlugin, + PassManagerStagePluginManager, +) +from qiskit.transpiler.passes.optimization import ( + Optimize1qGatesDecomposition, + CommutativeCancellation, + Collect2qBlocks, + ConsolidateBlocks, + CXCancellation, +) +from qiskit.transpiler.passes import Depth, Size, FixedPoint, MinimumPoint +from qiskit.transpiler.passes.utils.gates_basis import GatesInBasis +from qiskit.transpiler.passes.synthesis.unitary_synthesis import UnitarySynthesis +from qiskit.passmanager.flow_controllers import ConditionalController from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passes.layout.vf2_layout import VF2LayoutStopReason @@ -378,6 +392,111 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) +class OptimizationPassManager(PassManagerStagePlugin): + """Plugin class for optimization stage""" + + def pass_manager(self, pass_manager_config, optimization_level=None) -> PassManager: + """Build pass manager for optimization stage.""" + # Obtain the translation method required for this pass to work + translation_method = pass_manager_config.translation_method or "translator" + optimization = PassManager() + if optimization_level != 0: + plugin_manager = PassManagerStagePluginManager() + _depth_check = [Depth(recurse=True), FixedPoint("depth")] + _size_check = [Size(recurse=True), FixedPoint("size")] + # Minimum point check for optimization level 3. + _minimum_point_check = [ + Depth(recurse=True), + Size(recurse=True), + MinimumPoint(["depth", "size"], "optimization_loop"), + ] + + def _opt_control(property_set): + return (not property_set["depth_fixed_point"]) or ( + not property_set["size_fixed_point"] + ) + + translation = plugin_manager.get_passmanager_stage( + "translation", + translation_method, + pass_manager_config, + optimization_level=optimization_level, + ) + if optimization_level == 1: + # Steps for optimization level 1 + _opt = [ + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), + CXCancellation(), + ] + elif optimization_level == 2: + # Steps for optimization level 2 + _opt = [ + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), + CommutativeCancellation( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + ), + ] + elif optimization_level == 3: + # Steps for optimization level 3 + _opt = [ + Collect2qBlocks(), + ConsolidateBlocks( + basis_gates=pass_manager_config.basis_gates, + target=pass_manager_config.target, + approximation_degree=pass_manager_config.approximation_degree, + ), + UnitarySynthesis( + pass_manager_config.basis_gates, + approximation_degree=pass_manager_config.approximation_degree, + coupling_map=pass_manager_config.coupling_map, + backend_props=pass_manager_config.backend_properties, + method=pass_manager_config.unitary_synthesis_method, + plugin_config=pass_manager_config.unitary_synthesis_plugin_config, + target=pass_manager_config.target, + ), + Optimize1qGatesDecomposition( + basis=pass_manager_config.basis_gates, target=pass_manager_config.target + ), + CommutativeCancellation(target=pass_manager_config.target), + ] + + def _opt_control(property_set): + return not property_set["optimization_loop_minimum_point"] + + else: + raise TranspilerError(f"Invalid optimization_level: {optimization_level}") + + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + # Build nested Flow controllers + def _unroll_condition(property_set): + return not property_set["all_gates_in_basis"] + + # Check if any gate is not in the basis, and if so, run unroll passes + _unroll_if_out_of_basis = [ + GatesInBasis(pass_manager_config.basis_gates, target=pass_manager_config.target), + ConditionalController(unroll, condition=_unroll_condition), + ] + + if optimization_level == 3: + optimization.append(_minimum_point_check) + else: + optimization.append(_depth_check + _size_check) + opt_loop = ( + _opt + _unroll_if_out_of_basis + _minimum_point_check + if optimization_level == 3 + else _opt + _unroll_if_out_of_basis + _depth_check + _size_check + ) + optimization.append(opt_loop, do_while=_opt_control) + return optimization + else: + return None + + class AlapSchedulingPassManager(PassManagerStagePlugin): """Plugin class for alap scheduling stage.""" diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index ca1a40e3d823..471a9d91fab1 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -50,7 +50,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout_method = pass_manager_config.layout_method or "default" 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 + optimization_method = pass_manager_config.optimization_method or "default" scheduling_method = pass_manager_config.scheduling_method or "default" approximation_degree = pass_manager_config.approximation_degree unitary_synthesis_method = pass_manager_config.unitary_synthesis_method @@ -113,11 +113,9 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa ) elif unroll_3q is not None: 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 - ) + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=0 + ) return StagedPassManager( init=init, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index f364c2577edd..22be66c4370c 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -15,17 +15,8 @@ Level 1 pass manager: light optimization by simple adjacent gate collapsing. """ from __future__ import annotations -from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig -from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler import ConditionalController, FlowController -from qiskit.transpiler.passes import CXCancellation -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 GatesInBasis from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.preset_passmanagers.plugin import ( PassManagerStagePluginManager, @@ -63,7 +54,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout_method = pass_manager_config.layout_method or "default" 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 + optimization_method = pass_manager_config.optimization_method or "default" scheduling_method = pass_manager_config.scheduling_method or "default" approximation_degree = pass_manager_config.approximation_degree unitary_synthesis_method = pass_manager_config.unitary_synthesis_method @@ -76,16 +67,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "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 - _depth_check = [Depth(recurse=True), FixedPoint("depth")] - _size_check = [Size(recurse=True), FixedPoint("size")] - - def _opt_control(property_set): - return (not property_set["depth_fixed_point"]) or (not property_set["size_fixed_point"]) - - _opt = [Optimize1qGatesDecomposition(basis=basis_gates, target=target), CXCancellation()] - unroll_3q = None # Build full pass manager if coupling_map or initial_layout: @@ -118,26 +99,10 @@ def _opt_control(property_set): ) else: pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) - if optimization_method is None: - optimization = PassManager() - unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] - # Build nested Flow controllers - def _unroll_condition(property_set): - return not property_set["all_gates_in_basis"] - - # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis: list[BasePass | FlowController] = [ - GatesInBasis(basis_gates, target=target), - ConditionalController(unroll, condition=_unroll_condition), - ] - - optimization.append(_depth_check + _size_check) - opt_loop = _opt + _unroll_if_out_of_basis + _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 - ) + + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=1 + ) sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=1 diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 42ccd9e0c4f7..8815a983ca61 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -16,17 +16,8 @@ gate cancellation using commutativity rules. """ from __future__ import annotations -from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig -from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler import ConditionalController, FlowController -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 GatesInBasis from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.preset_passmanagers.plugin import ( PassManagerStagePluginManager, @@ -64,7 +55,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout_method = pass_manager_config.layout_method or "default" 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 + optimization_method = pass_manager_config.optimization_method or "default" scheduling_method = pass_manager_config.scheduling_method or "default" approximation_degree = pass_manager_config.approximation_degree unitary_synthesis_method = pass_manager_config.unitary_synthesis_method @@ -77,19 +68,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "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 - _depth_check = [Depth(recurse=True), FixedPoint("depth")] - _size_check = [Size(recurse=True), FixedPoint("size")] - - def _opt_control(property_set): - return (not property_set["depth_fixed_point"]) or (not property_set["size_fixed_point"]) - - _opt: list[BasePass] = [ - Optimize1qGatesDecomposition(basis=basis_gates, target=target), - CommutativeCancellation(basis_gates=basis_gates, target=target), - ] - unroll_3q = None # Build pass manager if coupling_map or initial_layout: @@ -118,25 +96,10 @@ 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) - if optimization_method is None: - optimization = PassManager() - unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] - # Build nested Flow controllers - def _unroll_condition(property_set): - return not property_set["all_gates_in_basis"] - - # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis: list[BasePass | FlowController] = [ - GatesInBasis(basis_gates, target=target), - ConditionalController(unroll, condition=_unroll_condition), - ] - optimization.append(_depth_check + _size_check) - opt_loop = _opt + _unroll_if_out_of_basis + _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 - ) + + optimization = plugin_manager.get_passmanager_stage( + "optimization", optimization_method, pass_manager_config, optimization_level=2 + ) sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=2 diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 8b6f28e58137..1e4c7d589b35 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -16,23 +16,11 @@ gate cancellation using commutativity rules and unitary synthesis. """ from __future__ import annotations -from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.passmanager_config import PassManagerConfig -from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import StagedPassManager -from qiskit.transpiler.passes import MinimumPoint -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 OptimizeSwapBeforeMeasure from qiskit.transpiler.passes import RemoveDiagonalGatesBeforeMeasure -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import GatesInBasis -from qiskit.transpiler.runningpassmanager import ConditionalController, FlowController from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler.preset_passmanagers.plugin import ( PassManagerStagePluginManager, @@ -70,9 +58,9 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa layout_method = pass_manager_config.layout_method or "default" 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 + optimization_method = pass_manager_config.optimization_method or "default" scheduling_method = pass_manager_config.scheduling_method or "default" - backend_properties = pass_manager_config.backend_properties approximation_degree = pass_manager_config.approximation_degree unitary_synthesis_method = pass_manager_config.unitary_synthesis_method unitary_synthesis_plugin_config = pass_manager_config.unitary_synthesis_plugin_config @@ -84,35 +72,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa "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. - _minimum_point_check: list[BasePass | FlowController] = [ - Depth(recurse=True), - Size(recurse=True), - MinimumPoint(["depth", "size"], "optimization_loop"), - ] - - def _opt_control(property_set): - return not property_set["optimization_loop_minimum_point"] - - _opt: list[BasePass | FlowController] = [ - Collect2qBlocks(), - ConsolidateBlocks( - basis_gates=basis_gates, target=target, approximation_degree=approximation_degree - ), - 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, - ), - Optimize1qGatesDecomposition(basis=basis_gates, target=target), - CommutativeCancellation(target=target), - ] - # Build pass manager init = common.generate_control_flow_options_check( layout_method=layout_method, @@ -152,49 +111,20 @@ def _opt_control(property_set): "translation", translation_method, pass_manager_config, optimization_level=3 ) - if optimization_method is None: - optimization = PassManager() - unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] - # Build nested Flow controllers - def _unroll_condition(property_set): - return not property_set["all_gates_in_basis"] - - # Check if any gate is not in the basis, and if so, run unroll passes - _unroll_if_out_of_basis: list[BasePass | FlowController] = [ - GatesInBasis(basis_gates, target=target), - ConditionalController(unroll, condition=_unroll_condition), + 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) + _direction = [ + pass_ + for x in common.generate_pre_op_passmanager(target, coupling_map).passes() + for pass_ in x["passes"] ] - - optimization.append(_minimum_point_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"] - ] - if optimization is not None: - optimization.append( - _opt + _unroll_if_out_of_basis + _minimum_point_check, - do_while=_opt_control, - ) - else: - pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) - optimization.append( - _opt + _unroll_if_out_of_basis + _minimum_point_check, do_while=_opt_control - ) else: - 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) + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) sched = plugin_manager.get_passmanager_stage( "scheduling", scheduling_method, pass_manager_config, optimization_level=3 diff --git a/qiskit/transpiler/preset_passmanagers/plugin.py b/qiskit/transpiler/preset_passmanagers/plugin.py index aa935fd7bb77..d46a892e11c7 100644 --- a/qiskit/transpiler/preset_passmanagers/plugin.py +++ b/qiskit/transpiler/preset_passmanagers/plugin.py @@ -76,7 +76,7 @@ instruction on the target backend. * - ``optimization`` - ``qiskit.transpiler.optimization`` - - There are no reserved plugin names + - ``default`` - 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 diff --git a/releasenotes/notes/default-reserved-plugin-name-3825c2000a579e38.yaml b/releasenotes/notes/default-reserved-plugin-name-3825c2000a579e38.yaml index 904ad9b4b926..ebb207eb837b 100644 --- a/releasenotes/notes/default-reserved-plugin-name-3825c2000a579e38.yaml +++ b/releasenotes/notes/default-reserved-plugin-name-3825c2000a579e38.yaml @@ -2,8 +2,8 @@ upgrade: - | The plugin name ``default`` is reserved for the :ref:`stage_table` - ``layout`` and ``scheduling``. These stages previously did not reserve this - plugin name, but the ``default`` name is now used to represent Qiskit's - built-in default method for these stages. If you were using these names - for plugins on these stages these will conflict with Qiskit's usage and - you should rename your plugin. + ``layout``, ``optimization``, and ``scheduling``. These stages previously + did not reserve this plugin name, but the ``default`` name is now used to + represent Qiskit's built-in default method for these stages. If you were + using these names for plugins on these stages these will conflict with + Qiskit's usage and you should rename your plugin. diff --git a/setup.py b/setup.py index 6cb88200eff2..77f39a7aa068 100644 --- a/setup.py +++ b/setup.py @@ -146,6 +146,9 @@ "sabre = qiskit.transpiler.preset_passmanagers.builtin_plugins:SabreSwapPassManager", "none = qiskit.transpiler.preset_passmanagers.builtin_plugins:NoneRoutingPassManager", ], + "qiskit.transpiler.optimization": [ + "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:OptimizationPassManager", + ], "qiskit.transpiler.layout": [ "default = qiskit.transpiler.preset_passmanagers.builtin_plugins:DefaultLayoutPassManager", "trivial = qiskit.transpiler.preset_passmanagers.builtin_plugins:TrivialLayoutPassManager", diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 268bf4ea1f72..7074d0f2a8ea 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -47,6 +47,7 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.preset_passmanagers import level0, level1, level2, level3 from qiskit.transpiler.passes import Collect2qBlocks, GatesInBasis +from qiskit.transpiler.preset_passmanagers.builtin_plugins import OptimizationPassManager def mock_get_passmanager_stage( @@ -71,6 +72,8 @@ def mock_get_passmanager_stage( return pm elif stage_name == "routing": return PassManager([]) + elif stage_name == "optimization": + return OptimizationPassManager().pass_manager(pm_config, optimization_level) elif stage_name == "layout": return PassManager([]) else: