Skip to content

Commit

Permalink
Combine splitter into builder
Browse files Browse the repository at this point in the history
Most of the work done by the splitter is now done in the builder.
This should give more clarity and control over the mapping between
pre-build and post-build objects. The `SplitterDirective` class
takes on the organizational tasks of the old `Splitter`, giving
directives to the builder about what should be on- or off-chip.

Also:
- Add unit tests for splitter refactoring.
- Raise `BuildError` if learning objects are on_chip or learning and
  `precompute` are combined. Fixes #208 and #209.
- Pass no decoder cache to sub-models. Decoder cache wasn't working
  due to lack of context manager which is normally constructed by
  the top-level network build. Fixes #207.
- Handle sliced probes. Closes #205.
- Check that splitter handles sliced probes. Closes #206.
- Test that splitter does not mutate network. Closes #211.
- Simulation is now identical whether precompute is True or False,
  on both emulator and chip. The tolerance for test_precompute is
  now zero.
  • Loading branch information
arvoelke authored and tbekolay committed Apr 15, 2019
1 parent 97cc92a commit 2799902
Show file tree
Hide file tree
Showing 18 changed files with 1,159 additions and 1,250 deletions.
61 changes: 57 additions & 4 deletions nengo_loihi/builder/builder.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from collections import defaultdict, OrderedDict
import logging

from nengo import Network
from nengo import Network, Node, Ensemble, Connection, 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.builder.inputs import LoihiInput
from nengo_loihi.decode_neurons import (
Preset10DecodeNeurons,
OnOffDecodeNeurons,
)
from nengo_loihi.inputs import LoihiInput

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -106,10 +107,28 @@ def __init__(self, dt=0.001, label=None, builder=None):
self.build_callback = None
self.decoder_cache = NoDecoderCache()

# Host models filled in by the build process
def create_host_model(label, dt):
# We don't use a decoder cache because it requires a context
# manager that differs depending on which sub-model is being built
return NengoModel(
dt=float(dt),
label="%s, dt=%f" % (label, dt),
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 = create_host_model(label="%s, host_pre" % label, dt=dt)
self.host = create_host_model(label="%s, host" % label, dt=dt)

# Objects created by the model for simulation on Loihi
self.inputs = OrderedDict()
self.blocks = OrderedDict()

# Will be filled in by the simulator __init__
self.splitter_directive = None

# Will be filled in by the network builder
self.toplevel = None
self.config = None
Expand Down Expand Up @@ -145,8 +164,11 @@ def __init__(self, dt=0.001, label=None, builder=None):
# magnitude/weight resolution)
self.pes_wgt_exp = 4

# Will be provided by Simulator
# 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")
Expand All @@ -167,8 +189,39 @@ def add_block(self, block):
assert block not in self.blocks
self.blocks[block] = len(self.blocks)

def delegate(self, obj):
"""Returns the Model corresponding to where obj should be built."""
if not isinstance(obj, (Node, Ensemble, Probe)):
# Note: this is safe because any objects built from within a normal
# nengo model (other than self) will not be re-delegated
return self
elif self.splitter_directive.on_chip(obj):
return self
elif self.splitter_directive.is_precomputable(obj):
return self.host_pre
else:
return self.host

def build(self, obj, *args, **kwargs):
built = self.builder.build(self, obj, *args, **kwargs)
# Don't build the passthrough nodes or connections
passthrough_directive = self.splitter_directive.passthrough_directive
if (isinstance(obj, Node)
and obj in passthrough_directive.removed_passthroughs):
return None
if (isinstance(obj, Connection)
and obj in passthrough_directive.removed_connections):
return None

# Note: any callbacks for host_pre or host will not be invoked here
model = self.delegate(obj)
if model is not self:
# 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
Expand Down
Loading

0 comments on commit 2799902

Please sign in to comment.