Repository: qiboteam/qibo

Issue:
Add reference to qibosoq paper 
https://arxiv.org/abs/2310.05851

Comments:

Patch:
diff --git a/doc/source/appendix/citing-qibo.rst b/doc/source/appendix/citing-qibo.rst
index 179bbcd47..40f8a3b45 100644
--- a/doc/source/appendix/citing-qibo.rst
+++ b/doc/source/appendix/citing-qibo.rst
@@ -73,6 +73,12 @@ Peer-Reviewed Articles
 
 .. _`arXiv:2308.06313`: https://arxiv.org/abs/2308.06313
 
+* R. Carobene, A. Candido, J. Serrano, A.O-Fuertes, A. Giachero, S. Carrazza,
+  *Qibosoq: an open-source framework for quantum circuit RFSoC programming*
+  (2023), (`arXiv:2310.05851`_)
+
+.. _`arXiv:2310.05851`: https://arxiv.org/abs/2310.05851
+
 Software References in Zenodo
 -----------------------------
 
@@ -102,6 +108,11 @@ Software References in Zenodo
 
 .. _`https://doi.org/10.5281/zenodo.7662185`: https://doi.org/10.5281/zenodo.7662185
 
+* R. Carobene, A. Candido, J. Serrano, S. Carrazza, E. Pedicillo. (2023).
+  qiboteam/qibosoq: Qibosoq. Zenodo. `https://doi.org/10.5281/zenodo.8083285`_.
+
+.. _`https://doi.org/10.5281/zenodo.8083285`: https://doi.org/10.5281/zenodo.8083285
+
 
 
 Conference Proceedings
diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py
index ecda9aafc..da72791fe 100644
--- a/src/qibo/gates/gates.py
+++ b/src/qibo/gates/gates.py
@@ -255,7 +255,9 @@ class SX(Gate):
         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)`.
@@ -296,7 +298,9 @@ class SXDG(Gate):
         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)`.
@@ -864,6 +868,29 @@ class U3(_Un_):
         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.
@@ -934,6 +961,15 @@ class CZ(Gate):
     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.
@@ -1739,7 +1775,9 @@ class RXY(_Rnn_):
         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


Test patch:
diff --git a/tests/test_gates_gates.py b/tests/test_gates_gates.py
index 60db57a4a..08b230c90 100644
--- a/tests/test_gates_gates.py
+++ b/tests/test_gates_gates.py
@@ -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)
@@ -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
@@ -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)
@@ -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