Repository: Qiskit/qiskit

Issue:
Qiskit transpile different basis gate set
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Informations
- **Qiskit version**:  
{'qiskit-terra': '0.16.0', 
'qiskit-aer': '0.7.0', 
'qiskit-ignis': '0.5.0',
 'qiskit-ibmq-provider': '0.11.0',
 'qiskit-aqua': '0.8.0', 
'qiskit': '0.23.0'}
- **Python version**: Python 3.8.1
- **Operating system**: macOs

### What is the current behavior?

Currently, the output from the qiskit transpile function for my random gate set (random gates) with the basis gate set 'rx', 'ry' and 'cx' is this circuit: 

<img width="1183" alt="Bildschirmfoto 2020-10-25 um 17 56 11" src="https://user-images.githubusercontent.com/60929371/97113540-60ace000-16eb-11eb-96c9-11c462b40142.png">

I'm not sure, why the transpile function gives sometimes three single qubit rotations (Is it not possible to decompose every  single qubit rotation into one  rx( -pi <= theta<= pi)  and one ry( -pi <= theta <= pi) rotation ?).

The second minor issue is that sometimes a rotation with an angle of 0 degree is still in the circuit and not removed (see image).  

### Steps to reproduce the problem

The following code produces the circuit of the provided image. 
`
qc = QuantumCircuit(3)

qc.h(0)
qc.h(1)
qc.x(0)
qc.h(0)
qc.ry(np.pi/4, 0)
qc.rx(np.pi/10, 0)
qc.y(1)
qc.rx(np.pi/10, 0)
qc.rx(np.pi/10, 0)
qc.h(2)
qc.cx(2, 1)
qc.x(0)
qc.cx(0, 1)
qc.y(2)
qc.rx(np.pi/10, 0)
qc.rx(np.pi/10, 0)
qc.y(0)
qc.x(2)
qc.rx(np.pi/10, 0)
qc.ry(np.pi/4, 2)
qc.cx(0, 2)
qc.h(2)
qc.cx(2, 1)
qc.x(0)
qc.cx(0, 1)
qc.rx(np.pi/10, 0)
qc.rx(np.pi/10, 0)

qc.x(2)
qc.ry(np.pi/4, 2)
qc.ry(np.pi/4, 2)
qc.ry(np.pi/4, 2)
qc.cx(0, 2)
qc.h(2)
qc.cx(2, 1)
qc.measure_all()

coupling_string = [[0, 1], [1, 0], [2, 0], [0,2], [1,2], [2, 1]]
CM = CouplingMap(coupling_string)
basis_gates = ['id', 'ry', 'rx', 'cx']
transpiled_qc = transpile(qc, coupling_map=CM, basis_gates=basis_gates, optimization_level=3, seed_transpiler=1)


### What is the expected behavior?

Normally I would expect that the transpile function with the passed basis gate set would give me in my circuit only two consecutive applied single qubit roations and not three and that rotations with an angle of 0 would be removed.  

Am I wrong with my expectations ?

### Suggested solutions
...



Discussion:
A general single-qubit gate requires 3 Pauli rotations, right? E.g. the ZYZ decomposition (see for instance [this paper](https://arxiv.org/pdf/quant-ph/0406176.pdf)). But we're working on improving the compiler for these arbitrary gate sets, to e.g. remove things like `RX(0)`.

Or is there a special decomposition you had in mind?
So I dug into the `Rx(0)` in the output circuit. The source of this is coming from the level 3 optimization pass `CollectBlocks` and then `UnitarySynthesis`. The two qubit decomposer is adding the rx(0) to the circuit when decomposing the unitary matrix, which happens before we run `Optimize1qGatesDecomposition`. This then gets skipped over inside `Optimize1qGatesDecomposition` because it skips single qubit gates already in the target basis since the eutler decomposition will often expand it to multiple gates: https://github.com/Qiskit/qiskit-terra/blob/master/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py#L73-L75

To optimize away the rx(0) case we'll probably have to modify that if statement to manually check for 0 degree rotations and either manually remove them or call the decomposer if the angles are 0 and let it's simplification do the removal.

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
@@ -15,6 +15,8 @@
 from itertools import groupby
 import logging
 
+import numpy as np
+
 from qiskit.circuit import QuantumCircuit, QuantumRegister
 from qiskit.quantum_info import Operator
 from qiskit.transpiler.basepasses import TransformationPass
@@ -71,8 +73,14 @@ def run(self, dag):
         runs = dag.collect_runs(self.euler_basis_names[self.basis])
         runs = _split_runs_on_parameters(runs)
         for run in runs:
-            # Don't try to optimize a single 1q gate
             if len(run) <= 1:
+                params = run[0].op.params
+                # Remove single identity gates
+                if run[0].op.name in self.euler_basis_names[self.basis] and len(
+                        params) > 0 and np.array_equal(run[0].op.to_matrix(),
+                                                       np.eye(2)):
+                    dag.remove_op_node(run[0])
+                # Don't try to optimize a single 1q gate
                 continue
             q = QuantumRegister(1, "q")
             qc = QuantumCircuit(1)


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
@@ -18,6 +18,7 @@
 import numpy as np
 
 from qiskit.circuit import QuantumRegister, QuantumCircuit, ClassicalRegister
+from qiskit.circuit.library.standard_gates import U3Gate
 from qiskit.transpiler import PassManager
 from qiskit.transpiler.passes import Optimize1qGatesDecomposition
 from qiskit.transpiler.passes import BasisTranslator
@@ -235,6 +236,86 @@ def test_parameterized_expressions_in_circuits(self, basis):
             Operator(qc.bind_parameters({theta: 3.14, phi: 10})).equiv(
                 Operator(result.bind_parameters({theta: 3.14, phi: 10}))))
 
+    def test_identity_xyx(self):
+        """Test lone identity gates in rx ry basis are removed."""
+        circuit = QuantumCircuit(2)
+        circuit.rx(0, 1)
+        circuit.ry(0, 0)
+        basis = ['rxx', 'rx', 'ry']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_zxz(self):
+        """Test lone identity gates in rx rz basis are removed."""
+        circuit = QuantumCircuit(2)
+        circuit.rx(0, 1)
+        circuit.rz(0, 0)
+        basis = ['cz', 'rx', 'rz']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_psx(self):
+        """Test lone identity gates in p sx basis are removed."""
+        circuit = QuantumCircuit(1)
+        circuit.p(0, 0)
+        basis = ['cx', 'p', 'sx']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_u(self):
+        """Test lone identity gates in u basis are removed."""
+        circuit = QuantumCircuit(1)
+        circuit.u(0, 0, 0, 0)
+        basis = ['cx', 'u']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_u3(self):
+        """Test lone identity gates in u3 basis are removed."""
+        circuit = QuantumCircuit(1)
+        circuit.append(U3Gate(0, 0, 0), [0])
+        basis = ['cx', 'u3']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_r(self):
+        """Test lone identity gates in r basis are removed."""
+        circuit = QuantumCircuit(1)
+        circuit.r(0, 0, 0)
+        basis = ['r']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
+    def test_identity_u1x(self):
+        """Test lone identity gates in u1 rx basis are removed."""
+        circuit = QuantumCircuit(2)
+        circuit.u1(0, 0)
+        circuit.rx(0, 1)
+        basis = ['cx', 'u1', 'rx']
+        passmanager = PassManager()
+        passmanager.append(BasisTranslator(sel, basis))
+        passmanager.append(Optimize1qGatesDecomposition(basis))
+        result = passmanager.run(circuit)
+        self.assertEqual([], result.data)
+
 
 if __name__ == '__main__':
     unittest.main()