From e45b1152f12e5c8f57a4802f04d50dcbda29c336 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 12 May 2021 09:42:49 -0400 Subject: [PATCH 01/27] Add FullPassManager class for pass manager with defined stages This commit adds a new PassManager subclass, FullPassManager. This class is used to have a PassManager with a defined structure and stages for the normal transpile workflow. The preset pass managers are then updated to be FullPassManager objects they conform to the fixed structure. Having a class with defined phases gives us flexibility in the future for making the transpiler pluggable with external plugins (similar to what's done in PR #6124) and also have backend hook points before or after different phases of the transpile. Fixes #5978 --- qiskit/transpiler/passmanager.py | 154 ++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 41 +++-- .../transpiler/preset_passmanagers/level1.py | 47 ++++-- .../transpiler/preset_passmanagers/level2.py | 49 ++++-- .../transpiler/preset_passmanagers/level3.py | 52 ++++-- ...add-full-passmanager-5a377f1b71480f72.yaml | 19 +++ 6 files changed, 297 insertions(+), 65 deletions(-) create mode 100644 releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 473d7bc92df9..d0b25ee8637f 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -312,3 +312,157 @@ def passes(self) -> List[Dict[str, BasePass]]: item["flow_controllers"] = {} ret.append(item) return ret + + +class FullPassManager(PassManager): + """A full Pass manager pipeline for a backend + + Instances of FullPassManager define a full compilation pipeline from a abstract virtual + circuit to one that is optimized and capable of running on the specified backend. It is + built using predefined stages: + + 1. Init - any initial passes that are run before we start embedding the circuit to the backend + 2. Layout - This stage runs layout and maps the virtual qubits in the + circuit to the physical qubits on a backend + 3. Routing - This stage runs after a layout has been run and will insert any + necessary gates to move the qubit states around until it can be run on + backend's compuling map. + 4. Translation - Perform the basis gate translation, in other words translate the gates + in the circuit to the target backend's basis set + 5. Pre-Optimization - Any passes to run before the main optimization loop + 6. Optimization - The main optimization loop, this will typically run in a loop trying to optimize + the circuit until a condtion (such as fixed depth) is reached. + 7. Post-Optimization - Any passes to run after the main optimization loop + + """ + + phases = [ + "init", + "layout", + "routing", + "translation", + "pre_optimization", + "optimization", + "post_optimization", + ] + + def __init__( + self, + init=None, + layout=None, + routing=None, + translation=None, + pre_optimization=None, + optimization=None, + post_optimization=None, + ): + """Initialize a new FullPassManager object + + Args: + init (PassManager): A passmanager to run for the initial stage of the + compilation. + layout (PassManager): A passmanager to run for the layout stage of the + compilation. + routing (PassManager): A pass manager to run for the routing stage + of the compilation + translation (PassManager): A pass manager to run for the translation + stage of the compilation + pre_opt (PassManager): A pass manager to run before the optimization + loop + optimization (PassManager): A pass manager to run for the + optimization loop stage + post_opt (PassManager): A pass manager to run after the optimization + loop + """ + super().__init__() + self._init = init + self._layout = layout + self._routing = routing + self._translation = translation + self._pre_optimization = pre_optimization + self._optimization = optimization + self._post_optimization = post_optimization + self._update_passmanager() + + def _update_passmanager(self): + self._pass_sets = [] + if self._init: + self._pass_sets.extend(self._init._pass_sets) + if self._layout: + self._pass_sets.extend(self._layout._pass_sets) + if self._routing: + self._pass_sets.extend(self._routing._pass_sets) + if self._translation: + self._pass_sets.extend(self._translation._pass_sets) + if self._pre_optimization: + self._pass_sets.extend(self._pre_optimization._pass_sets) + if self._optimization: + self._pass_sets.extend(self._optimization._pass_sets) + if self._post_optimization: + self._pass_sets.extend(self._post_optimization._pass_sets) + + @property + def init(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the init stage.""" + return self._init + + @init.setter + def init(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the init stage.""" + self._init = value + self._update_passmanager() + + @property + def layout(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the layout stage.""" + return self._layout + + @layout.setter + def layout(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the layout stage.""" + self._layout = value + self._update_passmanager() + + @property + def routing(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the routing stage.""" + return self._routing + + @routing.setter + def routing(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the routing stage.""" + self._routing = value + self._update_passmanager() + + @property + def pre_optimization(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the pre_optimization stage.""" + return self._pre_optimization + + @pre_optimization.setter + def pre_optimization(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the pre_optimization stage.""" + self._pre_optimization = value + self._update_passmanager() + + @property + def optimization(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the optimization stage.""" + return self._optimization + + @optimization.setter + def optimization(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the optimization stage.""" + self._optimization = value + self._update_passmanager() + + @property + def post_optimization(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the post_optimization stage.""" + return self._post_optimization + + @post_optimization.setter + def post_optimization(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the post_optimization stage.""" + self._post_optimization = value + self._update_passmanager() diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index e34cb4cd92a2..72bc38997e5c 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -17,6 +17,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import FullPassManager from qiskit.transpiler.passes import Unroller from qiskit.transpiler.passes import BasisTranslator @@ -49,7 +50,7 @@ from qiskit.transpiler import TranspilerError -def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> FullPassManager: """Level 0 pass manager: no explicit optimization other than mapping to backend. This pass manager applies the user-given initial layout. If none is given, a trivial @@ -170,18 +171,30 @@ def _direction_condition(property_set): raise TranspilerError("Invalid scheduling method %s." % scheduling_method) # Build pass manager - pm0 = PassManager() if coupling_map or initial_layout: - pm0.append(_given_layout) - pm0.append(_choose_layout, condition=_choose_layout_condition) - pm0.append(_embed) - pm0.append(_unroll3q) - pm0.append(_swap_check) - pm0.append(_swap, condition=_swap_condition) - pm0.append(_unroll) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout, condition=_choose_layout_condition) + layout.append(_embed) + routing = PassManager() + routing.append(_unroll3q) + routing.append(_swap_check) + routing.append(_swap, condition=_swap_condition) + else: + layout = None + routing = None + translation = PassManager(_unroll) if coupling_map and not coupling_map.is_symmetric: - pm0.append(_direction_check) - pm0.append(_direction, condition=_direction_condition) - pm0.append(_unroll) - pm0.append(_scheduling) - return pm0 + pre_opt = PassManager(_direction_check) + pre_opt.append(_direction, condition=_direction_condition) + pre_opt.append(_unroll) + else: + pre_opt = None + post_opt = PassManager(_scheduling) + return FullPassManager( + layout=layout, + routing=routing, + translation=translation, + pre_optimization=pre_opt, + post_optimization=post_opt, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index deab87f30467..78d504b547d4 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -17,6 +17,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import FullPassManager from qiskit.transpiler.passes import Unroller from qiskit.transpiler.passes import BasisTranslator @@ -201,21 +202,35 @@ def _opt_control(property_set): raise TranspilerError("Invalid scheduling method %s." % scheduling_method) # Build pass manager - pm1 = PassManager() + if coupling_map or initial_layout: - pm1.append(_given_layout) - pm1.append(_choose_layout_and_score, condition=_choose_layout_condition) - pm1.append(_improve_layout, condition=_not_perfect_yet) - pm1.append(_embed) - pm1.append(_unroll3q) - pm1.append(_swap_check) - pm1.append(_swap, condition=_swap_condition) - pm1.append(_unroll) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout_and_score, condition=_choose_layout_condition) + layout.append(_improve_layout, condition=_not_perfect_yet) + layout.append(_embed) + routing = PassManager() + routing.append(_unroll3q) + routing.append(_swap_check) + routing.append(_swap, condition=_swap_condition) + else: + layout = None + routing = None + translation = PassManager(_unroll) + pre_optimization = PassManager() if coupling_map and not coupling_map.is_symmetric: - pm1.append(_direction_check) - pm1.append(_direction, condition=_direction_condition) - pm1.append(_reset) - pm1.append(_depth_check + _opt + _unroll, do_while=_opt_control) - pm1.append(_scheduling) - - return pm1 + pre_optimization.append(_direction_check) + pre_optimization.append(_direction, condition=_direction_condition) + pre_optimization.append(_reset) + optimization = PassManager() + optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) + post_optimization = PassManager(_scheduling) + + return FullPassManager( + layout=layout, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + post_optimization=post_optimization, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d26222b3cc61..5e27535bbd12 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -18,6 +18,8 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import FullPassManager + from qiskit.transpiler.passes import Unroller from qiskit.transpiler.passes import BasisTranslator @@ -238,22 +240,35 @@ def _opt_control(property_set): raise TranspilerError("Invalid scheduling method %s." % scheduling_method) # Build pass manager - pm2 = PassManager() if coupling_map or initial_layout: - pm2.append(_given_layout) - pm2.append(_choose_layout_0, condition=_choose_layout_condition) - pm2.append(_choose_layout_1, condition=_trivial_not_perfect) - pm2.append(_choose_layout_2, condition=_csp_not_found_match) - pm2.append(_embed) - pm2.append(_unroll3q) - pm2.append(_swap_check) - pm2.append(_swap, condition=_swap_condition) - pm2.append(_unroll) + 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(_choose_layout_2, condition=_csp_not_found_match) + layout.append(_embed) + routing = PassManager() + routing.append(_unroll3q) + routing.append(_swap_check) + routing.append(_swap, condition=_swap_condition) + else: + layout = None + routing = None + translation = PassManager(_unroll) + pre_optimization = PassManager() if coupling_map and not coupling_map.is_symmetric: - pm2.append(_direction_check) - pm2.append(_direction, condition=_direction_condition) - pm2.append(_reset) - pm2.append(_depth_check + _opt + _unroll, do_while=_opt_control) - pm2.append(_scheduling) - - return pm2 + pre_optimization.append(_direction_check) + pre_optimization.append(_direction, condition=_direction_condition) + pre_optimization.append(_reset) + optimization = PassManager() + optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) + post_optimization = PassManager(_scheduling) + + return FullPassManager( + layout=layout, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + post_optimization=post_optimization, + ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 257bc4babe88..3c9aef8f7376 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -19,6 +19,8 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passmanager import FullPassManager + from qiskit.transpiler.passes import Unroller from qiskit.transpiler.passes import BasisTranslator @@ -246,23 +248,37 @@ def _opt_control(property_set): raise TranspilerError("Invalid scheduling method %s." % scheduling_method) # Build pass manager - pm3 = PassManager() - pm3.append(_unroll3q) - pm3.append(_reset + _meas) + init = PassManager() + init.append(_unroll3q) + init.append(_reset + _meas) if coupling_map or initial_layout: - pm3.append(_given_layout) - pm3.append(_choose_layout_0, condition=_choose_layout_condition) - pm3.append(_choose_layout_1, condition=_trivial_not_perfect) - pm3.append(_choose_layout_2, condition=_csp_not_found_match) - pm3.append(_embed) - pm3.append(_swap_check) - pm3.append(_swap, condition=_swap_condition) - pm3.append(_unroll) + layout = PassManager() + layout.append(_given_layout) + layout.append(_choose_layout_0, condition=_choose_layout_condition) + layout.append(_choose_layout_1, condition=_trivial_not_perfect) + layout.append(_choose_layout_2, condition=_csp_not_found_match) + layout.append(_embed) + routing = PassManager() + routing.append(_swap_check) + routing.append(_swap, condition=_swap_condition) + else: + layout = None + routing = None + translation = PassManager(_unroll) + pre_optimization = PassManager() if coupling_map and not coupling_map.is_symmetric: - pm3.append(_direction_check) - pm3.append(_direction, condition=_direction_condition) - pm3.append(_reset) - pm3.append(_depth_check + _opt + _unroll, do_while=_opt_control) - pm3.append(_scheduling) - - return pm3 + pre_optimization.append(_direction_check) + pre_optimization.append(_direction, condition=_direction_condition) + pre_optimization.append(_reset) + optimization = PassManager() + optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) + post_optimization = PassManager(_scheduling) + return FullPassManager( + init=init, + layout=layout, + routing=routing, + translation=translation, + pre_optimization=pre_optimization, + optimization=optimization, + post_optimization=post_optimization, + ) diff --git a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml new file mode 100644 index 000000000000..aa4c9ce79bc9 --- /dev/null +++ b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml @@ -0,0 +1,19 @@ +--- +features: + - | + Added a new class, :class:`qiskit.transpiler.FullPassManager`, which is + a :class:`~qiskit.transpiler.PassManager` subclass that has a pipeline + with defined phases to the circuit compilation. Each phase is a + :class:`~qiskit.transpiler.PassManager` object that will get executed + in a fixed order. For example:: + + from qiskit.transpiler.passes import * + from qiskit.transpiler import PassManager, FullPassManager + + basis_gates = ['rx', 'ry', 'rxx'] + init = PassManager([UnitarySynthesis(basis_gates), Unroll3qOrMore()]) + translate = PassManager([Collect2qBlocks(), + ConsolidateBlocks(basis_gates=basis_gates), + UnitarySynthesis(basis_gates)]) + + full_pass_manager = FullPassManager(init=init, translation=translate) From 90c242cb163d1d6245df36c25f372a146a6e1af7 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 12 May 2021 13:55:50 -0400 Subject: [PATCH 02/27] Add docs --- qiskit/transpiler/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 14365ebaec62..642436583d5f 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -364,6 +364,7 @@ .. autosummary:: :toctree: ../stubs/ + FullPassManager PassManager PassManagerConfig PropertySet @@ -417,6 +418,7 @@ from .runningpassmanager import FlowController from .passmanager import PassManager from .passmanager_config import PassManagerConfig +from .passmanager import FullPassManager from .propertyset import PropertySet from .exceptions import TranspilerError, TranspilerAccessError from .fencedobjs import FencedDAGCircuit, FencedPropertySet From 53f34bab2cecd43bcdc5eea65a9d2994c52305dd Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 2 Jun 2021 11:33:11 -0400 Subject: [PATCH 03/27] Deduplicate preset passmanager construction --- .../transpiler/preset_passmanagers/common.py | 160 ++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 109 +++--------- .../transpiler/preset_passmanagers/level1.py | 127 ++++---------- .../transpiler/preset_passmanagers/level2.py | 131 ++++---------- .../transpiler/preset_passmanagers/level3.py | 126 ++++---------- 5 files changed, 274 insertions(+), 379 deletions(-) create mode 100644 qiskit/transpiler/preset_passmanagers/common.py diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py new file mode 100644 index 000000000000..c1ea7946e215 --- /dev/null +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -0,0 +1,160 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=invalid-name + +"""Common preset passmanager generators.""" + +from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel + +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.passes import Unroller +from qiskit.transpiler.passes import BasisTranslator +from qiskit.transpiler.passes import UnrollCustomDefinitions +from qiskit.transpiler.passes import Unroll3qOrMore +from qiskit.transpiler.passes import Collect2qBlocks +from qiskit.transpiler.passes import ConsolidateBlocks +from qiskit.transpiler.passes import UnitarySynthesis +from qiskit.transpiler.passes import CheckMap +from qiskit.transpiler.passes import GateDirection +from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements +from qiskit.transpiler.passes import CheckGateDirection +from qiskit.transpiler.passes import TimeUnitConversion +from qiskit.transpiler.passes import ALAPSchedule +from qiskit.transpiler.passes import ASAPSchedule +from qiskit.transpiler.passes import FullAncillaAllocation +from qiskit.transpiler.passes import EnlargeWithAncilla +from qiskit.transpiler.passes import ApplyLayout +from qiskit.transpiler.passes import RemoveResetInZeroState +from qiskit.transpiler.exceptions import TranspilerError + + +def generate_embed_passmanager(coupling_map): + """Generate a layout embedding :class:`~qiskit.transpiler.PassManager` + + This is used to generate a :class:`~qiskit.transpiler.PassManager` object + that can be used to expand and apply an initial layout to a circuit + + Args: + coupling_map (CouplingMap): The coupling map for the backend to embed + the circuit to. + Returns: + PassManager: The embedding passmanager that assumes the layout property + set has been set in earlier stages + """ + return PassManager([FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()]) + + +def generate_routing_passmanager(routing_pass, coupling_map): + """Generate a routing :class:`~qiskit.transpiler.PassManager` + + Args: + routing_pass (TransformationPass): The pass which will perform the + routing + coupling_map (CouplingMap): The coupling map of the backend to route + for + Returns: + PassManager: The routing pass manager + """ + routing = PassManager() + routing.append(Unroll3qOrMore()) + routing.append(CheckMap(coupling_map)) + + def _swap_condition(property_set): + return not property_set["is_swap_mapped"] + + routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition) + return routing + + +def generate_pre_op_passmanager(coupling_map=None, remove_reset_in_zero=False): + """Generate a pre-optimization loop :class:`~qiskit.transpiler.PassManager` + + This pass manager will check to ensure that directionality from the coupling + map is respected + + Args: + coupling_map (CouplingMap): The coupling map to use + remove_reset_in_zero (bool): If ``True`` include the remove reset in + zero pass in the generated PassManager + Returns: + PassManager: The pass manager + + """ + pre_opt = PassManager() + if coupling_map: + pre_opt.append(CheckGateDirection(coupling_map)) + + def _direction_condition(property_set): + return not property_set["is_direction_mapped"] + + pre_opt.append([GateDirection(coupling_map)], condition=_direction_condition) + if remove_reset_in_zero: + pre_opt.append(RemoveResetInZeroState()) + return pre_opt + + +def generate_translation_passmanager(basis_gates, method="basis", approximation_degree=None): + """Generate a basis translation :class:`~qiskit.transpiler.PassManager` + + Args: + basis_gates (list): A list + method (str): The basis translation method to use + approximation_degree (float): The heuristic approximation degree to + use. Can be between 0 and 1. + + Returns: + PassManager: The basis translation pass manager + + Raises: + TranspilerError: If the ``method`` kwarg is not a valid value + """ + if method == "unroller": + unroll = [Unroller(basis_gates)] + elif method == "translator": + unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] + elif method == "synthesis": + unroll = [ + Unroll3qOrMore(), + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=basis_gates), + UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + ] + else: + raise TranspilerError("Invalid translation method %s." % method) + return PassManager(unroll) + + +def generate_scheduling_post_opt(instruction_durations, scheduling_method): + """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager` + + Args: + instruction_durations (dict): The dictionary of instruction durations + scheduling_method (str): The scheduling method to use, can either be + ``'asap'``/``'as_soon_as_possible'`` or + ``'alap'``/``'as_late_as_possible'`` + + Returns: + PassManager: The scheduling pass manager + + Raises: + TranspilerError: If the ``scheduling_method`` kwarg is not a valid value + """ + scheduling = PassManager([TimeUnitConversion(instruction_durations)]) + if scheduling_method: + if scheduling_method in {"alap", "as_late_as_possible"}: + scheduling.append(ALAPSchedule(instruction_durations)) + elif scheduling_method in {"asap", "as_soon_as_possible"}: + scheduling.append(ASAPSchedule(instruction_durations)) + else: + raise TranspilerError("Invalid scheduling method %s." % scheduling_method) + return scheduling diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 72bc38997e5c..cd90ffd6d00b 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -19,34 +19,17 @@ from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import FullPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPSchedule -from qiskit.transpiler.passes import ASAPSchedule from qiskit.transpiler.passes import Error - +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler import TranspilerError @@ -85,7 +68,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> FullPassMana backend_properties = pass_manager_config.backend_properties approximation_degree = pass_manager_config.approximation_degree - # 1. Choose an initial layout if not set by user (default: trivial layout) + # Choose an initial layout if not set by user (default: trivial layout) _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -102,95 +85,43 @@ def _choose_layout_condition(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 2. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 3. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - _swap = [BarrierBeforeFinalMeasurements()] + # Choose routing pass if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=2, search_width=2)] + routing_pass = LookaheadSwap(coupling_map, search_depth=2, search_width=2) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="basic", seed=seed_transpiler) elif routing_method == "none": - _swap += [ - Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] - elif translation_method == "synthesis": - _unroll = [ - Unroll3qOrMore(), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map)] - - # 7. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - _scheduling = [TimeUnitConversion(instruction_durations)] - if scheduling_method: - if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] - elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] - else: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) - # Build pass manager if coupling_map or initial_layout: layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout, condition=_choose_layout_condition) - layout.append(_embed) - routing = PassManager() - routing.append(_unroll3q) - routing.append(_swap_check) - routing.append(_swap, condition=_swap_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = common.generate_routing_passmanager(routing_pass, coupling_map) else: layout = None routing = None - translation = PassManager(_unroll) + translation = common.generate_translation_passmanager( + basis_gates, translation_method, approximation_degree + ) if coupling_map and not coupling_map.is_symmetric: - pre_opt = PassManager(_direction_check) - pre_opt.append(_direction, condition=_direction_condition) - pre_opt.append(_unroll) + pre_opt = common.generate_pre_op_passmanager(coupling_map) + pre_opt += translation else: pre_opt = None - post_opt = PassManager(_scheduling) + post_opt = common.generate_scheduling_post_opt(instruction_durations, scheduling_method) return FullPassManager( layout=layout, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 78d504b547d4..4693e4258070 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -19,39 +19,22 @@ from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import FullPassManager -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore from qiskit.transpiler.passes import CXCancellation -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth -from qiskit.transpiler.passes import RemoveResetInZeroState from qiskit.transpiler.passes import Optimize1qGatesDecomposition -from qiskit.transpiler.passes import ApplyLayout -from qiskit.transpiler.passes import CheckGateDirection from qiskit.transpiler.passes import Layout2qDistance -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPSchedule -from qiskit.transpiler.passes import ASAPSchedule from qiskit.transpiler.passes import Error +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler import TranspilerError @@ -93,7 +76,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: backend_properties = pass_manager_config.backend_properties approximation_degree = pass_manager_config.approximation_degree - # 1. Use trivial layout if no layout given + # Use trivial layout if no layout given _given_layout = SetLayout(initial_layout) _choose_layout_and_score = [ @@ -104,7 +87,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: def _choose_layout_condition(property_set): return not property_set["layout"] - # 2. Use a better layout on densely connected qubits, if circuit needs swaps + # Use a better layout on densely connected qubits, if circuit needs swaps if layout_method == "trivial": _improve_layout = TrivialLayout(coupling_map) elif layout_method == "dense": @@ -122,67 +105,25 @@ def _not_perfect_yet(property_set): and property_set["trivial_layout_score"] != 0 ) - # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 4. Decompose so only 1-qubit and 2-qubit gates remain - _unroll3q = Unroll3qOrMore() - - # 5. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - _swap = [BarrierBeforeFinalMeasurements()] if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=4, search_width=4)] + routing_pass = LookaheadSwap(coupling_map, search_depth=4, search_width=4) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="lookahead", seed=seed_transpiler) elif routing_method == "none": - _swap += [ - Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 6. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] - elif translation_method == "synthesis": - _unroll = [ - Unroll3qOrMore(), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 7. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map)] - - # 8. Remove zero-state reset - _reset = RemoveResetInZeroState() - - # 9. Merge 1q rotations and cancel CNOT gates iteratively until no more change in depth + # Build optimization loop: merge 1q rotations and cancel CNOT gates iteratively + # until no more change in depth _depth_check = [Depth(), FixedPoint("depth")] def _opt_control(property_set): @@ -190,41 +131,31 @@ def _opt_control(property_set): _opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()] - # 10. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - _scheduling = [TimeUnitConversion(instruction_durations)] - if scheduling_method: - if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] - elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] - else: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) - - # Build pass manager - + # Build full pass manager if coupling_map or initial_layout: layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout_and_score, condition=_choose_layout_condition) layout.append(_improve_layout, condition=_not_perfect_yet) - layout.append(_embed) - routing = PassManager() - routing.append(_unroll3q) - routing.append(_swap_check) - routing.append(_swap, condition=_swap_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = common.generate_routing_passmanager(routing_pass, coupling_map) else: layout = None routing = None - translation = PassManager(_unroll) - pre_optimization = PassManager() + translation = common.generate_translation_passmanager( + basis_gates, translation_method, approximation_degree + ) if coupling_map and not coupling_map.is_symmetric: - pre_optimization.append(_direction_check) - pre_optimization.append(_direction, condition=_direction_condition) - pre_optimization.append(_reset) + pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) optimization = PassManager() - optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) - post_optimization = PassManager(_scheduling) + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + opt_loop = _depth_check + _opt + unroll + optimization.append(opt_loop, do_while=_opt_control) + post_optimization = common.generate_scheduling_post_opt( + instruction_durations, scheduling_method + ) return FullPassManager( layout=layout, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 5e27535bbd12..629ce3f92d17 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -20,41 +20,25 @@ from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import FullPassManager - -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions -from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import CSPLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth -from qiskit.transpiler.passes import RemoveResetInZeroState from qiskit.transpiler.passes import Optimize1qGatesDecomposition from qiskit.transpiler.passes import CommutativeCancellation -from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import Layout2qDistance -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import ConsolidateBlocks -from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPSchedule -from qiskit.transpiler.passes import ASAPSchedule + + from qiskit.transpiler.passes import Error +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler import TranspilerError @@ -98,14 +82,14 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: backend_properties = pass_manager_config.backend_properties approximation_degree = pass_manager_config.approximation_degree - # 1. Search for a perfect layout, or choose a dense layout, if no layout given + # Search for a perfect layout, or choose a dense layout, if no layout given _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): # layout hasn't been set yet return not property_set["layout"] - # 1a. If layout_method is not set, first try a trivial layout + # If layout_method is not set, first try a trivial layout _choose_layout_0 = ( [] if pass_manager_config.layout_method @@ -114,7 +98,7 @@ def _choose_layout_condition(property_set): Layout2qDistance(coupling_map, property_name="trivial_layout_score"), ] ) - # 1b. If a trivial layout wasn't perfect (ie no swaps are needed) then try using + # If a trivial layout wasn't perfect (ie no swaps are needed) then try using # CSP layout to find a perfect layout _choose_layout_1 = ( [] @@ -145,7 +129,7 @@ def _csp_not_found_match(property_set): return True return False - # 1c. if CSP layout doesn't converge on a solution use layout_method (dense) to get a layout + # if CSP layout doesn't converge on a solution use layout_method (dense) to get a layout if layout_method == "trivial": _choose_layout_2 = TrivialLayout(coupling_map) elif layout_method == "dense": @@ -157,67 +141,25 @@ def _csp_not_found_match(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 2. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 3. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - _swap = [BarrierBeforeFinalMeasurements()] if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=20, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=20, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=5)] + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=5) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) elif routing_method == "none": - _swap += [ - Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] - elif translation_method == "synthesis": - _unroll = [ - Unroll3qOrMore(), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any bad CX directions - _direction_check = [CheckGateDirection(coupling_map)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map)] - - # 7. Remove zero-state reset - _reset = RemoveResetInZeroState() - - # 8. 1q rotation merge and commutative cancellation iteratively until no more change in depth + # Build optimization loop: 1q rotation merge and commutative cancellation iteratively until + # no more change in depth _depth_check = [Depth(), FixedPoint("depth")] def _opt_control(property_set): @@ -228,17 +170,6 @@ def _opt_control(property_set): CommutativeCancellation(basis_gates=basis_gates), ] - # 9. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - _scheduling = [TimeUnitConversion(instruction_durations)] - if scheduling_method: - if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] - elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] - else: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) - # Build pass manager if coupling_map or initial_layout: layout = PassManager() @@ -246,23 +177,25 @@ def _opt_control(property_set): layout.append(_choose_layout_0, condition=_choose_layout_condition) layout.append(_choose_layout_1, condition=_trivial_not_perfect) layout.append(_choose_layout_2, condition=_csp_not_found_match) - layout.append(_embed) - routing = PassManager() - routing.append(_unroll3q) - routing.append(_swap_check) - routing.append(_swap, condition=_swap_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = common.generate_routing_passmanager(routing_pass, coupling_map) else: layout = None routing = None - translation = PassManager(_unroll) - pre_optimization = PassManager() + translation = common.generate_translation_passmanager( + basis_gates, translation_method, approximation_degree + ) if coupling_map and not coupling_map.is_symmetric: - pre_optimization.append(_direction_check) - pre_optimization.append(_direction, condition=_direction_condition) - pre_optimization.append(_reset) + pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) optimization = PassManager() - optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) - post_optimization = PassManager(_scheduling) + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + opt_loop = _depth_check + _opt + unroll + optimization.append(opt_loop, do_while=_opt_control) + post_optimization = common.generate_scheduling_post_opt( + instruction_durations, scheduling_method + ) return FullPassManager( layout=layout, diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 3c9aef8f7376..37045e56c047 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -21,26 +21,17 @@ from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passmanager import FullPassManager - -from qiskit.transpiler.passes import Unroller -from qiskit.transpiler.passes import BasisTranslator -from qiskit.transpiler.passes import UnrollCustomDefinitions from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import CheckMap -from qiskit.transpiler.passes import GateDirection from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import CSPLayout from qiskit.transpiler.passes import TrivialLayout from qiskit.transpiler.passes import DenseLayout from qiskit.transpiler.passes import NoiseAdaptiveLayout from qiskit.transpiler.passes import SabreLayout -from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements from qiskit.transpiler.passes import BasicSwap from qiskit.transpiler.passes import LookaheadSwap from qiskit.transpiler.passes import StochasticSwap from qiskit.transpiler.passes import SabreSwap -from qiskit.transpiler.passes import FullAncillaAllocation -from qiskit.transpiler.passes import EnlargeWithAncilla from qiskit.transpiler.passes import FixedPoint from qiskit.transpiler.passes import Depth from qiskit.transpiler.passes import RemoveResetInZeroState @@ -51,13 +42,9 @@ from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis -from qiskit.transpiler.passes import ApplyLayout from qiskit.transpiler.passes import Layout2qDistance -from qiskit.transpiler.passes import CheckGateDirection -from qiskit.transpiler.passes import TimeUnitConversion -from qiskit.transpiler.passes import ALAPSchedule -from qiskit.transpiler.passes import ASAPSchedule from qiskit.transpiler.passes import Error +from qiskit.transpiler.preset_passmanagers import common from qiskit.transpiler import TranspilerError @@ -101,10 +88,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: backend_properties = pass_manager_config.backend_properties approximation_degree = pass_manager_config.approximation_degree - # 1. Unroll to 1q or 2q gates - _unroll3q = Unroll3qOrMore() - - # 2. Layout on good qubits if calibration info available, otherwise on dense links + # Layout on good qubits if calibration info available, otherwise on dense links _given_layout = SetLayout(initial_layout) def _choose_layout_condition(property_set): @@ -163,60 +147,23 @@ def _trivial_not_perfect(property_set): else: raise TranspilerError("Invalid layout method %s." % layout_method) - # 3. Extend dag/layout with ancillas using the full coupling map - _embed = [FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout()] - - # 4. Swap to fit the coupling map - _swap_check = CheckMap(coupling_map) - - def _swap_condition(property_set): - return not property_set["is_swap_mapped"] - - _swap = [BarrierBeforeFinalMeasurements()] if routing_method == "basic": - _swap += [BasicSwap(coupling_map)] + routing_pass = BasicSwap(coupling_map) elif routing_method == "stochastic": - _swap += [StochasticSwap(coupling_map, trials=200, seed=seed_transpiler)] + routing_pass = StochasticSwap(coupling_map, trials=200, seed=seed_transpiler) elif routing_method == "lookahead": - _swap += [LookaheadSwap(coupling_map, search_depth=5, search_width=6)] + routing_pass = LookaheadSwap(coupling_map, search_depth=5, search_width=6) elif routing_method == "sabre": - _swap += [SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler)] + routing_pass = SabreSwap(coupling_map, heuristic="decay", seed=seed_transpiler) elif routing_method == "none": - _swap += [ - Error( - msg="No routing method selected, but circuit is not routed to device. " - "CheckMap Error: {check_map_msg}", - action="raise", - ) - ] + routing_pass = Error( + msg="No routing method selected, but circuit is not routed to device. " + "CheckMap Error: {check_map_msg}", + action="raise", + ) else: raise TranspilerError("Invalid routing method %s." % routing_method) - # 5. Unroll to the basis - if translation_method == "unroller": - _unroll = [Unroller(basis_gates)] - elif translation_method == "translator": - from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel - - _unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] - elif translation_method == "synthesis": - _unroll = [ - Unroll3qOrMore(), - Collect2qBlocks(), - ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), - ] - else: - raise TranspilerError("Invalid translation method %s." % translation_method) - - # 6. Fix any CX direction mismatch - _direction_check = [CheckGateDirection(coupling_map)] - - def _direction_condition(property_set): - return not property_set["is_direction_mapped"] - - _direction = [GateDirection(coupling_map)] - # 8. Optimize iteratively until no more change in depth. Removes useless gates # after reset and before measure, commutes gates and optimizes contiguous blocks. _depth_check = [Depth(), FixedPoint("depth")] @@ -224,10 +171,6 @@ def _direction_condition(property_set): def _opt_control(property_set): return not property_set["depth_fixed_point"] - _reset = [RemoveResetInZeroState()] - - _meas = [OptimizeSwapBeforeMeasure(), RemoveDiagonalGatesBeforeMeasure()] - _opt = [ Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), @@ -236,43 +179,40 @@ def _opt_control(property_set): CommutativeCancellation(), ] - # 9. Unify all durations (either SI, or convert to dt if known) - # Schedule the circuit only when scheduling_method is supplied - _scheduling = [TimeUnitConversion(instruction_durations)] - if scheduling_method: - if scheduling_method in {"alap", "as_late_as_possible"}: - _scheduling += [ALAPSchedule(instruction_durations)] - elif scheduling_method in {"asap", "as_soon_as_possible"}: - _scheduling += [ASAPSchedule(instruction_durations)] - else: - raise TranspilerError("Invalid scheduling method %s." % scheduling_method) - # Build pass manager - init = PassManager() - init.append(_unroll3q) - init.append(_reset + _meas) + init = PassManager( + [ + Unroll3qOrMore(), + RemoveResetInZeroState(), + OptimizeSwapBeforeMeasure(), + 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=_trivial_not_perfect) layout.append(_choose_layout_2, condition=_csp_not_found_match) - layout.append(_embed) - routing = PassManager() - routing.append(_swap_check) - routing.append(_swap, condition=_swap_condition) + layout += common.generate_embed_passmanager(coupling_map) + routing = common.generate_routing_passmanager(routing_pass, coupling_map) else: layout = None routing = None - translation = PassManager(_unroll) - pre_optimization = PassManager() + translation = common.generate_translation_passmanager( + basis_gates, translation_method, approximation_degree + ) if coupling_map and not coupling_map.is_symmetric: - pre_optimization.append(_direction_check) - pre_optimization.append(_direction, condition=_direction_condition) - pre_optimization.append(_reset) + pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) + else: + pre_optimization = common.generate_pre_op_passmanager(remove_reset_in_zero=True) optimization = PassManager() - optimization.append(_depth_check + _opt + _unroll, do_while=_opt_control) - post_optimization = PassManager(_scheduling) + unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] + opt_loop = _depth_check + _opt + unroll + optimization.append(opt_loop, do_while=_opt_control) + post_optimization = common.generate_scheduling_post_opt( + instruction_durations, scheduling_method + ) return FullPassManager( init=init, layout=layout, From 1bebc5199f61f9501eb61257a410831d07247f8e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 2 Jun 2021 11:45:57 -0400 Subject: [PATCH 04/27] Update docs --- qiskit/transpiler/passmanager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index d0b25ee8637f..469df09b138c 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -323,7 +323,7 @@ class FullPassManager(PassManager): 1. Init - any initial passes that are run before we start embedding the circuit to the backend 2. Layout - This stage runs layout and maps the virtual qubits in the - circuit to the physical qubits on a backend + circuit to the physical qubits on a backend 3. Routing - This stage runs after a layout has been run and will insert any necessary gates to move the qubit states around until it can be run on backend's compuling map. @@ -334,6 +334,10 @@ class FullPassManager(PassManager): the circuit until a condtion (such as fixed depth) is reached. 7. Post-Optimization - Any passes to run after the main optimization loop + These stages will be executed in order and any stage set to ``None`` will be skipped. If + a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here + (for example in the case of a Pass that covers both Layout and Routing) you will want to set + that to the earliest stage in sequence that it covers. """ phases = [ From f177d5f495cd58da8b14d4b9e039f99db02348b6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 10 Sep 2021 08:57:18 -0400 Subject: [PATCH 05/27] Add dedicated scheduling stage to FullPassManager --- qiskit/transpiler/passmanager.py | 18 ++++++++++++++++++ .../transpiler/preset_passmanagers/level0.py | 4 ++-- .../transpiler/preset_passmanagers/level1.py | 4 ++-- .../transpiler/preset_passmanagers/level2.py | 4 ++-- .../transpiler/preset_passmanagers/level3.py | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 469df09b138c..e155f1cac12c 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -333,6 +333,7 @@ class FullPassManager(PassManager): 6. Optimization - The main optimization loop, this will typically run in a loop trying to optimize the circuit until a condtion (such as fixed depth) is reached. 7. Post-Optimization - Any passes to run after the main optimization loop + 8. Scheduling - Any hardware aware scheduling passes These stages will be executed in order and any stage set to ``None`` will be skipped. If a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here @@ -348,6 +349,7 @@ class FullPassManager(PassManager): "pre_optimization", "optimization", "post_optimization", + "scheduling", ] def __init__( @@ -359,6 +361,7 @@ def __init__( pre_optimization=None, optimization=None, post_optimization=None, + scheduling=None, ): """Initialize a new FullPassManager object @@ -377,6 +380,7 @@ def __init__( optimization loop stage post_opt (PassManager): A pass manager to run after the optimization loop + scheduling (PassManager): A pass manager to run any scheduling passes """ super().__init__() self._init = init @@ -386,6 +390,7 @@ def __init__( self._pre_optimization = pre_optimization self._optimization = optimization self._post_optimization = post_optimization + self._scheduling = scheduling self._update_passmanager() def _update_passmanager(self): @@ -404,6 +409,8 @@ def _update_passmanager(self): self._pass_sets.extend(self._optimization._pass_sets) if self._post_optimization: self._pass_sets.extend(self._post_optimization._pass_sets) + if self._scheduling: + self._pass_sets.extend(self._scheduling._pass_sets) @property def init(self): @@ -470,3 +477,14 @@ def post_optimization(self, value): """Set the :class:`~qiskit.transpiler.PassManager` for the post_optimization stage.""" self._post_optimization = value self._update_passmanager() + + @property + def scheduling(self): + """Get the :class:`~qiskit.transpiler.PassManager` for the scheduling stage.""" + return self._post_optimization + + @scheduling.setter + def scheduling(self, value): + """Set the :class:`~qiskit.transpiler.PassManager` for the scheduling stage.""" + self._post_optimization = value + self._update_passmanager() diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 4cf1cd6b0191..8fe9809ed2be 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -124,7 +124,7 @@ def _choose_layout_condition(property_set): pre_opt += translation else: pre_opt = None - post_opt = common.generate_scheduling_post_opt( + sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) return FullPassManager( @@ -132,5 +132,5 @@ def _choose_layout_condition(property_set): routing=routing, translation=translation, pre_optimization=pre_opt, - post_optimization=post_opt, + scheduling=sched, ) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 509c7313d458..b381296d0531 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -156,7 +156,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - post_optimization = common.generate_scheduling_post_opt( + sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) @@ -166,5 +166,5 @@ def _opt_control(property_set): translation=translation, pre_optimization=pre_optimization, optimization=optimization, - post_optimization=post_optimization, + scheduling=sched, ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index f6ead142d80b..ef494056d346 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -194,7 +194,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - post_optimization = common.generate_scheduling_post_opt( + sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) @@ -204,5 +204,5 @@ def _opt_control(property_set): translation=translation, pre_optimization=pre_optimization, optimization=optimization, - post_optimization=post_optimization, + scheduling=sched, ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index b00aedbaf1df..13891318688f 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -218,7 +218,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - post_optimization = common.generate_scheduling_post_opt( + sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) return FullPassManager( @@ -228,5 +228,5 @@ def _opt_control(property_set): translation=translation, pre_optimization=pre_optimization, optimization=optimization, - post_optimization=post_optimization, + scheduling=sched, ) From cc3c4fd72b04db30e05f5a7eaff8d53262692b01 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 10 Sep 2021 09:34:44 -0400 Subject: [PATCH 06/27] Add missing new UnitarySynthesis kwargs after rebase --- .../transpiler/preset_passmanagers/common.py | 18 ++++++++++++++++-- .../transpiler/preset_passmanagers/level1.py | 2 +- .../transpiler/preset_passmanagers/level2.py | 2 +- .../transpiler/preset_passmanagers/level3.py | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 87cc73c798c4..8cfccf1cb47e 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -106,7 +106,9 @@ def _direction_condition(property_set): return pre_opt -def generate_translation_passmanager(basis_gates, method="basis", approximation_degree=None): +def generate_translation_passmanager( + basis_gates, method="basis", approximation_degree=None, coupling_map=None, backend_props=None +): """Generate a basis translation :class:`~qiskit.transpiler.PassManager` Args: @@ -114,6 +116,13 @@ def generate_translation_passmanager(basis_gates, method="basis", approximation_ method (str): The basis translation method to use approximation_degree (float): The heuristic approximation degree to use. Can be between 0 and 1. + coupling_map (CouplingMap): the coupling map of the backend + in case synthesis is done on a physical circuit. The + directionality of the coupling_map will be taken into + account if pulse_optimize is True/None and natural_direction + is True/None. + backend_props (BackendProperties): Properties of a backend to + synthesize for (e.g. gate fidelities). Returns: PassManager: The basis translation pass manager @@ -130,7 +139,12 @@ def generate_translation_passmanager(basis_gates, method="basis", approximation_ Unroll3qOrMore(), Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), - UnitarySynthesis(basis_gates, approximation_degree=approximation_degree), + UnitarySynthesis( + basis_gates, + approximation_degree=approximation_degree, + coupling_map=coupling_map, + backend_props=backend_props, + ), ] else: raise TranspilerError("Invalid translation method %s." % method) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index b381296d0531..e76b682d6369 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -146,7 +146,7 @@ def _opt_control(property_set): layout = None routing = None translation = common.generate_translation_passmanager( - basis_gates, translation_method, approximation_degree + basis_gates, translation_method, approximation_degree, coupling_map, backend_properties ) if coupling_map and not coupling_map.is_symmetric: pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index ef494056d346..f11fc8c00cdf 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -184,7 +184,7 @@ def _opt_control(property_set): layout = None routing = None translation = common.generate_translation_passmanager( - basis_gates, translation_method, approximation_degree + basis_gates, translation_method, approximation_degree, coupling_map, backend_properties ) if coupling_map and not coupling_map.is_symmetric: pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 13891318688f..9da121ae4924 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -208,7 +208,7 @@ def _opt_control(property_set): layout = None routing = None translation = common.generate_translation_passmanager( - basis_gates, translation_method, approximation_degree + basis_gates, translation_method, approximation_degree, coupling_map, backend_properties ) if coupling_map and not coupling_map.is_symmetric: pre_optimization = common.generate_pre_op_passmanager(coupling_map, True) From 19373542b5ec47d0a6223046c86d36e87a4dfbe5 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Oct 2021 16:40:33 -0400 Subject: [PATCH 07/27] Use basis_translator as default method instead of basis Co-authored-by: Kevin Krsulich --- qiskit/transpiler/preset_passmanagers/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 8cfccf1cb47e..8a09596b5a36 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -107,7 +107,7 @@ def _direction_condition(property_set): def generate_translation_passmanager( - basis_gates, method="basis", approximation_degree=None, coupling_map=None, backend_props=None + basis_gates, method="basis_translator", approximation_degree=None, coupling_map=None, backend_props=None ): """Generate a basis translation :class:`~qiskit.transpiler.PassManager` @@ -132,7 +132,7 @@ def generate_translation_passmanager( """ if method == "unroller": unroll = [Unroller(basis_gates)] - elif method == "translator": + elif method == "basis_translator": unroll = [UnrollCustomDefinitions(sel, basis_gates), BasisTranslator(sel, basis_gates)] elif method == "synthesis": unroll = [ From f30ee80d0e76ca570f5972496b3211652b498d44 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Oct 2021 17:26:28 -0400 Subject: [PATCH 08/27] Rename FullPassManager StructuredPassManager --- qiskit/transpiler/__init__.py | 4 ++-- qiskit/transpiler/passmanager.py | 6 +++--- qiskit/transpiler/preset_passmanagers/level0.py | 6 +++--- qiskit/transpiler/preset_passmanagers/level1.py | 4 ++-- qiskit/transpiler/preset_passmanagers/level2.py | 4 ++-- qiskit/transpiler/preset_passmanagers/level3.py | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 642436583d5f..bc8dfd54534b 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -364,7 +364,7 @@ .. autosummary:: :toctree: ../stubs/ - FullPassManager + StructuredPassManager PassManager PassManagerConfig PropertySet @@ -418,7 +418,7 @@ from .runningpassmanager import FlowController from .passmanager import PassManager from .passmanager_config import PassManagerConfig -from .passmanager import FullPassManager +from .passmanager import StructuredPassManager from .propertyset import PropertySet from .exceptions import TranspilerError, TranspilerAccessError from .fencedobjs import FencedDAGCircuit, FencedPropertySet diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index e155f1cac12c..92a744cef6dc 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -314,10 +314,10 @@ def passes(self) -> List[Dict[str, BasePass]]: return ret -class FullPassManager(PassManager): +class StructuredPassManager(PassManager): """A full Pass manager pipeline for a backend - Instances of FullPassManager define a full compilation pipeline from a abstract virtual + Instances of StructuredPassManager define a full compilation pipeline from a abstract virtual circuit to one that is optimized and capable of running on the specified backend. It is built using predefined stages: @@ -363,7 +363,7 @@ def __init__( post_optimization=None, scheduling=None, ): - """Initialize a new FullPassManager object + """Initialize a new StructuredPassManager object Args: init (PassManager): A passmanager to run for the initial stage of the diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 271c73fc34ca..c3afac8c1537 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import FullPassManager +from qiskit.transpiler.passmanager import StructuredPassManager from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout @@ -34,7 +34,7 @@ from qiskit.transpiler import TranspilerError -def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> FullPassManager: +def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StructuredPassManager: """Level 0 pass manager: no explicit optimization other than mapping to backend. This pass manager applies the user-given initial layout. If none is given, a trivial @@ -135,7 +135,7 @@ def _choose_layout_condition(property_set): sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) - return FullPassManager( + return StructuredPassManager( layout=layout, routing=routing, translation=translation, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index ee7507a365e4..d42a360010d9 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import FullPassManager +from qiskit.transpiler.passmanager import StructuredPassManager from qiskit.transpiler.passes import CXCancellation from qiskit.transpiler.passes import SetLayout @@ -168,7 +168,7 @@ def _opt_control(property_set): instruction_durations, scheduling_method, timing_constraints, inst_map ) - return FullPassManager( + return StructuredPassManager( layout=layout, routing=routing, translation=translation, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index e18fc20178b1..29ad38e3c2c5 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -19,7 +19,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import FullPassManager +from qiskit.transpiler.passmanager import StructuredPassManager from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import CSPLayout @@ -206,7 +206,7 @@ def _opt_control(property_set): instruction_durations, scheduling_method, timing_constraints, inst_map ) - return FullPassManager( + return StructuredPassManager( layout=layout, routing=routing, translation=translation, diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index a7ce8d8b56b2..c2c1e2bcc729 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -20,7 +20,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import FullPassManager +from qiskit.transpiler.passmanager import StructuredPassManager from qiskit.transpiler.passes import Unroll3qOrMore from qiskit.transpiler.passes import SetLayout @@ -238,7 +238,7 @@ def _opt_control(property_set): sched = common.generate_scheduling_post_opt( instruction_durations, scheduling_method, timing_constraints, inst_map ) - return FullPassManager( + return StructuredPassManager( init=init, layout=layout, routing=routing, From f336a3444240042560a910d57f1f4d4d73841ed6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 18 Oct 2021 17:31:33 -0400 Subject: [PATCH 09/27] Rename generate_scheduling_post_opt() generate_scheduling() --- qiskit/transpiler/preset_passmanagers/common.py | 4 +--- qiskit/transpiler/preset_passmanagers/level0.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index a24a9224b63a..303af086c083 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -207,9 +207,7 @@ def generate_translation_passmanager( return PassManager(unroll) -def generate_scheduling_post_opt( - instruction_durations, scheduling_method, timing_constraints, inst_map -): +def generate_scheduling(instruction_durations, scheduling_method, timing_constraints, inst_map): """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager` Args: diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index c3afac8c1537..c9ba8d4b2bd9 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -132,7 +132,7 @@ def _choose_layout_condition(property_set): pre_opt += translation else: pre_opt = None - sched = common.generate_scheduling_post_opt( + sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) return StructuredPassManager( diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index d42a360010d9..a45b1ef6a538 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -164,7 +164,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - sched = common.generate_scheduling_post_opt( + sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 29ad38e3c2c5..4387cc90f260 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -202,7 +202,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - sched = common.generate_scheduling_post_opt( + sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index c2c1e2bcc729..2a14f3f06c88 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -235,7 +235,7 @@ def _opt_control(property_set): unroll = [pass_ for x in translation.passes() for pass_ in x["passes"]] opt_loop = _depth_check + _opt + unroll optimization.append(opt_loop, do_while=_opt_control) - sched = common.generate_scheduling_post_opt( + sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) return StructuredPassManager( From 06582d4ae1a6277ddc5a840cfe29ec843dc1c74f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 23 May 2022 10:47:54 -0400 Subject: [PATCH 10/27] Fix missing and incorrect arguments --- qiskit/transpiler/preset_passmanagers/common.py | 4 ++-- qiskit/transpiler/preset_passmanagers/level0.py | 1 + qiskit/transpiler/preset_passmanagers/level1.py | 4 +++- qiskit/transpiler/preset_passmanagers/level2.py | 4 +++- qiskit/transpiler/preset_passmanagers/level3.py | 4 +++- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 9093e2d10631..f2ecd1960351 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -217,7 +217,7 @@ def _direction_condition(property_set): def generate_translation_passmanager( target, basis_gates=None, - method="basis_translator", + method="translator", approximation_degree=None, coupling_map=None, backend_props=None, @@ -253,7 +253,7 @@ def generate_translation_passmanager( """ if method == "unroller": unroll = [Unroller(basis_gates)] - elif method == "basis_translator": + elif method == "translator": unroll = [ # Use unitary synthesis for basis aware decomposition of # UnitaryGates before custom unrolling diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index d8a4930e292a..4f821a36a2b2 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -119,6 +119,7 @@ def _choose_layout_condition(property_set): routing_pass, target, coupling_map=coupling_map, + seed_transpiler=seed_transpiler, ) else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 50eb390da118..45fd5bf04d91 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -74,7 +74,7 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: initial_layout = pass_manager_config.initial_layout 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 "basis_translator" + translation_method = pass_manager_config.translation_method or "translator" scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -189,6 +189,8 @@ def _opt_control(property_set): target, coupling_map, vf2_call_limit=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2 + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, ) else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 30ef5c4df82b..84ca8a405e99 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -76,7 +76,7 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: initial_layout = pass_manager_config.initial_layout 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 "basis_translator" + translation_method = pass_manager_config.translation_method or "translator" scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler @@ -174,6 +174,8 @@ def _opt_control(property_set): target, coupling_map=coupling_map, vf2_call_limit=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2 + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, ) else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 7b5b30ca6150..dc8fb4b50612 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -202,6 +202,8 @@ def _opt_control(property_set): target, coupling_map=coupling_map, vf2_call_limit=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2 + backend_properties=backend_properties, + seed_transpiler=seed_transpiler, ) else: layout = None @@ -223,7 +225,7 @@ def _opt_control(property_set): pre_optimization = common.generate_pre_op_passmanager(target, coupling_map, True) _direction = [ pass_ - for x in common.generate_pre_op_passmanager(target, coupling_map) + 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 From 35a8db9c712a189f99d9f6bb2d0dc97886858495 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 23 May 2022 18:00:42 -0400 Subject: [PATCH 11/27] Fix more rebase issues --- qiskit/transpiler/preset_passmanagers/common.py | 12 +++++------- qiskit/transpiler/preset_passmanagers/level0.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 9 +++++++-- qiskit/transpiler/preset_passmanagers/level2.py | 9 +++++++-- qiskit/transpiler/preset_passmanagers/level3.py | 9 +++++++-- 5 files changed, 27 insertions(+), 14 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index f2ecd1960351..e495132f997a 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -75,9 +75,9 @@ def generate_unroll_3q( UnitarySynthesis( basis_gates, approximation_degree=approximation_degree, - plugin_config=unitary_synthesis_plugin_config, method=unitary_synthesis_method, min_qubits=3, + plugin_config=unitary_synthesis_plugin_config, target=target, ) ) @@ -114,13 +114,11 @@ def _trivial_not_perfect(property_set): def _run_post_layout_condition(property_set): - # Optimization level 1 tries trivial first so embed check that we're not - # using a perfect trivial layout. if _trivial_not_perfect(property_set): vf2_stop_reason = property_set["VF2Layout_stop_reason"] - if vf2_stop_reason is not None and vf2_stop_reason == VF2LayoutStopReason.SOLUTION_FOUND: - return False - return True + if vf2_stop_reason is None or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND: + return True + return False def _apply_post_layout_condition(property_set): @@ -169,7 +167,7 @@ def _swap_condition(property_set): routing.append([BarrierBeforeFinalMeasurements(), routing_pass], condition=_swap_condition) - if vf2_call_limit is not None: + if (target is not None or backend_properties is not None) and vf2_call_limit is not None: routing.append( VF2PostLayout( target, diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 4f821a36a2b2..d84377a5ae6a 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -63,7 +63,7 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StructuredPa initial_layout = pass_manager_config.initial_layout 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 "basis_translator" + translation_method = pass_manager_config.translation_method or "translator" scheduling_method = pass_manager_config.scheduling_method instruction_durations = pass_manager_config.instruction_durations seed_transpiler = pass_manager_config.seed_transpiler diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 45fd5bf04d91..f7ae6de1ae06 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -184,11 +184,14 @@ def _opt_control(property_set): 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: + 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=int(5e4), # Set call limit to ~100ms with retworkx 0.10.2 + vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, ) @@ -205,7 +208,9 @@ def _opt_control(property_set): unitary_synthesis_method, unitary_synthesis_plugin_config, ) - if coupling_map and not coupling_map.is_symmetric: + 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) diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 84ca8a405e99..54f2ee6285bf 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -169,11 +169,14 @@ def _opt_control(property_set): 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: + 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=int(5e6), # Set call limit to ~10 sec with retworkx 0.10.2 + vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, ) @@ -190,7 +193,9 @@ def _opt_control(property_set): unitary_synthesis_method, unitary_synthesis_plugin_config, ) - if coupling_map and not coupling_map.is_symmetric: + 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) diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index dc8fb4b50612..bb32502553b4 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -197,11 +197,14 @@ def _opt_control(property_set): 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: + 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=int(3e7), # Set call limit to ~60 sec with retworkx 0.10.2 + vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, ) @@ -221,7 +224,9 @@ def _opt_control(property_set): 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: + 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_ From 10f543ddb6fdb2c114c6b58d45b134bfac4e8e51 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 24 May 2022 10:01:18 -0400 Subject: [PATCH 12/27] Fix even more rebase issues --- .../passes/optimization/consolidate_blocks.py | 7 ++++- .../transpiler/preset_passmanagers/common.py | 29 ++++++++++++------- .../transpiler/preset_passmanagers/level0.py | 4 ++- .../transpiler/preset_passmanagers/level1.py | 3 +- .../transpiler/preset_passmanagers/level2.py | 2 +- .../transpiler/preset_passmanagers/level3.py | 2 +- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 90c0858705bb..c9f2d540b60e 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -117,7 +117,7 @@ def run(self, dag): # If 1q runs are collected before consolidate those too runs = self.property_set["run_list"] or [] for run in runs: - if run[0] in all_block_gates: + if any(gate in all_block_gates for gate in run): continue if len(run) == 1 and not self._check_not_in_basis( run[0].name, run[0].qargs, global_index_map @@ -135,6 +135,11 @@ def run(self, dag): continue unitary = UnitaryGate(operator) dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False) + # Clear collected blocks and runs as they are no longer valid after consolidation + if "run_list" in self.property_set: + del self.property_set["run_list"] + if "block_list" in self.property_set: + del self.property_set["block_list"] return dag def _check_not_in_basis(self, gate_name, qargs, global_index_map): diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index e495132f997a..551c7bc0bbfe 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -22,6 +22,7 @@ from qiskit.transpiler.passes import UnrollCustomDefinitions from qiskit.transpiler.passes import Unroll3qOrMore from qiskit.transpiler.passes import Collect2qBlocks +from qiskit.transpiler.passes import Collect1qRuns from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes import UnitarySynthesis from qiskit.transpiler.passes import CheckMap @@ -113,14 +114,6 @@ def _trivial_not_perfect(property_set): return False -def _run_post_layout_condition(property_set): - if _trivial_not_perfect(property_set): - vf2_stop_reason = property_set["VF2Layout_stop_reason"] - if vf2_stop_reason is None or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND: - return True - return False - - def _apply_post_layout_condition(property_set): # if VF2 Post layout found a solution we need to re-apply the better # layout. Otherwise we can skip apply layout. @@ -139,6 +132,7 @@ def generate_routing_passmanager( vf2_call_limit=None, backend_properties=None, seed_transpiler=None, + check_trivial=False, ): """Generate a routing :class:`~qiskit.transpiler.PassManager` @@ -154,11 +148,25 @@ def generate_routing_passmanager( synthesize for (e.g. gate fidelities). seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. - - + check_trivial (bool): If set to true this will condition running the + :class:`~.VF2PostLayout` pass after routing on whether a trivial + layout was tried and was found to not be perfect. This is only + needed if the constructed pass manager runs :class:`~.TrivialLayout` + as a first layout attempt and uses it if it's a perfect layout + (as is the case with preset pass manager level 1). Returns: PassManager: The routing pass manager """ + + def _run_post_layout_condition(property_set): + # If we check trivial layout and the found trivial layout was not perfect also + # ensure VF2 initial layout was not used before running vf2 post layout + if not check_trivial or _trivial_not_perfect(property_set): + vf2_stop_reason = property_set["VF2Layout_stop_reason"] + if vf2_stop_reason is None or vf2_stop_reason != VF2LayoutStopReason.SOLUTION_FOUND: + return True + return False + routing = PassManager() routing.append(CheckMap(coupling_map)) @@ -283,6 +291,7 @@ def generate_translation_passmanager( ), Unroll3qOrMore(target=target, basis_gates=basis_gates), Collect2qBlocks(), + Collect1qRuns(), ConsolidateBlocks(basis_gates=basis_gates, target=target), UnitarySynthesis( basis_gates=basis_gates, diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index d84377a5ae6a..5cac379374f9 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -134,7 +134,9 @@ def _choose_layout_condition(property_set): unitary_synthesis_method, unitary_synthesis_plugin_config, ) - if coupling_map and not coupling_map.is_symmetric: + if (coupling_map and not coupling_map.is_symmetric) or ( + target is not None and target.get_non_global_operation_names(strict_direction=True) + ): pre_opt = common.generate_pre_op_passmanager(target, coupling_map) pre_opt += translation else: diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index f7ae6de1ae06..01933d8833e4 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -185,7 +185,7 @@ def _opt_control(property_set): 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: + 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, @@ -194,6 +194,7 @@ def _opt_control(property_set): vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, + check_trivial=True, ) else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 54f2ee6285bf..85c0bacdc292 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -170,7 +170,7 @@ def _opt_control(property_set): 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: + 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, diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index bb32502553b4..1ee9782d1c97 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -198,7 +198,7 @@ def _opt_control(property_set): 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: + 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, From 5e5657eab730e090d63ab92f0ea623031af6c6a4 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 25 May 2022 10:38:43 -0400 Subject: [PATCH 13/27] Only run unroll3q on level 0-2 if coupling map is set To preserve the behavior prior to this reorganization this commit makes the Unroll3qorMore pass run if we have a coupling map set. This is because this pass is only needed to be run for these optimization levels so that the layout and routing passes can function (as they only work with 2q gates). If we're not running these passes we shouldn't be unrolling gates. This will fix the last 4 QAOA test failures post the recent rebase as that was failing because we were trying to unroll an unbound 4q hamiltonian gate when weren't before. --- qiskit/transpiler/preset_passmanagers/level0.py | 15 ++++++++------- qiskit/transpiler/preset_passmanagers/level1.py | 15 ++++++++------- qiskit/transpiler/preset_passmanagers/level2.py | 16 ++++++++-------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 5cac379374f9..e6d2cfbf5595 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -109,8 +109,16 @@ def _choose_layout_condition(property_set): else: raise TranspilerError("Invalid routing method %s." % routing_method) + unroll_3q = None # Build pass manager if coupling_map or initial_layout: + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout, condition=_choose_layout_condition) @@ -144,13 +152,6 @@ def _choose_layout_condition(property_set): sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) - unroll_3q = common.generate_unroll_3q( - target, - basis_gates, - approximation_degree, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - ) return StructuredPassManager( init=unroll_3q, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 01933d8833e4..1ca0046238ef 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -176,8 +176,16 @@ def _opt_control(property_set): _opt = [Optimize1qGatesDecomposition(basis_gates), CXCancellation()] + unroll_3q = None # Build full pass manager if coupling_map or initial_layout: + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout_0, condition=_choose_layout_condition) @@ -223,13 +231,6 @@ def _opt_control(property_set): sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) - unroll_3q = common.generate_unroll_3q( - target, - basis_gates, - approximation_degree, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - ) return StructuredPassManager( init=unroll_3q, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 85c0bacdc292..95305769ea3c 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -162,8 +162,16 @@ def _opt_control(property_set): CommutativeCancellation(basis_gates=basis_gates), ] + unroll_3q = None # Build pass manager if coupling_map or initial_layout: + unroll_3q = common.generate_unroll_3q( + target, + basis_gates, + approximation_degree, + unitary_synthesis_method, + unitary_synthesis_plugin_config, + ) layout = PassManager() layout.append(_given_layout) layout.append(_choose_layout_0, condition=_choose_layout_condition) @@ -207,14 +215,6 @@ def _opt_control(property_set): sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) - unroll_3q = common.generate_unroll_3q( - target, - basis_gates, - approximation_degree, - unitary_synthesis_method, - unitary_synthesis_plugin_config, - ) - return StructuredPassManager( init=unroll_3q, layout=layout, From aa1db1f8941cad54ed91ded539465a259640edaf Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 25 May 2022 16:56:21 -0400 Subject: [PATCH 14/27] Rework StructuredPassManager as a more dynamic StagedPassManager This commit redefines the previous StructuredPassManager class into a more dynamic StagedPassmanager. The StagedPassManager doesn't have fixed hard coded stages anymore but instead lets users define their own stages. It also adds implicit 'pre_' and 'post_' hook points for each listed stage. This lets users dynamically define the stages based on a particular use case. --- qiskit/transpiler/__init__.py | 4 +- qiskit/transpiler/passmanager.py | 284 ++++++++---------- .../transpiler/preset_passmanagers/level0.py | 6 +- .../transpiler/preset_passmanagers/level1.py | 4 +- .../transpiler/preset_passmanagers/level2.py | 4 +- .../transpiler/preset_passmanagers/level3.py | 4 +- 6 files changed, 141 insertions(+), 165 deletions(-) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 4037d5acb72e..ef54b79df113 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -612,7 +612,7 @@ .. autosummary:: :toctree: ../stubs/ - StructuredPassManager + StagedPassManager PassManager PassManagerConfig PropertySet @@ -668,7 +668,7 @@ from .runningpassmanager import FlowController, ConditionalController, DoWhileController from .passmanager import PassManager from .passmanager_config import PassManagerConfig -from .passmanager import StructuredPassManager +from .passmanager import StagedPassManager from .propertyset import PropertySet from .exceptions import TranspilerError, TranspilerAccessError from .fencedobjs import FencedDAGCircuit, FencedPropertySet diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index acea527efdb6..362dc7999428 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -321,12 +321,22 @@ def passes(self) -> List[Dict[str, BasePass]]: return ret -class StructuredPassManager(PassManager): - """A full Pass manager pipeline for a backend - - Instances of StructuredPassManager define a full compilation pipeline from a abstract virtual - circuit to one that is optimized and capable of running on the specified backend. It is - built using predefined stages: +class StagedPassManager(PassManager): + """A Pass manager pipeline built up of individual stages + + This class enables building a compilation pipeline out of fixed stages. + Each ``StagedPassManager`` defines a list of stages which are executed in + a fixed order, and each stage is defined as a standalone :class:`~.PassManager` + instance. There are also ``pre_`` and ``post_`` stages for each defined stage. + This enables easily composing and replacing different stages and also adding + hook points to enable programmtic modifications to a pipeline. When using a staged + pass manager you are not able to modify the individual passes and are only able + to modify stages. If you do an inline modification to a defined stage be sure to + run the :meth:`~.StagedPassManager.update_passmanager` method. + + By default instances of StagedPassManager define a typical full compilation + pipeline from an abstract virtual circuit to one that is optimized and + capable of running on the specified backend. The default pre-defined stages are: 1. Init - any initial passes that are run before we start embedding the circuit to the backend 2. Layout - This stage runs layout and maps the virtual qubits in the @@ -336,162 +346,128 @@ class StructuredPassManager(PassManager): backend's compuling map. 4. Translation - Perform the basis gate translation, in other words translate the gates in the circuit to the target backend's basis set - 5. Pre-Optimization - Any passes to run before the main optimization loop - 6. Optimization - The main optimization loop, this will typically run in a loop trying to optimize - the circuit until a condtion (such as fixed depth) is reached. - 7. Post-Optimization - Any passes to run after the main optimization loop + 6. Optimization - The main optimization loop, this will typically run in a loop trying to + optimize the circuit until a condtion (such as fixed depth) is reached. 8. Scheduling - Any hardware aware scheduling passes These stages will be executed in order and any stage set to ``None`` will be skipped. If a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here - (for example in the case of a Pass that covers both Layout and Routing) you will want to set - that to the earliest stage in sequence that it covers. + (for example in the case of a :class:`~.Pass` that covers both Layout and Routing) you will want + to set that to the earliest stage in sequence that it covers. """ - phases = [ - "init", - "layout", - "routing", - "translation", - "pre_optimization", - "optimization", - "post_optimization", - "scheduling", - ] - - def __init__( - self, - init=None, - layout=None, - routing=None, - translation=None, - pre_optimization=None, - optimization=None, - post_optimization=None, - scheduling=None, - ): - """Initialize a new StructuredPassManager object + def __init__(self, phases=None, **kwargs): + """Initialize a new StagedPassManager object Args: - init (PassManager): A passmanager to run for the initial stage of the - compilation. - layout (PassManager): A passmanager to run for the layout stage of the - compilation. - routing (PassManager): A pass manager to run for the routing stage - of the compilation - translation (PassManager): A pass manager to run for the translation - stage of the compilation - pre_opt (PassManager): A pass manager to run before the optimization - loop - optimization (PassManager): A pass manager to run for the - optimization loop stage - post_opt (PassManager): A pass manager to run after the optimization - loop - scheduling (PassManager): A pass manager to run any scheduling passes + phases (List[str]): An optional list of phases to use for this + instance. If this is not specified the default stages list + ``['init', 'layout', 'routing', 'translation', 'optimization', 'scheduling']`` is + used + kwargs: The initial :class:`~.PassManager` values for any phases + defined in ``phases``. If a argument is not defined the + phases will default to ``None`` indicating an empty/undefined + phase. + + Raises: + AttributeError: If a stage in the input keyword arguments is not defined. """ + if phases is None: + self.phases = [ + "init", + "layout", + "routing", + "translation", + "optimization", + "scheduling", + ] + else: + self.phases = phases super().__init__() - self._init = init - self._layout = layout - self._routing = routing - self._translation = translation - self._pre_optimization = pre_optimization - self._optimization = optimization - self._post_optimization = post_optimization - self._scheduling = scheduling - self._update_passmanager() - - def _update_passmanager(self): + for phase in self.phases: + pre_phase = "pre_" + phase + post_phase = "post_" + phase + setattr(self, pre_phase, None) + setattr(self, phase, None) + setattr(self, post_phase, None) + + for phase, pm in kwargs.items(): + if ( + phase not in self.phases + and (phase.startswith("pre_") and phase[4:] not in self.phases) + and (phase.startswith("post_") and phase[5:] not in self.phases) + ): + raise AttributeError(f"{phase} is not a valid stage.") + setattr(self, phase, pm) + self.update_passmanager() + + def update_passmanager(self): + """Update the internal state from the defined stages + + This method will update the ``StagedPassManager`` instance based on the current + state of the defined stages. This happens automatically when ever an object is + initialized or a stage is replaced with a new :class:`~.PassManager`. + However, if you do an inplace modification of a defined stage you will + need to run this method to have that changed reflected here. For example:: + + from qiskit.transpiler import PassManager, StagedPassManager + from qiskit.transpiler.passes import Depth + + staged_pm = StagedPassManager(stages=['single_stage'], single_stage=PassManager()) + staged_pm.single_stage.append(Depth()) + staged_pm.update_passmanager() + + + without the ``update_passmananger()`` call ``staged_pm`` will not know to run + :class:`~.Depth`. + """ self._pass_sets = [] - if self._init: - self._pass_sets.extend(self._init._pass_sets) - if self._layout: - self._pass_sets.extend(self._layout._pass_sets) - if self._routing: - self._pass_sets.extend(self._routing._pass_sets) - if self._translation: - self._pass_sets.extend(self._translation._pass_sets) - if self._pre_optimization: - self._pass_sets.extend(self._pre_optimization._pass_sets) - if self._optimization: - self._pass_sets.extend(self._optimization._pass_sets) - if self._post_optimization: - self._pass_sets.extend(self._post_optimization._pass_sets) - if self._scheduling: - self._pass_sets.extend(self._scheduling._pass_sets) - - @property - def init(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the init stage.""" - return self._init - - @init.setter - def init(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the init stage.""" - self._init = value - self._update_passmanager() - - @property - def layout(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the layout stage.""" - return self._layout - - @layout.setter - def layout(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the layout stage.""" - self._layout = value - self._update_passmanager() - - @property - def routing(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the routing stage.""" - return self._routing - - @routing.setter - def routing(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the routing stage.""" - self._routing = value - self._update_passmanager() - - @property - def pre_optimization(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the pre_optimization stage.""" - return self._pre_optimization - - @pre_optimization.setter - def pre_optimization(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the pre_optimization stage.""" - self._pre_optimization = value - self._update_passmanager() - - @property - def optimization(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the optimization stage.""" - return self._optimization - - @optimization.setter - def optimization(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the optimization stage.""" - self._optimization = value - self._update_passmanager() - - @property - def post_optimization(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the post_optimization stage.""" - return self._post_optimization - - @post_optimization.setter - def post_optimization(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the post_optimization stage.""" - self._post_optimization = value - self._update_passmanager() - - @property - def scheduling(self): - """Get the :class:`~qiskit.transpiler.PassManager` for the scheduling stage.""" - return self._post_optimization - - @scheduling.setter - def scheduling(self, value): - """Set the :class:`~qiskit.transpiler.PassManager` for the scheduling stage.""" - self._post_optimization = value - self._update_passmanager() + for phase in self.phases: + # Add pre-phase PM + pre_phase_pm = getattr(self, "pre_" + phase, None) + if pre_phase_pm is not None: + self._pass_sets.extend(pre_phase_pm._pass_sets) + # Add phase PM + phase_pm = getattr(self, phase, None) + if phase_pm is not None: + self._pass_sets.extend(phase_pm._pass_sets) + # Add post-phase PM + post_phase_pm = getattr(self, "post_" + phase, None) + if post_phase_pm is not None: + self._pass_sets.extend(post_phase_pm._pass_sets) + + def __setattr__(self, attr, value): + super().__setattr__(attr, value) + if ( + attr in self.phases + or (attr.startswith("pre_") and attr[4:] not in self.phases) + or (attr.startswith("post_") and attr[5:] not in self.phases) + ): + self.update_passmanager() + + def append( + self, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any, + ) -> None: + raise NotImplementedError + + def replace( + self, + index: int, + passes: Union[BasePass, List[BasePass]], + max_iteration: int = None, + **flow_controller_conditions: Any, + ) -> None: + raise NotImplementedError + + # Raise NotImplemntedError on individual pass manipulation + def remove(self, index: int) -> None: + raise NotImplementedError + + def __setitem__(self, index, item): + raise NotImplementedError + + def __add__(self, other): + raise NotImplementedError diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index e6d2cfbf5595..5051cd3ffe8c 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import StructuredPassManager +from qiskit.transpiler.passmanager import StagedPassManager from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import TrivialLayout @@ -34,7 +34,7 @@ from qiskit.transpiler import TranspilerError -def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StructuredPassManager: +def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 0 pass manager: no explicit optimization other than mapping to backend. This pass manager applies the user-given initial layout. If none is given, a trivial @@ -153,7 +153,7 @@ def _choose_layout_condition(property_set): instruction_durations, scheduling_method, timing_constraints, inst_map ) - return StructuredPassManager( + return StagedPassManager( init=unroll_3q, layout=layout, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 1ca0046238ef..0c573fde0e9d 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -18,7 +18,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import StructuredPassManager +from qiskit.transpiler.passmanager import StagedPassManager from qiskit.transpiler.passes import CXCancellation from qiskit.transpiler.passes import SetLayout @@ -232,7 +232,7 @@ def _opt_control(property_set): instruction_durations, scheduling_method, timing_constraints, inst_map ) - return StructuredPassManager( + return StagedPassManager( init=unroll_3q, layout=layout, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 95305769ea3c..617069f89ca9 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -19,7 +19,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import StructuredPassManager +from qiskit.transpiler.passmanager import StagedPassManager from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout @@ -215,7 +215,7 @@ def _opt_control(property_set): sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) - return StructuredPassManager( + return StagedPassManager( init=unroll_3q, layout=layout, routing=routing, diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 1ee9782d1c97..e81d3c7af6cb 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -20,7 +20,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passmanager import StructuredPassManager +from qiskit.transpiler.passmanager import StagedPassManager from qiskit.transpiler.passes import SetLayout from qiskit.transpiler.passes import VF2Layout @@ -251,7 +251,7 @@ def _opt_control(property_set): sched = common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, inst_map ) - return StructuredPassManager( + return StagedPassManager( init=init, layout=layout, routing=routing, From c51518fc82aa08c5184259ca6d92ef2f0d476b34 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 May 2022 07:23:26 -0400 Subject: [PATCH 15/27] Fix docs --- qiskit/transpiler/passmanager.py | 22 +++++++++---------- ...add-full-passmanager-5a377f1b71480f72.yaml | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 362dc7999428..e77da7d069d6 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -338,17 +338,17 @@ class StagedPassManager(PassManager): pipeline from an abstract virtual circuit to one that is optimized and capable of running on the specified backend. The default pre-defined stages are: - 1. Init - any initial passes that are run before we start embedding the circuit to the backend - 2. Layout - This stage runs layout and maps the virtual qubits in the - circuit to the physical qubits on a backend - 3. Routing - This stage runs after a layout has been run and will insert any - necessary gates to move the qubit states around until it can be run on - backend's compuling map. - 4. Translation - Perform the basis gate translation, in other words translate the gates - in the circuit to the target backend's basis set - 6. Optimization - The main optimization loop, this will typically run in a loop trying to - optimize the circuit until a condtion (such as fixed depth) is reached. - 8. Scheduling - Any hardware aware scheduling passes + #. ``init`` - any initial passes that are run before we start embedding the circuit to the backend + #. ``layout`` - This stage runs layout and maps the virtual qubits in the + circuit to the physical qubits on a backend + #. ``routing`` - This stage runs after a layout has been run and will insert any + necessary gates to move the qubit states around until it can be run on + backend's compuling map. + #. ``translation`` - Perform the basis gate translation, in other words translate the gates + in the circuit to the target backend's basis set + #. ``optimization`` - The main optimization loop, this will typically run in a loop trying to + optimize the circuit until a condtion (such as fixed depth) is reached. + #. ``scheduling`` - Any hardware aware scheduling passes These stages will be executed in order and any stage set to ``None`` will be skipped. If a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here diff --git a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml index aa4c9ce79bc9..37dec565f768 100644 --- a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml +++ b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml @@ -1,19 +1,19 @@ --- features: - | - Added a new class, :class:`qiskit.transpiler.FullPassManager`, which is + Added a new class, :class:`qiskit.transpiler.StagedPassManager`, which is a :class:`~qiskit.transpiler.PassManager` subclass that has a pipeline - with defined phases to the circuit compilation. Each phase is a + with defined phases to perform circuit compilation. Each phase is a :class:`~qiskit.transpiler.PassManager` object that will get executed in a fixed order. For example:: from qiskit.transpiler.passes import * - from qiskit.transpiler import PassManager, FullPassManager + from qiskit.transpiler import PassManager, StagedPassManager basis_gates = ['rx', 'ry', 'rxx'] - init = PassManager([UnitarySynthesis(basis_gates), Unroll3qOrMore()]) + init = PassManager([UnitarySynthesis(basis_gates, min_qubits=3), Unroll3qOrMore()]) translate = PassManager([Collect2qBlocks(), ConsolidateBlocks(basis_gates=basis_gates), UnitarySynthesis(basis_gates)]) - full_pass_manager = FullPassManager(init=init, translation=translate) + staged_pm = StagedPassManager(phases=['init', 'translation'], init=init, translation=translate) From f6cbb55fc77ea3ef3e4bfd6a2f5faf479a6429c5 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 May 2022 09:44:15 -0400 Subject: [PATCH 16/27] Update internal pass set on each access This commit updates the api for the StagedPassManager to refresh the internal pass list on each access. This adds a little overhead but with the tradeoff of making in place modifications to a stage's pass manager reflected without needing to manually call an update method. --- qiskit/transpiler/passmanager.py | 58 ++++++++------- .../transpiler/test_staged_passmanager.py | 74 +++++++++++++++++++ 2 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 test/python/transpiler/test_staged_passmanager.py diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index e77da7d069d6..4369aa543b34 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -331,8 +331,7 @@ class StagedPassManager(PassManager): This enables easily composing and replacing different stages and also adding hook points to enable programmtic modifications to a pipeline. When using a staged pass manager you are not able to modify the individual passes and are only able - to modify stages. If you do an inline modification to a defined stage be sure to - run the :meth:`~.StagedPassManager.update_passmanager` method. + to modify stages. By default instances of StagedPassManager define a typical full compilation pipeline from an abstract virtual circuit to one that is optimized and @@ -390,37 +389,17 @@ def __init__(self, phases=None, **kwargs): setattr(self, pre_phase, None) setattr(self, phase, None) setattr(self, post_phase, None) - for phase, pm in kwargs.items(): if ( phase not in self.phases - and (phase.startswith("pre_") and phase[4:] not in self.phases) - and (phase.startswith("post_") and phase[5:] not in self.phases) + and not (phase.startswith("pre_") and phase[4:] in self.phases) + and not (phase.startswith("post_") and phase[5:] in self.phases) ): raise AttributeError(f"{phase} is not a valid stage.") setattr(self, phase, pm) - self.update_passmanager() - - def update_passmanager(self): - """Update the internal state from the defined stages - - This method will update the ``StagedPassManager`` instance based on the current - state of the defined stages. This happens automatically when ever an object is - initialized or a stage is replaced with a new :class:`~.PassManager`. - However, if you do an inplace modification of a defined stage you will - need to run this method to have that changed reflected here. For example:: - - from qiskit.transpiler import PassManager, StagedPassManager - from qiskit.transpiler.passes import Depth - - staged_pm = StagedPassManager(stages=['single_stage'], single_stage=PassManager()) - staged_pm.single_stage.append(Depth()) - staged_pm.update_passmanager() - + self._update_passmanager() - without the ``update_passmananger()`` call ``staged_pm`` will not know to run - :class:`~.Depth`. - """ + def _update_passmanager(self): self._pass_sets = [] for phase in self.phases: # Add pre-phase PM @@ -443,7 +422,7 @@ def __setattr__(self, attr, value): or (attr.startswith("pre_") and attr[4:] not in self.phases) or (attr.startswith("post_") and attr[5:] not in self.phases) ): - self.update_passmanager() + self._update_passmanager() def append( self, @@ -466,8 +445,33 @@ def replace( def remove(self, index: int) -> None: raise NotImplementedError + def __getitem(self, index): + self._update_passmanager() + return super().__getitem__(index) + + def __len__(self): + self._update_passmanager() + return super().__len__() + def __setitem__(self, index, item): raise NotImplementedError def __add__(self, other): raise NotImplementedError + + def _create_running_passmanager(self) -> RunningPassManager: + self._update_passmanager() + return super()._create_running_passmanager() + + def passes(self) -> List[Dict[str, BasePass]]: + self._update_passmanager() + return super().passes() + + def run( + self, + circuits: Union[QuantumCircuit, List[QuantumCircuit]], + output_name: str = None, + callback: Callable = None, + ) -> Union[QuantumCircuit, List[QuantumCircuit]]: + self._update_passmanager() + return super().run(circuits, output_name, callback) diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py new file mode 100644 index 000000000000..5c2c9b1280b3 --- /dev/null +++ b/test/python/transpiler/test_staged_passmanager.py @@ -0,0 +1,74 @@ +# 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. + +"""Test the passmanager logic""" + +import copy + +import numpy as np + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.circuit.library import U2Gate +from qiskit.converters import circuit_to_dag +from qiskit.transpiler import PassManager, PropertySet, StagedPassManager +from qiskit.transpiler.passes import CommutativeCancellation +from qiskit.transpiler.passes import Optimize1qGates, Unroller, Depth +from qiskit.test import QiskitTestCase + + +class TestStagedPassManager(QiskitTestCase): + def test_default_stages(self): + spm = StagedPassManager() + self.assertEqual( + spm.phases, ["init", "layout", "routing", "translation", "optimization", "scheduling"] + ) + spm = StagedPassManager( + init=PassManager([Optimize1qGates()]), + routing=PassManager([Unroller(["u", "cx"])]), + scheduling=PassManager([Depth()]), + ) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Unroller", "Depth"], + ) + + def test_inplace_edit(self): + spm = StagedPassManager(phases=["single_stage"]) + spm.single_stage = PassManager([Optimize1qGates(), Depth()]) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Depth"], + ) + spm.single_stage.append(Unroller(["u"])) + spm.single_stage.append(Depth()) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Optimize1qGates", "Depth", "Unroller", "Depth"], + ) + + def test_invalid_stage(self): + with self.assertRaises(AttributeError): + StagedPassManager(phases=["init"], translation=PassManager()) + + def test_pre_phase_is_valid_stage(self): + spm = StagedPassManager(phases=["init"], pre_init=PassManager([Depth()])) + self.assertEqual( + [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], + ["Depth"], + ) + + def test_append_extend_not_implemented(self): + spm = StagedPassManager() + with self.assertRaises(NotImplementedError): + spm.append(Depth()) + with self.assertRaises(NotImplementedError): + spm + PassManager() From 445fdfbf77445d95150f9fcba60a6a146335ad07 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 May 2022 09:50:20 -0400 Subject: [PATCH 17/27] Rename phases attribute to stages --- qiskit/transpiler/passmanager.py | 74 +++++++++---------- .../transpiler/test_staged_passmanager.py | 8 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 4369aa543b34..18ea4bf7d4ff 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -355,24 +355,24 @@ class StagedPassManager(PassManager): to set that to the earliest stage in sequence that it covers. """ - def __init__(self, phases=None, **kwargs): + def __init__(self, stages=None, **kwargs): """Initialize a new StagedPassManager object Args: - phases (List[str]): An optional list of phases to use for this + stages (List[str]): An optional list of stages to use for this instance. If this is not specified the default stages list ``['init', 'layout', 'routing', 'translation', 'optimization', 'scheduling']`` is used - kwargs: The initial :class:`~.PassManager` values for any phases - defined in ``phases``. If a argument is not defined the - phases will default to ``None`` indicating an empty/undefined - phase. + kwargs: The initial :class:`~.PassManager` values for any stages + defined in ``stages``. If a argument is not defined the + stages will default to ``None`` indicating an empty/undefined + stage. Raises: AttributeError: If a stage in the input keyword arguments is not defined. """ - if phases is None: - self.phases = [ + if stages is None: + self.stages = [ "init", "layout", "routing", @@ -381,46 +381,46 @@ def __init__(self, phases=None, **kwargs): "scheduling", ] else: - self.phases = phases + self.stages = stages super().__init__() - for phase in self.phases: - pre_phase = "pre_" + phase - post_phase = "post_" + phase - setattr(self, pre_phase, None) - setattr(self, phase, None) - setattr(self, post_phase, None) - for phase, pm in kwargs.items(): + for stage in self.stages: + pre_stage = "pre_" + stage + post_stage = "post_" + stage + setattr(self, pre_stage, None) + setattr(self, stage, None) + setattr(self, post_stage, None) + for stage, pm in kwargs.items(): if ( - phase not in self.phases - and not (phase.startswith("pre_") and phase[4:] in self.phases) - and not (phase.startswith("post_") and phase[5:] in self.phases) + stage not in self.stages + and not (stage.startswith("pre_") and stage[4:] in self.stages) + and not (stage.startswith("post_") and stage[5:] in self.stages) ): - raise AttributeError(f"{phase} is not a valid stage.") - setattr(self, phase, pm) + raise AttributeError(f"{stage} is not a valid stage.") + setattr(self, stage, pm) self._update_passmanager() def _update_passmanager(self): self._pass_sets = [] - for phase in self.phases: - # Add pre-phase PM - pre_phase_pm = getattr(self, "pre_" + phase, None) - if pre_phase_pm is not None: - self._pass_sets.extend(pre_phase_pm._pass_sets) - # Add phase PM - phase_pm = getattr(self, phase, None) - if phase_pm is not None: - self._pass_sets.extend(phase_pm._pass_sets) - # Add post-phase PM - post_phase_pm = getattr(self, "post_" + phase, None) - if post_phase_pm is not None: - self._pass_sets.extend(post_phase_pm._pass_sets) + for stage in self.stages: + # Add pre-stage PM + pre_stage_pm = getattr(self, "pre_" + stage, None) + if pre_stage_pm is not None: + self._pass_sets.extend(pre_stage_pm._pass_sets) + # Add stage PM + stage_pm = getattr(self, stage, None) + if stage_pm is not None: + self._pass_sets.extend(stage_pm._pass_sets) + # Add post-stage PM + post_stage_pm = getattr(self, "post_" + stage, None) + if post_stage_pm is not None: + self._pass_sets.extend(post_stage_pm._pass_sets) def __setattr__(self, attr, value): super().__setattr__(attr, value) if ( - attr in self.phases - or (attr.startswith("pre_") and attr[4:] not in self.phases) - or (attr.startswith("post_") and attr[5:] not in self.phases) + attr in self.stages + or (attr.startswith("pre_") and attr[4:] not in self.stages) + or (attr.startswith("post_") and attr[5:] not in self.stages) ): self._update_passmanager() diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py index 5c2c9b1280b3..ca64e925b552 100644 --- a/test/python/transpiler/test_staged_passmanager.py +++ b/test/python/transpiler/test_staged_passmanager.py @@ -29,7 +29,7 @@ class TestStagedPassManager(QiskitTestCase): def test_default_stages(self): spm = StagedPassManager() self.assertEqual( - spm.phases, ["init", "layout", "routing", "translation", "optimization", "scheduling"] + spm.stages, ["init", "layout", "routing", "translation", "optimization", "scheduling"] ) spm = StagedPassManager( init=PassManager([Optimize1qGates()]), @@ -42,7 +42,7 @@ def test_default_stages(self): ) def test_inplace_edit(self): - spm = StagedPassManager(phases=["single_stage"]) + spm = StagedPassManager(stages=["single_stage"]) spm.single_stage = PassManager([Optimize1qGates(), Depth()]) self.assertEqual( [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], @@ -57,10 +57,10 @@ def test_inplace_edit(self): def test_invalid_stage(self): with self.assertRaises(AttributeError): - StagedPassManager(phases=["init"], translation=PassManager()) + StagedPassManager(stages=["init"], translation=PassManager()) def test_pre_phase_is_valid_stage(self): - spm = StagedPassManager(phases=["init"], pre_init=PassManager([Depth()])) + spm = StagedPassManager(stages=["init"], pre_init=PassManager([Depth()])) self.assertEqual( [x.__class__.__name__ for passes in spm.passes() for x in passes["passes"]], ["Depth"], From e34cbb80399a5fe75c535ade8f2890d915f9fe5e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 May 2022 09:52:11 -0400 Subject: [PATCH 18/27] Fix lint --- test/python/transpiler/test_staged_passmanager.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py index ca64e925b552..593b3f6724ea 100644 --- a/test/python/transpiler/test_staged_passmanager.py +++ b/test/python/transpiler/test_staged_passmanager.py @@ -10,17 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test the passmanager logic""" +# pylint: disable=missing-function-docstring,missing-class-docstring -import copy +"""Test the staged passmanager logic""" -import numpy as np - -from qiskit import QuantumRegister, QuantumCircuit -from qiskit.circuit.library import U2Gate -from qiskit.converters import circuit_to_dag -from qiskit.transpiler import PassManager, PropertySet, StagedPassManager -from qiskit.transpiler.passes import CommutativeCancellation +from qiskit.transpiler import PassManager, StagedPassManager from qiskit.transpiler.passes import Optimize1qGates, Unroller, Depth from qiskit.test import QiskitTestCase @@ -71,4 +65,4 @@ def test_append_extend_not_implemented(self): with self.assertRaises(NotImplementedError): spm.append(Depth()) with self.assertRaises(NotImplementedError): - spm + PassManager() + spm += PassManager() From 5e007f30e2bb38058034d97be434ca3c33cb3174 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 26 May 2022 14:38:09 -0400 Subject: [PATCH 19/27] Explicitly set name in qpy compat tests The rework of the preset passmanager construction changes the import order slightly and the number of circuits constructed in a session prior to the qpy compat deserialization side generating equivalent circuits for comparision has changed. This is causing all the deserialization side numbers to change and the tests are now failing because the circuit names are not equivalent. Since the exact name is a side effect of the import order (based on the number of unnamed circuits created in the session priort) it's not part of what the qpy tests are validating. We're trying to assert that the naem is preserved loading a qpy file across a version boundary. To fix this issues this commit adds an explicit name to the generation for all the circuits to ensure that we have a deterministic name for each circuit. --- test/qpy_compat/test_qpy.py | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index eb69653e2c98..29e2bfcc8097 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -56,7 +56,7 @@ def generate_full_circuit(): def generate_unitary_gate_circuit(): """Generate a circuit with a unitary gate.""" - unitary_circuit = QuantumCircuit(5) + unitary_circuit = QuantumCircuit(5, name="unitary_circuit") unitary_circuit.unitary(random_unitary(32, seed=100), [0, 1, 2, 3, 4]) unitary_circuit.measure_all() return unitary_circuit @@ -66,7 +66,7 @@ def generate_random_circuits(): """Generate multiple random circuits.""" random_circuits = [] for i in range(1, 15): - qc = QuantumCircuit(i) + qc = QuantumCircuit(i, name=f"random_circuit-{i}") qc.h(0) if i > 1: for j in range(i - 1): @@ -83,7 +83,9 @@ def generate_random_circuits(): def generate_string_parameters(): """Generate a circuit from pauli tensor opflow.""" - return (X ^ Y ^ Z).to_circuit_op().to_circuit() + op_circuit = (X ^ Y ^ Z).to_circuit_op().to_circuit() + op_circuit.name = "X^Y^Z" + return op_circuit def generate_register_edge_cases(): @@ -91,7 +93,7 @@ def generate_register_edge_cases(): register_edge_cases = [] # Circuit with shared bits in a register qubits = [Qubit() for _ in range(5)] - shared_qc = QuantumCircuit() + shared_qc = QuantumCircuit(name="shared_bits") shared_qc.add_bits(qubits) shared_qr = QuantumRegister(bits=qubits) shared_qc.add_register(shared_qr) @@ -108,7 +110,7 @@ def generate_register_edge_cases(): qr = QuantumRegister(name="bar", bits=qr[:3] + [Qubit(), Qubit()]) cr = ClassicalRegister(5, "foo") cr = ClassicalRegister(name="classical_bar", bits=cr[:3] + [Clbit(), Clbit()]) - hybrid_qc = QuantumCircuit(qr, cr) + hybrid_qc = QuantumCircuit(qr, cr, name="mix_standalone_bits_registers") hybrid_qc.h(0) hybrid_qc.cx(0, 1) hybrid_qc.cx(0, 2) @@ -119,7 +121,7 @@ def generate_register_edge_cases(): # Circuit with mixed standalone and shared registers qubits = [Qubit() for _ in range(5)] clbits = [Clbit() for _ in range(5)] - mixed_qc = QuantumCircuit() + mixed_qc = QuantumCircuit(name="mix_standalone_bits_with_registers") mixed_qc.add_bits(qubits) mixed_qc.add_bits(clbits) qr = QuantumRegister(bits=qubits) @@ -139,7 +141,7 @@ def generate_register_edge_cases(): qr_standalone = QuantumRegister(2, "standalone") qubits = [Qubit() for _ in range(5)] clbits = [Clbit() for _ in range(5)] - ooo_qc = QuantumCircuit() + ooo_qc = QuantumCircuit(name="out_of_order_bits") ooo_qc.add_bits(qubits) ooo_qc.add_bits(clbits) random.seed(42) @@ -165,7 +167,7 @@ def generate_register_edge_cases(): def generate_parameterized_circuit(): """Generate a circuit with parameters and parameter expressions.""" - param_circuit = QuantumCircuit(1) + param_circuit = QuantumCircuit(1, name="parameterized") theta = Parameter("theta") lam = Parameter("λ") theta_pi = 3.14159 * theta @@ -193,7 +195,7 @@ def generate_qft_circuit(): ) qubits = 3 - qft_circ = QuantumCircuit(qubits, qubits) + qft_circ = QuantumCircuit(qubits, qubits, name="QFT") qft_circ.initialize(state) qft_circ.append(QFT(qubits), range(qubits)) qft_circ.measure(range(qubits), range(qubits)) @@ -207,7 +209,7 @@ def generate_param_phase(): theta = Parameter("theta") phi = Parameter("phi") sum_param = theta + phi - qc = QuantumCircuit(5, 1, global_phase=sum_param) + qc = QuantumCircuit(5, 1, global_phase=sum_param, name="parameter_phase") qc.h(0) for i in range(4): qc.cx(i, i + 1) @@ -223,7 +225,7 @@ def generate_param_phase(): output_circuits.append(qc) # Generate circuit with Parameter global phase theta = Parameter("theta") - bell_qc = QuantumCircuit(2, global_phase=theta) + bell_qc = QuantumCircuit(2, global_phase=theta, name="bell_param_global_phase") bell_qc.h(0) bell_qc.cx(0, 1) bell_qc.measure_all() @@ -245,7 +247,7 @@ def generate_single_clbit_condition_teleportation(): # pylint: disable=invalid- def generate_parameter_vector(): """Generate tests for parameter vector element ordering.""" - qc = QuantumCircuit(11) + qc = QuantumCircuit(11, name="parameter_vector") input_params = ParameterVector("x_par", 11) user_params = ParameterVector("θ_par", 11) for i, param in enumerate(user_params): @@ -257,7 +259,7 @@ def generate_parameter_vector(): def generate_parameter_vector_expression(): # pylint: disable=invalid-name """Generate tests for parameter vector element ordering.""" - qc = QuantumCircuit(7) + qc = QuantumCircuit(7, name="vector_expansion") entanglement = [[i, i + 1] for i in range(7 - 1)] input_params = ParameterVector("x_par", 14) user_params = ParameterVector("\u03B8_par", 1) @@ -283,7 +285,7 @@ def generate_evolution_gate(): synthesis = SuzukiTrotter() evo = PauliEvolutionGate([(Z ^ I) + (I ^ Z)] * 5, time=2.0, synthesis=synthesis) - qc = QuantumCircuit(2) + qc = QuantumCircuit(2, name="pauli_evolution_circuit") qc.append(evo, range(2)) return qc @@ -294,7 +296,7 @@ def generate_control_flow_circuits(): # If instruction circuits = [] - qc = QuantumCircuit(2, 2) + qc = QuantumCircuit(2, 2, name="control_flow") qc.h(0) qc.measure(0, 0) true_body = QuantumCircuit(1) @@ -304,7 +306,7 @@ def generate_control_flow_circuits(): qc.measure(1, 1) circuits.append(qc) # If else instruction - qc = QuantumCircuit(2, 2) + qc = QuantumCircuit(2, 2, name="if_else") qc.h(0) qc.measure(0, 0) false_body = QuantumCircuit(1) @@ -314,7 +316,7 @@ def generate_control_flow_circuits(): qc.measure(1, 1) circuits.append(qc) # While loop - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="while_loop") block = QuantumCircuit(2, 1) block.h(0) block.cx(0, 1) @@ -323,7 +325,7 @@ def generate_control_flow_circuits(): qc.append(while_loop, [0, 1], [0]) circuits.append(qc) # for loop range - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="for_loop") body = QuantumCircuit(2, 1) body.h(0) body.cx(0, 1) @@ -333,7 +335,7 @@ def generate_control_flow_circuits(): qc.append(for_loop_op, [0, 1], [0]) circuits.append(qc) # For loop iterator - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(2, 1, name="for_loop_iterator") for_loop_op = ForLoopOp(iter(range(5)), None, body=body) qc.append(for_loop_op, [0, 1], [0]) circuits.append(qc) @@ -399,7 +401,7 @@ def assert_equal(reference, qpy, count, bind=None): sys.exit(1) # Don't compare name on bound circuits if bind is None and reference.name != qpy.name: - msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}" + msg = f"Circuit {count} name mismatch {reference.name} != {qpy.name}\n{reference}\n{qpy}" sys.stderr.write(msg) sys.exit(2) if reference.metadata != qpy.metadata: From 75e3d41425fb3eb359b83161a153d3db217219b5 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jun 2022 09:01:54 -0400 Subject: [PATCH 20/27] Apply suggestions from code review Co-authored-by: Luciano Bello --- qiskit/transpiler/preset_passmanagers/common.py | 12 ++++-------- .../notes/add-full-passmanager-5a377f1b71480f72.yaml | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 551c7bc0bbfe..a1c78beff789 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -106,23 +106,19 @@ def _trivial_not_perfect(property_set): # Verify that a trivial layout is perfect. If trivial_layout_score > 0 # the layout is not perfect. The layout is unconditionally set by trivial # layout so we need to clear it before contuing. - if ( + return ( property_set["trivial_layout_score"] is not None and property_set["trivial_layout_score"] != 0 - ): - return True - return False + ) def _apply_post_layout_condition(property_set): # if VF2 Post layout found a solution we need to re-apply the better # layout. Otherwise we can skip apply layout. - if ( + return ( property_set["VF2PostLayout_stop_reason"] is not None and property_set["VF2PostLayout_stop_reason"] is VF2PostLayoutStopReason.SOLUTION_FOUND - ): - return True - return False + ) def generate_routing_passmanager( diff --git a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml index 37dec565f768..be59f4545743 100644 --- a/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml +++ b/releasenotes/notes/add-full-passmanager-5a377f1b71480f72.yaml @@ -16,4 +16,4 @@ features: ConsolidateBlocks(basis_gates=basis_gates), UnitarySynthesis(basis_gates)]) - staged_pm = StagedPassManager(phases=['init', 'translation'], init=init, translation=translate) + staged_pm = StagedPassManager(stages=['init', 'translation'], init=init, translation=translate) From 37ad7787a16c1c7d99f1c4ce416e54e319f694e6 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jun 2022 09:30:56 -0400 Subject: [PATCH 21/27] Run black --- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index bad0bc591419..62e162d937a4 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -175,7 +175,7 @@ def _vf2_match_not_found(property_set): latencies_from_target( coupling_map, instruction_durations, basis_gates, backend_properties, target ) - ) + ), ) elif routing_method == "none": routing_pass = Error( diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index ccc127872f46..d0521f23a8ff 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -207,7 +207,7 @@ def _opt_control(property_set): vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, - use_barrier_before_measurement=not toqm_pass + use_barrier_before_measurement=not toqm_pass, ) else: layout = None diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 482b1325430e..b205ca5f8721 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -227,7 +227,7 @@ def _opt_control(property_set): vf2_call_limit=vf2_call_limit, backend_properties=backend_properties, seed_transpiler=seed_transpiler, - use_barrier_before_measurement=not toqm_pass + use_barrier_before_measurement=not toqm_pass, ) else: layout = None From a2472f29b01d19a8ee55118d5533e507d6cda871 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jun 2022 09:32:44 -0400 Subject: [PATCH 22/27] Update type hint --- qiskit/transpiler/preset_passmanagers/__init__.py | 2 +- qiskit/transpiler/preset_passmanagers/level1.py | 2 +- qiskit/transpiler/preset_passmanagers/level2.py | 2 +- qiskit/transpiler/preset_passmanagers/level3.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index 45ddada26c71..eb2f0d2aed39 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -136,7 +136,7 @@ def generate_preset_pass_manager( to use this option. Returns: - PassManager: The preset pass manager for the given options + StagedPassManager: The preset pass manager for the given options Raises: ValueError: if an invalid value for ``optimization_level`` is passed in. diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 62e162d937a4..724fb12afb4b 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -44,7 +44,7 @@ from qiskit.utils.optionals import HAS_TOQM -def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 1 pass manager: light optimization by simple adjacent gate collapsing. This pass manager applies the user-given initial layout. If none is given, diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index d0521f23a8ff..71727518fd72 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -44,7 +44,7 @@ from qiskit.utils.optionals import HAS_TOQM -def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 2 pass manager: medium optimization by initial layout selection and gate cancellation using commutativity rules. diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index b205ca5f8721..e1da263aced6 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -51,7 +51,7 @@ from qiskit.utils.optionals import HAS_TOQM -def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: +def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassManager: """Level 3 pass manager: heavy optimization by noise adaptive qubit mapping and gate cancellation using commutativity rules and unitary synthesis. From c5ac5f69f2006cbb43afe184ac63027f9394613b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jun 2022 09:35:08 -0400 Subject: [PATCH 23/27] Remove out of date docstring note --- qiskit/transpiler/preset_passmanagers/level0.py | 4 ---- qiskit/transpiler/preset_passmanagers/level1.py | 4 ---- qiskit/transpiler/preset_passmanagers/level2.py | 4 ---- qiskit/transpiler/preset_passmanagers/level3.py | 4 ---- 4 files changed, 16 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/level0.py b/qiskit/transpiler/preset_passmanagers/level0.py index 2dacd0334360..37aadc1c4452 100644 --- a/qiskit/transpiler/preset_passmanagers/level0.py +++ b/qiskit/transpiler/preset_passmanagers/level0.py @@ -45,10 +45,6 @@ def level_0_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa The pass manager then unrolls the circuit to the desired basis, and transforms the circuit to match the coupling map. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. diff --git a/qiskit/transpiler/preset_passmanagers/level1.py b/qiskit/transpiler/preset_passmanagers/level1.py index 724fb12afb4b..5da52fb23052 100644 --- a/qiskit/transpiler/preset_passmanagers/level1.py +++ b/qiskit/transpiler/preset_passmanagers/level1.py @@ -56,10 +56,6 @@ def level_1_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa circuit to match the coupling map. Finally, optimizations in the form of adjacent gate collapse and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. diff --git a/qiskit/transpiler/preset_passmanagers/level2.py b/qiskit/transpiler/preset_passmanagers/level2.py index 71727518fd72..c2ee13ad8500 100644 --- a/qiskit/transpiler/preset_passmanagers/level2.py +++ b/qiskit/transpiler/preset_passmanagers/level2.py @@ -58,10 +58,6 @@ def level_2_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Finally, optimizations in the form of commutative gate cancellation and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index e1da263aced6..7a0f22319ecb 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -65,10 +65,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa Finally, optimizations in the form of commutative gate cancellation, resynthesis of two-qubit unitary blocks, and redundant reset removal are performed. - Note: - In simulators where ``coupling_map=None``, only the unrolling and - optimization stages are done. - Args: pass_manager_config: configuration of the pass manager. From af9e2446c0b59e364ed9d3a7e405db5b882986a1 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 16 Jun 2022 17:42:26 -0400 Subject: [PATCH 24/27] Update copyright header date in qiskit/transpiler/preset_passmanagers/common.py Co-authored-by: Luciano Bello --- qiskit/transpiler/preset_passmanagers/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index fb4664f1f322..ce01dce6a26e 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (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 From 5a446a0781505cef7b8b8940a35338fc65412195 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 17 Jun 2022 10:02:29 -0400 Subject: [PATCH 25/27] Add check for invalid stage names --- qiskit/transpiler/passmanager.py | 17 ++++++++++++ .../transpiler/test_staged_passmanager.py | 26 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 18ea4bf7d4ff..5add2dd6050c 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -12,6 +12,8 @@ """Manager for a set of Passes and their scheduling during transpilation.""" +import io +import re from typing import Union, List, Callable, Dict, Any import dill @@ -355,6 +357,10 @@ class StagedPassManager(PassManager): to set that to the earliest stage in sequence that it covers. """ + invalid_stage_regex = re.compile( + r"\s|\+|\-|\*|\/|\\|\%|\<|\>|\@|\!|\~|\^|\&|\:|\[|\]|\{|\}|\(|\)" + ) + def __init__(self, stages=None, **kwargs): """Initialize a new StagedPassManager object @@ -370,6 +376,7 @@ def __init__(self, stages=None, **kwargs): Raises: AttributeError: If a stage in the input keyword arguments is not defined. + ValueError: If an invalid stage name is specified. """ if stages is None: self.stages = [ @@ -381,6 +388,16 @@ def __init__(self, stages=None, **kwargs): "scheduling", ] else: + invalid_stages = [ + stage for stage in stages if self.invalid_stage_regex.search(stage) is not None + ] + if invalid_stages: + with io.StringIO() as msg: + msg.write(f"The following stage names are not valid: {invalid_stages[0]}") + for invalid_stage in invalid_stages[1:]: + msg.write(f", {invalid_stage}") + raise ValueError(msg.getvalue()) + self.stages = stages super().__init__() for stage in self.stages: diff --git a/test/python/transpiler/test_staged_passmanager.py b/test/python/transpiler/test_staged_passmanager.py index 593b3f6724ea..79045b2cd86d 100644 --- a/test/python/transpiler/test_staged_passmanager.py +++ b/test/python/transpiler/test_staged_passmanager.py @@ -66,3 +66,29 @@ def test_append_extend_not_implemented(self): spm.append(Depth()) with self.assertRaises(NotImplementedError): spm += PassManager() + + def test_invalid_stages(self): + invalid_stages = [ + "two words", + "two-words", + "two+words", + "two&words", + "[two_words]", + "", + "{two_words}", + "(two_words)", + "two^words", + "two_words!", + "^two_words", + "@two_words", + "two~words", + r"two\words", + "two/words", + ] + all_stages = invalid_stages + ["two_words", "init"] + + with self.assertRaises(ValueError) as err: + StagedPassManager(all_stages) + message = str(err.exception) + for stage in invalid_stages: + self.assertIn(stage, message) From 5325217f5c0cf9f2ce475c76b4af3d197519db5a Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 20 Jun 2022 15:43:03 -0400 Subject: [PATCH 26/27] Add backwards compatibility note --- qiskit/transpiler/passmanager.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 5add2dd6050c..064579624c91 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -351,6 +351,17 @@ class StagedPassManager(PassManager): optimize the circuit until a condtion (such as fixed depth) is reached. #. ``scheduling`` - Any hardware aware scheduling passes + .. note:: + + For backwards compatibility the relative positioning of these default + stages will remain stable moving forward. However, new stages may be + added to the default stage list in between current stages. For example, + in a future release a new phase, something like ``logical_optimization``, could be added + immediately after the existing ``init`` stage in the default stage list. + This would preserve compatibility for pre-existing ``StagedPassManager`` + users as the relative positions of the stage are preserved so the behavior + will not change between releases. + These stages will be executed in order and any stage set to ``None`` will be skipped. If a :class:`~qiskit.transpiler.PassManager` input is being used for more than 1 stage here (for example in the case of a :class:`~.Pass` that covers both Layout and Routing) you will want From 8b2756524f5c856600e07fc3e76f91aeb83aa010 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Mon, 20 Jun 2022 16:11:16 -0400 Subject: [PATCH 27/27] Add docs on using StagedPassManager features with preset passmanagers --- qiskit/transpiler/__init__.py | 68 +++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 9274d09414e1..ccaec1f4d224 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -595,6 +595,74 @@
+.. dropdown:: Working with preset :class:`~.PassManager` + :animate: fade-in-slide-down + + By default Qiskit includes functions to build preset :class:`~.PassManager` objects. + These preset passmangers are what get used by the :func:`~.transpile` function + for each optimization level. If you'd like to work directly with a + preset pass manager you can use the :func:`~.generate_preset_pass_manager` + function to easily generate one. For example: + + .. code-block:: python + + from qiskit.transpiler.preset_passmanager import generate_preset_pass_manager + from qiskit.providers.fake_provider import FakeLagosV2 + + backend = FakeLagosV2() + pass_manager = generate_preset_pass_manager(3, backend) + + which will generate a :class:`~.StagedPassManager` object for optimization level 3 + targetting the :class:`~.FakeLagosV2` backend (equivalent to what is used internally + by :func:`~.transpile` with ``backend=FakeLagosV2()`` and ``optimization_level=3``). + You can use this just like working with any other :class:`~.PassManager`. However, + because it is a :class:`~.StagedPassManager` it also makes it easy to compose and/or + replace stages of the pipeline. For example, if you wanted to run a custom scheduling + stage using dynamical decoupling (via the :class:`~.PadDynamicalDecoupling` pass) and + also add initial logical optimization prior to routing you would do something like + (building off the previous example): + + .. code-block:: python + + from qiskit.circuit.library import XGate, HGate, RXGate, PhaseGate, TGate, TdgGate + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import ALAPScheduleAnalysis, PadDynamicalDecoupling + from qiskit.transpiler.passes import CXCancellation, InverseCancellation + + backend_durations = backend.target.durations() + dd_sequence = [XGate(), XGate()] + scheduling_pm = PassManager([ + ALAPScheduleAnalysis(backend_durations), + PadDynamicalDecoupling(backend_durations, dd_sequence), + ]) + inverse_gate_list = [ + HGate(), + (RXGate(np.pi / 4), RXGate(-np.pi / 4)), + (PhaseGate(np.pi / 4), PhaseGate(-np.pi / 4)), + (TGate(), TdgGate()), + + ]) + logical_opt = PassManager([ + CXCancellation(), + InverseCancellation([HGate(), (RXGate(np.pi / 4), RXGate(-np.pi / 4)) + ]) + + + # Add pre-layout stage to run extra logical optimization + pass_manager.pre_layout = logical_opt + # Set scheduling stage to custom pass manager + pass_manager.scheduling = scheduling_pm + + + Then when :meth:`~.StagedPassManager.run` is called on ``pass_manager`` the + ``logical_opt`` :class:`~.PassManager` will be called prior to the ``layout`` stage + and for the ``scheduling`` stage our custom :class:`~.PassManager` + ``scheduling_pm`` will be used. + + .. raw:: html + +
+ Transpiler API ==============