Esempio n. 1
0
    def test_threadsafety(self):
        stack = ThreadLocalStack()
        stack.append(1)

        class CheckIndependence(ThreadedAssertion):
            def init_thread(self, worker):
                stack.append(2)

            def assert_thread(self, worker):
                assert list(stack) == [2]

        CheckIndependence(n_threads=2)
Esempio n. 2
0
    def test_implements_stack(self):
        stack = ThreadLocalStack()
        assert list(stack) == []
        stack.append(1)
        assert list(stack) == [1]
        stack.append(2)
        assert list(stack) == [1, 2]
        assert stack.pop() == 2
        assert list(stack) == [1]
        assert stack.pop() == 1
        assert list(stack) == []

        with pytest.raises(IndexError):
            stack.pop()
Esempio n. 3
0
    def test_has_size_limit(self):
        maxsize = 5
        stack = ThreadLocalStack(maxsize=maxsize)
        for i in range(maxsize):
            stack.append(i)

        with pytest.raises(RuntimeError):
            stack.append(5)
Esempio n. 4
0
class Config:
    """Configures network-level defaults and additional parameters.

    Every `.Network` contains an associated ``Config`` object which can
    be manipulated to change network-specific defaults, and to store
    additional parameters (for example, those specific to a backend).

    A ``Config`` object can configure objects of any class, but it has to be
    told the classes to configure first. This is either done on instantiation
    of the ``Config`` object or by calling the `.configures`  method.
    This sets up a mapping between configured class and a `.ClassParams`
    object that sets the default values for that class. Attempting to
    configure an instance of a configure class will create a mapping from
    that instance to an `.InstanceParams` object to configure additional
    parameters for that instance.

    Parameters
    ----------
    *configures
        The classes that this ``Config`` instance will configure.

    Attributes
    ----------
    params : dict
        Maps configured classes and instances to their `.ClassParams`
        or `.InstanceParams` object.

    Examples
    --------
    To configure defaults on a network:

    .. testcode::

       net = nengo.Network()
       net.config[nengo.Ensemble].radius = 1.5
       with net:
           ens = nengo.Ensemble(10, 1)
       ens.radius == 1.5  # True

    To add a new parameter to a Nengo object:

    .. testcode::

       net.config[nengo.Ensemble].set_param(
           'location', nengo.params.Parameter('location')
       )
       net.config[ens].location = 'cortex'

    To group together a set of parameters:

    .. testcode::

       gaba = nengo.Config(nengo.Connection)
       gaba[nengo.Connection].synapse = nengo.Lowpass(0.008)
       with net, gaba:
           conn = nengo.Connection(ens, ens)
       conn.synapse == nengo.Lowpass(0.008)  # True

    To configure a new type of object:

    .. testcode::

       class SynapseInfo:
           label = nengo.params.StringParam('label', default=None)
       gaba.configures(SynapseInfo)
       gaba[SynapseInfo].label = "GABA"  # Set default label

    """

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

    def __init__(self, *configures):
        self.params = {}
        if len(configures) > 0:
            self.configures(*configures)

    def __enter__(self):
        Config.context.append(self)
        return self

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

        config = Config.context.pop()

        if config is not self:
            raise ConfigError(
                "Config.context in bad state; was expecting "
                f"current context to be '{self}' but instead got "
                f"'{config}'.")

    def __contains__(self, key):
        raise TypeError(f"Cannot check if {key!r} is in a config.")

    def __getitem__(self, key):
        # If we have the exact thing, we'll just return it
        if key in self.params:
            return self.params[key]

        # If key is a class return a superclass's ClassParams
        if inspect.isclass(key):
            for cls in key.__mro__:
                if cls in self.params:
                    return self.params[cls]

            # If no superclass ClassParams, KeyError
            raise ConfigError(
                f"Type '{key.__name__}' is not set up for configuration. "
                f"Call 'configures({key.__name__})' first.")

        # For new instances, if we configure a class in the mro we're good
        for cls in type(key).__mro__:
            if cls in self.params:
                clsparams = self.params[cls]
                instparams = InstanceParams(key, clsparams)
                self.params[key] = instparams
                return instparams

        # If we don't configure the class, KeyError
        raise ConfigError(
            f"Type '{type(key).__name__}' is not set up for configuration. Call "
            f"configures('{type(key).__name__}') first.")

    def __repr__(self):
        classes = [key.__name__ for key in self.params if inspect.isclass(key)]
        return (f"<{type(self).__name__} at 0x{id(self):x} "
                f"configuring ({', '.join(classes)})>")

    def __str__(self):
        return "\n".join(str(v) for v in self.params.values())

    @staticmethod
    def all_defaults(nengo_cls=None):
        """Look up all of the default values in the current context.

        Parameters
        ----------
        nengo_cls : class, optional
            If specified, only the defaults for a particular class will
            be returned. If not specified, the defaults for all configured
            classes will be returned.

        Returns
        -------
        str
        """
        lines = []
        if nengo_cls is None:
            all_configured = set()
            for config in Config.context:
                all_configured.update(key for key in config.params
                                      if inspect.isclass(key))
            lines.extend([Config.all_defaults(key) for key in all_configured])
        else:
            lines.append(f"Current defaults for {nengo_cls.__name__}:")
            for attr in dir(nengo_cls):
                desc = getattr(nengo_cls, attr)
                if is_param(desc) and desc.configurable:
                    val = Config.default(nengo_cls, attr)
                    lines.append(f"  {attr}: {val}")
        return "\n".join(lines)

    @staticmethod
    def default(nengo_cls, param):
        """Look up the current default value for a parameter.

        The default is found by going through the config stack, from most
        specific to least specific. The network that an object is in
        is the most specific; the top-level network is the least specific.
        If no default is found there, then the parameter's default value
        is returned.
        """

        # Get the descriptor
        desc = getattr(nengo_cls, param)
        if not desc.configurable:
            raise ConfigError("Unconfigurable parameters have no defaults. "
                              "Please ensure you are not using the 'Default' "
                              "keyword with an unconfigurable parameter.")

        for config in reversed(Config.context):
            for cls in nengo_cls.__mro__:
                # If a default has been set for this config, return it
                if cls in config.params and config[cls] in desc:
                    return getattr(config[cls], param)

        # Otherwise, return the param default
        return desc.default

    def configures(self, *classes):
        """Start configuring a particular class and its instances."""
        if len(classes) == 0:
            raise TypeError("configures() takes 1 or more arguments (0 given)")
        for klass in classes:
            self.params[klass] = ClassParams(klass)
Esempio n. 5
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
Esempio n. 6
0
 def test_implements_clear(self):
     stack = ThreadLocalStack()
     stack.append(1)
     stack.clear()
     assert len(stack) == 0
Esempio n. 7
0
 def test_has_length(self):
     stack = ThreadLocalStack()
     assert len(stack) == 0
     stack.append(1)
     assert len(stack) == 1
Esempio n. 8
0
class Config(object):
    """Configures network-level behavior and backend specific parameters.

    Every ``Network`` contains an associated ``Config`` object which can
    be manipulated to change overall network behavior, and to store
    backend specific parameters. Subnetworks inherit the ``Config`` of
    their parent, but can be manipulated independently.
    The top-level network inherits ``nengo.toplevel_config``.

    Attributes
    ----------
    params : dict
        Maps configured classes and instances to their ``ClassParams``
        or ``InstanceParams`` object.

    Example
    -------
    >>> class A(object): pass
    >>> inst = A()
    >>> config = Config(A)
    >>> config[A].set_param('amount', Parameter(default=1))
    >>> print(config[inst].amount)
    1
    >>> config[inst].amount = 3
    >>> print(config[inst].amount)
    3
    >>> print(config[A].amount)
    1
    """

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

    def __init__(self, *configures):
        self.params = {}
        if len(configures) > 0:
            self.configures(*configures)

    def __enter__(self):
        Config.context.append(self)
        return self

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

        config = Config.context.pop()

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

    def __getitem__(self, key):
        # If we have the exact thing, we'll just return it
        if key in self.params:
            return self.params[key]

        # If key is a class return a superclass's ClassParams
        if inspect.isclass(key):
            for cls in key.__mro__:
                if cls in self.params:
                    return self.params[cls]

            # If no superclass ClassParams, KeyError
            raise ConfigError(
                "Type '%(name)s' is not set up for configuration. "
                "Call 'configures(%(name)s)' first." % {'name': key.__name__})

        # For new instances, if we configure a class in the mro we're good
        for cls in key.__class__.__mro__:
            if cls in self.params:
                clsparams = self.params[cls]
                instparams = InstanceParams(key, clsparams)
                self.params[key] = instparams
                return instparams

        # If we don't configure the class, KeyError
        raise ConfigError(
            "Type '%(name)s' is not set up for configuration. Call "
            "configures('%(name)s') first." % {'name': key.__class__.__name__})

    def __repr__(self):
        classes = [key.__name__ for key in self.params if inspect.isclass(key)]
        return "<%s(%s)>" % (self.__class__.__name__, ', '.join(classes))

    def __str__(self):
        return "\n".join(str(v) for v in itervalues(self.params))

    @staticmethod
    def all_defaults(nengo_cls=None):
        """Look up all of the default values in the current context.

        Parameters
        ----------
        nengo_cls : class, optional
            If specified, only the defaults for a particular class will
            be returned. If not specified, the defaults for all configured
            classes will be returned.

        Returns
        -------
        str
        """
        lines = []
        if nengo_cls is None:
            all_configured = set()
            for config in Config.context:
                all_configured.update(key for key in config.params
                                      if inspect.isclass(key))
            lines.extend([Config.all_defaults(key) for key in all_configured])
        else:
            lines.append("Current defaults for %s:" % nengo_cls.__name__)
            for attr in dir(nengo_cls):
                desc = getattr(nengo_cls, attr)
                if is_param(desc) and desc.configurable:
                    val = Config.default(nengo_cls, attr)
                    lines.append("  %s: %s" % (attr, val))
        return "\n".join(lines)

    @staticmethod
    def default(nengo_cls, param):
        """Look up the current default value for a parameter.

        The default is found by going through the config stack, from most
        specific to least specific. The network that an object is in
        is the most specific; the top-level network is the least specific.
        If no default is found there, then the parameter's default value
        is returned.
        """

        # Get the descriptor
        desc = getattr(nengo_cls, param)
        if not desc.configurable:
            raise ConfigError("Unconfigurable parameters have no defaults. "
                              "Please ensure you are not using the 'Default' "
                              "keyword with an unconfigurable parameter.")

        for config in reversed(Config.context):

            # If a default has been set for this config, return it
            if nengo_cls in config.params and config[nengo_cls] in desc:
                return getattr(config[nengo_cls], param)

        # Otherwise, return the param default
        return desc.default

    def configures(self, *classes):
        """Start configuring a particular class and its instances."""
        if len(classes) == 0:
            raise TypeError("configures() takes 1 or more arguments (0 given)")
        for klass in classes:
            self.params[klass] = ClassParams(klass)