Repository: Qiskit/qiskit

Issue:
GateDirection pass goes outside allowed basis gates
### Environment

- **Qiskit Terra version**: 0.25
- **Python version**: 3.9
- **Operating system**: MacOS


### What is happening?

Trying to run a clifford simulation on a fake backend with ECR basis gates leads to transpilation errors with unsupported basis gates

### How can we reproduce the issue?

```python
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit.providers.fake_provider import FakeSherbrooke, FakeWashington

# Stabilizer simulator for FakeSherbrooke
clifford_basis_gates = ['ecr', 'sx', 'x', 'id', 'z', 's', 'sdg']
backend = AerSimulator.from_backend(
    FakeSherbrooke(),
    method="stabilizer",
    basis_gates=clifford_basis_gates, 
)

# Circuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)

# Transpile
transpile(qc, backend)
```
Raises
```
TranspilerError: "Unable to translate the operations in the circuit: ['ry', 'h', 'sdg', 'sx', 's', 'ecr', 'x'] to the backend's (or manually specified) target basis: ['break_loop', 'quantum_channel', 's', 'save_expval_var', 'for_loop', 'roerror', 'continue_loop', 'save_probabilities', 'z', 'qerror_loc', 'reset', 'save_clifford', 'snapshot', 'if_else', 'id', 'save_probabilities_dict', 'ecr', 'save_state', 'save_amplitudes_sq', 'sdg', 'save_stabilizer', 'delay', 'while_loop', 'save_expval', 'set_stabilizer', 'sx', 'measure', 'barrier', 'x']. This likely means the target basis is not universal or there are additional equivalence rules needed in the EquivalenceLibrary being used. For more details on this error see: https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.BasisTranslator.html#translation_errors"
```

### What should happen?

Running with a manually specified basis gates works (though the output is not at all optimized in terms of number of 1Q gates):

```python
transpile(qc, basis_gates=["ecr", "x", "sx", "id", "z", "s", "sdg"])
```
```
global phase: 3π/2
     ┌───┐┌────┐┌───┐ ┌─────┐     ┌──────┐┌───┐
q_0: ┤ S ├┤ √X ├┤ S ├─┤ Sdg ├─────┤0     ├┤ X ├
     ├───┤├───┬┘├───┴┐└┬───┬┘┌───┐│  Ecr │└───┘
q_1: ┤ S ├┤ S ├─┤ √X ├─┤ S ├─┤ S ├┤1     ├─────
     └───┘└───┘ └────┘ └───┘ └───┘└──────┘     
```


### Any suggestions?

Running transpilation with a callback looks like the issues is due to the `GateDirection` pass which inserts non-Clifford gates outside the allowed basis gates into the circuit and causes the rest of the transpilation to fail. 

```python
def callback(**kwargs):
    print(f"{type(kwargs['pass_']).__name__} output gates: {kwargs['dag'].count_ops()}")

transpile(qc, backend, callback=callback).draw()
```

```
ContainsInstruction, gates: {'h': 1, 'cx': 1}
UnitarySynthesis, gates: {'h': 1, 'cx': 1}
HighLevelSynthesis, gates: {'h': 1, 'cx': 1}
Unroll3qOrMore, gates: {'h': 1, 'cx': 1}
SetLayout, gates: {'h': 1, 'cx': 1}
TrivialLayout, gates: {'h': 1, 'cx': 1}
CheckMap, gates: {'h': 1, 'cx': 1}
FullAncillaAllocation, gates: {'h': 1, 'cx': 1}
EnlargeWithAncilla, gates: {'h': 1, 'cx': 1}
ApplyLayout, gates: {'h': 1, 'cx': 1}
CheckMap, gates: {'h': 1, 'cx': 1}
UnitarySynthesis, gates: {'h': 1, 'cx': 1}
HighLevelSynthesis, gates: {'h': 1, 'cx': 1}
UnrollCustomDefinitions, gates: {'h': 1, 'cx': 1}
BasisTranslator, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'ecr': 1, 'x': 1}
CheckGateDirection, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'ecr': 1, 'x': 1}
GateDirection, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}   # NON BASIS RY INSERTED HERE
RemoveResetInZeroState, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
Depth, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
FixedPoint, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
Size, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
FixedPoint, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
Optimize1qGatesDecomposition, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
CXCancellation, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
GatesInBasis, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
UnitarySynthesis, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
HighLevelSynthesis, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
UnrollCustomDefinitions, gates: {'s': 6, 'sx': 2, 'sdg': 1, 'x': 1, 'ry': 2, 'ecr': 1, 'h': 2}
# raises exception here
```


This pass should only insert gates supported by the backend, and be updated to work with Clifford basis backends to enable simulation testing


Discussion:
In general, the transpiler was never designed to support discrete-basis synthesis at the same level of support as for continuous basis gates.  While it's `GateDirection` that happens to fail here, and we can probably tweak the exact replacement it uses to keep things stabiliser-supported for longer into the transpilation pipelines (and potentially even working for you), _in principle_, this is trying to use `transpile` in an out-of-spec manner.  This is also why `transpile` is not giving well-optimised results.

If it's sufficient for you, we can potentially change the decomposition of the ECR reversal in `GateDirection`, though in general it would be a serious additional commitment if the transpiler needed to support optimal discrete-basis synthesis.
I find it a major frustration when trying to do any testing and debugging of primitives at utility scale without real support for Clifford basis in the transpiler, and I think this only becomes more important as we increase the number of qubits and Clifford simulation becomes the only way to do simulator based testing. At the moment I'm not overly concerned about the sub-optimality of the result, just that it can run for testing and debugging. 

In the future it would be nice if it could synthesis optimally as well. Now this Clifford support this doesn't need to necessarily be discrete basis gates, it could use backend basis gates (which are Clifford + RZ), and then validate the RZ angles are k*pi/2, and then either have an extra pass to replace these with I, Z, S, Sdg depending on the angle, or update Aer to support clifford-angled RZs for its stabilizer method. But achieving will require making sure synthesis passes respect this by mapping Clifford to valid Clifford angled RZ decompositions in synthesis and optimization routines, which is currently not the case.
If you have a particular need for a large-scale feature from Qiskit (which I would argue that optimal discrete-basis synthesis is), please can you be sure to mention it during quarterly planning so it can be worked into our prioritisation, and we can work out who will be responsible?

Resolving patch:
diff --git a/qiskit/transpiler/passes/utils/gate_direction.py b/qiskit/transpiler/passes/utils/gate_direction.py
--- a/qiskit/transpiler/passes/utils/gate_direction.py
+++ b/qiskit/transpiler/passes/utils/gate_direction.py
@@ -21,7 +21,9 @@
 from qiskit.circuit import QuantumRegister, ControlFlowOp
 from qiskit.dagcircuit import DAGCircuit, DAGOpNode
 from qiskit.circuit.library.standard_gates import (
-    RYGate,
+    SGate,
+    SdgGate,
+    SXGate,
     HGate,
     CXGate,
     CZGate,
@@ -49,11 +51,14 @@ class GateDirection(TransformationPass):
         q_1: ┤ X ├      q_1: ┤ H ├──■──┤ H ├
              └───┘           └───┘     └───┘
 
-             ┌──────┐          ┌───────────┐┌──────┐┌───┐
-        q_0: ┤0     ├     q_0: ┤ RY(-pi/2) ├┤1     ├┤ H ├
-             │  ECR │  =       └┬──────────┤│  ECR │├───┤
-        q_1: ┤1     ├     q_1: ─┤ RY(pi/2) ├┤0     ├┤ H ├
-             └──────┘           └──────────┘└──────┘└───┘
+
+                          global phase: 3π/2
+             ┌──────┐           ┌───┐ ┌────┐┌─────┐┌──────┐┌───┐
+        q_0: ┤0     ├     q_0: ─┤ S ├─┤ √X ├┤ Sdg ├┤1     ├┤ H ├
+             │  ECR │  =       ┌┴───┴┐├────┤└┬───┬┘│  Ecr │├───┤
+        q_1: ┤1     ├     q_1: ┤ Sdg ├┤ √X ├─┤ S ├─┤0     ├┤ H ├
+             └──────┘          └─────┘└────┘ └───┘ └──────┘└───┘
+
 
              ┌──────┐          ┌───┐┌──────┐┌───┐
         q_0: ┤0     ├     q_0: ┤ H ├┤1     ├┤ H ├
@@ -90,11 +95,19 @@ def __init__(self, coupling_map, target=None):
         self._cx_dag.apply_operation_back(HGate(), [qr[0]], [])
         self._cx_dag.apply_operation_back(HGate(), [qr[1]], [])
 
+        # This is done in terms of less-efficient S/SX/Sdg gates instead of the more natural
+        # `RY(pi /2)` so we have a chance for basis translation to keep things in a discrete basis
+        # during resynthesis, if that's what's being asked for.
         self._ecr_dag = DAGCircuit()
         qr = QuantumRegister(2)
+        self._ecr_dag.global_phase = -pi / 2
         self._ecr_dag.add_qreg(qr)
-        self._ecr_dag.apply_operation_back(RYGate(-pi / 2), [qr[0]], [])
-        self._ecr_dag.apply_operation_back(RYGate(pi / 2), [qr[1]], [])
+        self._ecr_dag.apply_operation_back(SGate(), [qr[0]], [])
+        self._ecr_dag.apply_operation_back(SXGate(), [qr[0]], [])
+        self._ecr_dag.apply_operation_back(SdgGate(), [qr[0]], [])
+        self._ecr_dag.apply_operation_back(SdgGate(), [qr[1]], [])
+        self._ecr_dag.apply_operation_back(SXGate(), [qr[1]], [])
+        self._ecr_dag.apply_operation_back(SGate(), [qr[1]], [])
         self._ecr_dag.apply_operation_back(ECRGate(), [qr[1], qr[0]], [])
         self._ecr_dag.apply_operation_back(HGate(), [qr[0]], [])
         self._ecr_dag.apply_operation_back(HGate(), [qr[1]], [])


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
@@ -51,16 +51,20 @@
 from qiskit.circuit.library import (
     CXGate,
     CZGate,
+    ECRGate,
     HGate,
     RXGate,
     RYGate,
     RZGate,
+    SGate,
     SXGate,
+    SXdgGate,
+    SdgGate,
     U1Gate,
     U2Gate,
     UGate,
     XGate,
-    SGate,
+    ZGate,
 )
 from qiskit.circuit.measure import Measure
 from qiskit.compiler import transpile
@@ -417,6 +421,28 @@ def test_transpile_bell(self):
         circuits = transpile(qc, backend)
         self.assertIsInstance(circuits, QuantumCircuit)
 
+    def test_transpile_bell_discrete_basis(self):
+        """Test that it's possible to transpile a very simple circuit to a discrete stabiliser-like
+        basis.  In general, we do not make any guarantees about the possibility or quality of
+        transpilation in these situations, but this is at least useful as a check that stuff that
+        _could_ be possible remains so."""
+
+        target = Target(num_qubits=2)
+        for one_q in [XGate(), SXGate(), SXdgGate(), SGate(), SdgGate(), ZGate()]:
+            target.add_instruction(one_q, {(0,): None, (1,): None})
+        # This is only in one direction, and not the direction we're going to attempt to lay it out
+        # onto, so we can test the basis translation.
+        target.add_instruction(ECRGate(), {(1, 0): None})
+
+        qc = QuantumCircuit(2)
+        qc.h(0)
+        qc.cx(0, 1)
+
+        # Try with the initial layout in both directions to ensure we're dealing with the basis
+        # having only a single direction.
+        self.assertIsInstance(transpile(qc, target=target, initial_layout=[0, 1]), QuantumCircuit)
+        self.assertIsInstance(transpile(qc, target=target, initial_layout=[1, 0]), QuantumCircuit)
+
     def test_transpile_one(self):
         """Test transpile a single circuit.
 
diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py
--- a/test/python/transpiler/test_gate_direction.py
+++ b/test/python/transpiler/test_gate_direction.py
@@ -172,12 +172,16 @@ def test_ecr_flip(self):
         #       ├─────────┴┐│  Ecr │├───┤
         # qr_1: ┤ Ry(-π/2) ├┤1     ├┤ H ├
         #       └──────────┘└──────┘└───┘
-        expected = QuantumCircuit(qr)
-        expected.ry(pi / 2, qr[0])
-        expected.ry(-pi / 2, qr[1])
-        expected.ecr(qr[0], qr[1])
-        expected.h(qr[0])
-        expected.h(qr[1])
+        expected = QuantumCircuit(qr, global_phase=-pi / 2)
+        expected.s(1)
+        expected.sx(1)
+        expected.sdg(1)
+        expected.sdg(0)
+        expected.sx(0)
+        expected.s(0)
+        expected.ecr(0, 1)
+        expected.h(0)
+        expected.h(1)
 
         pass_ = GateDirection(coupling)
         after = pass_.run(dag)
@@ -405,8 +409,13 @@ def test_coupling_map_control_flow(self):
             expected.h([0, 1])
             expected.cx(0, 1)
             with expected.if_test((circuit.clbits[0], True)) as else_:
-                expected.ry(pi / 2, 2)
-                expected.ry(-pi / 2, 3)
+                expected.global_phase -= pi / 2
+                expected.sdg(2)
+                expected.sx(2)
+                expected.s(2)
+                expected.s(3)
+                expected.sx(3)
+                expected.sdg(3)
                 expected.ecr(2, 3)
                 expected.h([2, 3])
             with else_:
@@ -442,8 +451,13 @@ def test_target_control_flow(self):
             expected.h([0, 1])
             expected.cx(0, 1)
             with expected.if_test((circuit.clbits[0], True)) as else_:
-                expected.ry(pi / 2, 2)
-                expected.ry(-pi / 2, 3)
+                expected.global_phase -= pi / 2
+                expected.sdg(2)
+                expected.sx(2)
+                expected.s(2)
+                expected.s(3)
+                expected.sx(3)
+                expected.sdg(3)
                 expected.ecr(2, 3)
                 expected.h([2, 3])
             with else_: