Skip to content

Commit

Permalink
Tweaks to documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jakelishman committed Oct 16, 2023
1 parent 9346ce5 commit c56bf7c
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 142 deletions.
192 changes: 99 additions & 93 deletions qiskit/passmanager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
Overview
========
Qiskit pass manager is somewhat inspired by the `LLVM compiler <https://llvm.org/>`_,
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 <https://llvm.org/>`_,
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.
Expand All @@ -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
Expand All @@ -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
------------
Expand All @@ -207,8 +214,8 @@ def digit_condition(property_set):
ConditionalController
DoWhileController
PropertySet
-----------
Compilation state
-----------------
.. autosummary::
:toctree: ../stubs/
Expand All @@ -220,7 +227,6 @@ def digit_condition(property_set):
----------
.. autoexception:: PassManagerError
"""

from .passmanager import BasePassManager
Expand Down
2 changes: 1 addition & 1 deletion qiskit/passmanager/base_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down
23 changes: 15 additions & 8 deletions qiskit/passmanager/flow_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 = {}
Expand All @@ -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.
Expand Down
16 changes: 11 additions & 5 deletions qiskit/passmanager/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit c56bf7c

Please sign in to comment.