Repository: Qiskit/qiskit

Issue:
Basis gate decomposition success depends on knowing internal implimentation details
### What should we add?

Consider the following simple circuit:

```python
qc = QuantumCircuit(1)
qc.y(0)
```
I should be able to decompose this into the following:

```
   ┌────────┐┌───┐
q: ┤ Rz(-π) ├┤ X ├
   └────────┘└───┘
```

However, using the `rz` and `x` basis in the transpiler fails:

```python

transpile(qc, basis_gates=['rz', 'x'])
```
```
Unable to translate the operations in the circuit: ['y'] to the backend's (or manually specified) target basis: ['reset', 'delay', 'x', 'snapshot', 'rz', 'barrier', 'measure']
```

In order to work around this, I one needs to know about the internals of how the gate equivalence works.  Namely that adding an `sx` gate that is used as an intermediary will yield the correct decomposition, and the above circuit:

```python
transpile(qc, basis_gates=['rz', 'sx', 'x'])
```

It would be nice if the success of the decomposition did not rely on understanding internal implementation issues.



Discussion:
In principle, the basis translator can do what you're asking, no internal details necessary.  The problem is that the standard equivalence library is mostly populated by rules that translate into the `[sx, rz, cx]`, and then conversions between `sx` and some other bits, and the different rotation axes - they notably don't tend to use the "full" `x` or `z`.

Algorithmically all the pieces are in place, we just need to play a bit of whack-a-mole adding some extra equivalences to the standard equivalence library as we find interesting cases.  For your specific example, I think adding the three Pauli relations `X -> -iYZ` etc (or whatever the Levi-Civita ordering is that makes it work) would fix it.

Looking further, it gets pretty interesting/complex when we start talking about the optimisation/resynthesis of 1q/2q blocks.  We have a finite number of special-case targets for them, so it's quite easy to come up with awkward limited gate sets for backends that we wouldn't be able to optimise for.
Just to illustrate the point a little - the basis translator actually uses the rule `Y -> SSHSSH`, `H -> S Sx S`, and `S -> U1(pi/2) -> RZ(pi/2)` in your working example - we never actually put any `S`, `H` or `U1` gates on the intermediate circuit, but the decomposition logically goes via them.

Resolving patch:
diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py
--- a/qiskit/circuit/library/standard_gates/equivalence_library.py
+++ b/qiskit/circuit/library/standard_gates/equivalence_library.py
@@ -1188,6 +1188,15 @@
     def_x.append(inst, qargs, cargs)
 _sel.add_equivalence(XGate(), def_x)
 
+# XGate
+#                 global phase: π/2
+#    ┌───┐        ┌───┐┌───┐
+# q: ┤ X ├  ≡  q: ┤ Y ├┤ Z ├
+#    └───┘        └───┘└───┘
+def_x = QuantumCircuit(1, global_phase=pi / 2)
+def_x.y(0)
+def_x.z(0)
+_sel.add_equivalence(XGate(), def_x)
 
 # CXGate
 
@@ -1407,6 +1416,16 @@
     def_y.append(inst, qargs, cargs)
 _sel.add_equivalence(YGate(), def_y)
 
+# YGate
+#                 global phase: π/2
+#    ┌───┐        ┌───┐┌───┐
+# q: ┤ Y ├  ≡  q: ┤ Z ├┤ X ├
+#    └───┘        └───┘└───┘
+def_y = QuantumCircuit(1, global_phase=pi / 2)
+def_y.z(0)
+def_y.x(0)
+_sel.add_equivalence(YGate(), def_y)
+
 # CYGate
 #
 # q_0: ──■──     q_0: ─────────■───────
@@ -1433,7 +1452,6 @@
 def_z.append(U1Gate(pi), [q[0]], [])
 _sel.add_equivalence(ZGate(), def_z)
 
-# """
 # ZGate
 #
 #    ┌───┐        ┌───┐┌───┐
@@ -1448,6 +1466,16 @@
     def_z.append(inst, qargs, cargs)
 _sel.add_equivalence(ZGate(), def_z)
 
+# ZGate
+#                 global phase: π/2
+#    ┌───┐        ┌───┐┌───┐
+# q: ┤ Z ├  ≡  q: ┤ X ├┤ Y ├
+#    └───┘        └───┘└───┘
+def_z = QuantumCircuit(1, global_phase=pi / 2)
+def_z.x(0)
+def_z.y(0)
+_sel.add_equivalence(ZGate(), def_z)
+
 # CZGate
 #
 # q_0: ─■─     q_0: ───────■───────


Test patch:
diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py
--- a/test/python/compiler/test_transpiler.py
+++ b/test/python/compiler/test_transpiler.py
@@ -1643,6 +1643,20 @@ def test_initial_layout_with_overlapping_qubits(self, opt_level):
             transpiled.layout.initial_layout, Layout({0: qc.qubits[1], 1: qc.qubits[0]})
         )
 
+    @combine(opt_level=[0, 1, 2, 3], basis=[["rz", "x"], ["rx", "z"], ["rz", "y"], ["ry", "x"]])
+    def test_paulis_to_constrained_1q_basis(self, opt_level, basis):
+        """Test that Pauli-gate circuits can be transpiled to constrained 1q bases that do not
+        contain any root-Pauli gates."""
+        qc = QuantumCircuit(1)
+        qc.x(0)
+        qc.barrier()
+        qc.y(0)
+        qc.barrier()
+        qc.z(0)
+        transpiled = transpile(qc, basis_gates=basis, optimization_level=opt_level)
+        self.assertGreaterEqual(set(basis) | {"barrier"}, transpiled.count_ops().keys())
+        self.assertEqual(Operator(qc), Operator(transpiled))
+
 
 @ddt
 class TestPostTranspileIntegration(QiskitTestCase):