class_docstr: Tests for caching behaviour
class_docstr_tok: ["Tests","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):
      """Test that, when using parameter-shift transform,
      caching reduces the number of evaluations to their optimum
      when computing Hessians."""
      dev = DefaultQubit()
      params = tf.Variable(tf.range(1, num_params + 1) / 10)
  
      N = num_params
  
      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.prod(qml.PauliZ(0), qml.PauliX(1)))
  
          tape = qml.tape.QuantumScript.from_queue(q)
          return qml.execute(
              [tape],
              dev,
              gradient_fn=qml.gradients.param_shift,
              cache=cache,
              max_diff=2,
          )[0]
  
      # No caching: number of executions is not ideal
      with qml.Tracker(dev) as tracker:
          with tf.GradientTape() as jac_tape:
              with tf.GradientTape() as grad_tape:
                  res = cost(params, cache=False)
              grad = grad_tape.gradient(res, params)
          hess1 = jac_tape.jacobian(grad, params)
  
      if num_params == 2:
          # compare to theoretical result
          x, y, *_ = params
          expected = tf.convert_to_tensor(
              [
                  [
                      2 * tf.cos(2 * x) * tf.sin(y) ** 2,
                      tf.sin(2 * x) * tf.sin(2 * y),
                  ],
                  [
                      tf.sin(2 * x) * tf.sin(2 * y),
                      -2 * tf.cos(x) ** 2 * tf.cos(2 * y),
                  ],
              ]
          )
          assert np.allclose(expected, hess1)
  
      expected_runs = 1  # forward pass
  
      # Jacobian of an involutory observable:
      # ------------------------------------
      #
      # 2 * N execs: evaluate the analytic derivative of <A>
      # 1 execs: Get <A>, the expectation value of the tape with unshifted parameters.
      num_shifted_evals = 2 * N
      runs_for_jacobian = num_shifted_evals + 1
      expected_runs += runs_for_jacobian
  
      # Each tape used to compute the Jacobian is then shifted again
      expected_runs += runs_for_jacobian * num_shifted_evals
      assert tracker.totals["executions"] == expected_runs
  
      # Use caching: number of executions is ideal
  
      with qml.Tracker(dev) as tracker2:
          with tf.GradientTape() as jac_tape:
              with tf.GradientTape() as grad_tape:
                  res = cost(params, cache=True)
              grad = grad_tape.gradient(res, params)
          hess2 = jac_tape.jacobian(grad, params)
      assert np.allclose(hess1, hess2)
  
      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 tracker2.totals["executions"] == expected_runs_ideal
      assert expected_runs_ideal < expected_runs
  
imports: import pytest
  import numpy
  import pennylane
  from pennylane.devices import DefaultQubit
  from pennylane.gradients import param_shift
  from pennylane import execute
label_desc: Use image node_id 1 for calling the TestCaching obj's underlying member method code with example usage: obj.test_caching_param_shift_hessian(num_params) and returns: qml