Beispiel #1
0
    def coerce(self, conn, function):
        function = super().coerce(conn, function)

        if function is None:
            function_info = FunctionInfo(function=None, size=None)
        elif isinstance(function, FunctionInfo):
            function_info = function
        elif is_array_like(function):
            array = np.array(function, copy=False, dtype=np.float64)
            self.check_array(conn, array)
            function_info = FunctionInfo(function=array, size=array.shape[1])
        elif callable(function):
            function_info = FunctionInfo(
                function=function, size=self.determine_size(conn, function))
            # TODO: necessary?
            super().coerce(conn, function_info)
        else:
            raise ValidationError("Invalid connection function type %r "
                                  "(must be callable or array-like)"
                                  % type(function).__name__,
                                  attr=self.name, obj=conn)

        self.check_function_can_be_applied(conn, function_info)

        return function_info
    def __init__(self, shape, init=1.0):
        super().__init__()

        self.shape = shape

        if is_array_like(init):
            init = np.asarray(init, dtype=rc.float_dtype)

            # check that the shape of init is compatible with the given shape
            # for this transform
            expected_shape = None
            if shape[0] != shape[1]:
                # init must be 2D if transform is not square
                expected_shape = shape
            elif init.ndim == 1:
                expected_shape = (shape[0], )
            elif init.ndim >= 2:
                expected_shape = shape

            if expected_shape is not None and init.shape != expected_shape:
                raise ValidationError(
                    "Shape of initial value %s does not match expected "
                    "shape %s" % (init.shape, expected_shape),
                    attr="init",
                )

        self.init = init
    def __init__(self, indices, data, shape):
        super().__init__()

        self.indices = indices
        self.shape = shape

        # if data is not a distribution
        if is_array_like(data):
            data = np.asarray(data)

            # convert scalars to vectors
            if data.size == 1:
                data = data.item() * np.ones(self.indices.shape[0],
                                             dtype=data.dtype)

            if data.ndim != 1 or data.shape[0] != self.indices.shape[0]:
                raise ValidationError(
                    "Must be a vector of the same length as `indices`",
                    attr="data",
                    obj=self,
                )

        self.data = data
        self._allocated = None
        self._dense = None
Beispiel #4
0
    def coerce(self, conn, function):
        function = super().coerce(conn, function)

        if function is None:
            function_info = FunctionInfo(function=None, size=None)
        elif isinstance(function, FunctionInfo):
            function_info = function
        elif is_array_like(function):
            array = np.array(function, copy=False, dtype=np.float64)
            self.check_array(conn, array)
            function_info = FunctionInfo(function=array, size=array.shape[1])
        elif callable(function):
            function_info = FunctionInfo(function=function,
                                         size=self.determine_size(
                                             conn, function))
            # TODO: necessary?
            super().coerce(conn, function_info)
        else:
            raise ValidationError("Invalid connection function type %r "
                                  "(must be callable or array-like)" %
                                  type(function).__name__,
                                  attr=self.name,
                                  obj=conn)

        self.check_function_can_be_applied(conn, function_info)

        return function_info
Beispiel #5
0
 def hashvalue(self, instance):
     d = self.__get__(instance, None)
     if d is None:
         return hash(d)
     return hash(
         tuple((k, array_hash(v) if is_array_like(v) else hash(v))
               for k, v in d.items()))
Beispiel #6
0
def build_node(model, node):
    """Builds a `.Node` object into a model.

    The node build function is relatively simple. It involves creating input
    and output signals, and connecting them with an `.Operator` that depends
    on the type of ``node.output``.

    Parameters
    ----------
    model : Model
        The model to build into.
    node : Node
        The node to build.

    Notes
    -----
    Sets ``model.params[node]`` to ``None``.
    """

    # input signal
    if not is_array_like(node.output) and node.size_in > 0:
        sig_in = Signal(shape=node.size_in, name="%s.in" % node)
        model.add_op(Reset(sig_in))
    else:
        sig_in = None

    # Provide output
    if node.output is None:
        sig_out = sig_in
    elif isinstance(node.output, Process):
        sig_out = Signal(shape=node.size_out, name="%s.out" % node)
        model.build(node.output, sig_in, sig_out, mode="set")
    elif callable(node.output):
        sig_out = (
            Signal(shape=node.size_out, name="%s.out" % node)
            if node.size_out > 0
            else None
        )
        model.add_op(SimPyFunc(output=sig_out, fn=node.output, t=model.time, x=sig_in))
    elif is_array_like(node.output):
        sig_out = Signal(node.output.astype(rc.float_dtype), name="%s.out" % node)
    else:
        raise BuildError("Invalid node output type %r" % type(node.output).__name__)

    model.sig[node]["in"] = sig_in
    model.sig[node]["out"] = sig_out
    model.params[node] = None
Beispiel #7
0
    def coerce(self, conn, transform):
        if transform is None:
            transform = NoTransform(conn.size_mid)
        elif is_array_like(transform) or isinstance(transform, Distribution):
            transform = Dense((conn.size_out, conn.size_mid), transform)

        if transform.size_in != conn.size_mid:
            if isinstance(transform, Dense) and (
                transform.shape[0] == transform.shape[1]
            ):
                # we provide a different error message in this case;
                # the transform is not changing the dimensionality of the
                # signal, so the blame most likely lies with the function
                raise ValidationError(
                    "Function output size is incorrect; should return a "
                    "vector of size %d" % conn.size_mid,
                    attr=self.name,
                    obj=conn,
                )
            else:
                raise ValidationError(
                    "Transform input size (%d) not equal to %s output size "
                    "(%d)"
                    % (transform.size_in, type(conn.pre_obj).__name__, conn.size_mid),
                    attr=self.name,
                    obj=conn,
                )

        if transform.size_out != conn.size_out:
            raise ValidationError(
                "Transform output size (%d) not equal to connection "
                "output size (%d)" % (transform.size_out, conn.size_out),
                attr=self.name,
                obj=conn,
            )

        # we don't support repeated indices on 2D transforms because it makes
        # the matrix multiplication more complicated (we'd need to expand
        # the weight matrix for the duplicated rows/columns). it could be done
        # if there were a demand at some point.
        if isinstance(transform, Dense) and len(transform.init_shape) == 2:

            def repeated_inds(x):
                return not isinstance(x, slice) and np.unique(x).size != len(x)

            if repeated_inds(conn.pre_slice):
                raise ValidationError(
                    "Input object selection has repeated indices",
                    attr=self.name,
                    obj=conn,
                )
            if repeated_inds(conn.post_slice):
                raise ValidationError(
                    "Output object selection has repeated indices",
                    attr=self.name,
                    obj=conn,
                )

        return super().coerce(conn, transform)
Beispiel #8
0
def build_node(model, node):
    """Builds a `.Node` object into a model.

    The node build function is relatively simple. It involves creating input
    and output signals, and connecting them with an `.Operator` that depends
    on the type of ``node.output``.

    Parameters
    ----------
    model : Model
        The model to build into.
    node : Node
        The node to build.

    Notes
    -----
    Sets ``model.params[node]`` to ``None``.
    """

    # input signal
    if not is_array_like(node.output) and node.size_in > 0:
        sig_in = Signal(np.zeros(node.size_in), name="%s.in" % node)
        model.add_op(Reset(sig_in))
    else:
        sig_in = None

    # Provide output
    if node.output is None:
        sig_out = sig_in
    elif isinstance(node.output, Process):
        sig_out = Signal(np.zeros(node.size_out), name="%s.out" % node)
        model.build(node.output, sig_in, sig_out)
    elif callable(node.output):
        sig_out = (Signal(np.zeros(node.size_out), name="%s.out" % node)
                   if node.size_out > 0 else None)
        model.add_op(SimPyFunc(
            output=sig_out, fn=node.output, t=model.time, x=sig_in))
    elif is_array_like(node.output):
        sig_out = Signal(node.output, name="%s.out" % node)
    else:
        raise BuildError(
            "Invalid node output type %r" % type(node.output).__name__)

    model.sig[node]['in'] = sig_in
    model.sig[node]['out'] = sig_out
    model.params[node] = None
Beispiel #9
0
def __ufunc_power(base, exponent):
    """Operator that implements raising to some power with Gyrus operator(s).

    Currently only supports ``base ** exponent`` where ``exponent`` is not an Operator.
    That is, the Gyrus operator is the base. The same exponent is applied to each
    element of the base.
    """
    if is_array_like(exponent):
        assert isinstance(base, Operator)
        return base.decode(lambda x: x**exponent)
    return NotImplemented
Beispiel #10
0
def assert_is_deepcopy(cp, original):
    assert cp is not original  # ensures separate parameters
    for param in iter_params(cp):
        param_inst = getattr(cp, param)
        if isinstance(param_inst,
                      (nengo.solvers.Solver, nengo.base.NengoObject)):
            assert_is_copy(param_inst, getattr(original, param))
        elif is_array_like(param_inst):
            assert np.all(param_inst == getattr(original, param))
        else:
            assert param_inst == getattr(original, param)
Beispiel #11
0
    def coerce(self, instance, obj):
        # Do nothing if no object is given -- that's fine
        if obj is None:
            return None

        # Make sure obj is either callable or an array
        if (not callable(obj)) and (not is_array_like(obj)):
            raise nengo.exceptions.ValidationError(
                "connectivity must either be a Npost x Npre array or a callable",
                attr=self.name,
                obj=instance)

        # Convert the given array to the right format
        if is_array_like(obj):
            obj = np.array(obj, copy=False, dtype=np.float64)
            if obj.ndim != 2:
                raise nengo.exceptions.ValidationError(
                    "connectivity must be a 2D array",
                    attr=self.name,
                    obj=instance)

        return obj
Beispiel #12
0
def build_neurons(model, neurontype, neurons, input_sig=None, output_sig=None):
    """Builds a `.NeuronType` object into a model.

    This function adds a `.SimNeurons` operator connecting the input current to the
    neural output signals, and handles any additional state variables defined
    within the neuron type.

    Parameters
    ----------
    model : Model
        The model to build into.
    neurontype : NeuronType
        Neuron type to build.
    neuron : Neurons
        The neuron population object corresponding to the neuron type.

    Notes
    -----
    Does not modify ``model.params[]`` and can therefore be called
    more than once with the same `.NeuronType` instance.
    """
    input_sig = model.sig[neurons]["in"] if input_sig is None else input_sig
    output_sig = model.sig[neurons]["out"] if output_sig is None else output_sig

    dtype = input_sig.dtype
    n_neurons = neurons.size_in
    rng = np.random.RandomState(model.seeds[neurons.ensemble] + 1)
    state_init = neurontype.make_state(n_neurons, rng=rng, dtype=dtype)
    state = {}

    for key, init in state_init.items():
        if key in model.sig[neurons]:
            raise BuildError(
                f"State name '{key}' overlaps with existing signal name")
        if is_array_like(init):
            model.sig[neurons][key] = Signal(initial_value=init,
                                             name=f"{neurons}.{key}")
            state[key] = model.sig[neurons][key]
        elif isinstance(init, np.random.RandomState):
            # Pass through RandomState instances
            state[key] = init
        else:
            raise BuildError(
                f"State '{key}' is of type '{type(init).__name__}'. Only array-likes "
                "and RandomStates are currently supported.")

    model.add_op(
        SimNeurons(neurons=neurontype,
                   J=input_sig,
                   output=output_sig,
                   state=state))
Beispiel #13
0
def build_neurons(model, neurontype, neurons):
    """Builds a `.NeuronType` object into a model.

    This function adds a `.SimNeurons` operator connecting the input current to the
    neural output signals, and handles any additional state variables defined
    within the neuron type.

    Parameters
    ----------
    model : Model
        The model to build into.
    neurontype : NeuronType
        Neuron type to build.
    neuron : Neurons
        The neuron population object corresponding to the neuron type.

    Notes
    -----
    Does not modify ``model.params[]`` and can therefore be called
    more than once with the same `.NeuronType` instance.
    """
    dtype = model.sig[neurons]["in"].dtype
    n_neurons = neurons.size_in
    rng = np.random.RandomState(model.seeds[neurons.ensemble] + 1)
    state_init = neurontype.make_state(n_neurons, rng=rng, dtype=dtype)
    state = {}

    for key, init in state_init.items():
        if key in model.sig[neurons]:
            raise BuildError("State name %r overlaps with existing signal name" % key)
        if is_array_like(init):
            model.sig[neurons][key] = Signal(
                initial_value=init, name="%s.%s" % (neurons, key)
            )
            state[key] = model.sig[neurons][key]
        elif isinstance(init, np.random.RandomState):
            # Pass through RandomState instances
            state[key] = init
        else:
            raise BuildError(
                "State %r is of type %r. Only array-likes and RandomStates are "
                "currently supported." % (key, type(init).__name__)
            )

    model.sig[neurons]["out"] = (
        state["spikes"] if neurontype.spiking else state["rates"]
    )
    model.add_op(
        SimNeurons(neurons=neurontype, J=model.sig[neurons]["in"], state=state)
    )
Beispiel #14
0
def __ufunc_add(a, b):
    """Operator that implements element-wise addition with Gyrus operator(s).

    If either operand is a Gyrus fold, then it is replaced with its underlying
    array of Gyrus operators to follow NumPy's broadcasting rules for np.add.

    If both operands are Gyrus operators, then they are added together by Nengo
    transform(s) that add every dimension, element-wise.

    If one of the operands is like an array, then it is expected to be either
    0D (scalar) or 1D (list). In the 0D case, the scalar is provided by a Nengo node,
    and broadcasted through Nengo transforms according to the shape of the other
    operand. In the 1D case, the list is expected to have the same number of elements
    as the size_out of each element in the other operand, such that the same vector can
    be added to every vector that is produced by the other operand.
    """
    args = _broadcast_folds(a, b)
    if args is not None:
        return asoperator(np.add(*args))
    elif isinstance(a, Operator) and isinstance(b, Operator):
        return reduce_transform([a, b], trs=[1, 1], axis=0)
    elif isinstance(b, Operator):
        a, b = b, a
    # At least one of the two operands has to be an Operator.
    assert isinstance(a, Operator)
    assert not isinstance(b, Operator)  # due to first if statement
    if is_array_like(b):
        # This can be generalized to handle a wider variety of cases,
        # But, similar to __ufunc_multiply we are keeping the behaviour
        # as unambiguous as possible for now.
        b = np.asarray(b)
        if np.all(b == 0):
            return a
        elif b.ndim == 0:
            # Creates a single scalar Node and then transforms it
            # according to the size_out of each element in a.
            b = broadcast_scalar(b, size_out=a.size_out)
        elif b.ndim == 1:
            # Repeats the same Node for every element in a.
            if np.any(a.size_out != len(b)):
                raise TypeError(
                    f"add size mismatch for a (operator) + b (array): "
                    f"a.size_out ({a.size_out}) must match len(b) ({len(b)})")
            b = asoperator(np.broadcast_to(stimulus(b), shape=a.shape))
        else:
            return NotImplemented
        assert isinstance(b, Operator)
        return np.add(a, b)
    return NotImplemented
Beispiel #15
0
def __ufunc_multiply(a, b):
    """Operator that implements element-wise multiplication with Gyrus operator(s).

    If either operand is a Gyrus fold, then it is replaced with its underlying
    array of Gyrus operators to follow NumPy's broadcasting rules for np.multiply.

    If both operands are Gyrus operators, then they are multiplied together by using
    ``gyrus.multiply``, which vectorizes an element-wise product network across both
    operands.

    If one of the operands is like an array, then it is expected to be either
    0D (scalar) or 1D (list). In either case, the operand becomes a Nengo transform
    that is applied to each Gyrus operator to scale its outputs element-wise.
    """
    # gyrus.multiply (used here and defined elsewhere) is a bit different from
    # the ufunc defined here, np.multiply. The former only supports multiplying two
    # operators. The latter delegates to the former (in the same way as __mul__), but
    # also delegates to transform to handle a wider variety of types. In particular, if
    # one of the two operands is not an operator, then it will be used as a transform on
    # the other operand. To make the semantics of this unambiguous, in a similar manner
    # to __ufunc_add, only 0D or 1D transforms are currently supported, such that the
    # transform is doing an element-wise multiplication on each element.
    args = _broadcast_folds(a, b)
    if args is not None:
        return asoperator(np.multiply(*args))
    elif isinstance(a, Operator) and isinstance(b, Operator):
        return multiply(a, b)
    elif isinstance(b, Operator):
        a, b = b, a
    # At least one of the two operands has to be an Operator.
    assert isinstance(a, Operator)
    assert not isinstance(b, Operator)  # due to first if statement
    if is_array_like(b):
        b = np.asarray(b)
        # Scalars (b.ndim == 0) are fine, as is, as they naturally work with
        # nengo.Connection. 1D arrays also work, as is, so long as their length is equal
        # to the number of input dimensions in Nengo, which will also be the number of
        # output dimensions.
        if np.all(b == 1):
            return a
        elif b.ndim == 1 and np.any(a.size_out != len(b)):
            raise TypeError(
                f"multiply size mismatch for a (operator) * b (array): "
                f"a.size_out ({a.size_out}) must match len(b) ({len(b)})")
        elif b.ndim >= 2:
            return NotImplemented
    return transform(a, tr=b)
Beispiel #16
0
 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,
                 )
Beispiel #17
0
    def coerce(self, node, output):
        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(
                "Invalid node output type %r" % type(output).__name__,
                attr=self.name,
                obj=node,
            )

        return output
Beispiel #18
0
    def coerce(self, node, output):
        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=np.float64)
            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("Invalid node output type %r" %
                                  type(output).__name__,
                                  attr=self.name, obj=node)

        return output
Beispiel #19
0
    def make_step(self, signals, dt, rng):
        src = signals[self.src]
        dst = signals[self.dst]
        src_slice = self.src_slice if self.src_slice is not None else Ellipsis
        dst_slice = self.dst_slice if self.dst_slice is not None else Ellipsis
        inc = self.inc

        # If there are repeated indices in dst_slice, special handling needed.
        repeats = False
        if npext.is_array_like(dst_slice):
            # copy because we might modify it
            dst_slice = np.array(dst_slice)
            if dst_slice.dtype.kind != "b":
                # get canonical, positive indices first
                dst_slice[dst_slice < 0] += len(dst)
                repeats = len(np.unique(dst_slice)) < len(dst_slice)

        if inc and repeats:

            def step_copy():
                np.add.at(dst, dst_slice, src[src_slice])

        elif inc:

            def step_copy():
                dst[dst_slice] += src[src_slice]

        elif repeats:
            raise BuildError(
                f"{self}: Cannot have repeated indices in "
                "``dst_slice`` when copy is not an increment"
            )
        else:

            def step_copy():
                dst[dst_slice] = src[src_slice]

        return step_copy
Beispiel #20
0
def equal(a, b):
    if is_array_like(a) or is_array_like(b):
        return np.array_equal(a, b)
    else:
        return a == b
Beispiel #21
0
def equal(a, b):
    """Check if two (possibly array-like) objects are equal."""
    if is_array_like(a) or is_array_like(b):
        return np.array_equal(a, b)
    else:
        return a == b
Beispiel #22
0
def equal(a, b):
    if is_array_like(a) or is_array_like(b):
        return np.array_equal(a, b)
    else:
        return a == b