def __init__(self, spike_times, name=None, copied=False): if not isinstance(spike_times, list): Global._error( 'In a SpikeSourceArray, spike_times must be a Python list.') if isinstance(spike_times[0], list): # several neurons nb_neurons = len(spike_times) else: # a single Neuron nb_neurons = 1 spike_times = [spike_times] # Create a fake neuron just to be sure the description has the correct parameters neuron = Neuron(parameters="", equations="", spike=" t == 0", reset="", name="Spike source", description="Spike source array.") SpecificPopulation.__init__(self, geometry=nb_neurons, neuron=neuron, name=name, copied=copied) self.init['spike_times'] = spike_times
def _load_data(filename): """ Internally loads data contained in a given file. :param filename: path to the file. :return: A dictionary with the connectivity and synaptic variables if the file ``filename`` is available otherwise None is returned. """ (_, fname) = os.path.split(filename) extension = os.path.splitext(fname)[1] if extension == '.mat': Global._error('Unable to load Matlab format.') return None elif extension == '.gz': try: import gzip except: Global._error('gzip is not installed.') return None try: with gzip.open(filename, mode='rb') as r_file: desc = pickle.load(r_file) return desc except Exception as e: Global._print('Unable to read the file ' + filename) Global._print(e) return None elif extension == '.npz': try: data = np.load(filename, allow_pickle=True) desc = {} for attribute in data.files: # We need to distinguish two cases: 1) full network save # or 2) single pop/proj. The first case leads to a dictionary # of several objects. The latter to a dictionary containing all # values. if data[attribute].dtype == np.dtype('O'): # attribute is a collection of multiple objects desc[attribute] = data[attribute].item(0) else: # attribute is a scalar/array desc[attribute] = data[attribute] return desc except Exception as e: Global._print('Unable to read the file ' + filename) Global._print(e) return None else: try: with open(filename, mode='rb') as r_file: desc = pickle.load(r_file) return desc except Exception as e: Global._print('Unable to read the file ' + filename) Global._print(e) return None
def set_image(self, image_name): """ Sets an image (.png, .jpg or whatever is supported by PIL) into the firing rate of the population. If the image has a different size from the population, it will be resized. """ try: im = Image.open(image_name) except : # image does not exist Global._error('The image ' + image_name + ' does not exist.') exit(0) # Resize the image if needed (width, height) = (self.geometry[1], self.geometry[0]) if im.size != (width, height): Global._warning('The image ' + image_name + ' does not have the same size '+str(im.size)+' as the population ' + str((width, height)) + '. It will be resized.') im = im.resize((width, height)) # Check if only the luminance should be extracted if self.dimension == 2 or self.geometry[2] == 1: im=im.convert("L") # Set the rate of the population if not Global._network[0]['compiled']: self.r = (np.array(im))/255. else: self.cyInstance.set_r(np.array(im).reshape(self.size)/255.)
def runtime_version(self): try: result = self.cy_cc.runtime_version() except Exception as e: Global._print(e) Global._error('CUDA is not correctly installed on your system.') return result
def _load_data(filename): " Internally loads data contained in a file" (path, fname) = os.path.split(filename) extension = os.path.splitext(fname)[1] desc = None if extension == '.mat': Global._error('Unable to load Matlab format.') return desc elif extension == '.gz': try: import gzip except: Global._error('gzip is not installed.') return desc try: with gzip.open(filename, mode = 'rb') as r_file: desc = pickle.load(r_file) except Exception as e: Global._print('Unable to read the file ' + filename) Global._print(e) return desc else: try: with open(filename, mode = 'rb') as r_file: desc = pickle.load(r_file) except Exception as e: Global._print('Unable to read the file ' + filename) Global._print(e) return desc return desc
def eventdriven(self, expression): # Standardize the equation real_tau, _, steadystate = self.standardize_ODE(expression) if real_tau is None: # the equation can not be standardized Global._print(self.expression) Global._error( 'The equation is not a linear ODE and can not be evaluated exactly.' ) # Check the steady state is not dependent on other variables for var in self.variables: if self.local_dict[var] in steadystate.atoms(): Global._print(self.expression) Global._error( 'The equation can not depend on other variables (' + var + ') to be evaluated exactly.') # Obtain C code variable_name = self.c_code(self.local_dict[self.name]) steady = self.c_code(steadystate) if steady == '0': code = variable_name + ' *= exp(dt*(_last_event%(local_index)s - (t))/(' + self.c_code( real_tau) + '));' else: code = variable_name + ' = ' + steady + ' + (' + variable_name + ' - ' + steady + ')*exp(dt*(_last_event%(local_index)s - (t))/(' + self.c_code( real_tau) + '));' return code
def _instantiate(self, module): # Create the Cython instance try: self.cyInstance = getattr(module, self.class_name + '_wrapper')( self.size, self.max_delay) except: Global._error('unable to instantiate the population', self.name)
def __init__(self, population, name=None, mode='window', window = 100.0, scaling=1.0, smooth=1.0, cut=3.0): """ *Parameters*: * **population**: the Population to convert. Its neuron type must be rate-coded. * **name**: the (optional) name of the hybrid population. * **mode**: mode of computation of the firing rate. ``'window'`` (default) or ``'isi'``. * **window**: the extent of the sliding window (in ms) used to compute the firing rate in the 'window' mode(default: 100.0 ms). * **cut**: cutting frequency of the ``'isi'`` kernel. default 2.0. * **scaling**: the scaling of the firing rate. Defines what a firing rate of 1 Hz outputs (default: 1.0). * **smooth**: time constant (in ms) of the low-pass filter used to smooth the firing rate (default: 1 ms, i.e no smoothing) """ self.population = population self.name = name self.mode = mode self.scaling = scaling self.window = window self.smooth = smooth self.cut = cut if not self.population.neuron_type.description['type'] == 'spike': Global._error('the population ' + self.population.name + ' must contain spiking neurons.') if self.mode == 'window': self._code = self._create_window() elif self.mode == 'adaptive': self._code = self._create_adaptive() elif self.mode == 'isi': self._code = self._create_isi() else: Global._error('Spike2RatePopulation: Unknown method ' + self.mode) self._specific = True
def __init__(self, population, name=None, scaling=1.0, refractory=None): """ *Parameters*: * **population**: the Population to convert. Its neuron type must be spiking. * **name**: the (optional) name of the hybrid population. * **scaling**: the scaling of the firing rate. Defines what a rate ``r`` of 1.0 means in Hz (default: 1.0). * **refractory**: a refractory period in ms to ensure the ISI is not too high (default: None) """ self.population = population if not self.population.neuron_type.description['type'] == 'rate': Global._error('the population ' + self.population.name + ' must contain rate-coded neurons.') # Create the description, but it will not be used for generation Population.__init__( self, geometry = self.population.geometry, name=name, neuron = Neuron( parameters=""" scaling = %(scaling)s : population """ % {'scaling': scaling} , equations=""" p = Uniform(0.0, 1.0) rates = p """, spike="rates>p", refractory=refractory ) ) self._specific = True
def extract_spike_variable(description): cond = prepare_string(description['raw_spike']) if len(cond) > 1: Global.Global._print(description['raw_spike']) Global._error('The spike condition must be a single expression') translator = Equation('raw_spike_cond', cond[0].strip(), description) raw_spike_code = translator.parse() # Also store the variables used in the condition, as it may be needed for CUDA generation spike_code_dependencies = translator.dependencies() reset_desc = [] if 'raw_reset' in description.keys() and description['raw_reset']: reset_desc = process_equations(description['raw_reset']) for var in reset_desc: translator = Equation(var['name'], var['eq'], description) var['cpp'] = translator.parse() var['dependencies'] = translator.dependencies() return { 'spike_cond': raw_spike_code, 'spike_cond_dependencies': spike_code_dependencies, 'spike_reset': reset_desc}
def _replace_random(self, loc_eqs, glob_eqs, random_distributions): """ we replace the rand_%(id)s by the corresponding curand... term """ # double precision methods have a postfix prec_extension = "" if Global.config['precision'] == "float" else "_double" for rd in random_distributions: if rd['dist'] == "Uniform": term = """( curand_uniform%(postfix)s( &%(rd)s[i] ) * (%(max)s - %(min)s) + %(min)s )""" % {'postfix': prec_extension, 'rd': rd['name'], 'min': rd['args'].split(',')[0], 'max': rd['args'].split(',')[1]} loc_eqs = loc_eqs.replace(rd['name']+"[i]", term) term = """( curand_uniform%(postfix)s( &%(rd)s[0] ) * (%(max)s - %(min)s) + %(min)s )""" % {'postfix': prec_extension, 'rd': rd['name'], 'min': rd['args'].split(',')[0], 'max': rd['args'].split(',')[1]} glob_eqs = glob_eqs.replace(rd['name']+"[0]", term) elif rd['dist'] == "Normal": term = """( curand_normal%(postfix)s( &%(rd)s[i] ) * %(sigma)s + %(mean)s )""" % {'postfix': prec_extension, 'rd': rd['name'], 'mean': rd['args'].split(",")[0], 'sigma': rd['args'].split(",")[1]} loc_eqs = loc_eqs.replace(rd['name']+"[i]", term) term = """( curand_normal%(postfix)s( &%(rd)s[0] ) * %(sigma)s + %(mean)s )""" % {'postfix': prec_extension, 'rd': rd['name'], 'mean': rd['args'].split(",")[0], 'sigma': rd['args'].split(",")[1]} glob_eqs = glob_eqs.replace(rd['name']+"[0]", term) elif rd['dist'] == "LogNormal": term = """( curand_log_normal%(postfix)s( &%(rd)s[i], %(mean)s, %(std_dev)s) )""" % {'postfix': prec_extension, 'rd': rd['name'], 'mean': rd['args'].split(',')[0], 'std_dev': rd['args'].split(',')[1]} loc_eqs = loc_eqs.replace(rd['name']+"[i]", term) term = """( curand_log_normal%(postfix)s( &%(rd)s[0], %(mean)s, %(std_dev)s) )""" % {'postfix': prec_extension, 'rd': rd['name'], 'mean': rd['args'].split(',')[0], 'std_dev': rd['args'].split(',')[1]} glob_eqs = glob_eqs.replace(rd['name']+"[0]", term) else: Global._error("Unsupported random distribution on GPUs: " + rd['dist']) # set indices loc_eqs = loc_eqs % {'global_index': '[0]'} glob_eqs = glob_eqs % {'global_index': '[0]'} return loc_eqs, glob_eqs
def rank_from_coordinates(self, coord): """ Returns the rank of a neuron based on coordinates. *Parameter*: * **coord**: coordinate tuple, can be multidimensional. """ try: rank = self._rank_from_coord(coord, self.geometry) except: Global._error( "rank_from_coordinates(): There is no neuron of coordinates", coord, "in the population", self.name, self.geometry, ) if rank > self.size: Global._error( "rank_from_coordinates(), neuron", str(coord), ": the population", self.name, "has only", self.size, "neurons (geometry " + str(self.geometry) + ").", ) else: return rank
def neuron(self, *coord): """ Returns an ``IndividualNeuron`` object wrapping the neuron with the provided rank or coordinates. """ # Transform arguments if len(coord) == 1: if isinstance(coord[0], int): rank = coord[0] if not rank < self.size: Global._error( " when accessing neuron", str(rank), ": the population", self.name, "has only", self.size, "neurons (geometry " + str(self.geometry) + ").", ) else: rank = self.rank_from_coordinates(coord[0]) if rank is None: return None else: # a tuple rank = self.rank_from_coordinates(coord) if rank is None: return None # Return corresponding neuron return IndividualNeuron(self, rank)
def __init__(self, rates, schedule=0., period= -1., name=None, copied=False): neuron = Neuron( parameters="", equations=" r = 0.0", name="Timed Array", description="Timed array source." ) # Geometry of the population geometry = rates.shape[1:] # Check the schedule if isinstance(schedule, (int, float)): if float(schedule) <= 0.0: schedule = Global.config['dt'] schedule = [ float(schedule*i) for i in range(rates.shape[0])] if len(schedule) > rates.shape[0]: Global._error('TimedArray: the length of the schedule parameter cannot exceed the first dimension of the rates parameter.') if len(schedule) < rates.shape[0]: Global._warning('TimedArray: the length of the schedule parameter is smaller than the first dimension of the rates parameter (more data than time points). Make sure it is what you expect.') SpecificPopulation.__init__(self, geometry=geometry, neuron=neuron, name=name, copied=copied) self.init['schedule'] = schedule self.init['rates'] = rates self.init['period'] = period
def reset(self, attributes=-1): """ Resets all parameters and variables of the population to the value they had before the call to compile(). *Parameters:* * **attributes**: list of attributes (parameter or variable) which should be reinitialized. Default: all attributes. """ if attributes == -1: try: self.set(self.init) except Exception as e: Global._print(e) Global._error("Population.reset(): something went wrong while resetting", var) else: # only some of them for var in attributes: # check it exists if not var in self.attributes: Global._warning("Population.reset():", var, "is not an attribute of the population, skipping.") continue try: self.__setattr__(var, self.init[var]) except Exception as e: Global._print(e) Global._warning("Population.reset(): something went wrong while resetting", var) self.cyInstance.activate(self.enabled) self.cyInstance.reset()
def connectivity_matrix(self, fill=0.0): """ Returns a dense connectivity matrix (2D Numpy array) representing the connections between the pre- and post-populations. The first index of the matrix represents post-synaptic neurons, the second the pre-synaptic ones. If PopulationViews were used for creating the projection, the matrix is expanded to the whole populations by default. *Parameters*: * **fill**: value to put in the matrix when there is no connection (default: 0.0). """ if isinstance(self.pre, PopulationView): size_pre = self.pre.population.size else: size_pre = self.pre.size if isinstance(self.post, PopulationView): size_post = self.post.population.size else: size_post = self.post.size res = np.ones((size_post, size_pre)) * fill for rank in self.post_ranks: idx = self.post_ranks.index(rank) try: preranks = self.cyInstance.pre_rank(idx) w = self.cyInstance.get_dendrite_w(idx) except: Global._error('The connectivity matrix can only be accessed after compilation') return [] res[rank, preranks] = w return res
def check_and_apply_pow_fix(eqs): """ CUDA SDKs before 7.5 had an error if std=c++11 is enabled related to pow(double, int). Only pow(double, double) was detected as device function, the pow(double, int) will be detected as host function. (This was fixed within SDK 7.5) To support also earlier versions, we simply add a double type cast. """ if eqs.strip() == "": # nothing to do return eqs try: from ANNarchy.generator.CudaCheck import CudaCheck if CudaCheck().runtime_version() > 7000: # nothing to do, is working in higher SDKs return eqs except: Global._error('CUDA is not correctly installed on your system') if Global.config['verbose']: Global._print( 'occurance of pow() and SDK below 7.5 detected, apply fix.') # detect all pow statements pow_occur = re.findall(r"pow[\( [\S\s]*?\)*?, \d+\)]*?", eqs) for term in pow_occur: eqs = eqs.replace(term, term.replace(', ', ', (double)')) return eqs
def _data(self): "Method gathering all info about the projection when calling save()" if not self.initialized: Global._error('save_connectivity(): the network has not been compiled yet.') desc = {} desc['name'] = self.name desc['pre'] = self.pre.name desc['post'] = self.post.name desc['target'] = self.target desc['post_ranks'] = self.post_ranks desc['attributes'] = self.attributes desc['parameters'] = self.parameters desc['variables'] = self.variables desc['pre_ranks'] = self.cyInstance.pre_rank_all() desc['delays'] = self._get_delay() # Attributes to save attributes = self.attributes if not 'w' in self.attributes: attributes.append('w') # Save all attributes for var in attributes: try: desc[var] = getattr(self.cyInstance, 'get_'+var)() except: Global._warning('Can not save the attribute ' + var + ' in the projection.') return desc
def __init__(self, pre, post, target, psp="w * pre.r", operation="sum"): """ Projection based on shared weights: each post-synaptic neuron uses the same weights, so they need to be instantiated only once to save memory. Learning is not possible for now. The ``synapse`` argument is removed, replaced by a single ``psp`` argument to modified what is summed and ``operation`` to replace the summation operation by max-pooling or similar.. *Parameters*: * **pre**: pre-synaptic population (either its name or a ``Population`` object). * **post**: post-synaptic population (either its name or a ``Population`` object). * **target**: type of the connection. * **psp**: function to be summed. By default: ``w * pre.r`` * **operation**: function applied on ``psp`` ("sum", "max", "min", "mean"). "sum" is the default. """ # Create the description, but it will not be used for generation Projection.__init__( self, pre, post, target, synapse = SharedSynapse(psp=psp, operation=operation) ) self._omp_config['psp_schedule'] = 'schedule(dynamic)' if not Global.config["paradigm"] == "openmp": Global._error('Weight sharing is only implemented for the OpenMP paradigm.') exit(0) if not pre.neuron_type.type == 'rate': Global._error('Weight sharing is only implemented for rate-coded populations.') exit(0)
def __init__(self, geometry, name=None, copied=False): """ *Parameters*: * *geometry*: population geometry as tuple. It must correspond to the image size and be fixed through the whole simulation. * *name*: unique name of the population (optional). About the geometry: * If the geometry is 2D, it corresponds to the (height, width) of the image. Only the luminance of the pixels will be represented (grayscale image). * If the geometry is 3D, the third dimension can be either 1 (grayscale) or 3 (color). If the third dimension is 3, each will correspond to the RGB values of the pixels. .. warning:: Due to the indexing system of Numpy, a 640*480 image should be fed into a (480, 640) or (480, 640, 3) population. """ # Check geometry if isinstance(geometry, int) or len(geometry)==1: Global._error('The geometry of an ImagePopulation should be 2D (grayscale) or 3D (color).') if len(geometry)==3 and (geometry[2]!=3 and geometry[2]!=1): Global._error('The third dimension of an ImagePopulation should be either 1 (grayscale) or 3 (color).') if len(geometry)==3 and geometry[2]==1: geometry = (int(geometry[0]), int(geometry[1])) # Create the population Population.__init__(self, geometry = geometry, name=name, neuron = Neuron(parameters="r = 0.0"), copied=copied)
def _set_cython_attribute(self, attribute, value): """ Sets the value of the given attribute for all neurons in the population, as a Numpy array having the same geometry as the population if it is local. :param attribute: should be a string representing the variables's name. :param value: a value or Numpy array of the right size. """ try: ctype = self._get_attribute_cpp_type(attribute) if attribute in self.neuron_type.description['local']: if isinstance(value, np.ndarray): self.cyInstance.set_local_attribute_all( attribute, value.reshape(self.size), ctype) elif isinstance(value, list): self.cyInstance.set_local_attribute_all( attribute, np.array(value).reshape(self.size), ctype) else: self.cyInstance.set_local_attribute_all( attribute, value * np.ones(self.size), ctype) else: self.cyInstance.set_global_attribute(attribute, value, ctype) except Exception as e: Global._debug(e) err_msg = """Population.set(): either the variable '%(attr)s' does not exist in the population '%(pop)s', or the provided array does not have the right size.""" Global._error(err_msg % {'attr': attribute, 'pop': self.name})
def _load_from_sparse(self, pre, post, weights, delays): from scipy.sparse import csc_matrix # Create an empty CSR object csr = Connector.CSR() # Find offsets if isinstance(self.pre, PopulationView): pre_ranks = self.pre.ranks else: pre_ranks = [i for i in range(self.pre.size)] if isinstance(self.post, PopulationView): post_ranks = self.post.ranks else: post_ranks = [i for i in range(self.post.size)] # Process the sparse matrix and fill the csr weights.sort_indices() (pre, post) = weights.shape if (pre, post) != (len(pre_ranks), len(post_ranks)): Global._error("connect_from_sparse(): the sparse matrix does not have the correct dimensions.") Global._print('Expected:', (len(pre_ranks), len(post_ranks))) Global._print('Received:', (pre, post)) exit(0) for idx_post in range(post): idx_pre = weights.getcol(idx_post).indices w = weights.getcol(idx_post).data pr = [pre_ranks[i] for i in idx_pre] csr.add(post_ranks[idx_post], pr, w, [float(delays)]) return csr
def warp_size(self, device=0): try: result = self.cy_cc.warp_size(device) except Exception as e: Global._print(e) Global._error('CUDA is not correctly installed on your system.') return result
def parse(self, part=None): if not part: part = self.eq expression = transform_condition(part) # Check if there is a == in the condition if '==' in expression: # Is it the only term, or are there other operations? if '&' in expression or '|' in expression: expression = re.sub(r'([\w\s.]+)==([\w\s.]+)', r'Equality(\1, \2)', expression) else: terms = expression.split('==') expression = 'Equality(' + terms[0] + ', ' + terms[1] + ')' # Check if there is a != in the condition if '!=' in expression: # Is it the only term, or are there other operations? if '&' in expression or '|' in expression: expression = re.sub(r'([\w\s.]+)!=([\w\s.]+)', r'Not(Equality(\1, \2))', expression) else: terms = expression.split('!=') expression = 'Not(Equality(' + terms[0] + ', ' + terms[1] + '))' try: eq = parse_expr(expression, local_dict = self.local_dict, transformations = ((auto_number, convert_xor,)) ) except: Global._print(expression) Global._error('The function depends on unknown variables.') return ccode(eq, precision=8, user_functions=self.user_functions)
def __init__(self, geometry, name=None, copied=False): """ :param geometry: population geometry as tuple. It must correspond to the image size and be fixed through the whole simulation. :param name: unique name of the population (optional). """ # Check geometry if isinstance(geometry, int) or len(geometry) == 1: Global._error( 'The geometry of an ImagePopulation should be 2D (grayscale) or 3D (color).' ) if len(geometry) == 3 and (geometry[2] != 3 and geometry[2] != 1): Global._error( 'The third dimension of an ImagePopulation should be either 1 (grayscale) or 3 (color).' ) if len(geometry) == 3 and geometry[2] == 1: geometry = (int(geometry[0]), int(geometry[1])) # Create the population Population.__init__(self, geometry=geometry, name=name, neuron=Neuron(parameters="r = 0.0"), copied=copied)
def __init__(self, spike_times, name=None): if not isinstance(spike_times, list): Global._error('in SpikeSourceArray, spike_times must be a Python list.') exit(0) if isinstance(spike_times[0], list): # several neurons nb_neurons = len(spike_times) else: # a single Neuron nb_neurons = 1 spike_times = [ spike_times ] # Create a fake neuron just to be sure the description has the correct parameters neuron = Neuron( parameters=""" spike_times = 0.0 """, equations="", spike=" t == spike_times", reset="", name="Spike source", description="Spikes source array." ) Population.__init__(self, geometry=nb_neurons, neuron=neuron, name=name) # Do some sorting to save C++ complexity times = [] for neur_times in spike_times: times.append(sorted(list(set(neur_times)))) # suppress doublons and sort self.init['spike_times'] = times
def save_connectivity(self, filename): """ Saves the projection pattern in a file. Only the connectivity matrix, the weights and delays are saved, not the other synaptic variables. The generated data should be used to create a projection in another network:: proj.connect_from_file(filename) *Parameters*: * **filename**: file where the data will be saved. """ if not self.initialized: Global._error('save_connectivity(): the network has not been compiled yet.') return data = { 'name': self.name, 'post_ranks': self.post_ranks, 'pre_ranks': self.cyInstance.pre_rank_all(), # was: [self.cyInstance.pre_rank(n) for n in range(self.size)], 'w': self.cyInstance.get_w(), 'delay': self.cyInstance.get_delay() if hasattr(self.cyInstance, 'get_delay') else None, 'max_delay': self.max_delay, 'uniform_delay': self.uniform_delay, 'size': self.size, 'nb_synapses': sum([self.cyInstance.nb_synapses(n) for n in range(self.size)]) } try: import cPickle as pickle # Python2 except: import pickle # Python3 with open(filename, 'wb') as wfile: pickle.dump(data, wfile, protocol=pickle.HIGHEST_PROTOCOL)
def _store_connectivity(self, method, args, delay): self._connection_method = method self._connection_args = args self._connection_delay = delay # Analyse the delay if isinstance(delay, (int, float)): # Uniform delay self.max_delay = int(delay/Global.config['dt']) self.uniform_delay = int(delay/Global.config['dt']) elif isinstance(delay, RandomDistribution): # Non-uniform delay self.uniform_delay = -1 # Ensure no negative delays are generated if delay.min == None: delay.min = Global.config['dt'] # The user needs to provide a max in order to compute max_delay if delay.max == None: Global._error('Projection.connect_xxx(): if you use a non-bounded random distribution for the delays (e.g. Normal), you need to set the max argument to limit the maximal delay.') exit(0) self.max_delay = int(delay.max/Global.config['dt']) elif isinstance(delay, (list, np.ndarray)): # connect_from_matrix/sparse self.uniform_delay = -1 self.max_delay = int(max([max(l) for l in delay])/Global.config['dt']) else: Global._error('Projection.connect_xxx(): delays are not valid!') exit(0) # Transmit the max delay to the pre pop if isinstance(self.pre, PopulationView): self.pre.population.max_delay = max(self.max_delay, self.pre.population.max_delay) else: self.pre.max_delay = max(self.max_delay, self.pre.max_delay)
def set_image(self, image_name): """ Sets an image (.png, .jpg or whatever is supported by PIL) into the firing rate of the population. If the image has a different size from the population, it will be resized. """ try: im = Image.open(image_name) except: # image does not exist Global._error('The image ' + image_name + ' does not exist.') exit(0) # Resize the image if needed (width, height) = (self.geometry[1], self.geometry[0]) if im.size != (width, height): Global._warning('The image ' + image_name + ' does not have the same size ' + str(im.size) + ' as the population ' + str((width, height)) + '. It will be resized.') im = im.resize((width, height)) # Check if only the luminance should be extracted if self.dimension == 2 or self.geometry[2] == 1: im = im.convert("L") # Set the rate of the population if not Global._network[0]['compiled']: self.r = (np.array(im)) / 255. else: self.cyInstance.set_r(np.array(im).reshape(self.size) / 255.)
def _set_cython_attribute(self, attribute, value): """ Sets the value of the given attribute for all post-synaptic neurons in the projection, as a NumPy array having the same geometry as the population if it is local. Parameter: * *attribute*: should be a string representing the variables's name. """ if isinstance(value, np.ndarray): value = list(value) if isinstance(value, list): if len(value) == len(self.post_ranks): for idx, n in enumerate(self.post_ranks): if not len(value[idx]) == self.cyInstance.nb_synapses(idx): Global._error('The postynaptic neuron ' + str(n) + ' receives '+ str(self.cyInstance.nb_synapses(idx))+ ' synapses.') exit(0) getattr(self.cyInstance, 'set_dendrite_'+attribute)(idx, value[idx]) else: Global._error('The projection has ' + self.size + ' post-synaptic neurons.') elif isinstance(value, RandomDistribution): for idx, n in enumerate(self.post_ranks): getattr(self.cyInstance, 'set_dendrite_'+attribute)(idx, value.get_values(self.cyInstance.nb_synapses(idx))) else: # a single value if attribute in self.synapse_type.description['local']: for idx, n in enumerate(self.post_ranks): getattr(self.cyInstance, 'set_dendrite_'+attribute)(idx, value*np.ones(self.cyInstance.nb_synapses(idx))) else: getattr(self.cyInstance, 'set_'+attribute)(value*np.ones(len(self.post_ranks)))
def _init_attributes(self): """ Method used after compilation to initialize the attributes.""" # Initialize the population self.initialized = True # Transfer the initial values of all attributes for name, value in self.init.items(): if isinstance(value, Global.Constant): self.__setattr__(name, value.value) else: self.__setattr__(name, value) # Activate the population self.cyInstance.activate(self.enabled) # Reset to generate the right structures self.cyInstance.reset() # If the spike population has a refractory period: if self.neuron_type.type == 'spike' and self.neuron_type.description['refractory']: if isinstance(self.neuron_type.description['refractory'], str): # a global variable try: self.refractory = eval('self.'+self.neuron_type.description['refractory']) except Exception as e: Global._print(e, self.neuron_type.description['refractory']) Global._error('The initialization for the refractory period is not valid.') else: # a value self.refractory = self.neuron_type.description['refractory'] # Spiking neurons can compute a mean FR if self.neuron_type.type == 'spike': getattr(self.cyInstance, 'compute_firing_rate')(self._compute_mean_fr)
def set_variable_equation(self, name, equation): """ Changes the equation of a variable for the projection. If the variable ``w`` is defined in the Synapse description through: eta * dw/dt = pre.r * post.r one can change the equation with: proj.set_variable_equation('w', 'eta * dw/dt = pre.r * (post.r - 0.1) ') Only the equation should be provided, the flags have to be changed with ``set_variable_flags()``. .. warning:: This method should be used with great care, it is advised to define another Synapse object instead. *Parameters*: * **name**: the name of the variable. * **equation**: the new equation as string. """ rk_var = self._find_variable_index(name) if rk_var == -1: Global._error('The projection '+self.name+' has no variable called ' + name) return self.synapse_type.description['variables'][rk_var]['eq'] = equation
def _set_cython_attribute(self, attribute, value): """ Sets the value of the given attribute for all neurons in the population, as a NumPy array having the same geometry as the population if it is local. *Parameter:* * **attribute**: should be a string representing the variables's name. * **value**: a value or Numpy array of the right size. """ try: if attribute in self.neuron_type.description['local']: if isinstance(value, np.ndarray): getattr(self.cyInstance, 'set_'+attribute)(value.reshape(self.size)) elif isinstance(value, list): getattr(self.cyInstance, 'set_'+attribute)(np.array(value).reshape(self.size)) else: getattr(self.cyInstance, 'set_'+attribute)(value * np.ones( self.size )) else: getattr(self.cyInstance, 'set_'+attribute)(value) except Exception as e: Global._debug(e) err_msg = """Population.set(): either the variable '%(attr)s' does not exist in the population '%(pop)s', or the provided array does not have the right size.""" Global._error(err_msg % { 'attr': attribute, 'pop': self.name } )
def connect_gaussian(self, amp, sigma, min_val, max_distance=0.0): """ Creates the diagonal connection pattern for 4D populations and Gaussian filter.. *Parameters*: * **amp**: maximal value of the Gaussian. * **sigma**: width of the Gaussian. * **min_val**: minimal value of the weight. * **max_distance**: maximal distance for the Gaussian. """ self.amp = amp self.sigma = sigma self.min_val = min_val self.max_distance = max_distance self.weights = {} if not (self.pre.dimension == 4 and self.post.dimension == 4): Global._error( 'The diagonal projection only works when both populations have 4 dimensions.' ) exit(0) self.offset_w = (self.pre.geometry[0] - (self.pre.geometry[0] % 2)) / 2.0 self.offset_h = (self.pre.geometry[1] - (self.pre.geometry[1] % 2)) / 2.0 self.sigma_w = self.sigma * (self.post.geometry[2] - self.post.geometry[2] % 2) self.sigma_h = self.sigma * (self.post.geometry[3] - self.post.geometry[3] % 2) # for post2 in xrange(self.post.geometry[2]): # for post3 in xrange(self.post.geometry[3]): # for pre0 in xrange(self.pre.geometry[0]): # for pre1 in xrange(self.pre.geometry[1]): # for pre2 in xrange(self.pre.geometry[2]): # for pre3 in xrange(self.pre.geometry[3]): # dist_w = (post2 - (pre0+pre2) + self.offset_w) # dist_h = (post3 - (pre1+pre3) + self.offset_h) # val = self.amp * np.exp(- (dist_w*dist_w/self.sigma_w/self.sigma_w + dist_h*dist_h/self.sigma_h/self.sigma_h) ) # self.weights[(dist_w, dist_h)] = val for dist_w in xrange( int(self.offset_w) - self.pre.geometry[0] - self.pre.geometry[2], int(self.offset_w) + self.post.geometry[2]): for dist_h in xrange( int(self.offset_h) - self.pre.geometry[1] - self.pre.geometry[3], int(self.offset_h) + self.post.geometry[3]): val = self.amp * np.exp( -(dist_w * dist_w / self.sigma_w / self.sigma_w + dist_h * dist_h / self.sigma_h / self.sigma_h)) self.weights[(dist_w, dist_h)] = val # create a fake CSR object self._create() return self
def check_and_apply_pow_fix(eqs): """ CUDA SDKs before 7.5 had an error if std=c++11 is enabled related to pow(double, int). Only pow(double, double) was detected as device function, the pow(double, int) will be detected as host function. (This was fixed within SDK 7.5) To support also earlier versions, we simply add a double type cast. """ if eqs.strip() == "": # nothing to do return eqs try: from ANNarchy.generator.CudaCheck import CudaCheck if CudaCheck().runtime_version() > 7000: # nothing to do, is working in higher SDKs return eqs except: Global._error('CUDA is not correctly installed on your system') if Global.config['verbose']: Global._print('occurance of pow() and SDK below 7.5 detected, apply fix.') # detect all pow statements pow_occur = re.findall(r"pow[\( [\S\s]*?\)*?, \d+\)]*?", eqs) for term in pow_occur: eqs = eqs.replace(term, term.replace(', ', ', (double)')) return eqs
def _data(self): "Method gathering all info about the projection when calling save()" if not self.initialized: Global._error('save_connectivity(): the network has not been compiled yet.') return {} desc = {} desc['name'] = self.name desc['post_ranks'] = self.post_ranks desc['attributes'] = self.attributes desc['parameters'] = self.parameters desc['variables'] = self.variables desc['pre_ranks'] = self.cyInstance.pre_rank_all() # Attributes to save attributes = self.attributes if not 'w' in self.attributes: attributes.append('w') # Save all attributes for var in attributes: try: desc[var] = getattr(self.cyInstance, 'get_'+var)() except Exception as e: Global._error('Can not save the attribute ' + var + ' in the projection.') return desc
def create_synapse(self, rank, w=0.0, delay=0): """ Creates a synapse for this dendrite with the given pre-synaptic neuron. *Parameters*: * **rank**: rank of the pre-synaptic neuron * **w**: synaptic weight (defalt: 0.0). * **delay**: synaptic delay (default = dt) """ if not Global.config['structural_plasticity']: Global._error('"structural_plasticity" has not been set to True in setup(), can not add the synapse.') return if rank in self.pre_ranks: Global._error('the synapse of rank ' + str(rank) + ' already exists.') return # Set default values for the additional variables extra_attributes = {} for var in self.proj.synapse_type.description['parameters'] + self.proj.synapse_type.description['variables']: if not var['name'] in ['w', 'delay'] and var['name'] in self.proj.synapse_type.description['local']: if not isinstance(self.proj.init[var['name']], (int, float, bool)): init = var['init'] else: init = self.proj.init[var['name']] extra_attributes[var['name']] = init try: self.proj.cyInstance.add_synapse(self.post_rank, rank, w, int(delay/Global.config['dt']), **extra_attributes) except Exception as e: Global._print(e)
def __init__(self, spike_times, name=None): if not isinstance(spike_times, list): Global._error('In a SpikeSourceArray, spike_times must be a Python list.') if isinstance(spike_times[0], list): # several neurons nb_neurons = len(spike_times) else: # a single Neuron nb_neurons = 1 spike_times = [ spike_times ] # Create a fake neuron just to be sure the description has the correct parameters neuron = Neuron( parameters=""" spike_times = 0.0 : int """, equations="", spike=" t == spike_times", reset="", name="Spike source", description="Spikes source array." ) Population.__init__(self, geometry=nb_neurons, neuron=neuron, name=name) self.init['spike_times'] = spike_times
def reset(self, attributes=-1): """ Resets all parameters and variables of the population to the value they had before the call to compile(). :param attributes: list of attributes (parameter or variable) which should be reinitialized. Default: all attributes. """ if attributes == -1: try: self.set(self.init) except Exception as e: Global._print(e) Global._error( "Population.reset(): something went wrong while resetting." ) else: # only some of them for var in attributes: # check it exists if not var in self.attributes: Global._warning( "Population.reset():", var, "is not an attribute of the population, skipping.") continue try: self.__setattr__(var, self.init[var]) except Exception as e: Global._print(e) Global._warning( "Population.reset(): something went wrong while resetting", var) self.cyInstance.activate(self.enabled) self.cyInstance.reset()
def extract_spike_variable(description): cond = prepare_string(description['raw_spike']) if len(cond) > 1: Global.Global._print(description['raw_spike']) Global._error('The spike condition must be a single expression') translator = Equation('raw_spike_cond', cond[0].strip(), description) raw_spike_code = translator.parse() # Also store the variables used in the condition, as it may be needed for CUDA generation spike_code_dependencies = translator.dependencies() reset_desc = [] if 'raw_reset' in description.keys() and description['raw_reset']: reset_desc = process_equations(description['raw_reset']) for var in reset_desc: translator = Equation(var['name'], var['eq'], description) var['cpp'] = translator.parse() var['dependencies'] = translator.dependencies() return { 'spike_cond': raw_spike_code, 'spike_cond_dependencies': spike_code_dependencies, 'spike_reset': reset_desc }
def gpu_count(self): try: result = self.cy_cc.gpu_count() except Exception as e: Global._print(e) Global._error('CUDA is not correctly installed on your system.') return result
def connect_gaussian(self, amp, sigma, delays=0.0, limit=0.01, allow_self_connections=False): """ Builds a Gaussian connection pattern between the two populations. Each neuron in the postsynaptic population is connected to a region of the presynaptic population centered around the neuron with the same normalized coordinates using a Gaussian profile. *Parameters*: * **amp**: amplitude of the Gaussian function * **sigma**: width of the Gaussian function * **delays**: synaptic delay, either a single value or a random distribution object (default=dt). * **limit**: proportion of *amp* below which synapses are not created (default: 0.01) * **allow_self_connections**: allows connections between a neuron and itself. """ if self.pre!=self.post: allow_self_connections = True if isinstance(self.pre, PopulationView) or isinstance(self.post, PopulationView): Global._error('Gaussian connector is only possible on whole populations, not PopulationViews.') self.connector_name = "Gaussian" self.connector_description = "Gaussian, $A$ %(A)s, $\sigma$ %(sigma)s, delays %(delay)s"% {'A': str(amp), 'sigma': str(sigma), 'delay': _process_random(delays)} self._store_connectivity(Connector.gaussian, (amp, sigma, delays, limit, allow_self_connections), delays) return self
def max_threads_per_block(self, device=0): try: result = self.cy_cc.max_threads_per_block(device) except Exception as e: Global._print(e) Global._error('CUDA is not correctly installed on your system.') return result
def __init__(self, geometry, name=None): """ *Parameters*: * *geometry*: population geometry as tuple. It must correspond to the image size and be fixed through the whole simulation. * If the geometry is 2D, it corresponds to the (height, width) of the image. Only the luminance of the pixels will be represented (grayscale image). * If the geometry is 3D, the third dimension can be either 1 (grayscale) or 3 (color). If the third dimension is 3, each will correspond to the RGB values of the pixels. .. warning:: Due to the indexing system of Numpy, a 640*480 image should be fed into a (480, 640) or (480, 640, 3) population. * *name*: unique name of the population (optional). """ # Check geometry if isinstance(geometry, int) or len(geometry)==1: Global._error('The geometry of an ImagePopulation should be 2D (grayscale) or 3D (color).') exit(0) if len(geometry)==3 and (geometry[2]!=3 and geometry[2]!=1): Global._error('The third dimension of an ImagePopulation should be either 1 (grayscale) or 3 (color).') exit(0) if len(geometry)==3 and geometry[2]==1: geometry = (geometry[0], geometry[1]) # Create the population Population.__init__(self, geometry = geometry, name=name, neuron = Neuron(parameters="r = 0.0") )
def _load_from_sparse(self, pre, post, weights, delays): # Create an empty LIL object lil = LILConnectivity() # Find offsets if isinstance(self.pre, PopulationView): pre_ranks = self.pre.ranks else: pre_ranks = [i for i in range(self.pre.size)] if isinstance(self.post, PopulationView): post_ranks = self.post.ranks else: post_ranks = [i for i in range(self.post.size)] # Process the sparse matrix and fill the lil weights.sort_indices() (pre, post) = weights.shape if (pre, post) != (len(pre_ranks), len(post_ranks)): Global._print("ERROR: connect_from_sparse(): the sparse matrix does not have the correct dimensions.") Global._print('Expected:', (len(pre_ranks), len(post_ranks))) Global._print('Received:', (pre, post)) Global._error('Quitting...') for idx_post in range(post): idx_pre = weights.getcol(idx_post).indices w = weights.getcol(idx_post).data pr = [pre_ranks[i] for i in idx_pre] lil.add(post_ranks[idx_post], pr, w, [float(delays)]) return lil
def connect_from_matrix(self, weights, delays=0.0, pre_post=False): """ Builds a connection pattern according to a dense connectivity matrix. The matrix must be N*M, where N is the number of neurons in the post-synaptic population and M in the pre-synaptic one. Lists of lists must have the same size. If a synapse should not be created, the weight value should be None. *Parameters*: * **weights**: a matrix or list of lists representing the weights. If a value is None, the synapse will not be created. * **delays**: a matrix or list of lists representing the delays. Must represent the same synapses as weights. If the argument is omitted, delays are 0. * **pre_post**: states which index is first. By default, the first dimension is related to the post-synaptic population. If ``pre_post`` is True, the first dimension is the pre-synaptic population. """ # Store the synapses self.connector_name = "Connectivity matrix" self.connector_description = "Connectivity matrix" if isinstance(weights, list): try: weights= np.array(weights) except: Global._error('connect_from_matrix(): You must provide a dense 2D matrix.') self._store_connectivity(self._load_from_matrix, (weights, delays, pre_post), delays) return self
def connect_fixed_number_post(self, number, weights=1.0, delays=0.0, allow_self_connections=False, force_multiple_weights=False): """ Builds a connection pattern between the two populations with a fixed number of post-synaptic neurons. Each neuron in the pre-synaptic population sends connections to a fixed number of neurons of the post-synaptic population chosen randomly. *Parameters*: * **number**: number of synapses per pre-synaptic neuron. * **weights**: either a single value for all synapses or a RandomDistribution object. * **delays**: either a single value for all synapses or a RandomDistribution object (default = dt) * **allow_self_connections** : defines if self-connections are allowed (default=False) * **force_multiple_weights**: if a single value is provided for ``weights`` and there is no learning, a single weight value will be used for the whole projection instead of one per synapse. Setting ``force_multiple_weights`` to True ensures that a value per synapse will be used. """ if self.pre!=self.post: allow_self_connections = True if number > self.post.size: Global._error('connect_fixed_number_post: the number of post-synaptic neurons exceeds the size of the population.') self.connector_name = "Random Divergent" self.connector_description = "Random Divergent 1 $\\rightarrow$ %(number)s, weights %(weight)s, delays %(delay)s"% {'weight': _process_random(weights), 'delay': _process_random(delays), 'number': number} if isinstance(weights, (int, float)) and not force_multiple_weights: self._single_constant_weight = True self._store_connectivity( fixed_number_post, (number, weights, delays, allow_self_connections, "lil", "post_to_pre"), delays, "lil", "post_to_pre") return self
def connect_dog(self, amp_pos, sigma_pos, amp_neg, sigma_neg, delays=0.0, limit=0.01, allow_self_connections=False): """ Builds a Difference-Of-Gaussians connection pattern between the two populations. Each neuron in the postsynaptic population is connected to a region of the presynaptic population centered around the neuron with the same normalized coordinates using a Difference-Of-Gaussians profile. *Parameters*: * **amp_pos**: amplitude of the positive Gaussian function * **sigma_pos**: width of the positive Gaussian function * **amp_neg**: amplitude of the negative Gaussian function * **sigma_neg**: width of the negative Gaussian function * **delays**: synaptic delay, either a single value or a random distribution object (default=dt). * **limit**: proportion of *amp* below which synapses are not created (default: 0.01) * **allow_self_connections**: allows connections between a neuron and itself. """ if self.pre!=self.post: allow_self_connections = True if isinstance(self.pre, PopulationView) or isinstance(self.post, PopulationView): Global._error('DoG connector is only possible on whole populations, not PopulationViews.') self.connector_name = "Difference-of-Gaussian" self.connector_description = "Difference-of-Gaussian, $A^+ %(Aplus)s, $\sigma^+$ %(sigmaplus)s, $A^- %(Aminus)s, $\sigma^-$ %(sigmaminus)s, delays %(delay)s"% {'Aplus': str(amp_pos), 'sigmaplus': str(sigma_pos), 'Aminus': str(amp_neg), 'sigmaminus': str(sigma_neg), 'delay': _process_random(delays)} self._store_connectivity( dog, (amp_pos, sigma_pos, amp_neg, sigma_neg, delays, limit, allow_self_connections, "lil", "post_to_pre"), delays, "lil", "post_to_pre") return self
def check_structure(populations, projections): """ Checks the structure before compilation to display more useful error messages. """ from ANNarchy.extensions.convolution.Transpose import Transpose # Check variable names _check_reserved_names(populations, projections) # Check that projections are created before compile for proj in projections: if isinstance(proj, Transpose): continue if not proj._connection_method: Global._error( 'The projection between populations', proj.pre.id, 'and', proj.post.id, 'has not been connected.', ' Call a connector method before compiling the network.') # Check if the storage formats are valid for the selected paradigm _check_storage_formats(projections) # Check that synapses access existing variables in the pre or post neurons _check_prepost(populations, projections) # Check locality of variable is respected _check_locality(populations, projections)
def get_values(self, shape): """ Returns a np.ndarray with the given shape """ Global._error( 'instantiated base class RandomDistribution is not allowed.') return 0.0
def _generate(self): # Generate the code if self.pre.dimension == 2 and self.post.dimension == 2: self._generate_omp_1d() elif self.pre.dimension == 4 and self.post.dimension == 4: self._generate_omp_2d_gaussian() else: Global._error('The diagonal projection only works when both populations have 2 or 4 dimensions.')