diff --git a/doc/source/api-reference/qibo.rst b/doc/source/api-reference/qibo.rst index c6b44d2645..0f55b2004d 100644 --- a/doc/source/api-reference/qibo.rst +++ b/doc/source/api-reference/qibo.rst @@ -930,6 +930,13 @@ Toffoli :members: :member-order: bysource +CCZ +""" + +.. autoclass:: qibo.gates.CCZ + :members: + :member-order: bysource + Deutsch """"""" diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index a3076a5de4..a3dd66c2c6 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -133,6 +133,7 @@ def create(self, dtype): self.ECR = self.matrices.ECR self.SYC = self.matrices.SYC self.TOFFOLI = self.matrices.TOFFOLI + self.CCZ = self.matrices.CCZ matrices = QiboMatrices() diff --git a/src/qibo/backends/npmatrices.py b/src/qibo/backends/npmatrices.py index 184ef9c546..e1c1767a0f 100644 --- a/src/qibo/backends/npmatrices.py +++ b/src/qibo/backends/npmatrices.py @@ -446,6 +446,22 @@ def TOFFOLI(self): dtype=self.dtype, ) + @cached_property + def CCZ(self): + return self._cast( + [ + [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], + ], + dtype=self.dtype, + ) + def DEUTSCH(self, theta): sin = self.np.sin(theta) + 0j # 0j necessary for right tensorflow dtype cos = self.np.cos(theta) + 0j diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 435ce821a9..54b02b45f2 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -2308,6 +2308,56 @@ def congruent(self, use_toffolis: bool = True) -> List[Gate]: ] +class CCZ(Gate): + """The controlled-CZ gate. + + Corresponds to the following unitary matrix + + .. math:: + \\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} + + Args: + q0 (int): the first control qubit id number. + q1 (int): the second control qubit id number. + q2 (int): the target qubit id number. + """ + + def __init__(self, q0, q1, q2): + super().__init__() + self.name = "ccz" + self.draw_label = "Z" + self.control_qubits = (q0, q1) + self.target_qubits = (q2,) + self.init_args = [q0, q1, q2] + self.unitary = True + + @property + def qasm_label(self): + return "ccz" + + def decompose(self) -> List[Gate]: + """Decomposition of :math:`\\text{CCZ}` gate. + + Decompose :math:`\\text{CCZ}` gate into :class:`qibo.gates.H` in + the target qubit, followed by :class:`qibo.gates.TOFFOLI`, followed + by a :class:`qibo.gates.H` in the target qubit. + """ + from qibo.transpiler.decompositions import ( # pylint: disable=C0415 + standard_decompositions, + ) + + return standard_decompositions(self) + + class DEUTSCH(ParametrizedGate): """The Deutsch gate. diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index f559f0088a..33ab35b56b 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -465,3 +465,4 @@ def _u3_to_gpi2(t, p, l): standard_decompositions.add( gates.ECR, [gates.S(0), gates.SX(1), gates.CNOT(0, 1), gates.X(0)] ) +standard_decompositions.add(gates.CCZ, [gates.H(2), gates.TOFFOLI(0, 1, 2), gates.H(2)]) diff --git a/tests/test_gates_gates.py b/tests/test_gates_gates.py index dbd04d9be5..3246413c31 100644 --- a/tests/test_gates_gates.py +++ b/tests/test_gates_gates.py @@ -3,7 +3,7 @@ import numpy as np import pytest -from qibo import gates +from qibo import Circuit, gates, matrices from qibo.parameter import Parameter from qibo.quantum_info import random_hermitian, random_statevector, random_unitary @@ -1206,6 +1206,46 @@ def test_toffoli(backend, applyx): assert gates.TOFFOLI(0, 1, 2).unitary +def test_ccz(backend): + nqubits = 3 + initial_state = random_statevector(2**nqubits, backend=backend) + final_state = apply_gates( + backend, + [gates.CCZ(0, 1, 2)], + nqubits=nqubits, + initial_state=initial_state, + ) + + matrix = 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], + ], + dtype=np.complex128, + ) + matrix = backend.cast(matrix, dtype=matrix.dtype) + + target_state = matrix @ initial_state + backend.assert_allclose(final_state, target_state) + + assert gates.CCZ(0, 1, 2).qasm_label == "ccz" + assert not gates.CCZ(0, 1, 2).clifford + assert gates.CCZ(0, 1, 2).unitary + + # test decomposition + decomposition = Circuit(3) + decomposition.add(gates.CCZ(0, 1, 2).decompose()) + decomposition = decomposition.unitary(backend) + + backend.assert_allclose(decomposition, backend.cast(matrices.CCZ), atol=1e-10) + + def test_deutsch(backend): theta = 0.1234 nqubits = 3