Skip to content

Commit

Permalink
Merge pull request #1035 from qiboteam/decompose_U3
Browse files Browse the repository at this point in the history
Add custom `decompose` method to `gates.U3` and `gates.CZ`
  • Loading branch information
MatteoRobbiati authored Oct 11, 2023
2 parents f6b0c2e + d699f69 commit 1ecdf9a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 10 deletions.
44 changes: 41 additions & 3 deletions src/qibo/gates/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ def qasm_label(self):
return "sx"

def decompose(self):
"""A global phase difference exists between the definitions of
"""Decomposition of :math:`\\sqrt{X}` up to global phase.
A global phase difference exists between the definitions of
:math:`\\sqrt{X}` and :math:`\\text{RX}(\\pi / 2)`, with :math:`\\text{RX}`
being the :class:`qibo.gates.RX` gate. More precisely,
:math:`\\sqrt{X} = e^{i \\pi / 4} \\, \\text{RX}(\\pi / 2)`.
Expand Down Expand Up @@ -296,7 +298,9 @@ def qasm_label(self):
return "sxdg"

def decompose(self):
"""A global phase difference exists between the definitions of
"""Decomposition of :math:`(\\sqrt{X})^{\\dagger}` up to global phase.
A global phase difference exists between the definitions of
:math:`\\sqrt{X}` and :math:`\\text{RX}(\\pi / 2)`, with :math:`\\text{RX}`
being the :class:`qibo.gates.RX` gate. More precisely,
:math:`(\\sqrt{X})^{\\dagger} = e^{-i \\pi / 4} \\, \\text{RX}(-\\pi / 2)`.
Expand Down Expand Up @@ -864,6 +868,29 @@ def _dagger(self) -> "Gate":
theta, lam, phi = tuple(-x for x in self.parameters) # pylint: disable=E1130
return self.__class__(self.target_qubits[0], theta, phi, lam)

def decompose(self) -> List[Gate]:
"""Decomposition of :math:`U_{3}` up to global phase.
A global phase difference exists between the definitions of
:math:`U3` and this decomposition. More precisely,
.. math::
U_{3}(\\theta, \\phi, \\lambda) = e^{i \\, \\frac{3 \\pi}{2}}
\\, \\text{RZ}(\\phi + \\pi) \\, \\sqrt{X} \\, \\text{RZ}(\\theta + \\pi)
\\, \\sqrt{X} \\, \\text{RZ}(\\lambda) \\, ,
where :math:`\\text{RZ}` and :math:`\\sqrt{X}` are, respectively,
:class:`qibo.gates.RZ` and :class`qibo.gates.SX`.
"""
q = self.init_args[0]
return [
RZ(q, self.init_kwargs["lam"]),
SX(q),
RZ(q, self.init_kwargs["theta"] + math.pi),
SX(q),
RZ(q, self.init_kwargs["phi"] + math.pi),
]


class CNOT(Gate):
"""The Controlled-NOT gate.
Expand Down Expand Up @@ -934,6 +961,15 @@ def __init__(self, q0, q1):
def qasm_label(self):
return "cz"

def decompose(self) -> List[Gate]:
"""Decomposition of :math:`\\text{CZ}` gate.
Decompose :math:`\\text{CZ}` gate into :class:`qibo.gates.H` in the target qubit,
followed by :class:`qibo.gates.CNOT`, followed by another :class:`qibo.gates.H`
in the target qubit"""
q0, q1 = self.init_args
return [H(q1), CNOT(q0, q1), H(q1)]


class CSX(Gate):
"""The Controlled-:math:`\\sqrt{X}` gate.
Expand Down Expand Up @@ -1739,7 +1775,9 @@ def __init__(self, q0, q1, theta, trainable=True):
self.draw_label = "RXY"

def decompose(self, *free, use_toffolis: bool = True) -> List[Gate]:
"""This decomposition has a global phase difference with respect to the
"""Decomposition of :math:`\\text{R_{XY}}` up to global phase.
This decomposition has a global phase difference with respect to the
original gate due to a phase difference in :math:`\\left(\\sqrt{X}\\right)^{\\dagger}`.
"""
q0, q1 = self.target_qubits
Expand Down
50 changes: 43 additions & 7 deletions tests/test_gates_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,15 +394,24 @@ def test_u2(backend):
assert gates.U2(0, phi, lam).unitary


def test_u3(backend):
theta = 0.1111
phi = 0.1234
lam = 0.4321
@pytest.mark.parametrize("seed_observable", list(range(1, 10 + 1)))
@pytest.mark.parametrize("seed_state", list(range(1, 10 + 1)))
def test_u3(backend, seed_state, seed_observable):
nqubits = 1
initial_state = random_statevector(2**nqubits, backend=backend)
theta, phi, lam = np.random.rand(3)

initial_state = random_statevector(2**nqubits, seed=seed_state, backend=backend)
final_state = apply_gates(
backend, [gates.U3(0, theta, phi, lam)], initial_state=initial_state
)
# test decomposition
final_state_decompose = apply_gates(
backend,
gates.U3(0, theta, phi, lam).decompose(),
nqubits=nqubits,
initial_state=initial_state,
)

cost, sint = np.cos(theta / 2), np.sin(theta / 2)
ep = np.exp(1j * (phi + lam) / 2)
em = np.exp(1j * (phi - lam) / 2)
Expand All @@ -414,6 +423,15 @@ def test_u3(backend):

backend.assert_allclose(final_state, target_state)

# testing random expectation value due to global phase difference
observable = random_hermitian(2**nqubits, seed=seed_observable, backend=backend)
backend.assert_allclose(
np.transpose(np.conj(final_state_decompose))
@ observable
@ final_state_decompose,
np.transpose(np.conj(target_state)) @ observable @ target_state,
)

assert gates.U3(0, theta, phi, lam).qasm_label == "u3"
assert not gates.U3(0, theta, phi, lam).clifford
assert gates.U3(0, theta, phi, lam).unitary
Expand All @@ -436,15 +454,24 @@ def test_cnot(backend, applyx):
assert gates.CNOT(0, 1).unitary


@pytest.mark.parametrize("seed_observable", list(range(1, 10 + 1)))
@pytest.mark.parametrize("seed_state", list(range(1, 10 + 1)))
@pytest.mark.parametrize("controlled_by", [False, True])
def test_cz(backend, controlled_by):
def test_cz(backend, controlled_by, seed_state, seed_observable):
nqubits = 2
initial_state = random_statevector(2**nqubits, backend=backend)
initial_state = random_statevector(2**nqubits, seed=seed_state, backend=backend)
matrix = np.eye(4)
matrix[3, 3] = -1
matrix = backend.cast(matrix, dtype=matrix.dtype)

target_state = np.dot(matrix, initial_state)
# test decomposition
final_state_decompose = apply_gates(
backend,
gates.CZ(0, 1).decompose(),
nqubits=nqubits,
initial_state=initial_state,
)

if controlled_by:
gate = gates.Z(1).controlled_by(0)
Expand All @@ -457,6 +484,15 @@ def test_cz(backend, controlled_by):

backend.assert_allclose(final_state, target_state)

# testing random expectation value due to global phase difference
observable = random_hermitian(2**nqubits, seed=seed_observable, backend=backend)
backend.assert_allclose(
np.transpose(np.conj(final_state_decompose))
@ observable
@ final_state_decompose,
np.transpose(np.conj(target_state)) @ observable @ target_state,
)

assert gates.CZ(0, 1).qasm_label == "cz"
assert gates.CZ(0, 1).clifford
assert gates.CZ(0, 1).unitary
Expand Down

0 comments on commit 1ecdf9a

Please sign in to comment.