Source code for nengo_loihi.builder.builder

from collections import defaultdict, OrderedDict
import logging

from nengo import Ensemble, Network, Node, Probe
from nengo.builder import Model as NengoModel
from nengo.builder.builder import Builder as NengoBuilder
from nengo.builder.network import build_network
from nengo.cache import NoDecoderCache

from nengo_loihi.block import LoihiBlock
from nengo_loihi.decode_neurons import Preset10DecodeNeurons, OnOffDecodeNeurons
from nengo_loihi.inputs import LoihiInput

logger = logging.getLogger(__name__)


[docs]class Model: """The data structure for the emulator/hardware simulator. Defines methods for adding inputs and blocks. Also handles build functions, and information associated with building the Nengo model. Some of the Model attributes can be modified before the build process to change how certain build functions work. Those attributes are listed first in the attributes section below. To change them, create an instance of `.Model` and pass it in to `.Simulator` along with your network:: model = nengo_loihi.builder.Model(dt=0.001) model.pes_error_scale = 50. with nengo_loihi.Simulator(network, model=model) as sim: sim.run(1.0) Parameters ---------- dt : float, optional (Default: 0.001) The length of a simulator timestep, in seconds. label : str, optional (Default: None) A name or description to differentiate models. builder : Builder, optional (Default: None) A `.Builder` instance to keep track of build functions. If None, the default builder will be used. Attributes ---------- Build parameters decode_neurons : DecodeNeurons Type of neurons used to facilitate decoded (NEF-style) connections. decode_tau : float Time constant of lowpass synaptic filter used with decode neurons. intercept_limit : float Limit for clipping intercepts, to avoid neurons with high gains. node_neurons : DecodeNeurons Type of neurons used to convert real-valued node outputs to spikes for the chip. pes_error_scale : float Scaling for PES errors, before rounding and clipping to -127..127. pes_wgt_exp : int Learning weight exponent (base 2) for PES learning connections. This controls the maximum weight magnitude (where a larger exponent means larger potential weights, but lower weight resolution). vth_nonspiking : float Voltage threshold for non-spiking neurons (i.e. voltage decoders). Internal attributes blocks : list of LoihiBlock List of Loihi blocks simulated by this model. builder : Builder The build functions used by this model. dt : float The length of a simulator timestep, in seconds. chip2host_params : dict Mapping from Nengo objects to any additional parameters associated with those objects for use during the build process. inputs : list of LoihiInput List of inputs to this model. label : str or None A name or description to differentiate models. objs : dict Dictionary mapping from Nengo objects to Nengo Loihi objects. params : dict Mapping from objects to namedtuples containing parameters generated in the build process. probes : list List of all probes. Probes must be added to this list in the build process, as this list is used by Simulator. seeded : dict All objects are assigned a seed, whether the user defined the seed or it was automatically generated. 'seeded' keeps track of whether the seed is user-defined. We consider the seed to be user-defined if it was set directly on the object, or if a seed was set on the network in which the object resides, or if a seed was set on any ancestor network of the network in which the object resides. seeds : dict Mapping from objects to the integer seed assigned to that object. """ def __init__(self, dt=0.001, label=None, builder=None): self.dt = dt self.label = label self.builder = Builder() if builder is None else builder self.build_callback = None self.decoder_cache = NoDecoderCache() # TODO: these models may not look/behave exactly the same as # standard nengo models, because they don't have a toplevel network # built into them or configs set self.host_pre = NengoModel( dt=float(dt), label="%s:host_pre, dt=%f" % (label, dt), decoder_cache=NoDecoderCache(), ) self.host = NengoModel( dt=float(dt), label="%s:host, dt=%f" % (label, dt), decoder_cache=NoDecoderCache(), ) # Objects created by the model for simulation on Loihi self.inputs = OrderedDict() self.blocks = OrderedDict() # Will be filled in by the simulator __init__ self.split = None # Will be filled in by the network builder self.toplevel = None self.config = None # Resources used by the build process self.objs = defaultdict(dict) self.params = {} # Holds data generated when building objects self.probes = [] self.probe_conns = {} self.seeds = {} self.seeded = {} # --- other (typically standard) parameters # Filter on decode neurons self.decode_tau = 0.005 # ^TODO: how to choose this filter? Even though the input is spikes, # it may not be absolutely necessary since tau_rc provides a filter, # and maybe we don't want double filtering if connection has a filter self.decode_neurons = Preset10DecodeNeurons(dt=dt) self.node_neurons = OnOffDecodeNeurons(dt=dt) # voltage threshold for non-spiking neurons (i.e. voltage decoders) self.vth_nonspiking = 10 # limit for clipping intercepts, to avoid neurons with high gains self.intercept_limit = 0.95 # scaling for PES errors, before rounding and clipping to -127..127 self.pes_error_scale = 100.0 # learning weight exponent for PES (controls the maximum weight # magnitude/weight resolution) self.pes_wgt_exp = 4 # Used to track interactions between host models self.chip2host_params = {} self.chip2host_receivers = OrderedDict() self.host2chip_senders = OrderedDict() self.needs_sender = {} def __getstate__(self): raise NotImplementedError("Can't pickle nengo_loihi.builder.Model") def __setstate__(self, state): raise NotImplementedError("Can't pickle nengo_loihi.builder.Model") def __str__(self): return "%s(%s)" % (type(self).__name__, self.label) def add_input(self, input): assert isinstance(input, LoihiInput) assert input not in self.inputs self.inputs[input] = len(self.inputs) def add_block(self, block): assert isinstance(block, LoihiBlock) assert block not in self.blocks self.blocks[block] = len(self.blocks) def build(self, obj, *args, **kwargs): # Don't build the objects marked as "to_remove" by PassthroughSplit if obj in self.split.passthrough.to_remove: return None if not isinstance(obj, (Node, Ensemble, Probe)): model = self elif self.split.on_chip(obj): model = self else: # Note: callbacks for the host_model will not be invoked model = self.host_model(obj) # done for compatibility with nengo<=2.8.0 # otherwise we could just copy over the initial # seeding to all other models model.seeds[obj] = self.seeds[obj] model.seeded[obj] = self.seeded[obj] built = model.builder.build(model, obj, *args, **kwargs) if self.build_callback is not None: self.build_callback(obj) return built def has_built(self, obj): return obj in self.params
[docs] def host_model(self, obj): """Returns the Model corresponding to where obj should be built.""" if self.split.is_precomputable(obj): return self.host_pre else: return self.host
[docs]class Builder(NengoBuilder): """Fills in the Loihi Model object based on the Nengo Network. We cannot use the Nengo builder as is because we make normal Nengo networks for host-to-chip and chip-to-host communication. To keep Nengo and Nengo Loihi builders separate, we make a blank subclass, which effectively copies the class. """ builders = {}
Builder.register(Network)(build_network)