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]))
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}).")
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))
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
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])
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))
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)
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
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
def _function(self, *args, **kwargs): raise FunctionError( "StatefulFunction is not meant to be called explicitly")
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 )
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)