code: def adjoint_vjp(
      tape: QuantumTape, cotangents: Tuple[Number], state=None
  ):
      """The vector jacobian product used in reverse-mode differentiation.
  
      Implements the adjoint method outlined in
      `Jones and Gacon <https://arxiv.org/abs/2009.02823>`__ to differentiate an input tape.
  
      After a forward pass, the circuit is reversed by iteratively applying adjoint
      gates to scan backwards through the circuit.
  
      .. note::
  
          The adjoint differentiation method has the following restrictions:
  
          * Cannot differentiate with respect to observables.
  
          * Observable being measured must have a matrix.
  
      Args:
          tape (QuantumTape): circuit that the function takes the gradient of
          cotangents (Tuple[Number]): gradient vector for output parameters. For computing
              the full Jacobian, the cotangents can be batched to vectorize the computation.
              In this case, the cotangents can have the following shapes. ``batch_size``
              below refers to the number of entries in the Jacobian:
  
              * For a state measurement, cotangents must have shape ``(batch_size, 2 ** n_wires)``.
              * For ``n`` expectation values, the cotangents must have shape ``(n, batch_size)``.
                If ``n = 1``, then the shape must be ``(batch_size,)``.
  
          state (TensorLike): the final state of the circuit; if not provided,
              the final state will be computed by executing the tape
  
      Returns:
          Tuple[Number]: gradient vector for input parameters
      """
      # See ``adjoint_jacobian.md`` to more information on the algorithm.
  
      # Map wires if custom wire labels used)
      if set(tape.wires) != set(range(tape.num_wires)):
          wire_map = {w: i for i, w in enumerate(tape.wires)}
          tapes, fn = qml.map_wires(tape, wire_map)
          tape = fn(tapes)
  
      ket = state if state is not None else get_final_state(tape)[0]
  
      bras, batch_size, null_batch_indices = _get_vjp_bras(
          tape, cotangents, ket
      )
      if bras is None:
          # Cotangents are zeros
          if batch_size is None:
              return tuple(0.0 for _ in tape.trainable_params)
          return tuple(
              np.zeros((len(tape.trainable_params), batch_size))
          )
  
      if isinstance(tape.measurements[0], qml.measurements.StateMP):
  
          def real_if_expval(val):
              return val
  
      else:
  
          def real_if_expval(val):
              return np.real(val)
  
      param_number = (
          len(
              tape.get_parameters(
                  trainable_only=False, operations_only=True
              )
          )
          - 1
      )
      trainable_param_number = len(tape.trainable_params) - 1
  
      res_shape = (
          (len(tape.trainable_params),)
          if batch_size is None
          else (len(tape.trainable_params), batch_size)
      )
      cotangents_in = np.empty(
          res_shape, dtype=tape.measurements[0].numeric_type
      )
      summing_axis = (
          None if batch_size is None else tuple(range(1, np.ndim(bras)))
      )
  
      for op in reversed(tape.operations[tape.num_preps :]):
          adj_op = qml.adjoint(op)
          ket = apply_operation(adj_op, ket)
  
          if op.num_params == 1:
              if param_number in tape.trainable_params:
                  d_op_matrix = operation_derivative(op)
                  ket_temp = apply_operation(
                      qml.QubitUnitary(d_op_matrix, wires=op.wires), ket
                  )
  
                  # Pad cotangent in with zeros for batch number with zero cotangents
                  cot_in = real_if_expval(
                      np.sum(
                          np.conj(bras) * ket_temp, axis=summing_axis
                      )
                  )
                  for i in null_batch_indices:
                      cot_in = np.insert(cot_in, i, 0.0)
                  cotangents_in[trainable_param_number] = cot_in
  
                  trainable_param_number -= 1
              param_number -= 1
  
          bras = apply_operation(
              adj_op, bras, is_state_batched=bool(batch_size)
          )
  
      return tuple(cotangents_in)
  
file_path: pennylane/pennylane/devices/qubit/adjoint_jacobian.py
imports: from numbers import Number
  from typing import Tuple
  import numpy
  import pennylane
  from pennylane.operation import operation_derivative
  from pennylane.tape import QuantumTape
  from .apply_operation import apply_operation
  from .simulate import get_final_state
  from .initialize_state import create_initial_state
label_desc: Use image node_id 6 for calling a global function with example usage: adjoint_vjp(tape, cotangents, state) and returns: tuple, tuple, tuple, val, np