docstr: Test that we don't compute unnecessary outputs in the grad scalar loop
docstr_tok: ["Test","that","we","do","n't","compute","unnecessary","outputs","in","the","grad","scalar","loop"]
code: def test_unused_grad_loop_opt(self, wrt):
      """Test that we don't compute unnecessary outputs in the grad scalar loop"""
      (
          test_a1,
          test_a2,
          test_b1,
          test_z,
          *expected_dds,
          expected_ddz,
      ) = self.few_iters_case
  
      a1, a2, b1, z = pt.scalars("a1", "a2", "b1", "z")
      hyp2f1_out = pt.hyp2f1(a1, a2, b1, z)
      wrt_vars = [v for i, v in enumerate((a1, a2, b1, z)) if i in wrt]
      hyp2f1_grad = pt.grad(hyp2f1_out, wrt=wrt_vars)
  
      mode = get_default_mode().including("local_useless_2f1grad_loop")
      f_grad = function([a1, a2, b1, z], hyp2f1_grad, mode=mode)
  
      if len(wrt) == 3 and (
          config.mode == "FAST_COMPILE" or not config.cxx
      ):
          # In this case we actually get two scalar_loops, because the merged one can't be executed in Python
          [scalar_loop_op1, scalar_loop_op2] = [
              node.op.scalar_op
              for node in f_grad.maker.fgraph.toposort()
              if isinstance(node.op, Elemwise)
              and isinstance(node.op.scalar_op, ScalarLoop)
          ]
          assert scalar_loop_op1.nin == 10 + 3 * 2  # wrt=[0, 1]
          assert scalar_loop_op2.nin == 10 + 3 * 1  # wrt=[2]
      else:
          [scalar_loop_op] = [
              node.op.scalar_op
              for node in f_grad.maker.fgraph.apply_nodes
              if isinstance(node.op, Elemwise)
              and isinstance(node.op.scalar_op, ScalarLoop)
          ]
          assert scalar_loop_op.nin == 10 + 3 * len(wrt)
  
      rtol = 1e-9 if config.floatX == "float64" else 2e-3
      np.testing.assert_allclose(
          f_grad(test_a1, test_a2, test_b1, test_z),
          [dd for i, dd in enumerate(expected_dds) if i in wrt],
          rtol=rtol,
      )
  
imports: import warnings
  import numpy
  import pytest
  from pytensor.gradient import verify_grad
  from pytensor.scalar import ScalarLoop
  from pytensor.tensor.elemwise import Elemwise
  from functools import partial
  import scipy.special
  import scipy.stats
  from pytensor import function, grad
  from pytensor import tensor
  from pytensor.compile.mode import get_default_mode
  from pytensor.configdefaults import config
  from pytensor.tensor import gammaincc, inplace, vector
  from tests import unittest_tools
  from tests.tensor.utils import _good_broadcast_unary_chi2sf, _good_broadcast_unary_normal, _good_broadcast_unary_normal_float, _good_broadcast_unary_normal_float_no_complex, _good_broadcast_unary_normal_float_no_complex_small_neg_range, _good_broadcast_unary_normal_no_complex, _grad_broadcast_unary_0_2_no_complex, _grad_broadcast_unary_abs1_no_complex, _grad_broadcast_unary_normal, _grad_broadcast_unary_normal_small_neg_range, check_floatX, copymod, integers_ranged, makeBroadcastTester, random_ranged, upcast_int8_nfunc
label_desc: Use image node_id 3 for calling the TestHyp2F1Grad obj's underlying member method code with example usage: obj.test_unused_grad_loop_opt(wrt) without return types