code: def gradient_calc(
      seq_len,
      input_size,
      hidden_size,
      batch_size,
      add_init_state=False,
      epsilon=None,
      rand_scale=None,
      inp_bl=None,
  ):
      NervanaObject.be.bsz = NervanaObject.be.batch_size = batch_size
  
      input_shape = (input_size, seq_len * batch_size)
  
      # generate input if one is not given
      if inp_bl is None:
          inp_bl = np.random.randn(*input_shape)
  
      # neon gru instance
      gru = GRU(
          hidden_size,
          init=Gaussian(),
          activation=Tanh(),
          gate_activation=Logistic(),
      )
      inpa = gru.be.array(np.copy(inp_bl))
  
      # run fprop on the baseline input
      gru.configure((input_size, seq_len))
      gru.prev_layer = True
      gru.allocate()
  
      test_buffer = DeltasTree()
      gru.allocate_deltas(test_buffer)
      test_buffer.allocate_buffers()
      gru.set_deltas(test_buffer)
  
      if add_init_state is True:
          slice_shape = (hidden_size, batch_size)
          ini_s = np.random.randn(*slice_shape)
          ini_s_dev = gru.be.array(ini_s.copy())
          out_bl = gru.fprop(inpa, ini_s_dev).get()
      else:
          out_bl = gru.fprop(inpa).get()
  
      # random scaling/hash to generate fake loss
      if rand_scale is None:
          rand_scale = np.random.random(out_bl.shape) * 2.0 - 1.0
      # loss function would be:
      # loss_bl = np.sum(rand_scale * out_bl)
  
      # run back prop with rand_scale as the errors
      # use copy to avoid any interactions
      deltas_neon = gru.bprop(gru.be.array(np.copy(rand_scale))).get()
  
      # add a perturbation to each input element
      grads_est = np.zeros(inpa.shape)
      inp_pert = inp_bl.copy()
      for pert_ind in range(inpa.size):
          save_val = inp_pert.flat[pert_ind]
  
          inp_pert.flat[pert_ind] = save_val + epsilon
          reset_gru(gru)
          gru.allocate()
          if add_init_state is True:
              ini_s_dev = gru.be.array(ini_s.copy())
              out_pos = gru.fprop(
                  gru.be.array(inp_pert), ini_s_dev
              ).get()
          else:
              out_pos = gru.fprop(gru.be.array(inp_pert)).get()
  
          inp_pert.flat[pert_ind] = save_val - epsilon
          reset_gru(gru)
          gru.allocate()
          if add_init_state is True:
              ini_s_dev = gru.be.array(ini_s.copy())
              out_neg = gru.fprop(
                  gru.be.array(inp_pert), ini_s_dev
              ).get()
          else:
              out_neg = gru.fprop(gru.be.array(inp_pert)).get()
  
          # calculate the loss with perturbations
          loss_pos = np.sum(rand_scale * out_pos)
          loss_neg = np.sum(rand_scale * out_neg)
          # compute the gradient estimate
          grad = 0.5 / float(epsilon) * (loss_pos - loss_neg)
  
          grads_est.flat[pert_ind] = grad
  
          # reset the perturbed input element
          inp_pert.flat[pert_ind] = save_val
  
      del gru
      return (grads_est, deltas_neon)
  
file_path: neon/tests/test_gru.py
imports: import itertools
  import numpy
  from neon import NervanaObject, logger
  from neon.initializers.initializer import Constant, Gaussian
  from neon.layers import GRU
  from neon.transforms import Logistic, Tanh
  from neon.layers.container import DeltasTree
  from gru_ref import GRU
  from utils import allclose_with_out
label_desc: Use image node_id 10 for calling a global function with example usage: gradient_calc(seq_len, input_size, hidden_size, batch_size, add_init_state, epsilon, rand_scale, inp_bl) and returns: grads_est,  deltas_neon