Repository: Qiskit/qiskit

Issue:
Add ECR/iSwap to list of gates handled by `Clifford.from_circuit`
### What should we add?

`Clifford.from_circuit` has a list of 1- and 2-qubit Cliffords that it knows how to handle via updating the tableau directly. https://github.com/Qiskit/qiskit-terra/blob/1fb00e628a73f960980648cf9c4db0dfecc936ca/qiskit/quantum_info/operators/symplectic/clifford_circuits.py#L331

This list currently doesn't include some Clifford gates from the standard library like `ECR` and `iSwap`. For `iSwap`, this is not a problem because it's `.definition` is all Clifford, but `ECR` is defined in terms of `RZX` and so can't be converted directly. This partially covered by #9475 but requires falling back to the matrix form to reconstruct the tableau. 


Discussion:
There are two approaches here:

The first is to add `ecr` to the clifford circuits [`_BASIS_2q`](https://github.com/Qiskit/qiskit-terra/blob/e016f9ca612482fe2b15af4d54828cf3c1899f6f/qiskit/quantum_info/operators/symplectic/clifford_circuits.py#L346) using the following Clifford table:

```
[[ True  True  True False  True]
 [False  True False False False]
 [False False  True False  True]
 [False  True  True  True False]]
```

The second is to add an equivalent definition of the `ECRGate()` to the [equivalence library](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/library/standard_gates/equivalence_library.py) (assuming that the Clifford class will handle equivalent definitions): 

```
qc = QuantumCircuit(2)
qc.h(1)
qc.s(0)
qc.sdg(1)
qc.h(1)
qc.cx(0, 1)
qc.x(0)
qc.x(1)
print (qc)
print (Clifford(qc))
assert (Operator(qc).equiv(Operator(ECRGate())))
```
Note that in the circuit library there are some Clifford gates that do not appear in the [clifford_circuits.py](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/quantum_info/operators/symplectic/clifford_circuits.py) file,
but since their definition includes only Clifford gates, then they are converted automatically into  Cliffords.
These gates include:
```
CYGate()
iSWAPGate()
SXGate()
SXdgGate()
```


Do we want to add them to `clifford_circuits.py` ?
> There are two approaches here:
> 
> The first is to add `ecr` to the clifford circuits [`_BASIS_2q`](https://github.com/Qiskit/qiskit-terra/blob/e016f9ca612482fe2b15af4d54828cf3c1899f6f/qiskit/quantum_info/operators/symplectic/clifford_circuits.py#L346) using the following Clifford table:
> 
> ...
> 
> The second is to add an equivalent definition of the `ECRGate()` to the [equivalence library](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/library/standard_gates/equivalence_library.py) (assuming that the Clifford class will handle equivalent definitions):
> 
> ...
>
These both seem viable to me. Having a Clifford-only definition in the equivalence library would be useful in general, since the only path from ECR to CX now requires to RZXs, which in turn will generate two CX gates. I don't think `clifford_circuits.py` currently checks the `EquivalenceLibrary` though, only `Instruction.definition`, though I don't see any reason why it couldn't.

> Note that in the circuit library there are some Clifford gates that do not appear in the [clifford_circuits.py](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/quantum_info/operators/symplectic/clifford_circuits.py) file, but since their definition includes only Clifford gates, then they are converted automatically into Cliffords. These gates include:
> 
> ...
> 
> Do we want to add them to `clifford_circuits.py` ?

Having them in `clifford_circuits.py` should be moderately faster, but I think having them there would be beneficial for completeness and to guard against the case that their definition changes in the future to something like a QASM-style definition that isn't expressed in terms of Cliffords.
Copying over context from @ShellyGarion 's comment on https://github.com/Qiskit/qiskit-terra/pull/9475#issuecomment-1431014041 that we should discuss here:

> Gates that are Cliffords only for certain parameters (e.g. an integer multiplication of pi or pi/2).
>     Perhaps they can also be added to the file [clifford_circuits.py](https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/quantum_info/operators/symplectic/clifford_circuits.py), defining new lists of gates:
>     _BASIS_1Q_with_params and _BASIS_2Q_with_params, and check both the gate name and the gate params [here](https://github.com/Qiskit/qiskit-terra/blob/e016f9ca612482fe2b15af4d54828cf3c1899f6f/qiskit/quantum_info/operators/symplectic/clifford_circuits.py#L49).
> 
>             RXGate(theta=np.pi / 2),
>             RYGate(theta=np.pi / 2),
>             RZGate(phi=np.pi / 2),
>             CPhaseGate(theta=np.pi),
>             CRXGate(theta=np.pi),
>             CRYGate(theta=np.pi),
>             CRZGate(theta=np.pi),
>             RXXGate(theta=np.pi / 2),
>             RYYGate(theta=np.pi / 2),
>             RZZGate(theta=np.pi / 2),
>             RZXGate(theta=np.pi / 2),
>             XXMinusYYGate(theta=np.pi),
>             XXPlusYYGate(theta=-np.pi),

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
@@ -616,6 +616,26 @@
     def_ecr.append(inst, qargs, cargs)
 _sel.add_equivalence(ECRGate(), def_ecr)
 
+# ECRGate decomposed to Clifford gates (up to a global phase)
+#
+#                  global phase: 7π/4
+#      ┌──────┐         ┌───┐      ┌───┐
+# q_0: ┤0     ├    q_0: ┤ S ├───■──┤ X ├
+#      │  Ecr │  ≡      ├───┴┐┌─┴─┐└───┘
+# q_1: ┤1     ├    q_1: ┤ √X ├┤ X ├─────
+#      └──────┘         └────┘└───┘
+
+q = QuantumRegister(2, "q")
+def_ecr_cliff = QuantumCircuit(q, global_phase=-pi / 4)
+for inst, qargs, cargs in [
+    (SGate(), [q[0]], []),
+    (SXGate(), [q[1]], []),
+    (CXGate(), [q[0], q[1]], []),
+    (XGate(), [q[0]], []),
+]:
+    def_ecr_cliff.append(inst, qargs, cargs)
+_sel.add_equivalence(ECRGate(), def_ecr_cliff)
+
 # SGate
 #
 #    ┌───┐        ┌─────────┐
diff --git a/qiskit/quantum_info/operators/symplectic/clifford.py b/qiskit/quantum_info/operators/symplectic/clifford.py
--- a/qiskit/quantum_info/operators/symplectic/clifford.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford.py
@@ -85,8 +85,11 @@ class Clifford(BaseOperator, AdjointMixin, Operation):
     :class:`~qiskit.circuit.library.XGate`, :class:`~qiskit.circuit.library.YGate`,
     :class:`~qiskit.circuit.library.ZGate`, :class:`~qiskit.circuit.library.HGate`,
     :class:`~qiskit.circuit.library.SGate`, :class:`~qiskit.circuit.library.SdgGate`,
+    :class:`~qiskit.circuit.library.SXGate`, :class:`~qiskit.circuit.library.SXdgGate`,
     :class:`~qiskit.circuit.library.CXGate`, :class:`~qiskit.circuit.library.CZGate`,
-    :class:`~qiskit.circuit.library.SwapGate`.
+    :class:`~qiskit.circuit.library.CYGate`, :class:`~qiskit.circuit.library.DXGate`,
+    :class:`~qiskit.circuit.library.SwapGate`, :class:`~qiskit.circuit.library.iSwapGate`,
+    :class:`~qiskit.circuit.library.ECRGate`.
     They can be converted back into a :class:`~qiskit.circuit.QuantumCircuit`,
     or :class:`~qiskit.circuit.Gate` object using the :meth:`~Clifford.to_circuit`
     or :meth:`~Clifford.to_instruction` methods respectively. Note that this
diff --git a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py
--- a/qiskit/quantum_info/operators/symplectic/clifford_circuits.py
+++ b/qiskit/quantum_info/operators/symplectic/clifford_circuits.py
@@ -230,6 +230,42 @@ def _append_sdg(clifford, qubit):
     return clifford
 
 
+def _append_sx(clifford, qubit):
+    """Apply an SX gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        qubit (int): gate qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    x = clifford.x[:, qubit]
+    z = clifford.z[:, qubit]
+
+    clifford.phase ^= ~x & z
+    x ^= z
+    return clifford
+
+
+def _append_sxdg(clifford, qubit):
+    """Apply an SXdg gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        qubit (int): gate qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    x = clifford.x[:, qubit]
+    z = clifford.z[:, qubit]
+
+    clifford.phase ^= x & z
+    x ^= z
+    return clifford
+
+
 def _append_v(clifford, qubit):
     """Apply a V gate to a Clifford.
 
@@ -312,6 +348,23 @@ def _append_cz(clifford, control, target):
     return clifford
 
 
+def _append_cy(clifford, control, target):
+    """Apply a CY gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        control (int): gate control qubit index.
+        target (int): gate target qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    clifford = _append_sdg(clifford, target)
+    clifford = _append_cx(clifford, control, target)
+    clifford = _append_s(clifford, target)
+    return clifford
+
+
 def _append_swap(clifford, qubit0, qubit1):
     """Apply a Swap gate to a Clifford.
 
@@ -328,6 +381,61 @@ def _append_swap(clifford, qubit0, qubit1):
     return clifford
 
 
+def _append_iswap(clifford, qubit0, qubit1):
+    """Apply a iSwap gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        qubit0 (int): first qubit index.
+        qubit1 (int): second  qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    clifford = _append_s(clifford, qubit0)
+    clifford = _append_h(clifford, qubit0)
+    clifford = _append_s(clifford, qubit1)
+    clifford = _append_cx(clifford, qubit0, qubit1)
+    clifford = _append_cx(clifford, qubit1, qubit0)
+    clifford = _append_h(clifford, qubit1)
+    return clifford
+
+
+def _append_dcx(clifford, qubit0, qubit1):
+    """Apply a DCX gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        qubit0 (int): first qubit index.
+        qubit1 (int): second  qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    clifford = _append_cx(clifford, qubit0, qubit1)
+    clifford = _append_cx(clifford, qubit1, qubit0)
+    return clifford
+
+
+def _append_ecr(clifford, qubit0, qubit1):
+    """Apply an ECR gate to a Clifford.
+
+    Args:
+        clifford (Clifford): a Clifford.
+        qubit0 (int): first qubit index.
+        qubit1 (int): second  qubit index.
+
+    Returns:
+        Clifford: the updated Clifford.
+    """
+    clifford = _append_s(clifford, qubit0)
+    clifford = _append_sx(clifford, qubit1)
+    clifford = _append_cx(clifford, qubit0, qubit1)
+    clifford = _append_x(clifford, qubit0)
+
+    return clifford
+
+
 # Basis Clifford Gates
 _BASIS_1Q = {
     "i": _append_i,
@@ -340,9 +448,19 @@ def _append_swap(clifford, qubit0, qubit1):
     "s": _append_s,
     "sdg": _append_sdg,
     "sinv": _append_sdg,
+    "sx": _append_sx,
+    "sxdg": _append_sxdg,
     "v": _append_v,
     "w": _append_w,
 }
-_BASIS_2Q = {"cx": _append_cx, "cz": _append_cz, "swap": _append_swap}
+_BASIS_2Q = {
+    "cx": _append_cx,
+    "cz": _append_cz,
+    "cy": _append_cy,
+    "swap": _append_swap,
+    "iswap": _append_iswap,
+    "ecr": _append_ecr,
+    "dcx": _append_dcx,
+}
 # Non-clifford gates
 _NON_CLIFFORD = {"t", "tdg", "ccx", "ccz"}


Test patch:
diff --git a/test/python/quantum_info/operators/symplectic/test_clifford.py b/test/python/quantum_info/operators/symplectic/test_clifford.py
--- a/test/python/quantum_info/operators/symplectic/test_clifford.py
+++ b/test/python/quantum_info/operators/symplectic/test_clifford.py
@@ -23,11 +23,17 @@
 from qiskit.circuit.library import (
     CXGate,
     CZGate,
+    CYGate,
     HGate,
     IGate,
     SdgGate,
     SGate,
+    SXGate,
+    SXdgGate,
     SwapGate,
+    iSwapGate,
+    ECRGate,
+    DCXGate,
     XGate,
     YGate,
     ZGate,
@@ -82,11 +88,13 @@ def _define(self):
 def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None):
     """Generate a pseudo random Clifford circuit."""
 
+    qubits_1_gates = ["i", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg", "v", "w"]
+    qubits_2_gates = ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"]
     if gates == "all":
         if num_qubits == 1:
-            gates = ["i", "x", "y", "z", "h", "s", "sdg", "v", "w"]
+            gates = qubits_1_gates
         else:
-            gates = ["i", "x", "y", "z", "h", "s", "sdg", "v", "w", "cx", "cz", "swap"]
+            gates = qubits_1_gates + qubits_2_gates
 
     instructions = {
         "i": (IGate(), 1),
@@ -96,11 +104,17 @@ def random_clifford_circuit(num_qubits, num_gates, gates="all", seed=None):
         "h": (HGate(), 1),
         "s": (SGate(), 1),
         "sdg": (SdgGate(), 1),
+        "sx": (SXGate(), 1),
+        "sxdg": (SXdgGate(), 1),
         "v": (VGate(), 1),
         "w": (WGate(), 1),
         "cx": (CXGate(), 2),
+        "cy": (CYGate(), 2),
         "cz": (CZGate(), 2),
         "swap": (SwapGate(), 2),
+        "iswap": (iSwapGate(), 2),
+        "ecr": (ECRGate(), 2),
+        "dcx": (DCXGate(), 2),
     }
 
     if isinstance(seed, np.random.Generator):
@@ -140,6 +154,8 @@ def test_append_1_qubit_gate(self):
             "sinv": np.array([[[True, True], [False, True]]], dtype=bool),
             "v": np.array([[[True, True], [True, False]]], dtype=bool),
             "w": np.array([[[False, True], [True, True]]], dtype=bool),
+            "sx": np.array([[[True, False], [True, True]]], dtype=bool),
+            "sxdg": np.array([[[True, False], [True, True]]], dtype=bool),
         }
 
         target_phase = {
@@ -155,6 +171,8 @@ def test_append_1_qubit_gate(self):
             "sinv": np.array([[True, False]], dtype=bool),
             "v": np.array([[False, False]], dtype=bool),
             "w": np.array([[False, False]], dtype=bool),
+            "sx": np.array([[False, True]], dtype=bool),
+            "sxdg": np.array([[False, False]], dtype=bool),
         }
 
         target_stabilizer = {
@@ -170,6 +188,8 @@ def test_append_1_qubit_gate(self):
             "sinv": "+Z",
             "v": "+X",
             "w": "+Y",
+            "sx": "-Y",
+            "sxdg": "+Y",
         }
 
         target_destabilizer = {
@@ -185,9 +205,25 @@ def test_append_1_qubit_gate(self):
             "sinv": "-Y",
             "v": "+Y",
             "w": "+Z",
+            "sx": "+X",
+            "sxdg": "+X",
         }
 
-        for gate_name in ("i", "id", "iden", "x", "y", "z", "h", "s", "sdg", "v", "w"):
+        for gate_name in (
+            "i",
+            "id",
+            "iden",
+            "x",
+            "y",
+            "z",
+            "h",
+            "s",
+            "sdg",
+            "v",
+            "w",
+            "sx",
+            "sxdg",
+        ):
             with self.subTest(msg="append gate %s" % gate_name):
                 cliff = Clifford([[1, 0], [0, 1]])
                 cliff = _append_operation(cliff, gate_name, [0])
@@ -273,6 +309,8 @@ def test_1_qubit_conj_relations(self):
             "w * x * v = y",
             "w * y * v = z",
             "w * z * v = x",
+            "sdg * h * sdg = sx",
+            "s * h * s = sxdg",
         ]
 
         for rel in rels:
@@ -410,6 +448,14 @@ def test_2_qubit_relations(self):
             cliff = _append_operation(cliff, "sdg", [0])
             self.assertEqual(cliff, cliff1)
 
+        with self.subTest(msg="relation between cx and dcx"):
+            cliff = Clifford(np.eye(4))
+            cliff1 = cliff.copy()
+            cliff = _append_operation(cliff, "cx", [0, 1])
+            cliff = _append_operation(cliff, "cx", [1, 0])
+            cliff1 = _append_operation(cliff1, "dcx", [0, 1])
+            self.assertEqual(cliff, cliff1)
+
     def test_barrier_delay_sim(self):
         """Test barrier and delay instructions can be simulated"""
         target_circ = QuantumCircuit(2)
@@ -591,7 +637,9 @@ class TestCliffordDecomposition(QiskitTestCase):
             ["h", "s", "sdg"],
             ["h", "s", "v"],
             ["h", "s", "w"],
-            ["h", "s", "sdg", "i", "x", "y", "z", "v", "w"],
+            ["h", "sx", "sxdg"],
+            ["s", "sx", "sxdg"],
+            ["h", "s", "sdg", "i", "x", "y", "z", "v", "w", "sx", "sxdg"],
         ]
     )
     def test_to_operator_1qubit_gates(self, gates):
@@ -609,11 +657,17 @@ def test_to_operator_1qubit_gates(self, gates):
         gates=[
             ["cx"],
             ["cz"],
+            ["cy"],
             ["swap"],
+            ["iswap"],
+            ["ecr"],
+            ["dcx"],
             ["cx", "cz"],
+            ["cx", "cz", "cy"],
             ["cx", "swap"],
             ["cz", "swap"],
             ["cx", "cz", "swap"],
+            ["cx", "cz", "cy", "swap", "iswap", "ecr", "dcx"],
         ]
     )
     def test_to_operator_2qubit_gates(self, gates):