Source code for topo.submodel

"""
A set of tools which allow specifying a model consisting of sheets
organized in levels, and projections connecting these sheets. The
sheets have an attribute matchconditions allowing to specify which
other (incoming) sheets a sheet should connect to.

Instances of the LabelDecorator decorator are offered for setting
parameters/matchconditions for a sheet within a level, as well as
parameters for projections.
"""

import itertools
from collections import defaultdict

import param
import lancet
import numbergen
from holoviews.core.tree import AttrTree
import topo.sheet, topo.projection
from specifications import SheetSpec, ProjectionSpec, ModelSpec, ArraySpec # pyflakes:ignore (API import)
from topo.misc.commandline import global_params



[docs]def select(index, *decorators): """ A meta-decorator that applies one-of-N possible decorators based on the index. The index may be a boolean value for selecting between two options. """ def wrapped(*args, **kwargs): return decorators[int(index)](*args, **kwargs) return wrapped
[docs]def order_projections(model, connection_order): """ Helper function for reproducing random streams when time_dependent=False (where the order of projection instantiation matters). This function should only be used for legacy reasons and should not be used with new models. The first argument is an instance of Model (with projections configured) and the second is the projection ordering, specified by the (decorated) method names generating those projections. This function allows sorting on a single source sheet property. To specify such an ordering, use a tuple where the first element is the relevant method name and the second element is a source sheet property dictionary to match. For instance, specifying the connection_order list as: [('V1_afferent_projections', {'polarity':'On'}), ('V1_afferent_projections', {'polarity':'Off'})] will order the 'On' projections generated by the V1_afferent_projections method before the 'Off' projections. """ connection_list = [el if isinstance(el, tuple) else (el, None) for el in connection_order] for spec in model.projections: matches = [(i, el) for i, el in enumerate(connection_list) if el[0] == spec.matchname] if len(matches) == 0: raise Exception("Could not order projection %r" % spec) elif len(matches) == 1: (i, (k, v)) = matches[0] spec.sort_precedence = i continue property_keys = [pdict.keys() for (_, (_, pdict)) in matches] if not all(len(pkeys)==1 for pkeys in property_keys): raise Exception("Please specify only a single property to sort on") if not all(pkey[0]==property_keys[0][0] for pkey in property_keys): raise Exception("Please specify only a single property to sort on") key = property_keys[0][0] spec_property_value = spec.src.properties[key] match = [ind for (ind, (_, pdict)) in matches if pdict[key] == spec_property_value] if len(match) != 1: raise Exception("Could not order projection %r by property %r" % (spec, key)) spec.sort_precedence = match[0]
[docs]class MatchConditions(object): """ Decorator class for matchconditions. """ def __call__(self, level, method_name): def decorator(f): f._matchcondition_level = level f._target_method_name = method_name return f return decorator def __repr__(self): return "MatchConditions()"
[docs]class ComponentDecorator(object): """ Decorator class that can be instantiated with a component type to create a decorator used to associate methods with the corresponding component. This class works by setting a '_component_type' attribute on the decorated method. Methods that have been annotated in this way may be tracked in classes decorated with the ComponentRegistry class decorator. """ def __init__(self, name, object_type): self.name = name self.type = object_type # Enable IPython tab completion in the settings method kwarg_string = ", ".join("%s=%s" % (name, type(p.default)) for (name, p) in object_type.params().items()) self.params.__func__.__doc__ = 'params(%s)' % kwarg_string
[docs] def params(self, **kwargs): """ A convenient way of generating parameter dictionaries with tab-completion in IPython. """ return kwargs
def __call__(self, f): f._component_type = self.type return f def __repr__(self): return "ComponentDecorator(%s, %s)" % (self.name, self.type.name)
[docs]class ComponentRegistry(object): """ An instance of this class is to be used as class decorator. Any decorated class using ClassDecorators on their methods will be registered with their corresponding component types. """ def __init__(self): self.method_registry = defaultdict(dict) self.type_registry = defaultdict(dict) self.matchconditions = []
[docs] def compute_conditions(self, level, model, properties): """ Collect the matchcondition dictionary for a particular level given a certain Model instance and sheet properties. If no matchconditions are available, an empty dictionary is returned. Respects the appropriate method resolution order (mro) of the given model instance. """ mro = [el.__name__ for el in model.__class__.mro()] filtered = [(target, fn) for (cls, lvl, target, fn) in self.matchconditions if (cls in mro) and lvl == level] return dict((target, fn(model, properties)) for (target, fn) in filtered)
[docs] def lookup(self, cls, name, mode): """ Given a class, a method name and a mode, return either the component type (if mode='type) or the appropriate method (if mode='method'). """ mro = [el.__name__ for el in cls.mro()] registry = self.method_registry if mode=='method' else self.type_registry for class_name in mro: entries = registry[class_name] if name in entries: return entries[name] raise KeyError("Could not find method named %r." " Please ensure classes using component decorators" " are decorated with the Model.definition" " class decorator." % name)
def __call__(self, cls): for name, method in cls.__dict__.iteritems(): class_name = cls.__name__ component_type = getattr(method, "_component_type", False) if component_type: method_name = method.__name__ self.method_registry[class_name].update({method_name:method}) self.type_registry[class_name].update({method_name:component_type}) match_level = getattr(method, '_matchcondition_level', False) target_method_name = getattr(method, '_target_method_name', False) if match_level and target_method_name: info = (class_name, match_level, target_method_name, method) self.matchconditions.append(info) return cls # A singleton to be used by model class using decorators
definition = ComponentRegistry()
[docs]class Model(param.Parameterized): """ The available setup options are: :'training_patterns': fills the training_patterns AttrTree with pattern generator instances. The path is the name of the input sheet. Usually calls PatternCoordinator to do this. :'sheets_setup': determines the number of sheets, their types and names sets sheet parameters according to the registered methods in level sets sheet matchconditions according to the registered methods in matchconditions :'projections': determines which connections should be present between the sheets according to the matchconditions of SheetSpec objects, using connect to specify the connection type and sets their parameters according to the registered methods in connect The available instantiate options are: :'sheets': instantiates all sheets and registers them in topo.sim :'projections': instantiates all projections and registers them in topo.sim """ random_seed = param.Integer(default=None, doc=""" Overrides the default global seed on param.random_seed when not None.""") __abstract = True # A convenient handle on the definition class decorator definition = definition matchconditions = MatchConditions() sheet_decorators = set() projection_decorators = set() @classmethod def register_decorator(cls, object_type): name = object_type.name decorator = ComponentDecorator(name, object_type) setattr(cls, name, decorator) if issubclass(object_type, topo.sheet.Sheet): cls.sheet_decorators.add(decorator) if issubclass(object_type, topo.projection.Projection): cls.projection_decorators.add(decorator) @property
[docs] def modified_parameters(self): "Dictionary of modified model parameters" return {k:v for k,v in self.get_param_values(onlychanged=True)}
def __init__(self, register=True, time_dependent=True, **params): numbergen.TimeAware.time_dependent = time_dependent if register: self._register_global_params(params) super(Model,self).__init__(**params) self.specification = None self.properties = {} # Training patterns need to be accessed by GeneratorSheets self.properties['training_patterns'] = AttrTree() def _register_global_params(self, params): """ Register the parameters of this object as global parameters available for users to set from the command line. Values supplied as global parameters will override those of the given dictionary of params. """ for name,obj in self.params().items(): global_params.add(**{name:obj}) for name,val in params.items(): global_params.params(name).default=val params.update(global_params.get_param_values()) params["name"]=self.name #==============================================# # Public methods to be implemented by modelers # #==============================================#
[docs] def property_setup(self, properties): """ Method to precompute any useful properties from the class parameters. For instance, if there is a ``num_lags`` parameter, this method could compute the actual projection delays and store it as properties['lags']. The return value is the updated 'properties' dictionary. In addition, this method can be used to configure class attributes of the model components. """ return properties
[docs] def training_pattern_setup(self, **overrides): """ Returns a dictionary of PatternGenerators to be added to self.training_patterns, with the target sheet name keys and pattern generator values. The overrides keywords can be used by a subclass to parameterize the training patterns e.g. override the default parameters of a PatternCoordinator object. """ raise NotImplementedError
[docs] def sheet_setup(self): """ Returns a dictionary of properties or equivalent Lancet.Args object. Each outer key must be the level name and the values are lists of property dictionaries for the sheets at that level (or equivalent Lancet Args object). For instance, two LGN sheets at the 'LGN' level could be defined by either: {'LGN':[{'polarity':'ON'}, {'polarity':'OFF'}]} OR {'LGN':lancet.List('polarity', ['ON', 'OFF'])} The specified properties are used to initialize the sheets AttrTree with SheetSpec objects. """ raise NotImplementedError
[docs] def analysis_setup(self): """ Set up appropriate defaults for analysis functions in topo.analysis.featureresponses. """ pass #====================================================# # Remaining methods should not need to be overridden # #====================================================#
[docs] def setup(self,setup_options=True): """ This method can be used to setup certain parts of the submodel. If setup_options=True, all setup methods are called. setup_options can also be a list, whereas all list items of available_setup_options are accepted. Available setup options are: 'training_patterns','sheets','projections' and 'analysis'. This method returns a ModelSpec object which is also set as the value of the 'specification' attribute. Please consult the docstring of the Model class for more information about each setup option. """ available_setup_options = ['attributes', 'training_patterns', 'sheets', 'projections', 'analysis'] if self.random_seed is not None: param.random_seed = self.random_seed if setup_options==True: setup_options = available_setup_options if 'attributes' in setup_options: self.properties = self.property_setup({}) model = ModelSpec(self, self.properties) if 'training_patterns' in setup_options: training_patterns = self.training_pattern_setup() for name, training_pattern in training_patterns.items(): model.training_patterns.set_path(name, training_pattern) self.properties['training_patterns'] = training_patterns if 'sheets' in setup_options: sheet_properties = self.sheet_setup() enumeration = enumerate(sheet_properties.items()) for (ordering, (level, property_list)) in enumeration: sheet_type = self.definition.lookup(self.__class__, level, mode='type') if isinstance(property_list, lancet.Identity): property_list = [{}] elif isinstance(property_list, lancet.Args): property_list = property_list.specs # If an empty list or Args() elif not property_list: continue for properties in property_list: spec_properties = dict(level=level, **properties) sheet_spec = SheetSpec(sheet_type, spec_properties) sheet_spec.sort_precedence = ordering model.sheets.set_path(str(sheet_spec), sheet_spec) model = self._update_sheet_spec_parameters(model) if 'projections' in setup_options: model = self._compute_projection_specs(model) if 'analysis' in setup_options: self.analysis_setup() self.specification = model return model
def _update_sheet_spec_parameters(self, model): for sheet_spec in model.sheets.data.values(): param_method = self.definition.lookup(self.__class__, sheet_spec.level, 'method') if not param_method: raise Exception("Parameters for sheet level %r not specified" % sheet_spec.level) updated_params = param_method(self, sheet_spec.properties) sheet_spec.update(**updated_params) return model def _matchcondition_holds(self, matchconditions, src_sheet): """ Given a dictionary of properties to match and a target sheet spec, return True if the matchcondition holds else False. """ matches=True if matchconditions is None: return False for incoming_key, incoming_value in matchconditions.items(): if (incoming_key in src_sheet.properties and \ str(src_sheet.properties[incoming_key]) != str(incoming_value)) \ or (incoming_key not in src_sheet.properties and incoming_value is not None): matches=False break return matches def _compute_projection_specs(self, model): """ Loop through all possible combinations of SheetSpec objects in self.sheets If the src_sheet fulfills all criteria specified in dest_sheet.matchconditions, create a new ProjectionSpec object and add this item to self.projections. """ sheetspec_product = itertools.product(model.sheets.data.values(), model.sheets.data.values()) for src_sheet, dest_sheet in sheetspec_product: conditions = self.definition.compute_conditions(dest_sheet.level, self, dest_sheet.properties) for matchname, matchconditions in conditions.items(): if self._matchcondition_holds(matchconditions, src_sheet): paramfn = self.definition.lookup(self.__class__, matchname, 'method') projtype = self.definition.lookup(self.__class__, matchname, 'type') proj = ProjectionSpec(projtype, src_sheet, dest_sheet) paramsets = paramfn(self, src_sheet.properties, dest_sheet.properties) paramsets = [paramsets] if isinstance(paramsets, dict) else paramsets for paramset in paramsets: proj = ProjectionSpec(projtype, src_sheet, dest_sheet) proj.update(**paramset) # Only used when time_dependent=False # (which is to be deprecated) proj.matchname = matchname path = (str(dest_sheet), paramset['name']) model.projections.set_path(path, proj) return model def __call__(self,setup_options=True, instantiate_options=True, verbose=False): """ A convenient way to setup a model object, instantiate it and return it. """ model = self.setup(setup_options) model(instantiate_options, verbose) return model def __getitem__(self, key): "Convenient property access." return self.properties[key] def __setitem__(self, key, val): raise NotImplementedError("Models must define properties via the property_setup method")
[docs] def keys(self): "The list of available property keys." return self.properties.keys()
[docs] def items(self): "The property items." return self.properties.items() # Register the sheets and projections available in Topographica
[docs]def register_submodel_decorators(classes,superclass=None): """ Register a Model decorator for each of the non-abstract classes provided. Only registers those that are subclasses of the given superclass, if specified. """ with param.logging_level('CRITICAL'): for c in classes: if (isinstance(c, type) and (superclass==None or issubclass(c, superclass)) and (not hasattr(c, "_%s__abstract" % c.name))): Model.register_decorator(c)
register_submodel_decorators(param.concrete_descendents(topo.sheet.Sheet).values()) register_submodel_decorators(param.concrete_descendents(topo.projection.Projection).values())