Repository: PennyLaneAI/pennylane
Pull request #3410 (2022-11-22 22:08:00)

Add control CZ gate: CCZ
**Description of the Change:**

Add the controlled CZ gate: CCZ.
  ```
  >>> ccz = qml.CCZ(wires=[0, 1, 2])
  >>> matrix = ccz.compute_matrix()
  [[ 1  0  0  0  0  0  0  0]
   [ 0  1  0  0  0  0  0  0]
   [ 0  0  1  0  0  0  0  0]
   [ 0  0  0  1  0  0  0  0]
   [ 0  0  0  0  1  0  0  0]
   [ 0  0  0  0  0  1  0  0]
   [ 0  0  0  0  0  0  1  0]
   [ 0  0  0  0  0  0  0 -1]]
  ```


codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3410](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (79dee39) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/29c5888c58d762fbc330292c8a7e87d97d75ed0a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (29c5888) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

```diff
@@           Coverage Diff           @@
##           master    #3410   +/-   ##
=======================================
  Coverage   99.70%   99.70%           
=======================================
  Files         298      298           
  Lines       26245    26271   +26     
=======================================
+ Hits        26167    26193   +26     
  Misses         78       78           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/devices/default\_mixed.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9taXhlZC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/devices/default\_qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/devices/null\_qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvbnVsbF9xdWJpdC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/drawer/tape\_mpl.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RyYXdlci90YXBlX21wbC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/ops/functions/is\_commuting.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9mdW5jdGlvbnMvaXNfY29tbXV0aW5nLnB5) | `100.00% <ø> (ø)` | |
| [pennylane/ops/qubit/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9fX2luaXRfXy5weQ==) | `100.00% <ø> (ø)` | |
| [...nylane/transforms/optimization/pattern\_matching.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvb3B0aW1pemF0aW9uL3BhdHRlcm5fbWF0Y2hpbmcucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/ops/qubit/attributes.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9hdHRyaWJ1dGVzLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/qubit/non\_parametric\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3410/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9ub25fcGFyYW1ldHJpY19vcHMucHk=) | `100.00% <100.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 904cddf68..68447ad5c 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -4,6 +4,21 @@
 
 <h3>New features since last release</h3>
 
+* Add the controlled CZ gate: CCZ.
+  ```pycon
+  >>> ccz = qml.CCZ(wires=[0, 1, 2])
+  >>> matrix = ccz.compute_matrix()
+  [[ 1  0  0  0  0  0  0  0]
+   [ 0  1  0  0  0  0  0  0]
+   [ 0  0  1  0  0  0  0  0]
+   [ 0  0  0  1  0  0  0  0]
+   [ 0  0  0  0  1  0  0  0]
+   [ 0  0  0  0  0  1  0  0]
+   [ 0  0  0  0  0  0  1  0]
+   [ 0  0  0  0  0  0  0 -1]]
+  ```
+  [#3408](https://github.com/PennyLaneAI/pennylane/pull/3408)
+
 * Add the controlled Hadamard gate.
   ```pycon
   >>> ch = qml.CH(wires=[0, 1])
diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py
index 98f8f1e77..035c1eb0c 100644
--- a/pennylane/devices/default_mixed.py
+++ b/pennylane/devices/default_mixed.py
@@ -88,6 +88,7 @@ class DefaultMixed(QubitDevice):
         "SWAP",
         "CSWAP",
         "Toffoli",
+        "CCZ",
         "CY",
         "CZ",
         "CH",
diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py
index e468bf0dd..1d33b9512 100644
--- a/pennylane/devices/default_qubit.py
+++ b/pennylane/devices/default_qubit.py
@@ -118,6 +118,7 @@ class DefaultQubit(QubitDevice):
         "SQISW",
         "CSWAP",
         "Toffoli",
+        "CCZ",
         "CY",
         "CZ",
         "CH",
diff --git a/pennylane/devices/null_qubit.py b/pennylane/devices/null_qubit.py
index ecdaa18e1..dfc69c058 100644
--- a/pennylane/devices/null_qubit.py
+++ b/pennylane/devices/null_qubit.py
@@ -71,6 +71,7 @@ class NullQubit(QubitDevice):
         "Toffoli",
         "CY",
         "CZ",
+        "CCZ",
         "CH",
         "PhaseShift",
         "ControlledPhaseShift",
diff --git a/pennylane/drawer/tape_mpl.py b/pennylane/drawer/tape_mpl.py
index 74e731f06..02c669c89 100644
--- a/pennylane/drawer/tape_mpl.py
+++ b/pennylane/drawer/tape_mpl.py
@@ -87,6 +87,7 @@ special_cases = {
     ops.Toffoli: _add_cx,
     ops.MultiControlledX: _add_multicontrolledx,
     ops.CZ: _add_cz,
+    ops.CCZ: _add_cz,
     ops.Barrier: _add_barrier,
     ops.WireCut: _add_wirecut,
 }
diff --git a/pennylane/ops/functions/is_commuting.py b/pennylane/ops/functions/is_commuting.py
index 6ab8708c0..4cff885b6 100644
--- a/pennylane/ops/functions/is_commuting.py
+++ b/pennylane/ops/functions/is_commuting.py
@@ -69,6 +69,7 @@ def _get_target_name(op):
     _control_base_map = {
         "CNOT": "PauliX",
         "CZ": "PauliZ",
+        "CCZ": "PauliZ",
         "CY": "PauliY",
         "CH": "Hadamard",
         "CSWAP": "SWAP",
diff --git a/pennylane/ops/qubit/__init__.py b/pennylane/ops/qubit/__init__.py
index f3938aa91..f37bbc768 100644
--- a/pennylane/ops/qubit/__init__.py
+++ b/pennylane/ops/qubit/__init__.py
@@ -53,6 +53,7 @@ ops = {
     "SX",
     "CNOT",
     "CZ",
+    "CCZ",
     "CY",
     "CH",
     "SWAP",
diff --git a/pennylane/ops/qubit/attributes.py b/pennylane/ops/qubit/attributes.py
index f6021793f..77f285869 100644
--- a/pennylane/ops/qubit/attributes.py
+++ b/pennylane/ops/qubit/attributes.py
@@ -154,19 +154,19 @@ approximation.
 """
 
 self_inverses = Attribute(
-    ["Hadamard", "PauliX", "PauliY", "PauliZ", "CNOT", "CZ", "CH", "CY", "SWAP", "Toffoli"]
+    ["Hadamard", "PauliX", "PauliY", "PauliZ", "CNOT", "CZ", "CY", "CH", "SWAP", "Toffoli", "CCZ"]
 )
 """Attribute: Operations that are their own inverses."""
 
 
-symmetric_over_all_wires = Attribute(["CZ", "SWAP"])
+symmetric_over_all_wires = Attribute(["CZ", "CCZ", "SWAP"])
 """Attribute: Operations that are the same if you exchange the order of wires.
 
 For example, ``qml.CZ(wires=[0, 1])`` has the same effect as ``qml.CZ(wires=[1,
 0])`` due to symmetry of the operation.
 """
 
-symmetric_over_control_wires = Attribute(["Toffoli"])
+symmetric_over_control_wires = Attribute(["CCZ", "Toffoli"])
 """Attribute: Controlled operations that are the same if you exchange the order of all but
 the last (target) wire.
 
@@ -181,6 +181,7 @@ diagonal_in_z_basis = Attribute(
         "S",
         "T",
         "CZ",
+        "CCZ",
         "DiagonalQubitUnitary",
         "RZ",
         "PhaseShift",
diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py
index 1cf6c7965..38346f28b 100644
--- a/pennylane/ops/qubit/non_parametric_ops.py
+++ b/pennylane/ops/qubit/non_parametric_ops.py
@@ -1116,6 +1116,9 @@ class CZ(Operation):
     def pow(self, z):
         return super().pow(z % 2)
 
+    def _controlled(self, wire):
+        return CCZ(wires=wire + self.wires)
+
     @property
     def control_wires(self):
         return Wires(self.wires[0])
@@ -1911,6 +1914,148 @@ class CSWAP(Operation):
         return True
 
 
+class CCZ(Operation):
+    r"""CCZ(wires)
+    CCZ (controlled-controlled-Z) gate.
+
+    .. math::
+
+        CCZ =
+        \begin{pmatrix}
+        1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\
+        0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\
+        0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\
+        0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\
+        0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\
+        0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\
+        0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\
+        0 & 0 & 0 & 0 & 0 & 0 & 0 & -1
+        \end{pmatrix}
+
+    **Details:**
+
+    * Number of wires: 3
+    * Number of parameters: 0
+
+    Args:
+        wires (Sequence[int]): the subsystem the gate acts on
+    """
+    num_wires = 3
+    num_params = 0
+    """int: Number of trainable parameters that the operator depends on."""
+
+    basis = "Z"
+
+    def label(self, decimals=None, base_label=None, cache=None):
+        return base_label or "Z"
+
+    @staticmethod
+    @lru_cache()
+    def compute_matrix():  # pylint: disable=arguments-differ
+        r"""Representation of the operator as a canonical matrix in the computational basis (static method).
+
+        The canonical matrix is the textbook matrix representation that does not consider wires.
+        Implicitly, this assumes that the wires of the operator correspond to the global wire order.
+
+        .. seealso:: :meth:`~.CCZ.matrix`
+
+
+        Returns:
+            ndarray: matrix
+
+        **Example**
+
+        >>> print(qml.CCZ.compute_matrix())
+        [[1 0 0 0 0 0 0 0]
+         [0 1 0 0 0 0 0 0]
+         [0 0 1 0 0 0 0 0]
+         [0 0 0 1 0 0 0 0]
+         [0 0 0 0 1 0 0 0]
+         [0 0 0 0 0 1 0 0]
+         [0 0 0 0 0 0 1 0]
+         [0 0 0 0 0 0 0 -1]]
+        """
+        return np.array(
+            [
+                [1, 0, 0, 0, 0, 0, 0, 0],
+                [0, 1, 0, 0, 0, 0, 0, 0],
+                [0, 0, 1, 0, 0, 0, 0, 0],
+                [0, 0, 0, 1, 0, 0, 0, 0],
+                [0, 0, 0, 0, 1, 0, 0, 0],
+                [0, 0, 0, 0, 0, 1, 0, 0],
+                [0, 0, 0, 0, 0, 0, 1, 0],
+                [0, 0, 0, 0, 0, 0, 0, -1],
+            ]
+        )
+
+    @staticmethod
+    def compute_decomposition(wires):
+        r"""Representation of the operator as a product of other operators (static method).
+
+        .. math:: O = O_1 O_2 \dots O_n.
+
+
+        .. seealso:: :meth:`~.Toffoli.decomposition`.
+
+        Args:
+            wires (Iterable, Wires): wires that the operator acts on
+
+        Returns:
+            list[Operator]: decomposition into lower level operations
+
+        **Example:**
+
+        >>> qml.CCZ.compute_decomposition((0,1,2))
+        [CNOT(wires=[1, 2]),
+        Adjoint(T)(wires=[2]),
+        CNOT(wires=[0, 2]),
+        T(wires=[2]),
+        CNOT(wires=[1, 2]),
+        Adjoint(T)(wires=[2]),
+        CNOT(wires=[0, 2]),
+        T(wires=[2]),
+        T(wires=[1]),
+        CNOT(wires=[0, 1]),
+        Hadamard(wires=[2]),
+        T(wires=[0]),
+        Adjoint(T)(wires=[1]),
+        CNOT(wires=[0, 1]),
+        Hadamard(wires=[2]),]
+
+        """
+        return [
+            CNOT(wires=[wires[1], wires[2]]),
+            qml.adjoint(T(wires=wires[2])),
+            CNOT(wires=[wires[0], wires[2]]),
+            T(wires=wires[2]),
+            CNOT(wires=[wires[1], wires[2]]),
+            qml.adjoint(T(wires=wires[2])),
+            CNOT(wires=[wires[0], wires[2]]),
+            T(wires=wires[2]),
+            T(wires=wires[1]),
+            CNOT(wires=[wires[0], wires[1]]),
+            Hadamard(wires=wires[2]),
+            T(wires=wires[0]),
+            qml.adjoint(T(wires=wires[1])),
+            CNOT(wires=[wires[0], wires[1]]),
+            Hadamard(wires=[2]),
+        ]
+
+    def adjoint(self):
+        return CCZ(wires=self.wires)
+
+    def pow(self, z):
+        return super().pow(z % 2)
+
+    @property
+    def control_wires(self):
+        return Wires(self.wires[:2])
+
+    @property
+    def is_hermitian(self):
+        return True
+
+
 class Toffoli(Operation):
     r"""Toffoli(wires)
     Toffoli (controlled-controlled-X) gate.
diff --git a/pennylane/transforms/optimization/pattern_matching.py b/pennylane/transforms/optimization/pattern_matching.py
index d1d50358c..54a64143f 100644
--- a/pennylane/transforms/optimization/pattern_matching.py
+++ b/pennylane/transforms/optimization/pattern_matching.py
@@ -442,6 +442,7 @@ def _first_match_qubits(node_c, node_p, n_qubits_p):
     control_base = {
         "CNOT": "PauliX",
         "CZ": "PauliZ",
+        "CCZ": "PauliZ",
         "CY": "PauliY",
         "CH": "Hadamard",
         "CSWAP": "SWAP",


Test patch:
diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py
index 4bcfbc1fe..9bc136c7d 100755
--- a/pennylane/devices/tests/test_gates.py
+++ b/pennylane/devices/tests/test_gates.py
@@ -47,6 +47,7 @@ ops = {
     "CRot": qml.CRot(0, 0, 0, wires=[0, 1]),
     "CSWAP": qml.CSWAP(wires=[0, 1, 2]),
     "CZ": qml.CZ(wires=[0, 1]),
+    "CCZ": qml.CCZ(wires=[0, 1, 2]),
     "CY": qml.CY(wires=[0, 1]),
     "CH": qml.CH(wires=[0, 1]),
     "DiagonalQubitUnitary": qml.DiagonalQubitUnitary(np.array([1, 1]), wires=[0]),
@@ -145,6 +146,7 @@ ECR = np.array(
 )
 CNOT = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
 CZ = np.diag([1, 1, 1, -1])
+CCZ = np.diag([1, 1, 1, 1, 1, 1, 1, -1])
 CY = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]])
 CH = np.array(
     [
@@ -306,7 +308,7 @@ two_qubit_param = [
 ]
 two_qubit_multi_param = [(qml.CRot, crot)]
 # list of all three-qubit gates
-three_qubit = [(qml.Toffoli, toffoli), (qml.CSWAP, CSWAP)]
+three_qubit = [(qml.Toffoli, toffoli), (qml.CSWAP, CSWAP), (qml.CCZ, CCZ)]
 
 # single qubit unitary matrix
 theta = 0.8364
diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py
index 08c67323c..e4caa3248 100644
--- a/tests/devices/test_default_qubit_tf.py
+++ b/tests/devices/test_default_qubit_tf.py
@@ -36,6 +36,7 @@ from gate_data import (
     T,
     CNOT,
     CZ,
+    CCZ,
     SWAP,
     CNOT,
     Toffoli,
@@ -100,7 +101,7 @@ two_qubit_param = [
     (qml.MultiRZ, MultiRZ2),
     (qml.ControlledPhaseShift, ControlledPhaseShift),
 ]
-three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP)]
+three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP), (qml.CCZ, CCZ)]
 four_qubit_param = [(qml.OrbitalRotation, OrbitalRotation)]
 
 #####################################################
diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py
index 568757aeb..1be78ff00 100644
--- a/tests/devices/test_default_qubit_torch.py
+++ b/tests/devices/test_default_qubit_torch.py
@@ -61,6 +61,7 @@ from gate_data import (
     SingleExcitationPlus,
     T,
     Toffoli,
+    CCZ,
     X,
     Y,
     Z,
@@ -123,7 +124,7 @@ two_qubit_param = [
     (qml.SingleExcitationPlus, SingleExcitationPlus),
     (qml.SingleExcitationMinus, SingleExcitationMinus),
 ]
-three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP)]
+three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP), (qml.CCZ, CCZ)]
 four_qubit_param = [
     (qml.DoubleExcitation, DoubleExcitation),
     (qml.DoubleExcitationPlus, DoubleExcitationPlus),
diff --git a/tests/gate_data.py b/tests/gate_data.py
index 4a07e1daa..21f7dce47 100644
--- a/tests/gate_data.py
+++ b/tests/gate_data.py
@@ -80,6 +80,8 @@ CSWAP = math.array(
 Toffoli = math.diag([1 for i in range(8)])
 Toffoli[6:8, 6:8] = math.array([[0, 1], [1, 0]])
 
+CCZ = math.diag([1] * 7 + [-1])
+
 w = math.exp(2 * np.pi * 1j / 8)
 QFT = math.array(
     [
diff --git a/tests/ops/functions/test_is_hermitian.py b/tests/ops/functions/test_is_hermitian.py
index ee78b4ca0..2a315bcc3 100644
--- a/tests/ops/functions/test_is_hermitian.py
+++ b/tests/ops/functions/test_is_hermitian.py
@@ -29,6 +29,7 @@ hermitian_ops = (
     qml.PauliZ(0),
     qml.CNOT([0, 1]),
     qml.CZ([0, 1]),
+    qml.CCZ([0, 1, 2]),
     qml.CY([0, 1]),
     qml.CH([0, 1]),
     qml.SWAP([0, 1]),
diff --git a/tests/ops/op_math/test_control.py b/tests/ops/op_math/test_control.py
index 51113fe28..b845df79d 100644
--- a/tests/ops/op_math/test_control.py
+++ b/tests/ops/op_math/test_control.py
@@ -205,17 +205,19 @@ def test_no_control_defined():
         assert type(op) in {qml.ControlledPhaseShift, qml.Toffoli, qml.CRX, qml.CSWAP, qml.CH}
 
 
-def test_no_decomposition_defined():
+def test_decomposition_defined():
     """Test that a controlled gate that has no control transform defined,
-    as well as no decomposition transformed defined, still works correctly"""
+    and a decomposition transformed defined, still works correctly"""
 
     with QuantumTape() as tape:
-        ctrl(qml.CZ, 0)(wires=[1, 2])
+        ctrl(qml.CY, 0)(wires=[1, 2])
 
     tape = tape.expand()
 
-    assert len(tape.operations) == 1
-    assert tape.operations[0].name == "C(CZ)"
+    assert len(tape.operations) == 2
+
+    assert tape.operations[0].name == "C(CRY)"
+    assert tape.operations[1].name == "C(S)"
 
 
 def test_controlled_template():
diff --git a/tests/ops/qubit/test_non_parametric_ops.py b/tests/ops/qubit/test_non_parametric_ops.py
index 98575dab6..f322715ad 100644
--- a/tests/ops/qubit/test_non_parametric_ops.py
+++ b/tests/ops/qubit/test_non_parametric_ops.py
@@ -19,7 +19,26 @@ import itertools
 
 import numpy as np
 import pytest
-from gate_data import CNOT, CSWAP, CZ, ECR, ISWAP, SISWAP, SWAP, H, I, S, T, Toffoli, X, Y, Z, CH
+
+from gate_data import (
+    CNOT,
+    CSWAP,
+    CZ,
+    CCZ,
+    ECR,
+    ISWAP,
+    SISWAP,
+    SWAP,
+    H,
+    I,
+    S,
+    T,
+    Toffoli,
+    X,
+    Y,
+    Z,
+    CH,
+)
 from scipy.sparse import csr_matrix
 from scipy.stats import unitary_group
 
@@ -36,6 +55,7 @@ NON_PARAMETRIZED_OPERATIONS = [
     (qml.ISWAP, ISWAP),
     (qml.SISWAP, SISWAP),
     (qml.CZ, CZ),
+    (qml.CCZ, CCZ),
     (qml.S, S),
     (qml.T, T),
     (qml.CSWAP, CSWAP),
@@ -405,6 +425,48 @@ class TestDecompositions:
 
         assert np.allclose(decomposed_matrix, op.matrix(), atol=tol, rtol=0)
 
+    def test_ccz_decomposition(self, tol):
+        """Tests that the decomposition of the CCZ gate is correct"""
+        op = qml.CCZ(wires=[0, 1, 2])
+        res = op.decomposition()
+
+        assert len(res) == 15
+
+        mats = []
+
+        for i in reversed(res):
+            if i.wires == Wires([2]):
+                mats.append(np.kron(np.eye(4), i.matrix()))
+            elif i.wires == Wires([1]):
+                mats.append(np.kron(np.eye(2), np.kron(i.matrix(), np.eye(2))))
+            elif i.wires == Wires([0]):
+                mats.append(np.kron(i.matrix(), np.eye(4)))
+            elif i.wires == Wires([0, 1]) and i.name == "CNOT":
+                mats.append(np.kron(i.matrix(), np.eye(2)))
+            elif i.wires == Wires([1, 2]) and i.name == "CNOT":
+                mats.append(np.kron(np.eye(2), i.matrix()))
+            elif i.wires == Wires([0, 2]) and i.name == "CNOT":
+                mats.append(
+                    np.array(
+                        [
+                            [1, 0, 0, 0, 0, 0, 0, 0],
+                            [0, 1, 0, 0, 0, 0, 0, 0],
+                            [0, 0, 1, 0, 0, 0, 0, 0],
+                            [0, 0, 0, 1, 0, 0, 0, 0],
+                            [0, 0, 0, 0, 0, 1, 0, 0],
+                            [0, 0, 0, 0, 1, 0, 0, 0],
+                            [0, 0, 0, 0, 0, 0, 0, 1],
+                            [0, 0, 0, 0, 0, 0, 1, 0],
+                        ]
+                    )
+                )
+            else:
+                raise Exception("Unexpected gate in decomposition.")
+
+        decomposed_matrix = np.linalg.multi_dot(mats)
+
+        assert np.allclose(decomposed_matrix, op.matrix(), atol=tol, rtol=0)
+
     def test_CSWAP_decomposition(self, tol):
         """Tests that the decomposition of the CSWAP gate is correct"""
         op = qml.CSWAP(wires=[0, 1, 2])
@@ -1090,6 +1152,7 @@ period_two_ops = (
     qml.SWAP(wires=(0, 1)),
     qml.ISWAP(wires=(0, 1)),
     qml.ECR(wires=(0, 1)),
+    qml.CCZ(wires=(0, 1, 2)),
     qml.CSWAP(wires=(0, 1, 2)),
     qml.Toffoli(wires=(0, 1, 2)),
     qml.MultiControlledX(wires=(0, 1, 2, 3)),
@@ -1275,6 +1338,11 @@ class TestControlledMethod:
         out = original._controlled("a")
         assert qml.equal(original, out)
 
+    def test_CZ(self):
+        """Test the PauliZ _controlled method."""
+        out = qml.CZ(wires=[0, 1])._controlled("a")
+        assert qml.equal(out, qml.CCZ(("a", 0, 1)))
+
 
 class TestSparseMatrix:
     @pytest.mark.parametrize("op, mat", SPARSE_MATRIX_SUPPORTED_OPERATIONS)
@@ -1305,6 +1373,7 @@ label_data = [
     (qml.ECR(wires=(0, 1)), "ECR", "ECR⁻¹"),
     (qml.SISWAP(wires=(0, 1)), "SISWAP", "SISWAP⁻¹"),
     (qml.SQISW(wires=(0, 1)), "SISWAP", "SISWAP⁻¹"),
+    (qml.CCZ(wires=(0, 1, 2)), "Z", "Z"),
     (qml.CSWAP(wires=(0, 1, 2)), "SWAP", "SWAP"),
     (qml.Toffoli(wires=(0, 1, 2)), "X", "X"),
     (qml.MultiControlledX(wires=(0, 1, 2, 3)), "X", "X"),
@@ -1339,6 +1408,7 @@ control_data = [
     (qml.CY(wires=(0, 1)), Wires(0)),
     (qml.CH(wires=(0, 1)), Wires(0)),
     (qml.CSWAP(wires=(0, 1, 2)), Wires([0])),
+    (qml.CCZ(wires=(0, 1, 2)), Wires([0, 1])),
     (qml.Toffoli(wires=(0, 1, 2)), Wires([0, 1])),
     (qml.MultiControlledX(wires=[0, 1, 2, 3, 4]), Wires([0, 1, 2, 3])),
 ]
@@ -1364,6 +1434,7 @@ involution_ops = [  # ops who are their own inverses
     qml.SWAP((0, 1)),
     qml.ECR((0, 1)),
     qml.CSWAP((0, 1, 2)),
+    qml.CCZ((0, 1, 2)),
     qml.Toffoli((0, 1, 2)),
     qml.MultiControlledX(wires=(0, 1, 2, 3)),
     qml.Barrier(0),