code: def finite_diff(
      tape: qml.tape.QuantumTape,
      argnum=None,
      h=1e-7,
      approx_order=1,
      n=1,
      strategy="forward",
      f0=None,
      validate_params=True,
  ) -> (Sequence[qml.tape.QuantumTape], Callable):
      r"""Transform a circuit to compute the finite-difference gradient of all gate parameters with respect to its inputs.
  
      Args:
          tape (QNode or QuantumTape): quantum circuit to differentiate
          argnum (int or list[int] or None): Trainable parameter indices to differentiate
              with respect to. If not provided, the derivatives with respect to all
              trainable parameters are returned. Note that the indices are with respect to
              the list of trainable parameters.
          h (float): finite difference method step size
          approx_order (int): The approximation order of the finite-difference method to use.
          n (int): compute the :math:`n`-th derivative
          strategy (str): The strategy of the finite difference method. Must be one of
              ``"forward"``, ``"center"``, or ``"backward"``.
              For the ``"forward"`` strategy, the finite-difference shifts occur at the points
              :math:`x_0, x_0+h, x_0+2h,\dots`, where :math:`h` is some small
              stepsize. The ``"backwards"`` strategy is similar, but in
              reverse: :math:`x_0, x_0-h, x_0-2h, \dots`. Finally, the
              ``"center"`` strategy results in shifts symmetric around the
              unshifted point: :math:`\dots, x_0-2h, x_0-h, x_0, x_0+h, x_0+2h,\dots`.
          f0 (tensor_like[float] or None): Output of the evaluated input tape. If provided,
              and the gradient recipe contains an unshifted term, this value is used,
              saving a quantum evaluation.
          validate_params (bool): Whether to validate the tape parameters or not. If ``True``,
              the ``Operation.grad_method`` attribute and the circuit structure will be analyzed
              to determine if the trainable parameters support the finite-difference method.
              If ``False``, the finite-difference method will be applied to all parameters.
  
      Returns:
          qnode (QNode) or tuple[List[QuantumTape], function]:
  
          The transformed circuit as described in :func:`qml.transform <pennylane.transform>`. Executing this circuit
          will provide the Jacobian in the form of a tensor, a tuple, or a nested tuple depending upon the nesting
          structure of measurements in the original circuit.
  
      **Example**
  
      This transform can be registered directly as the quantum gradient transform
      to use during autodifferentiation:
  
      >>> dev = qml.device("default.qubit", wires=2)
      >>> @qml.qnode(dev, interface="autograd", diff_method="finite-diff")
      ... def circuit(params):
      ...     qml.RX(params[0], wires=0)
      ...     qml.RY(params[1], wires=0)
      ...     qml.RX(params[2], wires=0)
      ...     return qml.expval(qml.PauliZ(0))
      >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
      >>> qml.jacobian(circuit)(params)
      array([-0.38751725, -0.18884792, -0.38355708])
  
      When differentiating QNodes with multiple measurements using Autograd or TensorFlow, the outputs of the QNode first
      need to be stacked. The reason is that those two frameworks only allow differentiating functions with array or
      tensor outputs, instead of functions that output sequences. In contrast, Jax and Torch require no additional
      post-processing.
  
      >>> import jax
      >>> dev = qml.device("default.qubit", wires=2)
      >>> @qml.qnode(dev, interface="jax", diff_method="finite-diff")
      ... def circuit(params):
      ...     qml.RX(params[0], wires=0)
      ...     qml.RY(params[1], wires=0)
      ...     qml.RX(params[2], wires=0)
      ...     return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(0))
      >>> params = jax.numpy.array([0.1, 0.2, 0.3])
      >>> jax.jacobian(circuit)(params)
      (Array([-0.38751727, -0.18884793, -0.3835571 ], dtype=float32),
      Array([0.6991687 , 0.34072432, 0.6920237 ], dtype=float32))
  
  
      .. details::
          :title: Usage Details
  
          This gradient transform can be applied directly to :class:`QNode <pennylane.QNode>`
          objects. However, for performance reasons, we recommend providing the gradient transform
          as the ``diff_method`` argument of the QNode decorator, and differentiating with your
          preferred machine learning framework.
  
          >>> @qml.qnode(dev)
          ... def circuit(params):
          ...     qml.RX(params[0], wires=0)
          ...     qml.RY(params[1], wires=0)
          ...     qml.RX(params[2], wires=0)
          ...     return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(0))
          >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
          >>> qml.gradients.finite_diff(circuit)(params)
          ((tensor(-0.38751724, requires_grad=True),
            tensor(-0.18884792, requires_grad=True),
            tensor(-0.38355709, requires_grad=True)),
           (tensor(0.69916868, requires_grad=True),
            tensor(0.34072432, requires_grad=True),
            tensor(0.69202366, requires_grad=True)))
  
          This quantum gradient transform can also be applied to low-level
          :class:`~.QuantumTape` objects. This will result in no implicit quantum
          device evaluation. Instead, the processed tapes, and post-processing
          function, which together define the gradient are directly returned:
  
          >>> ops = [qml.RX(p, wires=0) for p in params]
          >>> measurements = [qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(0))]
          >>> tape = qml.tape.QuantumTape(ops, measurements)
          >>> gradient_tapes, fn = qml.gradients.finite_diff(tape)
          >>> gradient_tapes
          [<QuantumTape: wires=[0], params=3>,
           <QuantumTape: wires=[0], params=3>,
           <QuantumTape: wires=[0], params=3>,
           <QuantumTape: wires=[0], params=3>]
  
          This can be useful if the underlying circuits representing the gradient
          computation need to be analyzed.
  
          Note that ``argnum`` refers to the index of a parameter within the list of trainable
          parameters. For example, if we have:
  
          >>> tape = qml.tape.QuantumScript(
          ...     [qml.RX(1.2, wires=0), qml.RY(2.3, wires=0), qml.RZ(3.4, wires=0)],
          ...     [qml.expval(qml.PauliZ(0))],
          ...     trainable_params = [1, 2]
          ... )
          >>> qml.gradients.finite_diff(tape, argnum=1)
  
          The code above will differentiate the third parameter rather than the second.
  
          The output tapes can then be evaluated and post-processed to retrieve the gradient:
  
          >>> dev = qml.device("default.qubit", wires=2)
          >>> fn(qml.execute(gradient_tapes, dev, None))
          ((tensor(-0.56464251, requires_grad=True),
           tensor(-0.56464251, requires_grad=True),
           tensor(-0.56464251, requires_grad=True)),
          (tensor(0.93203912, requires_grad=True),
           tensor(0.93203912, requires_grad=True),
           tensor(0.93203912, requires_grad=True)))
  
          This gradient transform is compatible with devices that use shot vectors for execution.
  
          >>> shots = (10, 100, 1000)
          >>> dev = qml.device("default.qubit", wires=2, shots=shots)
          >>> @qml.qnode(dev)
          ... def circuit(params):
          ...     qml.RX(params[0], wires=0)
          ...     qml.RY(params[1], wires=0)
          ...     qml.RX(params[2], wires=0)
          ...     return qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(0))
          >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
          >>> qml.gradients.finite_diff(circuit, h=10e-2)(params)
          (((array(-2.), array(-2.), array(0.)), (array(3.6), array(3.6), array(0.))),
           ((array(1.), array(0.4), array(1.)),
            (array(-1.62), array(-0.624), array(-1.62))),
           ((array(-0.48), array(-0.34), array(-0.46)),
            (array(0.84288), array(0.6018), array(0.80868))))
  
          The outermost tuple contains results corresponding to each element of the shot vector.
      """
  
      transform_name = "finite difference"
      assert_no_tape_batching(tape, transform_name)
  
      if any(
          qml.math.get_dtype_name(p) == "float32"
          for p in tape.get_parameters()
      ):
          warn(
              "Finite differences with float32 detected. Answers may be inaccurate. float64 is recommended.",
              UserWarning,
          )
      number_parameters = len(tape.trainable_params)
      number_measurements = len(tape.measurements)
      if argnum is None and not tape.trainable_params:
          return _no_trainable_grad(tape)
  
      trainable_params = choose_trainable_params(tape, argnum)
      diff_methods = (
          find_and_validate_gradient_methods(
              tape, "numeric", trainable_params
          )
          if validate_params
          else {idx: "F" for idx in trainable_params}
      )
  
      if all(g == "0" for g in diff_methods.values()):
          return _all_zero_grad(tape)
  
      gradient_tapes = []
      shapes = []
      c0 = None
  
      coeffs, shifts = finite_diff_coeffs(
          n=n, approx_order=approx_order, strategy=strategy
      )
  
      if 0 in shifts:
          # Finite difference formula includes a term with zero shift.
  
          if f0 is None:
              # Ensure that the unshifted tape is appended
              # to the gradient tapes, if not already.
              gradient_tapes.append(tape)
  
          # Store the unshifted coefficient. We know that
          # it will always be the first coefficient due to processing.
          c0 = coeffs[0]
          shifts = shifts[1:]
          coeffs = coeffs[1:]
  
      for i, _ in enumerate(tape.trainable_params):
          if i not in diff_methods or diff_methods[i] == "0":
              # parameter has zero gradient
              shapes.append(0)
              continue
  
          g_tapes = generate_shifted_tapes(tape, i, shifts * h)
          gradient_tapes.extend(g_tapes)
          shapes.append(len(g_tapes))
  
      def _single_shot_batch_result(results):
          """Auxiliary function for post-processing one batch of results corresponding to finite shots or a single
          component of a shot vector"""
          grads = []
          start = 1 if c0 is not None and f0 is None else 0
          r0 = f0 or results[0]
  
          output_dims = []
          # TODO: Update shape for CV variables
          for m in tape.measurements:
              if isinstance(m, ProbabilityMP):
                  output_dims.append(2 ** len(m.wires))
              else:
                  output_dims.append(1)
  
          for s in shapes:
              if s == 0:
                  # parameter has zero gradient
                  if not isinstance(results[0], tuple):
                      g = qml.math.zeros_like(results[0])
                  else:
                      g = []
                      for i in output_dims:
                          zero = qml.math.squeeze(qml.math.zeros(i))
                          g.append(zero)
  
                  grads.append(g)
                  continue
  
              res = results[start : start + s]
              start = start + s
  
              # compute the linear combination of results
              # and coefficients
  
              pre_grads = []
  
              if number_measurements == 1:
                  res = qml.math.stack(res)
                  c = qml.math.convert_like(coeffs, res)
                  lin_comb = qml.math.tensordot(res, c, [[0], [0]])
                  pre_grads.append(lin_comb)
              else:
                  for i in range(number_measurements):
                      r = qml.math.stack([r[i] for r in res])
                      c = qml.math.convert_like(coeffs, r)
                      lin_comb = qml.math.tensordot(r, c, [[0], [0]])
                      pre_grads.append(lin_comb)
  
              # Add on the unshifted term
              if c0 is not None:
                  if number_measurements == 1:
                      c = qml.math.convert_like(c0, r0)
                      pre_grads = [pre_grads[0] + r0 * c]
                  else:
                      for i in range(number_measurements):
                          r_i = r0[i]
                          c = qml.math.convert_like(c0, r_i)
                          pre_grads[i] = pre_grads[i] + r_i * c
  
              coeff_div = qml.math.cast_like(
                  qml.math.convert_like(1 / h**n, pre_grads[0]),
                  pre_grads[0],
              )
  
              if len(tape.measurements) > 1:
                  pre_grads = tuple(
                      qml.math.convert_like(i * coeff_div, coeff_div)
                      for i in pre_grads
                  )
              else:
                  pre_grads = qml.math.convert_like(
                      pre_grads[0] * coeff_div, coeff_div
                  )
  
              grads.append(pre_grads)
          # Single measurement
          if number_measurements == 1:
              if number_parameters == 1:
                  return grads[0]
              return tuple(grads)
  
          # Reordering to match the right shape for multiple measurements
          grads_reorder = [
              [0] * number_parameters
              for _ in range(len(tape.measurements))
          ]
          for i in range(number_measurements):
              for j in range(number_parameters):
                  grads_reorder[i][j] = grads[j][i]
  
          # To tuple
          if number_parameters == 1:
              return tuple(elem[0] for elem in grads_reorder)
          return tuple(tuple(elem) for elem in grads_reorder)
  
      processing_fn = functools.partial(
          _processing_fn,
          shots=tape.shots,
          single_shot_batch_fn=_single_shot_batch_result,
      )
  
      return gradient_tapes, processing_fn
  
file_path: pennylane/pennylane/gradients/finite_difference.py
imports: from typing import Sequence, Callable
  import functools
  from functools import partial
  from warnings import warn
  import numpy
  from scipy.special import factorial
  from scipy.linalg import solve
  import pennylane
  from pennylane.measurements import ProbabilityMP
  from pennylane import transform
  from pennylane.transforms.tape_expand import expand_invalid_trainable
  from pennylane.gradients.gradient_transform import _contract_qjac_with_cjac
  from .general_shift_rules import generate_shifted_tapes
  from .gradient_transform import _all_zero_grad, assert_no_tape_batching, choose_trainable_params, find_and_validate_gradient_methods, _no_trainable_grad
label_desc: Use image node_id 4 for calling a global function with example usage: finite_diff(tape, argnum, h, approx_order, n, strategy, f0, validate_params) and returns: gradient_tapes,  processing_fn, _no_trainable_grad, _all_zero_grad, tuple, tuple, tuple, grads