def combine(self, obj): """Combine in series with another LinearFilter.""" if not isinstance(obj, LinearFilter): raise ValidationError("Can only combine with other LinearFilters", attr="obj") if self.analog != obj.analog: raise ValidationError("Cannot combine analog and digital filters", attr="obj") num = np.polymul(self.num, obj.num) den = np.polymul(self.den, obj.den) return LinearFilter( num, den, analog=self.analog, default_size_in=self.default_size_in, default_size_out=self.default_size_out, default_dt=self.default_dt, seed=self.seed, )
def coerce(self, node, func): """ Performs validation on the function passed to TensorNode, and sets ``size_out`` if necessary. Parameters ---------- node : `.TensorNode` The node whose ``tensor_func`` parameter is being set. func : callable The function being assigned to the TensorNode. Returns ------- output : callable The function after validation is applied. """ output = super(TensorFuncParam, self).coerce(node, func) if node.size_out is None: if not callable(func): raise ValidationError("TensorNode output must be a function", attr=self.name, obj=node) with tf.Graph().as_default(): t, x = tf.constant(0.0), tf.zeros((1, node.size_in)) args = (t, x) if node.size_in > 0 else (t, ) try: result = func(*args) except Exception as e: raise ValidationError( "Calling TensorNode function with arguments %s " "produced an error:\n%s" % (args, e), attr=self.name, obj=node) validate_output(result) node.size_out = result.get_shape()[1].value return output
def determine_size(self, instance, function): args = self.function_args(instance, function) value, invoked = checked_call(function, *args) if not invoked: raise ValidationError( "function '%s' must accept a single np.array argument" % function, attr=self.name, obj=instance, ) return np.asarray(value).size
def check_type(self, instance, value, type_): if value is not None and not isinstance(value, type_): if isinstance(type_, tuple): type_str = " or ".join((t.__name__ for t in type_)) else: type_str = type_.__name__ raise ValidationError("Must be of type %r (got type %r)." % (type_str, type(value).__name__), attr=self.name, obj=instance)
def check_pre(self, conn, rule): pre = conn.pre_obj if rule.modifies in ("decoders", "weights"): # pre object must be neural if not isinstance(pre, (Ensemble, Neurons)): raise ValidationError( "'pre' must be of type 'Ensemble' or 'Neurons' for " f"learning rule '{rule}' (got type '{type(pre).__name__}')", attr=self.name, obj=conn, ) if isinstance(pre, Ensemble) and isinstance( pre.neuron_type, Direct): raise ValidationError( "'pre' cannot have neuron type 'Direct'. Connections from " "'Direct' ensembles do not have decoders or weights.", attr=self.name, obj=conn, )
def check_ndarray(self, node, output): if len(output.shape) > 1: raise ValidationError( "Node output must be a vector (got shape %s)" % (output.shape, ), attr=self.name, obj=node, ) if node.size_in != 0: raise ValidationError("output must be callable if size_in != 0", attr=self.name, obj=node) if node.size_out is not None and node.size_out != output.size: raise ValidationError( "Size of Node output (%d) does not match " "size_out (%d)" % (output.size, node.size_out), attr=self.name, obj=node, )
def __init__(self, x, p): super(PDF, self).__init__() psum = np.sum(p) if np.abs(psum - 1) > 1e-8: raise ValidationError( "PDF must sum to one (sums to %f)" % psum, attr='p', obj=self) self.x = x self.p = p if len(self.x) != len(self.p): raise ValidationError( "`x` and `p` must be the same length", attr='p', obj=self) # make cumsum = [0] + cumsum, cdf = 0.5 * (cumsum[:-1] + cumsum[1:]) cumsum = np.cumsum(p) cumsum *= 0.5 cumsum[1:] = cumsum[:-1] + cumsum[1:] self.cdf = cumsum
def __set__(self, node, output): super(OutputParam, self).validate(node, output) size_in_set = node.size_in is not None node.size_in = node.size_in if size_in_set else 0 # --- Validate and set the new size_out if output is None: if node.size_out is not None: warnings.warn("'Node.size_out' is being overwritten with " "'Node.size_in' since 'Node.output=None'") node.size_out = node.size_in elif isinstance(output, Process): if not size_in_set: node.size_in = output.default_size_in if node.size_out is None: node.size_out = output.default_size_out elif callable(output): # We trust user's size_out if set, because calling output # may have unintended consequences (e.g., network communication) if node.size_out is None: result = self.validate_callable(node, output) node.size_out = 0 if result is None else result.size elif is_array_like(output): # Make into correctly shaped numpy array before validation output = npext.array(output, min_dims=1, copy=False, dtype=np.float64) self.validate_ndarray(node, output) if not np.all(np.isfinite(output)): raise ValidationError("Output value must be finite.", attr=self.name, obj=node) node.size_out = output.size else: raise ValidationError("Invalid node output type %r" % type(output).__name__, attr=self.name, obj=node) # --- Set output self.data[node] = output
def validate_callable(self, node, output): t, x = 0.0, np.zeros(node.size_in) args = (t, x) if node.size_in > 0 else (t, ) result, invoked = checked_call(output, *args) if not invoked: msg = ("output function '%s' is expected to accept exactly " "%d argument" % (output, len(args))) msg += (' (time, as a float)' if len(args) == 1 else 's (time, as a float and data, as a NumPy array)') raise ValidationError(msg, attr=self.name, obj=node) if result is not None: result = np.asarray(result) if len(result.shape) > 1: raise ValidationError("Node output must be a vector (got shape" " %s)" % (result.shape, ), attr=self.name, obj=node) return result
def coerce(self, instance, size_in): if is_string(size_in): if size_in not in self.valid_strings: raise ValidationError( "%r is not a valid string value (must be one of %s)" % (size_in, self.strings), attr=self.name, obj=instance) return size_in else: return super(LearningRuleTypeSizeInParam, self).coerce( instance, size_in) # IntParam validation
def __get__(self, instance, type_): if instance is None: # Return self so default can be inspected return self if not self.configurable and instance not in self.data: raise ValidationError( "Unconfigurable parameters have no defaults. Please ensure the" " value of the parameter is set before trying to access it.", attr=self.name, obj=instance) return self.data.get(instance, self.default)
def coerce(self, node, output): # pylint: disable=arguments-renamed output = super().coerce(node, output) size_in_set = node.size_in is not None node.size_in = node.size_in if size_in_set else 0 # --- Validate and set the new size_out if output is None: if node.size_out is not None: warnings.warn("'Node.size_out' is being overwritten with " "'Node.size_in' since 'Node.output=None'") node.size_out = node.size_in elif isinstance(output, Process): if not size_in_set: node.size_in = output.default_size_in if node.size_out is None: node.size_out = output.default_size_out elif callable(output): self.check_callable_args_list(node, output) # We trust user's size_out if set, because calling output # may have unintended consequences (e.g., network communication) if node.size_out is None: node.size_out = self.check_callable_output(node, output) elif is_array_like(output): # Make into correctly shaped numpy array before validation output = npext.array(output, min_dims=1, copy=False, dtype=rc.float_dtype) self.check_ndarray(node, output) if not np.all(np.isfinite(output)): raise ValidationError("Output value must be finite.", attr=self.name, obj=node) node.size_out = output.size else: raise ValidationError( f"Invalid node output type '{type(output).__name__}'", attr=self.name, obj=node, ) return output
def add_input_mapping(self, name, input_vectors, input_scales=1.0): """Adds a set of input vectors to the associative memory network. Creates a transform with the given input vectors between the a named input node and associative memory element input to enable the inputs to be mapped onto ensembles of the Associative Memory. Parameters ---------- name: str Name to use for the input node. This name will be used as the name of the attribute for the associative memory network. input_vectors: array_like The list of vectors to be compared against. input_scales: float or array_like, optional Scaling factor to apply on each of the input vectors. Note that it is possible to scale each vector independently. """ # --- Put arguments in canonical form n_vectors, d_vectors = input_vectors.shape if is_iterable(input_vectors): input_vectors = np.array(input_vectors, ndmin=2) if not is_iterable(input_scales): input_scales = input_scales * np.ones((1, n_vectors)) else: input_scales = np.array(input_scales, ndmin=2) # --- Check some preconditions if input_scales.shape[1] != n_vectors: raise ValidationError("Number of input_scale values (%d) does not " "match number of input vectors (%d)." % (input_scales.shape[1], n_vectors), attr='input_scales') if hasattr(self, name): raise ValidationError("Name '%s' already exists as a node in the " "associative memory." % name, attr='name') # --- Finally, make the input node and connect it in_node = nengo.Node(size_in=d_vectors, label=name) setattr(self, name, in_node) nengo.Connection(in_node, self.elem_input, synapse=None, transform=input_vectors * input_scales.T)
def __init__(self, initial_state=None): super().__init__() self.initial_state = initial_state if self.initial_state is not None: for name, value in self.initial_state.items(): if name not in self.state: raise ValidationError( "State variable %r not recognized; should be one of %s" % (name, ", ".join(repr(k) for k in self.state)), attr="initial_state", obj=self, ) if not (isinstance(value, Distribution) or is_array_like(value)): raise ValidationError( "State variable %r must be a distribution or array-like" % (name,), attr="initial_state", obj=self, )
def coerce(self, instance, string): string = super().coerce(instance, string) string = string.lower() if self.lower else string if string not in self.value_set: raise ValidationError( "String %r must be one of %s" % (string, list(self.values)), attr=self.name, obj=instance, ) return string
def __init__(self, options, weights=None): super(Choice, self).__init__() self.options = options self.weights = weights weights = (np.ones(len(self.options)) if self.weights is None else self.weights) if len(weights) != len(self.options): raise ValidationError( "Number of weights (%d) must match number of options (%d)" % (len(weights), len(self.options)), attr='weights', obj=self) if not all(weights >= 0): raise ValidationError("All weights must be non-negative", attr='weights', obj=self) total = float(weights.sum()) if total <= 0: raise ValidationError("Sum of weights must be positive (got %f)" % total, attr='weights', obj=self) self.p = weights / total
def coerce(self, instance, value): value = super().coerce(instance, value) if value is not None: for i, v in enumerate(value): if not is_integer(v): raise ValidationError( "Element %d must be an int (got type %r)" % (i, type(v).__name__), attr=self.name, obj=instance, ) if self.low is not None and v < self.low: raise ValidationError( "Element %d must be >= %d (got %d)" % (i, self.low, v), attr=self.name, obj=instance, ) return value
def check_function_can_be_applied(self, conn, function_info): function, size = function_info type_pre = type(conn.pre_obj).__name__ if function is not None: if not isinstance(conn.pre_obj, (Node, Ensemble)): raise ValidationError( "function can only be set for connections from an Ensemble" " or Node (got type %r)" % type_pre, attr=self.name, obj=conn, ) if isinstance(conn.pre_obj, Node) and conn.pre_obj.output is None: raise ValidationError( "Cannot apply functions to passthrough nodes", attr=self.name, obj=conn, )
def sample(self, n, d=None, rng=np.random): if d is not None and self.dimensions != d: raise ValidationError( f"Options must be of dimensionality {d} (got {self.dimensions})", attr="options", obj=self, ) i = np.searchsorted(np.cumsum(self.p), rng.rand(n)) return self.options[i]
def __init__( self, n_neurons, n_ensembles, ens_dimensions=1, label=None, seed=None, add_to_container=None, **ens_kwargs, ): if "dimensions" in ens_kwargs: raise ValidationError( "'dimensions' is not a valid argument to EnsembleArray. " "To set the number of ensembles, use 'n_ensembles'. To set " "the number of dimensions per ensemble, use 'ens_dimensions'.", attr="dimensions", obj=self, ) super().__init__(label, seed, add_to_container) for param, value in ens_kwargs.items(): if is_iterable(value): ens_kwargs[param] = Samples(value) self.config[Ensemble].update(ens_kwargs) label_prefix = "" if label is None else label + "_" self.n_neurons_per_ensemble = n_neurons self.n_ensembles = n_ensembles self.dimensions_per_ensemble = ens_dimensions # These may be set in add_neuron_input and add_neuron_output self.neuron_input, self.neuron_output = None, None self.ea_ensembles = [] with self: self.input = Node(size_in=self.dimensions, label="input") for i in range(n_ensembles): e = Ensemble( n_neurons, self.dimensions_per_ensemble, label=f"{label_prefix}{i}", ) Connection( self.input[i * ens_dimensions : (i + 1) * ens_dimensions], e, synapse=None, ) self.ea_ensembles.append(e) self.add_output("output", function=None)
def validate_output(output, minibatch_size=None, output_d=None, dtype=None): """ Performs validation on the output of a TensorNode ``tensor_func``. Parameters ---------- output : ``tf.Tensor`` or ``tf.TensorSpec`` Output from the ``tensor_func``. minibatch_size : int Expected minibatch size for the simulation. output_d Expected output dimensionality for the function. dtype Expected dtype of the function output. """ if not isinstance(output, (tf.Tensor, tf.TensorSpec)): raise ValidationError( "TensorNode function must return a Tensor (got %s)" % type(output), attr="tensor_func", ) if minibatch_size is not None and output.shape[0] != minibatch_size: raise ValidationError( "TensorNode output should have batch size %d (got %d)" % (minibatch_size, output.shape[0]), attr="tensor_func", ) if output_d is not None and np.prod(output.shape[1:]) != output_d: raise ValidationError( "TensorNode output should have size %d (got shape %s with size %d)" % (minibatch_size, output.shape[1:], np.prod(output.shape[1:])), attr="tensor_func", ) if dtype is not None and output.dtype != dtype: raise ValidationError( "TensorNode output should have dtype %s " "(got %s)" % (dtype, output.dtype), attr="tensor_func", )
def coerce(self, instance, nengo_obj): nengo_objects = ( NengoObject, ObjView, nengo.ensemble.Neurons, nengo.connection.LearningRule, ) if not isinstance(nengo_obj, nengo_objects): raise ValidationError("'%s' is not a Nengo object" % nengo_obj, attr=self.name, obj=instance) if self.nonzero_size_in and nengo_obj.size_in < 1: raise ValidationError("'%s' must have size_in > 0." % nengo_obj, attr=self.name, obj=instance) if self.nonzero_size_out and nengo_obj.size_out < 1: raise ValidationError("'%s' must have size_out > 0." % nengo_obj, attr=self.name, obj=instance) return super().coerce(instance, nengo_obj)
def coerce(self, instance, value): if not (isinstance(value, SparseMatrix) or (scipy_sparse is not None and isinstance(value, scipy_sparse.spmatrix))): raise ValidationError( "Must be `nengo.transforms.SparseMatrix` or " "`scipy.sparse.spmatrix`, got %s" % type(value), attr="init", obj=instance, ) return super().coerce(instance, value)
def mul_encoders(self, Y, E, copy=False): """Helper function that projects signal ``Y`` onto encoders ``E``. Parameters ---------- Y : ndarray The signal of interest. E : (dimensions, n_neurons) array_like or None Array of encoders. If None, ``Y`` will be returned unchanged. copy : bool, optional (Default: False) Whether a copy of ``Y`` should be returned if ``E`` is None. """ if self.weights and E is None: raise ValidationError( "Encoders must be provided for weight solver", attr='E') if not self.weights and E is not None: raise ValidationError( "Encoders must be 'None' for decoder solver", attr='E') return np.dot(Y, E) if E is not None else Y.copy() if copy else Y
def validate(self, instance, vocab): super(VocabularyParam, self).validate(instance, vocab) if vocab is not None and not isinstance(vocab, Vocabulary): raise ValidationError( "Must be of type 'Vocabulary' (got type %r)." % vocab.__class__.__name__, attr=self.name, obj=instance) return vocab
def check_array(self, conn, ndarray): if not isinstance(conn.eval_points, np.ndarray): raise ValidationError( "In order to set 'function' to specific points, 'eval_points' " "must be also be set to specific points.", attr=self.name, obj=conn) if ndarray.ndim != 2: raise ValidationError("array must be 2D (got %dD)" % ndarray.ndim, attr=self.name, obj=conn) if ndarray.shape[0] != conn.eval_points.shape[0]: raise ValidationError( "Number of evaluation points must match number " "of function points (%d != %d)" % (ndarray.shape[0], conn.eval_points.shape[0]), attr=self.name, obj=conn)
def coerce(self, instance, function): function_info = super(DeltaRuleFunctionParam, self).coerce( instance, function) function, size = function_info if function is not None and size != self.function_test_size: raise ValidationError( "Function '%s' input and output sizes must be equal" % function, attr=self.name, obj=instance) return function_info
def validate(self, instance, num): if num is not None: if not is_number(num): raise ValidationError("Must be a number; got '%s'" % num, attr=self.name, obj=instance) low_comp = 0 if self.low_open else -1 if self.low is not None and compare(num, self.low) <= low_comp: raise ValidationError( "Value must be greater than %s%s (got %s)" % ( "" if self.low_open else "or equal to ", self.low, num), attr=self.name, obj=instance) high_comp = 0 if self.high_open else 1 if self.high is not None and compare(num, self.high) >= high_comp: raise ValidationError( "Value must be less than %s%s (got %s)" % ( "" if self.high_open else "or equal to ", self.high, num), attr=self.name, obj=instance) super(NumberParam, self).validate(instance, num)
def coerce(self, conn, solver): if solver is ConnectionDefault: solver = Config.default(Connection, 'solver') solver = super().coerce(conn, solver) if solver is not None and solver.weights: raise ValidationError( "weight solvers only work for ensemble to " "ensemble connections, not probes", attr=self.name, obj=conn) return solver
def coerce(self, instance, nengo_obj): # pylint: disable=arguments-renamed nengo_objects = ( NengoObject, ObjView, nengo.ensemble.Neurons, nengo.connection.LearningRule, ) if not isinstance(nengo_obj, nengo_objects): raise ValidationError(f"'{nengo_obj}' is not a Nengo object", attr=self.name, obj=instance) if self.nonzero_size_in and nengo_obj.size_in < 1: raise ValidationError(f"'{nengo_obj}' must have size_in > 0.", attr=self.name, obj=instance) if self.nonzero_size_out and nengo_obj.size_out < 1: raise ValidationError(f"'{nengo_obj}' must have size_out > 0.", attr=self.name, obj=instance) return super().coerce(instance, nengo_obj)