Repository: Qiskit/qiskit

Issue:
Allow `TransformationPass` to write to property set
The property set is used to communicate information about the transpilation process "out-of-band" from one pass to the next. We originally disallowed `TransformationPass`es from writing it, with the idea that `AnalysisPass`es would exclusively collect information and `TransformationPass`es would exclusively modify circuits.

I think the latter assumption is hard to maintain going forward. For example a TransformationPass may have internal state that is useful to a future pass (the example I have is the "final_layout" that is computed from a routing pass). We would need to communicate this information somehow. 

Within the current architecture, the only way to do this is to write an AnalysisPass that does the exact same thing as the routing pass, but does not actually transform the circuit and only returns the "final_layout". This is clearly expensive.

So I think the solution is to either allow a `TransformationPass` to modify property set, or to declare the routing pass as inheriting from both `AnalysisPass` *and* `TransformationPass`. I couldn't get the latter to actually work though.


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/runningpassmanager.py b/qiskit/transpiler/runningpassmanager.py
--- a/qiskit/transpiler/runningpassmanager.py
+++ b/qiskit/transpiler/runningpassmanager.py
@@ -45,8 +45,8 @@ def __init__(self, max_iteration):
         self.working_list = []
 
         # global property set is the context of the circuit held by the pass manager
-        # as it runs through its scheduled passes. Analysis passes may update the property_set,
-        # but transformation passes have read-only access (via the fenced_property_set).
+        # as it runs through its scheduled passes. The flow controller
+        # have read-only access (via the fenced_property_set).
         self.property_set = PropertySet()
         self.fenced_property_set = FencedPropertySet(self.property_set)
 
@@ -150,8 +150,8 @@ def _do_pass(self, pass_, dag, options):
         return dag
 
     def _run_this_pass(self, pass_, dag):
+        pass_.property_set = self.property_set
         if pass_.is_transformation_pass:
-            pass_.property_set = self.fenced_property_set
             # Measure time if we have a callback or logging set
             start_time = time()
             new_dag = pass_.run(dag)
@@ -171,7 +171,6 @@ def _run_this_pass(self, pass_, dag):
                                                                          type(new_dag)))
             dag = new_dag
         elif pass_.is_analysis_pass:
-            pass_.property_set = self.property_set
             # Measure time if we have a callback or logging set
             start_time = time()
             pass_.run(FencedDAGCircuit(dag))


Test patch:
diff --git a/test/python/transpiler/test_pass_scheduler.py b/test/python/transpiler/test_pass_scheduler.py
--- a/test/python/transpiler/test_pass_scheduler.py
+++ b/test/python/transpiler/test_pass_scheduler.py
@@ -28,7 +28,7 @@
 from qiskit.test import QiskitTestCase
 from ._dummy_passes import (PassA_TP_NR_NP, PassB_TP_RA_PA, PassC_TP_RA_PA,
                             PassD_TP_NR_NP, PassE_AP_NR_NP, PassF_reduce_dag_property,
-                            PassH_Bad_TP, PassI_Bad_AP, PassJ_Bad_NoReturn,
+                            PassI_Bad_AP, PassJ_Bad_NoReturn,
                             PassK_check_fixed_point_property, PassM_AP_NR_NP)
 
 
@@ -276,13 +276,6 @@ def test_non_idempotent_pass(self):
                               'run transformation pass PassF_reduce_dag_property',
                               'dag property = 3'])
 
-    def test_fenced_property_set(self):
-        """Transformation passes are not allowed to modify the property set."""
-        self.passmanager.append(PassH_Bad_TP())
-        self.assertSchedulerRaises(self.circuit, self.passmanager,
-                                   ['run transformation pass PassH_Bad_TP'],
-                                   TranspilerError)
-
     def test_fenced_dag(self):
         """Analysis passes are not allowed to modified the DAG."""
         qr = QuantumRegister(2)
@@ -471,6 +464,15 @@ def test_remove_nonexistent_plugin(self):
         """Tries to remove a plugin that does not exist."""
         self.assertRaises(KeyError, FlowController.remove_flow_controller, "foo")
 
+    def test_bad_conditional(self):
+        """Flow controller are not allowed to modify the property set."""
+
+        def bad_condition(property_set):
+            property_set['property'] = 'forbidden write'
+
+        self.passmanager.append(PassA_TP_NR_NP(), condition=bad_condition)
+        self.assertRaises(TranspilerError, self.passmanager.run, self.circuit)
+
 
 class TestDumpPasses(SchedulerTestCase):
     """Testing the passes method."""