Repository: Qiskit/qiskit

Issue:
Warning statement upon compiling `cz` gate into `['h', 'cx', 'rz', 'sx', 'x']` gate set
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: `0.18.0`
- **Python version**: `3.9.6`
- **Operating system**: `Pop!_OS 21.04 x86_64`

### What is the current behavior?
Upon updating from version `0.17.4`, there is a substantially amount of additional warning printing upon compiling cz gates to the basis gate set: `['h', 'cx', 'rz', 'sx', 'x']`:

```
/home/username/anaconda3/envs/qiskit-new/lib/python3.9/site-packages/qiskit/transpiler/runningpassmanager.py:166: UserWarning: Resynthesized [<qiskit.dagcircuit.dagnode.DAGNode object at 0x7f70be79e040>] and got global phase: π/4
      ┌─────────┐┌────┐┌─────────┐
qr_0: ┤ Rz(π/2) ├┤ √X ├┤ Rz(π/2) ├
      └─────────┘└────┘└─────────┘, but the original was native and the new value is longer.  This indicates an efficiency bug in synthesis.  Please report it by opening an issue here: https://github.com/Qiskit/qiskit-terra/issues/new/choose
  new_dag = pass_.run(dag)
```

This doesn't appear to change the ability for the circuits to run correctly, it just fills up the terminal with a lot of non-needed statements, especially when running large algorithms requiring many cz gates.


### Steps to reproduce the problem
```
from qiskit import QuantumCircuit, QuantumRegister
from qiskit import transpile

qr = QuantumRegister(2)
qc_ = QuantumCircuit(qr)

qc_.cz(qr[0], qr[1])

qc = transpile(qc_, basis_gates=['h', 'cx', 'rz', 'sx', 'x'])
```


### What is the expected behavior?
Not printing a ton of warning statements upon compiling to this basis gate set


### Suggested solutions
Remove the printing? The basis gates we are using is a pretty common basis gate set, and compiling away from the cz gate is the intended behavior.




Discussion:
I would like to take this issue.
The compilation is correct. This warning was built in so that users can flag such cases for careful assessment. Here what happened was the compiler was given the task of synthesizing the Hadamard gate over the {'h', 'cx', 'sx', 'x', 'rz'} basis, and came up with RZ-SX-RZ. Under the "shorter-is-better" metric, this means the synthesizer should be improved. Under an "error-rate" metric, this doesn't matter.
When we decided to include this warning, I think we didn't consider the "mixing" of native gate sets, where the synthesis method used (here: PSX) doesn't surject onto all the gates included in the native set.

Probably this is best handled by using something other than gate count to measure circuit improvement, and [recent `retworkx` work](https://github.com/Qiskit/retworkx/pull/383) would be relevant.

I'll look at this tomorrow.

Resolving patch:
diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py
--- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py
+++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py
@@ -38,28 +38,31 @@ def __init__(self, basis=None):
                 and the Euler basis.
         """
         super().__init__()
+
         self._target_basis = basis
         self._decomposers = None
+
         if basis:
-            self._decomposers = []
+            self._decomposers = {}
             basis_set = set(basis)
             euler_basis_gates = one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES
             for euler_basis_name, gates in euler_basis_gates.items():
                 if set(gates).issubset(basis_set):
                     basis_copy = copy.copy(self._decomposers)
-                    for base in basis_copy:
+                    for base in basis_copy.keys():
                         # check if gates are a superset of another basis
-                        # and if so, remove that basis
-                        if set(euler_basis_gates[base.basis]).issubset(set(gates)):
-                            self._decomposers.remove(base)
+                        if set(base).issubset(set(gates)):
+                            # if so, remove that basis
+                            del self._decomposers[base]
                         # check if the gates are a subset of another basis
-                        elif set(gates).issubset(set(euler_basis_gates[base.basis])):
+                        elif set(gates).issubset(set(base)):
+                            # if so, don't bother
                             break
                     # if not a subset, add it to the list
                     else:
-                        self._decomposers.append(
-                            one_qubit_decompose.OneQubitEulerDecomposer(euler_basis_name)
-                        )
+                        self._decomposers[
+                            tuple(gates)
+                        ] = one_qubit_decompose.OneQubitEulerDecomposer(euler_basis_name)
 
     def run(self, dag):
         """Run the Optimize1qGatesDecomposition pass on `dag`.
@@ -70,7 +73,7 @@ def run(self, dag):
         Returns:
             DAGCircuit: the optimized DAG.
         """
-        if not self._decomposers:
+        if self._decomposers is None:
             logger.info("Skipping pass because no basis is set")
             return dag
         runs = dag.collect_1q_runs()
@@ -86,21 +89,20 @@ def run(self, dag):
                 if "u2" not in self._target_basis and "u1" not in self._target_basis:
                     continue
 
-            new_circs = []
             operator = run[0].op.to_matrix()
             for gate in run[1:]:
                 operator = gate.op.to_matrix().dot(operator)
-            for decomposer in self._decomposers:
-                new_circs.append(decomposer._decompose(operator))
-            if new_circs:
-                new_circ = min(new_circs, key=len)
+
+            new_circs = {k: v._decompose(operator) for k, v in self._decomposers.items()}
+
+            if len(new_circs) > 0:
+                new_basis, new_circ = min(new_circs.items(), key=lambda x: len(x[1]))
 
                 # do we even have calibrations?
                 has_cals_p = dag.calibrations is not None and len(dag.calibrations) > 0
-                # is this run all in the target set and also uncalibrated?
+                # is this run in the target set of this particular decomposer and also uncalibrated?
                 rewriteable_and_in_basis_p = all(
-                    g.name in self._target_basis
-                    and (not has_cals_p or not dag.has_calibration_for(g))
+                    g.name in new_basis and (not has_cals_p or not dag.has_calibration_for(g))
                     for g in run
                 )
                 # does this run have uncalibrated gates?


Test patch:
diff --git a/test/python/transpiler/test_optimize_1q_decomposition.py b/test/python/transpiler/test_optimize_1q_decomposition.py
--- a/test/python/transpiler/test_optimize_1q_decomposition.py
+++ b/test/python/transpiler/test_optimize_1q_decomposition.py
@@ -23,6 +23,7 @@
 from qiskit.circuit.library.standard_gates import UGate, SXGate, PhaseGate
 from qiskit.circuit.library.standard_gates import U3Gate, U2Gate, U1Gate
 from qiskit.circuit.random import random_circuit
+from qiskit.compiler import transpile
 from qiskit.transpiler import PassManager
 from qiskit.transpiler.passes import Optimize1qGatesDecomposition
 from qiskit.transpiler.passes import BasisTranslator
@@ -526,6 +527,23 @@ def test_u_rewrites_to_phase(self):
         msg = f"expected:\n{expected}\nresult:\n{result}"
         self.assertEqual(expected, result, msg=msg)
 
+    def test_no_warning_on_hadamard(self):
+        """
+        Test Hadamards don't trigger the inefficiency warning when they're part of the basis.
+
+        See #6848.
+        """
+        qc = QuantumCircuit(2)
+        qc.cz(0, 1)
+        basis = ["h", "cx", "rz", "sx", "x"]
+        result = transpile(qc, basis_gates=basis)
+        expected = QuantumCircuit(2)
+        expected.h(1)
+        expected.cx(0, 1)
+        expected.h(1)
+        msg = f"expected:\n{expected}\nresult:\n{result}"
+        self.assertEqual(expected, result, msg=msg)
+
 
 if __name__ == "__main__":
     unittest.main()