Esempio n. 1
0
class RandomizedSVD(LeastSquaresSolver):
    """Solve a least-squares system using a randomized (partial) SVD.

    Useful for solving large matrices quickly, but non-optimally.

    Parameters
    ----------
    n_components : int, optional
        The number of SVD components to compute. A small survey of activity
        matrices suggests that the first 60 components capture almost all
        the variance.
    n_oversamples : int, optional
        The number of additional samples on the range of A.
    n_iter : int, optional
        The number of power iterations to perform (can help with noisy data).

    See also
    --------
    sklearn.utils.extmath.randomized_svd : Function used by this class
    """

    n_components = IntParam("n_components", low=1)
    n_oversamples = IntParam("n_oversamples", low=0)
    n_iter = IntParam("n_iter", low=0)

    def __init__(self, n_components=60, n_oversamples=10, n_iter=0):
        from sklearn.utils.extmath import (  # pylint: disable=import-outside-toplevel
            randomized_svd,
        )

        assert randomized_svd
        super().__init__()
        self.n_components = n_components
        self.n_oversamples = n_oversamples
        self.n_iter = n_iter

    def __call__(self, A, Y, sigma, rng=np.random):
        from sklearn.utils.extmath import (  # pylint: disable=import-outside-toplevel
            randomized_svd,
        )

        Y, m, n, _, matrix_in = format_system(A, Y)
        if min(m, n) <= self.n_components + self.n_oversamples:
            # more efficient to do a full SVD
            return SVD()(A, Y, sigma, rng=rng)

        U, s, V = randomized_svd(
            A,
            self.n_components,
            n_oversamples=self.n_oversamples,
            n_iter=self.n_iter,
            random_state=rng,
        )
        si = s / (s ** 2 + m * sigma ** 2)
        X = np.dot(V.T, si[:, None] * np.dot(U.T, Y))
        info = {"rmses": npext.rms(Y - np.dot(A, X), axis=0)}
        return X if matrix_in else X.ravel(), info
Esempio n. 2
0
class Superposition(Network):
    """Network for superposing multiple inputs.

    Parameters
    ----------
    n_inputs : int
        Number of inputs.
    vocab : Vocabulary or int
        The vocabulary to use to interpret the vector. If an integer is given,
        the default vocabulary of that dimensionality will be used.
    neurons_per_dimension : int, optional (Default: 200)
        Number of neurons to use in each dimension.
    kwargs : dict
        Keyword arguments passed through to `nengo_spa.Network`.

    Attributes
    ----------
    inputs : sequence
        Inputs.
    output : nengo.Node
        Output.
    """

    vocab = VocabularyOrDimParam('vocab', default=None, readonly=True)
    neurons_per_dimension = IntParam('neurons_per_dimension',
                                     default=200,
                                     low=1,
                                     readonly=True)
    n_inputs = IntParam('n_inputs', optional=False, low=1, readonly=True)

    def __init__(self,
                 n_inputs,
                 vocab=Default,
                 neurons_per_dimension=Default,
                 **kwargs):
        kwargs.setdefault('label', "Superposition")
        super(Superposition, self).__init__(**kwargs)

        self.vocab = vocab
        self.neurons_per_dimension = neurons_per_dimension
        self.n_inputs = n_inputs

        with self:
            self.superposition_net, self.inputs, self.output = (
                self.vocab.algebra.implement_superposition(
                    self.neurons_per_dimension, self.vocab.dimensions,
                    self.n_inputs))

        for inp in self.inputs:
            self.declare_input(inp, self.vocab)
        self.declare_output(self.output, self.vocab)
Esempio n. 3
0
class TensorNode(Node):
    """
    Inserts TensorFlow code into a Nengo model.

    Parameters
    ----------
    tensor_func : callable
        A function that maps node inputs to outputs
    size_in : int (Default: 0)
        The number of elements in the input vector
    size_out : int (Default: None)
        The number of elements in the output vector (if None, value will be
        inferred by calling ``tensor_func``)
    label : str (Default: None)
        A name for the node, used for debugging and visualization
    """

    tensor_func = TensorFuncParam('tensor_func')
    size_in = IntParam('size_in', default=0, low=0, optional=True)
    size_out = IntParam('size_out', default=None, low=1, optional=True)

    def __init__(self,
                 tensor_func,
                 size_in=Default,
                 size_out=Default,
                 label=Default):
        # pylint: disable=non-parent-init-called,super-init-not-called
        # note: we bypass the Node constructor, because we don't want to
        # perform validation on `output`
        NengoObject.__init__(self, label=label, seed=None)

        self.size_in = size_in
        self.size_out = size_out
        self.tensor_func = tensor_func

    @property
    def output(self):
        """
        Ensure that nothing tries to evaluate the `output` attribute
        (indicating that something is trying to simulate this as a regular
        `nengo.Node` rather than a TensorNode.
        """
        def output_func(*_):
            raise SimulationError(
                "Cannot call TensorNode output function (this probably means "
                "you are trying to use a TensorNode inside a Simulator other "
                "than NengoDL)")

        return output_func
Esempio n. 4
0
class RandomizedSVD(LeastSquaresSolver):
    """Solve a least-squares system using a randomized (partial) SVD.

    Parameters
    ----------
    n_components : int (default is 60)
        The number of SVD components to compute. A small survey of activity
        matrices suggests that the first 60 components capture almost all
        the variance.
    n_oversamples: int (default is 10)
        The number of additional samples on the range of A.
    n_iter : int (default is 0)
        The number of power iterations to perform (can help with noisy data).

    See also
    --------
    ``sklearn.utils.extmath.randomized_svd`` for details about the parameters.
    """

    n_components = IntParam('n_components', low=1)
    n_oversamples = IntParam('n_oversamples', low=0)
    n_iter = IntParam('n_iter', low=0)

    def __init__(self, n_components=60, n_oversamples=10, n_iter=0):
        from sklearn.utils.extmath import randomized_svd  # error early if DNE
        assert randomized_svd
        super(RandomizedSVD, self).__init__()
        self.n_components = n_components
        self.n_oversamples = n_oversamples
        self.n_iter = n_iter

    def __call__(self, A, Y, sigma, rng=np.random):
        from sklearn.utils.extmath import randomized_svd

        Y, m, n, _, matrix_in = format_system(A, Y)
        if min(m, n) <= self.n_components + self.n_oversamples:
            # more efficient to do a full SVD
            return SVD()(A, Y, sigma, rng=rng)

        U, s, V = randomized_svd(A,
                                 self.n_components,
                                 n_oversamples=self.n_oversamples,
                                 n_iter=self.n_iter,
                                 random_state=rng)
        si = s / (s**2 + m * sigma**2)
        X = np.dot(V.T, si[:, None] * np.dot(U.T, Y))
        info = {'rmses': npext.rms(Y - np.dot(A, X), axis=0)}
        return X if matrix_in else X.ravel(), info
Esempio n. 5
0
class Scalar(Network):
    """Represents a single scalar.

    Parameters
    ----------
    n_neurons : int, optional (Default: 50)
        Number of neurons to represent the scalar.
    **kwargs : dict
        Keyword arguments passed through to `nengo_spa.Network`.

    Attributes
    ----------
    input : nengo.Node
         Input.
    output : nengo.Node
        Output.
    """

    n_neurons = IntParam("n_neurons", default=50, low=1, readonly=True)

    def __init__(self, n_neurons=Default, **kwargs):
        super(Scalar, self).__init__(**kwargs)

        self.n_neurons = n_neurons

        with self:
            self.scalar = nengo.Ensemble(self.n_neurons, 1)

        self.input = self.scalar
        self.output = self.scalar
        self.declare_input(self.input, None)
        self.declare_output(self.output, None)
Esempio n. 6
0
class WeightSymmetryLR(nengo.learning_rules.LearningRuleType,
                       SupportDefaultsMixin):
    modifies = 'decoders'
    seed = IntParam('seed', default=None, optional=True, readonly=True)

    def __init__(self, learning_rate=1., seed=Default):
        super(WeightSymmetryLR, self).__init__(learning_rate, size_in=1)
        self.seed = seed
Esempio n. 7
0
class TensorNode(Node):
    """Inserts TensorFlow code into a Nengo model.  A TensorNode operates in
    much the same way as a :class:`~nengo:nengo.Node`, except its inputs and
    outputs are defined using TensorFlow operations.

    The TensorFlow code is defined in a function or callable class
    (``tensor_func``).  This function accepts the current simulation time as
    input, or the current simulation time and a Tensor ``x`` if
    ``node.size_in > 0``.  ``x`` will have shape
    ``(sim.minibatch_size, node.size_in``), and the function should return a
    Tensor with shape ``(sim.minibatch_size, node.size_out)``.
    ``node.size_out`` will be inferred by calling the function once and
    checking the output, if it isn't set when the Node is created.

    If ``tensor_func`` has a ``pre_build`` attribute, that function will be
    called once when the model is constructed.  This can be used to compute any
    constant values or set up variables -- things that don't need to
    execute every simulation timestep.

    Parameters
    ----------
    tensor_func : callable
        a function that maps node inputs to outputs
    size_in : int, optional (Default: 0)
        the number of elements in the input vector
    size_out : int, optional (Default: None)
        the number of elements in the output vector (if None, value will be
        inferred by calling ``tensor_func``)
    label : str, optional (Default: None)
        a name for the node, used for debugging and visualization
    """

    tensor_func = TensorFuncParam('tensor_func')
    size_in = IntParam('size_in', default=0, low=0, optional=True)
    size_out = IntParam('size_out', default=None, low=1, optional=True)

    def __init__(self, tensor_func, size_in=Default, size_out=Default,
                 label=Default):
        # note: we bypass the Node constructor, because we don't want to
        # perform validation on `output`
        NengoObject.__init__(self, label=label, seed=None)

        self.size_in = size_in
        self.size_out = size_out
        self.tensor_func = tensor_func
Esempio n. 8
0
def test_copy_instance_params():
    with nengo.Network() as original:
        original.config[nengo.Ensemble].set_param(
            "test", IntParam("test", optional=True))
        ens = nengo.Ensemble(10, 1)
        original.config[ens].test = 42

    cp = original.copy()
    assert cp.config[cp.ensembles[0]].test == 42
Esempio n. 9
0
class Compare(Network):
    """Computes the dot product of two inputs.

    Parameters
    ----------
    vocab : Vocabulary or int
        The vocabulary to use to interpret the vector. If an integer is given,
        the default vocabulary of that dimensionality will be used.
    neurons_per_dimension : int, optional (Default: 200)
        Number of neurons to use in each product computation.
    **kwargs : dict
        Keyword arguments passed through to `nengo_spa.Network`.

    Attributes
    ----------
    input_a : nengo.Node
        First input vector.
    input_b : nengo.Node
        Second input vector.
    output : nengo.Node
        Output.
    """

    vocab = VocabularyOrDimParam("vocab", default=None, readonly=True)
    neurons_per_dimension = IntParam("neurons_per_dimension",
                                     default=200,
                                     low=1,
                                     readonly=True)

    def __init__(self, vocab=Default, neurons_per_dimension=Default, **kwargs):
        super(Compare, self).__init__(**kwargs)

        self.vocab = vocab
        self.neurons_per_dimension = neurons_per_dimension

        with self:
            with nengo.Config(nengo.Ensemble) as cfg:
                cfg[nengo.Ensemble].eval_points = nengo.dists.CosineSimilarity(
                    self.vocab.dimensions + 2)
                cfg[nengo.Ensemble].intercepts = nengo.dists.CosineSimilarity(
                    self.vocab.dimensions + 2)
                self.product = nengo.networks.Product(
                    self.neurons_per_dimension, self.vocab.dimensions)
            self.output = nengo.Node(size_in=1, label="output")
            nengo.Connection(
                self.product.output,
                self.output,
                transform=np.ones((1, self.vocab.dimensions)),
            )

        self.input_a = self.product.input_a
        self.input_b = self.product.input_b

        self.declare_input(self.input_a, self.vocab)
        self.declare_input(self.input_b, self.vocab)
        self.declare_output(self.output, None)
Esempio n. 10
0
class ConstrainedConnectivity(Connectivity):
    """
    This class can be used to specify convergence and divergence parameters for
    a connection. Furthermore, an optional probability matrix or callable can be
    supplied that computes connection probabilities between individual neurons.
    """

    convergence = IntParam(
        name="convergence",
        optional=True,
        readonly=True,
    )

    divergence = IntParam(
        name="divergence",
        optional=True,
        readonly=True,
    )

    probabilities = ConnectivityProbabilitiesParam(
        name="probabilities",
        default=None,
        optional=True,
        readonly=True,
    )

    @property
    def _argreprs(self):
        return [
            "convergence={}".format(self.convergence),
            "divergence={}".format(self.divergence),
            "probabilities={}".format(self.probabilities),
        ]

    def __init__(self, convergence=None, divergence=None, probabilities=None):
        super().__init__()

        self.convergence = convergence
        self.divergence = divergence
        self.probabilities = probabilities
Esempio n. 11
0
def add_spinnaker_params(config):
    """Add SpiNNaker specific parameters to a configuration object."""
    # Add simulator parameters
    config.configures(Simulator)

    config[Simulator].set_param("placer", CallableParameter(default=par.place))
    config[Simulator].set_param("placer_kwargs", DictParam(default={}))

    config[Simulator].set_param("allocater",
                                CallableParameter(default=par.allocate))
    config[Simulator].set_param("allocater_kwargs", DictParam(default={}))

    config[Simulator].set_param("router", CallableParameter(default=par.route))
    config[Simulator].set_param("router_kwargs", DictParam(default={}))

    config[Simulator].set_param("node_io", Parameter(default=Ethernet))
    config[Simulator].set_param("node_io_kwargs", DictParam(default={}))

    # Add function_of_time parameters to Nodes
    config[nengo.Node].set_param("function_of_time", BoolParam(default=False))
    config[nengo.Node].set_param("function_of_time_period",
                                 NumberParam(default=None, optional=True))

    # Add multiple-core options to Nodes
    config[nengo.Node].set_param(
        "n_cores_per_chip",
        IntParam(default=None, low=1, high=16, optional=True))
    config[nengo.Node].set_param("n_chips",
                                 IntParam(default=None, low=1, optional=True))
    # Add optimisation control parameters to (passthrough) Nodes. None means
    # that a heuristic will be used to determine if the passthrough Node should
    # be removed.
    config[nengo.Node].set_param("optimize_out",
                                 BoolParam(default=None, optional=True))

    # Add profiling parameters to Ensembles
    config[nengo.Ensemble].set_param("profile", BoolParam(default=False))
    config[nengo.Ensemble].set_param("profile_num_samples",
                                     NumberParam(default=None, optional=True))
Esempio n. 12
0
class AML(nengo.learning_rules.LearningRuleType, SupportDefaultsMixin):
    """Association matrix learning rule (AML).

    Error is used as target vector and the pre-synaptic ensemble provides the
    cue vector.
    """
    error_type = 'decoded'
    modifies = 'decoders'
    seed = IntParam('seed', default=None, optional=True, readonly=True)

    def __init__(self, d, learning_rate=1., seed=Default):
        super(AML, self).__init__(learning_rate, size_in=d + 2)
        self.seed = seed
Esempio n. 13
0
class TestClass:
    """For testing that defaults are properly rendered in docs."""

    int_param = IntParam("int_param", default=1)
    str_param = StringParam("str_param", default="hello")

    def __init__(self,
                 int_param=Default,
                 str_param=Default,
                 module_param=np.random):
        """Init method"""

    def another_method(self, module_param=np.random):
        """A method"""
Esempio n. 14
0
class Process(object):
    """A general system with input, output, and state.

    Attributes
    ----------
    default_size_out : int
        If `d` isn't specified in `run` or `run_steps`, this will be used.
        Default: 1.
    default_dt : float
        If `dt` isn't specified in `run`, `run_steps`, `ntrange`, or `trange`,
        this will be used. Default: 0.001 (1 millisecond).
    """
    default_size_out = IntParam(low=0)
    default_dt = NumberParam(low=0, low_open=True)

    def __init__(self):
        self.default_size_out = 1
        self.default_dt = 0.001

    def make_step(self, size_in, size_out, dt, rng):
        raise NotImplementedError("Process must implement `make_step` method.")

    def run_steps(self, n_steps, d=None, dt=None, rng=np.random):
        # TODO: allow running with input
        d = self.default_size_out if d is None else d
        dt = self.default_dt if dt is None else dt
        step = self.make_step(0, d, dt, rng)
        output = np.zeros((n_steps, d))
        for i in range(n_steps):
            output[i] = step(i * dt)
        return output

    def run(self, t, d=None, dt=None, rng=np.random):
        # TODO: allow running with input
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.run_steps(n_steps, d=d, dt=dt, rng=rng)

    def ntrange(self, n_steps, dt=None):
        dt = self.default_dt if dt is None else dt
        return dt * np.arange(1, n_steps + 1)

    def trange(self, t, dt=None):
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.ntrange(n_steps, dt=dt)
Esempio n. 15
0
def test_copy_instance_params():
    with nengo.Network() as orig_net:
        orig_net.config[nengo.Ensemble].set_param(
            "test", IntParam("test", optional=True))
        orig_net.config[nengo.Ensemble].set_param(
            "test2", NdarrayParam("test2", optional=True))

        orig_ens = nengo.Ensemble(10, 1)
        orig_net.config[orig_ens].test = 42
        orig_net.config[orig_ens].test2 = np.array([49])

    # test copy function
    copy_net = orig_net.copy()
    copy_ens = copy_net.ensembles[0]
    assert copy_net.config[copy_ens].test == 42
    assert np.array_equal(copy_net.config[copy_ens].test2, np.array([49]))

    # test copy via pickle
    copy_net = pickle.loads(pickle.dumps(orig_net))
    copy_ens = copy_net.ensembles[0]
    assert copy_net.config[copy_ens].test == 42
    assert np.array_equal(copy_net.config[copy_ens].test2, np.array([49]))
Esempio n. 16
0
class Product(Network):
    """Multiplies two scalars.

    Parameters
    ----------
    n_neurons : int, optional (Default: 200)
        Number of neurons to use in product computation.
    kwargs : dict
        Keyword arguments passed through to `nengo_spa.Network`.

    Attributes
    ----------
    input_a : nengo.Node
        First input.
    input_b : nengo.Node
        Second input.
    output : nengo.Node
        Output.
    """

    n_neurons = IntParam('n_neurons', default=200, low=1, readonly=True)

    def __init__(self, n_neurons=Default, **kwargs):
        kwargs.setdefault('label', "Product")
        super(Product, self).__init__(**kwargs)

        self.n_neurons = n_neurons

        with self:
            self.product = nengo.networks.Product(self.n_neurons, 1)

        self.input_a = self.product.input_a
        self.input_b = self.product.input_b
        self.output = self.product.output

        self.declare_input(self.input_a, None)
        self.declare_input(self.input_b, None)
        self.declare_output(self.output, None)
Esempio n. 17
0
class Convolution(Transform):
    """An N-dimensional convolutional transform.

    The dimensionality of the convolution is determined by the input shape.

    .. versionadded:: 3.0.0

    Parameters
    ----------
    n_filters : int
        The number of convolutional filters to apply
    input_shape : tuple of int or `.ChannelShape`
        Shape of the input signal to the convolution; e.g.,
        ``(height, width, channels)`` for a 2D convolution with
        ``channels_last=True``.
    kernel_size : tuple of int, optional
        Size of the convolutional kernels (1 element for a 1D convolution,
        2 for a 2D convolution, etc.).
    strides : tuple of int, optional
        Stride of the convolution (1 element for a 1D convolution, 2 for
        a 2D convolution, etc.).
    padding : ``"same"`` or ``"valid"``, optional
        Padding method for input signal. "Valid" means no padding, and
        convolution will only be applied to the fully-overlapping areas of the
        input signal (meaning the output will be smaller). "Same" means that
        the input signal is zero-padded so that the output is the same shape
        as the input.
    channels_last : bool, optional
        If ``True`` (default), the channels are the last dimension in the input
        signal (e.g., a 28x28 image with 3 channels would have shape
        ``(28, 28, 3)``).  ``False`` means that channels are the first
        dimension (e.g., ``(3, 28, 28)``).
    init : `.Distribution` or `~numpy:numpy.ndarray`, optional
        A predefined kernel with shape
        ``kernel_size + (input_channels, n_filters)``, or a ``Distribution``
        that will be used to initialize the kernel.

    Notes
    -----
    As is typical in neural networks, this is technically correlation rather
    than convolution (because the kernel is not flipped).
    """

    n_filters = IntParam("n_filters", low=1)
    input_shape = ChannelShapeParam("input_shape", low=1)
    kernel_size = ShapeParam("kernel_size", low=1)
    strides = ShapeParam("strides", low=1)
    padding = EnumParam("padding", values=("same", "valid"))
    channels_last = BoolParam("channels_last")
    init = DistOrArrayParam("init")

    _param_init_order = ["channels_last", "input_shape"]

    def __init__(
            self,
            n_filters,
            input_shape,
            kernel_size=(3, 3),
            strides=(1, 1),
            padding="valid",
            channels_last=True,
            init=Uniform(-1, 1),
    ):
        super().__init__()

        self.n_filters = n_filters
        self.channels_last = channels_last  # must be set before input_shape
        self.input_shape = input_shape
        self.kernel_size = kernel_size
        self.strides = strides
        self.padding = padding
        self.init = init

        if len(kernel_size) != self.dimensions:
            raise ValidationError(
                "Kernel dimensions (%d) do not match input dimensions (%d)" %
                (len(kernel_size), self.dimensions),
                attr="kernel_size",
            )
        if len(strides) != self.dimensions:
            raise ValidationError(
                "Stride dimensions (%d) do not match input dimensions (%d)" %
                (len(strides), self.dimensions),
                attr="strides",
            )
        if not isinstance(init, Distribution):
            if init.shape != self.kernel_shape:
                raise ValidationError(
                    "Kernel shape %s does not match expected shape %s" %
                    (init.shape, self.kernel_shape),
                    attr="init",
                )

    @property
    def _argreprs(self):
        argreprs = [
            "n_filters=%r" % (self.n_filters, ),
            "input_shape=%s" % (self.input_shape.shape, ),
        ]
        if self.kernel_size != (3, 3):
            argreprs.append("kernel_size=%r" % (self.kernel_size, ))
        if self.strides != (1, 1):
            argreprs.append("strides=%r" % (self.strides, ))
        if self.padding != "valid":
            argreprs.append("padding=%r" % (self.padding, ))
        if self.channels_last is not True:
            argreprs.append("channels_last=%r" % (self.channels_last, ))
        return argreprs

    def sample(self, rng=np.random):
        if isinstance(self.init, Distribution):
            # we sample this way so that any variancescaling distribution based
            # on n/d is scaled appropriately
            kernel = [
                self.init.sample(self.input_shape.n_channels,
                                 self.n_filters,
                                 rng=rng)
                for _ in range(np.prod(self.kernel_size))
            ]
            kernel = np.reshape(kernel, self.kernel_shape)
        else:
            kernel = np.array(self.init, dtype=rc.float_dtype)
        return kernel

    @property
    def kernel_shape(self):
        """Full shape of kernel."""
        return self.kernel_size + (self.input_shape.n_channels, self.n_filters)

    @property
    def size_in(self):
        return self.input_shape.size

    @property
    def size_out(self):
        return self.output_shape.size

    @property
    def dimensions(self):
        """Dimensionality of convolution."""
        return self.input_shape.dimensions

    @property
    def output_shape(self):
        """Output shape after applying convolution to input."""
        output_shape = np.array(self.input_shape.spatial_shape,
                                dtype=rc.float_dtype)
        if self.padding == "valid":
            output_shape -= self.kernel_size
            output_shape += 1
        output_shape /= self.strides
        output_shape = tuple(np.ceil(output_shape).astype(rc.int_dtype))
        output_shape = (output_shape +
                        (self.n_filters, ) if self.channels_last else
                        (self.n_filters, ) + output_shape)

        return ChannelShape(output_shape, channels_last=self.channels_last)
Esempio n. 18
0
class Ensemble(NengoObject):
    """A group of neurons that collectively represent a vector.

    Parameters
    ----------
    n_neurons : int
        The number of neurons.
    dimensions : int
        The number of representational dimensions.
    radius : int, optional
        The representational radius of the ensemble.
    encoders : Distribution or ndarray (`n_neurons`, `dimensions`), optional
        The encoders, used to transform from representational space
        to neuron space. Each row is a neuron's encoder, each column is a
        representational dimension.
    intercepts : Distribution or ndarray (`n_neurons`), optional
        The point along each neuron's encoder where its activity is zero. If
        e is the neuron's encoder, then the activity will be zero when
        dot(x, e) <= c, where c is the given intercept.
    max_rates : Distribution or ndarray (`n_neurons`), optional
        The activity of each neuron when dot(x, e) = 1, where e is the neuron's
        encoder.
    eval_points : Distribution or ndarray (`n_eval_points`, `dims`), optional
        The evaluation points used for decoder solving, spanning the interval
        (-radius, radius) in each dimension, or a distribution from which to
        choose evaluation points. Default: ``UniformHypersphere``.
    n_eval_points : int, optional
        The number of evaluation points to be drawn from the `eval_points`
        distribution. If None (the default), then a heuristic is used to
        determine the number of evaluation points.
    neuron_type : Neurons, optional
        The model that simulates all neurons in the ensemble.
    noise : StochasticProcess, optional
        Random noise injected directly into each neuron in the ensemble
        as current. A sample is drawn for each individual neuron on
        every simulation step.
    seed : int, optional
        The seed used for random number generation.
    label : str, optional
        A name for the ensemble. Used for debugging and visualization.
    """

    n_neurons = IntParam(default=None, low=1)
    dimensions = IntParam(default=None, low=1)
    radius = NumberParam(default=1, low=1e-10)
    neuron_type = NeuronTypeParam(default=LIF())
    encoders = DistributionParam(default=UniformHypersphere(surface=True),
                                 sample_shape=('n_neurons', 'dimensions'))
    intercepts = DistributionParam(default=Uniform(-1.0, 1.0),
                                   optional=True,
                                   sample_shape=('n_neurons', ))
    max_rates = DistributionParam(default=Uniform(200, 400),
                                  optional=True,
                                  sample_shape=('n_neurons', ))
    n_eval_points = IntParam(default=None, optional=True)
    eval_points = DistributionParam(default=UniformHypersphere(),
                                    sample_shape=('*', 'dimensions'))
    bias = DistributionParam(default=None,
                             optional=True,
                             sample_shape=('n_neurons', ))
    gain = DistributionParam(default=None,
                             optional=True,
                             sample_shape=('n_neurons', ))
    noise = StochasticProcessParam(default=None, optional=True)
    seed = IntParam(default=None, optional=True)
    label = StringParam(default=None, optional=True)

    def __init__(self,
                 n_neurons,
                 dimensions,
                 radius=Default,
                 encoders=Default,
                 intercepts=Default,
                 max_rates=Default,
                 eval_points=Default,
                 n_eval_points=Default,
                 neuron_type=Default,
                 gain=Default,
                 bias=Default,
                 noise=Default,
                 seed=Default,
                 label=Default):

        self.n_neurons = n_neurons
        self.dimensions = dimensions
        self.radius = radius
        self.encoders = encoders
        self.intercepts = intercepts
        self.max_rates = max_rates
        self.label = label
        self.n_eval_points = n_eval_points
        self.eval_points = eval_points
        self.bias = bias
        self.gain = gain
        self.neuron_type = neuron_type
        self.noise = noise
        self.seed = seed
        self._neurons = Neurons(self)

    def __getitem__(self, key):
        return ObjView(self, key)

    def __len__(self):
        return self.dimensions

    @property
    def neurons(self):
        return self._neurons

    @neurons.setter
    def neurons(self, dummy):
        raise AttributeError("neurons cannot be overwritten.")

    @property
    def probeable(self):
        return ["decoded_output", "input"]

    @property
    def size_in(self):
        return self.dimensions

    @property
    def size_out(self):
        return self.dimensions
class BasalGanglia(Network):
    """Winner take all network, typically used for action selection.

    The basal ganglia network outputs approximately 0 at the dimension with
    the largest value, and is negative elsewhere.

    While the basal ganglia is primarily defined by its winner-take-all
    function, it is also organized to match the organization of the human
    basal ganglia. It consists of five ensembles:

    * Striatal D1 dopamine-receptor neurons (*strD1*)
    * Striatal D2 dopamine-receptor neurons (*strD2*)
    * Subthalamic nucleus (*stn*)
    * Globus pallidus internus / substantia nigra reticulata (*gpi*)
    * Globus pallidus externus (*gpe*)

    Interconnections between these areas are also based on known
    neuroanatomical connections. See [1]_ for more details, and [2]_ for
    the original non-spiking basal ganglia model by
    Gurney, Prescott & Redgrave that this model is based on.

    .. note:: The default `nengo.solvers.Solver` for the basal ganglia is
              `nengo.solvers.NnlsL2nz`, which requires SciPy. If SciPy is not
              installed, the global default solver will be used instead.

    Parameters
    ----------
    action_count : int
        Number of actions.
    n_neuron_per_ensemble : int, optional
        Number of neurons in each ensemble in the network.
    output_weight : float, optional
        A scaling factor on the output of the basal ganglia
        (specifically on the connection out of the GPi).
    input_bias : float, optional
        An amount by which to bias all dimensions of the input node.
        Biasing the input node is important for ensuring that all input
        dimensions are positive and easily comparable.
    ampa_synapse : Synapse, optional
        Synapse for connections corresponding to biological connections
        to AMPA receptors (i.e., connections from STN to to GPi and GPe).
    gaba_synapse : Synapse, optional
        Synapse for connections corresponding to biological connections
        to GABA receptors (i.e., connections from StrD1 to GPi, StrD2 to GPe,
        and GPe to GPi and STN).
    kwargs
        Passed through the `nengo_spa.Network`.

    Attributes
    ----------
    bias_input : nengo.Node or None
        If *input_bias* is non-zero, this node will be created to bias
        all of the dimensions of the input signal.
    gpe : nengo.networks.EnsembleArray
        Globus pallidus externus ensembles.
    gpi : nengo.networks.EnsembleArray
        Globus pallidus internus ensembles.
    input : nengo.Node
        Accepts the input signal.
    output : nengo.Node
        Provides the output signal.
    stn : nengo.networks.EnsembleArray
        Subthalamic nucleus ensembles.
    strD1 : nengo.networks.EnsembleArray
        Striatal D1 ensembles.
    strD2 : nengo.networks.EnsembleArray
        Striatal D2 ensembles.

    References
    ----------
    .. [1] Stewart, T. C., Choo, X., & Eliasmith, C. (2010).
       Dynamic behaviour of a spiking model of action selection in the
       basal ganglia. In Proceedings of the 10th international conference on
       cognitive modeling (pp. 235-40).
    .. [2] Gurney, K., Prescott, T., & Redgrave, P. (2001).
       A computational model of action selection in the basal
       ganglia. Biological Cybernetics 84, 401-423.
    """

    input_synapse = SynapseParam('input_synapse', default=Lowpass(0.002))
    ampa_synapse = SynapseParam('ampa_synapse', default=Lowpass(0.002))
    gaba_synapse = SynapseParam('gaba_synapse', default=Lowpass(0.008))
    n_neurons_per_ensemble = IntParam('n_neurons_per_ensemble',
                                      default=100,
                                      low=1,
                                      readonly=True)
    output_weight = NumberParam('output_weight', default=-3., readonly=True)
    input_bias = NumberParam('input_bias', default=0., readonly=True)

    def __init__(self,
                 action_count,
                 n_neurons_per_ensemble=Default,
                 output_weight=Default,
                 input_bias=Default,
                 ampa_synapse=Default,
                 gaba_synapse=Default,
                 sBCBG_params=None,
                 **kwargs):

        kwargs.setdefault('label', "Basal ganglia")
        super(BasalGanglia, self).__init__(**kwargs)

        self.action_count = action_count
        self.input_connections = {}
        self.input_bias = input_bias

        if BasalGanglia.sBCBG:

            # parameters
            filter_tau = .01
            if output_weight == Default:
                self.output_weight = -0.001

            import sBCBG
            if sBCBG_params == None:
                sBCBG_params = {}
            sBCBG.nengo_instantiate(
                self.action_count, self,
                sBCBG_params if sBCBG_params != None else {})

            with self:

                # connect input to CSN
                self.input = nengo.Node(label="input",
                                        size_in=self.action_count)
                scale = nengo.Ensemble(100, self.input.size_out)
                nengo.Connection(self.input, scale)
                for d in range(self.action_count):
                    nengo.Connection(scale[d],
                                     self.pops['CSN'][d],
                                     function=lambda x: 10 * x,
                                     synapse=.01,
                                     label='CSN input')

                # add bias input (BG performs best in the range 0.5--1.5)
                if abs(self.input_bias) > 0.0:
                    self.bias_input = nengo.Node(np.ones(self.action_count) *
                                                 input_bias,
                                                 label="basal ganglia bias")
                    nengo.Connection(self.bias_input, self.input)

                # connect GPi to output (inhibitory)
                decoding_weight = 1  # scaling of decoding GPi->out
                self.output = nengo.Node(label="output",
                                         size_in=self.action_count)
                for d in range(self.action_count):
                    GPi_ens = self.pops["GPi"][d]
                    decoder_values = np.ones(
                        (GPi_ens.n_neurons, 1)) * decoding_weight
                    nengo.Connection(
                        GPi_ens,
                        self.output[d],
                        synapse=nengo.synapses.Lowpass(filter_tau),
                        transform=self.output_weight,
                        #eval_points=eval_points)
                        solver=nengo.solvers.NoSolver(decoder_values))

        else:
            self.n_neurons_per_ensemble = n_neurons_per_ensemble
            self.ampa_synapse = ampa_synapse
            self.gaba_synapse = gaba_synapse
            self.output_weight = output_weight

            # Affects all ensembles / connections in the BG
            # unless overwritten with general_config
            config = nengo.Config(nengo.Ensemble, nengo.Connection)
            config[nengo.Ensemble].radius = 1.5
            config[nengo.Ensemble].encoders = nengo.dists.Choice([[1]])
            try:
                # Best, if we have SciPy
                config[nengo.Connection].solver = nengo.solvers.NnlsL2nz()
            except ImportError:
                warnings.warn("SciPy is not installed, so BasalGanglia will "
                              "use the default decoder solver. Installing "
                              "SciPy may improve BasalGanglia performance.")

            ea_params = {
                'n_neurons': self.n_neurons_per_ensemble,
                'n_ensembles': self.action_count
            }

            with self, config:
                self.strD1 = EnsembleArray(label="Striatal D1 neurons",
                                           intercepts=nengo.dists.Uniform(
                                               Weights.e, 1),
                                           **ea_params)
                self.strD2 = EnsembleArray(label="Striatal D2 neurons",
                                           intercepts=nengo.dists.Uniform(
                                               Weights.e, 1),
                                           **ea_params)
                self.stn = EnsembleArray(label="Subthalamic nucleus",
                                         intercepts=nengo.dists.Uniform(
                                             Weights.ep, 1),
                                         **ea_params)
                self.gpi = EnsembleArray(label="Globus pallidus internus",
                                         intercepts=nengo.dists.Uniform(
                                             Weights.eg, 1),
                                         **ea_params)
                self.gpe = EnsembleArray(label="Globus pallidus externus",
                                         intercepts=nengo.dists.Uniform(
                                             Weights.ee, 1),
                                         **ea_params)

                self.input = nengo.Node(label="input",
                                        size_in=self.action_count)
                self.output = nengo.Node(label="output",
                                         size_in=self.action_count)

                # add bias input (BG performs best in the range 0.5--1.5)
                if abs(self.input_bias) > 0.0:
                    self.bias_input = nengo.Node(np.ones(self.action_count) *
                                                 self.input_bias,
                                                 label="basal ganglia bias")
                    nengo.Connection(self.bias_input, self.input)

                # spread the input to StrD1, StrD2, and STN
                nengo.Connection(self.input,
                                 self.strD1.input,
                                 synapse=None,
                                 transform=Weights.ws * (1 + Weights.lg))
                nengo.Connection(self.input,
                                 self.strD2.input,
                                 synapse=None,
                                 transform=Weights.ws * (1 - Weights.le))
                nengo.Connection(self.input,
                                 self.stn.input,
                                 synapse=None,
                                 transform=Weights.wt)

                # connect the striatum to the GPi and GPe (inhibitory)
                strD1_output = self.strD1.add_output('func_str',
                                                     Weights.str_func)
                strD2_output = self.strD2.add_output('func_str',
                                                     Weights.str_func)
                self.gaba = nengo.Network("GABAergic connections")
                self.gaba.config[nengo.Connection].synapse = self.gaba_synapse
                with self.gaba:
                    nengo.Connection(strD1_output,
                                     self.gpi.input,
                                     transform=-Weights.wm)
                    nengo.Connection(strD2_output,
                                     self.gpe.input,
                                     transform=-Weights.wm)

                # connect the STN to GPi and GPe (broad and excitatory)
                tr = Weights.wp * np.ones(
                    (self.action_count, self.action_count))
                stn_output = self.stn.add_output('func_stn', Weights.stn_func)
                self.ampa = nengo.Network("AMPAergic connectiions")
                self.ampa.config[nengo.Connection].synapse = self.ampa_synapse
                with self.ampa:
                    nengo.Connection(stn_output, self.gpi.input, transform=tr)
                    nengo.Connection(stn_output, self.gpe.input, transform=tr)

                # connect the GPe to GPi and STN (inhibitory)
                gpe_output = self.gpe.add_output('func_gpe', Weights.gpe_func)
                with self.gaba:
                    nengo.Connection(gpe_output,
                                     self.gpi.input,
                                     transform=-Weights.we)
                    nengo.Connection(gpe_output,
                                     self.stn.input,
                                     transform=-Weights.wg)

                # connect GPi to output (inhibitory)
                gpi_output = self.gpi.add_output('func_gpi', Weights.gpi_func)
                nengo.Connection(gpi_output,
                                 self.output,
                                 synapse=None,
                                 transform=self.output_weight)

    def connect_input(self, source, transform=Default, index=None):
        self.input_connections[index] = nengo.Connection(
            source,
            self.input[index],
            transform=transform,
            synapse=self.input_synapse)
Esempio n. 20
0
class Conjgrad(LeastSquaresSolver):
    """Solve a least-squares system using conjugate gradient."""

    tol = NumberParam('tol', low=0)
    maxiters = IntParam('maxiters', low=1, optional=True)
    X0 = NdarrayParam('X0', shape=('*', '*'), optional=True)

    def __init__(self, tol=1e-2, maxiters=None, X0=None):
        super(Conjgrad, self).__init__()
        self.tol = tol
        self.maxiters = maxiters
        self.X0 = X0

    def __call__(self, A, Y, sigma, rng=None):
        Y, m, n, d, matrix_in = format_system(A, Y)
        X = np.zeros((n, d)) if self.X0 is None else np.array(self.X0)
        if X.shape != (n, d):
            raise ValidationError("Must be shape %s, got %s" %
                                  ((n, d), X.shape),
                                  attr='X0',
                                  obj=self)

        damp = m * sigma**2
        rtol = self.tol * np.sqrt(m)
        G = lambda x: np.dot(A.T, np.dot(A, x)) + damp * x
        B = np.dot(A.T, Y)

        iters = -np.ones(d, dtype='int')
        for i in range(d):
            X[:, i], iters[i] = self._conjgrad_iters(G,
                                                     B[:, i],
                                                     X[:, i],
                                                     maxiters=self.maxiters,
                                                     rtol=rtol)

        info = {'rmses': rmses(A, X, Y), 'iterations': iters}
        return X if matrix_in else X.ravel(), info

    @staticmethod
    def _conjgrad_iters(calcAx, b, x, maxiters=None, rtol=1e-6):
        """Solve a single-RHS linear system using conjugate gradient."""

        if maxiters is None:
            maxiters = b.shape[0]

        r = b - calcAx(x)
        p = r.copy()
        rsold = np.dot(r, r)

        for i in range(maxiters):
            Ap = calcAx(p)
            alpha = rsold / np.dot(p, Ap)
            x += alpha * p
            r -= alpha * Ap

            rsnew = np.dot(r, r)
            beta = rsnew / rsold

            if np.sqrt(rsnew) < rtol:
                break

            if beta < 1e-12:  # no perceptible change in p
                break

            # p = r + beta*p
            p *= beta
            p += r
            rsold = rsnew

        return x, i + 1
Esempio n. 21
0
class Node(NengoObject):
    """Provide non-neural inputs to Nengo objects and process outputs.

    Nodes can accept input, and perform arbitrary computations
    for the purpose of controlling a Nengo simulation.
    Nodes are typically not part of a brain model per se,
    but serve to summarize the assumptions being made
    about sensory data or other environment variables
    that cannot be generated by a brain model alone.

    Nodes can also be used to test models by providing specific input signals
    to parts of the model, and can simplify the input/output interface of a
    `~nengo.Network` when used as a relay to/from its internal
    ensembles (see `~nengo.networks.EnsembleArray` for an example).

    Parameters
    ----------
    output : callable, array_like, or None
        Function that transforms the Node inputs into outputs,
        a constant output value, or None to transmit signals unchanged.
    size_in : int, optional
        The number of dimensions of the input data parameter.
    size_out : int, optional
        The size of the output signal. If None, it will be determined
        based on the values of ``output`` and ``size_in``.
    label : str, optional
        A name for the node. Used for debugging and visualization.
    seed : int, optional
        The seed used for random number generation.
        Note: no aspects of the node are random, so currently setting
        this seed has no effect.

    Attributes
    ----------
    label : str
        The name of the node.
    output : callable, array_like, or None
        The given output.
    size_in : int
        The number of dimensions for incoming connection.
    size_out : int
        The number of output dimensions.
    """

    probeable = ('output', )

    output = OutputParam('output', default=None)
    size_in = IntParam('size_in', default=None, low=0, optional=True)
    size_out = IntParam('size_out', default=None, low=0, optional=True)

    def __init__(self,
                 output=Default,
                 size_in=Default,
                 size_out=Default,
                 label=Default,
                 seed=Default):
        if not (seed is Default or seed is None):
            raise NotImplementedError(
                "Changing the seed of a node has no effect")
        super().__init__(label=label, seed=seed)

        self.size_in = size_in
        self.size_out = size_out
        self.output = output  # Must be set after size_out; may modify size_out

    def __getitem__(self, key):
        return ObjView(self, key)

    def __len__(self):
        return self.size_out
Esempio n. 22
0
class SqrtBeta(Distribution):
    """Distribution of the square root of a Beta distributed random variable.

    Given ``n + m`` dimensional random unit vectors, the length of subvectors
    with ``m`` elements will be distributed according to this distribution.

    Parameters
    ----------
    n: int
        Number of subvectors.
    m: int, optional
        Length of each subvector.

    See also
    --------
    nengo.dists.SubvectorLength
    """

    n = IntParam("n", low=0)
    m = IntParam("m", low=0)

    def __init__(self, n, m=1):
        super().__init__()
        self.n = n
        self.m = m

    def sample(self, num, d=None, rng=np.random):
        shape = self._sample_shape(num, d)
        return np.sqrt(rng.beta(self.m / 2.0, self.n / 2.0, size=shape))

    def cdf(self, x):
        """Cumulative distribution function.

        .. note:: Requires SciPy.

        Parameters
        ----------
        x : array_like
            Evaluation points in [0, 1].

        Returns
        -------
        cdf : array_like
            Probability that ``X <= x``.
        """
        from scipy.special import betainc  # pylint: disable=import-outside-toplevel

        sq_x = x * x
        return np.where(sq_x < 1.0, betainc(self.m / 2.0, self.n / 2.0, sq_x),
                        np.ones_like(x))

    def pdf(self, x):
        """Probability distribution function.

        .. note:: Requires SciPy.

        Parameters
        ----------
        x : array_like
            Evaluation points in [0, 1].

        Returns
        -------
        pdf : array_like
            Probability density at ``x``.
        """
        from scipy.special import beta  # pylint: disable=import-outside-toplevel

        return (2 / beta(0.5 * self.m, 0.5 * self.n) * x**(self.m - 1) *
                (1 - x * x)**(0.5 * self.n - 1))

    def ppf(self, y):
        """Percent point function (inverse cumulative distribution).

        .. note:: Requires SciPy.

        Parameters
        ----------
        y : array_like
            Cumulative probabilities in [0, 1].

        Returns
        -------
        ppf : array_like
            Evaluation points ``x`` in [0, 1] such that ``P(X <= x) = y``.
        """
        from scipy.special import betaincinv  # pylint: disable=import-outside-toplevel

        sq_x = betaincinv(self.m / 2.0, self.n / 2.0, y)
        return np.sqrt(sq_x)
Esempio n. 23
0
class Ensemble(NengoObject):
    """A group of neurons that collectively represent a vector.

    Parameters
    ----------
    n_neurons : int
        The number of neurons.
    dimensions : int
        The number of representational dimensions.

    radius : int, optional (Default: 1.0)
        The representational radius of the ensemble.
    encoders : Distribution or (n_neurons, dimensions) array_like, optional \
               (Default: UniformHypersphere(surface=True))
        The encoders used to transform from representational space
        to neuron space. Each row is a neuron's encoder; each column is a
        representational dimension.
    intercepts : Distribution or (n_neurons,) array_like, optional \
                 (Default: ``nengo.dists.Uniform(-1.0, 1.0)``)
        The point along each neuron's encoder where its activity is zero. If
        ``e`` is the neuron's encoder, then the activity will be zero when
        ``dot(x, e) <= c``, where ``c`` is the given intercept.
    max_rates : Distribution or (n_neurons,) array_like, optional \
                (Default: ``nengo.dists.Uniform(200, 400)``)
        The activity of each neuron when the input signal ``x`` is magnitude 1
        and aligned with that neuron's encoder ``e``;
        i.e., when ``dot(x, e) = 1``.
    eval_points : Distribution or (n_eval_points, dims) array_like, optional \
                  (Default: ``nengo.dists.UniformHypersphere()``)
        The evaluation points used for decoder solving, spanning the interval
        (-radius, radius) in each dimension, or a distribution from which
        to choose evaluation points.
    n_eval_points : int, optional (Default: None)
        The number of evaluation points to be drawn from the ``eval_points``
        distribution. If None, then a heuristic is used to determine
        the number of evaluation points.
    neuron_type : `~nengo.neurons.NeuronType`, optional \
                  (Default: ``nengo.LIF()``)
        The model that simulates all neurons in the ensemble
        (see `~nengo.neurons.NeuronType`).
    gain : Distribution or (n_neurons,) array_like (Default: None)
        The gains associated with each neuron in the ensemble. If None, then
        the gain will be solved for using ``max_rates`` and ``intercepts``.
    bias : Distribution or (n_neurons,) array_like (Default: None)
        The biases associated with each neuron in the ensemble. If None, then
        the gain will be solved for using ``max_rates`` and ``intercepts``.
    noise : Process, optional (Default: None)
        Random noise injected directly into each neuron in the ensemble
        as current. A sample is drawn for each individual neuron on
        every simulation step.
    normalize_encoders : bool, optional (Default: True)
        Indicates whether the encoders should be normalized.
    label : str, optional (Default: None)
        A name for the ensemble. Used for debugging and visualization.
    seed : int, optional (Default: None)
        The seed used for random number generation.

    Attributes
    ----------
    bias : Distribution or (n_neurons,) array_like or None
        The biases associated with each neuron in the ensemble.
    dimensions : int
        The number of representational dimensions.
    encoders : Distribution or (n_neurons, dimensions) array_like
        The encoders, used to transform from representational space
        to neuron space. Each row is a neuron's encoder, each column is a
        representational dimension.
    eval_points : Distribution or (n_eval_points, dims) array_like
        The evaluation points used for decoder solving, spanning the interval
        (-radius, radius) in each dimension, or a distribution from which
        to choose evaluation points.
    gain : Distribution or (n_neurons,) array_like or None
        The gains associated with each neuron in the ensemble.
    intercepts : Distribution or (n_neurons) array_like or None
        The point along each neuron's encoder where its activity is zero. If
        ``e`` is the neuron's encoder, then the activity will be zero when
        ``dot(x, e) <= c``, where ``c`` is the given intercept.
    label : str or None
        A name for the ensemble. Used for debugging and visualization.
    max_rates : Distribution or (n_neurons,) array_like or None
        The activity of each neuron when ``dot(x, e) = 1``,
        where ``e`` is the neuron's encoder.
    n_eval_points : int or None
        The number of evaluation points to be drawn from the ``eval_points``
        distribution. If None, then a heuristic is used to determine
        the number of evaluation points.
    n_neurons : int or None
        The number of neurons.
    neuron_type : NeuronType
        The model that simulates all neurons in the ensemble
        (see ``nengo.neurons``).
    noise : Process or None
        Random noise injected directly into each neuron in the ensemble
        as current. A sample is drawn for each individual neuron on
        every simulation step.
    radius : int
        The representational radius of the ensemble.
    seed : int or None
        The seed used for random number generation.
    """

    probeable = ('decoded_output', 'input', 'scaled_encoders')

    n_neurons = IntParam('n_neurons', low=1)
    dimensions = IntParam('dimensions', low=1)
    radius = NumberParam('radius', default=1.0, low=1e-10)
    encoders = DistOrArrayParam('encoders',
                                default=UniformHypersphere(surface=True),
                                sample_shape=('n_neurons', 'dimensions'))
    intercepts = DistOrArrayParam('intercepts',
                                  default=Uniform(-1.0, 1.0),
                                  optional=True,
                                  sample_shape=('n_neurons', ))
    max_rates = DistOrArrayParam('max_rates',
                                 default=Uniform(200, 400),
                                 optional=True,
                                 sample_shape=('n_neurons', ))
    eval_points = DistOrArrayParam('eval_points',
                                   default=UniformHypersphere(),
                                   sample_shape=('*', 'dimensions'))
    n_eval_points = IntParam('n_eval_points', default=None, optional=True)
    neuron_type = NeuronTypeParam('neuron_type', default=LIF())
    gain = DistOrArrayParam('gain',
                            default=None,
                            optional=True,
                            sample_shape=('n_neurons', ))
    bias = DistOrArrayParam('bias',
                            default=None,
                            optional=True,
                            sample_shape=('n_neurons', ))
    noise = ProcessParam('noise', default=None, optional=True)
    normalize_encoders = BoolParam('normalize_encoders',
                                   default=True,
                                   optional=True)

    def __init__(self,
                 n_neurons,
                 dimensions,
                 radius=Default,
                 encoders=Default,
                 intercepts=Default,
                 max_rates=Default,
                 eval_points=Default,
                 n_eval_points=Default,
                 neuron_type=Default,
                 gain=Default,
                 bias=Default,
                 noise=Default,
                 normalize_encoders=Default,
                 label=Default,
                 seed=Default):
        super(Ensemble, self).__init__(label=label, seed=seed)
        self.n_neurons = n_neurons
        self.dimensions = dimensions
        self.radius = radius
        self.encoders = encoders
        self.intercepts = intercepts
        self.max_rates = max_rates
        self.n_eval_points = n_eval_points
        self.eval_points = eval_points
        self.bias = bias
        self.gain = gain
        self.neuron_type = neuron_type
        self.noise = noise
        self.normalize_encoders = normalize_encoders

    def __getitem__(self, key):
        return ObjView(self, key)

    def __len__(self):
        return self.dimensions

    @property
    def neurons(self):
        """A direct interface to the neurons in the ensemble."""
        return Neurons(self)

    @neurons.setter
    def neurons(self, dummy):
        raise ReadonlyError(attr="neurons", obj=self)

    @property
    def size_in(self):
        """The dimensionality of the ensemble."""
        return self.dimensions

    @property
    def size_out(self):
        """The dimensionality of the ensemble."""
        return self.dimensions
Esempio n. 24
0
class NengoObject(with_metaclass(NetworkMember)):
    """A base class for Nengo objects.

    Parameters
    ----------
    label : string
        A descriptive label for the object.
    seed : int
        The seed used for random number generation.

    Attributes
    ----------
    label : string
        A descriptive label for the object.
    seed : int
        The seed used for random number generation.
    """

    label = StringParam('label', default=None, optional=True)
    seed = IntParam('seed', default=None, optional=True)

    def __init__(self, label, seed):
        self.label = label
        self.seed = seed

    def __getstate__(self):
        raise NotImplementedError("Nengo objects do not support pickling")

    def __setstate__(self, state):
        raise NotImplementedError("Nengo objects do not support pickling")

    def __setattr__(self, name, val):
        if hasattr(self, '_initialized') and not hasattr(self, name):
            warnings.warn(
                "Creating new attribute '%s' on '%s'. "
                "Did you mean to change an existing attribute?" % (name, self),
                SyntaxWarning)
        if val is Default:
            val = Config.default(type(self), name)

        if rc.getboolean('exceptions', 'simplified'):
            try:
                super(NengoObject, self).__setattr__(name, val)
            except ValidationError:
                exc_info = sys.exc_info()
                reraise(exc_info[0], exc_info[1], None)
        else:
            super(NengoObject, self).__setattr__(name, val)

    def __str__(self):
        return self._str(
            include_id=not hasattr(self, 'label') or self.label is None)

    def __repr__(self):
        return self._str(include_id=True)

    def _str(self, include_id):
        return "<%s%s%s>" % (
            self.__class__.__name__, "" if not hasattr(self, 'label') else
            " (unlabeled)" if self.label is None else ' "%s"' % self.label,
            " at 0x%x" % id(self) if include_id else "")

    @classmethod
    def param_list(cls):
        """Returns a list of parameter names that can be set."""
        return (attr for attr in dir(cls) if is_param(getattr(cls, attr)))

    @property
    def params(self):
        """Returns a list of parameter names that can be set."""
        return self.param_list()
Esempio n. 25
0
class Process(FrozenObject):
    """A general system with input, output, and state.

    For more details on how to use processes and make
    custom process subclasses, see :doc:`examples/processes`.

    Parameters
    ----------
    default_size_in : int (Default: 0)
        Sets the default size in for nodes using this process.
    default_size_out : int (Default: 1)
        Sets the default size out for nodes running this process. Also,
        if ``d`` is not specified in `~.Process.run` or `~.Process.run_steps`,
        this will be used.
    default_dt : float (Default: 0.001 (1 millisecond))
        If ``dt`` is not specified in `~.Process.run`, `~.Process.run_steps`,
        `~.Process.ntrange`, or `~.Process.trange`, this will be used.
    seed : int, optional (Default: None)
        Random number seed. Ensures random factors will be the same each run.

    Attributes
    ----------
    default_dt : float
        If ``dt`` is not specified in `~.Process.run`, `~.Process.run_steps`,
        `~.Process.ntrange`, or `~.Process.trange`, this will be used.
    default_size_in : int
        The default size in for nodes using this process.
    default_size_out : int
        The default size out for nodes running this process. Also, if ``d`` is
        not specified in `~.Process.run` or `~.Process.run_steps`,
        this will be used.
    seed : int or None
        Random number seed. Ensures random factors will be the same each run.
    """

    default_size_in = IntParam('default_size_in', low=0)
    default_size_out = IntParam('default_size_out', low=0)
    default_dt = NumberParam('default_dt', low=0, low_open=True)
    seed = IntParam('seed', low=0, high=maxint, optional=True)

    def __init__(self,
                 default_size_in=0,
                 default_size_out=1,
                 default_dt=0.001,
                 seed=None):
        super(Process, self).__init__()
        self.default_size_in = default_size_in
        self.default_size_out = default_size_out
        self.default_dt = default_dt
        self.seed = seed

    def apply(self, x, d=None, dt=None, rng=np.random, copy=True, **kwargs):
        """Run process on a given input.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        x : ndarray
            The input signal given to the process.
        d : int, optional (Default: None)
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional (Default: None)
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.RandomState` (Default: ``numpy.random``)
            Random number generator used for stochstic processes.
        copy : bool, optional (Default: True)
            If True, a new output array will be created for output.
            If False, the input signal ``x`` will be overwritten.
        """
        shape_in = as_shape(np.asarray(x[0]).shape, min_dim=1)
        shape_out = as_shape(self.default_size_out if d is None else d)
        dt = self.default_dt if dt is None else dt
        rng = self.get_rng(rng)
        step = self.make_step(shape_in, shape_out, dt, rng, **kwargs)
        output = np.zeros((len(x), ) + shape_out) if copy else x
        for i, xi in enumerate(x):
            output[i] = step((i + 1) * dt, xi)
        return output

    def get_rng(self, rng):
        """Get a properly seeded independent RNG for the process step.

        Parameters
        ----------
        rng : `numpy.random.RandomState`
            The parent random number generator to use if the seed is not set.
        """
        seed = rng.randint(maxint) if self.seed is None else self.seed
        return np.random.RandomState(seed)

    def make_step(self, shape_in, shape_out, dt, rng):
        """Create function that advances the process forward one time step.

        This must be implemented by all custom processes.

        Parameters
        ----------
        shape_in : tuple
            The shape of the input signal.
        shape_out : tuple
            The shape of the output signal.
        dt : float
            The simulation timestep.
        rng : `numpy.random.RandomState`
            A random number generator.
        """
        raise NotImplementedError("Process must implement `make_step` method.")

    def run(self, t, d=None, dt=None, rng=np.random, **kwargs):
        """Run process without input for given length of time.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        t : float
            The length of time to run.
        d : int, optional (Default: None)
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional (Default: None)
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.RandomState` (Default: ``numpy.random``)
            Random number generator used for stochstic processes.
        """
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.run_steps(n_steps, d=d, dt=dt, rng=rng, **kwargs)

    def run_steps(self, n_steps, d=None, dt=None, rng=np.random, **kwargs):
        """Run process without input for given number of steps.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        n_steps : int
            The number of steps to run.
        d : int, optional (Default: None)
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional (Default: None)
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.RandomState` (Default: ``numpy.random``)
            Random number generator used for stochstic processes.
        """
        shape_in = as_shape(0)
        shape_out = as_shape(self.default_size_out if d is None else d)
        dt = self.default_dt if dt is None else dt
        rng = self.get_rng(rng)
        step = self.make_step(shape_in, shape_out, dt, rng, **kwargs)
        output = np.zeros((n_steps, ) + shape_out)
        for i in range(n_steps):
            output[i] = step((i + 1) * dt)
        return output

    def ntrange(self, n_steps, dt=None):
        """Create time points corresponding to a given number of steps.

        Parameters
        ----------
        n_steps : int
            The given number of steps.
        dt : float, optional (Default: None)
            Simulation timestep. If None, ``default_dt`` will be used.
        """
        dt = self.default_dt if dt is None else dt
        return dt * np.arange(1, n_steps + 1)

    def trange(self, t, dt=None):
        """Create time points corresponding to a given length of time.

        Parameters
        ----------
        t : float
            The given length of time.
        dt : float, optional (Default: None)
            Simulation timestep. If None, ``default_dt`` will be used.
        """
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.ntrange(n_steps, dt=dt)
Esempio n. 26
0
class TensorNode(Node):
    """Inserts TensorFlow code into a Nengo model.  A TensorNode operates in
    much the same way as a :class:`~nengo:nengo.Node`, except its inputs and
    outputs are defined using TensorFlow operations.

    The TensorFlow code is defined in a function or callable class
    (``tensor_func``).  This function accepts the current simulation time as
    input, or the current simulation time and a Tensor ``x`` if
    ``node.size_in > 0``.  ``x`` will have shape
    ``(sim.minibatch_size, node.size_in``), and the function should return a
    Tensor with shape ``(sim.minibatch_size, node.size_out)``.
    ``node.size_out`` will be inferred by calling the function once and
    checking the output, if it isn't set when the Node is created.

    If ``tensor_func`` has a ``pre_build`` attribute, that function will be
    called once when the model is constructed.  This can be used to compute any
    constant values or set up variables -- things that don't need to
    execute every simulation timestep.

    .. code-block:: python

        def pre_build(shape_in, shape_out):
            print(shape_in)  # (minibatch_size, node.size_in)
            print(shape_out)  # (minibatch_size, node.size_out)

    If ``tensor_func`` has a ``post_build`` attribute, that function will be
    called after the simulator is created and whenever it is reset.  This can
    be used to set any random elements in the TensorNode or perform any
    post-initialization setup required by the node (e.g., loading pretrained
    weights).

    .. code-block:: python

        def post_build(sess, rng):
            print(sess)  # the TensorFlow simulation session object
            print(rng)  # random number generator (np.random.RandomState)

    Parameters
    ----------
    tensor_func : callable
        A function that maps node inputs to outputs
    size_in : int, optional (Default: 0)
        The number of elements in the input vector
    size_out : int, optional (Default: None)
        The number of elements in the output vector (if None, value will be
        inferred by calling ``tensor_func``)
    label : str, optional (Default: None)
        A name for the node, used for debugging and visualization
    """

    tensor_func = TensorFuncParam('tensor_func')
    size_in = IntParam('size_in', default=0, low=0, optional=True)
    size_out = IntParam('size_out', default=None, low=1, optional=True)

    def __init__(self,
                 tensor_func,
                 size_in=Default,
                 size_out=Default,
                 label=Default):
        # pylint: disable=non-parent-init-called,super-init-not-called
        # note: we bypass the Node constructor, because we don't want to
        # perform validation on `output`
        NengoObject.__init__(self, label=label, seed=None)

        self.size_in = size_in
        self.size_out = size_out
        self.tensor_func = tensor_func

    @property
    def output(self):
        raise BuildError(
            "TensorNode does not have an `output` attribute (this probably "
            "means you are trying to use a TensorNode inside a Simulator "
            "other than Nengo DL)")
Esempio n. 27
0
class Process(FrozenObject):
    """A general system with input, output, and state.

    For more details on how to use processes and make
    custom process subclasses, see :doc:`examples/advanced/processes`.

    Parameters
    ----------
    default_size_in : int
        Sets the default size in for nodes using this process.
    default_size_out : int
        Sets the default size out for nodes running this process. Also,
        if ``d`` is not specified in `~.Process.run` or `~.Process.run_steps`,
        this will be used.
    default_dt : float
        If ``dt`` is not specified in `~.Process.run`, `~.Process.run_steps`,
        `~.Process.ntrange`, or `~.Process.trange`, this will be used.
    seed : int, optional
        Random number seed. Ensures random factors will be the same each run.

    Attributes
    ----------
    default_dt : float
        If ``dt`` is not specified in `~.Process.run`, `~.Process.run_steps`,
        `~.Process.ntrange`, or `~.Process.trange`, this will be used.
    default_size_in : int
        The default size in for nodes using this process.
    default_size_out : int
        The default size out for nodes running this process. Also, if ``d`` is
        not specified in `~.Process.run` or `~.Process.run_steps`,
        this will be used.
    seed : int or None
        Random number seed. Ensures random factors will be the same each run.
    """

    default_size_in = IntParam("default_size_in", low=0)
    default_size_out = IntParam("default_size_out", low=0)
    default_dt = NumberParam("default_dt", low=0, low_open=True)
    seed = IntParam("seed", low=0, high=maxint, optional=True)

    def __init__(self,
                 default_size_in=0,
                 default_size_out=1,
                 default_dt=0.001,
                 seed=None):
        super().__init__()
        self.default_size_in = default_size_in
        self.default_size_out = default_size_out
        self.default_dt = default_dt
        self.seed = seed

    def apply(self, x, d=None, dt=None, rng=np.random, copy=True, **kwargs):
        """Run process on a given input.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        x : ndarray
            The input signal given to the process.
        d : int, optional
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.mtrand.RandomState`
            Random number generator used for stochstic processes.
        copy : bool, optional
            If True, a new output array will be created for output.
            If False, the input signal ``x`` will be overwritten.
        """
        shape_in = as_shape(np.asarray(x[0]).shape, min_dim=1)
        shape_out = as_shape(self.default_size_out if d is None else d)
        dt = self.default_dt if dt is None else dt
        rng = self.get_rng(rng)
        state = self.make_state(shape_in, shape_out, dt)
        step = self.make_step(shape_in, shape_out, dt, rng, state, **kwargs)
        output = np.zeros((len(x), ) + shape_out) if copy else x
        for i, xi in enumerate(x):
            output[i] = step((i + 1) * dt, xi)
        return output

    def get_rng(self, rng):
        """Get a properly seeded independent RNG for the process step.

        Parameters
        ----------
        rng : `numpy.random.mtrand.RandomState`
            The parent random number generator to use if the seed is not set.
        """
        seed = rng.randint(maxint) if self.seed is None else self.seed
        return np.random.RandomState(seed)

    def make_state(self, shape_in, shape_out, dt, dtype=None):
        """Get a dictionary of signals to represent the state of this process.

        The builder uses this to allocate memory for the process state, so
        that the state can be represented as part of the whole simulator state.

        Parameters
        ----------
        shape_in : tuple
            The shape of the input signal.
        shape_out : tuple
            The shape of the output signal.
        dt : float
            The simulation timestep.
        dtype : `numpy.dtype`
            The data type requested by the builder. If `None`, then this
            function is free to choose the best type for the signals involved.

        Returns
        -------
        initial_state : {string: `numpy.ndarray`}
            A dictionary mapping keys to arrays containing the initial state
            values. The keys will be used to identify the signals in
            `.Process.make_step`.
        """
        return {}  # Implement if the process has a state

    def make_step(self, shape_in, shape_out, dt, rng, state):
        """Create function that advances the process forward one time step.

        This must be implemented by all custom processes. The parameters below
        indicate what information is provided by the builder.

        Parameters
        ----------
        shape_in : tuple
            The shape of the input signal.
        shape_out : tuple
            The shape of the output signal.
        dt : float
            The simulation timestep.
        rng : `numpy.random.mtrand.RandomState`
            A random number generator.
        state : {string: `numpy.ndarray`}
            A dictionary mapping keys to signals, where the signals fully
            represent the state of the process. The signals are initialized
            by `.Process.make_state`.
        """
        raise NotImplementedError("Process must implement `make_step` method.")

    def run(self, t, d=None, dt=None, rng=np.random, **kwargs):
        """Run process without input for given length of time.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        t : float
            The length of time to run.
        d : int, optional
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.mtrand.RandomState`
            Random number generator used for stochstic processes.
        """
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.run_steps(n_steps, d=d, dt=dt, rng=rng, **kwargs)

    def run_steps(self, n_steps, d=None, dt=None, rng=np.random, **kwargs):
        """Run process without input for given number of steps.

        Keyword arguments that do not appear in the parameter list below
        will be passed to the ``make_step`` function of this process.

        Parameters
        ----------
        n_steps : int
            The number of steps to run.
        d : int, optional
            Output dimensionality. If None, ``default_size_out`` will be used.
        dt : float, optional
            Simulation timestep. If None, ``default_dt`` will be used.
        rng : `numpy.random.mtrand.RandomState`
            Random number generator used for stochstic processes.
        """
        shape_in = as_shape(0)
        shape_out = as_shape(self.default_size_out if d is None else d)
        dt = self.default_dt if dt is None else dt
        rng = self.get_rng(rng)
        state = self.make_state(shape_in, shape_out, dt)
        step = self.make_step(shape_in, shape_out, dt, rng, state, **kwargs)
        output = np.zeros((n_steps, ) + shape_out)
        for i in range(n_steps):
            output[i] = step((i + 1) * dt)
        return output

    def ntrange(self, n_steps, dt=None):
        """Create time points corresponding to a given number of steps.

        Parameters
        ----------
        n_steps : int
            The given number of steps.
        dt : float, optional
            Simulation timestep. If None, ``default_dt`` will be used.
        """
        dt = self.default_dt if dt is None else dt
        return dt * np.arange(1, n_steps + 1)

    def trange(self, t, dt=None):
        """Create time points corresponding to a given length of time.

        Parameters
        ----------
        t : float
            The given length of time.
        dt : float, optional
            Simulation timestep. If None, ``default_dt`` will be used.
        """
        dt = self.default_dt if dt is None else dt
        n_steps = int(np.round(float(t) / dt))
        return self.ntrange(n_steps, dt=dt)
Esempio n. 28
0
class Transcode(Network):
    """Transcode from, to, and between Semantic Pointers.

    This can thought of the equivalent of a `nengo.Node` for Semantic Pointers.

    Either the *input_vocab* or the *output_vocab* argument must not be *None*.
    (If you want both arguments to be *None*, use a normal `nengo.Node`.)
    Which one of the parameters in the pairs *input_vocab/size_in* and
    *output_vocab/size_out* is not set to *None*, determines whether a Semantic
    Pointer input/output or a normal vector input/output is expected.

    Parameters
    ----------
    function : func, optional (Default: None)
        Function that transforms the input Semantic Pointer to an output
        Semantic Pointer. The function signature depends on *input_vocab*:

        * If *input_vocab* is *None*, the allowed signatures are the same as
          for a `nengo.Node`. Either ``function(t)`` or ``function(t, x)``
          where *t* (float) is the current simulation time and *x* (NumPy
          array) is the current input to transcode with size *size_in*.
        * If *input_vocab* is not *None*, the signature has to be
          ``function(t, sp)`` where *t* (float) is the current simulation time
          and *sp* (`.SemanticPointer`) is the current Semantic Pointer input.
          The associated vocabulary can be obtained via ``sp.vocab``.

        The allowed function return value depends on *output_vocab*:

        * If *output_vocab* is *None*, the return value must be a NumPy array
          (or equivalent) of size *size_out* or *None* (i.e. no return value)
          if *size_out* is *None*.
        * If *output_vocab* is not *None*, the return value can be either of:
          NumPy array, `.SemanticPointer` instance, or an SemanticPointer
          expression or symbolic expression as string that gets parsed with
          the *output_vocab*.
    input_vocab : Vocabulary, optional (Default: None)
        Input vocabulary. Mutually exclusive with *size_in*.
    output_vocab : Vocabulary, optional (Default: None)
        Output vocabulary. Mutually exclusive with *size_out*.
    size_in : int, optional (Default: None)
        Input size. Mutually exclusive with *input_vocab*.
    size_out : int, optional (Default: None)
        Output size. Mutually exclusive with *output_vocab*.
    **kwargs : dict
        Additional keyword arguments passed to `nengo_spa.Network`.

    Attributes
    ----------
    input : nengo.Node
        Input.
    output : nengo.Node
        Output.
    """

    function = TranscodeFunctionParam(
        "function", optional=True, default=None, readonly=True
    )
    input_vocab = VocabularyOrDimParam(
        "input_vocab", optional=True, default=None, readonly=True
    )
    output_vocab = VocabularyOrDimParam(
        "output_vocab", optional=True, default=None, readonly=True
    )
    size_in = IntParam("size_in", optional=True, default=None, readonly=True)
    size_out = IntParam("size_out", optional=True, default=None, readonly=True)

    def __init__(
        self,
        function=Default,
        input_vocab=Default,
        output_vocab=Default,
        size_in=Default,
        size_out=Default,
        **kwargs
    ):
        super(Transcode, self).__init__(**kwargs)

        # Vocabs need to be set before function which accesses vocab for
        # validation.
        self.input_vocab = input_vocab
        self.output_vocab = output_vocab
        self.size_in = size_in
        self.size_out = size_out

        if self.input_vocab is None and self.output_vocab is None:
            raise ValidationError(
                "At least one of input_vocab and output_vocab needs to be "
                "set. If neither the input nor the output is a Semantic "
                "Pointer, use a basic nengo.Node instead.",
                self,
            )
        if self.input_vocab is not None and self.size_in is not None:
            raise ValidationError(
                "The input_vocab and size_in arguments are mutually " "exclusive.",
                "size_in",
                self,
            )
        if self.output_vocab is not None and self.size_out is not None:
            raise ValidationError(
                "The output_vocab and size_out arguments are mutually " "exclusive.",
                "size_in",
                self,
            )

        self.function = function

        node_size_in = (
            self.input_vocab.dimensions
            if self.input_vocab is not None
            else self.size_in
        )
        node_size_out = (
            self.output_vocab.dimensions
            if self.output_vocab is not None
            else self.size_out
        )
        if self.function is None:
            if node_size_in is None:
                node_size_in = self.output_vocab.dimensions
            node_size_out = None

        with self:
            self.node = nengo.Node(
                TranscodeFunctionParam.to_node_output(
                    self.function, self.input_vocab, self.output_vocab
                ),
                size_in=node_size_in,
                size_out=node_size_out,
            )
            self.input = self.node
            self.output = self.node

        if self.input_vocab is not None:
            self.declare_input(self.input, self.input_vocab)
        if self.output_vocab is not None:
            self.declare_output(self.output, self.output_vocab)
Esempio n. 29
0
class NengoObject(SupportDefaultsMixin, metaclass=NetworkMember):
    """A base class for Nengo objects.

    Parameters
    ----------
    label : string
        A descriptive label for the object.
    seed : int
        The seed used for random number generation.

    Attributes
    ----------
    label : string
        A descriptive label for the object.
    seed : int
        The seed used for random number generation.
    """

    # Order in which parameters have to be initialized.
    # Missing parameters will be initialized last in an undefined order.
    # This is needed for pickling and copying of Nengo objects when the
    # parameter initialization order matters.
    _param_init_order = []

    label = StringParam("label", default=None, optional=True)
    seed = IntParam("seed", default=None, optional=True)

    def __init__(self, label, seed):
        super().__init__()
        self._initialized = False
        self.label = label
        self.seed = seed

    def __getstate__(self):
        state = self.__dict__.copy()
        state["_initialized"] = False

        for attr in self.params:
            param = getattr(type(self), attr)
            if self in param:
                state[attr] = getattr(self, attr)

        return state

    def __setstate__(self, state):
        for attr in self._param_init_order:
            setattr(self, attr, state.pop(attr))

        for attr in self.params:
            if attr in state:
                setattr(self, attr, state.pop(attr))

        for k, v in state.items():
            setattr(self, k, v)

        self._initialized = True
        if len(nengo.Network.context) > 0:
            warnings.warn(NotAddedToNetworkWarning(self))

    def __setattr__(self, name, val):
        initialized = hasattr(self, "_initialized") and self._initialized
        if initialized and not hasattr(self, name):
            warnings.warn(
                "Creating new attribute '%s' on '%s'. "
                "Did you mean to change an existing attribute?" % (name, self),
                SyntaxWarning,
            )
        super().__setattr__(name, val)

    def __str__(self):
        return self._str(
            include_id=not hasattr(self, "label") or self.label is None)

    def __repr__(self):
        return self._str(include_id=True)

    def _str(self, include_id):
        return "<%s%s%s>" % (
            type(self).__name__,
            "" if not hasattr(self, "label") else
            " (unlabeled)" if self.label is None else ' "%s"' % self.label,
            " at 0x%x" % id(self) if include_id else "",
        )

    @property
    def params(self):
        """Returns a list of parameter names that can be set."""
        return list(iter_params(self))

    def copy(self, add_to_container=True):
        with warnings.catch_warnings():
            # We warn when copying since we can't change add_to_container.
            # However, we deal with it here, so we ignore the warning.
            warnings.simplefilter("ignore", category=NotAddedToNetworkWarning)
            c = copy(self)
        if add_to_container:
            nengo.Network.add(c)
        return c
Esempio n. 30
0
class Network:
    """A network contains ensembles, nodes, connections, and other networks.

    A network is primarily used for grouping together related
    objects and connections for visualization purposes.
    However, you can also use networks as a nice way to reuse
    network creation code.

    To group together related objects that you do not need to reuse,
    you can create a new ``Network`` and add objects in a ``with`` block.
    For example:

    .. testcode::

       network = nengo.Network()
       with network:
           with nengo.Network(label="Vision"):
               v1 = nengo.Ensemble(n_neurons=100, dimensions=2)
           with nengo.Network(label="Motor"):
               sma = nengo.Ensemble(n_neurons=100, dimensions=2)
           nengo.Connection(v1, sma)

    To reuse a group of related objects, you can create a new subclass
    of ``Network``, and add objects in the ``__init__`` method.
    For example:

    .. testcode::

       class OcularDominance(nengo.Network):
           def __init__(self):
               self.column = nengo.Ensemble(n_neurons=100, dimensions=2)

       network = nengo.Network()
       with network:
           left_eye = OcularDominance()
           right_eye = OcularDominance()
           nengo.Connection(left_eye.column, right_eye.column)

    Parameters
    ----------
    label : str, optional
        Name of the network.
    seed : int, optional
        Random number seed that will be fed to the random number generator.
        Setting the seed makes the network's build process deterministic.
    add_to_container : bool, optional
        Determines if this network will be added to the current container.
        If None, this network will be added to the network at the top of the
        ``Network.context`` stack unless the stack is empty.

    Attributes
    ----------
    connections : list
        `.Connection` instances in this network.
    ensembles : list
        `.Ensemble` instances in this network.
    label : str
        Name of this network.
    networks : list
        `.Network` instances in this network.
    nodes : list
        `.Node` instances in this network.
    probes : list
        `.Probe` instances in this network.
    seed : int
        Random seed used by this network.
    """

    context = ThreadLocalStack(maxsize=100)  # static stack of Network objects

    label = StringParam("label", optional=True, readonly=False)
    seed = IntParam("seed", optional=True, readonly=False)

    def __init__(self, label=None, seed=None, add_to_container=None):
        self.label = label
        self.seed = seed
        self._config = self.default_config()

        self._objects = {
            Ensemble: [],
            Node: [],
            Connection: [],
            Network: [],
            Probe: []
        }
        self._ensembles = self.objects[Ensemble]
        self._nodes = self.objects[Node]
        self._connections = self.objects[Connection]
        self._networks = self.objects[Network]
        self._probes = self.objects[Probe]

        # By default, we want to add to the current context, unless there is
        # no context; i.e., we're creating a top-level network.
        if add_to_container is None:
            add_to_container = len(Network.context) > 0

        if add_to_container:
            Network.add(self)

    @staticmethod
    def add(obj):
        """Add the passed object to ``Network.context``."""
        if len(Network.context) == 0:
            raise NetworkContextError(
                "'%s' must either be created inside a ``with network:`` "
                "block, or set add_to_container=False in the object's "
                "constructor." % obj)
        network = Network.context[-1]
        if not isinstance(network, Network):
            raise NetworkContextError("Current context (%s) is not a network" %
                                      network)
        for cls in type(obj).__mro__:
            if cls in network.objects:
                network.objects[cls].append(obj)
                break
        else:
            raise NetworkContextError("Objects of type %r cannot be added to "
                                      "networks." % type(obj).__name__)

    @staticmethod
    def default_config():
        """Constructs a `~.Config` object for setting defaults."""
        return Config(Connection, Ensemble, Node, Probe)

    def _all_objects(self, object_type):
        """Returns a list of all objects of the specified type."""
        # Make a copy of this network's list
        objects = list(self.objects[object_type])
        for subnet in self.networks:
            objects.extend(subnet._all_objects(object_type))
        return objects

    @property
    def all_objects(self):
        """(list) All objects in this network and its subnetworks."""
        objects = []
        for object_type in self.objects:
            objects.extend(self._all_objects(object_type))
        return objects

    @property
    def all_ensembles(self):
        """(list) All ensembles in this network and its subnetworks."""
        return self._all_objects(Ensemble)

    @property
    def all_nodes(self):
        """(list) All nodes in this network and its subnetworks."""
        return self._all_objects(Node)

    @property
    def all_networks(self):
        """(list) All networks in this network and its subnetworks."""
        return self._all_objects(Network)

    @property
    def all_connections(self):
        """(list) All connections in this network and its subnetworks."""
        return self._all_objects(Connection)

    @property
    def all_probes(self):
        """(list) All probes in this network and its subnetworks."""
        return self._all_objects(Probe)

    @property
    def objects(self):
        return self._objects

    @objects.setter
    def objects(self, _):
        raise ReadonlyError(attr="objects", obj=self)

    @property
    def ensembles(self):
        return self._ensembles

    @ensembles.setter
    def ensembles(self, _):
        raise ReadonlyError(attr="ensembles", obj=self)

    @property
    def nodes(self):
        return self._nodes

    @nodes.setter
    def nodes(self, _):
        raise ReadonlyError(attr="nodes", obj=self)

    @property
    def networks(self):
        return self._networks

    @networks.setter
    def networks(self, _):
        raise ReadonlyError(attr="networks", obj=self)

    @property
    def connections(self):
        return self._connections

    @connections.setter
    def connections(self, _):
        raise ReadonlyError(attr="connections", obj=self)

    @property
    def probes(self):
        return self._probes

    @probes.setter
    def probes(self, _):
        raise ReadonlyError(attr="probes", obj=self)

    @property
    def config(self):
        """(`.Config`) Configuration for this network."""
        return self._config

    @config.setter
    def config(self, _):
        raise ReadonlyError(attr="config", obj=self)

    @property
    def n_neurons(self):
        """(int) Number of neurons in this network, including subnetworks."""
        return sum(ens.n_neurons for ens in self.all_ensembles)

    def __contains__(self, obj):
        return type(obj) in self.objects and obj in self.objects[type(obj)]

    def __enter__(self):
        Network.context.append(self)
        self._config.__enter__()
        return self

    def __exit__(self, dummy_exc_type, dummy_exc_value, dummy_tb):
        if len(Network.context) == 0:
            raise NetworkContextError(
                "Network.context in bad state; was empty when "
                "exiting from a 'with' block.")

        config = Config.context[-1]
        if config is not self._config:
            raise ConfigError("Config.context in bad state; was expecting "
                              "current context to be '%s' but instead got "
                              "'%s'." % (self._config, config))

        network = Network.context.pop()
        if network is not self:
            raise NetworkContextError(
                "Network.context in bad state; was expecting current context "
                "to be '%s' but instead got '%s'." % (self, network))

        self._config.__exit__(dummy_exc_type, dummy_exc_value, dummy_tb)

    def __getstate__(self):
        state = self.__dict__.copy()
        state["label"] = self.label
        state["seed"] = self.seed
        return state

    def __setstate__(self, state):
        for k, v in state.items():
            setattr(self, k, v)
        if len(Network.context) > 0:
            warnings.warn(NotAddedToNetworkWarning(self))

    def __str__(self):
        return "<%s %s>" % (
            type(self).__name__,
            '"%s"' % self.label
            if self.label is not None else "(unlabeled) at 0x%x" % id(self),
        )

    def __repr__(self):
        return "<%s %s %s>" % (
            type(self).__name__,
            '"%s"' % self.label if self.label is not None else "(unlabeled)",
            "at 0x%x" % id(self),
        )

    def copy(self, add_to_container=None):
        with warnings.catch_warnings():
            # We warn when copying since we can't change add_to_container.
            # However, we deal with it here, so we ignore the warning.
            warnings.simplefilter("ignore", category=NotAddedToNetworkWarning)
            c = deepcopy(self)
        if add_to_container is None:
            add_to_container = len(Network.context) > 0
        if add_to_container:
            Network.add(c)
        return c