class_docstr: Test for caching behaviour
class_docstr_tok: ["Test","for","caching","behaviour"]
docstr: Test that, when using parameter-shift transform,
  caching reduces the number of evaluations to their optimum
  when computing Hessians.
docstr_tok: ["Test","that",",","when","using","parameter-shift","transform",",","caching","reduces","the","number","of","evaluations","to","their","optimum","when","computing","Hessians","."]
code: def test_caching_param_shift_hessian(self, num_params, tol):
      """Test that, when using parameter-shift transform,
      caching reduces the number of evaluations to their optimum
      when computing Hessians."""
      dev = qml.device("default.qubit.legacy", wires=2)
      params = tf.Variable(
          np.arange(1, num_params + 1) / 10, dtype=tf.float64
      )
  
      N = params.shape[0]
  
      def cost(x, cache):
          with qml.queuing.AnnotatedQueue() as q:
              qml.RX(x[0], wires=[0])
              qml.RY(x[1], wires=[1])
  
              for i in range(2, num_params):
                  qml.RZ(x[i], wires=[i % 2])
  
              qml.CNOT(wires=[0, 1])
              qml.var(qml.PauliZ(0) @ qml.PauliX(1))
  
          tape = qml.tape.QuantumScript.from_queue(q)
          return execute(
              [tape],
              dev,
              gradient_fn=param_shift,
              cache=cache,
              interface="tf",
              max_diff=2,
          )[0]
  
      # No caching: number of executions is not ideal
      with tf.GradientTape() as t2:
          with tf.GradientTape() as t1:
              res = cost(params, cache=False)
          grad = t1.gradient(res, params)
      hess1 = t2.jacobian(grad, params)
  
      if num_params == 2:
          # compare to theoretical result
          x, y, *_ = params * 1.0
          expected = np.array(
              [
                  [
                      2 * np.cos(2 * x) * np.sin(y) ** 2,
                      np.sin(2 * x) * np.sin(2 * y),
                  ],
                  [
                      np.sin(2 * x) * np.sin(2 * y),
                      -2 * np.cos(x) ** 2 * np.cos(2 * y),
                  ],
              ]
          )
          assert np.allclose(expected, hess1, atol=tol, rtol=0)
  
      nonideal_runs = dev.num_executions
  
      # Use caching: number of executions is ideal
      dev._num_executions = 0
      with tf.GradientTape() as t2:
          with tf.GradientTape() as t1:
              res = cost(params, cache=True)
          grad = t1.gradient(res, params)
      hess2 = t2.jacobian(grad, params)
  
      assert np.allclose(hess1, hess2, atol=tol, rtol=0)
  
      expected_runs_ideal = 1  # forward pass
      expected_runs_ideal += 2 * N  # Jacobian
      expected_runs_ideal += N + 1  # Hessian diagonal
      expected_runs_ideal += (
          4 * N * (N - 1) // 2
      )  # Hessian off-diagonal
      assert dev.num_executions == expected_runs_ideal
      assert expected_runs_ideal < nonideal_runs
  
imports: import numpy
  import pytest
  import pennylane
  from pennylane.gradients import finite_diff, param_shift
  from pennylane import execute
label_desc: Use image node_id 4 for calling the TestCaching obj's underlying member method code with example usage: obj.test_caching_param_shift_hessian(num_params, tol) and returns: execute