From 293e8ccb3980ef305849ae63af5fd67fa6d1293b Mon Sep 17 00:00:00 2001 From: Andrew Mundy Date: Tue, 25 Oct 2016 14:52:08 +0100 Subject: [PATCH] Minimise ensemble filter routing regions Extend the minimisation of filter routing tables to also work for ensembles. Each filter routing region reports the set of keys and masks it contains and these form the "off-set"s against which other regions are minimised. --- nengo_spinnaker/operators/lif.py | 22 +++++++++++++++- nengo_spinnaker/regions/filters.py | 42 +++++++++++++++++++++++++++--- tests/regions/test_filters.py | 8 ++++++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/nengo_spinnaker/operators/lif.py b/nengo_spinnaker/operators/lif.py index 780f5e3..990dc76 100644 --- a/nengo_spinnaker/operators/lif.py +++ b/nengo_spinnaker/operators/lif.py @@ -518,8 +518,28 @@ def get_signal_constraints(self): def load_to_machine(self, netlist, controller): """Load the ensemble data into memory.""" # Prepare the routing regions + # Begin by building the set of keys and masks expected by each region + on_sets = { + region: self.regions[region].get_expected_keys_and_masks() for + region in RoutingRegions + } + + # Hence build the set of keys and masks which should NOT match against + # each routing region. + off_sets = collections.defaultdict(set) + for a, on_set in iteritems(on_sets): + for b in RoutingRegions: + # Add the on-set for this region `b` to the off-set + off_sets[a].update(on_sets[b]) + + # Elements which are in the on-set for `a` cannot be in the off-set + off_sets[a].difference_update(on_sets[a]) + + # Build the filter routing region, minimising the entries subject to + # the off-sets which were just constructed. for region in RoutingRegions: - self.regions[region].build_routes() + self.regions[region].build_routes(minimise=True, + off_set=off_sets[region]) # Delegate the task of loading to the machine for cluster in self.clusters: diff --git a/nengo_spinnaker/regions/filters.py b/nengo_spinnaker/regions/filters.py index 79b843d..81bf1f8 100644 --- a/nengo_spinnaker/regions/filters.py +++ b/nengo_spinnaker/regions/filters.py @@ -353,8 +353,40 @@ def get_signal_constraints(self): return constraints - def build_routes(self, minimise=False): - """Build the data structure to be written to memory.""" + def get_expected_keys_and_masks(self): + """Extract the set of keys and masks which are expected to match + against the filter routing region. + + Returns + ------- + {(key, mask), ...} + Set of keys and masks. + """ + keys_and_masks = set() + + # Loop of the list of signals matched by this region and extract their + # keys and masks. + for signal, _ in self.signal_routes: + ks = signal.keyspace + keys_and_masks.add((ks.get_value(tag=self.filter_routing_tag), + ks.get_mask(tag=self.filter_routing_tag))) + + return keys_and_masks + + def build_routes(self, minimise=False, off_set=set()): + """Build the data structure to be written to memory. + + Parameters + ---------- + minimise : bool + Whether to use logic minimisation to reduce the size of the routing + tables. The minimisation used is greedy (not exact) so an off-set + (`off_set`) must be provided if it is desired that some packets do + not match against the minimised table. + off_set : {(key, mask), ...} + Set of keys and masks which should not match against the minimised + set of filter routes. + """ # Generate a list of signals (hashing by ID) to the filters they # target. signal_id_to_signals = dict() @@ -391,13 +423,15 @@ def build_routes(self, minimise=False): if target_set != other_target_set: off_sets[target_set].update(keymasks) - # (Minimise) and build the routing entries. + # (Minimise) and build the routing entries using the off-sets that were + # created above and the general off-set provided to this function. self.filter_routes = list() for (dmask, targets), keymasks in iteritems(targets_to_keymasks): for target in targets: all_keymasks = ( keymasks if not minimise else - ccf_minimise(keymasks, off_sets[(dmask, targets)]) + ccf_minimise(keymasks, + off_set.union(off_sets[(dmask, targets)])) ) # Write in an entry for each key and mask as usual diff --git a/tests/regions/test_filters.py b/tests/regions/test_filters.py index 2405239..b29db68 100644 --- a/tests/regions/test_filters.py +++ b/tests/regions/test_filters.py @@ -318,6 +318,14 @@ def test_filter_routing_region(): # Check that the memory requirement is sane assert filter_region.sizeof() == 4 * (1 + 4*len(signal_routes)) + # Check that the set of expected keys and masks can be extracted + assert filter_region.get_expected_keys_and_masks() == { + (ks_a.get_value(tag=ksc.filter_routing_tag), + ks_a.get_mask(tag=ksc.filter_routing_tag)), + (ks_b.get_value(tag=ksc.filter_routing_tag), + ks_b.get_mask(tag=ksc.filter_routing_tag)), + } + # Check that the written out data is sensible fp = tempfile.TemporaryFile() filter_region.build_routes()