From b66030e3b9192efdd3eb95cf25c6545fe0a13da4 Mon Sep 17 00:00:00 2001 From: Isha Rajput <33170219+irajput@users.noreply.github.com> Date: Fri, 14 Jan 2022 15:45:03 -0800 Subject: [PATCH] Nesting FlowControllers to allow nested conditionals in pass manager (#6962) * added reno * changes * added suggested changes * format * fixed raise * added tests * added tests for nested do_while * Test edit * Fix nested conditional test This was meant to test a conditional within a while loop. Before this commit, the condition being tested never changed during the while loop. With this commit, the value checked by the conditional is different for different iterations. And this test confirms that the conditional pass runs when it is supposed to and does not run when it is not supposed to. * Remove two tests that don't test this PR Two tests were included in this PR whose behavior would be unaffected by the code changes in this PR. So they don't belong in this PR. I am removing them with this commit. modified: test_pass_scheduler.py Co-authored-by: John Lapeyre Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/transpiler/passmanager.py | 9 ++- qiskit/transpiler/runningpassmanager.py | 38 +++++++++---- ...tionals-pass-manager-db7b8b9874018d0d.yaml | 8 +++ test/python/transpiler/test_pass_scheduler.py | 55 +++++++++++++++++++ 4 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/add-nested-conditionals-pass-manager-db7b8b9874018d0d.yaml diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index fcd69d6976d0..b472d463c808 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -167,8 +167,13 @@ def _normalize_passes( if isinstance(passes, BasePass): passes = [passes] for pass_ in passes: - if not isinstance(pass_, BasePass): - raise TranspilerError("%s is not a pass instance" % pass_.__class__) + if isinstance(pass_, FlowController): + # Normalize passes in nested FlowController + PassManager._normalize_passes(pass_.passes) + elif not isinstance(pass_, BasePass): + raise TranspilerError( + "%s is not a BasePass or FlowController instance " % pass_.__class__ + ) return passes def run( diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py index 7b065cad6ac9..5e4e5ff02b15 100644 --- a/qiskit/transpiler/runningpassmanager.py +++ b/qiskit/transpiler/runningpassmanager.py @@ -20,6 +20,7 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.transpiler.basepasses import BasePass from .propertyset import PropertySet from .fencedobjs import FencedPropertySet, FencedDAGCircuit from .exceptions import TranspilerError @@ -132,10 +133,10 @@ def run(self, circuit, output_name=None, callback=None): return circuit def _do_pass(self, pass_, dag, options): - """Do a pass and its "requires". + """Do either a pass and its "requires" or FlowController. Args: - pass_ (BasePass): Pass to do. + pass_ (BasePass or FlowController): Pass to do. dag (DAGCircuit): The dag on which the pass is ran. options (dict): PassManager options. Returns: @@ -144,18 +145,35 @@ def _do_pass(self, pass_, dag, options): Raises: TranspilerError: If the pass is not a proper pass instance. """ + if isinstance(pass_, BasePass): + # First, do the requires of pass_ + for required_pass in pass_.requires: + dag = self._do_pass(required_pass, dag, options) - # First, do the requires of pass_ - for required_pass in pass_.requires: - dag = self._do_pass(required_pass, dag, options) + # Run the pass itself, if not already run + if pass_ not in self.valid_passes: + dag = self._run_this_pass(pass_, dag) - # Run the pass itself, if not already run - if pass_ not in self.valid_passes: - dag = self._run_this_pass(pass_, dag) + # update the valid_passes property + self._update_valid_passes(pass_) - # update the valid_passes property - self._update_valid_passes(pass_) + # if provided a nested flow controller + elif isinstance(pass_, FlowController): + if isinstance(pass_, ConditionalController) and not isinstance( + pass_.condition, partial + ): + pass_.condition = partial(pass_.condition, self.fenced_property_set) + + elif isinstance(pass_, DoWhileController) and not isinstance(pass_.do_while, partial): + pass_.do_while = partial(pass_.do_while, self.fenced_property_set) + + for _pass in pass_: + self._do_pass(_pass, dag, pass_.options) + else: + raise TranspilerError( + "Expecting type BasePass or FlowController, got %s." % type(pass_) + ) return dag def _run_this_pass(self, pass_, dag): diff --git a/releasenotes/notes/add-nested-conditionals-pass-manager-db7b8b9874018d0d.yaml b/releasenotes/notes/add-nested-conditionals-pass-manager-db7b8b9874018d0d.yaml new file mode 100644 index 000000000000..c0d9d764dbe6 --- /dev/null +++ b/releasenotes/notes/add-nested-conditionals-pass-manager-db7b8b9874018d0d.yaml @@ -0,0 +1,8 @@ + +features: + - | + Allows adding nested FlowControllers (like ConditionalController) to PassManagers when adding passes in the append function. + example:: + + flow_unroll = [ConditionalController(_unroll, condition=_unroll_condition)] + pm.append(_depth_check + _opt + _unroll_check + flow_unroll, do_while=_opt_control) diff --git a/test/python/transpiler/test_pass_scheduler.py b/test/python/transpiler/test_pass_scheduler.py index 7a532066527a..0edd49d4583c 100644 --- a/test/python/transpiler/test_pass_scheduler.py +++ b/test/python/transpiler/test_pass_scheduler.py @@ -550,6 +550,61 @@ def test_fresh_initial_state(self): ], ) + def test_nested_conditional_in_loop(self): + """Run a loop with a nested conditional.""" + nested_conditional = [ + ConditionalController( + [PassA_TP_NR_NP()], condition=lambda property_set: property_set["property"] >= 5 + ) + ] + self.passmanager.append( + [PassK_check_fixed_point_property()] + + nested_conditional + + [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 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 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 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 PassF_reduce_dag_property", + "dag property = 2", + ] + self.assertScheduler(self.circuit, self.passmanager, expected) + class DoXTimesController(FlowController): """A control-flow plugin for running a set of passes an X amount of times."""