diff --git a/brian2genn/device.py b/brian2genn/device.py index 6c78f76..9e165aa 100644 --- a/brian2genn/device.py +++ b/brian2genn/device.py @@ -160,8 +160,8 @@ def extract_source_variables(variables, varname, smvariables): def find_executable(executable): """Tries to find 'executable' in the path - Modified version of distutils.spawn.find_executable as - this has stupid rules for extensions on Windows. + Modified version of distutils.spawn.find_executable as + this has stupid rules for extensions on Windows. Returns the complete filename or None if not found. """ path = os.environ.get('PATH', os.defpath) @@ -199,7 +199,7 @@ def after_run(self): class neuronModel(object): ''' - Class that contains all relevant information of a neuron model. + Class that contains all relevant information of a neuron model. ''' def __init__(self): @@ -335,6 +335,7 @@ def __init__(self): #: Set of all source and header files (to be included in runner) self.source_files = set() self.header_files = set() + self._seed = 0 self.connectivityDict = dict() self.groupDict = dict() @@ -478,7 +479,7 @@ def code_object(self, owner, name, abstract_code, variables, template_name, else: codeobj_class = GeNNUserCodeObject if ('_synapses_create_generator_' in name) or ('_synapses_create_array_' in name): - # Here we process max_row_length for synapses + # Here we process max_row_length for synapses # the strategy is to do a dry run of connection generationin in the model definition # function that has the same random numbers and just counts synaptic connections # rather than generating them for real @@ -685,8 +686,15 @@ def make_main_lines(self): runfuncs[name] = main_lines name, main_lines = procedures[-1] elif func == 'seed': - raise NotImplementedError('Setting a seed is currently ' - 'not supported') + logger.warn('Setting a seed with Brian2GeNN is experimental.') + self._seed = 0 if args is None else int(args) + if args == 0: + logger.warn('In Brian2GeNN, seed(0) is equivalent to ' + 'seed() and sets a randomly chosen seed.') + if self._seed == 0: + main_lines.append('rk_randomseed(brian::_mersenne_twister_states[0]);') + else: + main_lines.append(f'rk_seed({self._seed}, brian::_mersenne_twister_states[0]);') else: raise TypeError("Unknown main queue function type " + func) @@ -708,6 +716,8 @@ def fix_random_generators(self, model, code): # commonly used. We cannot check for explicit names `_rand`, etc., # since multiple uses of binomial or PoissonInput will need to names # that we cannot easily predict (poissoninput_binomial_2, etc.) + code = code.replace('_rand(_vectorisation_idx)', '$(gennrand_uniform)') + code = code.replace('_randn(_vectorisation_idx)', '$(gennrand_normal)') if '_vectorisation_idx)' in code: code = code.replace('_vectorisation_idx)', '_seed)') @@ -741,7 +751,7 @@ def build(self, directory='GeNNworkspace', compile=True, run=True, ''' print('building genn executable ...') - + if directory is None: # used during testing directory = tempfile.mkdtemp() @@ -980,7 +990,7 @@ def generate_max_row_length_code_objects(self, writer): code_object_defs[codeobj.name].add('const int _num%s = %s;' % (k, v.size)) except TypeError: pass - + for codeobj in itervalues(self.max_row_length_code_objects): ns = codeobj.variables # TODO: fix these freeze/CONSTANTS hacks somehow - they work but not elegant. @@ -989,8 +999,8 @@ def generate_max_row_length_code_objects(self, writer): code_object_defs[codeobj.name])) writer.write('code_objects/' + codeobj.name + '.cpp', code) - - + + def run(self, directory, use_GPU, with_output): gpu_arg = "1" if use_GPU else "0" if gpu_arg == "1": @@ -1061,7 +1071,7 @@ def compile_source(self, debug, directory, use_GPU): genn_bin = (find_executable("genn-buildmodel.bat") if os.sys.platform == 'win32' else find_executable("genn-buildmodel.sh")) - + if genn_bin is None: raise RuntimeError('Add GeNN\'s bin directory to the path ' 'or set the devices.genn.path preference.') @@ -1070,7 +1080,7 @@ def compile_source(self, debug, directory, use_GPU): genn_path = os.path.normpath(os.path.join(os.path.dirname(genn_bin), "..")) logger.debug('Using GeNN path determined from path: ' '"{}"'.format(genn_path)) - + # Check for GeNN compatibility genn_version = None version_file = os.path.join(genn_path, 'version.txt') @@ -1106,13 +1116,13 @@ def compile_source(self, debug, directory, use_GPU): if os.sys.platform == 'win32': # Make sure that all environment variables are upper case env = {k.upper() : v for k, v in iteritems(env)} - + # If there is vcvars command to call, start cmd with that cmd = '' msvc_env, vcvars_cmd = get_msvc_env() if vcvars_cmd: cmd += vcvars_cmd + ' && ' - # Otherwise, update environment, again ensuring + # Otherwise, update environment, again ensuring # that all variables are upper case else: env.update({k.upper() : v for k, v in iteritems(msvc_env)}) @@ -1121,11 +1131,11 @@ def compile_source(self, debug, directory, use_GPU): buildmodel_cmd = os.path.join(genn_path, 'bin', 'genn-buildmodel.bat') cmd += buildmodel_cmd + ' -s' - + # If we're not using CPU, add CPU option if not use_GPU: cmd += ' -c' - + # Add include directories # **NOTE** on windows semicolons are used to seperate multiple include paths # **HACK** argument list syntax to check_call doesn't support quoting arguments to batch @@ -1134,15 +1144,15 @@ def compile_source(self, debug, directory, use_GPU): cmd += ' -i "%s;%s;%s"' % (wdir, os.path.join(wdir, directory), os.path.join(wdir, directory, 'brianlib','randomkit')) cmd += ' magicnetwork_model.cpp' - + # Add call to build generated code cmd += ' && msbuild /m /verbosity:minimal /p:Configuration=Release "' + os.path.join(wdir, directory, 'magicnetwork_model_CODE', 'runner.vcxproj') + '"' - + # Add call to build executable cmd += ' && msbuild /m /verbosity:minimal /p:Configuration=Release "' + os.path.join(wdir, directory, 'project.vcxproj') + '"' - + # Run combined command - # **NOTE** because vcvars MODIFIED environment, + # **NOTE** because vcvars MODIFIED environment, # making seperate check_calls doesn't work check_call(cmd, cwd=directory, env=env) else: @@ -1150,7 +1160,7 @@ def compile_source(self, debug, directory, use_GPU): # declare the link flags as an environment variable so that GeNN's # generateALL can pick it up env['LDFLAGS'] = ' '.join(prefs['codegen.cpp.extra_link_args']) - + buildmodel_cmd = os.path.join(genn_path, 'bin', 'genn-buildmodel.sh') args = [buildmodel_cmd] if not use_GPU: @@ -1192,7 +1202,7 @@ def process_poisson_groups(self, objects, poisson_groups): for obj in poisson_groups: # throw error if events other than spikes are used event_keys = list(iterkeys(obj.events)) - if (len(event_keys) > 1 + if (len(event_keys) > 1 or (len(event_keys) == 1 and event_keys[0] != 'spike')): raise NotImplementedError( 'Brian2GeNN does not support events that are not spikes') @@ -1463,7 +1473,7 @@ def process_synapses(self, synapse_groups, objects): if k not in synapse_model.variables: self.add_array_variable(synapse_model, k, v) addVar= addVar.replace(k,'$('+k+')') - code= '\\n\\\n $(addToInSyn,'+addVar+');\\n' + code= '\\n\\\n $(addToInSyn,'+addVar+');\\n' synapse_model.main_code_lines['dynamics'] += code #quick and dirty test to avoid adding the same support code twice support_code = stringify('\n'.join(kwds['support_code_lines'])) @@ -1670,7 +1680,8 @@ def generate_model_source(self, writer, main_lines, use_GPU): dtDef=self.dtDef, prefs=prefs, precision=precision, - header_files=prefs['codegen.cpp.headers'] + header_files=prefs['codegen.cpp.headers'], + seed=self._seed, ) writer.write('magicnetwork_model.cpp', model_tmp) diff --git a/brian2genn/templates/model.cpp b/brian2genn/templates/model.cpp index a574d1b..5e25cf0 100644 --- a/brian2genn/templates/model.cpp +++ b/brian2genn/templates/model.cpp @@ -225,6 +225,7 @@ void modelDefinition(NNmodel &model) {% if prefs['devices.genn.kernel_timing'] %} model.setTiming(true); {% endif %} + model.setSeed({{seed}}); {% for neuron_model in neuron_models %} model.addNeuronPopulation<{{neuron_model.name}}NEURON>("{{neuron_model.name}}", {{neuron_model.N}}, {{neuron_model.name}}_p, {{neuron_model.name}}_ini); {% endfor %}