• Github
Table of Contents
0.16.1.0
  • Welcome to PsyNeuLink
  • Basics and Primer
  • Quick Reference
  • Core
  • Library
  • Contributors Guide
  • Docs >
  • UserDefinedFunction
Shortcuts

UserDefinedFunction¶

class psyneulink.core.components.functions.userdefinedfunction.UserDefinedFunction(custom_function=None, default_variable=None, params=None, owner=None, prefs=None, stateful_parameter=None, **kwargs)¶

A UserDefinedFunction (UDF) is used to “wrap” a Python function or method, lamdba function, or an expression written in string format as a PsyNeuLink Function, so that it can be used as the function of a Component. This is done automatically if a Python function or method is assigned as the function attribute of a Component. A Python function or method can also be wrapped explicitly, using the UserDefinedFunction constructor, and assigning the Python function or method to its custom_function argument. A Python function or method wrapped as a UDF must obey the following conventions to be treated correctly:

  • If providing a Python function, method, or lambda function, it must have at least one argument (that can be a positional or a keyword argument); this will be treated as the variable attribute of the UDF’s function. When the UDF calls the function or method that it wraps, an initial attempt is made to do so with variable as the name of the first argument; if that fails, it is called positionally. The argument is always passed as a 2d np.array, that may contain one or more items (elements in axis 0), depending upon the Component to which the UDF is assigned. It is the user’s responsibility to insure that the number of items expected in the first argument of the function or method is compatible with the circumstances in which it will be called. If providing a string expression, variable is optional. However, if variable is not included in the expression, the resulting UDF will not use variable at all in its calculation.

  • It may have have any number of additional arguments (positional and/or keyword); these are treated as parameters of the UDF, and can be modulated by ModulatorySignals like the parameters of ordinary PsyNeuLink Functions. If the UDF is assigned to (or automatically created for) a Mechanism or Projection, these parameters are each automatically assigned a ParameterPort so that they can be modulated by ControlSignals or LearningSignals, respectively. If the UDF is assigned to (or automatically created for) an InputPort or OutputPort, and any of the parameters are specified as Modulatory Parameters (see below), then they can be modulated by GatingSignals. The function or method wrapped by the UDF is called with these parameters by their name and with their current values (i.e., as determined by any ModulatorySignals assigned to them).

  • It may include self, owner, context, and/or params arguments; none of these are required, but can be included to gain access to the standard Function parameters (such as the history of its parameter values), those of the Component to which it has been assigned (i.e., its owner, and/or receive information about the current conditions of execution. When the function or method is called, an initial attempt is made to pass these arguments; if that fails, it is called again without them.

  • The parameters of a UDF can be specified as Modulatory Parameters in a parameter specification dictionary assigned to the params argument of the constructor for either the Python function or method, or of an explicitly defined UDF (see examples below). It can include either or both of the following two entries:

    MULTIPLICATIVE_PARAM: <parameter name>

    ADDITIVE_PARAM: <parameter name>

    These are used only when the UDF is assigned as the function of an InputPort or OutputPort that receives one more more GatingProjections.

Tip

The format of the variable passed to the custom_function function can be verified by adding a print(variable) or print(type(variable)) statement to the function.

Assigning a custom function to a Mechanism

The following example assigns a simple lambda function that returns the sum of the elements of a 1d array) to a TransferMechanism:

>>> import psyneulink as pnl
>>> my_mech = pnl.ProcessingMechanism(default_variable=[[0,0,0]],
...                                   function=lambda x:sum(x[0]))
>>> my_mech.execute(input = [1, 2, 3])
array([[6]])

Note that the function treats its argument, x, as a 2d array, and accesses its first item for the calculation. This is because the variable of my_mech is defined in the input_shapes argument of its constructor as having a single item (a 1d array of length 3; (see input_shapes). In the following example, a function is defined for a Mechanism in which the variable has two items, that are summed by the function:

>>> my_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                   function=lambda x: x[0] + x[1])
>>> my_mech.execute(input = [[1],[2]])
array([[3]])

The function argument can also be assigned a function defined in Python:

>>> def my_fct(variable):
...     return variable[0] + variable[1]
>>> my_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                   function=my_fct)

This will produce the same result as the last example. This can be useful for assigning the function to more than one Component.

More complicated functions, including ones with more than one parameter can also be used; for example:

>>> def my_sinusoidal_fct(input=[[0],[0]],
...                       phase=0,
...                       amplitude=1):
...    frequency = input[0]
...    t = input[1]
...    return amplitude * np.sin(2 * np.pi * frequency * t + phase)
>>> my_wave_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                        function=my_sinusoidal_fct)

Note that in this example, input is used as the name of the first argument, instead of variable as in the examples above. The name of the first argument of a function to be “wrapped” as a UDF does not matter; in general it is good practice to use variable, as the variable of the Component to which the UDF is assigned is what is passed to the function as its first argument. However, if it is helpful to name it something else, that is fine.

Notice also that my_sinusoidal_fct takes two values in its input argument, that it assigns to the frequency and t variables of the function. While it could have been specified more compactly as a 1d array with two elements (i.e. [0,0]), it is specified in the example as a 2d array with two items to make it clear that it matches the format of the default_variable for the ProcessingMechanism to which it will be assigned, which requires it be formatted this way (since the variable of all Components are converted to a 2d array).

my_sinusoidal_fct also has two other arguments, phase and amplitude. When it is assigned to my_wave_mech, those parameters are assigned to ParameterPorts of my_wave_mech, which that be used to modify their values by ControlSignals (see example below).

The function argument may also be an expression written as a string:

>>> my_mech = pnl.ProcessingMechanism(function='sum(variable, 2)')
>>> my_mech.execute(input=[1])
array([[3]])

This option is primarily designed for compatibility with other packages that use string expressions as their main description of computation and may be less flexible or reliable than the previous styles.

In all of the examples above, a UDF was automatically created for the functions assigned to the Mechanism. A UDF can also be created explicitly, as follows:

>>> my_sinusoidal_UDF = pnl.UserDefinedFunction(custom_function=my_sinusoidal_fct)
>>> my_wave_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                        function=my_sinusoidal_UDF)

When the UDF is created explicitly, parameters of the function can be included as arguments to its constructor, to assign them default values that differ from the those in the definition of the function, or for parameters that don’t define default values. For example:

>>> my_sinusoidal_UDF = pnl.UserDefinedFunction(custom_function=my_sinusoidal_fct,
...                                  phase=10,
...                                  amplitude=3)
>>> my_wave_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                        function=my_sinusoidal_UDF)

assigns my_sinusoidal_fct as the function for my_mech, but with the default values of its phase and amplitude parameters assigned new values. This can be useful for assigning the same function to different Mechanisms with different default values.

Explicitly defining the UDF can also be used to specify control for parameters of the function, as in the following example:

>>> my_mech = pnl.ProcessingMechanism(default_variable=[[0],[0]],
...                                   function=UserDefinedFunction(custom_function=my_sinusoidal_fct,
...                                                                amplitude=(1.0, pnl.CONTROL)))

This specifies that the default value of the amplitude parameter of my_sinusoidal_fct be 1.0, but its value should be modulated by a ControlSignal.

Custom functions can be as elaborate as desired, and can even include other PsyNeuLink Functions indirectly, such as:

>>> import psyneulink as pnl
>>> L = pnl.Logistic(gain = 2)
>>> def my_fct(variable):
...     return L(variable) + 2
>>> my_mech = pnl.ProcessingMechanism(input_shapes = 3, function = my_fct)
>>> my_mech.execute(input = [1, 2, 3])  
array([[2.88079708, 2.98201379, 2.99752738]])

Assigning of a custom function to a Port

A custom function can also be assigned as the function of an InputPort or OutputPort. For example, the following assigns my_sinusoidal_fct to the function of an OutputPort of my_mech, rather the Mechanism’s function:

>>> my_wave_mech = pnl.ProcessingMechanism(input_shapes=1,
...                                        function=pnl.Linear,
...                                        output_ports=[{pnl.NAME: 'SINUSOIDAL OUTPUT',
...                                                       pnl.VARIABLE: [(pnl.OWNER_VALUE, 0),pnl.EXECUTION_COUNT],
...                                                       pnl.FUNCTION: my_sinusoidal_fct}])

For details on how to specify a function of an OutputPort, see OutputPort Customization. Below is an example plot of the output of the ‘SINUSOIDAL OUTPUT’ OutputPort from my_wave_mech above, as the execution count increments, when the input to the mechanism is 0.005 for 1000 runs:

Sinusoid function

The parameters of a custom function assigned to an InputPort or OutputPort can also be used for gating. However, this requires that its Modulatory Parameters be specified (see above). This can be done by including a params argument in the definition of the function itself:

>>> def my_sinusoidal_fct(input=[[0],[0]],
...                      phase=0,
...                      amplitude=1,
...                      params={pnl.ADDITIVE_PARAM:'phase',
...                              pnl.MULTIPLICATIVE_PARAM:'amplitude'}):
...    frequency = input[0]
...    t = input[1]
...    return amplitude * np.sin(2 * np.pi * frequency * t + phase)

or in the explicit creation of a UDF:

>>> my_sinusoidal_UDF = pnl.UserDefinedFunction(custom_function=my_sinusoidal_fct,
...                                             phase=0,
...                                             amplitude=1,
...                                             params={pnl.ADDITIVE_PARAM:'phase',
...                                                     pnl.MULTIPLICATIVE_PARAM:'amplitude'})

The phase and amplitude parameters of my_sinusoidal_fct can now be used as the Modulatory Parameters for gating any InputPort or OutputPort to which the function is assigned (see Specifying gating and Examples).

Compiling a User Defined Function

User defined functions may also be automatically compiled, by adding them as a mechanism or projection function. There are several restrictions to take into account:

  • Lambda Functions – User defined functions currently do not support Python Lambda functions

  • Loops – User defined functions currently do not support Loops

  • Python Data Types – User defined functions currently do not support dict and class types

  • Nested Functions – User defined functions currently do not support recursive calls, nested functions, or closures

  • Slicing and comprehensions – User defined functions currently have limited support for slicing, and do not support comprehensions

  • Libraries – User defined functions currently do not support libraries, aside from NumPy (with limited support)

NumPy Support for Compiled User Defined Functions

Compiled User Defined Functions also provide access to limited compiled NumPy functionality; The supported state_features are listed as follows:

  • Data Types – Numpy Arrays and Matrices are supported, as long as their dimensionality is less than 3. In addition, the elementwise multiplication and addition of NumPy arrays and matrices is fully supported

  • Numerical functions – the exp and tanh methods are currently supported in compiled mode

It is highly recommended that users who require additional functionality request it as an issue here.

Class Definition:

custom_functionfunction

specifies the function to “wrap.” It can be any function or method, including a lambda function; see above for additional details.

paramsDict[param keyword: param value]default None

a parameter dictionary that specifies the parameters for the function. This can be used to define an additive_param and/or multiplicative_param for the UDF, by including one or both of the following entries:

ADDITIVE_PARAM: <param_name>

MULTIPLICATIVE_PARAM: <param_name>

Values specified for parameters in the dictionary override any assigned to those parameters in arguments of the constructor.

ownerComponent

component to which to assign the Function.

namestrdefault see name

specifies the name of the Function.

prefsPreferenceSet or specification dictdefault Function.classPreferences

specifies the PreferenceSet for the Function (see prefs for details).

variable: value

format and default value of the function “wrapped” by the UDF.

custom_functionfunction

the user-specified function: called by the Function’s owner when it is executed.

additive_paramstr

this contains the name of the additive_param, if one has been specified for the UDF (see above for details).

multiplicative_paramstr

this contains the name of the multiplicative_param, if one has been specified for the UDF (see above for details).

ownerComponent

component to which the Function has been assigned.

namestr

the name of the Function; if it is not specified in the name argument of the constructor, a default is assigned by FunctionRegistry (see Naming for conventions used for default and duplicate names).

prefsPreferenceSet or specification dict

the PreferenceSet for the Function; if it is not specified in the prefs argument of the constructor, a default is assigned using classPreferences defined in __init__.py (see Preferences for details).

_get_allowed_arguments()¶

Returns a set of argument names that can be passed into __init__, directly or through params dictionaries

Includes:
  • all Parameter constructor_argument names

  • all Parameter names except for those that have a constructor_argument

  • all ParameterAlias names

  • all other explicitly specified named arguments in __init__

_validate_params(request_set, target_set=None, context=None)¶

Validate params and assign validated values to targets,

This performs top-level type validation of params

This can be overridden by a subclass to perform more detailed checking (e.g., range, recursive, etc.) It is called only if the parameter_validation attribute is True (which it is by default)

IMPLEMENTATION NOTES:
  • future versions should add recursive and content (e.g., range) checking

  • should method return validated param set?

Parameters
  • validated (dict (target_set) - repository of params that have been) –

  • validated –

Return none

_initialize_parameters(context=None, **param_defaults)¶
Parameters
  • **param_defaults – maps Parameter names to their default

  • any (values. Sets instance-level Parameters dynamically for) –

  • object. (name that maps to a Parameter) –


© Copyright 2016, Jonathan D. Cohen.

Built with Sphinx using a theme provided by Read the Docs.
  • UserDefinedFunction
  • Github