Repository: Qiskit/qiskit

Issue:
Add CCZ and CS gates to the circuit library and CNOTDihedral class
### What should we add?

The `CCZ` gate is locally equivalent (up to 1-qubit gates) to the [CCXGate](https://qiskit.org/documentation/stubs/qiskit.circuit.library.CCXGate.html#qiskit.circuit.library.CCXGate) (Toffoli gate).

The `CS` gate is locally equivalent (up to 1-qubit gates) to the [CSXGate](https://qiskit.org/documentation/stubs/qiskit.circuit.library.CSXGate.html#qiskit.circuit.library.CSXGate).

First, it may be useful to add these two gates to the circuit library.

Then, one can also add them to the [CNOTDihedral](https://qiskit.org/documentation/stubs/qiskit.quantum_info.CNOTDihedral.html#qiskit.quantum_info.CNOTDihedral) class, to obtain a better synthesis of CNOT-Dihedral circuits, using:
https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/quantum_info/synthesis/cnotdihedral_decompose.py




Discussion:
Hi! If possible, I'd like to work on this issue :)
thanks @epelaaez - I've assigned you to this issue :)

Resolving patch:
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral.py b/qiskit/quantum_info/operators/dihedral/dihedral.py
--- a/qiskit/quantum_info/operators/dihedral/dihedral.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral.py
@@ -70,7 +70,8 @@ class CNOTDihedral(BaseOperator, AdjointMixin):
      :class:`~qiskit.circuit.library.TGate`, :class:`~qiskit.circuit.library.TdgGate`
      :class:`~qiskit.circuit.library.SGate`, :class:`~qiskit.circuit.library.SdgGate`,
      :class:`~qiskit.circuit.library.CXGate`, :class:`~qiskit.circuit.library.CZGate`,
-     :class:`~qiskit.circuit.library.SwapGate`.
+     :class:`~qiskit.circuit.library.CSGate`, :class:`~qiskit.circuit.library.CSdgGate`,
+     :class:`~qiskit.circuit.library.SwapGate`, :class:`~qiskit.circuit.library.CCZGate`.
      They can be converted back into a :class:`~qiskit.circuit.QuantumCircuit`,
      or :class:`~qiskit.circuit.Gate` object using the :meth:`~CNOTDihedral.to_circuit`
      or :meth:`~CNOTDihderal.to_instruction` methods respectively. Note that this
diff --git a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
--- a/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
+++ b/qiskit/quantum_info/operators/dihedral/dihedral_circuits.py
@@ -45,7 +45,7 @@ def _append_circuit(elem, circuit, qargs=None):
     else:
         gate = circuit
 
-    # Handle cx, cz and id since they are basic gates, and cannot be decomposed,
+    # Handle cx, cz, ccz and id since they are basic gates, and cannot be decomposed,
     if gate.name == "cx":
         if len(qargs) != 2:
             raise QiskitError("Invalid qubits for 2-qubit gate cx.")
@@ -64,6 +64,24 @@ def _append_circuit(elem, circuit, qargs=None):
         elem._append_phase(7, qargs[0])
         return elem
 
+    if gate.name == "ccz":
+        if len(qargs) != 3:
+            raise QiskitError("Invalid qubits for 2-qubit gate cx.")
+        elem._append_cx(qargs[1], qargs[2])
+        elem._append_phase(7, qargs[2])
+        elem._append_cx(qargs[0], qargs[2])
+        elem._append_phase(1, qargs[2])
+        elem._append_cx(qargs[1], qargs[2])
+        elem._append_phase(1, qargs[1])
+        elem._append_phase(7, qargs[2])
+        elem._append_cx(qargs[0], qargs[2])
+        elem._append_cx(qargs[0], qargs[1])
+        elem._append_phase(1, qargs[2])
+        elem._append_phase(1, qargs[0])
+        elem._append_phase(7, qargs[1])
+        elem._append_cx(qargs[0], qargs[1])
+        return elem
+
     if gate.name == "id":
         if len(qargs) != 1:
             raise QiskitError("Invalid qubits for 1-qubit gate id.")
@@ -148,6 +166,24 @@ def _append_circuit(elem, circuit, qargs=None):
             elem._append_phase(7, new_qubits[1])
             elem._append_phase(7, new_qubits[0])
 
+        elif instruction.operation.name == "cs" or gate.name == "cs":
+            if len(new_qubits) != 2:
+                raise QiskitError("Invalid qubits for 2-qubit gate cs.")
+            elem._append_phase(1, new_qubits[1])
+            elem._append_phase(1, new_qubits[0])
+            elem._append_cx(new_qubits[1], new_qubits[0])
+            elem._append_phase(7, new_qubits[0])
+            elem._append_cx(new_qubits[1], new_qubits[0])
+
+        elif instruction.operation.name == "csdg" or gate.name == "csdg":
+            if len(new_qubits) != 2:
+                raise QiskitError("Invalid qubits for 2-qubit gate csdg.")
+            elem._append_phase(7, new_qubits[1])
+            elem._append_phase(7, new_qubits[0])
+            elem._append_cx(new_qubits[1], new_qubits[0])
+            elem._append_phase(1, new_qubits[0])
+            elem._append_cx(new_qubits[1], new_qubits[0])
+
         elif instruction.operation.name == "swap" or gate.name == "swap":
             if len(new_qubits) != 2:
                 raise QiskitError("Invalid qubits for 2-qubit gate swap.")
@@ -155,6 +191,23 @@ def _append_circuit(elem, circuit, qargs=None):
             elem._append_cx(new_qubits[1], new_qubits[0])
             elem._append_cx(new_qubits[0], new_qubits[1])
 
+        elif instruction.operation.name == "ccz":
+            if len(new_qubits) != 3:
+                raise QiskitError("Invalid qubits for 3-qubit gate ccz.")
+            elem._append_cx(new_qubits[1], new_qubits[2])
+            elem._append_phase(7, new_qubits[2])
+            elem._append_cx(new_qubits[0], new_qubits[2])
+            elem._append_phase(1, new_qubits[2])
+            elem._append_cx(new_qubits[1], new_qubits[2])
+            elem._append_phase(1, new_qubits[1])
+            elem._append_phase(7, new_qubits[2])
+            elem._append_cx(new_qubits[0], new_qubits[2])
+            elem._append_cx(new_qubits[0], new_qubits[1])
+            elem._append_phase(1, new_qubits[2])
+            elem._append_phase(1, new_qubits[0])
+            elem._append_phase(7, new_qubits[1])
+            elem._append_cx(new_qubits[0], new_qubits[1])
+
         elif instruction.operation.name == "id":
             pass
 


Test patch:
diff --git a/test/python/quantum_info/operators/test_dihedral.py b/test/python/quantum_info/operators/test_dihedral.py
--- a/test/python/quantum_info/operators/test_dihedral.py
+++ b/test/python/quantum_info/operators/test_dihedral.py
@@ -30,7 +30,10 @@
     SdgGate,
     CXGate,
     CZGate,
+    CSGate,
+    CSdgGate,
     SwapGate,
+    CCZGate,
 )
 from qiskit.quantum_info.operators import Operator
 from qiskit.quantum_info.operators import random
@@ -42,10 +45,11 @@ def random_cnotdihedral_circuit(num_qubits, num_gates, gates="all", seed=None):
     """Generate a pseudo random CNOTDihedral circuit."""
 
     if gates == "all":
-        if num_qubits == 1:
-            gates = ["i", "x", "y", "z", "t", "tdg", "s", "sdg"]
-        else:
-            gates = ["i", "x", "y", "z", "t", "tdg", "s", "sdg", "cx", "cz", "swap"]
+        gates = ["i", "x", "y", "z", "t", "tdg", "s", "sdg"]
+        if num_qubits >= 2:
+            gates += ["cx", "cz", "cs", "csdg", "swap"]
+        if num_qubits >= 3:
+            gates += ["ccz"]
 
     instructions = {
         "i": (IGate(), 1),
@@ -58,7 +62,10 @@ def random_cnotdihedral_circuit(num_qubits, num_gates, gates="all", seed=None):
         "tdg": (TdgGate(), 1),
         "cx": (CXGate(), 2),
         "cz": (CZGate(), 2),
+        "cs": (CSGate(), 2),
+        "csdg": (CSdgGate(), 2),
         "swap": (SwapGate(), 2),
+        "ccz": (CCZGate(), 3),
     }
 
     if isinstance(seed, np.random.Generator):
@@ -169,13 +176,14 @@ def test_2_qubit_identities(self):
         circ1.cx(0, 1)
         circ1.cx(1, 0)
         circ1.cx(0, 1)
+        elem1 = CNOTDihedral(circ1)
+        self.assertEqual(elem, elem1, "Error: 2-qubit SWAP identity does not hold")
+
         circ2 = QuantumCircuit(2)
         circ2.cx(1, 0)
         circ2.cx(0, 1)
         circ2.cx(1, 0)
-        elem1 = CNOTDihedral(circ1)
         elem2 = CNOTDihedral(circ2)
-        self.assertEqual(elem, elem1, "Error: 2-qubit SWAP identity does not hold")
         self.assertEqual(elem1, elem2, "Error: 2-qubit SWAP identity does not hold")
 
         # CS01 = CS10 (symmetric)
@@ -207,6 +215,20 @@ def test_2_qubit_identities(self):
         elem2 = CNOTDihedral(circ2)
         self.assertEqual(elem1, elem2, "Error: 2-qubit CS identity does not hold")
 
+        circcs01 = QuantumCircuit(2)
+        circcs01.cs(0, 1)
+        elemcs01 = CNOTDihedral(circcs01)
+        self.assertEqual(elem1, elemcs01, "Error: 2-qubit CS identity does not hold")
+
+        circcs10 = QuantumCircuit(2)
+        circcs10.cs(1, 0)
+        elemcs10 = CNOTDihedral(circcs10)
+        self.assertEqual(elem1, elemcs10, "Error: 2-qubit CS identity does not hold")
+
+        circ_cs = CSGate()
+        elem_cs = CNOTDihedral(circ_cs)
+        self.assertEqual(elem1, elem_cs, "Error: 2-qubit CS identity does not hold")
+
         # TI*CS*TdgI = CS
         #
         #      ┌───┐┌───┐                 ┌─────┐
@@ -225,6 +247,13 @@ def test_2_qubit_identities(self):
         elem3 = CNOTDihedral(circ3)
         self.assertEqual(elem1, elem3, "Error: 2-qubit CS identity does not hold")
 
+        circ3 = QuantumCircuit(2)
+        circ3.t(0)
+        circ3.cs(0, 1)
+        circ3.tdg(0)
+        elem3 = CNOTDihedral(circ3)
+        self.assertEqual(elem1, elem3, "Error: 2-qubit CS identity does not hold")
+
         # IT*CS*ITdg = CS
         #
         #      ┌───┐
@@ -243,6 +272,13 @@ def test_2_qubit_identities(self):
         elem4 = CNOTDihedral(circ4)
         self.assertEqual(elem1, elem4, "Error: 2-qubit CS identity does not hold")
 
+        circ4 = QuantumCircuit(2)
+        circ4.t(1)
+        circ4.cs(0, 1)
+        circ4.tdg(1)
+        elem4 = CNOTDihedral(circ4)
+        self.assertEqual(elem1, elem4, "Error: 2-qubit CS identity does not hold")
+
         # XX*CS*XX*SS = CS
         #
         #      ┌───┐┌───┐                 ┌───┐┌───┐
@@ -265,6 +301,17 @@ def test_2_qubit_identities(self):
         elem5 = CNOTDihedral(circ5)
         self.assertEqual(elem1, elem5, "Error: 2-qubit CS identity does not hold")
 
+        circ5 = QuantumCircuit(2)
+        circ5.x(0)
+        circ5.x(1)
+        circ5.cs(0, 1)
+        circ5.x(0)
+        circ5.x(1)
+        circ5.s(0)
+        circ5.s(1)
+        elem5 = CNOTDihedral(circ5)
+        self.assertEqual(elem1, elem5, "Error: 2-qubit CS identity does not hold")
+
         # CSdg01 = CSdg10 (symmetric)
         #
         #      ┌─────┐
@@ -294,6 +341,20 @@ def test_2_qubit_identities(self):
         elem2 = CNOTDihedral(circ2)
         self.assertEqual(elem1, elem2, "Error: 2-qubit CSdg identity does not hold")
 
+        circsdg01 = QuantumCircuit(2)
+        circsdg01.csdg(0, 1)
+        elemcsdg01 = CNOTDihedral(circsdg01)
+        self.assertEqual(elem1, elemcsdg01, "Error: 2-qubit CSdg identity does not hold")
+
+        circsdg10 = QuantumCircuit(2)
+        circsdg10.csdg(1, 0)
+        elemcsdg10 = CNOTDihedral(circsdg10)
+        self.assertEqual(elem1, elemcsdg10, "Error: 2-qubit CSdg identity does not hold")
+
+        circ_csdg = CSdgGate()
+        elem_csdg = CNOTDihedral(circ_csdg)
+        self.assertEqual(elem1, elem_csdg, "Error: 2-qubit CS identity does not hold")
+
         # XI*CS*XI*ISdg = CSdg
         #
         #      ┌───┐┌───┐                  ┌───┐
@@ -313,6 +374,14 @@ def test_2_qubit_identities(self):
         elem3 = CNOTDihedral(circ3)
         self.assertEqual(elem1, elem3, "Error: 2-qubit CSdg identity does not hold")
 
+        circ3 = QuantumCircuit(2)
+        circ3.x(0)
+        circ3.cs(0, 1)
+        circ3.x(0)
+        circ3.sdg(1)
+        elem3 = CNOTDihedral(circ3)
+        self.assertEqual(elem3, elemcsdg01, "Error: 2-qubit CSdg identity does not hold")
+
         # IX*CS*IX*SdgI = CSdg
         #
         #      ┌───┐                      ┌─────┐
@@ -332,16 +401,25 @@ def test_2_qubit_identities(self):
         elem4 = CNOTDihedral(circ4)
         self.assertEqual(elem1, elem4, "Error: 2-qubit CSdg identity does not hold")
 
+        circ4 = QuantumCircuit(2)
+        circ4.x(1)
+        circ4.cs(0, 1)
+        circ4.x(1)
+        circ4.sdg(0)
+        elem4 = CNOTDihedral(circ4)
+        self.assertEqual(elemcsdg01, elem4, "Error: 2-qubit CSdg identity does not hold")
+
         # relations for CZ
         # CZ(0,1) = CZ(1,0)
         elem = CNOTDihedral(CZGate())
         circ1 = QuantumCircuit(2)
         circ1.cz(0, 1)
+        elem1 = CNOTDihedral(circ1)
+        self.assertEqual(elem, elem1, "Error: 2-qubit CZ identity does not hold")
+
         circ2 = QuantumCircuit(2)
         circ2.cz(1, 0)
-        elem1 = CNOTDihedral(circ1)
         elem2 = CNOTDihedral(circ2)
-        self.assertEqual(elem, elem1, "Error: 2-qubit CZ identity does not hold")
         self.assertEqual(elem1, elem2, "Error: 2-qubit CZ identity does not hold")
 
         # CZ = CS * CS
@@ -365,6 +443,12 @@ def test_2_qubit_identities(self):
         elem3 = CNOTDihedral(circ3)
         self.assertEqual(elem1, elem3, "Error: 2-qubit CZ identity does not hold")
 
+        circ3 = QuantumCircuit(2)
+        circ3.cs(0, 1)
+        circ3.cs(0, 1)
+        elem3 = CNOTDihedral(circ3)
+        self.assertEqual(elem1, elem3, "Error: 2-qubit CZ identity does not hold")
+
         # CZ = CSdg * CSdg
         #
         #      ┌─────┐               ┌─────┐
@@ -386,6 +470,12 @@ def test_2_qubit_identities(self):
         elem4 = CNOTDihedral(circ4)
         self.assertEqual(elem1, elem4, "Error: 2-qubit CZ identity does not hold")
 
+        circ4 = QuantumCircuit(2)
+        circ4.csdg(0, 1)
+        circ4.csdg(0, 1)
+        elem4 = CNOTDihedral(circ4)
+        self.assertEqual(elem1, elem4, "Error: 2-qubit CZ identity does not hold")
+
         # CZ = TdgTdg * CX * T^2I * CX * TdgTdg
         #
         #      ┌─────┐┌───┐┌───┐┌───┐┌───┐┌─────┐
@@ -492,6 +582,61 @@ def test_2_qubit_identities(self):
         elem2 = CNOTDihedral(circ2)
         self.assertEqual(elem1, elem2, "Error: 2-qubit CX01*CX10 identity does not hold")
 
+    def test_ccz_identities(self):
+        """Tests identities for CCZ gate"""
+
+        # Check the definition of a CCZ gate
+        circ_ccz = CCZGate()
+        elem_ccz = CNOTDihedral(circ_ccz)
+
+        circ1 = QuantumCircuit(3)
+        circ1.ccz(0, 1, 2)
+        elem1 = CNOTDihedral(circ1)
+        self.assertEqual(elem1, elem_ccz, "Error: 3-qubit CCZ identity does not hold")
+
+        circ2 = QuantumCircuit(3)
+        circ2.cx(1, 2)
+        circ2.tdg(2)
+        circ2.cx(0, 2)
+        circ2.t(2)
+        circ2.cx(1, 2)
+        circ2.t(1)
+        circ2.tdg(2)
+        circ2.cx(0, 2)
+        circ2.cx(0, 1)
+        circ2.t(2)
+        circ2.t(0)
+        circ2.tdg(1)
+        circ2.cx(0, 1)
+        elem2 = CNOTDihedral(circ2)
+        self.assertEqual(elem1, elem2, "Error: 3-qubit CCZ identity does not hold")
+
+        # Check that a CCZ gate is the same when permuting the qubits
+        circ3 = QuantumCircuit(3)
+        circ3.ccz(0, 2, 1)
+        elem3 = CNOTDihedral(circ3)
+        self.assertEqual(elem1, elem3, "Error: 3-qubit CCZ identity does not hold")
+
+        circ4 = QuantumCircuit(3)
+        circ4.ccz(1, 0, 2)
+        elem4 = CNOTDihedral(circ4)
+        self.assertEqual(elem1, elem4, "Error: 3-qubit CCZ identity does not hold")
+
+        circ5 = QuantumCircuit(3)
+        circ5.ccz(1, 2, 0)
+        elem5 = CNOTDihedral(circ5)
+        self.assertEqual(elem1, elem5, "Error: 3-qubit CCZ identity does not hold")
+
+        circ6 = QuantumCircuit(3)
+        circ6.ccz(2, 0, 1)
+        elem6 = CNOTDihedral(circ6)
+        self.assertEqual(elem1, elem6, "Error: 3-qubit CCZ identity does not hold")
+
+        circ7 = QuantumCircuit(3)
+        circ7.ccz(1, 2, 0)
+        elem7 = CNOTDihedral(circ7)
+        self.assertEqual(elem1, elem7, "Error: 3-qubit CCZ identity does not hold")
+
     def test_random_decompose(self):
         """
         Test that random elements are CNOTDihedral