code: def _contract_qjac_with_cjac(qjac, cjac, tape):
      """Contract a quantum Jacobian with a classical preprocessing Jacobian.
      Essentially, this function computes the generalized version of
      ``tensordot(qjac, cjac)`` over the tape parameter axis, adapted to the new
      return type system. This function takes the measurement shapes and different
      QNode arguments into account.
      """
      num_measurements = len(tape.measurements)
      has_partitioned_shots = tape.shots.has_partitioned_shots
  
      if isinstance(qjac, tuple) and len(qjac) == 1:
          qjac = qjac[0]
  
      if isinstance(cjac, tuple) and len(cjac) == 1:
          cjac = cjac[0]
  
      cjac_is_tuple = isinstance(cjac, tuple)
      if not cjac_is_tuple:
          is_square = cjac.ndim == 2 and cjac.shape[0] == cjac.shape[1]
  
          if not qml.math.is_abstract(cjac) and (
              is_square
              and qml.math.allclose(cjac, qml.numpy.eye(cjac.shape[0]))
          ):
              # Classical Jacobian is the identity. No classical processing is present in the QNode
              return qjac
  
      multi_meas = num_measurements > 1
  
      if cjac_is_tuple:
          multi_params = True
      else:
          _qjac = qjac
          if multi_meas:
              _qjac = _qjac[0]
          if has_partitioned_shots:
              _qjac = _qjac[0]
          multi_params = isinstance(_qjac, tuple)
  
      tdot = partial(qml.math.tensordot, axes=[[0], [0]])
  
      if not multi_params:
          # Without dimension (e.g. expval) or with dimension (e.g. probs)
          def _reshape(x):
              return qml.math.reshape(
                  x, (1,) if x.shape == () else (1, -1)
              )
  
          if not (multi_meas or has_partitioned_shots):
              # Single parameter, single measurements
              return tdot(_reshape(qjac), cjac)
  
          if not (multi_meas and has_partitioned_shots):
              return tuple(tdot(_reshape(q), cjac) for q in qjac)
  
          # Single parameter, multiple measurements
          return tuple(
              tuple(tdot(_reshape(_q), cjac) for _q in q) for q in qjac
          )
  
      if not multi_meas:
          # Multiple parameters, single measurement
          qjac = qml.math.stack(qjac)
          if not cjac_is_tuple:
              return tdot(qjac, qml.math.stack(cjac))
          return tuple(tdot(qjac, c) for c in cjac if c is not None)
  
      # Multiple parameters, multiple measurements
      if not cjac_is_tuple:
          return tuple(
              tdot(qml.math.stack(q), qml.math.stack(cjac))
              for q in qjac
          )
      return tuple(
          tuple(
              tdot(qml.math.stack(q), c) for c in cjac if c is not None
          )
          for q in qjac
      )
  
imports: from functools import partial
  import warnings
  import pennylane
  from pennylane.transforms.tape_expand import expand_invalid_trainable
  from pennylane.measurements import MutualInfoMP, StateMP, VarianceMP, VnEntropyMP, ProbabilityMP
label_desc: Use image node_id 15 for calling a global function with example usage: _contract_qjac_with_cjac(qjac, cjac, tape) and returns: tuple, tuple, tuple, tuple, qjac, qml, tdot, tuple, tdot