def Rop(f, wrt, eval_points):
    """
    Computes the R operation on `f` wrt to `wrt` evaluated at points given
    in `eval_points`. Mathematically this stands for the jacobian of `f` wrt
    to `wrt` right muliplied by the eval points.

    :type f: Variable or list of Variables
             `f` stands for the output of the computational graph to which you
             want to apply the R operator
    :type wrt: Variable or list of `Variables`s
               variables for which you compute the R operator of the expression
               described by `f`
    :type eval_points: Variable or list of Variables
                       evalutation points for each of the variables in `wrt`
    :rtype: Variable or list/tuple of Variables depending on type of f
    :return: symbolic expression such that
        R_op[i] = sum_j ( d f[i] / d wrt[j]) eval_point[j]
        where the indices in that expression are magic multidimensional
        indices that specify both the position within a list and all
        coordinates of the tensor element in the last.
        If `wrt` is a list/tuple, then return a list/tuple with the results.
    """
    from theano.tensor import as_tensor_variable
    using_list = isinstance(f, list)
    using_tuple = isinstance(f, tuple)
    if not isinstance(wrt, (list, tuple)):
        wrt = [wrt]

    if not isinstance(eval_points, (list, tuple)):
        eval_points = [eval_points]

    if not isinstance(f, (list, tuple)):
        f = [f]

    assert len(wrt) == len(eval_points)

    # Check that each element of wrt corresponds to an element
    # of eval_points with the same dimensionality.
    for pack in enumerate(zip(wrt, eval_points)):
        i = pack[0]
        wrt_elem, eval_point = pack[1]
        if not isinstance(wrt_elem, gof.Variable):
            wrt_elem = as_tensor_variable(wrt_elem)
        if not isinstance(eval_point, gof.Variable):
            eval_point = as_tensor_variable(eval_point)

        try:

            if wrt_elem.type.ndim != eval_point.type.ndim:
                raise ValueError('Element ' +
                                 str(i) +
                                 ' of wrt/eval_point have mismatched ' +
                                 'dimensionality: ' +
                                 str(wrt_elem.type.ndim) +
                                 ' versus ' +
                                 str(eval_point.type.ndim))
        except __HOLE__:
            # wrt_elem and eval_point don't always have ndim like random type
            # Tensor, Sparse and CudaNdArray have the ndim attribute
            pass

    seen_nodes = OrderedDict()

    def _traverse(node):
        """ TODO: writeme """

        if node is None:
            return

        op = node.op
        inputs = node.inputs

        # Compute the evaluation points corresponding to each of the
        # inputs of the node
        local_eval_points = []
        for inp in inputs:
            if inp in wrt:
                local_eval_points.append(eval_points[wrt.index(inp)])
            elif inp.owner is None:
                try:
                    local_eval_points.append(inp.zeros_like())
                except:
                    # None should be used for non-differentiable
                    # arguments, like for example random states
                    local_eval_points.append(None)
            elif inp.owner in seen_nodes:

                local_eval_points.append(
                    seen_nodes[inp.owner][inp.owner.outputs.index(inp)])

            else:
                # We actually need to compute the R_op for this node

                _traverse(inp.owner)
                local_eval_points.append(
                    seen_nodes[inp.owner][inp.owner.outputs.index(inp)])
        same_type_eval_points = []
        for x, y in zip(inputs, local_eval_points):
            if y is not None:
                if not isinstance(x, gof.Variable):
                    x = as_tensor_variable(x)
                if not isinstance(y, gof.Variable):
                    y = as_tensor_variable(y)
                try:
                    y = x.type.filter_variable(y)
                except TypeError:
                    # This is a hack
                    # Originally both grad and Rop were written
                    # with the assumption that a variable and the
                    # gradient wrt that variable would have the same
                    # dtype. This was a bad assumption because the
                    # gradient wrt an integer can take on non-integer
                    # values.
                    # grad is now fixed, but Rop is not, so when grad
                    # does the right thing and violates this assumption
                    # we have to make it be wrong for Rop to keep working
                    # Rop should eventually be upgraded to handle integers
                    # correctly, the same as grad
                    y = theano.tensor.cast(y, x.type.dtype)
                    y = x.type.filter_variable(y)
                assert x.type == y.type
                same_type_eval_points.append(y)
            else:
                same_type_eval_points.append(y)

        seen_nodes[node] = op.R_op(node.inputs, same_type_eval_points)
    # end _traverse

    # Populate the dictionary
    for out in f:
        _traverse(out.owner)

    rval = []
    for out in f:
        if out in wrt:
            rval.append(eval_points[wrt.index(out)])
        elif seen_nodes[out.owner][out.owner.outputs.index(out)] is None:
            raise ValueError(('The function is not differentiable with '
                              'respect to the provided inputs !'))
        else:
            rval.append(seen_nodes[out.owner][out.owner.outputs.index(out)])

    return format_as(using_list, using_tuple, rval)
dataset/ETHPy150Open rizar/attention-lvcsr/libs/Theano/theano/gradient.py/Rop