Beispiel #1
0
    def _validate_noise(self, noise):
        # Noise is a list or array
        if isinstance(noise, (np.ndarray, list)):
            if len(noise) == 1:
                pass
            # Variable is a list/array
            elif (not iscompatible(np.atleast_2d(noise),
                                   self.defaults.variable)
                  and not iscompatible(np.atleast_1d(noise),
                                       self.defaults.variable)
                  and len(noise) > 1):
                raise FunctionError(
                    "Noise parameter ({}) does not match default variable ({}). Noise parameter of {} "
                    "must be specified as a float, a function, or an array of the appropriate shape ({})."
                    .format(noise, self.defaults.variable, self.name,
                            np.shape(np.array(self.defaults.variable))))
            else:
                for i in range(len(noise)):
                    if isinstance(noise[i], DistributionFunction):
                        noise[i] = noise[i].execute
                    # if not isinstance(noise[i], (float, int)) and not callable(noise[i]):
                    if not np.isscalar(noise[i]) and not callable(noise[i]):
                        raise FunctionError(
                            "The elements of a noise list or array must be scalars or functions. "
                            "{} is not a valid noise element for {}".format(
                                noise[i], self.name))

        # Otherwise, must be a float, int or function
        elif not isinstance(noise, (float, int)) and not callable(noise):
            raise FunctionError(
                "Noise parameter ({}) for {} must be a float, function, or array/list of these."
                .format(noise, self.name))
    def _validate_params(self,
                         request_set,
                         target_set=None,
                         variable=None,
                         context=None):
        """Validate that variable had two items of equal length

        """
        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)

        err_two_items = FunctionError(
            "variable for {} ({}) must have two items".format(
                self.name, variable))

        try:
            if len(variable) != 2:
                raise err_two_items
        except TypeError:
            raise err_two_items

        try:
            if len(variable[0]) != len(variable[1]):
                raise FunctionError(
                    "The lengths of the items in the variable for {0} ({1},{2}) must be equal"
                    .format(self.name, variable[0], variable[1]))
        except TypeError:
            if is_iterable(variable[0]) ^ is_iterable(variable[1]):
                raise FunctionError(
                    "The lengths of the items in the variable for {0} ({1},{2}) must be equal"
                    .format(self.name, variable[0], variable[1]))
Beispiel #3
0
    def _validate_rate(self, rate):
        # FIX: CAN WE JUST GET RID OF THIS?
        # kmantel: this duplicates much code in _validate_params above, but that calls _instantiate_defaults
        # which I don't think is the right thing to do here, but if you don't call it in _validate_params
        # then a lot of things don't get instantiated properly
        if rate is not None:
            if isinstance(rate, list):
                rate = np.asarray(rate)

            rate_type_msg = 'The rate parameter of {0} must be a number or an array/list of at most 1d (you gave: {1})'
            if isinstance(rate, np.ndarray):
                # kmantel: current test_gating test depends on 2d rate
                #   this should be looked at but for now this restriction is removed
                # if rate.ndim > 1:
                #     raise FunctionError(rate_type_msg.format(self.name, rate))
                pass
            elif not isinstance(rate, numbers.Number):
                raise FunctionError(rate_type_msg.format(self.name, rate))

            if isinstance(rate, np.ndarray) and not iscompatible(rate, self.defaults.variable):
                if len(rate) != 1 and len(rate) != np.array(self.defaults.variable).size:
                    if self._variable_shape_flexibility is DefaultsFlexibility.FLEXIBLE:
                        self.defaults.variable = np.zeros_like(np.array(rate))
                        if self.verbosePref:
                            warnings.warn(f"The length ({len(rate)}) of the array specified for the rate parameter "
                                          f"({rate}) of {self.name} must match the length "
                                          f"({np.array(self.defaults.variable).size}) of the default input "
                                          f"({self.defaults.variable}); the default input has been updated to match.")
                        self._instantiate_value()
                        self._variable_shape_flexibility = DefaultsFlexibility.INCREASE_DIMENSION
                    else:
                        raise FunctionError(f"The length of the array specified for the rate parameter of "
                                            f"{len(rate)} ({self.name}) must match the length of the default input "
                                            f"({np.array(self.defaults.variable).size}).")
Beispiel #4
0
    def _validate_noise(self, noise):
        # Noise must be a scalar, list, array or Distribution Function

        if isinstance(noise, DistributionFunction):
            noise = noise.execute

        if isinstance(noise, (np.ndarray, list)):
            if len(noise) == 1:
                pass
            # Variable is a list/array
            elif (not iscompatible(np.atleast_2d(noise), self.defaults.variable)
                  and not iscompatible(np.atleast_1d(noise), self.defaults.variable) and len(noise) > 1):
                raise FunctionError(f"Noise parameter ({noise})  for '{self.name}' does not match default variable "
                                    f"({self.defaults.variable}); it must be specified as a float, a function, "
                                    f"or an array of the appropriate shape "
                                    f"({np.shape(np.array(self.defaults.variable))}).",
                    component=self)
            else:
                for i in range(len(noise)):
                    if isinstance(noise[i], DistributionFunction):
                        noise[i] = noise[i].execute
                    if (not np.isscalar(noise[i]) and not callable(noise[i])
                            and not iscompatible(np.atleast_2d(noise[i]), self.defaults.variable[i])
                            and not iscompatible(np.atleast_1d(noise[i]), self.defaults.variable[i])):
                        raise FunctionError(f"The element '{noise[i]}' specified in 'noise' for {self.name} "
                                             f"is not valid; noise must be list or array must be floats or functions.")
    def _validate_params(self, request_set, target_set=None, context=None):

        if request_set[MODE] in {PROB, PROB_INDICATOR}:
            if not self.defaults.variable.ndim == 2:
                raise FunctionError(
                    "If {} for {} {} is set to {}, variable must be 2d array".
                    format(MODE, self.__class__.__name__, Function.__name__,
                           PROB))
            values = self.defaults.variable[0]
            prob_dist = self.defaults.variable[1]
            if len(values) != len(prob_dist):
                raise FunctionError(
                    "If {} for {} {} is set to {}, the two items of its variable must be of equal "
                    "length (len item 1 = {}; len item 2 = {}".format(
                        MODE, self.__class__.__name__, Function.__name__, PROB,
                        len(values), len(prob_dist)))
            if not all((elem >= 0 and elem <= 1) for elem in prob_dist) == 1:
                raise FunctionError(
                    "If {} for {} {} is set to {}, the 2nd item of its variable ({}) must be an "
                    "array of elements each of which is in the (0,1) interval".
                    format(MODE, self.__class__.__name__, Function.__name__,
                           PROB, prob_dist))
            if self.is_initializing:
                return
            if not np.sum(prob_dist) == 1:
                raise FunctionError(
                    "If {} for {} {} is set to {}, the 2nd item of its variable ({}) must be an "
                    "array of probabilities that sum to 1".format(
                        MODE, self.__class__.__name__, Function.__name__, PROB,
                        prob_dist))
Beispiel #6
0
    def _validate_initializers(self, default_variable, context=None):
        for initial_value_name in self.initializers:

            initial_value = self._get_current_parameter_value(initial_value_name, context=context)

            if isinstance(initial_value, (list, np.ndarray)):
                if len(initial_value) != 1:
                    # np.atleast_2d may not be necessary here?
                    if np.shape(np.atleast_2d(initial_value)) != np.shape(np.atleast_2d(default_variable)):
                        raise FunctionError(f"{self.name}'s {initial_value_name} ({initial_value}) is incompatible "
                                            f"with its default_variable ({default_variable}).")
            elif not isinstance(initial_value, (float, int)):
                raise FunctionError(f"{self.name}'s {initial_value_name} ({initial_value}) "
                                    f"must be a number or a list/array of numbers.")
        def get_cust_fct_args(custom_function):
            """Get args of custom_function
            Return:
                - value of first arg (to be used as default_variable for UDF)
                - dict with all others (to be assigned as params of UDF)
                - dict with default values (from function definition, else set to None)
            """
            try:
                custom_function_signature = signature(custom_function)
            except ValueError:
                raise FunctionError(
                    "Assignment of a function or method ({}) without an "
                    "inspect.signature to a {} is not supported".format(
                        custom_function, self.__class__.__name__))

            args = {}
            defaults = {}
            for arg_name, arg in custom_function_signature.parameters.items():

                # MODIFIED 3/6/19 NEW: [JDC]
                # Custom function specified owner as arg
                if arg_name in {SELF, OWNER, CONTEXT}:
                    # Flag for inclusion in call to function
                    if arg_name == SELF:
                        self.self_arg = True
                    elif arg_name == OWNER:
                        self.owner_arg = True
                    else:
                        self.context_arg = True
                    # Skip rest, as these don't need to be params
                    continue
                # MODIFIED 3/6/19 END

                # Use definition from the function as default;
                #    this allows UDF to assign a value for this instance (including a MODULATORY spec)
                #    while assigning an actual value to current/defaults
                if arg.default is _empty:
                    defaults[arg_name] = None

                else:
                    defaults[arg_name] = arg.default

                # If arg is specified in the constructor for the UDF, assign that as its value
                if arg_name in kwargs:
                    args[arg_name] = kwargs[arg_name]
                # Otherwise, use the default value from the definition of the function
                else:
                    args[arg_name] = defaults[arg_name]

            # Assign default value of first arg as variable and remove from dict
            # .keys is ordered
            first_arg_name = list(
                custom_function_signature.parameters.keys())[0]
            variable = args[first_arg_name]
            if variable is _empty:
                variable = None
            del args[first_arg_name]

            return variable, args, defaults
 def _validate_variable(self, variable, context=None):
     """Validates that variable is 1d array
     """
     if len(np.atleast_2d(variable)) != 1:
         raise FunctionError(
             "Variable for {} must contain a single array or list of numbers"
             .format(self.name))
     return variable
Beispiel #9
0
    def _validate_params(self, request_set, target_set=None, context=None):

        # Handle list or array for rate specification
        if RATE in request_set:
            rate = request_set[RATE]

            if isinstance(rate, (list, np.ndarray)) and not iscompatible(
                    rate, self.defaults.variable):
                if len(rate) != 1 and len(rate) != np.array(
                        self.defaults.variable).size:
                    # If the variable was not specified, then reformat it to match rate specification
                    #    and assign class_defaults.variable accordingly
                    # Note: this situation can arise when the rate is parametrized (e.g., as an array) in the
                    #       StatefulFunction's constructor, where that is used as a specification for a function parameter
                    #       (e.g., for an IntegratorMechanism), whereas the input is specified as part of the
                    #       object to which the function parameter belongs (e.g., the IntegratorMechanism); in that
                    #       case, the StatefulFunction gets instantiated using its class_defaults.variable ([[0]]) before
                    #       the object itself, thus does not see the array specification for the input.
                    if self._default_variable_flexibility is DefaultsFlexibility.FLEXIBLE:
                        self._instantiate_defaults(variable=np.zeros_like(
                            np.array(rate)),
                                                   context=context)
                        if self.verbosePref:
                            warnings.warn(
                                "The length ({}) of the array specified for the rate parameter ({}) of {} "
                                "must match the length ({}) of the default input ({});  "
                                "the default input has been updated to match".
                                format(len(rate), rate, self.name,
                                       np.array(self.defaults.variable).size),
                                self.defaults.variable,
                            )
                    else:
                        raise FunctionError(
                            "The length of the array specified for the rate parameter of {} ({}) "
                            "must match the length of the default input ({}).".
                            format(
                                self.name,
                                # rate,
                                len(rate),
                                np.array(self.defaults.variable).size,
                                # self.defaults.variable,
                            ))
                        # OLD:
                        # self.paramClassDefaults[RATE] = np.zeros_like(np.array(rate))

                        # KAM changed 5/15 b/c paramClassDefaults were being updated and *requiring* future integrator functions
                        # to have a rate parameter of type ndarray/list

        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)

        if NOISE in target_set:
            noise = target_set[NOISE]
            if isinstance(noise, DistributionFunction):
                noise.owner = self
                target_set[NOISE] = noise.execute
            self._validate_noise(target_set[NOISE])
Beispiel #10
0
    def _validate_initializers(self, default_variable):
        for initial_value_name in self.initializers:

            initial_value = self.get_current_function_param(initial_value_name)

            if isinstance(initial_value, (list, np.ndarray)):
                if len(initial_value) != 1:
                    # np.atleast_2d may not be necessary here?
                    if np.shape(np.atleast_2d(initial_value)) != np.shape(
                            np.atleast_2d(default_variable)):
                        raise FunctionError(
                            "{}'s {} ({}) is incompatible with its default_variable ({}) ."
                            .format(self.name, initial_value_name,
                                    initial_value, default_variable))
            elif not isinstance(initial_value, (float, int)):
                raise FunctionError(
                    "{}'s {} ({}) must be a number or a list/array of numbers."
                    .format(self.name, initial_value_name, initial_value))
    def _validate_params(self, request_set, target_set=None, context=None):
        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)

        if STANDARD_DEVIATION in target_set:
            if target_set[STANDARD_DEVIATION] < 0.0:
                raise FunctionError(
                    "The standard_deviation parameter ({}) of {} must be greater than zero."
                    .format(target_set[STANDARD_DEVIATION], self.name))
Beispiel #12
0
        def get_cust_fct_args(custom_function):
            """Get args of custom_function
            Return:
                - value of first arg (to be used as default_variable for UDF)
                - dict with all others (to be assigned as params of UDF)
                - dict with default values (from function definition, else set to None)
            """
            from inspect import signature, _empty
            try:
                arg_names = custom_function.__code__.co_varnames
            except AttributeError:
                raise FunctionError("Can't get __code__ for custom_function")
            args = {}
            defaults = {}
            for arg_name, arg in signature(custom_function).parameters.items():

                # MODIFIED 3/6/19 NEW: [JDC]
                # Custom function specified owner as arg
                if arg_name in {SELF, OWNER, CONTEXT}:
                    # Flag for inclusion in call to function
                    if arg_name is SELF:
                        self.self_arg = True
                    elif arg_name is OWNER:
                        self.owner_arg = True
                    else:
                        self.context_arg = True
                    # Skip rest, as these don't need to be params
                    continue
                # MODIFIED 3/6/19 END

                # Use definition from the function as default;
                #    this allows UDF to assign a value for this instance (including a MODULATORY spec)
                #    while assigning an actual value to paramClassDefaults (in _assign_args_to_params_dicts);
                if arg.default is _empty:
                    defaults[arg_name] = None

                else:
                    defaults[arg_name] = arg.default

                # If arg is specified in the constructor for the UDF, assign that as its value
                if arg_name in kwargs:
                    args[arg_name] = kwargs[arg_name]
                # Otherwise, use the default value from the definition of the function
                else:
                    args[arg_name] = defaults[arg_name]

            # Assign default value of first arg as variable and remove from dict
            variable = args[arg_names[0]]
            if variable is _empty:
                variable = None
            del args[arg_names[0]]

            return variable, args, defaults
    def __init__(
            self,
            default_variable=None,
            size=None,
            matrix=HOLLOW_MATRIX,
            # metric:is_distance_metric=ENERGY,
            metric: tc.any(tc.enum(ENERGY, ENTROPY),
                           is_distance_metric) = ENERGY,
            transfer_fct: tc.optional(tc.any(function_type,
                                             method_type)) = None,
            normalize: bool = False,
            params=None,
            owner=None,
            prefs: is_pref_set = None):

        if size:
            if default_variable is None:
                default_variable = np.zeros(size)
            elif size != len(default_variable):
                raise FunctionError(
                    f"Both {repr(DEFAULT_VARIABLE)} ({default_variable}) and {repr(SIZE)} ({size}) "
                    f"are specified for {self.name} but are {SIZE}!=len({DEFAULT_VARIABLE})."
                )

        # Assign args to params and functionParams dicts
        params = self._assign_args_to_param_dicts(matrix=matrix,
                                                  metric=metric,
                                                  transfer_fct=transfer_fct,
                                                  normalize=normalize,
                                                  params=params)

        super().__init__(
            default_variable=default_variable,
            params=params,
            owner=owner,
            prefs=prefs,
        )

        # MODIFIED 6/12/19 NEW: [JDC]
        self._default_variable_flexibility = DefaultsFlexibility.FLEXIBLE
    def _function(
        self,
        variable=None,
        context=None,
        params=None,
    ):

        try:
            from scipy.special import erfinv
        except:
            raise FunctionError(
                "The UniformToNormalDist function requires the SciPy package.")

        mean = self.get_current_function_param(DIST_MEAN, context)
        standard_deviation = self.get_current_function_param(
            STANDARD_DEVIATION, context)

        sample = np.random.rand(1)[0]
        result = (
            (np.sqrt(2) * erfinv(2 * sample - 1)) * standard_deviation) + mean

        return self.convert_output_type(result)
Beispiel #15
0
    def _validate_params(self, request_set, target_set=None, context=None):

        # Handle list or array for rate specification
        if RATE in request_set:
            rate = request_set[RATE]

            if isinstance(rate, (list, np.ndarray)) and not iscompatible(rate, self.defaults.variable):
                if len(rate) != 1 and len(rate) != np.array(self.defaults.variable).size:
                    # If the variable was not specified, then reformat it to match rate specification
                    #    and assign class_defaults.variable accordingly
                    # Note: this situation can arise when the rate is parametrized (e.g., as an array) in the
                    #       StatefulFunction's constructor, where that is used as specification for a function parameter
                    #       (e.g., for an IntegratorMechanism), whereas the input is specified as part of the
                    #       object to which the function parameter belongs (e.g., the IntegratorMechanism); in that
                    #       case, the StatefulFunction gets instantiated using its class_defaults.variable ([[0]])
                    #       before the object itself, thus does not see the array specification for the input.
                    if self._variable_shape_flexibility is DefaultsFlexibility.FLEXIBLE:
                        self._instantiate_defaults(variable=np.zeros_like(np.array(rate)), context=context)
                        if self.verbosePref:
                            warnings.warn(f"The length ({len(rate)}) of the array specified for "
                                          f"the rate parameter ({rate}) of {self.name} must match the length "
                                          f"({np.array(self.defaults.variable).size}) of the default input "
                                          f"({self.defaults.variable}); the default input has been updated to match.")
                    else:
                        raise FunctionError(f"The length of the array specified for the rate parameter of {self.name}"
                                            f"({len(rate)}) must match the length of the default input "
                                            f"({np.array(self.defaults.variable).size}).")

        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)

        if NOISE in target_set:
            noise = target_set[NOISE]
            if isinstance(noise, DistributionFunction):
                noise.owner = self
                target_set[NOISE] = noise.execute
            self._validate_noise(target_set[NOISE])
    def __init__(
            self,
            default_variable=None,
            size=None,
            matrix=None,
            # metric:is_distance_metric=None,
            metric: tc.optional(
                tc.any(tc.enum(ENERGY, ENTROPY), is_distance_metric)) = None,
            transfer_fct: tc.optional(
                tc.optional(tc.any(types.FunctionType,
                                   types.MethodType))) = None,
            normalize: tc.optional(bool) = None,
            params=None,
            owner=None,
            prefs: tc.optional(is_pref_set) = None):

        if size:
            if default_variable is None:
                default_variable = np.zeros(size)
            elif size != len(default_variable):
                raise FunctionError(
                    f"Both {repr(DEFAULT_VARIABLE)} ({default_variable}) and {repr(SIZE)} ({size}) "
                    f"are specified for {self.name} but are {SIZE}!=len({DEFAULT_VARIABLE})."
                )

        super().__init__(
            default_variable=default_variable,
            matrix=matrix,
            metric=metric,
            transfer_fct=transfer_fct,
            normalize=normalize,
            params=params,
            owner=owner,
            prefs=prefs,
        )

        # MODIFIED 6/12/19 NEW: [JDC]
        self._variable_shape_flexibility = DefaultsFlexibility.FLEXIBLE
Beispiel #17
0
    def reinitialize(self, *args, context=None):
        """
            Resets `value <StatefulFunction.previous_value>`  and `previous_value <StatefulFunction.previous_value>`
            to the specified value(s).

            If arguments are passed into the reinitialize method, then reinitialize sets each of the attributes in
            `stateful_attributes <StatefulFunction.stateful_attributes>` to the value of the corresponding argument.
            Next, it sets the `value <StatefulFunction.value>` to a list containing each of the argument values.

            If reinitialize is called without arguments, then it sets each of the attributes in `stateful_attributes
            <StatefulFunction.stateful_attributes>` to the value of the corresponding attribute in `initializers
            <StatefulFunction.initializers>`. Next, it sets the `value <StatefulFunction.value>` to a list containing
            the values of each of the attributes in `initializers <StatefulFunction.initializers>`.

            Often, the only attribute in `stateful_attributes <StatefulFunction.stateful_attributes>` is
            `previous_value <StatefulFunction.previous_value>` and the only attribute in `initializers
            <StatefulFunction.initializers>` is `initializer <StatefulFunction.initializer>`, in which case
            the reinitialize method sets `previous_value <StatefulFunction.previous_value>` and `value
            <StatefulFunction.value>` to either the value of the argument (if an argument was passed into
            reinitialize) or the current value of `initializer <StatefulFunction.initializer>`.

            For specific types of StatefulFunction functions, the reinitialize method may carry out other
            reinitialization steps.

        """

        if context.execution_id is NotImplemented:
            context.execution_id = self.most_recent_context.execution_id

        reinitialization_values = []

        # no arguments were passed in -- use current values of initializer attributes
        if len(args) == 0 or args is None or all(arg is None for arg in args):
            for i in range(len(self.initializers)):
                initializer_name = self.initializers[i]
                reinitialization_values.append(
                    self.get_current_function_param(initializer_name, context))

        elif len(args) == len(self.initializers):
            for i in range(len(self.initializers)):
                initializer_name = self.initializers[i]
                if args[i] is None:
                    reinitialization_values.append(
                        self.get_current_function_param(
                            initializer_name, context))
                else:
                    # Not sure if np.atleast_1d is necessary here:
                    reinitialization_values.append(np.atleast_1d(args[i]))

        # arguments were passed in, but there was a mistake in their specification -- raise error!
        else:
            stateful_attributes_string = self.stateful_attributes[0]
            if len(self.stateful_attributes) > 1:
                for i in range(1, len(self.stateful_attributes) - 1):
                    stateful_attributes_string += ", "
                    stateful_attributes_string += self.stateful_attributes[i]
                stateful_attributes_string += " and "
                stateful_attributes_string += self.stateful_attributes[
                    len(self.stateful_attributes) - 1]

            initializers_string = self.initializers[0]
            if len(self.initializers) > 1:
                for i in range(1, len(self.initializers) - 1):
                    initializers_string += ", "
                    initializers_string += self.initializers[i]
                initializers_string += " and "
                initializers_string += self.initializers[len(self.initializers)
                                                         - 1]

            raise FunctionError(
                "Invalid arguments ({}) specified for {}. If arguments are specified for the "
                "reinitialize method of {}, then a value must be passed to reinitialize each of its "
                "stateful_attributes: {}, in that order. Alternatively, reinitialize may be called "
                "without any arguments, in which case the current values of {}'s initializers: {}, will"
                " be used to reinitialize their corresponding stateful_attributes."
                .format(args, self.name, self.name, stateful_attributes_string,
                        self.name, initializers_string))

        # rebuilding value rather than simply returning reinitialization_values in case any of the stateful
        # attrs are modified during assignment
        value = []
        for i in range(len(self.stateful_attributes)):
            setattr(self, self.stateful_attributes[i],
                    reinitialization_values[i])
            getattr(self.parameters, self.stateful_attributes[i]).set(
                reinitialization_values[i], context, override=True)
            value.append(getattr(self, self.stateful_attributes[i]))

        self.parameters.value.set(value, context, override=True)
        return value
Beispiel #18
0
 def _function(self, *args, **kwargs):
     raise FunctionError(
         "StatefulFunction is not meant to be called explicitly")
Beispiel #19
0
    def reset(self, *args, context=None, **kwargs):
        """
            Resets `value <StatefulFunction.previous_value>`  and `previous_value <StatefulFunction.previous_value>`
            to the specified value(s).

            If arguments are passed into the reset method, then reset sets each of the attributes in
            `stateful_attributes <StatefulFunction.stateful_attributes>` to the value of the corresponding argument.
            Next, it sets the `value <StatefulFunction.value>` to a list containing each of the argument values.

            If reset is called without arguments, then it sets each of the attributes in `stateful_attributes
            <StatefulFunction.stateful_attributes>` to the value of the corresponding attribute in `initializers
            <StatefulFunction.initializers>`. Next, it sets the `value <StatefulFunction.value>` to a list containing
            the values of each of the attributes in `initializers <StatefulFunction.initializers>`.

            Often, the only attribute in `stateful_attributes <StatefulFunction.stateful_attributes>` is
            `previous_value <StatefulFunction.previous_value>` and the only attribute in `initializers
            <StatefulFunction.initializers>` is `initializer <StatefulFunction.initializer>`, in which case
            the reset method sets `previous_value <StatefulFunction.previous_value>` and `value
            <StatefulFunction.value>` to either the value of the argument (if an argument was passed into
            reset) or the current value of `initializer <StatefulFunction.initializer>`.

            For specific types of StatefulFunction functions, the reset method may carry out other
            reinitialization steps.

        """
        num_stateful_attrs = len(self.stateful_attributes)
        if num_stateful_attrs >= 2:
            # old args specification can be supported only in subclasses
            # that explicitly define an order by overriding reset
            if len(args) > 0:
                raise FunctionError(
                    f'{self}.reset has more than one stateful attribute'
                    f' ({self.stateful_attributes}). You must specify reset'
                    ' values by keyword.')
            if len(kwargs) != num_stateful_attrs:
                type_name = type(self).__name__
                raise FunctionError(
                    'StatefulFunction.reset must receive a keyword argument for'
                    f' each item in {type_name}.stateful_attributes in the order in'
                    f' which they appear in {type_name}.value')

        if num_stateful_attrs == 1:
            try:
                kwargs[self.stateful_attributes[0]]
            except KeyError:
                try:
                    kwargs[self.stateful_attributes[0]] = args[0]
                except IndexError:
                    kwargs[self.stateful_attributes[0]] = None

        invalid_args = []

        # iterates in order arguments are sent in function call, so it
        # will match their order in value as long as they are listed
        # properly in subclass reset method signatures
        for attr in kwargs:
            try:
                kwargs[attr]
            except KeyError:
                kwargs[attr] = None

            if kwargs[attr] is not None:
                # from before: unsure if conversion to 1d necessary
                kwargs[attr] = np.atleast_1d(kwargs[attr])
            else:
                try:
                    kwargs[attr] = self._get_current_parameter_value(
                        getattr(self.parameters, attr).initializer,
                        context=context)
                except AttributeError:
                    invalid_args.append(attr)

        if len(invalid_args) > 0:
            raise FunctionError(
                f'Arguments {invalid_args} to reset are invalid because they do'
                f" not correspond to any of {self}'s stateful_attributes")

        # rebuilding value rather than simply returning reinitialization_values in case any of the stateful
        # attrs are modified during assignment
        value = []
        for attr, v in kwargs.items():
            # FIXME: HACK: Do not reinitialize random_state
            if attr != "random_state":
                getattr(self.parameters, attr).set(kwargs[attr],
                                                   context,
                                                   override=True)
                value.append(getattr(self.parameters, attr)._get(context))

        self.parameters.value.set(value, context, override=True)
        return value
    def _validate_params(self,
                         variable,
                         request_set,
                         target_set=None,
                         context=None):
        """Validate matrix param

        `matrix <Stability.matrix>` argument must be one of the following
            - 2d list, np.ndarray or np.matrix
            - ParameterPort for one of the above
            - MappingProjection with a parameterPorts[MATRIX] for one of the above

        Parse matrix specification to insure it resolves to a square matrix
        (but leave in the form in which it was specified so that, if it is a ParameterPort or MappingProjection,
         its current value can be accessed at runtime (i.e., it can be used as a "pointer")
        """

        # Validate matrix specification
        # (str can be automatically transformed to variable shape)
        if MATRIX in target_set and not isinstance(target_set[MATRIX], str):

            from psyneulink.core.components.projections.pathway.mappingprojection import MappingProjection
            from psyneulink.core.components.ports.parameterport import ParameterPort

            matrix = target_set[MATRIX]

            if isinstance(matrix, MappingProjection):
                try:
                    matrix = matrix._parameter_ports[MATRIX].value
                    param_type_string = "MappingProjection's ParameterPort"
                except KeyError:
                    raise FunctionError(
                        "The MappingProjection specified for the {} arg of {} ({}) must have a {} "
                        "ParameterPort that has been assigned a 2d array or matrix"
                        .format(MATRIX, self.name, matrix.shape, MATRIX))

            elif isinstance(matrix, ParameterPort):
                try:
                    matrix = matrix.value
                    param_type_string = "ParameterPort"
                except KeyError:
                    raise FunctionError(
                        "The value of the {} parameterPort specified for the {} arg of {} ({}) "
                        "must be a 2d array or matrix".format(
                            MATRIX, MATRIX, self.name, matrix.shape))

            else:
                param_type_string = "array or matrix"

            matrix = np.array(matrix)
            if matrix.ndim != 2:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} ({}) "
                    "must be a 2d array or matrix".format(
                        param_type_string, MATRIX, self.name, matrix))
            rows = matrix.shape[0]
            cols = matrix.shape[1]

            # this mirrors the transformation in _function
            # it is a hack, and a general solution should be found
            squeezed = np.array(self.defaults.variable)
            if squeezed.ndim > 1:
                squeezed = np.squeeze(squeezed)

            size = safe_len(squeezed)

            if rows != size:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} is the wrong size;"
                    "it is {}x{}, but must be square matrix of size {}".format(
                        param_type_string, MATRIX, self.name, rows, cols,
                        size))

            if rows != cols:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} ({}) "
                    "must be a square matrix".format(param_type_string, MATRIX,
                                                     self.name, matrix))

        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)
    def __init__(self,
                 custom_function=None,
                 default_variable=None,
                 params=None,
                 owner=None,
                 prefs: tc.optional(is_pref_set) = None,
                 **kwargs):

        def get_cust_fct_args(custom_function):
            """Get args of custom_function
            Return:
                - value of first arg (to be used as default_variable for UDF)
                - dict with all others (to be assigned as params of UDF)
                - dict with default values (from function definition, else set to None)
            """
            try:
                arg_names = custom_function.__code__.co_varnames
            except AttributeError:
                raise FunctionError("Can't get __code__ for custom_function")
            args = {}
            defaults = {}
            for arg_name, arg in signature(custom_function).parameters.items():

                # MODIFIED 3/6/19 NEW: [JDC]
                # Custom function specified owner as arg
                if arg_name in {SELF, OWNER, CONTEXT}:
                    # Flag for inclusion in call to function
                    if arg_name == SELF:
                        self.self_arg = True
                    elif arg_name == OWNER:
                        self.owner_arg = True
                    else:
                        self.context_arg = True
                    # Skip rest, as these don't need to be params
                    continue
                # MODIFIED 3/6/19 END

                # Use definition from the function as default;
                #    this allows UDF to assign a value for this instance (including a MODULATORY spec)
                #    while assigning an actual value to current/defaults
                if arg.default is _empty:
                    defaults[arg_name] = None

                else:
                    defaults[arg_name] = arg.default

                # If arg is specified in the constructor for the UDF, assign that as its value
                if arg_name in kwargs:
                    args[arg_name] = kwargs[arg_name]
                # Otherwise, use the default value from the definition of the function
                else:
                    args[arg_name] = defaults[arg_name]

            # Assign default value of first arg as variable and remove from dict
            variable = args[arg_names[0]]
            if variable is _empty:
                variable = None
            del args[arg_names[0]]

            return variable, args, defaults

        self.self_arg = False
        self.owner_arg = False
        self.context_arg = False

        # Get variable and names of other any other args for custom_function and assign to cust_fct_params
        if params is not None and CUSTOM_FUNCTION in params:
            custom_function = params[CUSTOM_FUNCTION]
        try:
            cust_fct_variable, self.cust_fct_params, defaults = get_cust_fct_args(custom_function)
        except FunctionError:
            raise FunctionError("Assignment of a built-in function or method ({}) to a {} is not supported".
                                format(custom_function, self.__class__.__name__))

        # If params is specified as arg in custom function's definition, move it to params in UDF's constructor
        if PARAMS in self.cust_fct_params:
            if self.cust_fct_params[PARAMS]:
                if params:
                    params.update(self.cust_fct_params)
                else:
                    params = self.cust_fct_params[PARAMS]
            del self.cust_fct_params[PARAMS]

        # If context is specified as arg in custom function's definition, delete it
        if CONTEXT in self.cust_fct_params:
            if self.cust_fct_params[CONTEXT]:
                context = self.cust_fct_params[CONTEXT]
            del self.cust_fct_params[CONTEXT]

        # Assign variable to default_variable if default_variable was not specified
        if default_variable is None:
            default_variable = cust_fct_variable
        elif cust_fct_variable and not iscompatible(default_variable, cust_fct_variable):
            owner_name = ' ({})'.format(owner.name) if owner else ''
            cust_fct_name = repr(custom_function.__name__)
            raise FunctionError("Value passed as \'default_variable\' for {} {} ({}) conflicts with specification of "
                                "first argument in constructor for {} itself ({}). "
                                "Try modifying specification of \'default_variable\' "
                                "for object to which {} is being assigned{}, and/or insuring that "
                                "the first argument of {} is at least a 2d array".
                                format(self.__class__.__name__, cust_fct_name, default_variable,
                                       cust_fct_name, cust_fct_variable, cust_fct_name, owner_name, cust_fct_name))

        super().__init__(
            default_variable=default_variable,
            custom_function=custom_function,
            params=params,
            owner=owner,
            prefs=prefs,
            **self.cust_fct_params
        )
Beispiel #22
0
    def _validate_params(self,
                         variable,
                         request_set,
                         target_set=None,
                         context=None):
        """Validate matrix param

        `matrix <Stability.matrix>` argument must be one of the following
            - 2d list, np.ndarray or np.matrix
            - ParameterState for one of the above
            - MappingProjection with a parameterStates[MATRIX] for one of the above

        Parse matrix specification to insure it resolves to a square matrix
        (but leave in the form in which it was specified so that, if it is a ParameterState or MappingProjection,
         its current value can be accessed at runtime (i.e., it can be used as a "pointer")
        """

        # Validate matrix specification
        if MATRIX in target_set:

            from psyneulink.core.components.projections.pathway.mappingprojection import MappingProjection
            from psyneulink.core.components.states.parameterstate import ParameterState

            matrix = target_set[MATRIX]

            if isinstance(matrix, str):
                matrix = get_matrix(matrix)

            if isinstance(matrix, MappingProjection):
                try:
                    matrix = matrix._parameter_states[MATRIX].value
                    param_type_string = "MappingProjection's ParameterState"
                except KeyError:
                    raise FunctionError(
                        "The MappingProjection specified for the {} arg of {} ({}) must have a {} "
                        "ParameterState that has been assigned a 2d array or matrix"
                        .format(MATRIX, self.name, matrix.shape, MATRIX))

            elif isinstance(matrix, ParameterState):
                try:
                    matrix = matrix.value
                    param_type_string = "ParameterState"
                except KeyError:
                    raise FunctionError(
                        "The value of the {} parameterState specified for the {} arg of {} ({}) "
                        "must be a 2d array or matrix".format(
                            MATRIX, MATRIX, self.name, matrix.shape))

            else:
                param_type_string = "array or matrix"

            matrix = np.array(matrix)
            if matrix.ndim != 2:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} ({}) "
                    "must be a 2d array or matrix".format(
                        param_type_string, MATRIX, self.name, matrix))
            rows = matrix.shape[0]
            cols = matrix.shape[1]
            # MODIFIED 11/25/17 OLD:
            # size = len(np.squeeze(self.defaults.variable))
            # MODIFIED 11/25/17 NEW:
            size = len(self.defaults.variable)
            # MODIFIED 11/25/17 END

            if rows != size:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} is the wrong size;"
                    "it is {}x{}, but must be square matrix of size {}".format(
                        param_type_string, MATRIX, self.name, rows, cols,
                        size))

            if rows != cols:
                raise FunctionError(
                    "The value of the {} specified for the {} arg of {} ({}) "
                    "must be a square matrix".format(param_type_string, MATRIX,
                                                     self.name, matrix))

        super()._validate_params(request_set=request_set,
                                 target_set=target_set,
                                 context=context)