Repository: PennyLaneAI/pennylane

Issue:
[unitaryhack] Define decomposition for `qml.Toffoli`
*This issue has been tagged for contributions during [unitaryHACK](https://unitaryfund.github.io/unitaryhack/participating-projects.html)*

Each PennyLane operation defines a decomposition. The decomposition of an operation is an ordered list of other operations. Applying this list of operations evolves quantum states the same way as applying the operation itself does. In most cases, this list includes elementary gates that are often supported by most quantum devices.

We would like to add the decomposition of the `qml.Toffoli` operation by:
1. Defining the decomposition method for [the `qml.Toffoli` operation](https://github.com/PennyLaneAI/pennylane/blob/1930230dde60112ad90af0951fa9295d374e1adf/pennylane/ops/qubit.py#L630) that has the following signature:
```python
    @staticmethod
    def decomposition(wires):
```
Other operations in the `pennylane/ops/qubit.py` file can serve as an example for defining this method (e.g., [see `qml.Hadamard`](https://github.com/PennyLaneAI/pennylane/blob/1930230dde60112ad90af0951fa9295d374e1adf/pennylane/ops/qubit.py#L87)).
2. Including a test case for the decomposition of `qml.Toffoli` in the `tests/ops/test_qubit_ops.py` file to verify its correctness.


Discussion:
Hi! I would like to work on this issue. Would it be a good idea to decompose the Toffoli gate to a list of Hadamard, CNOT and T gates?
Hi @tgag17, that's great :tada: Yes, using the Hadamard, CNOT and T gates for the decomposition would work nicely. :slightly_smiling_face:

Feel free to open up a PR (even if it's still a work in progress) and ask any questions that you may have.
Great! Thank you. 

Resolving patch:
diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py
--- a/pennylane/ops/qubit.py
+++ b/pennylane/ops/qubit.py
@@ -684,6 +684,27 @@ class Toffoli(Operation):
     def _matrix(cls, *params):
         return cls.matrix
 
+    @staticmethod
+    def decomposition(wires):
+        decomp_ops = [
+            Hadamard(wires=wires[2]),
+            CNOT(wires=[wires[1], wires[2]]),
+            T(wires=wires[2]).inv(),
+            CNOT(wires=[wires[0], wires[2]]),
+            T(wires=wires[2]),
+            CNOT(wires=[wires[1], wires[2]]),
+            T(wires=wires[2]).inv(),
+            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]),
+            T(wires=wires[1]).inv(),
+            CNOT(wires=[wires[0], wires[1]]),
+        ]
+        return decomp_ops
+
     def adjoint(self):
         return Toffoli(wires=self.wires)
 


Test patch:
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -685,6 +685,46 @@ def test_ISWAP_decomposition(self, tol):
 
         assert np.allclose(decomposed_matrix, op.matrix, atol=tol, rtol=0)
 
+    def test_toffoli_decomposition(self, tol):
+        """Tests that the decomposition of the Toffoli gate is correct"""
+        op = qml.Toffoli(wires=[0, 1, 2])
+        res = op.decomposition(op.wires)
+
+        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],
+                        ]
+                    )
+                )
+
+        decomposed_matrix = np.linalg.multi_dot(mats)
+
+        assert np.allclose(decomposed_matrix, op.matrix, atol=tol, rtol=0)
+
     def test_phase_shift(self, tol):
         """Test phase shift is correct"""
 
@@ -2401,7 +2441,7 @@ def test_decomposition_with_flips(self, n_ctrl_wires, control_val, mocker):
                 work_wires=work_wires,
                 control_values=control_values,
             )
-        tape = tape.expand(depth=2)
+        tape = tape.expand(depth=1)
         assert all(not isinstance(op, qml.MultiControlledX) for op in tape.operations)
 
         @qml.qnode(dev)
diff --git a/tests/transforms/test_control.py b/tests/transforms/test_control.py
--- a/tests/transforms/test_control.py
+++ b/tests/transforms/test_control.py
@@ -87,7 +87,7 @@ def test_nested_control():
     assert len(tape.operations) == 1
     op = tape.operations[0]
     assert isinstance(op, ControlledOperation)
-    new_tape = expand_tape(tape, 3)
+    new_tape = expand_tape(tape, 2)
     assert_equal_operations(new_tape.operations, [qml.Toffoli(wires=[7, 3, 0])])
 
 
@@ -99,7 +99,7 @@ def test_multi_control():
     assert len(tape.operations) == 1
     op = tape.operations[0]
     assert isinstance(op, ControlledOperation)
-    new_tape = expand_tape(tape, 3)
+    new_tape = expand_tape(tape, 1)
     assert_equal_operations(new_tape.operations, [qml.Toffoli(wires=[7, 3, 0])])