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)
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()
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)
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)
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
def test_implements_clear(self): stack = ThreadLocalStack() stack.append(1) stack.clear() assert len(stack) == 0
def test_has_length(self): stack = ThreadLocalStack() assert len(stack) == 0 stack.append(1) assert len(stack) == 1
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)