-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Pass manager refactoring: cleanup internals #10127
Changes from 4 commits
6505e20
3fb2464
830c279
07fec44
f0c96a1
fdc1f53
5a03304
f77cf03
8a46f2b
ac12c6d
19987f0
687590e
a5c8417
65fc13d
5034cc4
305fa58
e526bff
2051412
6211a5d
a18dd91
e20949a
5701447
59837b3
ca7dba6
2c11019
f3912e8
ae14aa7
388d1dd
5958f79
dd666e9
f28cad9
9346ce5
c56bf7c
e211d66
ed9a8d7
d889ac0
7fba347
b9348ae
da6c810
31fcf67
f0a547a
5d9f16e
df0ee05
d7cdf8f
f140148
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,11 +13,13 @@ | |
"""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 | ||
from collections.abc import Sequence, Callable | ||
from functools import partial | ||
from typing import List, Union, Any | ||
import logging | ||
|
||
from .base_pass import GenericPass | ||
from .propertyset import FuturePropertySet | ||
from .exceptions import PassManagerError | ||
|
||
logger = logging.getLogger(__name__) | ||
|
@@ -32,7 +34,21 @@ class FlowController: | |
|
||
registered_controllers = OrderedDict() | ||
|
||
def __init__(self, passes, options, **partial_controller): | ||
def __init__( | ||
self, | ||
passes: Sequence[GenericPass | "FlowController"], | ||
nkanazawa1989 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
options: dict, | ||
**partial_controller: Callable, | ||
): | ||
"""Create new flow controller. | ||
|
||
Args: | ||
passes: passes to add to the flow controller. This must be normalized. | ||
options: PassManager options. | ||
**partial_controller: Partially evaluated callables representing | ||
a condition to invoke flow controllers. This dictionary | ||
must be keyed on the registered controller name. | ||
""" | ||
self._passes = passes | ||
self.passes = FlowController.controller_factory(passes, options, **partial_controller) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would dearly love to refactor this I don't know if you'd prefer that to be a different PR, though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. I want to drop |
||
self.options = options | ||
|
@@ -71,6 +87,7 @@ def remove_flow_controller(cls, name): | |
|
||
Args: | ||
name (string): Name of the controller to remove. | ||
|
||
Raises: | ||
KeyError: If the controller to remove was not registered. | ||
""" | ||
|
@@ -90,28 +107,59 @@ def controller_factory( | |
Args: | ||
passes: passes to add to the flow controller. | ||
options: PassManager options. | ||
**partial_controller: Partially evaluated controller arguments in the form `{name:partial}` | ||
**partial_controller: Partially evaluated callables representing | ||
a condition to invoke flow controllers. This dictionary | ||
must be keyed on the registered controller name. | ||
Callable may only take property set, and if the property set is unbound, | ||
the factory method implicitly binds :class:`.FuturePropertySet`. | ||
When multiple controllers and conditions are provided, this recursively | ||
initializes controllers according to the priority defined by | ||
the key order of the :attr:`FlowController.registered_controllers`. | ||
|
||
Raises: | ||
PassManagerError: When partial_controller is not well-formed. | ||
|
||
Returns: | ||
FlowController: A FlowController instance. | ||
""" | ||
if not _is_passes_sequence(passes): | ||
raise PassManagerError( | ||
f"{passes.__class__} is not a valid BasePass of 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) | ||
|
||
for key, value in partial_controller.items(): | ||
if callable(value) and not isinstance(value, partial): | ||
partial_controller[key] = partial(value, FuturePropertySet()) | ||
for label, controller_cls in cls.registered_controllers.items(): | ||
if label in partial_controller: | ||
return controller_cls(passes, options, **partial_controller) | ||
raise PassManagerError(f"The controllers for {partial_controller} are not registered") | ||
return FlowControllerLinear(passes, options) | ||
|
||
|
||
def _is_passes_sequence(passes: Any) -> bool: | ||
"""Check if input is valid pass sequence. | ||
|
||
Valid pass sequence representation are: | ||
|
||
* BasePass, | ||
* FlowController, | ||
* Sequence of BasePass or FlowController. | ||
|
||
Note that nested sequence is not a valid pass sequence. | ||
FlowController.passes must be validated in constructor. | ||
""" | ||
if isinstance(passes, (GenericPass, FlowController)): | ||
return True | ||
for inner_pass in passes: | ||
if not isinstance(inner_pass, (GenericPass, FlowController)): | ||
return False | ||
return True | ||
nkanazawa1989 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class FlowControllerLinear(FlowController): | ||
"""The basic controller runs the passes one after the other.""" | ||
|
||
|
@@ -123,7 +171,17 @@ def __init__(self, passes, options): # pylint: disable=super-init-not-called | |
class DoWhileController(FlowController): | ||
"""Implements a set of passes in a do-while loop.""" | ||
|
||
def __init__(self, passes, options=None, do_while=None, **partial_controller): | ||
def __init__( | ||
self, | ||
passes: PassSequence, | ||
options: dict = None, | ||
do_while: Callable = None, | ||
**partial_controller: Callable, | ||
): | ||
if not callable(do_while): | ||
raise PassManagerError("The flow controller parameter do_while is not callable.") | ||
if not isinstance(do_while, partial): | ||
do_while = partial(do_while, FuturePropertySet()) | ||
self.do_while = do_while | ||
self.max_iteration = options["max_iteration"] if options else 1000 | ||
super().__init__(passes, options, **partial_controller) | ||
|
@@ -141,7 +199,17 @@ def __iter__(self): | |
class ConditionalController(FlowController): | ||
"""Implements a set of passes under a certain condition.""" | ||
|
||
def __init__(self, passes, options=None, condition=None, **partial_controller): | ||
def __init__( | ||
self, | ||
passes: PassSequence, | ||
options: dict = None, | ||
condition: Callable = None, | ||
**partial_controller: Callable, | ||
): | ||
if not callable(condition): | ||
raise PassManagerError("The flow controller parameter condition is not callable.") | ||
if not isinstance(condition, partial): | ||
condition = partial(condition, FuturePropertySet()) | ||
self.condition = condition | ||
super().__init__(passes, options, **partial_controller) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we actually use
BasePass.__eq__
? It seems really a pretty weird thing to have, to me. At any rate,hash(self) == hash(other)
is not a correct implementation of equality haha - it would have been better to not define an__eq__
at all and use the default implementation (which boils down tox is y
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's actually used. Pass runner (currently in flow controller) manages already run passes with a set instance, and it prevents execution of duplicated passes by design. This equivalence is used for the set object. For example,
https://github.com/Qiskit/qiskit-terra/blob/28113b6a9fc1fd31bc211a73fe7a6908fffa1114/test/python/transpiler/test_pass_scheduler.py#L798-L819
The actual log without equivalency check will be
Since both
PassC_TP_RA_PA
andPassB_TP_RA_PA
requirePassA_TP_NR_NP
, which is independently instantiated and added to.requires
. Dropping this violates ~30 unit tests, and may impact user's code by repeatedly executing the requires passes.Strictly speaking this implementation is not really robust. This logic uses Python repr as a easy hash, however every Qiskit object doesn't properly implement the repr dunder (e.g.
Target
). Developer should have implemented the equality dunder for every pass, but it's too late now.