Repository: Qiskit/qiskit

Issue:
Quantum cost of template substitution
### What is the expected enhancement?
Template substitution allows users to identify sub-circuits in a quantum circuit and replace them with equivalent quantum circuits with a lower implementation cost. For example `CNOT(0,1) CNOT(0,1)` is equivalent to the identity `I`. Since the implementation cost of `I` is lower than two CNOT gates this replacement makes sense. 

Currently, the implementation cost of a quantum circuit is evaluated in `TemplateSubstitution._quantum_cost(self, left, right)` in `template_substitution.py`. `_quantum_cost` evaluates the cost using a hard coded `cost_dict` instance. The user should be able to add/remove/modify elements from this dictionary as it is architecture dependent and the user may also whish to add his own gates to it.

`cost_dict` should therefore be an attribute of the `TemplateSubstitution` class that users can access when creating template matching transpiler passes.


Discussion:
Can I give this a try, @eggerdj ?
@born-2learn sure
To add/remove/modify elements of the `cost_dict` which is currently present in `template_substitution.py.`'s `_quantum_cost`, `cost_dict` should be made a parameter of the `TemplateSubstitution` class.

Here are the changes that I am proposing:
1. Adding `cost_dict` to the `__init__` constructor method of the `TemplateSubstitution` class, so that it can be altered with a simple object call, for example, `obj.cost_dict['x'] = 3`, which changes the cost of the `X` gate to `3`.
2. Updating the dictionary calls using `self` for updating costs in `_quantum_cost()`.

Please let me know if any more changes are needed, @eggerdj! I'll send in a PR asap for the same.

Resolving patch:
diff --git a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py
--- a/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py
+++ b/qiskit/transpiler/passes/optimization/template_matching/template_substitution.py
@@ -53,7 +53,7 @@ class TemplateSubstitution:
     Class to run the substitution algorithm from the list of maximal matches.
     """
 
-    def __init__(self, max_matches, circuit_dag_dep, template_dag_dep):
+    def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None):
         """
         Initialize TemplateSubstitution with necessary arguments.
         Args:
@@ -61,6 +61,8 @@ def __init__(self, max_matches, circuit_dag_dep, template_dag_dep):
              the template matching algorithm.
             circuit_dag_dep (DAGDependency): circuit in the dag dependency form.
             template_dag_dep (DAGDependency): template in the dag dependency form.
+            user_cost_dict (Optional[dict]): user provided cost dictionary that will override
+                the default cost dictionary.
         """
 
         self.match_stack = max_matches
@@ -72,6 +74,16 @@ def __init__(self, max_matches, circuit_dag_dep, template_dag_dep):
         self.dag_dep_optimized = DAGDependency()
         self.dag_optimized = DAGCircuit()
 
+        if user_cost_dict is not None:
+            self.cost_dict = dict(user_cost_dict)
+        else:
+            self.cost_dict = {'id': 0, 'x': 1, 'y': 1, 'z': 1, 'h': 1, 't': 1, 'tdg': 1, 's': 1,
+                              'sdg': 1, 'u1': 1, 'u2': 2, 'u3': 2, 'rx': 1, 'ry': 1, 'rz': 1,
+                              'r': 2, 'cx': 2, 'cy': 4, 'cz': 4, 'ch': 8, 'swap': 6, 'iswap': 8,
+                              'rxx': 9, 'ryy': 9, 'rzz': 5, 'rzx': 7, 'ms': 9, 'cu3': 10, 'crx': 10,
+                              'cry': 10, 'crz': 10, 'ccx': 21, 'rccx': 12, 'c3x': 96, 'rc3x': 24,
+                              'c4x': 312, 'p': 1}
+
     def _pred_block(self, circuit_sublist, index):
         """
         It returns the predecessors of a given part of the circuit.
@@ -103,18 +115,13 @@ def _quantum_cost(self, left, right):
         Returns:
             bool: True if the quantum cost is reduced
         """
-        cost_dict = {'id': 0, 'x': 1, 'y': 1, 'z': 1, 'h': 1, 't': 1, 'tdg': 1, 's': 1, 'sdg': 1,
-                     'u1': 1, 'u2': 2, 'u3': 2, 'rx': 1, 'ry': 1, 'rz': 1, 'r': 2, 'cx': 2,
-                     'cy': 4, 'cz': 4, 'ch': 8, 'swap': 6, 'iswap': 8, 'rxx': 9, 'ryy': 9,
-                     'rzz': 5, 'rzx': 7, 'ms': 9, 'cu3': 10, 'crx': 10, 'cry': 10, 'crz': 10,
-                     'ccx': 21, 'rccx': 12, 'c3x': 96, 'rc3x': 24, 'c4x': 312, 'p': 1}
         cost_left = 0
         for i in left:
-            cost_left += cost_dict[self.template_dag_dep.get_node(i).name]
+            cost_left += self.cost_dict[self.template_dag_dep.get_node(i).name]
 
         cost_right = 0
         for j in right:
-            cost_right += cost_dict[self.template_dag_dep.get_node(j).name]
+            cost_right += self.cost_dict[self.template_dag_dep.get_node(j).name]
 
         return cost_left > cost_right
 
diff --git a/qiskit/transpiler/passes/optimization/template_optimization.py b/qiskit/transpiler/passes/optimization/template_optimization.py
--- a/qiskit/transpiler/passes/optimization/template_optimization.py
+++ b/qiskit/transpiler/passes/optimization/template_optimization.py
@@ -44,7 +44,8 @@ class TemplateOptimization(TransformationPass):
 
     def __init__(self, template_list=None,
                  heuristics_qubits_param=None,
-                 heuristics_backward_param=None):
+                 heuristics_backward_param=None,
+                 user_cost_dict=None):
         """
         Args:
             template_list (list[QuantumCircuit()]): list of the different template circuit to apply.
@@ -62,6 +63,9 @@ def __init__(self, template_list=None,
                 predecessors that will be explored in the dag dependency of the circuit, each
                 qubits of the nodes are added to the set of authorized qubits. We advice to use
                 length=1. Check reference for more details.
+            user_cost_dict (Dict[str, int]): quantum cost dictonary passed to TemplateSubstitution
+                to configure its behavior. This will override any default values if None
+                is not given. The key is the name of the gate and the value its quantum cost.
         """
         super().__init__()
         # If no template is given; the template are set as x-x, cx-cx, ccx-ccx.
@@ -73,6 +77,8 @@ def __init__(self, template_list=None,
         self.heuristics_backward_param = heuristics_backward_param \
             if heuristics_backward_param is not None else []
 
+        self.user_cost_dict = user_cost_dict
+
     def run(self, dag):
         """
         Args:
@@ -129,7 +135,8 @@ def run(self, dag):
 
                 substitution = TemplateSubstitution(max_matches,
                                                     template_m.circuit_dag_dep,
-                                                    template_m.template_dag_dep)
+                                                    template_m.template_dag_dep,
+                                                    self.user_cost_dict)
                 substitution.run_dag_opt()
 
                 circuit_dag_dep = substitution.dag_dep_optimized


Test patch: