forked from INM-6/neural_network_meanfield
-
Notifications
You must be signed in to change notification settings - Fork 1
/
circuit.py
254 lines (219 loc) · 10.3 KB
/
circuit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
"""circuit.py: Main class providing functions to calculate the stationary
and dynamical properties of a given circuit.
Authors: Hannah Bos, Jannis Schuecker
"""
import numpy as np
from setup import Setup
from analytics import Analytics
class Circuit(object):
"""Provides functions to calculate the stationary and dynamical
properties of a given circuit.
Arguments:
label: string specifying circuit, options: 'microcircuit'
Keyword Arguments:
params: dictionary specifying parameter of the circuit, default
parameter given in params_circuit.py will be overwritten
analysis_type: string specifying level of analysis that is requested
default: 'dynamical'
options:
- None: only circuit and default analysis parameter
are set
- 'stationary': circuit and default analysis parameter
are set, mean and variance of input to each
populations as well as firing rates are calculated
- 'dynamical': circuit and default analysis parameter
are set, mean and variance of input to each
populations as well as firing rates are calculated,
variables for calculation of spectra are calculated
including the transfer function for all populations
fmin: minimal frequency in Hz, default: 0.1 Hz
fmax: maximal frequency in Hz, default: 150 Hz
df: frequency spacing in Hz, default: 1.0/(2*np.pi) Hz
to_file: boolean specifying whether firing rates and transfer
functions are written to file, default: True
from_file: boolean specifying whether firing rates and transfer
functions are read from file, default: True
if set to True and file is not found firing rates and
transfer function are calculated
"""
def __init__(self, label, params={}, **kwargs):
self.label = label
self.setup = Setup()
self.ana = Analytics()
if 'analysis_type' in kwargs:
self.analysis_type = kwargs['analysis_type']
else:
self.analysis_type = 'dynamical'
# set default analysis and circuit parameter
self._set_up_circuit(params, kwargs)
# set parameter derived from analysis and circuit parameter
new_vars = self.setup.get_params_for_analysis(self)
new_vars['label'] = self.label
self._set_class_variables(new_vars)
# set variables which require calculation in analytics class
self._calc_variables()
# updates variables of Circuit() and Analysis() classes, new variables
# are specified in the dictionary new_vars
def _set_class_variables(self, new_vars):
for key, value in new_vars.items():
setattr(self, key, value)
if 'params' in new_vars:
for key, value in new_vars['params'].items():
setattr(self, key, value)
self.ana.update_variables(new_vars)
# updates class variables of variables of Circuit() and Analysis()
# such that default analysis and circuit parameters are known
def _set_up_circuit(self, params, args):
# set default analysis parameter
new_vars = self.setup.get_default_params(args)
self._set_class_variables(new_vars)
# set circuit parameter
new_vars = self.setup.get_circuit_params(self, params)
self._set_class_variables(new_vars)
# quantities required for stationary analysis are calculated
def _set_up_for_stationary_analysis(self):
new_vars = self.setup.get_working_point(self)
self._set_class_variables(new_vars)
# quantities required for dynamical analysis are calculated
def _set_up_for_dynamical_analysis(self):
new_vars = self.setup.get_params_for_power_spectrum(self)
self._set_class_variables(new_vars)
# calculates quantities needed for analysis specified by analysis_type
def _calc_variables(self):
if self.analysis_type == 'dynamical':
self._set_up_for_stationary_analysis()
self._set_up_for_dynamical_analysis()
elif self.analysis_type == 'stationary':
self._set_up_for_stationary_analysis()
def alter_params(self, params):
"""Parameter specified in dictionary params are changed.
Changeable parameters are default analysis and circuit parameter,
as well as label and analysis_type.
Arguments:
params: dictionary, specifying new parameters
"""
self.params.update(params)
new_vars = self.setup.get_altered_circuit_params(self, self.label)
self._set_class_variables(new_vars)
new_vars = self.setup.get_params_for_analysis(self)
self._set_class_variables(new_vars)
self._calc_variables()
def create_power_spectra(self):
"""Returns frequencies and power spectra.
See: Eq. 9 in Bos et al. (2015)
Shape of output: (len(self.populations), len(self.omegas))
Output:
freqs: vector of frequencies in Hz
power: power spectra for all populations,
dimension len(self.populations) x len(freqs)
"""
power = np.asarray(map(self.ana.spec, self.ana.omegas))
return self.ana.omegas/(2.0*np.pi), np.transpose(power)
def create_power_spectra_approx(self):
"""Returns frequencies and power spectra approximated by
dominant eigenmode.
See: Eq. 15 in Bos et al. (2015)
Shape of output: (len(self.populations), len(self.omegas))
Output:
freqs: vector of frequencies in Hz
power: power spectra for all populations,
dimension len(self.populations) x len(freqs)
"""
power = np.asarray(map(self.ana.spec_approx, self.ana.omegas))
return self.ana.omegas/(2.0*np.pi), np.transpose(power)
def create_eigenvalue_spectra(self, matrix):
"""Returns frequencies and frequency dependence of eigenvalues of
matrix.
Arguments:
matrix: string specifying the matrix, options are the effective
connectivity matrix ('MH'), the propagator ('prop') and
the inverse of the propagator ('prop_inv')
Output:
freqs: vector of frequencies in Hz
eigs: spectra of all eigenvalues,
dimension len(self.populations) x len(freqs)
"""
eigs = [self.ana.eigs_evecs(matrix, w)[0] for w in self.ana.omegas]
eigs = np.transpose(np.asarray(eigs))
return self.ana.omegas/(2.0*np.pi), eigs
def create_eigenvector_spectra(self, matrix, label):
"""Returns frequencies and frequency dependence of
eigenvectors of matrix.
Arguments:
matrix: string specifying the matrix, options are the effective
connectivity matrix ('MH'), the propagator ('prop') and
the inverse of the propagator ('prop_inv')
label: string specifying whether spectra of left or right
eigenvectors are returned, options: 'left', 'right'
Output:
freqs: vector of frequencies in Hz
evecs: spectra of all eigenvectors,
dimension len(self.populations) x len(freqs) x len(self.populations)
"""
# one list entry for every eigenvector, evecs[i][j][k] is the
# ith eigenvectors at the jth frequency for the kth component
evecs = [np.zeros((len(self.ana.omegas), self.ana.dimension),
dtype=complex) for i in range(self.ana.dimension)]
for i, w in enumerate(self.ana.omegas):
eig, vr, vl = self.ana.eigs_evecs(matrix, w)
if label == 'right':
v = vr
elif label == 'left':
v = vl
for j in range(self.ana.dimension):
evecs[j][i] = v[j]
evecs = np.asarray([np.transpose(evecs[i]) for i in range(self.ana.dimension)])
return self.ana.omegas/(2.0*np.pi), evecs
def reduce_connectivity(self, M_red):
"""Connectivity (indegree matrix) is reduced, while the working
point is held constant.
Arguments:
M_red: matrix, with each element specifying how the corresponding
connection is altered, e.g the in-degree from population
j to population i is reduced by 30% with M_red[i][j]=0.7
"""
M_original = self.M_full[:]
if M_red.shape != M_original.shape:
raise RuntimeError('Dimension of mask matrix has to be the '
+ 'same as the original indegree matrix.')
self.M = M_original*M_red
self.ana.update_variables({'M': self.M})
def restore_full_connectivity(self):
'''Restore connectivity to full connectivity.'''
self.M = self.M_full
self.ana.update_variables({'M': self.M})
def get_effective_connectivity(self, freq):
"""Returns effective connectivity matrix.
Arguments:
freq: frequency in Hz
"""
return self.ana.create_MH(2*np.pi*freq)
def get_sensitivity_measure(self, freq, index=None):
"""Returns sensitivity measure.
see: Eq. 21 in Bos et al. (2015)
Arguments:
freq: frequency in Hz
Keyword arguments:
index: specifies index of eigenmode, default: None
if set to None the dominant eigenmode is assumed
"""
MH = self.get_effective_connectivity(freq)
e, U = np.linalg.eig(MH)
U_inv = np.linalg.inv(U)
if index is None:
# find eigenvalue closest to one
index = np.argmin(np.abs(e-1))
T = np.outer(U_inv[index],U[:,index])
T /= np.dot(U_inv[index],U[:,index])
T *= MH
return T
def get_transfer_function(self):
"""Returns dynamical transfer function depending on frequency.
Shape of output: (len(self.populations), len(self.omegas))
Output:
freqs: vector of frequencies in Hz
dyn_trans_func: power spectra for all populations,
dimension len(self.populations) x len(freqs)
"""
dyn_trans_func = np.asarray(map(self.ana.create_H, self.ana.omegas))
return self.ana.omegas/(2.0*np.pi), np.transpose(dyn_trans_func)