def local_elemwise_fusion_op(OP, max_input_fct=lambda node: 32,
                             maker=None):
    """
    We parametrize it to make it work for Elemwise and GpuElemwise op.

    Parameters
    ----------
    OP
        GpuElemwise or Elemwise class (the one that we want to fuse)
    max_input_fct
        A function that returns the maximum number of inputs
        that this elemwise can take (useful for GpuElemwise).
        GPU kernel currently has a limit of 256 bytes for
        the size of all parameters passed to it. As currently
        we pass many information only by parameter, we must
        limit how many ops we fuse together to avoid busting
        that 256 limit.

        On the CPU we limit to 32 input variables
        since that is the maximum numpy support.

    """
    if maker is None:
        def maker(node, scalar_op):
            return OP(scalar_op)

    def local_fuse(node):
        """
        As part of specialization, we fuse two consecutive elemwise Ops of the
        same shape.

        For mixed dtype, we let the Composite op do the cast. It lets the C
        compiler do the cast.
        The number of dimensions is validated at call time by theano itself.

        """
        # META TODO:  PUT THESE THINGS IN TRAC, NOT TODO NOTES!!
        # TODO: use broadcast flag?

        # TODO: don't do this optimization as a localOptimizer.
        # Analyze the graph in terms of elemwise subgraphs, and then
        # replace each subgraph with a Composite version.

        # TODO: use malloc and copy to transfer arguments that don't
        # fit within the parameter space of 256 bytes
        #
        # TODO: Merge with multiple output to merge when an inputs
        # have multiple clients. This can't be done with a local
        # optimiser.

        # TODO: Related: Support composites with multiple outputs

        # TODO: Use Composite to combine Elemwise and Reduce
        # operations.  We have to loop over the data anyway... might
        # as well sum it up while we're at it (this can be trickier
        # than i'm making it seound here. The data-traversal should be
        # done contiguously, and the summing-up might not be easy or
        # worthwhile if the summation axis doesn't line up with a
        # contiguous dimension)

        if type(node.op) is not OP:
            return False
        inputs = []  # inputs of the new Elemwise op.
        s_inputs = []  # inputs of the new scalar op used by the Composite.
        # Inputs of the new scalar op that represents the current node.
        s_g = []

        # There is a hard limit of 256 bytes for the formal argument list to a
        # GPU kernel function.
        max_nb_input = max_input_fct(node)
        # The number of inputs to the new fused op if we do not fuse more
        # inputs.
        new_nb_input = len(node.inputs)
        # Did we fuse something?
        # Needed as we can fuse unary op that don't change the number of
        # inputs.
        # And there is a case where the inputs are the same as the current
        # node. That won't change the number of inputs of the new op.
        fused = False

        for i in node.inputs:
            do_fusion = False
            catch = False
            # Will store inputs of the fused node that are not currently inputs
            # of the node we want to create (to avoid duplicating inputs).
            tmp_input = []
            # Same as tmp_input, but for scalars.
            tmp_scalar = []

            # We should not check the number of inputs here
            # As fusing op don't always change the number of input.
            # If a variable is used as multiple into to the same node,
            # we still want to fusion. So we take the set.
            if (i.owner and
                    isinstance(i.owner.op, OP) and
                    len(set([n for n, idx in i.clients])) == 1 and
                    # Do not merge elemwise that don't have the same
                    # broadcastable pattern to don't redo duplicate
                    # computation due to broadcast.
                    i.owner.outputs[0].broadcastable ==
                    node.outputs[0].broadcastable):
                do_fusion = True
                try:
                    tmp_s_input = []
                    # we should not put duplicate input into s_inputs and inputs
                    for ii in i.owner.inputs:
                        if ii in inputs:
                            tmp_s_input.append(s_inputs[inputs.index(ii)])
                        elif ii in tmp_input:
                            tmp_s_input.append(tmp_scalar[tmp_input.index(ii)])
                        else:
                            tmp = scalar.get_scalar_type(ii.dtype).make_variable()
                            try:
                                tv = gof.op.get_test_value(ii)
                                if tv.size > 0:
                                    tmp.tag.test_value = tv.flatten()[0]
                                else:
                                    tmp.tag.test_value = tv
                            except AttributeError:
                                pass
                            tmp_s_input.append(tmp)
                            tmp_input.append(ii)
                            tmp_scalar.append(tmp_s_input[-1])
                    s_op = i.owner.op.scalar_op(*tmp_s_input,
                                                return_list=True)

                    # if the scalar_op don't have a c implementation,
                    # we skip its fusion to allow the fusion of the
                    # other ops.
                    i.owner.op.scalar_op.c_code(s_op[0].owner,
                                                "test_presence_of_c_code",
                                                ["x" for x in i.owner.inputs],
                                                ["z" for z in i.owner.outputs],
                                                {})
                except MethodNotDefined:
                    catch = True
                except NotImplementedError:
                    catch = True
                if catch:
                    _logger.info(("%s does not implement the c_code function."
                                  " As well as being potentially slow, this"
                                  " disables loop fusion of this op.") %
                                 str(i.owner.op.scalar_op))
                    do_fusion = False

            # Compute the number of inputs in case we fuse this input.
            # We subtract 1 because we replace the existing input with the new
            # inputs from `tmp_input`.
            new_nb_input_ = new_nb_input + 1 - len(tmp_input)

            # If the new input is already an input of the current node, it was
            # already counted when `new_nb_input` was initialized to
            # len(node.inputs).
            # This can happen when a variable is used both by the Elemwise to
            # fuse and the current node.
            for x in tmp_input:
                if x in node.inputs:
                    new_nb_input_ -= 1

            if do_fusion and (new_nb_input_ <= max_nb_input):
                fused = True
                new_nb_input = new_nb_input_
                inputs.extend(tmp_input)
                s_inputs.extend(tmp_scalar)
                s_g.extend(s_op)
            else:
                # We must support the case where the same variable appear many
                # time in the inputs
                if inputs.count(i) == node.inputs.count(i):
                    s = s_inputs[inputs.index(i)]
                else:
                    s = scalar.get_scalar_type(i.dtype).make_variable()
                    try:
                        if theano.config.compute_test_value != 'off':
                            v = gof.op.get_test_value(i)
                            if v.size > 0:
                                s.tag.test_value = v.flatten()[0]
                    except AttributeError:
                        pass

                    inputs.append(i)
                    s_inputs.append(s)
                s_g.append(s)

        if not fused:
            return False

        if new_nb_input != len(inputs) or len(s_inputs) != len(inputs):
            raise Exception("""Something has gone wrong with the elemwise
fusion optimization. We skip this optimization. You can ignore this message,
your code will run correctly, but may be slower.""")

        s_new_out = node.op.scalar_op(*s_g, return_list=True)
        try:
            s_new_out[0].owner.op.c_code(s_new_out[0].owner,
                                         "test_presence_of_c_code",
                                         ["x" for x in s_g],
                                         ["z" for x in s_new_out], {})
        except MethodNotDefined:
            _logger.info(("%s does not implement the c_code function."
                          " As well as being potentially slow, this disables "
                          "loop fusion of this op.") % str(
                              s_new_out[0].owner.op))
            return False
        except NotImplementedError:
            _logger.info(("%s does not implement the c_code function. As well"
                          " as being potentially slow, this disables loop"
                          " fusion of this op.") % str(s_new_out[0].owner.op))
            return False

        # create the composite op.
        C = scalar.Composite(s_inputs, s_new_out)

        # create the new node.
        # Do not call make_node to have test_value
        n = maker(node, C)(*inputs).owner
        assert len(n.outputs) == 1
        assert node.outputs[0].dtype == n.outputs[0].dtype

        if len(n.inputs) > max_nb_input:
            _logger.info('loop fusion failed because Op would exceed'
                         ' kernel argument limit.')
            return False

        # we fuse as many that we can at the same time to make debug mode faster
        # debug mode will be faster as it won't test all intermediate step.
        while True:
            ret = local_fuse(n)
            if ret is not False and ret is not None:
                # print n,ret
                assert len(ret) == len(n.outputs)
                assert len(ret) == 1
                n = ret[0].owner
            else:
                break

        return n.outputs
    return local_fuse
dataset/ETHPy150Open rizar/attention-lvcsr/libs/Theano/theano/tensor/opt.py/local_elemwise_fusion_op/swapped operands of `-`