class_docstr: An operation representing the QDrift approximation for the complex matrix exponential
  of a given Hamiltonian.
  
  The QDrift subroutine provides a method to approximate the matrix exponential of a Hamiltonian
  expressed as a linear combination of terms which in general do not commute. For the Hamiltonian
  :math:`H = \Sigma_j h_j H_{j}`, the product formula is constructed by random sampling from the
  terms of the Hamiltonian with the probability :math:`p_j = h_j / \sum_{j} hj` as:
  
  .. math::
  
      \prod_{j}^{n} e^{i \lambda H_j \tau / n},
  
  where :math:`\tau` is time, :math:`\lambda = \sum_j |h_j|` and :math:`n` is the total number of
  terms to be sampled and added to the product. Note, the terms :math:`H_{j}` are assumed to be
  normalized such that the "impact" of each term is fully encoded in the magnitude of :math:`h_{j}`.
  
  The number of samples :math:`n` required for a given error threshold can be approximated by:
  
  .. math::
  
      n \ \approx \ \frac{2\lambda^{2}t^{2}}{\epsilon}
  
  For more details see `Phys. Rev. Lett. 123, 070503 (2019) <https://arxiv.org/abs/1811.08017>`_.
  
  Args:
      hamiltonian (Union[.Hamiltonian, .Sum]): The Hamiltonian written as a sum of operations
      time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}`
      n (int): An integer representing the number of exponentiated terms
      seed (int): The seed for the random number generator
  
  Raises:
      TypeError: The ``hamiltonian`` is not of type :class:`~.Hamiltonian`, or :class:`~.Sum`
      QuantumFunctionError: If the coefficients of ``hamiltonian`` are trainable and are used
          in a differentiable workflow.
  
  **Example**
  
  .. code-block:: python3
  
      coeffs = [0.25, 0.75]
      ops = [qml.PauliX(0), qml.PauliZ(0)]
      H = qml.dot(coeffs, ops)
  
      dev = qml.device("default.qubit", wires=2)
      @qml.qnode(dev)
      def my_circ():
          # Prepare some state
          qml.Hadamard(0)
  
          # Evolve according to H
          qml.QDrift(H, time=1.2, n=10, seed=10)
  
          # Measure some quantity
          return qml.probs()
  
  >>> my_circ()
  array([0.65379493, 0.        , 0.34620507, 0.        ])
  
  
  .. details::
      :title: Usage Details
  
      We currently **Do NOT** support computing gradients with respect to the
      coefficients of the input Hamiltonian. We can however compute the gradient
      with respect to the evolution time:
  
      .. code-block:: python3
  
          dev = qml.device("default.qubit", wires=2)
  
          @qml.qnode(dev)
          def my_circ(time):
              # Prepare H:
              H = qml.dot([0.2, -0.1], [qml.PauliY(0), qml.PauliZ(1)])
  
              # Prepare some state
              qml.Hadamard(0)
  
              # Evolve according to H
              qml.QDrift(H, time, n=10, seed=10)
  
              # Measure some quantity
              return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
  
  
      >>> time = np.array(1.23)
      >>> print(qml.grad(my_circ)(time))
      0.27980654844422853
  
      The error in the approximation of time evolution with the QDrift protocol is
      directly related to the number of samples used in the product. We provide a
      method to upper-bound the error:
  
      >>> H = qml.dot([0.25, 0.75], [qml.PauliX(0), qml.PauliZ(0)])
      >>> print(qml.QDrift.error(H, time=1.2, n=10))
      0.3661197552925645
docstr: Initialize the QDrift class
docstr_tok: ["Initialize","the","QDrift","class"]
code: def __init__(  # pylint: disable=too-many-arguments
      self,
      hamiltonian,
      time,
      n=1,
      seed=None,
      decomposition=None,
      id=None,
  ):
      r"""Initialize the QDrift class"""
  
      if isinstance(hamiltonian, Hamiltonian):
          coeffs, ops = hamiltonian.terms()
  
      elif isinstance(hamiltonian, Sum):
          coeffs, ops = [], []
          for op in hamiltonian:
              try:
                  coeffs.append(op.scalar)
                  ops.append(op.base)
              except AttributeError:  # coefficient is 1.0
                  coeffs.append(1.0)
                  ops.append(op)
  
      else:
          raise TypeError(
              f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum got {hamiltonian}"
          )
  
      if len(ops) < 2:
          raise ValueError(
              "There should be atleast 2 terms in the Hamiltonian. Otherwise use `qml.exp`"
          )
  
      if any(requires_grad(coeff) for coeff in coeffs):
          raise qml.QuantumFunctionError(
              "The QDrift template currently doesn't support differentiation through the "
              "coefficients of the input Hamiltonian."
          )
  
      if (
          decomposition is None
      ):  # need to do this to allow flatten and _unflatten
          unwrapped_coeffs = unwrap(coeffs)
          decomposition = _sample_decomposition(
              unwrapped_coeffs, ops, time, n=n, seed=seed
          )
  
      self._hyperparameters = {
          "n": n,
          "seed": seed,
          "base": hamiltonian,
          "decomposition": decomposition,
      }
      super().__init__(time, wires=hamiltonian.wires, id=id)
  
file_path: pennylane/pennylane/templates/subroutines/qdrift.py
imports: import pennylane
  from pennylane.operation import Operation
  from pennylane.math import requires_grad, unwrap
  from pennylane.ops import Sum, SProd, Hamiltonian
label_desc: Use image node_id 1 to create a new QDrift object from inherited base classes: Operation with example: obj = QDrift(hamiltonian, time, n, seed, decomposition, id)