parameters: (f, y_shape, y_dtype, xs, param)
return_statement: return jacobian
docstring: Computes the theoretical Jacobian for f regarding xs[param].
  
    One can think of the relation among f, xs and y as y = f(xs).
  
    Args:
      f: the function.
      y_shape: the shape of the result.
      y_dtype: the dtype of the result.
      xs: a list of tensors.
      param: the index of the target parameter.
  
    Returns:
      A 2-d numpy array representing the Jacobian. It has "x_size" rows
      and "y_size" columns where "x_size" is the number of elements in xs[param]
      and "y_size" is the number of elements in the result.
  
    Raises:
      ValueError: If result is empty but the gradient is nonzero.
docstring_summary: Computes the theoretical Jacobian for f regarding xs[param].
docstring_tokens: Computes the theoretical Jacobian for f regarding xs [ param ] .
function: def _compute_theoretical_jacobian(f, y_shape, y_dtype, xs, param):
    """Computes the theoretical Jacobian for f regarding xs[param].
  
    One can think of the relation among f, xs and y as y = f(xs).
  
    Args:
      f: the function.
      y_shape: the shape of the result.
      y_dtype: the dtype of the result.
      xs: a list of tensors.
      param: the index of the target parameter.
  
    Returns:
      A 2-d numpy array representing the Jacobian. It has "x_size" rows
      and "y_size" columns where "x_size" is the number of elements in xs[param]
      and "y_size" is the number of elements in the result.
  
    Raises:
      ValueError: If result is empty but the gradient is nonzero.
    """
    x = xs[param]
    # Complex vectors are treated as vectors of twice as many reals.
    x_shape = tuple(x.shape) + (2,) if x.dtype.is_complex else x.shape
    y_factor = 2 if y_dtype.is_complex else 1
  
    # To compute the jacobian, we treat x and y as one-dimensional vectors.
    x_size = _product(x_shape)
    x_val_size = _product(x_shape[1:])  # This is used for sparse gradients
    y_size = _product(y_shape) * y_factor
  
    # Allocate 2-D Jacobian, with x dimensions smashed into the first
    # dimension and y dimensions smashed into the second.
    jacobian = np.zeros((x_size, y_size), dtype=x.dtype.real_dtype.as_numpy_dtype)
  
    # For each of the entry of dy, we set this to be 1 and
    # everything else to be 0 and compute the gradients -- this will give us one
    # one column of the Jacobian matrix.
    dy_data = np.zeros(y_shape, dtype=y_dtype.as_numpy_dtype)
    dy_data_flat = dy_data.ravel().view(y_dtype.real_dtype.as_numpy_dtype)
    grad_fn_unprep = backprop.gradients_function(f, [param])
    grad_fn = _prepare(lambda dy, *xs: grad_fn_unprep(*xs, dy=dy),
                       [y_dtype] + [x.dtype for x in xs])
    for col in range(y_size):
      dy_data_flat[col] = 1
      grad = _to_numpy(grad_fn(dy_data, *xs)[0])
      grad = _eval_indexed_slices(grad)
      dy_data_flat[col] = 0
      if isinstance(grad, ops.IndexedSlicesValue):
        for i, v in zip(grad.indices, grad.values):
          r_begin = i * x_val_size
          r_end = r_begin + x_val_size
          jacobian[r_begin:r_end, col] += v.flat
      else:
        jacobian[:, col] = grad.ravel().view(jacobian.dtype)
  
    # If the output is empty, run the gradients at least once and make sure
    # they produce zeros.
    if y_size == 0:  # don't use 'not y_size', because y_size may not be an int
      grad = _to_numpy(grad_fn(dy_data, *xs)[0])
      if grad.shape != x.shape:
        raise ValueError("Empty gradient has wrong shape: expected %s, got %s" %
                         (x.shape, grad.shape))
      if np.any(grad):
        raise ValueError("Empty tensor with nonzero gradients")
  
    logging.vlog(1, "Theoretical Jacobian =\n%s", jacobian)
    return jacobian
function_tokens: def _compute_theoretical_jacobian ( f , y_shape , y_dtype , xs , param ) : x = xs [ param ] # Complex vectors are treated as vectors of twice as many reals. x_shape = tuple ( x . shape ) + ( 2 , ) if x . dtype . is_complex else x . shape y_factor = 2 if y_dtype . is_complex else 1 # To compute the jacobian, we treat x and y as one-dimensional vectors. x_size = _product ( x_shape ) x_val_size = _product ( x_shape [ 1 : ] ) # This is used for sparse gradients y_size = _product ( y_shape ) * y_factor # Allocate 2-D Jacobian, with x dimensions smashed into the first # dimension and y dimensions smashed into the second. jacobian = np . zeros ( ( x_size , y_size ) , dtype = x . dtype . real_dtype . as_numpy_dtype ) # For each of the entry of dy, we set this to be 1 and # everything else to be 0 and compute the gradients -- this will give us one # one column of the Jacobian matrix. dy_data = np . zeros ( y_shape , dtype = y_dtype . as_numpy_dtype ) dy_data_flat = dy_data . ravel ( ) . view ( y_dtype . real_dtype . as_numpy_dtype ) grad_fn_unprep = backprop . gradients_function ( f , [ param ] ) grad_fn = _prepare ( lambda dy , * xs : grad_fn_unprep ( * xs , dy = dy ) , [ y_dtype ] + [ x . dtype for x in xs ] ) for col in range ( y_size ) : dy_data_flat [ col ] = 1 grad = _to_numpy ( grad_fn ( dy_data , * xs ) [ 0 ] ) grad = _eval_indexed_slices ( grad ) dy_data_flat [ col ] = 0 if isinstance ( grad , ops . IndexedSlicesValue ) : for i , v in zip ( grad . indices , grad . values ) : r_begin = i * x_val_size r_end = r_begin + x_val_size jacobian [ r_begin : r_end , col ] += v . flat else : jacobian [ : , col ] = grad . ravel ( ) . view ( jacobian . dtype ) # If the output is empty, run the gradients at least once and make sure # they produce zeros. if y_size == 0 : # don't use 'not y_size', because y_size may not be an int grad = _to_numpy ( grad_fn ( dy_data , * xs ) [ 0 ] ) if grad . shape != x . shape : raise ValueError ( "Empty gradient has wrong shape: expected %s, got %s" % ( x . shape , grad . shape ) ) if np . any ( grad ) : raise ValueError ( "Empty tensor with nonzero gradients" ) logging . vlog ( 1 , "Theoretical Jacobian =\n%s" , jacobian ) return jacobian