Example #1
0
def pfunc(params, outputs=None, mode=None, updates=None, givens=None,
          no_default_updates=False, accept_inplace=False, name=None,
          rebuild_strict=True, allow_input_downcast=None,
          profile=None, on_unused_input=None, output_keys=None):
    """Function-constructor for graphs with shared variables.

    :type params: list of either Variable or Param instances.
    :param params: function parameters, these are not allowed to be shared
    variables

    :type outputs: list of Variables or Out instances
    :param outputs: expressions to compute

    :type mode: string or `theano.compile.Mode` instance.
    :param mode: compilation mode

    :type updates: iterable over pairs (shared_variable,
        new_expression). List, tuple or dict.
    :param updates: update the values for SharedVariable inputs
        according to these expressions

    :type givens: iterable over pairs (Var1, Var2) of Variables. List,
        tuple or dict.  The Var1 and Var2 in each pair must have the
        same Type.

    :param givens: specific substitutions to make in the computation
        graph (Var2 replaces Var1).

    :type no_default_updates: either bool or list of Variables
    :param no_default_updates: if True, do not perform any automatic
        update on Variables.  If False (default), perform them
        all. Else, perform automatic updates on all Variables that are
        neither in "updates" nor in "no_default_updates".

    :type name: None or string
    :param name: attaches a name to the profiling result of this function.

    :type allow_input_downcast: Boolean
    :param allow_input_downcast: True means that the values passed as
        inputs when calling the function can be silently downcasted to
        fit the dtype of the corresponding Variable, which may lose
        precision.  False means that it will only be cast to a more
        general, or precise, type. None (default) is almost like
        False, but allows downcasting of Python float scalars to
        floatX.

    :type profile: None, True, str, or ProfileStats instance
    :param profile: accumulate profiling information into a given ProfileStats
    instance. None is the default, and means to use the value of
    config.profile.
    If argument is `True` then a new ProfileStats instance will be
    used.  If argument is a string, a new ProfileStats instance will be created
    with that string as its `message` attribute.  This profiling object will be
    available via self.profile.

    :type on_unused_input: str
    :param on_unused_input: What to do if a variable in the 'inputs' list
        is not used in the graph. Possible values are 'raise', 'warn',
        'ignore' and None.


    :rtype: theano.compile.Function
    :returns: a callable object that will compute the outputs (given
        the inputs) and update the implicit function arguments
        according to the `updates`.


    :note: Regarding givens: Be careful to make sure that these
        substitutions are independent--behaviour when Var1 of one pair
        appears in the graph leading to Var2 in another expression is
        undefined.  Replacements specified with givens are different
        from optimizations in that Var2 is not expected to be
        equivalent to Var1.
    """
    #
    # This function works by cloning the graph (except for the
    # inputs), and then shipping it off to compile.function (There it
    # will be cloned again, unnecessarily, because it doesn't know
    # that we already cloned it.)
    #
    # First, it clones the replacements named in the givens argument,
    # and points each Var1 to the clone of Var2.  Then it sets the
    # inputs in the clone dictionary.  After these steps, we are
    # assuming that the clone dictionary contains all the inputs to
    # the computation graph.
    #
    # Then it clones the outputs and the update expressions.  This
    # rebuilds a computation graph from the inputs and the givens.
    #
    if updates is None:
        updates = []
    if givens is None:
        givens = []
    if profile is None:
        profile = config.profile
        # profile -> True or False
    if profile is True:
        profile = ProfileStats(message=name)
        # profile -> object
    if type(profile) == str:
        profile = ProfileStats(message=profile)
    # profile is typically either False or an object at this point.
    # No need to block other objects being passed through though. It might be
    # useful.

    if not isinstance(params, (list, tuple)):
        raise Exception("in pfunc() the first argument must be a list or "
                        "a tuple")

    if not isinstance(no_default_updates, bool)\
            and not isinstance(no_default_updates, list):
        raise TypeError("no_default_update should be either a boolean or "
                        "a list")

    if len(updates) > 0 and any(isinstance(v, Variable)
                                for v in iter_over_pairs(updates)):
        raise ValueError(
            "The updates parameter must be an OrderedDict/dict or a list of "
            "lists/tuples with 2 elements")

    # transform params into theano.compile.In objects.
    inputs = [_pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
              for p in params]

    # Check if some variable is present more than once in inputs
    in_variables = [input.variable for input in inputs]
    for i, v in enumerate(in_variables):
        if v in in_variables[(i + 1):]:
            dup_v_i = in_variables.index(v, (i + 1))
            raise UnusedInputError(
                ("Variable %s is used twice in inputs to theano.function, "
                 "at indices %i and %i.  This would result in values "
                 "provided for it being ignored. Please do not duplicate "
                 "variables in the inputs list." % (v, i, dup_v_i)))

    # Check that we are not using `givens` to replace input variables, because
    # this typically does nothing, contrary to what one may expect.
    in_var_set = set(in_variables)
    try:
        givens_pairs = list(givens.items())
    except AttributeError:
        givens_pairs = givens
    for x, y in givens_pairs:
        if x in in_var_set:
            raise RuntimeError(
                'You are trying to replace variable \'%s\' through the '
                '`givens` parameter, but this variable is an input to your '
                'function. Replacing inputs is currently forbidden because it '
                'has no effect. One way to modify an input `x` to a function '
                'evaluating f(x) is to define a new input `y` and use '
                '`theano.function([y], f(x), givens={x: g(y)})`. Another '
                'solution consists in using `theano.clone`, e.g. like this: '
                '`theano.function([x], '
                'theano.clone(f(x), replace={x: g(x)}))`.'
                % x)

    output_vars = rebuild_collect_shared(outputs,
                                         in_variables,
                                         replace=givens,
                                         updates=updates,
                                         rebuild_strict=rebuild_strict,
                                         copy_inputs_over=True,
                                         no_default_updates=no_default_updates)
    # extracting the arguments
    input_variables, cloned_outputs, other_stuff = output_vars
    clone_d, update_d, update_expr, shared_inputs = other_stuff

    for i, iv in zip(inputs, input_variables):
        i.variable = iv

    for sv in shared_inputs:
        # pass value of None
        # value will be stored in the resulting functions' defaults
        # list but since the value of shared variables never needs to
        # be refed, it is not needed
        if sv in update_d:
            si = In(variable=sv, value=sv.container, mutable=True,
                    borrow=True, update=update_d[sv], shared=True)
        else:
            si = In(variable=sv, value=sv.container,
                    mutable=False, borrow=True, shared=True)
        inputs.append(si)

    return orig_function(inputs, cloned_outputs, mode,
                         accept_inplace=accept_inplace, name=name,
                         profile=profile, on_unused_input=on_unused_input,
                         output_keys=output_keys)
Example #2
0
def pfunc(params, outputs=None, mode=None, updates=[], givens=[],
        no_default_updates=False, accept_inplace=False, name=None,
        rebuild_strict=True, allow_input_downcast=None,
        profile=None):
    """Function-constructor for graphs with shared variables.

    :type params: list of either Variable or Param instances.
    :param params: function parameters, these are not allowed to be shared
    variables

    :type outputs: list of Variables or Out instances
    :param outputs: expressions to compute

    :type mode: string or `theano.compile.Mode` instance.
    :param mode: compilation mode

    :type updates: iterable over pairs (shared_variable, new_expression). List, tuple or dict.
    :param updates: update the values for SharedVariable inputs according to these expressions

    :type givens: iterable over pairs (Var1, Var2) of Variables. List, tuple or dict.  The Var1
    and Var2 in each pair must have the same Type.

    :param givens: specific substitutions to make in the computation graph (Var2 replaces
    Var1).

    :type no_default_updates: either bool or list of Variables
    :param no_default_updates: if True, do not perform any automatic update on Variables.
    If False (default), perform them all. Else, perform automatic updates on all Variables
    that are neither in "updates" nor in "no_default_updates".

    :type name: None or string
    :param name: attaches a name to the Profiling result of this function when
    using ProfileMode (will be deprecated).

    :type allow_input_downcast: Boolean
    :param allow_input_downcast: True means that the values passed as
    inputs when calling the function can be silently downcasted to fit
    the dtype of the corresponding Variable, which may lose precision.
    False means that it will only be casted to a more general, or
    precise, type. None (default) is almost like False, but allows
    downcasting of Python float scalars to floatX.

    :type profile: None, True, str, or ProfileStats instance
    :param profile: accumulate profiling information into a given ProfileStats
    instance. None is the default, and means to use the value of
    config.profile.
    If argument is `True` then a new ProfileStats instance will be
    used.  If argument is a string, a new ProfileStats instance will be created
    with that string as its `message` attribute.  This profiling object will be
    available via self.profile.


    :rtype: theano.compile.Function
    :returns: a callable object that will compute the outputs (given the inputs)
    and update the implicit function arguments according to the `updates`.


    :note: Regarding givens: Be careful to make sure that these substitutions are
    independent--behaviour when Var1 of one pair appears in the graph leading to Var2 in
    another expression is undefined.  Replacements specified with givens are different from
    optimizations in that Var2 is not expected to be equivalent to Var1.

    """
    #
    # This function works by cloning the graph (except for the inputs), and then shipping it
    # off to compile.function
    # (There it will be cloned again, unnecessarily, because it doesn't know that we already
    # cloned it.)
    #
    # First, it clones the replacements named in the givens argument, and points each Var1 to
    # the clone of Var2.
    # Then it sets the inputs in the clone dictionary.
    # After these steps, we are assuming that the clone dictionary contains all the inputs to
    # the computation graph.
    #
    # Then it clones the outputs and the update expressions.  This rebuilds a computation graph
    # from the inputs and the givens.
    #
    if profile is None:
        profile = config.profile
        # profile -> True or False
    if profile == True:
        profile = ProfileStats(message=name)
        # profile -> object
    if type(profile) == str:
        profile = ProfileStats(message=profile)
    # profile is typically either False or an object at this point.
    # No need to block other objects being passed through though. It might be
    # useful.

    if not isinstance(params,(list,tuple)):
        raise Exception("in pfunc() the first argument must be a list or a tuple")

    if not isinstance(no_default_updates, bool)\
            and not isinstance(no_default_updates, list):
        raise TypeError("no_default_update should be either a boolean or a list")


    # transform params into theano.compile.In objects.
    inputs = [_pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
              for p in params]

    in_variables = [ input.variable for input in inputs ]
    output_vars = rebuild_collect_shared(
                              outputs
                            , in_variables
                            , replace            = givens
                            , updates            = updates
                            , rebuild_strict     = True
                            , copy_inputs_over   = True
                            , no_default_updates = no_default_updates )
    # extracting the arguments
    input_variables, cloned_outputs, other_stuff = output_vars
    clone_d, update_d, update_expr, shared_inputs = other_stuff

    for i, iv in zip(inputs, input_variables):
        i.variable = iv

    for sv in shared_inputs:
        if sv in update_d:
            si = In(variable=sv, value=sv.container, mutable=True,
                    borrow=True, update=update_d[sv])
        else:
            si = In(variable=sv, value=sv.container,
                    mutable=False, borrow=True)
        inputs.append(si)

    return orig_function(inputs, cloned_outputs, mode,
            accept_inplace=accept_inplace, name=name, profile=profile)
Example #3
0
def pfunc(params, outputs=None, mode=None, updates=[], givens=[],
        no_default_updates=False, accept_inplace=False, name=None,
        rebuild_strict=True, allow_input_downcast=None,
        profile=None):
    """Function-constructor for graphs with shared variables.

    :type params: list of either Variable or Param instances.
    :param params: function parameters, these are not allowed to be shared
    variables

    :type outputs: list of Variables or Out instances
    :param outputs: expressions to compute

    :type mode: string or `theano.compile.Mode` instance.
    :param mode: compilation mode

    :type updates: iterable over pairs (shared_variable, new_expression). List, tuple or dict.
    :param updates: update the values for SharedVariable inputs according to these expressions

    :type givens: iterable over pairs (Var1, Var2) of Variables. List, tuple or dict.  The Var1
    and Var2 in each pair must have the same Type.

    :param givens: specific substitutions to make in the computation graph (Var2 replaces
    Var1).

    :type no_default_updates: either bool or list of Variables
    :param no_default_updates: if True, do not perform any automatic update on Variables.
    If False (default), perform them all. Else, perform automatic updates on all Variables
    that are neither in "updates" nor in "no_default_updates".

    :type name: None or string
    :param name: attaches a name to the Profiling result of this function when
    using ProfileMode (will be deprecated).

    :type allow_input_downcast: Boolean
    :param allow_input_downcast: True means that the values passed as
    inputs when calling the function can be silently downcasted to fit
    the dtype of the corresponding Variable, which may lose precision.
    False means that it will only be cast to a more general, or
    precise, type. None (default) is almost like False, but allows
    downcasting of Python float scalars to floatX.

    :type profile: None, True, str, or ProfileStats instance
    :param profile: accumulate profiling information into a given ProfileStats
    instance. None is the default, and means to use the value of
    config.profile.
    If argument is `True` then a new ProfileStats instance will be
    used.  If argument is a string, a new ProfileStats instance will be created
    with that string as its `message` attribute.  This profiling object will be
    available via self.profile.


    :rtype: theano.compile.Function
    :returns: a callable object that will compute the outputs (given the inputs)
    and update the implicit function arguments according to the `updates`.


    :note: Regarding givens: Be careful to make sure that these substitutions are
    independent--behaviour when Var1 of one pair appears in the graph leading to Var2 in
    another expression is undefined.  Replacements specified with givens are different from
    optimizations in that Var2 is not expected to be equivalent to Var1.

    """
    #
    # This function works by cloning the graph (except for the inputs), and then shipping it
    # off to compile.function
    # (There it will be cloned again, unnecessarily, because it doesn't know that we already
    # cloned it.)
    #
    # First, it clones the replacements named in the givens argument, and points each Var1 to
    # the clone of Var2.
    # Then it sets the inputs in the clone dictionary.
    # After these steps, we are assuming that the clone dictionary contains all the inputs to
    # the computation graph.
    #
    # Then it clones the outputs and the update expressions.  This rebuilds a computation graph
    # from the inputs and the givens.
    #
    if profile is None:
        profile = config.profile
        # profile -> True or False
    if profile == True:
        profile = ProfileStats(message=name)
        # profile -> object
    if type(profile) == str:
        profile = ProfileStats(message=profile)
    # profile is typically either False or an object at this point.
    # No need to block other objects being passed through though. It might be
    # useful.

    if not isinstance(params,(list,tuple)):
        raise Exception("in pfunc() the first argument must be a list or a tuple")

    if not isinstance(no_default_updates, bool)\
            and not isinstance(no_default_updates, list):
        raise TypeError("no_default_update should be either a boolean or a list")


    # transform params into theano.compile.In objects.
    inputs = [_pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
              for p in params]

    in_variables = [ input.variable for input in inputs ]
    output_vars = rebuild_collect_shared(
                              outputs
                            , in_variables
                            , replace            = givens
                            , updates            = updates
                            , rebuild_strict     = True
                            , copy_inputs_over   = True
                            , no_default_updates = no_default_updates )
    # extracting the arguments
    input_variables, cloned_outputs, other_stuff = output_vars
    clone_d, update_d, update_expr, shared_inputs = other_stuff

    for i, iv in zip(inputs, input_variables):
        i.variable = iv

    for sv in shared_inputs:
        if sv in update_d:
            si = In(variable=sv, value=sv.container, mutable=True,
                    borrow=True, update=update_d[sv])
        else:
            si = In(variable=sv, value=sv.container,
                    mutable=False, borrow=True)
        inputs.append(si)

    return orig_function(inputs, cloned_outputs, mode,
            accept_inplace=accept_inplace, name=name, profile=profile)
Example #4
0
def pfunc(params,
          outputs=None,
          mode=None,
          updates=None,
          givens=None,
          no_default_updates=False,
          accept_inplace=False,
          name=None,
          rebuild_strict=True,
          allow_input_downcast=None,
          profile=None,
          on_unused_input=None):
    """Function-constructor for graphs with shared variables.

    :type params: list of either Variable or Param instances.
    :param params: function parameters, these are not allowed to be shared
    variables

    :type outputs: list of Variables or Out instances
    :param outputs: expressions to compute

    :type mode: string or `theano.compile.Mode` instance.
    :param mode: compilation mode

    :type updates: iterable over pairs (shared_variable, new_expression). List, tuple or dict.
    :param updates: update the values for SharedVariable inputs according to these expressions

    :type givens: iterable over pairs (Var1, Var2) of Variables. List, tuple or dict.  The Var1
    and Var2 in each pair must have the same Type.

    :param givens: specific substitutions to make in the computation graph (Var2 replaces
    Var1).

    :type no_default_updates: either bool or list of Variables
    :param no_default_updates: if True, do not perform any automatic update on Variables.
    If False (default), perform them all. Else, perform automatic updates on all Variables
    that are neither in "updates" nor in "no_default_updates".

    :type name: None or string
    :param name: attaches a name to the Profiling result of this function when
    using ProfileMode (will be deprecated).

    :type allow_input_downcast: Boolean
    :param allow_input_downcast: True means that the values passed as
    inputs when calling the function can be silently downcasted to fit
    the dtype of the corresponding Variable, which may lose precision.
    False means that it will only be cast to a more general, or
    precise, type. None (default) is almost like False, but allows
    downcasting of Python float scalars to floatX.

    :type profile: None, True, str, or ProfileStats instance
    :param profile: accumulate profiling information into a given ProfileStats
    instance. None is the default, and means to use the value of
    config.profile.
    If argument is `True` then a new ProfileStats instance will be
    used.  If argument is a string, a new ProfileStats instance will be created
    with that string as its `message` attribute.  This profiling object will be
    available via self.profile.

    :type on_unused_input: str
    :param on_unused_input: What to do if a variable in the 'inputs' list
        is not used in the graph. Possible values are 'raise', 'warn',
        'ignore' and None.


    :rtype: theano.compile.Function
    :returns: a callable object that will compute the outputs (given the inputs)
    and update the implicit function arguments according to the `updates`.


    :note: Regarding givens: Be careful to make sure that these substitutions are
    independent--behaviour when Var1 of one pair appears in the graph leading to Var2 in
    another expression is undefined.  Replacements specified with givens are different from
    optimizations in that Var2 is not expected to be equivalent to Var1.

    """
    #
    # This function works by cloning the graph (except for the inputs), and then shipping it
    # off to compile.function
    # (There it will be cloned again, unnecessarily, because it doesn't know that we already
    # cloned it.)
    #
    # First, it clones the replacements named in the givens argument, and points each Var1 to
    # the clone of Var2.
    # Then it sets the inputs in the clone dictionary.
    # After these steps, we are assuming that the clone dictionary contains all the inputs to
    # the computation graph.
    #
    # Then it clones the outputs and the update expressions.  This rebuilds a computation graph
    # from the inputs and the givens.
    #
    if updates is None:
        updates = []
    if givens is None:
        givens = []
    if profile is None:
        profile = config.profile
        # profile -> True or False
    if profile == True:
        profile = ProfileStats(message=name)
        # profile -> object
    if type(profile) == str:
        profile = ProfileStats(message=profile)
    # profile is typically either False or an object at this point.
    # No need to block other objects being passed through though. It might be
    # useful.

    if not isinstance(params, (list, tuple)):
        raise Exception(
            "in pfunc() the first argument must be a list or a tuple")

    if not isinstance(no_default_updates, bool)\
            and not isinstance(no_default_updates, list):
        raise TypeError(
            "no_default_update should be either a boolean or a list")

    # transform params into theano.compile.In objects.
    inputs = [
        _pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
        for p in params
    ]

    # Check if some variable is present more than once in inputs
    in_variables = [input.variable for input in inputs]
    for i, v in enumerate(in_variables):
        if v in in_variables[(i + 1):]:
            dup_v_i = in_variables.index(v, (i + 1))
            raise UnusedInputError(
                ("Variable %s is used twice in inputs to theano.function, "
                 "at indices %i and %i.  This would result in values "
                 "provided for it being ignored. Please do not duplicate "
                 "variables in the inputs list." % (v, i, dup_v_i)))

    # Check that we are not using `givens` to replace input variables, because
    # this typically does nothing, contrary to what one may expect.
    in_var_set = set(in_variables)
    try:
        givens_pairs = givens.items()
    except AttributeError:
        givens_pairs = givens
    for x, y in givens_pairs:
        if x in in_var_set:
            raise RuntimeError(
                'You are trying to replace variable \'%s\' through the '
                '`givens` parameter, but this variable is an input to your '
                'function. Replacing inputs is currently forbidden because it '
                'has no effect. One way to modify an input `x` to a function '
                'evaluating f(x) is to define a new input `y` and use '
                '`theano.function([y], f(x), givens={x: g(y)})`. Another '
                'solution consists in using `theano.clone`, e.g. like this: '
                '`theano.function([x], '
                'theano.clone(f(x), replace={x: g(x)}))`.' % x)

    output_vars = rebuild_collect_shared(outputs,
                                         in_variables,
                                         replace=givens,
                                         updates=updates,
                                         rebuild_strict=True,
                                         copy_inputs_over=True,
                                         no_default_updates=no_default_updates)
    # extracting the arguments
    input_variables, cloned_outputs, other_stuff = output_vars
    clone_d, update_d, update_expr, shared_inputs = other_stuff

    for i, iv in zip(inputs, input_variables):
        i.variable = iv

    for sv in shared_inputs:
        #pass value of None here
        #value will be stored in the resulting functions' defaults list
        #but since the value of shared variables never needs to be refed, it is not needed
        if sv in update_d:
            si = In(variable=sv,
                    value=sv.container,
                    mutable=True,
                    borrow=True,
                    update=update_d[sv],
                    shared=True)
        else:
            si = In(variable=sv,
                    value=sv.container,
                    mutable=False,
                    borrow=True,
                    shared=True)
        inputs.append(si)

    return orig_function(inputs,
                         cloned_outputs,
                         mode,
                         accept_inplace=accept_inplace,
                         name=name,
                         profile=profile,
                         on_unused_input=on_unused_input)
Example #5
0
def pfunc(
    params,
    outputs=None,
    mode=None,
    updates=None,
    givens=None,
    no_default_updates=False,
    accept_inplace=False,
    name=None,
    rebuild_strict=True,
    allow_input_downcast=None,
    profile=None,
    on_unused_input=None,
    output_keys=None,
):
    """
    Function-constructor for graphs with shared variables.

    Parameters
    ----------
    params : list of either Variable or In instances
        Function parameters, these are not allowed to be shared variables.
    outputs : list of Variables or Out instances
        Expressions to compute.
    mode : string or `theano.compile.Mode` instance
        Compilation mode.
    updates : iterable over pairs (shared_variable, new_expression). List, tuple or dict.
        Update the values for SharedVariable inputs according to these
        expressions
    givens : iterable over pairs (Var1, Var2) of Variables. List, tuple or dict.
        The Var1 and Var2 in each pair must have the same Type. Specific
        substitutions to make in the computation graph (Var2 replaces Var1).
    no_default_updates : either bool or list of Variables
        If True, do not perform any automatic update on Variables.
        If False (default), perform them all. Else, perform automatic updates
        on all Variables that are neither in "updates" nor in
        "no_default_updates".
    accept_inplace : bool
        True iff the graph can contain inplace operations prior to the
        optimization phase (default is False). *Note* this parameter is unsupported,
        and its use is not recommended.
    name : None or string
        Attaches a name to the profiling result of this function.
    allow_input_downcast : bool
        True means that the values passed as inputs when calling the function
        can be silently downcasted to fit the dtype of the corresponding
        Variable, which may lose precision. False means that it will only be cast to a more
        general, or precise, type. None (default) is almost like
        False, but allows downcasting of Python float scalars to
        floatX.
    profile : None, True, str, or ProfileStats instance
        Accumulate profiling information into a given ProfileStats instance.
        None is the default, and means to use the value of config.profile.
        If argument is `True` then a new ProfileStats instance will be used.
        If argument is a string, a new ProfileStats instance will be created
        with that string as its `message` attribute. This profiling object will
        be available via self.profile.
    on_unused_input : {'raise', 'warn','ignore', None}
        What to do if a variable in the 'inputs' list is not used in the graph.

    Returns
    -------
    theano.compile.Function
        A callable object that will compute the outputs (given the inputs) and
        update the implicit function arguments according to the `updates`.

    Notes
    -----
    Regarding givens: Be careful to make sure that these substitutions are
    independent--behaviour when Var1 of one pair appears in the graph leading
    to Var2 in another expression is undefined. Replacements specified with
    givens are different from optimizations in that Var2 is not expected to be
    equivalent to Var1.

    """
    #
    # This function works by cloning the graph (except for the
    # inputs), and then shipping it off to compile.function (There it
    # will be cloned again, unnecessarily, because it doesn't know
    # that we already cloned it.)
    #
    # First, it clones the replacements named in the givens argument,
    # and points each Var1 to the clone of Var2.  Then it sets the
    # inputs in the clone dictionary.  After these steps, we are
    # assuming that the clone dictionary contains all the inputs to
    # the computation graph.
    #
    # Then it clones the outputs and the update expressions.  This
    # rebuilds a computation graph from the inputs and the givens.
    #
    if updates is None:
        updates = []
    if givens is None:
        givens = []
    if profile is None:
        profile = config.profile or config.print_global_stats
        # profile -> True or False
        if profile is False:
            profile = None
    if profile is True:
        profile = ProfileStats(message=name)
        # profile -> object
    elif type(profile) == str:
        profile = ProfileStats(message=profile)
    # profile is typically either False or an object at this point.
    # No need to block other objects being passed through though. It might be
    # useful.

    if not isinstance(params, (list, tuple)):
        raise Exception("in pfunc() the first argument must be a list or "
                        "a tuple")

    if not isinstance(no_default_updates, bool) and not isinstance(
            no_default_updates, list):
        raise TypeError("no_default_update should be either a boolean or "
                        "a list")

    if len(updates) > 0 and any(
            isinstance(v, Variable) for v in iter_over_pairs(updates)):
        raise ValueError(
            "The updates parameter must be an OrderedDict/dict or a list of "
            "lists/tuples with 2 elements")

    # transform params into theano.compile.In objects.
    inputs = [
        _pfunc_param_to_in(p, allow_downcast=allow_input_downcast)
        for p in params
    ]

    # Check if some variable is present more than once in inputs
    in_variables = [input.variable for input in inputs]
    for i, v in enumerate(in_variables):
        if v in in_variables[(i + 1):]:
            dup_v_i = in_variables.index(v, (i + 1))
            raise UnusedInputError(
                ("Variable %s is used twice in inputs to theano.function, "
                 "at indices %i and %i.  This would result in values "
                 "provided for it being ignored. Please do not duplicate "
                 "variables in the inputs list." % (v, i, dup_v_i)))

    # Check that we are not using `givens` to replace input variables, because
    # this typically does nothing, contrary to what one may expect.
    in_var_set = set(in_variables)
    try:
        givens_pairs = list(givens.items())
    except AttributeError:
        givens_pairs = givens
    for x, y in givens_pairs:
        if x in in_var_set:
            raise RuntimeError(
                "You are trying to replace variable '%s' through the "
                "`givens` parameter, but this variable is an input to your "
                "function. Replacing inputs is currently forbidden because it "
                "has no effect. One way to modify an input `x` to a function "
                "evaluating f(x) is to define a new input `y` and use "
                "`theano.function([y], f(x), givens={x: g(y)})`. Another "
                "solution consists in using `theano.clone`, e.g. like this: "
                "`theano.function([x], "
                "theano.clone(f(x), replace={x: g(x)}))`." % x)

    # Extend the outputs with the updates on input variables so they are also
    # cloned
    additional_outputs = [i.update for i in inputs if i.update]
    if outputs is None:
        out_list = []
    else:
        if isinstance(outputs, (list, tuple)):
            out_list = list(outputs)
        else:
            out_list = [outputs]
    extended_outputs = out_list + additional_outputs

    output_vars = rebuild_collect_shared(
        extended_outputs,
        in_variables,
        replace=givens,
        updates=updates,
        rebuild_strict=rebuild_strict,
        copy_inputs_over=True,
        no_default_updates=no_default_updates,
    )
    # extracting the arguments
    input_variables, cloned_extended_outputs, other_stuff = output_vars
    clone_d, update_d, update_expr, shared_inputs = other_stuff

    # Recover only the clones of the original outputs
    if outputs is None:
        cloned_outputs = []
    else:
        if isinstance(outputs, (list, tuple)):
            cloned_outputs = cloned_extended_outputs[:len(outputs)]
        else:
            cloned_outputs = cloned_extended_outputs[0]

    for i, iv in zip(inputs, input_variables):
        i.variable = iv

        # If needed, replace the input's update by its cloned equivalent
        if i.update:
            i.update = clone_d[i.update]

    for sv in shared_inputs:
        # pass value of None
        # value will be stored in the resulting functions' defaults
        # list but since the value of shared variables never needs to
        # be refed, it is not needed
        if sv in update_d:
            si = In(
                variable=sv,
                value=sv.container,
                mutable=True,
                borrow=True,
                update=update_d[sv],
                shared=True,
            )
        else:
            si = In(variable=sv,
                    value=sv.container,
                    mutable=False,
                    borrow=True,
                    shared=True)
        inputs.append(si)

    return orig_function(
        inputs,
        cloned_outputs,
        mode,
        accept_inplace=accept_inplace,
        name=name,
        profile=profile,
        on_unused_input=on_unused_input,
        output_keys=output_keys,
    )