"""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()