-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Pass manager refactoring: add passmanager module (#10124)
* Add passmanager module and reorganize transpiler module * Documentation update Co-authored-by: Matthew Treinish <mtreinish@kortar.org> * Readd qiskit.transpiler.propertyset * Documentation update Co-authored-by: Matthew Treinish <mtreinish@kortar.org> Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com> * BasePass -> GenericPass to avoid name overlap --------- Co-authored-by: Matthew Treinish <mtreinish@kortar.org> Co-authored-by: John Lapeyre <jlapeyre@users.noreply.github.com>
- Loading branch information
1 parent
924702e
commit fbd64d9
Showing
16 changed files
with
1,154 additions
and
480 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.. _qiskit-passmanager: | ||
|
||
.. automodule:: qiskit.passmanager | ||
:no-members: | ||
:no-inherited-members: | ||
:no-special-members: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# 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. | ||
|
||
""" | ||
======================================= | ||
Passmanager (:mod:`qiskit.passmanager`) | ||
======================================= | ||
.. currentmodule:: qiskit.passmanager | ||
Overview | ||
======== | ||
Qiskit pass manager is somewhat inspired by the `LLVM compiler <https://llvm.org/>`_, | ||
but it is designed to take Qiskit object as an input instead of plain source code. | ||
The pass manager converts the input object into an intermediate representation (IR), | ||
and it can be optimized and get lowered with a variety of transformations over multiple passes. | ||
This representation must be preserved throughout the transformation. | ||
The passes may consume the hardware constraints that Qiskit backend may provide. | ||
Finally, the IR is converted back to some Qiskit object. | ||
Note that the input type and output type don't need to match. | ||
Execution of passes is managed by the :class:`.FlowController`, | ||
which is initialized with a set of transform and analysis passes and provides an iterator of them. | ||
This iterator can be conditioned on the :class:`.PropertySet`, which is a namespace | ||
storing the intermediate data necessary for the transformation. | ||
A pass has read and write access to the property set namespace, | ||
and the stored data is shared among scheduled passes. | ||
The :class:`BasePassManager` provides a user interface to build and execute transform passes. | ||
It internally spawns a :class:`BasePassRunner` instance to apply transforms to | ||
the input object. In this sense, the pass manager itself is unaware of the | ||
underlying IR, but it is indirectly tied to a particular IR through the pass runner class. | ||
The responsibilities of the pass runner are the following: | ||
* Defining the input type / pass manager IR / output type. | ||
* Converting an input object to a particular pass manager IR. | ||
* Preparing own property set namespace. | ||
* Running scheduled flow controllers to apply a series of transformations to the IR. | ||
* Converting the IR back to an output object. | ||
A single pass runner always takes a single input object and returns a single output object. | ||
Parallelism for multiple input objects is supported by the :class:`BasePassManager` by | ||
broadcasting the pass runner via the :mod:`qiskit.tools.parallel_map` function. | ||
The base class :class:`BasePassRunner` doesn't define any associated type by itself, | ||
and a developer needs to implement a subclass for a particular object type to optimize. | ||
This `veil of ignorance` allows us to choose the most efficient data representation | ||
for a particular optimization task, while we can reuse the pass flow control machinery | ||
for different input and output types. | ||
Base classes | ||
------------ | ||
.. autosummary:: | ||
:toctree: ../stubs/ | ||
BasePassRunner | ||
BasePassManager | ||
Flow controllers | ||
---------------- | ||
.. autosummary:: | ||
:toctree: ../stubs/ | ||
FlowController | ||
ConditionalController | ||
DoWhileController | ||
PropertySet | ||
----------- | ||
.. autosummary:: | ||
:toctree: ../stubs/ | ||
PropertySet | ||
Exceptions | ||
---------- | ||
.. autosummary:: | ||
:toctree: ../stubs/ | ||
PassManagerError | ||
""" | ||
|
||
from .passrunner import BasePassRunner | ||
from .passmanager import BasePassManager | ||
from .flow_controllers import FlowController, ConditionalController, DoWhileController | ||
from .base_pass import GenericPass | ||
from .propertyset import PropertySet | ||
from .exceptions import PassManagerError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# 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. | ||
|
||
"""Metaclass of Qiskit pass manager pass.""" | ||
|
||
from abc import abstractmethod | ||
from collections.abc import Hashable | ||
from inspect import signature | ||
from typing import Any | ||
|
||
from .propertyset import PropertySet | ||
|
||
|
||
class MetaPass(type): | ||
"""Metaclass for transpiler passes. | ||
Enforces the creation of some fields in the pass while allowing passes to | ||
override ``__init__``. | ||
""" | ||
|
||
def __call__(cls, *args, **kwargs): | ||
pass_instance = type.__call__(cls, *args, **kwargs) | ||
pass_instance._hash = hash(MetaPass._freeze_init_parameters(cls, args, kwargs)) | ||
return pass_instance | ||
|
||
@staticmethod | ||
def _freeze_init_parameters(class_, args, kwargs): | ||
self_guard = object() | ||
init_signature = signature(class_.__init__) | ||
bound_signature = init_signature.bind(self_guard, *args, **kwargs) | ||
arguments = [("class_.__name__", class_.__name__)] | ||
for name, value in bound_signature.arguments.items(): | ||
if value == self_guard: | ||
continue | ||
if isinstance(value, Hashable): | ||
arguments.append((name, type(value), value)) | ||
else: | ||
arguments.append((name, type(value), repr(value))) | ||
return frozenset(arguments) | ||
|
||
|
||
class GenericPass(metaclass=MetaPass): | ||
"""Generic pass class with IR-agnostic minimal functionality.""" | ||
|
||
def __init__(self): | ||
self.requires = [] # List of passes that requires | ||
self.preserves = [] # List of passes that preserves | ||
self.property_set = PropertySet() # This pass's pointer to the pass manager's property set. | ||
self._hash = hash(None) | ||
|
||
def __hash__(self): | ||
return self._hash | ||
|
||
def __eq__(self, other): | ||
return hash(self) == hash(other) | ||
|
||
def name(self): | ||
"""Return the name of the pass.""" | ||
return self.__class__.__name__ | ||
|
||
@abstractmethod | ||
def run(self, passmanager_ir: Any): | ||
"""Run a pass on the pass manager IR. This is implemented by the pass developer. | ||
Args: | ||
passmanager_ir: the dag on which the pass is run. | ||
Raises: | ||
NotImplementedError: when this is left unimplemented for a pass. | ||
""" | ||
raise NotImplementedError |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2023. | ||
# | ||
# 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. | ||
|
||
"""Qiskit pass manager exceptions.""" | ||
|
||
from qiskit.exceptions import QiskitError | ||
|
||
|
||
class PassManagerError(QiskitError): | ||
"""Pass manager error.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2017, 2019, 2023 | ||
# | ||
# 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. | ||
|
||
"""Pass flow controllers to provide pass iterator conditioned on the property set.""" | ||
from __future__ import annotations | ||
from collections import OrderedDict | ||
from collections.abc import Sequence | ||
from typing import Union, List | ||
import logging | ||
|
||
from .base_pass import GenericPass | ||
from .exceptions import PassManagerError | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class FlowController: | ||
"""Base class for multiple types of working list. | ||
This class is a base class for multiple types of working list. When you iterate on it, it | ||
returns the next pass to run. | ||
""" | ||
|
||
registered_controllers = OrderedDict() | ||
|
||
def __init__(self, passes, options, **partial_controller): | ||
self._passes = passes | ||
self.passes = FlowController.controller_factory(passes, options, **partial_controller) | ||
self.options = options | ||
|
||
def __iter__(self): | ||
yield from self.passes | ||
|
||
def dump_passes(self): | ||
"""Fetches the passes added to this flow controller. | ||
Returns: | ||
dict: {'options': self.options, 'passes': [passes], 'type': type(self)} | ||
""" | ||
# TODO remove | ||
ret = {"options": self.options, "passes": [], "type": type(self)} | ||
for pass_ in self._passes: | ||
if isinstance(pass_, FlowController): | ||
ret["passes"].append(pass_.dump_passes()) | ||
else: | ||
ret["passes"].append(pass_) | ||
return ret | ||
|
||
@classmethod | ||
def add_flow_controller(cls, name, controller): | ||
"""Adds a flow controller. | ||
Args: | ||
name (string): Name of the controller to add. | ||
controller (type(FlowController)): The class implementing a flow controller. | ||
""" | ||
cls.registered_controllers[name] = controller | ||
|
||
@classmethod | ||
def remove_flow_controller(cls, name): | ||
"""Removes a flow controller. | ||
Args: | ||
name (string): Name of the controller to remove. | ||
Raises: | ||
KeyError: If the controller to remove was not registered. | ||
""" | ||
if name not in cls.registered_controllers: | ||
raise KeyError("Flow controller not found: %s" % name) | ||
del cls.registered_controllers[name] | ||
|
||
@classmethod | ||
def controller_factory( | ||
cls, | ||
passes: Sequence[GenericPass | "FlowController"], | ||
options: dict, | ||
**partial_controller, | ||
): | ||
"""Constructs a flow controller based on the partially evaluated controller arguments. | ||
Args: | ||
passes: passes to add to the flow controller. | ||
options: PassManager options. | ||
**partial_controller: Partially evaluated controller arguments in the form `{name:partial}` | ||
Raises: | ||
PassManagerError: When partial_controller is not well-formed. | ||
Returns: | ||
FlowController: A FlowController instance. | ||
""" | ||
if None in partial_controller.values(): | ||
raise PassManagerError("The controller needs a condition.") | ||
|
||
if partial_controller: | ||
for registered_controller in cls.registered_controllers.keys(): | ||
if registered_controller in partial_controller: | ||
return cls.registered_controllers[registered_controller]( | ||
passes, options, **partial_controller | ||
) | ||
raise PassManagerError("The controllers for %s are not registered" % partial_controller) | ||
|
||
return FlowControllerLinear(passes, options) | ||
|
||
|
||
class FlowControllerLinear(FlowController): | ||
"""The basic controller runs the passes one after the other.""" | ||
|
||
def __init__(self, passes, options): # pylint: disable=super-init-not-called | ||
self.passes = self._passes = passes | ||
self.options = options | ||
|
||
|
||
class DoWhileController(FlowController): | ||
"""Implements a set of passes in a do-while loop.""" | ||
|
||
def __init__(self, passes, options=None, do_while=None, **partial_controller): | ||
self.do_while = do_while | ||
self.max_iteration = options["max_iteration"] if options else 1000 | ||
super().__init__(passes, options, **partial_controller) | ||
|
||
def __iter__(self): | ||
for _ in range(self.max_iteration): | ||
yield from self.passes | ||
|
||
if not self.do_while(): | ||
return | ||
|
||
raise PassManagerError("Maximum iteration reached. max_iteration=%i" % self.max_iteration) | ||
|
||
|
||
class ConditionalController(FlowController): | ||
"""Implements a set of passes under a certain condition.""" | ||
|
||
def __init__(self, passes, options=None, condition=None, **partial_controller): | ||
self.condition = condition | ||
super().__init__(passes, options, **partial_controller) | ||
|
||
def __iter__(self): | ||
if self.condition(): | ||
yield from self.passes | ||
|
||
|
||
# Alias to a sequence of all kind of pass elements | ||
PassSequence = Union[Union[GenericPass, FlowController], List[Union[GenericPass, FlowController]]] | ||
|
||
# Default controllers | ||
FlowController.add_flow_controller("condition", ConditionalController) | ||
FlowController.add_flow_controller("do_while", DoWhileController) |
Oops, something went wrong.