diff --git a/qiskit/passmanager/__init__.py b/qiskit/passmanager/__init__.py
index f21854756859..a0c4a8314c76 100644
--- a/qiskit/passmanager/__init__.py
+++ b/qiskit/passmanager/__init__.py
@@ -20,8 +20,8 @@
Overview
========
-Qiskit pass manager is somewhat inspired by the `LLVM compiler `_,
-but it is designed to take Python object as an input instead of plain source code.
+The Qiskit pass manager is somewhat inspired by the `LLVM compiler `_,
+but it is designed to take a Python object as an input instead of plain source code.
The pass manager converts the input Python object into an intermediate representation (IR),
and it can be optimized and get lowered with a variety of transformations over multiple passes.
@@ -30,31 +30,33 @@
.. note::
- Currently there is no actual use/design of multiple IRs in builtin Qiskit pass managers.
- At lest implementation of the :mod:`~.passmanager` module is agnostic to
+ Currently there is no actual use/design of multiple IRs in the builtin Qiskit pass managers.
+ The implementation of the :mod:`passmanager` module is agnostic to
actual IR types (i.e. no strict type check is performed), and the pass manager works
as long as the IR implements all methods required by subsequent passes.
- Concrete design for the use of multiple IR might be provided in the future release.
+ A concrete design for the use of multiple IRs might be provided in the future release.
-The passes may consume the hardware constraints that Qiskit backend may provide.
+The passes may consume the hardware constraints that the Qiskit backend may provide.
Finally, the IR is converted back to some Python object.
-Note that the input type and output type are not necessary consistent.
+Note that the input type and output type are not necessarily the same.
-Compilation in the pass manager is a chain of :class:`~.passmanager.Task` execution that
-takes an IR and outputs new IR with some optimization or data analysis.
-An atomic task is a `pass` which is a subclass of the :class:`.GenericPass` that implements
+Compilation in the pass manager is a chain of :class:`~.passmanager.Task` executions that
+take an IR and output a new IR with some optimization or data analysis.
+An atomic task is a *pass* which is a subclass of :class:`.GenericPass` that implements
a :meth:`.~GenericPass.run` method that performs some work on the received IR.
-A set of passes may form a `flow controller` which is a subclass of the
-:class:`.BaseFlowController`, that may repeatedly execute registered passes
-based on user provided conditions.
-Passes can share intermediate data via the :class:`.PropertySet` object which is
-a free form dictionary. A pass can populate the property set dictionary during the task execution.
+A set of passes may form a *flow controller*, which is a subclass of
+:class:`.BaseController`, which can implement arbitrary compilation-state-dependent logic for
+deciding which pass will get run next.
+Passes share intermediate data via the :class:`.PropertySet` object which is
+a free-form dictionary. A pass can populate the property set dictionary during the task execution.
A flow controller can also consume the property set to control the pass execution,
but this access must be read-only.
The property set is portable and handed over from pass to pass at execution.
-In addition to the property set, task can also receive :class:`.WorkflowStatus` data structure.
+In addition to the property set, tasks also receive a :class:`.WorkflowStatus` data structure.
This object is initialized when the first task is started and handed over to successors.
-The status is updated once after every pass logic is run.
+The status is updated after every pass is run, and contains information about the pipeline state
+(number of passes run, failure state, and so on) as opposed to the :class:`PropertySet`, which
+contains information about the IR being optimized.
A pass manager is a wrapper of the flow controller, with responsibilities of
@@ -66,125 +68,130 @@
This indicates that the flow controller itself is type-agnostic, and a developer must
implement a subclass of the :class:`BasePassManager` to manage the data conversion steps.
-This `veil of ignorance` allows us to choose the most efficient data representation
+This *veil of ignorance* allows us to choose the most efficient data representation
for a particular pass manager task, while we can reuse the flow control machinery
for different input and output types.
A single flow controller always takes a single IR object, and returns a single
IR object. Parallelism for multiple input objects is supported by the
:class:`BasePassManager` by broadcasting the flow controller via
-the :mod:`qiskit.tools.parallel_map` function.
+the :func:`qiskit.tools.parallel_map` function.
-Examples:
+Examples
+========
+
+We look into a toy optimization task, namely, preparing a row of numbers
+and remove a digit if the number is five.
+Such task might be easily done by converting the input numbers into string.
+We use the pass manager framework here, putting the efficiency aside for
+a moment to learn how to build a custom Qiskit compiler.
- We look into a toy optimization task, namely, preparing a row of numbers
- and remove a digit if the number is five.
- Such task might be easily done by converting the input numbers into string.
- We dare to use the pass manager framework here, putting the efficiency aside for
- a moment to learn how to build a custom Qiskit compiler.
+.. code-block:: python
- .. code-block:: python
+ from qiskit.passmanager import BasePassManager, GenericPass, ConditionalController
- from qiskit.passmanager import BasePassManager, GenericPass, ConditionalController
+ class ToyPassManager(BasePassManager):
- class ToyPassManager(BasePassManager):
+ def _passmanager_frontend(self, input_program: int, **kwargs) -> str:
+ return str(input_program)
- def _passmanager_frontend(self, input_program: int, **kwargs) -> str:
- return str(input_program)
+ def _passmanager_backend(self, passmanager_ir: str, in_program: int, **kwargs) -> int:
+ return int(passmanager_ir)
- def _passmanager_backend(self, passmanager_ir: str, in_program: int, **kwargs) -> int:
- return int(passmanager_ir)
+This pass manager inputs and outputs an integer number, while
+performing the optimization tasks on a string data.
+Hence, input, IR, output type are integer, string, integer, respectively.
+The :meth:`.~BasePassManager._passmanager_frontend` method defines the conversion from the
+input data to IR, and :meth:`.~BasePassManager._passmanager_backend` defines
+the conversion from the IR to output data.
+The pass manager backend is also given an :code:`in_program` parameter that contains the original
+``input_program`` to the front end, for referencing any original metadata of the input program for
+the final conversion.
- This pass manager inputs and outputs an integer number, while
- performing the optimization tasks on a string data.
- Hence, input, IR, output type are integer, string, integer, respectively.
- The :meth:`.~BasePassManager._passmanager_frontend` defines a conversion from the
- input data to IR, and :meth:`.~BasePassManager._passmanager_backend` defines
- a conversion from the IR to output data.
- The pass manager backend can also take an :code:`in_program` data just for referencing
- the original metadata of the input program for the final conversion.
+Next, we implement a pass that removes a digit when the number is five.
- Next, we implement a pass that removes a digit when the number is five.
+.. code-block:: python
- .. code-block:: python
+ class RemoveFive(GenericPass):
- class RemoveFive(GenericPass):
+ def run(self, passmanager_ir: str):
+ return passmanager_ir.replace("5", "")
- def run(self, passmanager_ir: str):
- return passmanager_ir.replace("5", "")
+ task = RemoveFive()
- task = RemoveFive()
+Finally, we instantiate a pass manager and schedule the task with it.
+Running the pass manager with random row of numbers returns
+new numbers that don't contain five.
- Finally, we instantiate a pass manager and schedule the task with it.
- Running the pass manager with random row of numbers returns
- new numbers that don't contain five.
+.. code-block:: python
- .. code-block:: python
+ pm = ToyPassManager()
+ pm.append(task)
- pm = ToyPassManager()
- pm.append(task)
+ pm.run([123456789, 45654, 36785554])
- pm.run([123456789, 45654, 36785554])
+Output:
- Output:
+.. parsed-literal::
- .. parsed-literal::
+ [12346789, 464, 36784]
- [12346789, 464, 36784]
+Now we consider the case of conditional execution.
+We avoid execution of the "remove five" task when the input number is
+six digits or less. Such control can be implemented by a flow controller.
+We start from an analysis pass that provides the flow controller
+with information about the number of digits.
- Now we consider the case of conditional execution.
- We avoid execution of the remove five task when the input number is
- six digit or less. Such control can be implemented by a flow controller.
- We start from an analysis pass that provides the flow controller
- with information about the number of digits.
+.. code-block:: python
- .. code-block:: python
+ class CountDigits(GenericPass):
- class CountDigits(GenericPass):
+ def run(self, passmanager_ir: str):
+ self.property_set["ndigits"] = len(passmanager_ir)
- def run(self, passmanager_ir: str):
- self.property_set["ndigits"] = len(passmanager_ir)
+ analysis_task = CountDigits()
- analysis_task = CountDigits()
+Then, we wrap the remove five task with the :class:`.ConditionalController`
+that runs the stored tasks only when the condition is met.
- Then, we wrap the remove five task with the :class:`.ConditionalController`
- that runs the stored tasks only when the condition is met.
+.. code-block:: python
- .. code-block:: python
+ def digit_condition(property_set):
+ # Return True when condition is met.
+ return property_set["ndigits"] > 6
- def digit_condition(property_set):
- # Return True when condition is met.
- return property_set["ndigits"] > 6
+ conditional_task = ConditionalController(
+ tasks=[RemoveFive()],
+ condition=digit_condition,
+ )
- conditional_task = ConditionalController(
- tasks=[RemoveFive()],
- condition=digit_condition,
- )
+As before, we schedule these passes with the pass manager and run.
- As before, we schedule these passes with the pass manager and run.
+.. code-block:: python
- .. code-block:: python
+ pm = ToyPassManager()
+ pm.append(analysis_task)
+ pm.append(conditional_task)
- pm = ToyPassManager()
- pm.append(analysis_task)
- pm.append(conditional_task)
+ pm.run([123456789, 45654, 36785554])
- pm.run([123456789, 45654, 36785554])
+Output:
- Output:
+.. parsed-literal::
- .. parsed-literal::
+ [12346789, 45654, 36784]
- [12346789, 45654, 36784]
+The "remove five" task is triggered only for the first and third input
+values, which have more than six digits.
- The remove five task is triggered only for the first and third input
- values with more than six digits.
+With the pass manager framework, a developer can flexibly customize
+the optimization task by combining multiple passes and flow controllers.
+See details for following class API documentations.
- With the pass manager framework, a developer can flexibly customize
- the optimization task by combining multiple passes and flow controllers.
- See details for following class API documentations.
+Interface
+=========
Base classes
------------
@@ -207,8 +214,8 @@ def digit_condition(property_set):
ConditionalController
DoWhileController
-PropertySet
------------
+Compilation state
+-----------------
.. autosummary::
:toctree: ../stubs/
@@ -220,7 +227,6 @@ def digit_condition(property_set):
----------
.. autoexception:: PassManagerError
-
"""
from .passmanager import BasePassManager
diff --git a/qiskit/passmanager/base_tasks.py b/qiskit/passmanager/base_tasks.py
index 2511d5e81aab..285666d13d5a 100644
--- a/qiskit/passmanager/base_tasks.py
+++ b/qiskit/passmanager/base_tasks.py
@@ -163,7 +163,7 @@ class BaseController(Task, ABC):
A controller is built with a collection of pass manager tasks,
and a subclass provides a custom logic to choose next task to run.
Note a controller can be nested into another controller,
- and controller itself doesn't provide any subroutine to modify the input IR.
+ and a controller itself doesn't provide any subroutine to modify the input IR.
"""
def __init__(
diff --git a/qiskit/passmanager/flow_controllers.py b/qiskit/passmanager/flow_controllers.py
index 805e1ddaa94d..0277c3731ea3 100644
--- a/qiskit/passmanager/flow_controllers.py
+++ b/qiskit/passmanager/flow_controllers.py
@@ -88,7 +88,11 @@ def iter_tasks(
class DoWhileController(BaseController):
- """A flow controller that repeatedly run the entire pipeline until the condition is not met."""
+ """Run the given tasks in a loop until the ``do_while`` condition on the property set becomes
+ ``False``.
+
+ The given tasks will always run at least once, and on iteration iteration of the loop, all the
+ tasks will be run (with the exception of a failure state being set)."""
def __init__(
self,
@@ -154,7 +158,8 @@ def iter_tasks(
class ConditionalController(BaseController):
- """A flow controller runs the pipeline once when the condition is met."""
+ """A flow controller runs the pipeline once if the condition is true, or does nothing if the
+ condition is false."""
def __init__(
self,
@@ -216,11 +221,15 @@ def iter_tasks(
class FlowController(BaseController):
- """A flow controller with namespace to register controller subclasses.
+ """A legacy factory for other flow controllers.
+
+ .. warning::
- This allows syntactic suger of writing pipeline. For example,
+ This class is primarily for compatibility with legacy versions of Qiskit, and in general,
+ you should prefer simply instantiating the controller you want, and adding it to the
+ relevant :class:`.PassManager` or other controller. Its use is deprecated.
- .. code-block:: python
+ This allows syntactic sugar for writing pipelines. For example::
FlowController.add_flow_controller("my_condition", CustomController)
@@ -246,8 +255,6 @@ class FlowController(BaseController):
Note that factory method implicitly determines the priority of the builtin controllers
when multiple controllers are called together,
and the behavior of generated controller is hardly debugged.
- This class might be dropped in the future release.
-
"""
registered_controllers = {}
@@ -268,7 +275,7 @@ def controller_factory(
options: dict,
**controllers,
):
- """Create new flow controller with normalization.
+ """Create a new flow controller with normalization.
Args:
passes: A list of optimization tasks.
diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py
index 317049cb7a71..dfe218a8a9a1 100644
--- a/qiskit/passmanager/passmanager.py
+++ b/qiskit/passmanager/passmanager.py
@@ -64,11 +64,17 @@ def append(
Args:
tasks: A set of pass manager tasks to be added to schedule. When multiple
tasks are provided, tasks are grouped together as a single flow controller.
+
flow_controller_conditions: Dictionary of control flow plugins.
Following built-in controllers are available by default:
- * do_while: The passes repeat until the callable returns False.
- * condition: The passes run only if the callable returns True.
+ * do_while: The passes repeat until the callable returns False. Corresponds to
+ :class:`.DoWhileController`.
+ * condition: The passes run only if the callable returns True. Corresponds to
+ :class:`.ConditionalController`.
+
+ In general you have finer-grained control over pass sequencing if you simply
+ instantiate the flow controller you want manually and given that to :meth:`append`.
"""
if flow_controller_conditions:
tasks = _legacy_build_flow_controller(
@@ -89,7 +95,7 @@ def replace(
"""Replace a particular pass in the scheduler.
Args:
- index: Pass index to replace, based on the position in passes().
+ index: Pass index to replace, based on the position in :meth:`passes`
tasks: A set of pass manager tasks to be added to schedule. When multiple
tasks are provided, tasks are grouped together as a single flow controller.
flow_controller_conditions: Dictionary of control flow plugins.
@@ -117,7 +123,7 @@ def remove(self, index: int) -> None:
"""Removes a particular pass in the scheduler.
Args:
- index: Pass index to remove, based on the position in passes().
+ index: Pass index to remove, based on the position in :meth:`passes`.
Raises:
PassManagerError: If the index is not found.
@@ -266,7 +272,7 @@ def callback_func(**kwargs):
)
def to_flow_controller(self) -> FlowControllerLinear:
- """Linearize this manager into a single :class:`.FlowControllerLiner`,
+ """Linearize this manager into a single :class:`.FlowControllerLinear`,
so that it can be nested inside another pass manager.
Returns:
diff --git a/qiskit/passmanager/propertyset.py b/qiskit/passmanager/propertyset.py
index 4b95c68c4648..f378ad4e95e1 100644
--- a/qiskit/passmanager/propertyset.py
+++ b/qiskit/passmanager/propertyset.py
@@ -38,7 +38,7 @@ class WorkflowStatus:
This data structure is initialized when the first task in the pass manager is run,
and recursively handed over to subsequent tasks.
- Pass will update status once after being executed and the lifetime of the
+ Each pass will update this status once after being executed, and the lifetime of the
workflow status object is the time during which the pass manager is running.
"""
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index 5ae623ac93ed..a173838d3ccf 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -132,8 +132,13 @@ def append(
flow_controller_conditions: Dictionary of control flow plugins.
Following built-in controllers are available by default:
- * do_while: The passes repeat until the callable returns False.
- * condition: The passes run only if the callable returns True.
+ * do_while: The passes repeat until the callable returns False. Corresponds to
+ :class:`.DoWhileController`.
+ * condition: The passes run only if the callable returns True. Corresponds to
+ :class:`.ConditionalController`.
+
+ In general, you have more control simply by creating the controller you want and
+ passing it to :meth:`append`.
Raises:
TranspilerError: if a pass in passes is not a proper pass.
@@ -237,6 +242,12 @@ def run(
property_set (PropertySet): the property set
count (int): the index for the pass execution
+ .. note::
+
+ Beware that the keyword arguments here are different to those used by the
+ generic :class:`.BasePassManager`. This pass manager will translate those
+ arguments into the form described above.
+
The exact arguments pass expose the internals of the pass
manager and are subject to change as the pass manager internals
change. If you intend to reuse a callback function over
diff --git a/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml b/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml
index 2d7d200d2854..289facf63220 100644
--- a/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml
+++ b/releasenotes/notes/add-passmanager-module-3ae30cff52cb83f1.yaml
@@ -3,58 +3,56 @@ features:
- |
A new module :mod:`qiskit.passmanager` is added.
This module implements a generic pass manager and flow controllers,
- and provides an infrastructure to manage execution of pass manager tasks.
- The pass manager is a baseclass and not aware of the input and output object types,
+ and provides infrastructure to manage execution of pass manager tasks.
+ The pass manager is a base class and not aware of the input and output object types,
and subclass must be created for a particular program type to optimize.
The :mod:`qiskit.transpiler` module is also reorganized to rebuild the existing
pass manager based off of the generic pass manager.
See upgrade notes for more details.
upgrade:
- |
- New base class for passes :class:`.GenericPass` and flow controllers :class:`.BaseController`
+ One new base class for passes (:class:`.GenericPass`) and one for flow controllers (:class:`.BaseController`)
are introduced in the :mod:`qiskit.passmanager` module.
Because the flow controller is a collection of passes and a controller can be recursively
nested into the task pipeline, new classes are designed with the idea of
- the composite pattern, and the interface class :class:`qiskit.passmanager.base_task.Task`
- is also introduced. This class defines a signature of :meth:`Task.execute` method.
+ the composite pattern, and the interface class :class:`.passmanager.Task`
+ is also introduced. This class defines the signature of a :meth:`.Task.execute` method.
This unified design eliminates complexity of the conventional pass manager;
the execution logic dispatch and task structure renormalization
- are no longer necessary, whom the :class:`RunningPassManager` used to be responsible for.
+ are no longer necessary, whom the :class:`.RunningPassManager` used to be responsible for.
Existing flow controllers :class:`.FlowControllerLinear`,
- :class:`.ConditionalController`, and :class:`.DoWhileController` are now subclass of
+ :class:`.ConditionalController`, and :class:`.DoWhileController` are now subclasses of
the :class:`.BaseController`.
Note that these controllers are no longer iterable, as they drop the implementation of
- :class:`__iter__` method.
- Task pipeline is in principle undetermined until the controller is executed with the
- populated property set, and iterator may return random chain of tasks
- if the controller is just iterated without having the property set populated.
+ :meth:`~object.__iter__` method; they are now only iterable in the context of a flow-controller
+ execution, which threads the compilation state through after each inner task is executed.
- |
- The :class:`RunningPassManager` becomes largely an alias of :class:`.FlowControllerLinear`,
+ The :class:`.RunningPassManager` becomes largely an alias of :class:`.FlowControllerLinear`,
and this class will be completely replaced with the flow controller in the feature release.
This means the running pass manager becomes a stateless flow controller, and
the pass manager framework consists of :class:`.BasePassManager` and :class:`.BaseController`.
The pass manager is responsible for the construction of task pipeline,
while the controller is responsible for the execution of associated tasks.
- Subclassing the :class:`RunningPassManager` is no longer recommended.
+ Subclassing the :class:`.RunningPassManager` is no longer recommended.
- |
- New class :class:`.WorkflowStatus` is introduced to track the status of pass manager workflow.
+ A new class :class:`.WorkflowStatus` is introduced to track the status of pass manager workflow.
This portable object is created when the first task in the pass manager pipeline is executed,
and handed over to the subsequent tasks.
Such status was previously managed by the :class:`.RunningPassManager` with instance variables,
- however, now running pass manager becomes a stateless controller object.
+ however, now running pass manager becomes a controller object.
- |
- Transpiler :class:`qiskit.transpiler.PassManager` is now a subclass of
- the :class:`qiskit.passmanager.BasePassManager`.
+ The transpiler-specific (:func:`.transpile`) :class:`.transpiler.PassManager` is now a subclass of
+ the :class:`.passmanager.BasePassManager`.
There is no API break at public member level due to this class hierarchy change.
- |
- New error baseclass :class:`~qiskit.passmanager.PassManagerError` is introduced.
- This will replace :class:`~qiskit.transpiler.TranspilerError` raised in the
- task handling machinery. The TranspilerError is now only used for the errors
- related to the failure in handling of the quantum circuit or DAG circuit object.
- Note that the TranspilerError can be caught by the PassManagerError
- because of their class hierarchy. For backward compatibility,
- :class:`qiskit.transpiler.PassManager` catches PassManagerError and
- re-raises the TranspilerError. This error replacement will be dropped in future.
+ A new exception :exc:`~qiskit.passmanager.PassManagerError` is introduced as the base class of
+ exceptions raised during pass-manager execution. The transpiler-specific
+ :class:`.transpile.PassManager` continues to raise :exc:`.TranspilerError`, which is now a
+ subclass of :exc:`.PassManagerError`, for errors raised by specific tasks. A generic failure of
+ the pass-manager machinery, typically indicating programmer error and not recoverable, will
+ raise :exc:`.PassManagerError` for general pass managers, but :class:`.transpile.PassManager`
+ will currently wrap this in its specific :exc:`.TranspilerError` for backwards compatibility.
+ This wrapping will be removed in the future.
- |
Use of :class:`.FencedObject` in the pass manager framework is removed.
These wrapper class cannot protect mutable object attribute from modification,
@@ -81,15 +79,18 @@ deprecations:
controller = ConditionalController([task1, task2], condition=lambda x: x["value1"] > 10)
pm.append(controller)
- The latter must allow more precise control on the order of controllers
- especially when multiple keyword arguments are specified together.
+ The latter allows more precise control on the order of controllers
+ especially when multiple keyword arguments are specified together, and allows for the
+ construction of general flow controllers that may have more than one pipeline or do not take a
+ single simple conditional function in their constructors.
- |
The :meth:`.FlowControllerLinear.append`, :meth:`.DoWhileController.append`,
- and :meth:`.ConditionalController.append` method are all deprecated immediately.
+ and :meth:`.ConditionalController.append` methods are all deprecated immediately.
The construction of pass manager task pipeline is the role of :class:`.BasePassManager`,
- and individual flow controller doesn't need to implement method like this.
+ and individual flow controller do not need to implement method like this. For a flow
+ controller, you should pass all the passes in one go directly to the constructor.
- |
- The all attribute and variable name :code:`passes` are replaced with :code:`tasks`
- in the :mode:`passmanager` module. Note that a task must indicate a union of pass and controller,
- and the singular form `pass` conflicts with the Python keyword.
+ The general attribute and variable name :code:`passes` is replaced with :code:`tasks`
+ all over the :mod:`qiskit.passmanager` module. Note that a task must indicate a union of pass
+ and controller, and the singular form `pass` conflicts with the Python keyword.
In this sense, use of `tasks` is much preferable.