From e22a394a988b54ab503af221863f8310d74f2670 Mon Sep 17 00:00:00 2001 From: Maximilian Schmidt <max.schmidt@fz-juelich.de> Date: Thu, 29 Mar 2018 15:20:17 +0900 Subject: [PATCH] Remove dependency from h5py_wrapper completely: Store data in binary numpy data files, add test_analysis to test analysis class, fix many bugs in analysis and analysis_helpers --- README.md | 2 +- environment.yaml | 4 - multiarea_model/analysis.py | 198 ++++++++++++++-------------- multiarea_model/analysis_helpers.py | 121 ++++++++++++----- multiarea_model/default_params.py | 14 +- multiarea_model/simulation.py | 37 ++++-- multiarea_model/theory.py | 11 +- tests/test_analysis.py | 33 +++++ tests/test_replace_cc.py | 12 +- tests/test_simulate_V1.py | 10 +- 10 files changed, 276 insertions(+), 166 deletions(-) create mode 100644 tests/test_analysis.py diff --git a/README.md b/README.md index 669f831..704be89 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ It can be conveniently run by executing `pytest` in the `tests/` folder: ## Requirements -h5py\_wrapper, python\_dicthash ([https://github.com/INM-6/python-dicthash](https://github.com/INM-6/python-dicthash)), +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), pyx, scipy, NEST 2.14.0 diff --git a/environment.yaml b/environment.yaml index e4f6bdf..46ef9cf 100644 --- a/environment.yaml +++ b/environment.yaml @@ -1,16 +1,12 @@ name: multiarea_model dependencies: - - future - numpy - matplotlib - pandas - python==3.6 - scipy - pip: - - h5py - - h5py_wrapper - nested_dict - - requests - https://github.com/INM-6/python-dicthash/archive/master.zip - pyx diff --git a/multiarea_model/analysis.py b/multiarea_model/analysis.py index 035a0b1..ea4b907 100644 --- a/multiarea_model/analysis.py +++ b/multiarea_model/analysis.py @@ -11,7 +11,7 @@ multi-area model of macaque visual cortex (Schmidt et al. 2017). Classes -------- -analysis : loads the data of the specified simulation and provides members +Analysis : loads the data of the specified simulation and provides members functions to post-process the data and plot it in various visualizations. Authors @@ -22,7 +22,6 @@ Sacha van Albada """ from . import analysis_helpers as ah import glob -import h5py_wrapper.wrapper as h5w import inspect import json import matplotlib.pyplot as plt @@ -30,6 +29,7 @@ import numpy as np import os import pandas as pd +from copy import copy from matplotlib.colors import LogNorm from matplotlib.ticker import FixedLocator from nested_dict import nested_dict @@ -101,38 +101,35 @@ class Analysis: rec_dir = os.path.join(self.simulation.data_dir, 'recordings') for data_type in data_list: if data_type == 'spikes': - h5_file = os.path.join(rec_dir, '_'.join((self.simulation.label, - 'spike_data.h5'))) - d = 'spike_dict' columns = ['senders', 'times'] + d = 'spike_dict' elif data_type == 'vm': assert(self.simulation.params['recording_dict']['record_vm']), "Trying to " "load membrane potentials, but these data have not been recorded" - h5_file = os.path.join(rec_dir, '_'.join((self.simulation.label, - 'vm_data.h5'))) d = 'vm_dict' columns = ['senders', 'times', 'V_m'] print('loading {}'.format(data_type)) data = {} - # Check if the data has been merged into one common file. - try: - lazy_load = h5w.load(h5_file, lazy=True) - except IOError: - lazy_load = {} - h5w.save(h5_file, {}) + # Check if the data has already been stored in binary file for area in self.areas_loaded: - if area in lazy_load: - data[area] = h5w.load(h5_file, path=area) - else: - area_dat = {} + data[area] = {} + fp = '-'.join((self.simulation.label, + self.simulation.params['recording_dict'][d]['label'], + area, + '*')) + files = glob.glob(os.path.join(rec_dir, fp)) + if len(files) == 0: + continue + for pop in self.network.structure[area]: fp = '-'.join((self.simulation.label, self.simulation.params['recording_dict'][d]['label'], area, - '*')) - files = glob.glob(os.path.join(rec_dir, fp)) - if len(files) == 0: - continue - for pop in self.network.structure[area]: + pop)) + fn = os.path.join(rec_dir, + '.'.join((fp, 'npy'))) + try: + data[area][pop] = np.load(fn) + except FileNotFoundError: fp = '-'.join((self.simulation.label, self.simulation.params['recording_dict'][d]['label'], area, @@ -145,10 +142,9 @@ class Analysis: names=columns, sep='\t', index_col=False), ignore_index=True) - area_dat[pop] = np.array(dat) - data[area] = area_dat + np.save(fn, np.array(dat)) + data[area][pop] = np.array(dat) # Store spike data to single hdf5 file - h5w.save(h5_file, data[area], path=area, write_mode='a') if data_type == 'spikes': self.spike_data = data elif data_type == 'vm': @@ -206,9 +202,13 @@ class Analysis: default_dict = {'areas': self.areas_loaded, 'pops': 'complete', 'compute_stat': False} params = ah._create_parameter_dict(default_dict, self.T, **keywords) + iterator = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) # Check if population rates have been stored with the same parameters - self.pop_rates = ah._check_stored_data(os.path.join(self.output_dir, 'pop_rates.json'), - params) + fp = os.path.join(self.output_dir, 'pop_rates.json') + self.pop_rates = ah._check_stored_data(fp, + copy(iterator), params) if self.pop_rates is None: print("Computing population rates") @@ -231,9 +231,7 @@ class Analysis: total_rates += rate[2] d[area]['total'] = (np.mean(total_rates), np.std(total_rates)) else: - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): + for area, pop in iterator: if pop in self.network.structure[area]: spikes = self.spike_data[area][pop][:, 1] indices = np.where(np.logical_and(spikes > params['t_min'], @@ -256,7 +254,7 @@ class Analysis: Calculate single neuron population rates and store them in member pop_rate_dists. If the distributions had previously been stored with the same parameters, they are loaded from file. - Relies on helper function pop_rate_distribution. + Uses helper function pop_rate_distribution. Parameters ---------- @@ -278,18 +276,20 @@ class Analysis: default_dict = {'areas': self.areas_loaded, 'pops': 'complete'} params = ah._create_parameter_dict( default_dict, self.T, **keywords) + iterator = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) # Check if population rates have been stored with the same parameters self.pop_rate_dists = ah._check_stored_data(os.path.join(self.output_dir, - 'pop_rate_dists.h5'), params) + 'pop_rate_dists'), + copy(iterator), params) if self.pop_rate_dists is None: print("Computing population dists") d = nested_dict() d['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): + for area, pop in iterator: if pop in self.network.structure[area]: res = list(ah.pop_rate_distribution(self.spike_data[area][pop], params['t_min'], @@ -303,7 +303,7 @@ class Analysis: def create_synchrony(self, **keywords): """ Calculate synchrony as the coefficient of variation of the population rate - and store in member synchrony. Relies on helper function synchrony. + and store in member synchrony. Uses helper function synchrony. If the synchrony has previously been stored with the same parameters, they are loaded from file. @@ -331,19 +331,19 @@ class Analysis: 'pops': 'complete', 'resolution': 1.0} params = ah._create_parameter_dict( default_dict, self.T, **keywords) - + iterator = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) # Check if synchrony values have been stored with the same parameters self.synchrony = ah._check_stored_data(os.path.join(self.output_dir, 'synchrony.json'), - params) + copy(iterator), params) if self.synchrony is None: print("Computing synchrony") d = nested_dict() d['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): - if pop in self.network.structure[area] > 0.: + for area, pop in iterator: + if pop in self.network.structure[area]: d[area][pop] = ah.synchrony(self.spike_data[area][pop], self.network.N[area][pop], params['t_min'], @@ -365,7 +365,7 @@ class Analysis: def create_rate_time_series(self, **keywords): """ Calculate time series of population- and area-averaged firing rates. - Relies on ah.pop_rate_time_series. + Uses ah.pop_rate_time_series. If the rates have previously been stored with the same parameters, they are loaded from file. @@ -401,10 +401,16 @@ class Analysis: default_dict, self.T, **keywords) # Check if firing rates have been stored with the same parameters - fn = os.path.join(self.output_dir, 'rate_time_series.h5') - self.rate_time_series = ah._check_stored_data(fn, params) - fn = os.path.join(self.output_dir, 'rate_time_series_pops.h5') - self.rate_time_series_pops = ah._check_stored_data(fn, params) + fp = os.path.join(self.output_dir, 'rate_time_series') + iterator_areas = ah.model_iter(mode='single', + areas=params['areas'], + pops=None) + iterator_pops = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) + self.rate_time_series = ah._check_stored_data(fp, copy(iterator_areas), params) + fp = os.path.join(self.output_dir, 'rate_time_series_pops') + self.rate_time_series_pops = ah._check_stored_data(fp, copy(iterator_pops), params) if self.rate_time_series is None: print('Computing rate time series') @@ -415,10 +421,7 @@ class Analysis: # population-averaged firing rates d_pops = nested_dict() d_pops['Parameters'] = params - - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): + for area, pop in iterator_pops: if pop in self.network.structure[area]: time_series = ah.pop_rate_time_series(self.spike_data[area][pop], self.network.N[area][pop], @@ -444,7 +447,7 @@ class Analysis: def create_synaptic_input(self, **keywords): """ Calculate synaptic input of populations and areas using the spike data. - Relies on function ah.pop_synaptic_input. + Uses function ah.pop_synaptic_input. If the synaptic inputs have previously been stored with the same parameters, they are loaded from file. @@ -479,10 +482,16 @@ class Analysis: default_dict, self.T, **keywords) # Check if synaptic inputs have been stored with the same parameters - fn = os.path.join(self.output_dir, 'synaptic_input.h5') - self.synaptic_input = ah._check_stored_data(fn, params) - fn = os.path.join(self.output_dir, 'synaptic_input_pops.h5') - self.synaptic_input_pops = ah._check_stored_data(fn, params) + iterator_areas = ah.model_iter(mode='single', + areas=params['areas'], + pops=None) + iterator_pops = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) + fp = os.path.join(self.output_dir, 'synaptic_input') + self.synaptic_input = ah._check_stored_data(fp, copy(iterator_areas), params) + fp = os.path.join(self.output_dir, 'synaptic_input_pops') + self.synaptic_input_pops = ah._check_stored_data(fp, copy(iterator_pops), params) if self.synaptic_input is None: print('Computing rate time series') @@ -491,32 +500,27 @@ class Analysis: d_pops = nested_dict() d_pops['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): + for area, pop in copy(iterator_pops): if pop in self.network.structure[area]: if 'I' in pop: - tau_syn = self.network.params['neuron_params']['single_neuron_dict']['tau_syn_in'] + tau_syn = self.network.params['neuron_params'][ + 'single_neuron_dict']['tau_syn_in'] else: - tau_syn = self.network.params['neuron_params']['single_neuron_dict']['tau_syn_ex'] -# if pop in self.rate_time_series_pops[area]: + tau_syn = self.network.params['neuron_params'][ + 'single_neuron_dict']['tau_syn_ex'] time_series = ah.synaptic_output(self.rate_time_series_pops[area][pop], tau_syn, params['t_min'], params['t_max'], resolution=params['resolution']) - # else: - # time_series = np.ones(params['t_max'] - params['t_min']) * np.nan d_pops[area][pop] = time_series self.synaptic_output_pops = d_pops.to_dict() d_pops = nested_dict() d_pops['Parameters'] = params d_pops['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): + for area, pop in iterator_pops: if pop in self.network.structure[area]: time_series = np.zeros( - (params['t_max'] - params['t_min']) / params['resolution']) + int((params['t_max'] - params['t_min']) / params['resolution'])) for source_area, source_pop in ah.model_iter(mode='single', areas=self.areas_loaded): if source_pop in self.network.structure[source_area]: @@ -530,7 +534,7 @@ class Analysis: d['Parameters'] = params for area in params['areas']: d[area] = np.zeros( - (params['t_max'] - params['t_min']) / params['resolution']) + int((params['t_max'] - params['t_min']) / params['resolution'])) for pop in self.network.structure[area]: d[area] += d_pops[area][pop] * self.network.N[area][pop] d[area] /= self.network.N[area]['total'] @@ -540,7 +544,7 @@ class Analysis: def create_pop_cv_isi(self, **keywords): """ Calculate population-averaged CV ISI values and store as member pop_cv_isi. - Relies on helper function cv_isi. + Uses helper function cv_isi. If the CV ISI have previously been stored with the same parameters, they are loaded from file. @@ -565,17 +569,19 @@ class Analysis: params = ah._create_parameter_dict( default_dict, self.T, **keywords) # Check if CV ISI have been stored with the same parameters - self.pop_cv_isi = ah._check_stored_data(os.path.join(self.output_dir, 'pop_cv_isi.json'), - params) + iterator = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) + fp = os.path.join(self.output_dir, 'pop_cv_isi.json') + self.pop_cv_isi = ah._check_stored_data(fp, + copy(iterator), params) if self.pop_cv_isi is None: print("Computing population CV ISI") d = nested_dict() d['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): - if pop in self.network.structure[area] > 0.: + for area, pop in iterator: + if pop in self.network.structure[area]: d[area][pop] = ah.pop_cv_isi(self.spike_data[area][pop], params['t_min'], params['t_max']) @@ -584,7 +590,7 @@ class Analysis: def create_pop_LvR(self, **keywords): """ Calculate poulation-averaged LvR (see Shinomoto et al. 2009) and - store as member pop_LvR. Relies on helper function LvR. + store as member pop_LvR. Uses helper function LvR. Parameters ---------- @@ -607,22 +613,24 @@ class Analysis: default_dict, self.T, **keywords) # Check if LvR have been stored with the same parameters - self.pop_LvR = ah._check_stored_data(os.path.join(self.output_dir, 'pop_LvR.json'), - params) + iterator = ah.model_iter(mode='single', + areas=params['areas'], + pops=params['pops']) + fp = os.path.join(self.output_dir, 'pop_LvR.json') + self.pop_LvR = ah._check_stored_data(fp, + copy(iterator), params) if self.pop_LvR is None: print("Computing population LvR") d = nested_dict() d['Parameters'] = params - for area, pop in ah.model_iter(mode='single', - areas=params['areas'], - pops=params['pops']): - if pop in self.network.structure[area] > 0.: + for area, pop in iterator: + if pop in self.network.structure[area]: if self.network.N[area][pop] > 0.: d[area][pop] = ah.pop_LvR(self.spike_data[area][pop], 2.0, params['t_min'], params['t_max'])[0] - self.pop_LvR = d.to_dict() + self.pop_LvR = d.to_dict() # ______________________________________________________________________________ # Function for plotting data @@ -839,11 +847,11 @@ class Analysis: if pop: plt.savefig(os.path.join(self.output_dir, '{}_power_spectrum_{}_{}.{}'.format(self.simulation.label, - area, pop, keywords['output']))) + area, pop, keywords['output']))) else: plt.savefig(os.path.join(self.output_dir, '{}_power_spectrum_{}.{}'.format(self.simulation.label, - area, keywords['output']))) + area, keywords['output']))) else: fig.show() @@ -928,19 +936,17 @@ class Analysis: """ members = inspect.getmembers(self) save_list_json = ['structure', 'pop_rates', 'synchrony', - 'func_conn', 'func_conn_pops', 'pop_cv_isi', + 'pop_cv_isi', 'pop_LvR', 'indegree_data', 'indegree_areas_data', - 'outdegree_data', 'outdegree_areas_data', - 'pop_LvR'] - save_list_hdf5 = ['activity_flux', 'activity_flux_pops', - 'pop_rate_dists', 'rate_time_series', - 'rate_time_series_pops', 'bold_signal', - 'synaptic_input', 'synaptic_input_pops'] + 'outdegree_data', 'outdegree_areas_data'] + save_list_npy = ['pop_rate_dists', 'rate_time_series', + 'rate_time_series_pops', 'bold_signal', + 'synaptic_input', 'synaptic_input_pops'] for i in range(0, len(members)): if members[i][0] in save_list_json: f = open(self.output_dir + members[i][0] + '.json', 'w') json.dump(members[i][1], f) f.close() - if members[i][0] in save_list_hdf5: - f = self.output_dir + members[i][0] + '.h5' - h5w.save(f, members[i][1], write_mode='w') + if members[i][0] in save_list_npy: + f = self.output_dir + members[i][0] + ah._save_dict_to_npy(f, members[i][1]) diff --git a/multiarea_model/analysis_helpers.py b/multiarea_model/analysis_helpers.py index fc7b9b2..f8f3eaa 100644 --- a/multiarea_model/analysis_helpers.py +++ b/multiarea_model/analysis_helpers.py @@ -38,13 +38,12 @@ Sacha van Albada """ -import copy +from copy import copy, deepcopy +from nested_dict import nested_dict import numpy as np -import h5py_wrapper.wrapper as h5w import json from itertools import product from scipy.signal import welch -################################# area_list = ['V1', 'V2', 'VP', 'V3', 'PIP', 'V3A', 'MT', 'V4t', 'V4', @@ -73,8 +72,9 @@ def model_iter(mode='single', areas=None, pops='complete', areas2=None, pops2='c Defaults to None, which correspons to taking all areas into account. pops, pops2 : string or list, optional If specified, loop only over these populations as target and source - populations. Defaults to 'complete', which correspons to taking all + populations. Defaults to 'complete', which corresponds to taking all areas into account. + If None, loop only over areas. Returns ------- @@ -83,6 +83,8 @@ def model_iter(mode='single', areas=None, pops='complete', areas2=None, pops2='c """ if mode == 'single': assert((areas2 is None) and (pops2 is 'complete')) + if pops is None or pops2 is None: + assert((pops is None) and (pops2 is None) or mode == 'single') if pops == 'complete': pops = pop_list if areas is None: @@ -92,9 +94,15 @@ def model_iter(mode='single', areas=None, pops='complete', areas2=None, pops2='c if areas2 is None: areas2 = area_list if mode == 'single': - return product(areas, pops) + if pops is None: + return product(areas) + else: + return product(areas, pops) elif mode == 'pairs': - return product(areas, pops, areas2, pops2) + if pops is None: + return product(areas, areas2) + else: + return product(areas, pops, areas2, pops2) def area_spike_train(spike_data): @@ -117,7 +125,7 @@ def area_spike_train(spike_data): data_array = np.array([]) for pop in spike_data: data_array = np.append(data_array, spike_data[pop]) - data_array = np.reshape(data_array, (len(data_array) * 0.5, 2)) + data_array = np.reshape(data_array, (-1, 2)) return data_array @@ -131,7 +139,7 @@ def centralize(data, time=False, units=False): """ assert(time is not False or units is not False) - res = copy.copy(data) + res = copy(data) if time is True: res = np.array([x - np.mean(x) for x in res]) if units is True: @@ -179,8 +187,10 @@ def sort_gdf_by_id(data, idmin=None, idmax=None): print('CT warning(sort_spiketrains_by_id): empty gdf data!') return None, None -# ______________________________________________________________________________ -# Helper functions for data loading + +""" +Helper functions for data loading +""" def _create_parameter_dict(default_dict, T, **keywords): @@ -210,7 +220,7 @@ def _create_parameter_dict(default_dict, T, **keywords): return d -def _check_stored_data(fn, param_dict): +def _check_stored_data(fp, fn_iter, param_dict): """ Check if a data member of the data class has already been computed with the same parameters. @@ -223,21 +233,23 @@ def _check_stored_data(fn, param_dict): Parameters of the calculation to compare with the parameters of the stored data. """ - if 'json' in fn: + if 'json' in fp: try: - f = open(fn) + f = open(fp) data = json.load(f) f.close() except IOError: return None - elif 'hdf5' in fn: + param_dict2 = data['Parameters'] + else: try: - data = h5w.load(fn) + data = _load_npy_to_dict(fp, fn_iter) except IOError: return None - param_dict2 = data['Parameters'] - param_dict_copy = copy.copy(param_dict) - param_dict2_copy = copy.copy(param_dict2) + with open('-'.join((fp, 'parameters')), 'r') as f: + param_dict2 = json.load(f) + param_dict_copy = copy(param_dict) + param_dict2_copy = copy(param_dict2) for k in param_dict: if isinstance(param_dict_copy[k], list) or isinstance(param_dict_copy[k], np.ndarray): param_dict_copy[k] = set(param_dict_copy[k]) @@ -251,8 +263,59 @@ def _check_stored_data(fn, param_dict): "with different parameters") return None -# ______________________________________________________________________________ -# Analysis functions + +def _save_dict_to_npy(fp, data): + """ + Save data dictionary to binary numpy files + by iteratively going through the dictionary. + + Parameters + ---------- + fp : str + File pattern to which the keys of the dictionary are attached. + data : dict + Dictionary containing the data + """ + for key, val in data.items(): + if key != 'Parameters': + fp_key = '-'.join((fp, key)) + if isinstance(val, dict): + _save_dict_to_npy(fp_key, val) + else: + np.save(fp_key, val) + else: + fp_key = '-'.join((fp, 'parameters')) + with open(fp_key, 'w') as f: + json.dump(val, f) + + +def _load_npy_to_dict(fp, fn_iter): + """ + Load data stored in the files defined by fp + and fn_iter to a dictionary. + + Parameters + ---------- + fp : str + Base file pattern of the npy files + fn_iter : iterable + Iterable defining all the suffixes that are + appended to fp to form the file names. + """ + data = nested_dict() + for it in fn_iter: + fp_it = (fp,) + it + fp_ = '{}.npy'.format('-'.join(fp_it)) + if len(it) == 1: + data[it[0]] = np.load(fp_) + else: + data[it[0]][it[1]] = np.load(fp_) + return data + + +""" +Analysis functions +""" def online_hist(fname, tmin, tmax, resolution=1.): @@ -383,8 +446,11 @@ def pop_rate_distribution(data_array, t_min, t_max, num_neur): data_array[:, 1] < t_max)) neurons = data_array[:, 0][indices] neurons = np.sort(neurons) - n = neurons[0] - rates = np.zeros(num_neur) + if len(neurons) > 0: + n = neurons[0] + else: # No spikes in [t_min, t_max] + n = None + rates = np.zeros(int(num_neur)) ii = 0 for i in range(neurons.size): if neurons[i] == n: @@ -474,8 +540,6 @@ def pop_rate_time_series(data_array, num_neur, t_min, t_max, return time_series -# Regularity measures - def pop_cv_isi(data_array, t_min, t_max): """ @@ -537,7 +601,7 @@ def ISI_SCC(data_array, t_min, t_max): ------- bins : numpy.ndarray ISI lags - valyes : numpy.ndarray + values : numpy.ndarray Serial correlation coefficient values """ indices = np.where(np.logical_and(data_array[:, 1] > t_min, @@ -546,9 +610,8 @@ def ISI_SCC(data_array, t_min, t_max): half = max(1000, 2 * (t_max - t_min)) / 2.0 if len(data_array) > 1 and len(indices) > 1: for i in np.unique(data_array[:, 0]): - print(i) intervals = np.diff(data_array[indices][ - mlab.find(data_array[indices, 0] == i), 1]) + np.where(data_array[indices, 0] == i), 1]) if intervals.size > 1: mean = np.mean(intervals) @@ -564,7 +627,6 @@ def ISI_SCC(data_array, t_min, t_max): return 0.0 - def pop_LvR(data_array, t_ref, t_min, t_max): """ Compute the LvR value of the given data_array. @@ -594,7 +656,7 @@ def pop_LvR(data_array, t_ref, t_min, t_max): LvR = np.array([]) for i in np.unique(data_array[:, 0]): intervals = np.diff(data_array[i_min:i_max][ - mlab.find(data_array[i_min:i_max, 0] == i), 1]) + np.where(data_array[i_min:i_max, 0] == i), 1]) if intervals.size > 1: val = np.sum((1. - 4 * intervals[0:-1] * intervals[1:] / (intervals[0:-1] + intervals[ 1:]) ** 2) * (1 + 4 * t_ref / (intervals[0:-1] + intervals[1:]))) @@ -604,7 +666,6 @@ def pop_LvR(data_array, t_ref, t_min, t_max): return np.mean(LvR), LvR -# Synchrony measures def synchrony(data_array, num_neur, t_min, t_max, resolution=1.0): """ Compute the synchrony of an array of spikes as the coefficient diff --git a/multiarea_model/default_params.py b/multiarea_model/default_params.py index ccca642..d76b505 100644 --- a/multiarea_model/default_params.py +++ b/multiarea_model/default_params.py @@ -129,7 +129,7 @@ connection_params = { # Poisson input with population-specific rates (het_poisson_stat) # or by time-varying current input (het_current_nonstat) # while still simulating all areas. In both cases, the data to replace - # the cortico-cortical input is loaded from `replace_cc_input_file`. + # the cortico-cortical input is loaded from `replace_cc_input_source`. 'replace_cc': False, # Whether to replace non-simulated areas by poisson sources @@ -137,11 +137,17 @@ connection_params = { # by specific rates ('het_poisson_stat') # or by time-varying specific current ('het_current_nonstat') # In the two latter cases, the data to replace the cortico-cortical - # input is loaded from `replace_cc_input_file` + # input is loaded from `replace_cc_input_source` 'replace_non_simulated_areas': None, - # File holding the input rates to replace cortico-cortical input - 'replace_cc_input_file': None, + # Source of the input rates to replace cortico-cortical input + # Either a json file (has to end on .json) holding a scalar values + # for each population or + # a base name such that files with names + # $(replace_cc_input_source)-area-population.npy + # (e.g. '$(replace_cc_input_source)-V1-23E.npy') + # contain the time series for each population. + 'replace_cc_input_source': None, # whether to redistribute CC synapse to meet literature value # of E-specificity diff --git a/multiarea_model/simulation.py b/multiarea_model/simulation.py index 6417ee2..d0b3d47 100644 --- a/multiarea_model/simulation.py +++ b/multiarea_model/simulation.py @@ -8,9 +8,10 @@ Schmidt et al. (2018). Classes ------- -simulation : Loads a parameter file that specifies simulation parameters for a +Simulation : Loads a parameter file that specifies simulation parameters for a simulation of the instance of the model. A simulation is identified by a unique hash label. + """ import h5py_wrapper as h5w @@ -22,6 +23,7 @@ import pprint import shutil import time +from .analysis_helpers import _load_npy_to_dict, model_iter from config import base_path, data_path from copy import copy from .default_params import nested_update, sim_params @@ -123,8 +125,16 @@ class Simulation: ''.join(('custom_Data_Model_', self.network.label, '.json'))), os.path.join('config_files', '_'.join((self.network.label, 'config')))] - if self.network.params['connection_params']['replace_cc_input_file'] is not None: - files.append(self.network.params['connection_params']['replace_cc_input_file']) + if self.network.params['connection_params']['replace_cc_input_source'] is not None: + fs = self.network.params['connection_params']['replace_cc_input_source'] + if '.json' in fs: + files.append(fs) + else: # Assume that the cc input is stored in one npy file per population + fn_iter = model_iter(mode='single', areas=self.network.area_list) + for it in fn_iter: + fp_it = (fs,) + it + fp_ = '{}.npy'.format('-'.join(fp_it)) + files.append(fp_) for f in files: shutil.copy2(os.path.join(base_path, f), self.data_dir) @@ -170,18 +180,19 @@ class Simulation: replace_cc = self.network.params['connection_params']['replace_cc'] replace_non_simulated_areas = self.network.params['connection_params'][ 'replace_non_simulated_areas'] - if self.network.params['connection_params']['replace_cc_input_file'] is None: - replace_cc_input_file = None + if self.network.params['connection_params']['replace_cc_input_source'] is None: + replace_cc_input_source = None else: - replace_cc_input_file = os.path.join(self.data_dir, - self.network.params['connection_params'][ - 'replace_cc_input_file']) + replace_cc_input_source = os.path.join(self.data_dir, + self.network.params['connection_params'][ + 'replace_cc_input_source']) if not replace_cc and set(self.areas_simulated) != set(self.network.area_list): if replace_non_simulated_areas == 'het_current_nonstat': - non_simulated_cc_input = h5w.load(replace_cc_input_file) + fn_iter = model_iter(mode='single', areas=self.network.area_list) + non_simulated_cc_input = _load_npy_to_dict(replace_cc_input_source, fn_iter) elif replace_non_simulated_areas == 'het_poisson_stat': - fn = self.network.params['connection_params']['replace_cc_input_file'] + fn = self.network.params['connection_params']['replace_cc_input_source'] with open(fn, 'r') as f: non_simulated_cc_input = json.load(f) elif replace_non_simulated_areas == 'hom_poisson_stat': @@ -196,9 +207,11 @@ class Simulation: " replace non-simulated areas.") if replace_cc == 'het_current_nonstat': - cc_input = h5w.load(replace_cc_input_file) + fn_iter = model_iter(mode='single', areas=self.network.area_list) + cc_input = _load_npy_to_dict(replace_cc_input_source, fn_iter) elif replace_cc == 'het_poisson_stat': - with open(self.network.params['connection_params']['replace_cc_input_file'], 'r') as f: + with open(self.network.params['connection_params'][ + 'replace_cc_input_source'], 'r') as f: cc_input = json.load(f) elif replace_cc == 'hom_poisson_stat': cc_input = {source_area_name: diff --git a/multiarea_model/theory.py b/multiarea_model/theory.py index b028421..e6ef7ac 100644 --- a/multiarea_model/theory.py +++ b/multiarea_model/theory.py @@ -10,16 +10,10 @@ Schmidt et al., 2017) to the network connectivity. Classes -------- - -theory : provides functionality to predict the stable fixed point of +Theory : provides functionality to predict the stable fixed point of the model, perform further analysis and execute the stabilization provedure. -Authors --------- -Maximilian Schmidt -Jannis Schuecker - """ import json @@ -197,7 +191,8 @@ class Theory(): mu_CC = np.array([]) sigma2_CC = np.array([]) if self.network.params['connection_params']['replace_cc'] == 'het_poisson_stat': - with open(self.network.params['connection_params']['replace_cc_input_file'], 'r') as f: + with open(self.network.params['connection_params'][ + 'replace_cc_input_source'], 'r') as f: rates = json.load(f) self.cc_input_rates = dict_to_vector(rates, self.network.area_list, diff --git a/tests/test_analysis.py b/tests/test_analysis.py new file mode 100644 index 0000000..fdb3f95 --- /dev/null +++ b/tests/test_analysis.py @@ -0,0 +1,33 @@ +import os +from multiarea_model import MultiAreaModel + +""" +Test analysis class: +Run a small simulation, load data, compute all measures +available in Analysis, save data and try load again. +""" + + +def test_analysis(): + base_dir = os.getcwd() + fn = os.path.join(base_dir, 'fullscale_rates.json') + network_params = {'connection_params': {'replace_non_simulated_areas': 'het_poisson_stat', + 'replace_cc_input_source': fn}, + 'N_scaling': 0.001, + 'K_scaling': 0.0001, + 'fullscale_rates': 'fullscale_rates.json'} + sim_params = {'t_sim': 500., + 'areas_simulated': ['V1', 'V2']} + M = MultiAreaModel(network_params, simulation=True, sim_spec=sim_params, analysis=True) + M.simulation.simulate() + M = MultiAreaModel(network_params, simulation=True, sim_spec=sim_params, analysis=True) + + M.analysis.create_pop_rates(t_min=100.) + M.analysis.create_pop_rate_dists(t_min=100.) + M.analysis.create_synchrony(t_min=100.) + M.analysis.create_rate_time_series(t_min=100.) + M.analysis.create_synaptic_input(t_min=100.) + M.analysis.create_pop_cv_isi(t_min=100.) + M.analysis.create_pop_LvR(t_min=100.) + + M.analysis.save() diff --git a/tests/test_replace_cc.py b/tests/test_replace_cc.py index 43be22a..2b2791d 100644 --- a/tests/test_replace_cc.py +++ b/tests/test_replace_cc.py @@ -1,4 +1,3 @@ -import h5py_wrapper.wrapper as h5w import json import numpy as np import os @@ -6,6 +5,7 @@ from multiarea_model import MultiAreaModel from multiarea_model.multiarea_helpers import vector_to_dict from multiarea_model.multiarea_helpers import create_mask from multiarea_model.default_params import complete_area_list, population_list +from multiarea_model.analysis_helpers import _save_dict_to_npy """ Test replacing cortico-cortical connections. @@ -23,7 +23,7 @@ def test_het_poisson_stat_mf(): json.dump(rates, f) network_params = {'connection_params': {'replace_cc': 'het_poisson_stat', - 'replace_cc_input_file': 'mf_rates.json'}} + 'replace_cc_input_source': 'mf_rates.json'}} theory_params = {} M = MultiAreaModel(network_params, theory=True, theory_spec=theory_params) p, r = M.theory.integrate_siegert() @@ -53,7 +53,7 @@ def test_het_poisson_stat_sim(): base_dir = os.getcwd() fn = os.path.join(base_dir, 'fullscale_rates.json') network_params = {'connection_params': {'replace_cc': 'het_poisson_stat', - 'replace_cc_input_file': fn}, + 'replace_cc_input_source': fn}, 'N_scaling': 0.001, 'K_scaling': 0.0001, 'fullscale_rates': 'fullscale_rates.json'} @@ -75,12 +75,12 @@ def test_hom_poisson_stat_sim(): def test_het_current_non_stat_sim(): curr = np.ones(10) * 10. het_current = {area: {pop: curr for pop in population_list} for area in complete_area_list} - h5w.save('het_current.h5', het_current, write_mode='w') + _save_dict_to_npy('het_current', het_current) base_dir = os.getcwd() - fn = os.path.join(base_dir, 'het_current.h5') + fs = os.path.join(base_dir, 'het_current') network_params = {'connection_params': {'replace_cc': 'het_current_nonstat', - 'replace_cc_input_file': fn}, + 'replace_cc_input_source': fs}, 'N_scaling': 0.001, 'K_scaling': 0.0001, 'fullscale_rates': 'fullscale_rates.json'} diff --git a/tests/test_simulate_V1.py b/tests/test_simulate_V1.py index 7754d6b..bf239cb 100644 --- a/tests/test_simulate_V1.py +++ b/tests/test_simulate_V1.py @@ -1,8 +1,8 @@ -import h5py_wrapper.wrapper as h5w import numpy as np import os from multiarea_model import MultiAreaModel from multiarea_model.default_params import complete_area_list, population_list +from multiarea_model.analysis_helpers import _save_dict_to_npy """ Test simulating only V1 @@ -13,7 +13,7 @@ def test_het_poisson_stat_sim(): base_dir = os.getcwd() fn = os.path.join(base_dir, 'fullscale_rates.json') network_params = {'connection_params': {'replace_non_simulated_areas': 'het_poisson_stat', - 'replace_cc_input_file': fn}, + 'replace_cc_input_source': fn}, 'N_scaling': 0.001, 'K_scaling': 0.0001, 'fullscale_rates': 'fullscale_rates.json'} @@ -38,12 +38,12 @@ def test_hom_poisson_stat_sim(): def test_het_current_non_stat_sim(): curr = np.ones(10) * 10. het_current = {area: {pop: curr for pop in population_list} for area in complete_area_list} - h5w.save('het_current.h5', het_current, write_mode='w') + _save_dict_to_npy('het_current', het_current) base_dir = os.getcwd() - fn = os.path.join(base_dir, 'het_current.h5') + fs = os.path.join(base_dir, 'het_current') network_params = {'connection_params': {'replace_non_simulated_areas': 'het_current_nonstat', - 'replace_cc_input_file': fn}, + 'replace_cc_input_source': fs}, 'N_scaling': 0.001, 'K_scaling': 0.0001, 'fullscale_rates': 'fullscale_rates.json'} -- GitLab