Source code for qibo.gates.gates

"""Module defining built-in quantum gate classes."""

# pylint: disable=C0103,W0212,W0223

import math
from typing import List, Tuple, Union

import numpy as np
from numpy.typing import ArrayLike

from qibo.backends import _check_backend
from qibo.config import PRECISION_TOL, raise_error
from qibo.gates.abstract import Gate, ParametrizedGate
from qibo.parameter import Parameter


[docs]class H(Gate): """The Hadamard gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{\\sqrt{2}} \\, \\begin{pmatrix} 1 & 1 \\\\ 1 & -1 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "h" self.draw_label = "H" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "h"
[docs]class X(Gate): """The Pauli-:math:`X` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 0 & 1 \\\\ 1 & 0 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "x" self.draw_label = "X" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "x" @Gate.check_controls def controlled_by(self, *q: int) -> Gate: """Fall back to CNOT and Toffoli if there is one or two controls.""" if len(q) == 1: gate = CNOT(q[0], self.target_qubits[0]) elif len(q) == 2: gate = TOFFOLI(q[0], q[1], self.target_qubits[0]) else: gate = super().controlled_by(*q) return gate def _base_decompose( self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: """Decomposes multi-control ``X`` gate to one-qubit, ``CNOT`` and ``TOFFOLI`` gates. Args: free (int): Ids of free qubits to use for the gate decomposition. use_toffolis: If ``True`` the decomposition contains only ``TOFFOLI`` gates. If ``False`` a congruent representation is used for ``TOFFOLI`` gates. See :class:`qibo.gates.TOFFOLI` for more details on this representation. method (str, optional): Choice of gate set for the decomposition. If ``"standard"``, decomposes circuit into :class:`qibo.gates.gates.CNOT`, :class:`qibo.gates.gates.RX`, :class:`qibo.gates.gates.RY`, :class:`qibo.gates.gates.RZ`, :class:`qibo.gates.gates.U1`, :class:`qibo.gates.gates.U2`, :class:`qibo.gates.gates.U3`, and Clifford gates. If ``"clifford_plus_t"``, decomposes the circuit into :class:`qibo.gates.gates.CNOT`, :class:`qibo.gates.gates.H`, :class:`qibo.gates.gates.S`, :class:`qibo.gates.gates.X`, :class:`qibo.gates.gates.Y`, :class:`qibo.gates.gates.Z`, and :class:`qibo.gates.gates.T`. Defaults to ``"standard"``. kwargs (dict, optional): Additional arguments. When ``method = "clifford_plus_t"``, one can set ``epsilon`` (:math:`\\epsilon`) precision for the transpilation of each gate into the Clifford + :class:`qibo.gates.gates.T` gate set. This precision defaults to :math:`\\epsilon = 10^{-16}`. Another possible keyword argument is ``mpmath_dps``, which defines the number of decimal places used by the ``mpmath`` package. ``mpmmath_dps`` defaults to :math:`256`. Returns: List[:class:`qibo.gates.abstract.Gate`]: Set of one-qubit, :class:`qibo.gates.CNOT`, and :class:`qibo.gates.TOFFOLI` gates that have the same effect as applying the original multi-control gate. """ if set(free) & set(self.qubits): raise_error( ValueError, "Cannot decompose multi-control X gate if free " "qubits coincide with target or controls.", ) controls = self.control_qubits target = self.target_qubits[0] ncontrols = len(controls) if ncontrols < 3: return [self.__class__(target).controlled_by(*controls)] decomp_gates = [] nqubits = ncontrols + 1 + len(free) if (nqubits >= 2 * ncontrols - 1) and (ncontrols >= 3): gates1 = [ TOFFOLI( controls[ncontrols - 2 - k], free[ncontrols - 4 - k], free[ncontrols - 3 - k], ).congruent() for k in range(ncontrols - 3) ] gates2 = TOFFOLI(controls[0], controls[1], free[0]).congruent() first_toffoli = TOFFOLI( controls[ncontrols - 1], free[ncontrols - 3], target ) decomp_gates.append(first_toffoli) for gates in gates1: decomp_gates.extend(gates) decomp_gates.extend(gates2) for gates in gates1[::-1]: decomp_gates.extend(gates) elif len(free) >= 1: m1 = nqubits // 2 free1 = controls[m1:] + (target,) + tuple(free[1:]) x1 = self.__class__(free[0]).controlled_by(*controls[:m1]) part1 = x1._base_decompose(*free1, use_toffolis=use_toffolis, **kwargs) free2 = controls[:m1] + tuple(free[1:]) controls2 = controls[m1:] + (free[0],) x2 = self.__class__(target).controlled_by(*controls2) part2 = x2._base_decompose(*free2, use_toffolis=use_toffolis, **kwargs) decomp_gates = [*part1, *part2] else: # pragma: no cover # impractical case raise_error( NotImplementedError, "``X`` decomposition not implemented for zero free qubits.", ) decomp_gates.extend(decomp_gates) return decomp_gates
[docs] def decompose( self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: return self._base_decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
[docs] def basis_rotation(self) -> Gate: return H(self.target_qubits[0])
[docs]class Y(Gate): """The Pauli-:math:`Y` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 0 & -i \\\\ i & 0 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "y" self.draw_label = "Y" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "y" @Gate.check_controls def controlled_by(self, *q: int) -> Gate: """Fall back to CY if there is only one control.""" if len(q) == 1: gate = CY(q[0], self.target_qubits[0]) else: gate = super().controlled_by(*q) return gate
[docs] def basis_rotation(self) -> Gate: from qibo import matrices # pylint: disable=C0415 matrix = (matrices.Y + matrices.Z) / math.sqrt(2) gate = Unitary(matrix, self.target_qubits[0], trainable=False) gate.clifford = True return gate
[docs]class Z(Gate): """The Pauli-:math:`Z` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & -1 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "z" self.draw_label = "Z" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "z" @Gate.check_controls def controlled_by(self, *q: int) -> Gate: """Fall back to CZ if there is only one control.""" if len(q) == 1: gate = CZ(q[0], self.target_qubits[0]) elif len(q) == 2: gate = CCZ(q[0], q[1], self.target_qubits[0]) else: gate = super().controlled_by(*q) return gate
[docs] def basis_rotation(self) -> None: return None
[docs]class SX(Gate): """The :math:`\\sqrt{X}` gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{2} \\, \\begin{pmatrix} 1 + i & 1 - i \\\\ 1 - i & 1 + i \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "sx" self.draw_label = "SX" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "sx"
[docs] def decompose( # pylint: disable=W0246 self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: """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)`. """ return super().decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
def _dagger(self) -> Gate: """""" return SXDG(self.init_args[0])
class SXDG(Gate): """The conjugate transpose of the :math:`\\sqrt{X}` gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{2} \\, \\begin{pmatrix} 1 - i & 1 + i \\\\ 1 + i & 1 - i \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "sxdg" self.draw_label = "SXDG" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "sxdg" def _dagger(self) -> Gate: """""" return SX(self.init_args[0])
[docs]class S(Gate): """The :math:`S` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & i \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "s" self.draw_label = "S" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "s" def _dagger(self) -> Gate: return SDG(*self.init_args)
class SDG(Gate): """The conjugate transpose of the :math:`S` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & -i \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "sdg" self.draw_label = "SDG" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "sdg" def _dagger(self) -> Gate: return S(*self.init_args)
[docs]class T(Gate): """The T gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i \\pi / 4} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "t" self.draw_label = "T" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "t" def _dagger(self) -> Gate: return TDG(*self.init_args)
class TDG(Gate): """The conjugate transpose of the T gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{-i \\pi / 4} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. """ def __init__(self, q: int): super().__init__() self.name = "tdg" self.draw_label = "TDG" self.target_qubits = (q,) self.init_args = [q] self.unitary = True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "tdg" def _dagger(self) -> Gate: return T(*self.init_args)
[docs]class I(Gate): """The identity gate. Args: *q (int): the qubit id numbers. """ def __init__(self, *q: int): super().__init__() self.name = "id" self.draw_label = "I" self.target_qubits = tuple(q) self.init_args = q self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "id"
[docs]class Align(ParametrizedGate): """Aligns proceeding qubit operations and (optionally) waits ``delay`` amount of time. .. note:: For this gate, the ``trainable`` parameter is by default set to ``False``. Args: q (int): The qubit ID. delay (int, optional): The time (in ns) for which to delay circuit execution on the specified qubits. Defaults to :math:`0`. """ def __init__(self, q: int, delay: int = 0, trainable: bool = False): if not isinstance(delay, int): raise_error( TypeError, f"delay must be type int, but it is type {type(delay)}." ) if delay < 0.0: raise_error(ValueError, "Delay must not be negative.") super().__init__(trainable) self.name = "align" self.draw_label = f"A({delay})" self.init_args = [q] self.init_kwargs = {"name": self.name, "delay": delay, "trainable": trainable} self.target_qubits = (q,) self._parameters = (delay,) self.nparams = 1
class _Rn_(ParametrizedGate): """Abstract class for defining the RX, RY and RZ rotations. Args: q (int): the qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = None self._controlled_gate = None self.target_qubits = (q,) self.unitary = True self.initparams = theta if isinstance(theta, Parameter): theta = theta() self.parameters = theta self.init_args = [q] self.init_kwargs = {"theta": theta, "trainable": trainable} @property def clifford(self) -> bool: return _is_clifford_given_angle(self.parameters[0]) def _dagger(self) -> Gate: """""" return self.__class__( self.target_qubits[0], -self.parameters[0] ) # pylint: disable=E1130 @Gate.check_controls def controlled_by(self, *q: int) -> Gate: """Fall back to CRn if there is only one control.""" if len(q) == 1: gate = self._controlled_gate( # pylint: disable=E1102 q[0], self.target_qubits[0], **self.init_kwargs ) else: gate = super().controlled_by(*q) return gate def generator_eigenvalue(self): return 0.5 def gradient(self, backend=None) -> Gate: """Gradient of the Gate.""" backend = _check_backend(backend=backend) return Unitary( -1j * self.generator_eigenvalue() * self.generator(backend)
[docs] @ backend.matrix_parametrized(self), *self.target_qubits, ) class RX(_Rn_): """Rotation around the X-axis of the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos \\frac{\\theta }{2} & -i\\sin \\frac{\\theta }{2} \\\\ -i\\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, trainable: bool = True): super().__init__(q, theta, trainable) self.name = "rx" self.draw_label = "RX" self._controlled_gate = CRX @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "rx"
[docs] def generator(self, backend=None) -> ArrayLike: backend = _check_backend(backend) return backend.matrices.X
[docs]class RY(_Rn_): """Rotation around the Y-axis of the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos \\frac{\\theta }{2} & -\\sin \\frac{\\theta }{2} \\\\ \\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, trainable: bool = True): super().__init__(q, theta, trainable) self.name = "ry" self.draw_label = "RY" self._controlled_gate = CRY @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "ry"
[docs] def generator(self, backend=None) -> ArrayLike: backend = _check_backend(backend) return backend.matrices.Y
[docs]class RZ(_Rn_): """Rotation around the Z-axis of the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} e^{-i \\theta / 2} & 0 \\\\ 0 & e^{i \\theta / 2} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, trainable: bool = True): super().__init__(q, theta, trainable) self.name = "rz" self.draw_label = "RZ" self._controlled_gate = CRZ @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "rz"
[docs] def generator(self, backend=None) -> ArrayLike: backend = _check_backend(backend) return backend.matrices.Z
[docs]class PRX(ParametrizedGate): """Phase :math:`RX` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos{(\\theta / 2)} & -i e^{-i \\phi} \\sin{(\\theta / 2)} \\\\ -i e^{i \\phi} \\sin{(\\theta / 2)} & \\cos{(\\theta / 2)} \\end{pmatrix} Args: q (int): the qubit id number. theta (float): the first angle corresponding to a rotation angle. phi (float): the second angle correspoding to a phase angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, phi: float, trainable=True): super().__init__(trainable) self.name = "prx" self.draw_label = "prx" self.target_qubits = (q,) self.unitary = True self.parameter_names = ["theta", "phi"] self.parameters = theta, phi self.theta = theta self.phi = phi self.nparams = 2 self.init_args = [q] self.init_kwargs = { "theta": theta, "phi": phi, "trainable": trainable, } @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "prx" def _dagger(self) -> Gate: theta = -self.theta phi = self.phi return self.__class__( self.target_qubits[0], theta, phi ) # pylint: disable=E1130
[docs]class GPI(ParametrizedGate): """The GPI gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 0 & e^{- i \\phi} \\\\ e^{i \\phi} & 0 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. phi (float): phase. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, phi: float, trainable: bool = True): super().__init__(trainable) self.name = "gpi" self.draw_label = "GPI" self.target_qubits = (q,) self.unitary = True self.parameter_names = "phi" self.parameters = phi self.nparams = 1 self.init_args = [q] self.init_kwargs = {"phi": phi, "trainable": trainable} @property def qasm_label(self) -> Tuple[str, str]: return "gpi", "gate gpi(phi) q {u3(pi, phi - pi/2, pi/2 - phi) q;}"
[docs]class GPI2(ParametrizedGate): """The GPI2 gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{\\sqrt{2}} \\, \\begin{pmatrix} 1 & -i e^{- i \\phi} \\\\ -i e^{i \\phi} & 1 \\\\ \\end{pmatrix} Args: q (int): the qubit id number. phi (float): phase. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, phi: float, trainable: bool = True): super().__init__(trainable) self.name = "gpi2" self.draw_label = "GPI2" self.target_qubits = (q,) self.unitary = True self.parameter_names = "phi" self.parameters = phi self.nparams = 1 self.init_args = [q] self.init_kwargs = {"phi": phi, "trainable": trainable} @property def qasm_label(self) -> Tuple[str, str]: return "gpi2", "gate gpi2(phi) q {u3(pi/2, phi - pi/2, pi/2 - phi) q;}" @property def clifford(self) -> bool: return _is_clifford_given_angle(self.parameters[0]) def _dagger(self) -> Gate: """""" return self.__class__(self.target_qubits[0], self.parameters[0] + math.pi)
class _Un_(ParametrizedGate): """Abstract class for defining the U1, U2 and U3 gates. Args: q (int): the qubit id number. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, trainable: bool = True): super().__init__(trainable) self.name = None self._controlled_gate = None self.nparams = 0 self.target_qubits = (q,) self.init_args = [q] self.unitary = True self.init_kwargs = {"trainable": trainable} @Gate.check_controls def controlled_by(self, *q: int) -> Gate: """Fall back to CUn if there is only one control.""" if len(q) == 1: gate = self._controlled_gate( # pylint: disable=E1102 q[0], self.target_qubits[0], **self.init_kwargs ) else: gate = super().controlled_by(*q) return gate
[docs]class U1(_Un_): """First general unitary gate. Also known as `Phase` gate or `P` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 \\\\ 0 & e^{i \\theta} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, trainable: bool = True): super().__init__(q, trainable=trainable) self.name = "u1" self.draw_label = "U1" self._controlled_gate = CU1 self.nparams = 1 self.parameters = theta self.init_kwargs = {"theta": theta, "trainable": trainable} @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "u1" def _dagger(self) -> Gate: theta = -self.parameters[0] return self.__class__(self.target_qubits[0], theta) # pylint: disable=E1130
[docs]class U2(_Un_): """Second general unitary gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{\\sqrt{2}} \\begin{pmatrix} e^{-i(\\phi + \\lambda )/2} & -e^{-i(\\phi - \\lambda )/2} \\\\ e^{i(\\phi - \\lambda )/2} & e^{i (\\phi + \\lambda )/2} \\\\ \\end{pmatrix} Args: q (int): the qubit id number. phi (float): first rotation angle. lamb (float): second rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, phi: float, lam: float, trainable: bool = True): super().__init__(q, trainable=trainable) self.name = "u2" self.draw_label = "U2" self._controlled_gate = CU2 self.nparams = 2 self._phi, self._lam = None, None self.init_kwargs = {"phi": phi, "lam": lam, "trainable": trainable} self.parameter_names = ["phi", "lam"] self.parameters = phi, lam @property def qasm_label(self) -> str: return "u2" def _dagger(self) -> Gate: """""" phi, lam = self.parameters phi, lam = math.pi - lam, -math.pi - phi return self.__class__(self.target_qubits[0], phi, lam)
[docs]class U3(_Un_): """Third general unitary gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} e^{-i(\\phi + \\lambda )/2}\\cos\\left (\\frac{\\theta }{2}\\right ) & -e^{-i(\\phi - \\lambda )/2}\\sin\\left (\\frac{\\theta }{2}\\right ) \\\\ e^{i(\\phi - \\lambda )/2}\\sin\\left (\\frac{\\theta }{2}\\right ) & e^{i (\\phi + \\lambda )/2}\\cos\\left (\\frac{\\theta }{2}\\right ) \\\\ \\end{pmatrix} Args: q (int): the qubit id number. theta (float): first rotation angle. phi (float): second rotation angle. lamb (float): third rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__( self, q: int, theta: float, phi: float, lam: float, trainable: bool = True ): super().__init__(q, trainable=trainable) self.name = "u3" self.draw_label = "U3" self._controlled_gate = CU3 self.nparams = 3 self._theta, self._phi, self._lam = None, None, None self.init_kwargs = { "theta": theta, "phi": phi, "lam": lam, "trainable": trainable, } self.parameter_names = ["theta", "phi", "lam"] self.parameters = theta, phi, lam
[docs] def decompose( # pylint: disable=W0246 self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> 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`. """ return super().decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
@property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "u3" 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)
[docs]class U1q(_Un_): """Native single-qubit gate in the Quantinuum platform. Corresponds to the following unitary matrix: .. math:: \\begin{pmatrix} \\cos\\left(\\frac{\\theta}{2}\\right) & -i \\, e^{-i \\, \\phi} \\, \\sin\\left(\\frac{\\theta}{2}\\right) \\\\ -i \\, e^{i \\, \\phi} \\, \\sin\\left(\\frac{\\theta}{2}\\right) & \\cos\\left(\\frac{\\theta}{2}\\right) \\\\ \\end{pmatrix} Note that :math:`U_{1q}(\\theta, \\phi) = U_{3}(\\theta, \\phi - \\frac{\\pi}{2}, \\frac{\\pi}{2} - \\phi)`, where :math:`U_{3}` is :class:`qibo.gates.U3`. Args: q (int): the qubit id number. theta (float): first rotation angle. phi (float): second rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q: int, theta: float, phi: float, trainable: bool = True): super().__init__(q, trainable=trainable) self.name = "u1q" self.draw_label = "U1q" self.nparams = 2 self._theta, self._phi = None, None self.init_kwargs = {"theta": theta, "phi": phi, "trainable": trainable} self.parameter_names = ["theta", "phi"] self.parameters = theta, phi @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) def _dagger(self) -> Gate: """""" theta, phi = self.init_kwargs["theta"], self.init_kwargs["phi"] return self.__class__(self.init_args[0], -theta, phi)
[docs]class CH(Gate): def __init__(self, q0: int, q1: int): super().__init__() self.name = "ch" self.draw_label = "H" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def qasm_label(self) -> str: return "ch"
[docs]class CNOT(Gate): """The Controlled-NOT gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\\\ 0 & 0 & 1 & 0 \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "cx" self.draw_label = "X" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "cx"
[docs]class CY(Gate): """The Controlled-:math:`Y` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & -i \\\\ 0 & 0 & i & 0 \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "cy" self.draw_label = "Y" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def qasm_label(self) -> str: return "cy"
[docs]class CZ(Gate): """The Controlled-Phase gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & -1 \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "cz" self.draw_label = "Z" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "cz"
[docs]class CSX(Gate): """The Controlled-:math:`\\sqrt{X}` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & e^{i\\pi/4} & e^{-i\\pi/4} \\\\ 0 & 0 & e^{-i\\pi/4} & e^{i\\pi/4} \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "csx" self.draw_label = "CSX" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def qasm_label(self) -> str: return "csx" def _dagger(self) -> Gate: """""" return CSXDG(*self.init_args)
class CSXDG(Gate): """The transpose conjugate of the Controlled-:math:`\\sqrt{X}` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & e^{-i\\pi/4} & e^{i\\pi/4} \\\\ 0 & 0 & e^{i\\pi/4} & e^{-i\\pi/4} \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "csxdg" self.draw_label = "CSXDG" self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True @property def qasm_label(self) -> str: return "csxdg" def _dagger(self) -> Gate: """""" return CSX(*self.init_args) class _CRn_(ParametrizedGate): """Abstract method for defining the CRX, CRY and CRZ gates. Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = None self.control_qubits = (q0,) self.target_qubits = (q1,) self.parameters = theta self.unitary = True self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "trainable": trainable} @property def clifford(self) -> bool: return _is_clifford_given_angle(self.parameters[0]) def _dagger(self) -> Gate: """""" q0 = self.control_qubits[0] q1 = self.target_qubits[0] theta = -self.parameters[0] return self.__class__(q0, q1, theta) # pylint: disable=E1130
[docs]class CRX(_CRn_): """Controlled rotation around the X-axis for the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & \\cos \\frac{\\theta }{2} & -i\\sin \\frac{\\theta }{2} \\\\ 0 & 0 & -i\\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "crx" self.draw_label = "RX" @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "crx"
[docs]class CRY(_CRn_): """Controlled rotation around the Y-axis for the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & \\cos \\frac{\\theta }{2} & -\\sin \\frac{\\theta }{2} \\\\ 0 & 0 & \\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Note that this differs from the :class:`qibo.gates.RZ` gate. Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: int, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "cry" self.draw_label = "RY" @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "cry"
[docs]class CRZ(_CRn_): """Controlled rotation around the Z-axis for the Bloch sphere. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & e^{-i \\theta / 2} & 0 \\\\ 0 & 0 & 0 & e^{i \\theta / 2} \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "crz" self.draw_label = "RZ" @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "crz"
class _CUn_(ParametrizedGate): """Abstract method for defining the CU1, CU2 and CU3 gates. Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, trainable: bool = True): super().__init__(trainable) self.name = None self.nparams = 0 self.control_qubits = (q0,) self.target_qubits = (q1,) self.init_args = [q0, q1] self.unitary = True self.init_kwargs = {"trainable": trainable}
[docs]class CU1(_CUn_): """Controlled first general unitary gate. Also known as `CPhase`. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & e^{i \\theta } \\\\ \\end{pmatrix} Note that this differs from the :class:`qibo.gates.CRZ` gate. Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, trainable=trainable) self.name = "cu1" self.draw_label = "U1" self.nparams = 1 self.parameters = theta self.init_kwargs = {"theta": theta, "trainable": trainable} @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "cu1" def _dagger(self) -> Gate: """""" q0 = self.control_qubits[0] q1 = self.target_qubits[0] theta = -self.parameters[0] return self.__class__(q0, q1, theta) # pylint: disable=E1130
[docs]class CU2(_CUn_): """Controlled second general unitary gate. Corresponds to the following unitary matrix .. math:: \\frac{1}{\\sqrt{2}} \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & e^{-i(\\phi + \\lambda )/2} & -e^{-i(\\phi - \\lambda )/2} \\\\ 0 & 0 & e^{i(\\phi - \\lambda )/2} & e^{i (\\phi + \\lambda )/2} \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. phi (float): first rotation angle. lamb (float): second rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__( self, q0: int, q1: int, phi: float, lam: float, trainable: bool = True ): super().__init__(q0, q1, trainable=trainable) self.name = "cu2" self.draw_label = "U2" self.nparams = 2 self.init_kwargs = {"phi": phi, "lam": lam, "trainable": trainable} self.parameter_names = ["phi", "lam"] self.parameters = phi, lam def _dagger(self) -> Gate: """""" q0 = self.control_qubits[0] q1 = self.target_qubits[0] phi, lam = self.parameters phi, lam = math.pi - lam, -math.pi - phi return self.__class__(q0, q1, phi, lam)
[docs]class CU3(_CUn_): """Controlled third general unitary gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & e^{-i(\\phi + \\lambda )/2}\\cos\\left (\\frac{\\theta }{2}\\right ) & -e^{-i(\\phi - \\lambda )/2}\\sin\\left (\\frac{\\theta }{2}\\right ) \\\\ 0 & 0 & e^{i(\\phi - \\lambda )/2}\\sin\\left (\\frac{\\theta }{2}\\right ) & e^{i (\\phi + \\lambda )/2}\\cos\\left (\\frac{\\theta }{2}\\right ) \\\\ \\end{pmatrix} Args: q0 (int): the control qubit id number. q1 (int): the target qubit id number. theta (float): first rotation angle. phi (float): second rotation angle. lamb (float): third rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__( self, q0: int, q1: int, theta: float, phi: float, lam: float, trainable: bool = True, ): super().__init__(q0, q1, trainable=trainable) self.name = "cu3" self.draw_label = "U3" self.nparams = 3 self._theta, self._phi, self._lam = None, None, None self.init_kwargs = { "theta": theta, "phi": phi, "lam": lam, "trainable": trainable, } self.parameter_names = ["theta", "phi", "lam"] self.parameters = theta, phi, lam @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "cu3" def _dagger(self) -> Gate: """""" q0 = self.control_qubits[0] q1 = self.target_qubits[0] theta, lam, phi = tuple(-x for x in self.parameters) # pylint: disable=E1130 return self.__class__(q0, q1, theta, phi, lam)
[docs]class SWAP(Gate): """The swap gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "swap" self.draw_label = "x" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "swap"
[docs]class iSWAP(Gate): """The iSWAP gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & i & 0 \\\\ 0 & i & 0 & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "iswap" self.draw_label = "i" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "iswap"
[docs]class SiSWAP(Gate): """The :math:`\\sqrt{\\text{iSWAP}}` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1/\\sqrt{2} & i/\\sqrt{2} & 0 \\\\ 0 & i/\\sqrt{2} & 1/\\sqrt{2} & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "siswap" self.draw_label = "si" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: return SiSWAPDG(*self.qubits)
class SiSWAPDG(Gate): """The :math:`\\left(\\sqrt{\\text{iSWAP}}\\right)^{\\dagger}` gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 1/\\sqrt{2} & -i/\\sqrt{2} & 0 \\\\ 0 & -i/\\sqrt{2} & 1/\\sqrt{2} & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "siswapdg" self.draw_label = "sidg" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: return SiSWAP(*self.qubits)
[docs]class FSWAP(Gate): """The fermionic swap gate. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 1 & 0 & 0 \\\\ 0 & 0 & 0 & -1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be f-swapped id number. q1 (int): the second qubit to be f-swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "fswap" self.draw_label = "fx" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "fswap"
[docs]class fSim(ParametrizedGate): """The fSim gate defined in `arXiv:2001.08343 <https://arxiv.org/abs/2001.08343>`_. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & \\cos \\theta & -i\\sin \\theta & 0 \\\\ 0 & -i\\sin \\theta & \\cos \\theta & 0 \\\\ 0 & 0 & 0 & e^{-i \\phi } \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. theta (float): Angle for the one-qubit rotation. phi (float): Angle for the ``|11>`` phase. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ # TODO: Check how this works with QASM. def __init__( self, q0: int, q1: int, theta: float, phi: float, trainable: bool = True ): super().__init__(trainable) self.name = "fsim" self.draw_label = "f" self.target_qubits = (q0, q1) self.unitary = True self.parameter_names = ["theta", "phi"] self.parameters = theta, phi self.nparams = 2 self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "phi": phi, "trainable": trainable} @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: """""" q0, q1 = self.target_qubits params = (-x for x in self.parameters) # pylint: disable=E1130 return self.__class__(q0, q1, *params)
[docs]class SYC(Gate): """The Sycamore gate, defined in the Supplementary Information of `Quantum supremacy using a programmable superconducting processor <https://www.nature.com/articles/s41586-019-1666-5>`_. Corresponding to the following unitary matrix .. math:: \\text{fSim}(\\pi / 2, \\, \\pi / 6) = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & 0 & -i & 0 \\\\ 0 & -i & 0 & 0 \\\\ 0 & 0 & 0 & e^{-i \\pi / 6} \\\\ \\end{pmatrix} \\, , where :math:`\\text{fSim}` is the :class:`qibo.gates.fSim` gate. Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "syc" self.draw_label = "SYC" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: """""" return fSim(*self.target_qubits, -np.pi / 2, -np.pi / 6)
[docs]class GeneralizedfSim(ParametrizedGate): """The fSim gate with a general rotation. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & R_{00} & R_{01} & 0 \\\\ 0 & R_{10} & R_{11} & 0 \\\\ 0 & 0 & 0 & e^{-i \\phi } \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. unitary (np.ndarray): Unitary that corresponds to the one-qubit rotation. phi (float): Angle for the ``|11>`` phase. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__( self, q0: int, q1: int, unitary: ArrayLike, phi: float, trainable: bool = True ): super().__init__(trainable) self.name = "generalizedfsim" self.draw_label = "gf" self.target_qubits = (q0, q1) self.unitary = True self.parameter_names = ["unitary", "phi"] self.parameters = unitary, phi self.nparams = 5 self.init_args = [q0, q1] self.init_kwargs = {"unitary": unitary, "phi": phi, "trainable": trainable} @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: q0, q1 = self.target_qubits u, phi = self.parameters init_kwargs = dict(self.init_kwargs) init_kwargs["unitary"] = np.conj(np.transpose(u)) init_kwargs["phi"] = -phi return self.__class__(q0, q1, **init_kwargs) @Gate.parameters.setter def parameters(self, x: ArrayLike): shape = tuple(x[0].shape) if shape != (2, 2): raise_error( ValueError, f"Invalid rotation shape {shape} for generalized fSim gate", ) ParametrizedGate.parameters.fset(self, x) # pylint: disable=no-member
class _Rnn_(ParametrizedGate): """Abstract class for defining the RXX, RYY, RZZ, and RZX rotations. Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = None self._controlled_gate = None self.target_qubits = (q0, q1) self.unitary = True self.parameters = theta self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "trainable": trainable} def _dagger(self) -> Gate: """""" q0, q1 = self.target_qubits return self.__class__(q0, q1, -self.parameters[0]) # pylint: disable=E1130
[docs]class RXX(_Rnn_): """Parametric 2-qubit XX interaction, or rotation about XX-axis. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos \\frac{\\theta }{2} & 0 & 0 & -i\\sin \\frac{\\theta }{2} \\\\ 0 & \\cos \\frac{\\theta }{2} & -i\\sin \\frac{\\theta }{2} & 0 \\\\ 0 & -i\\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} & 0 \\\\ -i\\sin \\frac{\\theta }{2} & 0 & 0 & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "rxx" self.draw_label = "RXX" @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "rxx"
[docs]class RYY(_Rnn_): """Parametric 2-qubit YY interaction, or rotation about YY-axis. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos \\frac{\\theta }{2} & 0 & 0 & i\\sin \\frac{\\theta }{2} \\\\ 0 & \\cos \\frac{\\theta }{2} & -i\\sin \\frac{\\theta }{2} & 0 \\\\ 0 & -i\\sin \\frac{\\theta }{2} & \\cos \\frac{\\theta }{2} & 0 \\\\ i\\sin \\frac{\\theta }{2} & 0 & 0 & \\cos \\frac{\\theta }{2} \\\\ \\end{pmatrix} Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "ryy" self.draw_label = "RYY" @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0]) @property def qasm_label(self) -> str: return "ryy"
[docs]class RZZ(_Rnn_): """Parametric 2-qubit ZZ interaction, or rotation about ZZ-axis. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} e^{-i \\theta / 2} & 0 & 0 & 0 \\\\ 0 & e^{i \\theta / 2} & 0 & 0 \\\\ 0 & 0 & e^{i \\theta / 2} & 0 \\\\ 0 & 0 & 0 & e^{-i \\theta / 2} \\\\ \\end{pmatrix} Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "rzz" self.draw_label = "RZZ" @property def hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "rzz"
[docs]class RZX(_Rnn_): """Parametric 2-qubit ZX interaction, or rotation about ZX-axis. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\text{RX}(\\theta) & 0 \\\\ 0 & \\text{RX}(-\\theta) \\\\ \\end{pmatrix} = \\begin{pmatrix} \\cos{\\frac{\\theta}{2}} & -i \\sin{\\frac{\\theta}{2}} & 0 & 0 \\\\ -i \\sin{\\frac{\\theta}{2}} & \\cos{\\frac{\\theta}{2}} & 0 & 0 \\\\ 0 & 0 & \\cos{\\frac{\\theta}{2}} & i \\sin{\\frac{\\theta}{2}} \\\\ 0 & 0 & i \\sin{\\frac{\\theta}{2}} & \\cos{\\frac{\\theta}{2}} \\\\ \\end{pmatrix} \\, , where :math:`\\text{RX}` is the :class:`qibo.gates.RX` gate. Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "rzx" self.draw_label = "RZX" @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0])
[docs]class RXXYY(_Rnn_): """Parametric 2-qubit :math:`XX + YY` interaction, or rotation about :math:`XX + YY`-axis. Corresponds to the following unitary matrix .. math:: \\exp\\left(-i \\frac{\\theta}{4}(XX + YY)\\right) = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & \\cos{\\frac{\\theta}{2}} & -i \\sin{\\frac{\\theta}{2}} & 0 \\\\ 0 & -i \\sin{\\frac{\\theta}{2}} & \\cos{\\frac{\\theta}{2}} & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} \\, , Args: q0 (int): the first entangled qubit id number. q1 (int): the second entangled qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(q0, q1, theta, trainable) self.name = "rxxyy" self.draw_label = "RXXYY" @property def hamming_weight(self) -> bool: return True
[docs] def decompose( # pylint: disable=W0246 self, *free, use_toffolis=True, method: str = "standard", **kwargs ) -> List[Gate]: """Decomposition of :math:`\\text{R_{XX-YY}}` 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}`. """ return super().decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
[docs]class MS(ParametrizedGate): """The Mølmer–Sørensen (MS) gate is a two-qubit gate native to trapped ions. Corresponds to the following unitary matrix .. math:: \\begin{pmatrix} \\cos(\\theta / 2) & 0 & 0 & -i e^{-i( \\phi_0 + \\phi_1)} \\sin(\\theta / 2) \\\\ 0 & \\cos(\\theta / 2) & -i e^{-i( \\phi_0 - \\phi_1)} \\sin(\\theta / 2) & 0 \\\\ 0 & -i e^{i( \\phi_0 - \\phi_1)} \\sin(\\theta / 2) & \\cos(\\theta / 2) & 0 \\\\ -i e^{i( \\phi_0 + \\phi_1)} \\sin(\\theta / 2) & 0 & 0 & \\cos(\\theta / 2) \\\\ \\end{pmatrix} Args: q0 (int): the first qubit to be swapped id number. q1 (int): the second qubit to be swapped id number. phi0 (float): first qubit's phase. phi1 (float): second qubit's phase theta (float, optional): arbitrary angle in the interval :math:`0 \\leq \\theta \\leq \\pi /2`. If :math:`\\theta \\rightarrow \\pi / 2`, the fully-entangling MS gate is defined. Defaults to :math:`\\pi / 2`. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ # TODO: Check how this works with QASM. def __init__( self, q0: int, q1: int, phi0: float, phi1: float, theta: float = math.pi / 2, trainable: bool = True, ): super().__init__(trainable) self.name = "ms" self.draw_label = "MS" self.target_qubits = (q0, q1) self.unitary = True if theta < 0.0 or theta > math.pi / 2: raise_error( ValueError, f"Theta is defined in the interval 0 <= theta <= pi/2, but it is {theta}.", ) self.parameter_names = ["phi0", "phi1", "theta"] self.parameters = phi0, phi1, theta self.nparams = 3 self.init_args = [q0, q1] self.init_kwargs = { "phi0": phi0, "phi1": phi1, "theta": theta, "trainable": trainable, } @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[2]) @property def qasm_label(self) -> str: return "ms" def _dagger(self) -> Gate: """""" q0, q1 = self.target_qubits phi0, phi1, theta = self.parameters return self.__class__(q0, q1, phi0 + math.pi, phi1, theta)
[docs]class GIVENS(ParametrizedGate): """The Givens gate. Corresponds to the following unitary matrix .. math:: \\exp(i \\, \\frac{\\theta}{2} \\, (X \\otimes Y - Y \\otimes X)) = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & \\cos(\\theta) & -\\sin(\\theta) & 0 \\\\ 0 & \\sin(\\theta) & \\cos(\\theta) & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit id number. q1 (int): the second qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = "g" self.draw_label = "G" self.target_qubits = (q0, q1) self.unitary = True self.parameter_names = "theta" self.parameters = theta self.nparams = 1 self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "trainable": trainable} @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: """""" return self.__class__(*self.target_qubits, -self.parameters[0])
[docs] def decompose( # pylint: disable=W0246 self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: """Decomposition of GIVENS gate according to the decomposition of the RBS gate in Ref. [1]. References: 1. R. M. S. Farias, T. O. Maciel, G. Camilo, R. Lin, S. Ramos-Calderer, and L. Aolita, *Quantum encoder for fixed-Hamming-weight subspaces*, `Phys. Rev. Applied 23, 044014 (2025) <https://doi.org/10.1103/PhysRevApplied.23.044014>`_. """ return super().decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
[docs]class RBS(ParametrizedGate): """The Reconfigurable Beam Splitter gate. Corresponds to the following unitary matrix .. math:: \\exp(-i \\, \\frac{\\theta}{2} \\, (X \\otimes Y - Y \\otimes X)) = \\begin{pmatrix} 1 & 0 & 0 & 0 \\\\ 0 & \\cos(\\theta) & \\sin(\\theta) & 0 \\\\ 0 & -\\sin(\\theta) & \\cos(\\theta) & 0 \\\\ 0 & 0 & 0 & 1 \\\\ \\end{pmatrix} Note that, in our implementation, :math:`\\text{RBS}(\\theta) = \\text{Givens}(-\\theta)`, where :math:`\\text{Givens}` is the :class:`qibo.gates.GIVENS` gate. However, we point out that this definition is not unique. Args: q0 (int): the first qubit id number. q1 (int): the second qubit id number. theta (float): the rotation angle. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__(self, q0: int, q1: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = "rbs" self.draw_label = "RBS" self.target_qubits = (q0, q1) self.unitary = True self.parameter_names = "theta" self.parameters = theta self.nparams = 1 self.init_args = [q0, q1] self.init_kwargs = {"theta": theta, "trainable": trainable} @property def hamming_weight(self) -> bool: return True def _dagger(self) -> Gate: """""" return self.__class__(*self.target_qubits, -self.parameters[0]) def _base_decompose( self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: """Decomposition of RBS gate as in Ref. [1]. Args: free (int): Ids of free qubits to use for the gate decomposition. use_toffolis: If ``True`` the decomposition contains only ``TOFFOLI`` gates. If ``False`` a congruent representation is used for ``TOFFOLI`` gates. See :class:`qibo.gates.TOFFOLI` for more details on this representation. method (str, optional): Choice of gate set for the decomposition. If ``"standard"``, decomposes circuit into :class:`qibo.gates.gates.CNOT`, :class:`qibo.gates.gates.RX`, :class:`qibo.gates.gates.RY`, :class:`qibo.gates.gates.RZ`, :class:`qibo.gates.gates.U1`, :class:`qibo.gates.gates.U2`, :class:`qibo.gates.gates.U3`, and Clifford gates. If ``"clifford_plus_t"``, decomposes the circuit into :class:`qibo.gates.gates.CNOT`, :class:`qibo.gates.gates.H`, :class:`qibo.gates.gates.S`, :class:`qibo.gates.gates.X`, :class:`qibo.gates.gates.Y`, :class:`qibo.gates.gates.Z`, and :class:`qibo.gates.gates.T`. Defaults to ``"standard"``. kwargs (dict, optional): Additional arguments. When ``method = "clifford_plus_t"``, one can set ``epsilon`` (:math:`\\epsilon`) precision for the transpilation of each gate into the Clifford + :class:`qibo.gates.gates.T` gate set. This precision defaults to :math:`\\epsilon = 10^{-16}`. Another possible keyword argument is ``mpmath_dps``, which defines the number of decimal places used by the ``mpmath`` package. ``mpmmath_dps`` defaults to :math:`256`. References: 1. R. M. S. Farias, T. O. Maciel, G. Camilo, R. Lin, S. Ramos-Calderer, and L. Aolita, *Quantum encoder for fixed-Hamming-weight subspaces*, `Phys. Rev. Applied 23, 044014 (2025) <https://doi.org/10.1103/PhysRevApplied.23.044014>`_. """ q0, q1 = self.target_qubits ncontrols = kwargs.get("ncontrols", 0) if ncontrols >= 2: # pragma: no cover return [ CNOT(q0, q1), CRY(q1, q0, 2 * self.init_kwargs["theta"]), CNOT(q0, q1), ] return [ H(q0), CNOT(q0, q1), RY(q0, self.init_kwargs["theta"]), RY(q1, self.init_kwargs["theta"]), CNOT(q0, q1), H(q0), ]
[docs]class ECR(Gate): """THe Echo Cross-Resonance gate. Corresponds to the following matrix .. math:: \\frac{1}{\\sqrt{2}} \\left( X \\, I - Y \\, X \\right) = \\frac{1}{\\sqrt{2}} \\, \\begin{pmatrix} 0 & 0 & 1 & i \\\\ 0 & 0 & i & 1 \\\\ 1 & -i & 0 & 0 \\\\ -i & 1 & 0 & 0 \\\\ \\end{pmatrix} Args: q0 (int): the first qubit id number. q1 (int): the second qubit id number. """ def __init__(self, q0: int, q1: int): super().__init__() self.name = "ecr" self.draw_label = "ECR" self.target_qubits = (q0, q1) self.init_args = [q0, q1] self.unitary = True @property def clifford(self) -> bool: return True
[docs] def decompose( # pylint: disable=W0246 self, *free: int, use_toffolis: bool = True, method: str = "standard", **kwargs ) -> List[Gate]: """Decomposition of :math:`\\textup{ECR}` gate up to global phase. A global phase difference exists between the definitions of :math:`\\textup{ECR}` and this decomposition. More precisely, .. math:: \\textup{ECR} = e^{i 7 \\pi / 4} \\, S(q_{0}) \\, \\sqrt{X}(q_{1}) \\, \\textup{CNOT}(q_{0}, q_{1}) \\, X(q_{0}) \\, . """ return super().decompose( *free, use_toffolis=use_toffolis, method=method, **kwargs )
[docs]class TOFFOLI(Gate): """The Toffoli 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 & 0 & 1 \\\\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 \\\\ \\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: int, q1: int, q2: int): super().__init__() self.name = "ccx" self.draw_label = "X" self.control_qubits = (q0, q1) self.target_qubits = (q2,) self.init_args = [q0, q1, q2] self.unitary = True @property def qasm_label(self) -> str: return "ccx"
[docs] def congruent(self, use_toffolis: bool = True) -> List[Gate]: """Congruent representation of ``TOFFOLI`` gate. This is a helper method for the decomposition of multi-control ``X`` gates. The congruent representation is based on Sec. 6.2 of `arXiv:9503016 <https://arxiv.org/abs/quant-ph/9503016>`_. The sequence of the gates produced here has the same effect as ``TOFFOLI`` with the phase of the ``|101>`` state reversed. Args: use_toffolis: If ``True`` a single ``TOFFOLI`` gate is returned. If ``False`` the congruent representation is returned. Returns: List with ``RY`` and ``CNOT`` gates that have the same effect as applying the original ``TOFFOLI`` gate. """ control0, control1 = self.control_qubits target = self.target_qubits[0] return [ RY(target, -math.pi / 4), CNOT(control1, target), RY(target, -math.pi / 4), CNOT(control0, target), RY(target, math.pi / 4), CNOT(control1, target), RY(target, math.pi / 4), ]
[docs]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: int, q1: int, q2: int): 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 hamming_weight(self) -> bool: return True @property def qasm_label(self) -> str: return "ccz"
[docs]class DEUTSCH(ParametrizedGate): """The Deutsch 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 & i \\cos{\\theta} & \\sin{\\theta} \\\\ 0 & 0 & 0 & 0 & 0 & 0 & \\sin{\\theta} & i \\cos{\\theta} \\\\ \\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: int, q1: int, q2: int, theta: float, trainable: bool = True): super().__init__(trainable) self.name = "deutsch" self.draw_label = "DE" self.control_qubits = (q0, q1) self.target_qubits = (q2,) self.unitary = True self.parameter_names = "theta" self.parameters = theta self.nparams = 1 self.init_args = [q0, q1, q2] self.init_kwargs = {"theta": theta, "trainable": trainable} @property def hamming_weight(self) -> bool: return _is_hamming_weight_given_angle(self.parameters[0], np.pi)
[docs]class FanOut(Gate): """The :math:`\\textrm{FanOut}` gate. Given a register containing :math:`k \\geq 2` qubits, this gate is equivalent to :math:`k - 1` :class:`qibo.gates.CNOT` gates in which the control qubit for all CNOTs is the first qubit in the register while the each CNOT has each of the remaining qubits in the register as its target qubit. Args: *q (int): Ids of the qubits which the gate acts on. The first id is considered to be the control qubit. """ def __init__(self, *q: int): if len(q) < 2: raise_error( ValueError, "``FanOut`` gate must be applied to at least two qubits." ) super().__init__() self.name = "fanout" self.draw_label = "X" self.control_qubits = (q[0],) self.target_qubits = q[1:] self.init_args = [*q] self.unitary = True @property def clifford(self) -> bool: return True
[docs]class GeneralizedRBS(ParametrizedGate): """The generalized (complex) Reconfigurable Beam Splitter gate (:math:`\\text{gRBS}`). Given a register called ``qubits_in`` containing :math:`m` qubits and a register named ``qubits_out`` containing :math:`m'` qubits, the :math:`\\text{gRBS}` is a :math:`(m + m')`-qubit gate that has the following matrix representation: .. math:: \\begin{pmatrix} I & & & & \\\\ & e^{-i\\phi}\\cos\\theta & & e^{-i\\phi}\\sin\\theta & \\\\ & & I' & & \\\\ & -e^{i\\phi}\\sin\\theta & & e^{i\\phi}\\cos\\theta & \\\\ & & & & I\\\\ \\end{pmatrix} \\,\\, , where :math:`I` and :math:`I'` are, respectively, identity matrices of size :math:`2^{m} - 1` and :math:`2^{m}(2^{m'} - 2)`. This unitary matrix is also known as a `Givens rotation <https://en.wikipedia.org/wiki/Givens_rotation>`_. References: 1. R. M. S. Farias, T. O. Maciel, G. Camilo, R. Lin, S. Ramos-Calderer, and L. Aolita, *Quantum encoder for fixed-Hamming-weight subspaces*, `Phys. Rev. Applied 23, 044014 (2025) <https://doi.org/10.1103/PhysRevApplied.23.044014>`_ Args: qubits_in (tuple or list): ids of "input" qubits. qubits_out (tuple or list): ids of "output" qubits. theta (float): the rotation angle. phi (float): the phase angle. Defaults to :math:`0.0`. trainable (bool): whether gate parameter can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. """ def __init__( self, qubits_in: Union[Tuple[int], List[int]], qubits_out: Union[Tuple[int], List[int]], theta: float, phi: float = 0.0, trainable: bool = True, ): super().__init__(trainable) self.name = "grbs" self.draw_label = "gRBS" self.target_qubits = tuple(qubits_in) + tuple(qubits_out) self.unitary = True self.parameter_names = ["theta", "phi"] self.parameters = theta, phi self.nparams = 2 self.init_args = [qubits_in, qubits_out] self.init_kwargs = {"theta": theta, "phi": phi, "trainable": trainable} @property def hamming_weight(self) -> bool: return len(self.init_args[0]) == len(self.init_args[1])
[docs] def on_qubits(self, qubit_map: dict) -> Gate: qubits_in = tuple(qubit_map.get(q) for q in self.init_args[0]) qubits_out = tuple(qubit_map.get(q) for q in self.init_args[1]) gate = self.__class__(qubits_in, qubits_out, **self.init_kwargs) if self.is_controlled_by: controls = (qubit_map.get(q) for q in self.control_qubits) gate = gate.controlled_by(*controls) return gate
[docs]class Unitary(ParametrizedGate): """Arbitrary unitary gate. Args: unitary: Unitary matrix as a tensor supported by the backend. *q (int): Qubit id numbers that the gate acts on. trainable (bool): whether gate parameters can be updated using :meth:`qibo.models.circuit.Circuit.set_parameters`. Defaults to ``True``. name (str): Optional name for the gate. check_unitary (bool): if ``True``, checks if ``unitary`` is an unitary operator. If ``False``, check is not performed and ``unitary`` attribute defaults to ``False``. Note that, even when the check is performed, there is no enforcement. This allows the user to create non-unitary gates. Default is ``True``. """ def __init__( self, unitary: ArrayLike, *q: int, trainable: bool = True, name: str = None, check_unitary: bool = True, ): super().__init__(trainable) self.name = "Unitary" if name is None else name self.draw_label = "U" self.target_qubits = tuple(q) self._hamming_weight = False self._clifford = False # TODO: Check that given ``unitary`` has proper shape? self.parameter_names = "u" self._parameters = (unitary,) self.nparams = 4 ** len(self.target_qubits) self.init_args = [unitary] + list(q) self.init_kwargs = { "name": name, "check_unitary": check_unitary, "trainable": trainable, } if check_unitary: engine = _check_engine(unitary) product = engine.conj(unitary).T @ unitary diagonals = all(engine.abs(1 - engine.diag(product)) < PRECISION_TOL) off_diagonals = bool( engine.all( engine.abs(product - engine.diag(engine.diag(product))) < PRECISION_TOL ) ) self.unitary = bool(diagonals and off_diagonals) del diagonals, off_diagonals, product @Gate.parameters.setter def parameters(self, x: ArrayLike) -> None: shape = self.parameters[0].shape engine = _check_engine(x) # Reshape doesn't accept a tuple if engine is pytorch. if isinstance(x, tuple): x = x[0] self._parameters = (engine.reshape(x, shape),) for gate in self.device_gates: # pragma: no cover gate.parameters = x @property def clifford(self) -> bool: return self._clifford @clifford.setter def clifford(self, value) -> None: self._clifford = value @property def hamming_weight(self) -> bool: return self._hamming_weight @hamming_weight.setter def hamming_weight(self, value) -> None: self._hamming_weight = value
[docs] def on_qubits(self, qubit_map: dict) -> Gate: args = [self.init_args[0]] args.extend(qubit_map.get(qubit) for qubit in self.target_qubits) gate = self.__class__(*args, **self.init_kwargs) if self.is_controlled_by: controls = (qubit_map.get(qubit) for qubit in self.control_qubits) gate = gate.controlled_by(*controls) gate.parameters = self.parameters return gate
def _dagger(self) -> Gate: engine = _check_engine(self.parameters[0]) ud = engine.conj(self.parameters[0].T) return self.__class__(ud, *self.target_qubits, **self.init_kwargs)
def _check_engine(array: ArrayLike): """Check if the array is a numpy or torch tensor and return the corresponding library.""" if (array.__class__.__name__ == "Tensor") or ( isinstance(array, tuple) and array[0].__class__.__name__ == "Tensor" ): import torch # pylint: disable=C0415 return torch return np def _is_clifford_given_angle(angle: Union[float, int], precision_tol: float = 1e-8): """Helper function to update Clifford boolean condition according to the given angle ``angle``.""" return isinstance(angle, (float, int)) and bool( angle % (math.pi / 2) < precision_tol ) def _is_hamming_weight_given_angle(angle: Union[float, int], target: float = 2 * np.pi): """Helper function to update Hamming weight boolean condition according to the given angles ``angle`` and ``target``.""" return isinstance(angle, (float, int)) and (angle % target).is_integer()