diff --git a/README.md b/README.md index 98ab245b5a0575fc74d30ac7c7d6b8ed23a57809..9c82bec131843f334d01b194a2003da007826087 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ technical parameters such as the number of parallel MPI processes and threads. The simulation uses the network simulator NEST (https://www.nest-simulator.org). For the simulations in [2, 3], we used NEST version 2.8.0. The code in this repository runs with a -later release of NEST, version 2.14.0. +later release of NEST, version 2.14.0, as well as NEST 3.0. `Theory` @@ -203,7 +203,7 @@ conveniently run by executing `pytest` in the `tests/` folder: ## Requirements Python 3, python\_dicthash ([https://github.com/INM-6/python-dicthash](https://github.com/INM-6/python-dicthash)), correlation\_toolbox ([https://github.com/INM-6/correlation-toolbox](https://github.com/INM-6/correlation-toolbox)), -pandas, numpy, nested_dict, matplotlib (2.1.2), scipy, pytest, NEST 2.14.0 +pandas, numpy, nested_dict, matplotlib (2.1.2), scipy, pytest, NEST 2.14.0 or NEST 3.0 Optional: seaborn, Sumatra diff --git a/multiarea_model/analysis.py b/multiarea_model/analysis.py index b5e76737b06f92c9fe6f8da19a3507d80b81fe82..0b0b5d6f25e926a6b2b3088a09cbd79b074c625b 100644 --- a/multiarea_model/analysis.py +++ b/multiarea_model/analysis.py @@ -128,17 +128,23 @@ class Analysis: data[area][pop] = np.load(fn, allow_pickle=True) except FileNotFoundError: if not hasattr(self, 'all_spikes'): + csv_args = {'names': columns, + 'sep': '\t', + 'index_col': False} + if self.network.params['USING_NEST_3']: + csv_args.update({'skiprows': 3}) + file_ending = 'dat' + else: + file_ending = 'gdf' fp = '.'.join(('-'.join((self.simulation.label, self.simulation.params[ 'recording_dict'][d]['label'], '*')), - 'gdf')) + file_ending)) files = glob.glob(os.path.join(rec_dir, fp)) dat = pd.DataFrame(columns=columns) for f in files: - dat = dat.append(pd.read_csv(f, - names=columns, sep='\t', - index_col=False), + dat = dat.append(pd.read_csv(f, **csv_args), ignore_index=True) self.all_spikes = dat print(area, pop) diff --git a/multiarea_model/default_params.py b/multiarea_model/default_params.py index 73dc579b541817e4b2eff76e949ae2c387e4efb3..a09b449254c5e4f908d03500f3e0500190eb1e13 100644 --- a/multiarea_model/default_params.py +++ b/multiarea_model/default_params.py @@ -14,6 +14,7 @@ Maximilian Schmidt from config import base_path import json import os +import nest import numpy as np @@ -37,7 +38,7 @@ Simulation parameters """ sim_params = { # master seed for random number generators - 'master_seed': 0, + 'rng_seed': 1, # simulation step (in ms) 'dt': 0.1, # simulated time (in ms) @@ -62,7 +63,11 @@ network_params = { 'K_scaling': 1., # Absolute path to the file holding full-scale rates for scaling # synaptic weights - 'fullscale_rates': None + 'fullscale_rates': None, + # Check whether NEST 2 or 3 is used. No straight way of checking this is + # available. But PrintNetwork was removed in NEST 3, so checking for its + # existence should suffice. + 'USING_NEST_3': 'PrintNetwork' not in dir(nest) } @@ -246,18 +251,22 @@ recording_dict = { # Parameters for the spike detectors 'spike_dict': { 'label': 'spikes', - 'withtime': True, - 'record_to': ['file'], 'start': 0.}, # Parameters for the voltmeters 'vm_dict': { 'label': 'vm', 'start': 0., 'stop': 1000., - 'interval': 0.1, - 'withtime': True, - 'record_to': ['file']} + 'interval': 0.1} } +if network_params['USING_NEST_3']: + recording_dict['spike_dict'].update({'record_to': 'ascii'}) + recording_dict['vm_dict'].update({'record_to': 'ascii'}) +else: + recording_dict['spike_dict'].update({'withtime': True, + 'record_to': ['file']}) + recording_dict['vm_dict'].update({'withtime': True, + 'record_to': ['file']}) sim_params.update({'recording_dict': recording_dict}) """ diff --git a/multiarea_model/simulation.py b/multiarea_model/simulation.py index 1615c86bcd3aad17bf4bd20fb337c487a0dd9fab..4d474babfc12033dd265f47a6cd41b8f72733a5c 100644 --- a/multiarea_model/simulation.py +++ b/multiarea_model/simulation.py @@ -145,7 +145,7 @@ class Simulation: Prepare NEST Kernel. """ nest.ResetKernel() - master_seed = self.params['master_seed'] + rng_seed = self.params['rng_seed'] num_processes = self.params['num_processes'] local_num_threads = self.params['local_num_threads'] vp = num_processes * local_num_threads @@ -153,15 +153,18 @@ class Simulation: 'total_num_virtual_procs': vp, 'overwrite_files': True, 'data_path': os.path.join(self.data_dir, 'recordings'), - 'print_time': False, - 'grng_seed': master_seed, - 'rng_seeds': list(range(master_seed + 1, - master_seed + vp + 1))}) - + 'print_time': False}) + if self.network.params['USING_NEST_3']: + nest.SetKernelStatus({'rng_seed': rng_seed}) + else: + nest.SetKernelStatus({'grng_seed': rng_seed, + 'rng_seeds': list(range(rng_seed + 1, + rng_seed + vp + 1))}) + self.pyrngs = [np.random.RandomState(s) for s in list(range( + rng_seed + vp + 1, rng_seed + 2 * (vp + 1)))] nest.SetDefaults(self.network.params['neuron_params']['neuron_model'], self.network.params['neuron_params']['single_neuron_dict']) - self.pyrngs = [np.random.RandomState(s) for s in list(range( - master_seed + vp + 1, master_seed + 2 * (vp + 1)))] + def create_recording_devices(self): """ @@ -170,7 +173,10 @@ class Simulation: - spike detector - voltmeter """ - self.spike_detector = nest.Create('spike_detector', 1) + try: + self.spike_detector = nest.Create('spike_recorder') + except: + self.spike_detector = nest.Create('spike_detector') status_dict = deepcopy(self.params['recording_dict']['spike_dict']) label = '-'.join((self.label, status_dict['label'])) @@ -347,10 +353,16 @@ class Simulation: 'network_gids.txt'), 'w') as f: for area in self.areas: for pop in self.network.structure[area.name]: + if self.network.params['USING_NEST_3']: + first_id = area.gids[pop][0].get()['global_id'] + last_id = area.gids[pop][-1].get()['global_id'] + else: + first_id = area.gids[pop][0] + last_id = area.gids[pop][1] f.write("{area},{pop},{g0},{g1}\n".format(area=area.name, pop=pop, - g0=area.gids[pop][0], - g1=area.gids[pop][1])) + g0=first_id, + g1=last_id)) def register_runtime(self): if sumatra_found: @@ -443,31 +455,39 @@ class Area: I_e += DC nest.SetStatus(gid, {'I_e': I_e}) - # Store first and last GID of each population - self.gids[pop] = (gid[0], gid[-1]) + if self.network.params['USING_NEST_3']: + # Store GIDCollection of each population + self.gids[pop] = gid + else: + # Store first and last GID of each population + self.gids[pop] = (gid[0], gid[-1]) # Initialize membrane potentials # This could also be done after creating all areas, which # might yield better performance. Has to be tested. - for t in np.arange(nest.GetKernelStatus('local_num_threads')): - local_nodes = np.array(nest.GetNodes( - [0], { - 'model': self.network.params['neuron_params']['neuron_model'], - 'thread': t - }, local_only=True - )[0]) - local_nodes_pop = local_nodes[(np.logical_and(local_nodes >= gid[0], - local_nodes <= gid[-1]))] - if len(local_nodes_pop) > 0: - vp = nest.GetStatus([local_nodes_pop[0]], 'vp')[0] - # vp is the same for all local nodes on the same thread - nest.SetStatus( - list(local_nodes_pop), 'V_m', self.simulation.pyrngs[vp].normal( - self.network.params['neuron_params']['V0_mean'], - self.network.params['neuron_params']['V0_sd'], - len(local_nodes_pop)) - ) - self.num_local_nodes += len(local_nodes_pop) + if self.network.params['USING_NEST_3']: + gid.V_m = nest.random.normal(self.network.params['neuron_params']['V0_mean'], + self.network.params['neuron_params']['V0_sd']) + else: + for t in np.arange(nest.GetKernelStatus('local_num_threads')): + local_nodes = np.array(nest.GetNodes( + [0], { + 'model': self.network.params['neuron_params']['neuron_model'], + 'thread': t + }, local_only=True + )[0]) + local_nodes_pop = local_nodes[(np.logical_and(local_nodes >= gid[0], + local_nodes <= gid[-1]))] + if len(local_nodes_pop) > 0: + vp = nest.GetStatus([local_nodes_pop[0]], 'vp')[0] + # vp is the same for all local nodes on the same thread + nest.SetStatus( + list(local_nodes_pop), 'V_m', self.simulation.pyrngs[vp].normal( + self.network.params['neuron_params']['V0_mean'], + self.network.params['neuron_params']['V0_sd'], + len(local_nodes_pop)) + ) + self.num_local_nodes += len(local_nodes_pop) def connect_populations(self): """ @@ -482,28 +502,41 @@ class Area: for pop in self.populations: # Always record spikes from all neurons to get correct # statistics - nest.Connect(tuple(range(self.gids[pop][0], self.gids[pop][1] + 1)), - self.simulation.spike_detector) + if self.network.params['USING_NEST_3']: + nest.Connect(self.gids[pop], + self.simulation.spike_detector) + else: + nest.Connect(tuple(range(self.gids[pop][0], self.gids[pop][1] + 1)), + self.simulation.spike_detector) if self.simulation.params['recording_dict']['record_vm']: for pop in self.populations: nrec = int(self.simulation.params['recording_dict']['Nrec_vm_fraction'] * self.neuron_numbers[pop]) - nest.Connect(self.simulation.voltmeter, - tuple(range(self.gids[pop][0], self.gids[pop][0] + nrec + 1))) + if self.network.params['USING_NEST_3']: + nest.Connect(self.simulation.voltmeter, + self.gids[pop][:nrec]) + else: + nest.Connect(self.simulation.voltmeter, + tuple(range(self.gids[pop][0], self.gids[pop][0] + nrec + 1))) if self.network.params['input_params']['poisson_input']: self.poisson_generators = [] for pop in self.populations: K_ext = self.external_synapses[pop] W_ext = self.network.W[self.name][pop]['external']['external'] - pg = nest.Create('poisson_generator', 1) + pg = nest.Create('poisson_generator') nest.SetStatus( pg, {'rate': self.network.params['input_params']['rate_ext'] * K_ext}) syn_spec = {'weight': W_ext} - nest.Connect(pg, - tuple( - range(self.gids[pop][0], self.gids[pop][1] + 1)), - syn_spec=syn_spec) + if self.network.params['USING_NEST_3']: + nest.Connect(pg, + self.gids[pop], + syn_spec=syn_spec) + else: + nest.Connect(pg, + tuple( + range(self.gids[pop][0], self.gids[pop][1] + 1)), + syn_spec=syn_spec) self.poisson_generators.append(pg[0]) def create_additional_input(self, input_type, source_area_name, cc_input): @@ -543,7 +576,7 @@ class Area: K = synapses[pop][source_pop] / self.neuron_numbers[pop] if input_type == 'het_current_nonstat': - curr_gen = nest.Create('step_current_generator', 1) + curr_gen = nest.Create('step_current_generator') dt = self.simulation.params['dt'] T = self.simulation.params['t_sim'] assert(len(cc_input[source_pop]) == int(T)) @@ -551,17 +584,27 @@ class Area: 'amplitude_times': np.arange(dt, T + dt, 1.)}) - nest.Connect(curr_gen, - tuple( - range(self.gids[pop][0], self.gids[pop][1] + 1)), - syn_spec=syn_spec) + if self.network.params['USING_NEST_3']: + nest.Connect(curr_gen, + self.gids[pop], + syn_spec=syn_spec) + else: + nest.Connect(curr_gen, + tuple( + range(self.gids[pop][0], self.gids[pop][1] + 1)), + syn_spec=syn_spec) elif 'poisson_stat' in input_type: # hom. and het. poisson lead here - pg = nest.Create('poisson_generator', 1) + pg = nest.Create('poisson_generator') nest.SetStatus(pg, {'rate': K * cc_input[source_pop]}) - nest.Connect(pg, - tuple( - range(self.gids[pop][0], self.gids[pop][1] + 1)), - syn_spec=syn_spec) + if self.network.params['USING_NEST_3']: + nest.Connect(pg, + self.gids[pop], + syn_spec=syn_spec) + else: + nest.Connect(pg, + tuple( + range(self.gids[pop][0], self.gids[pop][1] + 1)), + syn_spec=syn_spec) def connect(simulation, @@ -579,6 +622,7 @@ def connect(simulation, source_area : Area instance Source area of the projection """ + network = simulation.network synapses = extract_area_dict(network.synapses, network.structure, @@ -592,37 +636,80 @@ def connect(simulation, network.structure, target_area.name, source_area.name) - for target in target_area.populations: - for source in source_area.populations: - conn_spec = {'rule': 'fixed_total_number', - 'N': int(synapses[target][source])} - - syn_weight = {'distribution': 'normal_clipped', - 'mu': W[target][source], - 'sigma': W_sd[target][source]} - if target_area == source_area: - if 'E' in source: - syn_weight.update({'low': 0.}) - mean_delay = network.params['delay_params']['delay_e'] - elif 'I' in source: - syn_weight.update({'high': 0.}) - mean_delay = network.params['delay_params']['delay_i'] - else: - v = network.params['delay_params']['interarea_speed'] - s = network.distances[target_area.name][source_area.name] - mean_delay = s / v - - syn_delay = {'distribution': 'normal_clipped', - 'low': simulation.params['dt'], - 'mu': mean_delay, - 'sigma': mean_delay * network.params['delay_params']['delay_rel']} - syn_spec = {'weight': syn_weight, - 'delay': syn_delay, - 'model': 'static_synapse'} - - nest.Connect(tuple(range(source_area.gids[source][0], - source_area.gids[source][1] + 1)), - tuple(range(target_area.gids[target][0], - target_area.gids[target][1] + 1)), - conn_spec, - syn_spec) + + if network.params['USING_NEST_3']: + for target in target_area.populations: + for source in source_area.populations: + conn_spec = {'rule': 'fixed_total_number', + 'N': int(synapses[target][source])} + + if target_area == source_area: + if 'E' in source: + w_min = 0. + w_max = np.Inf + mean_delay = network.params['delay_params']['delay_e'] + elif 'I' in source: + w_min = np.NINF + w_max = 0. + mean_delay = network.params['delay_params']['delay_i'] + else: + w_min = 0. + w_max = np.Inf + v = network.params['delay_params']['interarea_speed'] + s = network.distances[target_area.name][source_area.name] + mean_delay = s / v + + syn_spec = { + 'synapse_model': 'static_synapse', + 'weight': nest.math.redraw( + nest.random.normal(mean=W[target][source], + std=W_sd[target][source]), + min=w_min, + max=w_max), + 'delay': nest.math.redraw( + nest.random.normal( + mean=mean_delay, + std=(mean_delay * + network.params['delay_params']['delay_rel'])), + min=simulation.params['dt'], + max=np.Inf) + } + + nest.Connect(source_area.gids[source], + target_area.gids[target], + conn_spec, + syn_spec) + else: + for target in target_area.populations: + for source in source_area.populations: + conn_spec = {'rule': 'fixed_total_number', + 'N': int(synapses[target][source])} + + syn_weight = {'distribution': 'normal_clipped', + 'mu': W[target][source], + 'sigma': W_sd[target][source]} + if target_area == source_area: + if 'E' in source: + syn_weight.update({'low': 0.}) + mean_delay = network.params['delay_params']['delay_e'] + elif 'I' in source: + syn_weight.update({'high': 0.}) + mean_delay = network.params['delay_params']['delay_i'] + else: + v = network.params['delay_params']['interarea_speed'] + s = network.distances[target_area.name][source_area.name] + mean_delay = s / v + syn_delay = {'distribution': 'normal_clipped', + 'low': simulation.params['dt'], + 'mu': mean_delay, + 'sigma': mean_delay * network.params['delay_params']['delay_rel']} + syn_spec = {'weight': syn_weight, + 'delay': syn_delay, + 'model': 'static_synapse'} + + nest.Connect(tuple(range(source_area.gids[source][0], + source_area.gids[source][1] + 1)), + tuple(range(target_area.gids[target][0], + target_area.gids[target][1] + 1)), + conn_spec, + syn_spec) diff --git a/multiarea_model/theory.py b/multiarea_model/theory.py index c00e54ca6a5e01ffb0664428a6d91762f4eeb428..10501a1fc264b5c74750dd2281d72959499ab980 100644 --- a/multiarea_model/theory.py +++ b/multiarea_model/theory.py @@ -89,8 +89,11 @@ class Theory: # external drive syn_dict = {'drift_factor': tau * np.array([K[:, -1] * J[:, -1]]).transpose(), 'diffusion_factor': tau * np.array([K[:, -1] * J[:, -1]**2]).transpose(), - 'model': 'diffusion_connection', 'receptor_type': 0} + if self.network.params['USING_NEST_3']: + syn_dict['synapse_model'] = 'diffusion_connection' + else: + syn_dict['model'] = 'diffusion_connection' nest.Connect(drive, neurons, 'all_to_all', syn_dict) # external DC drive (expressed in mV) @@ -101,8 +104,11 @@ class Theory: syn_dict = {'drift_factor': 1e3 * tau / C_m * np.array( self.network.add_DC_drive).reshape(dim, 1), 'diffusion_factor': 0., - 'model': 'diffusion_connection', 'receptor_type': 0} + if self.network.params['USING_NEST_3']: + syn_dict['synapse_model'] = 'diffusion_connection' + else: + syn_dict['model'] = 'diffusion_connection' nest.Connect(DC_drive, neurons, 'all_to_all', syn_dict) # handle switches for cortico-cortical connectivity if (self.network.params['connection_params']['replace_cc'] in @@ -115,8 +121,11 @@ class Theory: add_drive = nest.Create('siegert_neuron', 1, params={'rate': 1., 'mean': 1.}) syn_dict = {'drift_factor': np.array([mu_CC]).transpose(), 'diffusion_factor': np.array([sigma2_CC]).transpose(), - 'model': 'diffusion_connection', 'receptor_type': 0} + if self.network.params['USING_NEST_3']: + syn_dict['synapse_model'] = 'diffusion_connection' + else: + syn_dict['model'] = 'diffusion_connection' nest.Connect(add_drive, neurons, 'all_to_all', syn_dict) elif self.network.params['connection_params']['replace_cc'] == 'het_current_nonstat': raise NotImplementedError('Replacing the cortico-cortical input by' @@ -125,8 +134,11 @@ class Theory: # network connections syn_dict = {'drift_factor': tau * K[:, :-1] * J[:, :-1], 'diffusion_factor': tau * K[:, :-1] * J[:, :-1]**2, - 'model': 'diffusion_connection', 'receptor_type': 0} + if self.network.params['USING_NEST_3']: + syn_dict['synapse_model'] = 'diffusion_connection' + else: + syn_dict['model'] = 'diffusion_connection' nest.Connect(neurons, neurons, 'all_to_all', syn_dict) # create recording device @@ -134,11 +146,14 @@ class Theory: if interval is None: interval = dt - multimeter = nest.Create('multimeter', params={'record_from': ['rate'], - 'interval': interval, - 'to_screen': False, - 'to_file': False, - 'to_memory': True}) + multimeter_params = {'record_from': ['rate'], 'interval': interval} + if self.network.params['USING_NEST_3']: + multimeter_params.update({'record_to': 'memory'}) + else: + multimeter_params.update({'to_screen': False, + 'to_file': False, + 'to_memory': True}) + multimeter = nest.Create('multimeter', params=multimeter_params) # multimeter nest.Connect(multimeter, neurons) @@ -168,17 +183,26 @@ class Theory: initial_rates = next(gen) print("Iteration: {}".format(iteration)) for i in range(dim): - nest.SetStatus([neurons[i]], {'rate': initial_rates[i]}) + if self.network.params['USING_NEST_3']: + nest.SetStatus(neurons[i], {'rate': initial_rates[i]}) + else: + nest.SetStatus([neurons[i]], {'rate': initial_rates[i]}) # simulate nest.Simulate(T + dt) data = nest.GetStatus(multimeter)[0]['events'] # Extract the data of this iteration ind = np.where(np.logical_and(data['times'] > total_time, data['times'] <= total_time + T)) - res = np.array([np.insert(data['rate'][ind][np.where(data['senders'][ind] == n)], - 0, - initial_rates[i]) - for i, n in enumerate(neurons)]) + if self.network.params['USING_NEST_3']: + res = np.array([np.insert(data['rate'][ind][np.where(data['senders'][ind] == n.global_id)], + 0, + initial_rates[i]) + for i, n in enumerate(neurons)]) + else: + res = np.array([np.insert(data['rate'][ind][np.where(data['senders'][ind] == n)], + 0, + initial_rates[i]) + for i, n in enumerate(neurons)]) rates.append(res) iteration += 1 total_time += T