Repository: Qiskit/qiskit-terra
Pull request #5772 (2021-02-02 14:35:15)

Allow passmanager.append(flowcontroller) instead of passes
As part of adding support for a `BestOf` pass manager flow controller, @kdk [suggested](https://github.com/Qiskit/qiskit-terra/pull/3018#discussion_r375440895) the possibility of adding flow controllers to a pass manager.

I think that makes total sense.

CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/Qiskit/qiskit-terra?pullRequest=5772) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our [Contributor License Agreement](https://cla-assistant.io/Qiskit/qiskit-terra?pullRequest=5772) before we can accept your contribution.<br/>**1** out of **2** committers have signed the CLA.<br/><br/>:white_check_mark: 1ucian0<br/>:x: Luciano Bello<br/><hr/>**Luciano Bello** seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please [add the email address used for this commit to your account](https://help.github.com/articles/why-are-my-commits-linked-to-the-wrong-user/#commits-are-not-linked-to-any-user).<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/Qiskit/qiskit-terra?pullRequest=5772) it.</sub>

Patch:
diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py
index b6407e238..7983dc5a0 100644
--- a/qiskit/transpiler/passmanager.py
+++ b/qiskit/transpiler/passmanager.py
@@ -21,7 +21,7 @@ from qiskit.tools.parallel import parallel_map
 from qiskit.circuit import QuantumCircuit
 from .basepasses import BasePass
 from .exceptions import TranspilerError
-from .runningpassmanager import RunningPassManager
+from .runningpassmanager import RunningPassManager, FlowController
 
 
 class PassManager:
@@ -60,6 +60,9 @@ class PassManager:
             passes: A set of passes (a pass set) to be added to schedule. A pass set is a list of
                     passes that are controlled by the same flow controller. If a single pass is
                     provided, the pass set will only have that pass a single element.
+                    It is also possibble to append a
+                    :class:`~qiskit.transpiler.runningpassmanager.FlowController` instance and the
+                    rest of the parameter will be ignored.
             max_iteration: max number of iterations of passes.
             flow_controller_conditions: control flow plugins.
 
@@ -156,7 +159,10 @@ class PassManager:
                                                                               other.__class__))
 
     @staticmethod
-    def _normalize_passes(passes: Union[BasePass, List[BasePass]]) -> List[BasePass]:
+    def _normalize_passes(passes: Union[BasePass, List[BasePass], FlowController])\
+            -> List[BasePass]:
+        if isinstance(passes, FlowController):
+            return passes
         if isinstance(passes, BasePass):
             passes = [passes]
         for pass_ in passes:
diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py
index 02798843e..ade11f85e 100644
--- a/qiskit/transpiler/runningpassmanager.py
+++ b/qiskit/transpiler/runningpassmanager.py
@@ -75,12 +75,21 @@ class RunningPassManager:
         Raises:
             TranspilerError: if a pass in passes is not a proper pass.
         """
-        flow_controller_conditions = self._normalize_flow_controller(flow_controller_conditions)
-
-        self.working_list.append(
-            FlowController.controller_factory(passes,
-                                              self.passmanager_options,
-                                              **flow_controller_conditions))
+        # attaches the property set to the controller so it has access to it.
+        if isinstance(passes, ConditionalController):
+            passes.condition = partial(passes.condition, self.fenced_property_set)
+            self.working_list.append(passes)
+        if isinstance(passes, DoWhileController):
+            if not isinstance(passes.do_while, partial):
+                passes.do_while = partial(passes.do_while, self.fenced_property_set)
+            self.working_list.append(passes)
+        else:
+            flow_controller_conditions = self._normalize_flow_controller(flow_controller_conditions)
+            self.working_list.append(
+                FlowController.controller_factory(passes,
+                                                  self.passmanager_options,
+                                                  **flow_controller_conditions))
+            pass
 
     def _normalize_flow_controller(self, flow_controller):
         for name, param in flow_controller.items():
@@ -295,10 +304,10 @@ class FlowControllerLinear(FlowController):
 class DoWhileController(FlowController):
     """Implements a set of passes in a do-while loop."""
 
-    def __init__(self, passes, options, do_while=None,
+    def __init__(self, passes, options=None, do_while=None,
                  **partial_controller):
         self.do_while = do_while
-        self.max_iteration = options['max_iteration']
+        self.max_iteration = options['max_iteration'] if options else 1000
         super().__init__(passes, options, **partial_controller)
 
     def __iter__(self):
@@ -314,7 +323,7 @@ class DoWhileController(FlowController):
 class ConditionalController(FlowController):
     """Implements a set of passes under a certain condition."""
 
-    def __init__(self, passes, options, condition=None,
+    def __init__(self, passes, options=None, condition=None,
                  **partial_controller):
         self.condition = condition
         super().__init__(passes, options, **partial_controller)


Test patch:
diff --git a/test/python/transpiler/test_pass_scheduler.py b/test/python/transpiler/test_pass_scheduler.py
index 3abe78019..ad639156d 100644
--- a/test/python/transpiler/test_pass_scheduler.py
+++ b/test/python/transpiler/test_pass_scheduler.py
@@ -120,6 +120,18 @@ class TestUseCases(SchedulerTestCase):
                               'set property as True',
                               'run transformation pass PassA_TP_NR_NP'])
 
+    def test_conditional_passes_true_fc(self):
+        """A pass set with a conditional parameter (with FlowController). The callable is True."""
+        self.passmanager.append(PassE_AP_NR_NP(True))
+
+        self.passmanager.append(
+            ConditionalController([PassA_TP_NR_NP()],
+                                  condition=lambda property_set: property_set['property']))
+        self.assertScheduler(self.circuit, self.passmanager,
+                             ['run analysis pass PassE_AP_NR_NP',
+                              'set property as True',
+                              'run transformation pass PassA_TP_NR_NP'])
+
     def test_conditional_passes_false(self):
         """A pass set with a conditional parameter. The callable is False."""
         self.passmanager.append(PassE_AP_NR_NP(False))
@@ -371,6 +383,59 @@ class TestUseCases(SchedulerTestCase):
                               'run transformation pass PassF_reduce_dag_property',
                               'dag property = 2'])
 
+    def test_fixed_point_fc(self):
+        """A fixed point scheduler with flow control. """
+        self.passmanager.append(DoWhileController(
+            [PassK_check_fixed_point_property(),
+             PassA_TP_NR_NP(),
+             PassF_reduce_dag_property()],
+            do_while=lambda property_set: not property_set['property_fixed_point']))
+
+        expected = ['run analysis pass PassG_calculates_dag_property',
+                    'set property as 8 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 6',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 6 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 5',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 5 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 4',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 4 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 3',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 3 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 2',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 2 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 2',
+                    'run analysis pass PassG_calculates_dag_property',
+                    'set property as 2 (from dag.property)',
+                    'run analysis pass PassK_check_fixed_point_property',
+                    'run transformation pass PassA_TP_NR_NP',
+                    'run transformation pass PassF_reduce_dag_property',
+                    'dag property = 2']
+
+        self.assertScheduler(self.circuit, self.passmanager, expected)
+
     def test_fixed_point_pass_max_iteration(self):
         """A pass set with a do_while parameter that checks that
         the max_iteration is raised."""