content: # Required for Sphinx to follow autodoc_type_aliases
  from __future__ import annotations
  
  from itertools import product
  from functools import partial, reduce
  from operator import mul
  
  import numpy as np
  import scipy.sparse as sp
  from . import Qobj, qeye, sigmax, fock_dm, qdiags, qeye_like
  from .dimensions import Dimensions
  from .. import settings
  from . import data as _data
  from ..typing import LayerType
  
  
  __all__ = [
      "rx",
      "ry",
      "rz",
      "sqrtnot",
      "snot",
      "phasegate",
      "qrot",
      "cy_gate",
      "cz_gate",
      "s_gate",
      "t_gate",
      "cs_gate",
      "ct_gate",
      "cphase",
      "cnot",
      "csign",
      "berkeley",
      "swapalpha",
      "swap",
      "iswap",
      "sqrtswap",
      "sqrtiswap",
      "fredkin",
      "molmer_sorensen",
      "toffoli",
      "hadamard_transform",
      "qubit_clifford_group",
      "globalphase",
  ]
  
  
  _DIMS_2_QB = Dimensions([[2, 2], [2, 2]])
  _DIMS_3_QB = Dimensions([[2, 2, 2], [2, 2, 2]])
  
  
  def cy_gate(*, dtype: LayerType = None) -> Qobj:
      """Controlled Y gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing the rotation.
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, -1j], [0, 0, 1j, 0]],
          dims=_DIMS_2_QB,
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  def cz_gate(*, dtype: LayerType = None) -> Qobj:
      """Controlled Z gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing the rotation.
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([1, 1, 1, -1], dims=_DIMS_2_QB, dtype=dtype)
  
  
  def s_gate(*, dtype: LayerType = None) -> Qobj:
      """Single-qubit rotation also called Phase gate or the Z90 gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing
          a 90 degree rotation around the z-axis.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([1, 1j], dtype=dtype)
  
  
  def cs_gate(*, dtype: LayerType = None) -> Qobj:
      """Controlled S gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing the rotation.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([1, 1, 1, 1j], dims=_DIMS_2_QB, dtype=dtype)
  
  
  def t_gate(*, dtype: LayerType = None) -> Qobj:
      """Single-qubit rotation related to the S gate by the relationship S=T*T.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing a phase shift of pi/4.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([1, np.exp(1j * np.pi / 4)], dtype=dtype)
  
  
  def ct_gate(*, dtype: LayerType = None) -> Qobj:
      """Controlled T gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : :class:`.Qobj`
          Quantum object for operator describing the rotation.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags(
          [1, 1, 1, np.exp(1j * np.pi / 4)],
          dims=_DIMS_2_QB,
          dtype=dtype,
      )
  
  
  def rx(phi: float, *, dtype: LayerType = None) -> Qobj:
      """Single-qubit rotation for operator sigmax with angle phi.
  
      Parameters
      ----------
      phi : float
          Rotation angle
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : qobj
          Quantum object for operator describing the rotation.
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      return Qobj(
          [
              [np.cos(phi / 2), -1j * np.sin(phi / 2)],
              [-1j * np.sin(phi / 2), np.cos(phi / 2)],
          ],
          isherm=(phi % (2 * np.pi) <= settings.core["atol"]),
          isunitary=True,
      ).to(dtype)
  
  
  def ry(phi: float, *, dtype: LayerType = None) -> Qobj:
      """Single-qubit rotation for operator sigmay with angle phi.
  
      Parameters
      ----------
      phi : float
          Rotation angle
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : qobj
          Quantum object for operator describing the rotation.
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      return Qobj(
          [
              [np.cos(phi / 2), -np.sin(phi / 2)],
              [np.sin(phi / 2), np.cos(phi / 2)],
          ],
          isherm=(phi % (2 * np.pi) <= settings.core["atol"]),
          isunitary=True,
      ).to(dtype)
  
  
  def rz(phi: float, *, dtype: LayerType = None) -> Qobj:
      """Single-qubit rotation for operator sigmaz with angle phi.
  
      Parameters
      ----------
      phi : float
          Rotation angle
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : qobj
          Quantum object for operator describing the rotation.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([np.exp(-1j * phi / 2), np.exp(1j * phi / 2)], dtype=dtype)
  
  
  def sqrtnot(*, dtype: LayerType = None) -> Qobj:
      """Single-qubit square root NOT gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      result : qobj
          Quantum object for operator describing the square root NOT gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      return Qobj(
          [[0.5 + 0.5j, 0.5 - 0.5j], [0.5 - 0.5j, 0.5 + 0.5j]],
          isherm=False,
          isunitary=True,
      ).to(dtype)
  
  
  def snot(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the SNOT (Hadamard) gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      snot_gate : qobj
          Quantum object representation of SNOT gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [[np.sqrt(0.5), np.sqrt(0.5)], [np.sqrt(0.5), -np.sqrt(0.5)]],
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  def phasegate(theta: float, *, dtype: LayerType = None) -> Qobj:
      """
      Returns quantum object representing the phase shift gate.
  
      Parameters
      ----------
      theta : float
          Phase rotation angle.
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      phase_gate : qobj
          Quantum object representation of phase shift gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags([1, np.exp(1.0j * theta)], dtype=dtype)
  
  
  def qrot(theta: float, phi: float, *, dtype: LayerType = None) -> Qobj:
      """
      Single qubit rotation driving by Rabi oscillation with 0 detune.
  
      Parameters
      ----------
      phi : float
          The inital phase of the rabi pulse.
      theta : float
          The duration of the rabi pulse.
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      qrot_gate : :class:`.Qobj`
          Quantum object representation of physical qubit rotation under
          a rabi pulse.
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      return Qobj(
          [
              [np.cos(theta / 2), -1j * np.exp(-1j * phi) * np.sin(theta / 2)],
              [-1j * np.exp(1j * phi) * np.sin(theta / 2), np.cos(theta / 2)],
          ],
          isherm=(theta % (2 * np.pi) <= settings.core["atol"]),
          isunitary=True,
      ).to(dtype)
  
  
  #
  # 2 Qubit Gates
  #
  
  
  def cphase(theta: float, *, dtype: LayerType = None) -> Qobj:
      """
      Returns quantum object representing the controlled phase shift gate.
  
      Parameters
      ----------
      theta : float
          Phase rotation angle.
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      U : qobj
          Quantum object representation of controlled phase gate.
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qdiags(
          [1, 1, 1, np.exp(1.0j * theta)], dims=_DIMS_2_QB, dtype=dtype
      )
  
  
  def cnot(*, dtype: LayerType = None) -> Qobj:
      """
      Quantum object representing the CNOT gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      cnot_gate : qobj
          Quantum object representation of CNOT gate
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]],
          dims=_DIMS_2_QB,
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  def csign(*, dtype: LayerType = None) -> Qobj:
      """
      Quantum object representing the CSIGN gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      csign_gate : qobj
          Quantum object representation of CSIGN gate
  
      """
      return cz_gate(dtype=dtype)
  
  
  def berkeley(*, dtype: LayerType = None) -> Qobj:
      """
      Quantum object representing the Berkeley gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      berkeley_gate : qobj
          Quantum object representation of Berkeley gate
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      return Qobj(
          [
              [np.cos(np.pi / 8), 0, 0, 1.0j * np.sin(np.pi / 8)],
              [0, np.cos(3 * np.pi / 8), 1.0j * np.sin(3 * np.pi / 8), 0],
              [0, 1.0j * np.sin(3 * np.pi / 8), np.cos(3 * np.pi / 8), 0],
              [1.0j * np.sin(np.pi / 8), 0, 0, np.cos(np.pi / 8)],
          ],
          dims=_DIMS_2_QB,
          isherm=False,
          isunitary=True,
      ).to(dtype)
  
  
  def swapalpha(alpha: float, *, dtype: LayerType = None) -> Qobj:
      """
      Quantum object representing the SWAPalpha gate.
  
      Parameters
      ----------
      alpha : float
          Angle of the SWAPalpha gate.
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      swapalpha_gate : qobj
          Quantum object representation of SWAPalpha gate
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      phase = np.exp(1.0j * np.pi * alpha)
      return Qobj(
          [
              [1, 0, 0, 0],
              [0, 0.5 * (1 + phase), 0.5 * (1 - phase), 0],
              [0, 0.5 * (1 - phase), 0.5 * (1 + phase), 0],
              [0, 0, 0, 1],
          ],
          dims=_DIMS_2_QB,
          isherm=(np.abs(phase.imag) <= settings.core["atol"]),
          isunitary=True,
      ).to(dtype)
  
  
  def swap(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the SWAP gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      swap_gate : qobj
          Quantum object representation of SWAP gate
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]],
          dims=_DIMS_2_QB,
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  def iswap(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the iSWAP gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      iswap_gate : qobj
          Quantum object representation of iSWAP gate
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]],
          dims=_DIMS_2_QB,
          isherm=False,
          isunitary=True,
      ).to(dtype)
  
  
  def sqrtswap(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the square root SWAP gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      sqrtswap_gate : qobj
          Quantum object representation of square root SWAP gate
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          np.array(
              [
                  [1, 0, 0, 0],
                  [0, 0.5 + 0.5j, 0.5 - 0.5j, 0],
                  [0, 0.5 - 0.5j, 0.5 + 0.5j, 0],
                  [0, 0, 0, 1],
              ]
          ),
          dims=_DIMS_2_QB,
          isherm=False,
          isunitary=True,
      ).to(dtype)
  
  
  def sqrtiswap(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the square root iSWAP gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      sqrtiswap_gate : qobj
          Quantum object representation of square root iSWAP gate
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          np.array(
              [
                  [1, 0, 0, 0],
                  [0, 1 / np.sqrt(2), 1j / np.sqrt(2), 0],
                  [0, 1j / np.sqrt(2), 1 / np.sqrt(2), 0],
                  [0, 0, 0, 1],
              ]
          ),
          dims=_DIMS_2_QB,
          isherm=False,
          isunitary=True,
      ).to(dtype)
  
  
  def molmer_sorensen(theta: float, *, dtype: LayerType = None) -> Qobj:
      """
      Quantum object of a Mølmer–Sørensen gate.
  
      Parameters
      ----------
      theta: float
          The duration of the interaction pulse.
      target: int
          The indices of the target qubits.
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      molmer_sorensen_gate: :class:`.Qobj`
          Quantum object representation of the Mølmer–Sørensen gate.
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [
              [np.cos(theta / 2.0), 0, 0, -1.0j * np.sin(theta / 2.0)],
              [0, np.cos(theta / 2.0), -1.0j * np.sin(theta / 2.0), 0],
              [0, -1.0j * np.sin(theta / 2.0), np.cos(theta / 2.0), 0],
              [-1.0j * np.sin(theta / 2.0), 0, 0, np.cos(theta / 2.0)],
          ],
          dims=_DIMS_2_QB,
          isherm=(theta % (2 * np.pi) <= settings.core["atol"]),
          isunitary=True,
      ).to(dtype)
  
  
  #
  # 3 Qubit Gates
  #
  
  
  def fredkin(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the Fredkin gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      fredkin_gate : qobj
          Quantum object representation of Fredkin gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [
              [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, 0],
              [0, 0, 0, 0, 0, 0, 0, 1],
          ],
          dims=_DIMS_3_QB,
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  def toffoli(*, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the Toffoli gate.
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      toff_gate : qobj
          Quantum object representation of Toffoli gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return Qobj(
          [
              [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],
          ],
          dims=_DIMS_3_QB,
          isherm=True,
          isunitary=True,
      ).to(dtype)
  
  
  #
  # Miscellaneous Gates
  #
  
  
  def globalphase(theta: float, N: int = 1, *, dtype: LayerType = None) -> Qobj:
      """
      Returns quantum object representing the global phase shift gate.
  
      Parameters
      ----------
      theta : float
          Phase rotation angle.
  
      N : int:
          Number of qubits
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      phase_gate : qobj
          Quantum object representation of global phase shift gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "sparse")
      return qeye([2] * N, dtype=dtype) * np.exp(1.0j * theta)
  
  
  #
  # Operation on Gates
  #
  
  
  def _hamming_distance(x):
      """
      Calculate the bit-wise Hamming distance of x from 0: That is, the number
      1s in the integer x.
      """
      tot = 0
      while x:
          tot += 1
          x &= x - 1
      return tot
  
  
  def hadamard_transform(N: int = 1, *, dtype: LayerType = None) -> Qobj:
      """Quantum object representing the N-qubit Hadamard gate.
  
      Parameters
      ----------
      N : int:
          Number of qubits
  
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      q : qobj
          Quantum object representation of the N-qubit Hadamard gate.
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
      data = 2 ** (-N / 2) * np.array(
          [
              [(-1) ** _hamming_distance(i & j) for i in range(2**N)]
              for j in range(2**N)
          ]
      )
  
      return Qobj(data, dims=[[2] * N, [2] * N], isherm=True, isunitary=True).to(
          dtype
      )
  
  
  def _powers(op, N):
      """
      Generator that yields powers of an operator `op`,
      through to `N`.
      """
      acc = qeye_like(op)
      yield acc
  
      for _ in range(N - 1):
          acc *= op
          yield acc
  
  
  def qubit_clifford_group(*, dtype: LayerType = None) -> list[Qobj]:
      """
      Generates the Clifford group on a single qubit,
      using the presentation of the group given by Ross and Selinger
      (http://www.mathstat.dal.ca/~selinger/newsynth/).
  
      Parameters
      ----------
      dtype : str or type, [keyword only] [optional]
          Storage representation. Any data-layer known to `qutip.data.to` is
          accepted.
  
      Returns
      -------
      op : list of Qobj
          Clifford operators, represented as Qobj instances.
  
      """
      dtype = _data._parse_default_dtype(dtype, "dense")
  
      # The Ross-Selinger presentation of the single-qubit Clifford
      # group expresses each element in the form C_{ijk} = E^i X^j S^k
      # for gates E, X and S, and for i in range(3), j in range(2) and
      # k in range(4).
      #
      # We start by defining these gates. E is defined in terms of H,
      # \omega and S, so we define \omega and H first.
      w = np.exp(1j * 2 * np.pi / 8)
      H = snot()
  
      X = sigmax()
      S = phasegate(np.pi / 2)
      E = H @ (S**3) * w**3
  
      # partial(reduce, mul) returns a function that takes products
      # of its argument, by analogy to sum. Note that by analogy,
      # sum can be written as partial(reduce, add).
  
      # product(...) yields the Cartesian product of its arguments.
      # Here, each element is a tuple (E**i, X**j, S**k) such that
      # partial(reduce, mul) acting on the tuple yields E**i * X**j * S**k.
      gates = [
          op.to(dtype)
          for op in map(
              partial(reduce, mul),
              product(_powers(E, 3), _powers(X, 2), _powers(S, 4)),
          )
      ]
      for gate in gates:
          gate.isherm
          gate._isunitary = True
      return gates
  