Repository: Qiskit/qiskit-terra
Pull request #5503 (2020-12-10 16:13:02)

Easier way to call a pass on a circuit
Currently, there are no easy ways to call a pass on a circuit. Take the following example:

```python
from qiskit import *

circuit = QuantumCircuit(1)
circuit.h(0)
circuit.draw('mpl')
```
![image](https://user-images.githubusercontent.com/766693/101795515-fc48b300-3ad5-11eb-8352-1fdf91a5897c.png)

To run a transformation pass (for example, `BasisTranslator`) you either create a single-pass `PassManager`:

```python
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import BasisTranslator
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary

passmanager = PassManager()
passmanager.append(BasisTranslator(SessionEquivalenceLibrary, ['rx', 'rz']))
result = passmanager.run(circuit)
result.draw('mpl')
```
![image](https://user-images.githubusercontent.com/766693/101796827-57c77080-3ad7-11eb-82e5-9b2f922390e8.png)

or you need to convert back-and-forward to and from `DAGCircuit`:
```python
from qiskit.converters import circuit_to_dag, dag_to_circuit

basistranslator_pass = BasisTranslator(SessionEquivalenceLibrary, ['rx', 'rz'])
result = dag_to_circuit(basistranslator_pass.run(circuit_to_dag(circuit)))
result.draw('mpl')
```
![image](https://user-images.githubusercontent.com/766693/101797211-c60c3300-3ad7-11eb-8d1e-736b7b63980b.png)

This PR introduced the class method `on` in the `BasePass`. This allows to run a transformation pass like this:
```python
result = BasisTranslator.on(circuit, conf={'equivalence_library': SessionEquivalenceLibrary, 'target_basis': ['rx', 'rz']})
result.draw('mpl')
```
![image](https://user-images.githubusercontent.com/766693/101797211-c60c3300-3ad7-11eb-8d1e-736b7b63980b.png)

Similarly, an analysis pass (like a layout selector, `SabreLayout` in this case) will write in-place the `property_set` parameter:
```python
from qiskit.transpiler import CouplingMap, PropertySet
from qiskit.transpiler.passes import SabreLayout
from qiskit.test.mock import FakeTenerife

cmap5 = FakeTenerife().configuration().coupling_map

circuit = QuantumCircuit(4)
circuit.cx(0, 2)
circuit.cx(1, 3)
circuit.cx(3, 0)

property_set = {}
SabreLayout.on(circuit, property_set=property_set, conf={'coupling_map': CouplingMap(cmap5), 'seed': 0})
print(property_set)
circuit.draw('mpl')
```
```
{'layout': Layout({
3: Qubit(QuantumRegister(4, 'q'), 0),
2: Qubit(QuantumRegister(4, 'q'), 1),
4: Qubit(QuantumRegister(4, 'q'), 2),
1: Qubit(QuantumRegister(4, 'q'), 3),
0: Qubit(QuantumRegister(1, 'ancilla'), 0)
})}
```
![image](https://user-images.githubusercontent.com/766693/101809172-39687180-3ae5-11eb-941b-6a88574ecebe.png)

The argument `property_set` is also used as "input":
```python
from qiskit.transpiler.passes import ApplyLayout

result = ApplyLayout.on(circuit, property_set=property_set)
result.draw('mpl')
```
![image](https://user-images.githubusercontent.com/766693/101809396-81879400-3ae5-11eb-9568-956be8ccdb31.png)


Cryoris: I like the idea of simplifying this process a lot. I'm not quite a fan of the `conf` dictionary as standard way of calling a pass directly on a circuit though. Why not instead call it in the same fashion as on the DAGs?
```python
basistranslator_pass = BasisTranslator(SessionEquivalenceLibrary, ['rx', 'rz'])
result = basistranslator_pass.on(circuit)  
result.draw('mpl')
```
Like that you can also pass passes around and use them again without carrying the dictionaries along. 
1ucian0: maybe this?
```
basistranslator_pass = BasisTranslator(SessionEquivalenceLibrary, ['rx', 'rz'], conditional=)
result = basistranslator_pass(circuit)  
```

Patch:
diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py
index fcb6314c3..9619743ff 100644
--- a/qiskit/transpiler/basepasses.py
+++ b/qiskit/transpiler/basepasses.py
@@ -95,6 +95,45 @@ class BasePass(metaclass=MetaPass):
         """
         return isinstance(self, AnalysisPass)
 
+    def __call__(self, circuit, property_set=None):
+        """Runs the pass on circuit.
+
+        Args:
+            circuit (QuantumCircuit): the dag on which the pass is run.
+            property_set (PropertySet or dict or None): input/output property set. An analysis pass
+                might change the property set in-place.
+
+        Returns:
+            QuantumCircuit: If on transformation pass, the resulting QuantumCircuit. If analysis
+                   pass, the input circuit.
+        """
+        from qiskit.converters import circuit_to_dag, dag_to_circuit
+        from qiskit.dagcircuit.dagcircuit import DAGCircuit
+
+        property_set_ = None
+        if isinstance(property_set, dict):  # this includes (dict, PropertySet)
+            property_set_ = PropertySet(property_set)
+
+        if isinstance(property_set_, PropertySet):
+            self.property_set = property_set_
+
+        result = self.run(circuit_to_dag(circuit))
+
+        result_circuit = circuit
+
+        if isinstance(property_set, dict):  # this includes (dict, PropertySet)
+            property_set.clear()
+            property_set.update(self.property_set)
+
+        if isinstance(result, DAGCircuit):
+            result_circuit = dag_to_circuit(result)
+
+        if result is None and self.property_set['layout']:
+            result_circuit = circuit.copy()
+            result_circuit._layout = self.property_set['layout']
+
+        return result_circuit
+
 
 class AnalysisPass(BasePass):  # pylint: disable=abstract-method
     """An analysis pass: change property set, not DAG."""
diff --git a/releasenotes/notes/calling_a_pass-4148f285ea717c74.yaml b/releasenotes/notes/calling_a_pass-4148f285ea717c74.yaml
new file mode 100644
index 000000000..aa9c906a5
--- /dev/null
+++ b/releasenotes/notes/calling_a_pass-4148f285ea717c74.yaml
@@ -0,0 +1,37 @@
+---
+features:
+  - |
+    A new quick way to call a pass is being introduced for convenience. Now it is possible to direcly call a
+    :class:`~qiskit.transpiler.BasePass` instance with a :class:`~qiskit.circuit.quantumcircuit.QuantumCircuit`
+    instance as a parameter to run the pass on the given circuit.
+
+    For example, in case a transformation pass such as :class:`~qiskit.transpiler.passes.BasisTranslator`:
+
+    .. code-block::
+
+      from qiskit import QuantumCircuit
+      from qiskit.transpiler.passes import BasisTranslator
+      from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary as sel
+
+      circuit = QuantumCircuit(1)
+      circuit.h(0)
+
+      pass_instance = BasisTranslator(sel, ['rx', 'rz', 'cx'])
+      result = pass_instance(circuit)
+
+    For the case of an analysis pass, a property set (as `dict` or as :class:`~qiskit.transpiler.propertyset.PropertySet`)
+    needs to be added as a parameter and it might be modified "in-place":
+
+    .. code-block::
+
+      from qiskit import QuantumCircuit
+      from qiskit.transpiler.passes import Depth
+
+      circuit = QuantumCircuit(1)
+      circuit.h(0)
+
+      property_set = {}
+      pass_instance = Depth()
+      pass_instance(circuit, property_set)
+      print(property_set)
+


Test patch:
diff --git a/test/python/transpiler/_dummy_passes.py b/test/python/transpiler/_dummy_passes.py
index f4389ef30..fce6168ec 100644
--- a/test/python/transpiler/_dummy_passes.py
+++ b/test/python/transpiler/_dummy_passes.py
@@ -224,3 +224,23 @@ class PassM_AP_NR_NP(DummyAP):
         super().run(dag)
         self.argument1 *= 2
         logging.getLogger(logger).info('self.argument1 = %s', self.argument1)
+
+
+class PassN_AP_NR_NP(DummyAP):
+    """ A dummy analysis pass that deletes and nones properties.
+    AP: Analysis Pass
+    NR: No Requires
+    NP: No Preserves
+    """
+
+    def __init__(self, to_delete, to_none):
+        super().__init__()
+        self.to_delete = to_delete
+        self.to_none = to_none
+
+    def run(self, dag):
+        super().run(dag)
+        del self.property_set[self.to_delete]
+        logging.getLogger(logger).info('property %s deleted', self.to_delete)
+        self.property_set[self.to_none] = None
+        logging.getLogger(logger).info('property %s noned', self.to_none)
diff --git a/test/python/transpiler/test_pass_call.py b/test/python/transpiler/test_pass_call.py
new file mode 100644
index 000000000..6facc95e1
--- /dev/null
+++ b/test/python/transpiler/test_pass_call.py
@@ -0,0 +1,86 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2017, 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Test calling passes (passmanager-less)"""
+
+from qiskit import QuantumRegister, QuantumCircuit
+from qiskit.test import QiskitTestCase
+from qiskit.transpiler import PropertySet
+from ._dummy_passes import PassD_TP_NR_NP, PassE_AP_NR_NP, PassN_AP_NR_NP
+
+
+class TestPassCall(QiskitTestCase):
+    """Test calling passes (passmanager-less)."""
+
+    def assertMessageLog(self, context, messages):
+        """Checks the log messages"""
+        self.assertEqual([record.message for record in context.records], messages)
+
+    def test_transformation_pass(self):
+        """Call a transformation pass without a scheduler"""
+        qr = QuantumRegister(1, 'qr')
+        circuit = QuantumCircuit(qr, name='MyCircuit')
+
+        pass_d = PassD_TP_NR_NP(argument1=[1, 2])
+        with self.assertLogs('LocalLogger', level='INFO') as cm:
+            result = pass_d(circuit)
+
+        self.assertMessageLog(cm, ['run transformation pass PassD_TP_NR_NP', 'argument [1, 2]'])
+        self.assertEqual(circuit, result)
+
+    def test_analysis_pass_dict(self):
+        """Call an analysis pass without a scheduler (property_set dict)"""
+        qr = QuantumRegister(1, 'qr')
+        circuit = QuantumCircuit(qr, name='MyCircuit')
+        property_set = {'another_property': 'another_value'}
+
+        pass_e = PassE_AP_NR_NP('value')
+        with self.assertLogs('LocalLogger', level='INFO') as cm:
+            result = pass_e(circuit, property_set)
+
+        self.assertMessageLog(cm, ['run analysis pass PassE_AP_NR_NP', 'set property as value'])
+        self.assertEqual(property_set, {'another_property': 'another_value', 'property': 'value'})
+        self.assertIsInstance(property_set, dict)
+        self.assertEqual(circuit, result)
+
+    def test_analysis_pass_property_set(self):
+        """Call an analysis pass without a scheduler (PropertySet dict)"""
+        qr = QuantumRegister(1, 'qr')
+        circuit = QuantumCircuit(qr, name='MyCircuit')
+        property_set = PropertySet({'another_property': 'another_value'})
+
+        pass_e = PassE_AP_NR_NP('value')
+        with self.assertLogs('LocalLogger', level='INFO') as cm:
+            result = pass_e(circuit, property_set)
+
+        self.assertMessageLog(cm, ['run analysis pass PassE_AP_NR_NP', 'set property as value'])
+        self.assertEqual(property_set,
+                         PropertySet({'another_property': 'another_value', 'property': 'value'}))
+        self.assertIsInstance(property_set, PropertySet)
+        self.assertEqual(circuit, result)
+
+    def test_analysis_pass_remove_property(self):
+        """Call an analysis pass that removes a property without a scheduler"""
+        qr = QuantumRegister(1, 'qr')
+        circuit = QuantumCircuit(qr, name='MyCircuit')
+        property_set = {'to remove': 'value to remove', 'to none': 'value to none'}
+
+        pass_e = PassN_AP_NR_NP('to remove', 'to none')
+        with self.assertLogs('LocalLogger', level='INFO') as cm:
+            result = pass_e(circuit, property_set)
+
+        self.assertMessageLog(cm, ['run analysis pass PassN_AP_NR_NP',
+                                   'property to remove deleted',
+                                   'property to none noned'])
+        self.assertEqual(property_set, PropertySet({'to none': None}))
+        self.assertIsInstance(property_set, dict)
+        self.assertEqual(circuit, result)