Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StagedPassManager class for pass manager with defined stages #6403

Merged
merged 43 commits into from
Jun 21, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e45b115
Add FullPassManager class for pass manager with defined stages
mtreinish May 12, 2021
90c242c
Add docs
mtreinish May 12, 2021
c9a0ab4
Merge branch 'main' into transpiler-phases
mtreinish Jun 1, 2021
53f34ba
Deduplicate preset passmanager construction
mtreinish Jun 2, 2021
1bebc51
Update docs
mtreinish Jun 2, 2021
76ffb74
Merge branch 'main' into transpiler-phases
mtreinish Jun 2, 2021
c86a7fb
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish Sep 10, 2021
f177d5f
Add dedicated scheduling stage to FullPassManager
mtreinish Sep 10, 2021
cc3c4fd
Add missing new UnitarySynthesis kwargs after rebase
mtreinish Sep 10, 2021
1937354
Use basis_translator as default method instead of basis
mtreinish Oct 18, 2021
b392a45
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish Oct 18, 2021
f30ee80
Rename FullPassManager StructuredPassManager
mtreinish Oct 18, 2021
f336a34
Rename generate_scheduling_post_opt() generate_scheduling()
mtreinish Oct 18, 2021
8637fa1
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish May 23, 2022
06582d4
Fix missing and incorrect arguments
mtreinish May 23, 2022
35a8db9
Fix more rebase issues
mtreinish May 23, 2022
10f543d
Fix even more rebase issues
mtreinish May 24, 2022
ff55b06
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish May 25, 2022
5e5657e
Only run unroll3q on level 0-2 if coupling map is set
mtreinish May 25, 2022
aa1db1f
Rework StructuredPassManager as a more dynamic StagedPassManager
mtreinish May 25, 2022
d654b94
Merge branch 'main' into transpiler-phases
mtreinish May 25, 2022
c51518f
Fix docs
mtreinish May 26, 2022
f6cbb55
Update internal pass set on each access
mtreinish May 26, 2022
445fdfb
Rename phases attribute to stages
mtreinish May 26, 2022
e34cbb8
Fix lint
mtreinish May 26, 2022
5e007f3
Explicitly set name in qpy compat tests
mtreinish May 26, 2022
14494db
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish May 26, 2022
897678c
Merge branch 'main' into transpiler-phases
mtreinish Jun 14, 2022
8414533
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish Jun 14, 2022
75e3d41
Apply suggestions from code review
mtreinish Jun 16, 2022
7c14a6e
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish Jun 16, 2022
37ad778
Run black
mtreinish Jun 16, 2022
a2472f2
Update type hint
mtreinish Jun 16, 2022
c5ac5f6
Remove out of date docstring note
mtreinish Jun 16, 2022
af9e244
Update copyright header date in qiskit/transpiler/preset_passmanagers…
mtreinish Jun 16, 2022
5a446a0
Add check for invalid stage names
mtreinish Jun 17, 2022
2205e3e
Merge branch 'main' into transpiler-phases
mtreinish Jun 17, 2022
7b225ff
Merge branch 'main' into transpiler-phases
mtreinish Jun 17, 2022
0329fb3
Merge remote-tracking branch 'origin/main' into transpiler-phases
mtreinish Jun 20, 2022
5325217
Add backwards compatibility note
mtreinish Jun 20, 2022
8b27565
Add docs on using StagedPassManager features with preset passmanagers
mtreinish Jun 20, 2022
7a8a675
Merge branch 'main' into transpiler-phases
1ucian0 Jun 21, 2022
4063b13
Merge branch 'main' into transpiler-phases
mergify[bot] Jun 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions qiskit/transpiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,74 @@

<br>

.. 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

<br>


Transpiler API
==============
Expand All @@ -614,6 +682,7 @@
.. autosummary::
:toctree: ../stubs/

StagedPassManager
PassManager
PassManagerConfig
PropertySet
Expand Down Expand Up @@ -669,6 +738,7 @@
from .runningpassmanager import FlowController, ConditionalController, DoWhileController
from .passmanager import PassManager
from .passmanager_config import PassManagerConfig
from .passmanager import StagedPassManager
from .propertyset import PropertySet
from .exceptions import TranspilerError, TranspilerAccessError
from .fencedobjs import FencedDAGCircuit, FencedPropertySet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
184 changes: 184 additions & 0 deletions qiskit/transpiler/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -319,3 +321,185 @@ def passes(self) -> List[Dict[str, BasePass]]:
item["flow_controllers"] = {}
ret.append(item)
return ret


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.

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:

#. ``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

.. 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
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

Args:
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 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.
ValueError: If an invalid stage name is specified.
"""
if stages is None:
self.stages = [
"init",
"layout",
"routing",
"translation",
"optimization",
"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:
pre_stage = "pre_" + stage
post_stage = "post_" + stage
setattr(self, pre_stage, None)
setattr(self, stage, None)
setattr(self, post_stage, None)
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
for stage, pm in kwargs.items():
if (
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"{stage} is not a valid stage.")
setattr(self, stage, pm)
self._update_passmanager()

def _update_passmanager(self):
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
self._pass_sets = []
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
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)
Comment on lines +430 to +444
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would happen if I assign a StagedPassManager as a stage of itself (i.e. recursively)? Wouldn't _update_passmanager() become unstable?


def __setattr__(self, attr, value):
super().__setattr__(attr, value)
if (
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)
Comment on lines +450 to +451
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be this?

Suggested change
or (attr.startswith("pre_") and attr[4:] not in self.stages)
or (attr.startswith("post_") and attr[5:] not in self.stages)
or (attr.startswith("pre_") and attr[4:] in self.stages)
or (attr.startswith("post_") and attr[5:] in self.stages)

):
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 __getitem(self, index):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def __getitem(self, index):
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]]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make sense for this to be a @property?

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)
2 changes: 1 addition & 1 deletion qiskit/transpiler/preset_passmanagers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading