def _numpy_dfun(self, integration_variables, Rin): r""" Equations taken from [DPA_2013]_ , page 11242 .. math:: x_{ek} &= w_p\,J_N \, S_{ek} - J_iS_{ik} + W_eI_o + GJ_N \mathbf\Gamma(S_{ek}, S_{ej}, u_{kj}),\\ H(x_{ek}) &= \dfrac{a_ex_{ek}- b_e}{1 - \exp(-d_e(a_ex_{ek} -b_e))},\\ \dot{S}_{ek} &= -\dfrac{S_{ek}}{\tau_e} + (1 - S_{ek}) \, \gammaH(x_{ek}) \, x_{ik} &= J_N \, S_{ek} - S_{ik} + W_iI_o + \lambdaGJ_N \mathbf\Gamma(S_{ik}, S_{ej}, u_{kj}),\\ H(x_{ik}) &= \dfrac{a_ix_{ik} - b_i}{1 - \exp(-d_i(a_ix_{ik} -b_i))},\\ \dot{S}_{ik} &= -\dfrac{S_{ik}}{\tau_i} + \gamma_iH(x_{ik}) \, """ S = integration_variables[:2, :] # Synaptic gating dynamics R = integration_variables[2:4, :] # Rates # Synaptic gating dynamics dS_e = -(S[0] / self.tau_e) + (1 - S[0]) * R[0] * self.gamma_e dS_i = -(S[1] / self.tau_i) + R[1] * self.gamma_i # Rates # Low pass filtering, linear dynamics for variables updated from the spiking network # No dynamics in the case of TVB rates dR_e = numpy.where(self._Rin_e_mask, (-R[0] + Rin[0]) / self.tau_rin_e, 0.0) dR_i = numpy.where(self._Rin_i_mask, (-R[1] + Rin[1]) / self.tau_rin_i, 0.0) return numpy.array([dS_e, dS_i, dR_e, dR_i])
def update_state_variables_before_integration(self, state_variables, coupling, local_coupling=0.0, stimulus=0.0): if self.use_numba: state_variables = \ _numba_update_non_state_variables(state_variables.reshape(state_variables.shape[:-1]).T, coupling.reshape(coupling.shape[:-1]).T + local_coupling * state_variables[0], self.a, self.b, self.d, self.w, self.J_N, self.Rin, self.G, self.I_o) return state_variables.T[..., numpy.newaxis] # In this case, rates (H_e, H_i) are non-state variables, # i.e., they form part of state_variables but have no dynamics assigned on them # Most of the computations of this dfun aim at computing rates, including coupling considerations. # Therefore, we compute and update them only once a new state is computed, # and we consider them constant for any subsequent possible call to this function, # by any integration scheme S = state_variables[0, :] # synaptic gating dynamics Rint = state_variables[1, :] # Rates from Spiking Network, integrated Rin = state_variables[3, :] # Input rates from Spiking Network c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) # Currents I = self.w * self.J_N * S + self.I_o + coupling x = self.a * I - self.b # Rates # Only rates with _Rin <= 0 0 will be updated by TVB. # The rest, are updated from the Spiking Network R = numpy.where(self._Rin, Rint, x / (1 - numpy.exp(-self.d * x))) Rin = numpy.where( self._Rin, Rin, 0.0) # Reset to 0 the Rin for nodes not updated by Spiking Network # We now update the state_variable vector with the new rates and currents: state_variables[2, :] = R state_variables[3, :] = Rin state_variables[4, :] = I # Keep them here so that they are not recomputed in the dfun self._R = numpy.copy(R) self._Rin = numpy.copy(Rin) return state_variables
def update_non_state_variables(self, state_variables, coupling, local_coupling=0.0, use_numba=True): if use_numba: _numba_update_non_state_variables( state_variables.reshape(state_variables.shape[:-1]).T, coupling.reshape(coupling.shape[:-1]).T + local_coupling * state_variables[0], self.a_e, self.b_e, self.d_e, self.w_p, self.W_e, self.J_N, self.R_e, self.a_i, self.b_i, self.d_i, self.W_i, self.J_i, self.R_i, self.G, self.lamda, self.I_o) return state_variables # In this case, rates (H_e, H_i) are non-state variables, # i.e., they form part of state_variables but have no dynamics assigned on them # Most of the computations of this dfun aim at computing rates, including coupling considerations. # Therefore, we compute and update them only once a new state is computed, # and we consider them constant for any subsequent possible call to this function, # by any integration scheme S = state_variables[:2, :] # synaptic gating dynamics R = state_variables[2:, :] # rates c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) J_N_S_e = self.J_N * S[0] # TODO: Confirm that this computation is correct for this model depending on the r_e and r_i values! x_e = self.w_p * J_N_S_e - self.J_i * S[ 1] + self.W_e * self.I_o + coupling x_e = self.a_e * x_e - self.b_e # Only rates with r_e < 0 will be updated by TVB. H_e = numpy.where(self.R_e >= 0, R[0], x_e / (1 - numpy.exp(-self.d_e * x_e))) x_i = J_N_S_e - S[1] + self.W_i * self.I_o + self.lamda * coupling x_i = self.a_i * x_i - self.b_i # Only rates with r_i < 0 will be updated by TVB. H_i = numpy.where(self.R_i >= 0, R[1], x_i / (1 - numpy.exp(-self.d_i * x_i))) # We now update the state_variable vector with the new rates: state_variables[2, :] = H_e state_variables[3, :] = H_i return state_variables
def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0): r""" Equations taken from [DPA_2013]_ , page 11242 .. math:: x_{ek} &= w_p\,J_N \, S_{ek} - J_iS_{ik} + W_eI_o + GJ_N \mathbf\Gamma(S_{ek}, S_{ej}, u_{kj}),\\ H(x_{ek}) &= \dfrac{a_ex_{ek}- b_e}{1 - \exp(-d_e(a_ex_{ek} -b_e))},\\ \dot{S}_{ek} &= -\dfrac{S_{ek}}{\tau_e} + (1 - S_{ek}) \, \gammaH(x_{ek}) \, x_{ik} &= J_N \, S_{ek} - S_{ik} + W_iI_o + \lambdaGJ_N \mathbf\Gamma(S_{ik}, S_{ej}, u_{kj}),\\ H(x_{ik}) &= \dfrac{a_ix_{ik} - b_i}{1 - \exp(-d_i(a_ix_{ik} -b_i))},\\ \dot{S}_{ik} &= -\dfrac{S_{ik}}{\tau_i} + \gamma_iH(x_{ik}) \, """ S = state_variables[:, :] S[S < 0] = 0.0 S[S > 1] = 1.0 c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) J_N_S_e = self.J_N * S[0] # TODO: Confirm that this combutation is correct for this model depending on the r_e and r_i values! x_e = self.w_p * J_N_S_e - self.J_i * S[ 1] + self.W_e * self.I_o + coupling x_e = self.a_e * x_e - self.b_e H_e = numpy.where(self.r_e >= 0, self.r_e, x_e / (1 - numpy.exp(-self.d_e * x_e))) dS_e = -(S[0] / self.tau_e) + (1 - S[0]) * H_e * self.gamma_e x_i = J_N_S_e - S[1] + self.W_i * self.I_o + self.lamda * coupling x_i = self.a_i * x_i - self.b_i H_i = numpy.where(self.r_i >= 0, self.r_i, x_i / (1 - numpy.exp(-self.d_i * x_i))) dS_i = -(S[1] / self.tau_i) + H_i * self.gamma_i derivative = numpy.array([dS_e, dS_i]) return derivative
def dfun(self, vw, c, local_coupling=0.0): vw[0, :] = numpy.where(self.V_m == 0, vw[0, :].squeeze(), self.V_m)[:, numpy.newaxis] lc_0 = local_coupling * vw[0, :, 0] vw_ = vw.reshape(vw.shape[:-1]).T c_ = c.reshape(c.shape[:-1]).T deriv = _numba_dfun_g2d(vw_, c_, self.tau, self.I, self.a, self.b, self.c, self.d, self.e, self.f, self.g, self.beta, self.alpha, self.gamma, lc_0) return deriv.T[..., numpy.newaxis]
def _compute_region_inh_population_coupling(self, s_I, w_IE, w_II): # Scale w_IE, w_II per region accordingly, # to implement coupling schemes that depend on the total number of neurons # inh -> inh c_ii = w_II * s_I c_ii = numpy.sum(c_ii) \ - numpy.where(self._auto_connection, 0.0, c_ii) # optionally remove auto-connections # inh -> exc: return numpy.sum(w_IE * s_I), c_ii
def _compute_region_exc_population_coupling(self, s_E, w_EE, w_EI): # Scale w_EE, w_EI per region accordingly, # to implement coupling schemes that depend on the total number of neurons # exc -> exc c_ee = w_EE * s_E c_ee = numpy.sum(c_ee) \ - numpy.where(self._auto_connection, 0.0, c_ee) # optionally remove auto-connections # exc -> inh: return c_ee, numpy.sum(w_EI * s_E)
def dfun(self, state_variables, coupling, local_coupling=0.00): r""" .. math:: T \dot{\nu_\mu} &= -F_\mu(\nu_e,\nu_i) + \nu_\mu ,\all\mu\in\{e,i\}\\ dot{W}_k &= W_k/tau_w-b*E_k \\ """ E = state_variables[0, :] I = state_variables[1, :] W_e = state_variables[2, :] W_i = state_variables[3, :] noise = state_variables[4, :] derivative = numpy.empty_like(state_variables) # long-range coupling c_0 = coupling[0, :] # short-range (local) coupling lc_E = local_coupling * E lc_I = local_coupling * I # external firing rate Fe_ext = c_0 + lc_E + self.weight_noise * noise index_bad_input = numpy.where(Fe_ext * self.K_ext_e < 0) Fe_ext[index_bad_input] = 0.0 Fi_ext = lc_I # Excitatory firing rate derivation derivative[0] = (self.TF_excitatory( E, I, Fe_ext + self.external_input_ex_ex, Fi_ext + self.external_input_ex_in, W_e) - E) / self.T # Inhibitory firing rate derivation derivative[1] = (self.TF_inhibitory( E, I, Fe_ext + self.external_input_in_ex, Fi_ext + self.external_input_in_in, W_i) - I) / self.T # Adaptation excitatory mu_V, sigma_V, T_V = self.get_fluct_regime_vars( E, I, Fe_ext + self.external_input_ex_ex, Fi_ext + self.external_input_ex_in, W_e, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, self.E_L_e, self.N_tot, self.p_connect_e, self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i) derivative[2] = -W_e / self.tau_w_e + self.b_e * E + self.a_e * ( mu_V - self.E_L_e) / self.tau_w_e # Adaptation inhibitory mu_V, sigma_V, T_V = self.get_fluct_regime_vars( E, I, Fe_ext + self.external_input_in_ex, Fi_ext + self.external_input_in_in, W_i, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, self.E_L_i, self.N_tot, self.p_connect_e, self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i) derivative[3] = -W_i / self.tau_w_i + self.b_i * I + self.a_i * ( mu_V - self.E_L_i) / self.tau_w_i derivative[4] = -noise / self.tau_OU return derivative
def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0, ev=numexpr.evaluate): r""" The two state variables :math:`V` and :math:`W` are typically considered to represent a function of the neuron's membrane potential, such as the firing rate or dendritic currents, and a recovery variable, respectively. If there is a time scale hierarchy, then typically :math:`V` is faster than :math:`W` corresponding to a value of :math:`\tau` greater than 1. The equations of the generic 2D population model read .. math:: \dot{V} &= d \, \tau (-f V^3 + e V^2 + g V + \alpha W + \gamma I), \\ \dot{W} &= \dfrac{d}{\tau}\,\,(c V^2 + b V - \beta W + a), where external currents :math:`I` provide the entry point for local, long-range connectivity and stimulation. """ V = state_variables[0, :] V = numpy.where(self.V_m == 0, V, self.V_m) W = state_variables[1, :] #[State_variables, nodes] c_0 = coupling[0, :] tau = self.tau I = self.I a = self.a b = self.b c = self.c d = self.d e = self.e f = self.f g = self.g beta = self.beta alpha = self.alpha gamma = self.gamma lc_0 = local_coupling * V # Pre-allocate the result array then instruct numexpr to use it as output. # This avoids an expensive array concatenation derivative = numpy.empty_like(state_variables) ev('d * tau * (alpha * W - f * V**3 + e * V**2 + g * V + gamma * I + gamma *c_0 + lc_0)', out=derivative[0]) ev('d * (a + b * V + c * V**2 - beta * W) / tau', out=derivative[1]) return derivative
def _numpy_dfun(self, integration_variables, R, Rin): r""" Equations taken from [DPA_2013]_ , page 11242 .. math:: x_k &= w\,J_N \, S_k + I_o + J_N \mathbf\Gamma(S_k, S_j, u_{kj}),\\ H(x_k) &= \dfrac{ax_k - b}{1 - \exp(-d(ax_k -b))},\\ \dot{S}_k &= -\dfrac{S_k}{\tau_s} + (1 - S_k) \, H(x_k) \, \gamma """ S = integration_variables[0, :] # Synaptic gating dynamics Rint = integration_variables[ 1, :] # Rates from Spiking Network, integrated # Synaptic gating dynamics dS = -(S / self.tau_s) + (1 - S) * R * self.gamma # Rates # Low pass filtering, linear dynamics for rates updated from the spiking network # No dynamics in the case of TVB rates dRint = numpy.where(self._Rin_mask, (-Rint + Rin) / self.tau_rin, 0.0) return numpy.array([dS, dRint])
def update_state_variables_before_integration(self, state_variables, coupling, local_coupling=0.0, stimulus=0.0, use_numba=False): for ii in range(state_variables[0].shape[0]): _E = self._E(ii) # excitatory neurons'/modes' indices _I = self._I(ii) # inhibitory neurons'/modes' indices # Make sure that all empty positions are set to 0.0, if any: self._zero_empty_positions(state_variables, _E, _I, ii) # Set inhibitory synapses for excitatory neurons & excitatory synapses for inhibitory neurons to 0.0... self._zero_cross_synapses(state_variables, _E, _I, ii) # compute large scale coupling_ij = sum(C_ij * S_e(t-t_ij)) large_scale_coupling = numpy.sum(coupling[0, ii, :self._N_E_max]) if ii in self._spiking_regions_inds: # -----------------------------------Updates after previous iteration:---------------------------------- # Refractory neurons from past spikes if 6. t_ref > 0.0 self._refractory_neurons_E[ii] = state_variables[6, ii, _E] > 0.0 self._refractory_neurons_I[ii] = state_variables[6, ii, _I] > 0.0 # set 5. V_m for refractory neurons to V_reset state_variables[5, ii, _E] = numpy.where( self._refractory_neurons_E[ii], self._x_E(self.V_reset, ii), state_variables[5, ii, _E]) state_variables[5, ii, _I] = numpy.where( self._refractory_neurons_I[ii], self._x_I(self.V_reset, ii), state_variables[5, ii, _I]) # Compute spikes sent at time t: # 8. spikes state_variables[8, ii, _E] = numpy.where( state_variables[5, ii, _E] > self._x_E(self.V_thr, ii), 1.0, 0.0) state_variables[8, ii, _I] = numpy.where( state_variables[5, ii, _I] > self._x_I(self.V_thr, ii), 1.0, 0.0) self._spikes_E[ii] = state_variables[8, ii, _E] > 0.0 self._spikes_I[ii] = state_variables[8, ii, _I] > 0.0 # set 5. V_m for spiking neurons to V_reset state_variables[5, ii, _E] = numpy.where(self._spikes_E[ii], self._x_E(self.V_reset, ii), state_variables[5, ii, _E]) state_variables[5, ii, _I] = numpy.where(self._spikes_I[ii], self._x_I(self.V_reset, ii), state_variables[5, ii, _I]) # set 6. t_ref to tau_ref for spiking neurons state_variables[6, ii, _E] = numpy.where( self._spikes_E[ii], self._x_E(self.tau_ref_E, ii), state_variables[6, ii, _E]) state_variables[6, ii, _I] = numpy.where( self._spikes_I[ii], self._x_I(self.tau_ref_I, ii), state_variables[6, ii, _I]) # Refractory neurons including current spikes sent at time t self._refractory_neurons_E[ii] = numpy.logical_or( self._refractory_neurons_E[ii], self._spikes_E[ii]) self._refractory_neurons_I[ii] = numpy.logical_or( self._refractory_neurons_I[ii], self._spikes_I[ii]) # 9. rate # Compute the average population rate sum_of_population_spikes / number_of_population_neurons # separately for excitatory and inhibitory populations, # and set it at the first position of each population, similarly to the mean-field region nodes state_variables[9, ii, _E] = 0.0 state_variables[9, ii, _I] = 0.0 state_variables[9, ii, 0] = numpy.sum( state_variables[8, ii, _E]) / self._n_E(ii) state_variables[9, ii, self._N_E_max] = numpy.sum( state_variables[8, ii, _I]) / self._n_I(ii) # -------------------------------------Updates before next iteration:--------------------------------------- # ----------------------------------First deal with inputs at time t:--------------------------------------- # Collect external spikes received at time t, and update the incoming s_AMPA_ext synapse: # 7. spikes_ext # get external spike stimulus 7. spike_ext, if any: state_variables[7, ii, _E] = self._x_E(self.spikes_ext, ii) state_variables[7, ii, _I] = self._x_I(self.spikes_ext, ii) # 4. s_AMPA_ext # ds_AMPA_ext/dt = -1/tau_AMPA * (s_AMPA_exc + spikes_ext) # Add the spike at this point to s_AMPA_ext state_variables[4, ii, _E] += state_variables[7, ii, _E] # spikes_ext state_variables[4, ii, _I] += state_variables[7, ii, _I] # spikes_ext # Compute currents based on synaptic gating variables at time t: # V_E_E = V_m - V_E V_E_E = state_variables[5, ii, _E] - self._x_E(self.V_E, ii) V_E_I = state_variables[5, ii, _I] - self._x_I(self.V_E, ii) # 10. I_syn = I_L + I_AMPA + I_NMDA + I_GABA + I_AMPA_EXT state_variables[10, ii, _E] = numpy.sum(state_variables[10:, ii, _E], axis=0) state_variables[10, ii, _I] = numpy.sum(state_variables[10:, ii, _I], axis=0) # 11. I_L = g_m * (V_m - V_E) state_variables[11, ii, _E] = \ self._x_E(self.g_m_E, ii) * (state_variables[5, ii, _E] - self._x_E(self.V_L, ii)) state_variables[11, ii, _I] = \ self._x_I(self.g_m_I, ii) * (state_variables[5, ii, _I] - self._x_I(self.V_L, ii)) w_EE = self._x_E(self.w_EE, ii) w_EI = self._x_E(self.w_EI, ii) # 12. I_AMPA = g_AMPA * (V_m - V_E) * sum(w * s_AMPA_k) coupling_AMPA_E, coupling_AMPA_I = \ self._compute_region_exc_population_coupling(state_variables[0, ii, _E], w_EE, w_EI) state_variables[12, ii, _E] = self._x_E( self.g_AMPA_E, ii) * V_E_E * coupling_AMPA_E # s_AMPA state_variables[12, ii, _I] = self._x_I( self.g_AMPA_I, ii) * V_E_I * coupling_AMPA_I # 13. I_NMDA = g_NMDA * (V_m - V_E) / (1 + lamda_NMDA * exp(-beta*V_m)) * sum(w * s_NMDA_k) coupling_NMDA_E, coupling_NMDA_I = \ self._compute_region_exc_population_coupling(state_variables[2, ii, _E], w_EE, w_EI) state_variables[13, ii, _E] = \ self._x_E(self.g_NMDA_E, ii) * V_E_E \ / (self._x_E(self.lamda_NMDA, ii) * numpy.exp(-self._x_E(self.beta, ii) * state_variables[5, ii, _E])) \ * coupling_NMDA_E # s_NMDA state_variables[13, ii, _I] = \ self._x_I(self.g_NMDA_I, ii) * V_E_I \ / (self._x_I(self.lamda_NMDA, ii) * numpy.exp(-self._x_I(self.beta, ii) * state_variables[5, ii, _I])) \ * coupling_NMDA_I # s_NMDA # 14. I_GABA = g_GABA * (V_m - V_I) * sum(w_ij * s_GABA_k) w_IE = self._x_I(self.w_IE, ii) w_II = self._x_I(self.w_II, ii) coupling_GABA_E, coupling_GABA_I = \ self._compute_region_inh_population_coupling(state_variables[3, ii, _I], w_IE, w_II) state_variables[14, ii, _E] = self._x_E(self.g_GABA_E, ii) * \ (state_variables[5, ii, _E] - self._x_E(self.V_I, ii)) * \ coupling_GABA_E # s_GABA state_variables[14, ii, _I] = self._x_I(self.g_GABA_I, ii) * \ (state_variables[5, ii, _I] - self._x_I(self.V_I, ii)) * \ coupling_GABA_I # s_GABA # 15. I_AMPA_ext = g_AMPA_ext * (V_m - V_E) * ( G*sum{c_ij sum{s_AMPA_j(t-delay_ij)}} + s_AMPA_ext) # Compute large scale coupling_ij = sum(c_ij * S_e(t-t_ij)) large_scale_coupling += numpy.sum(local_coupling * state_variables[0, ii, _E]) state_variables[15, ii, _E] = self._x_E(self.g_AMPA_ext_E, ii) * V_E_E * \ (self._x_E(self.G, ii) * large_scale_coupling + state_variables[4, ii, _E]) # # feedforward inhibition state_variables[15, ii, _I] = self._x_I(self.g_AMPA_ext_I, ii) * V_E_I * \ (self._x_I(self.G, ii) * self._x_I(self.lamda, ii) * large_scale_coupling + state_variables[4, ii, _I]) else: # For mean field modes: # Given that the 3rd dimension corresponds to neurons, not modes, # we use only the first element of its population, i.e., 0 and _I[0], # and consider all the rest to be identical # Similarly, we assume that all parameters are of of these shapes: # (1, ), (1, 1), (number_of_regions, ), (number_of_regions, 1) # S_e = s_AMPA = s_NMDA # S_i = s_GABA # 1. s_NMDA # = s_AMPA for excitatory mean field models state_variables[1, ii, _E] = state_variables[0, ii, 0] # 1. x_NMDA, 4. s_AMPA_ext, 5. V_m, 6. t_ref, 7. spikes_ext, 8. spikes, 11. I_L # are 0 for mean field models: state_variables[[1, 4, 5, 6, 7, 8, 11], ii] = 0.0 # J_N is indexed by the receiver node J_N_E = self._region(self.J_N, ii) J_N_I = self._region(self.J_N, ii) # 12. I_AMPA # = w+ * J_N * S_e state_variables[12, ii, _E] = self._region( self.w_p, ii) * J_N_E * state_variables[0, ii, 0] # = J_N * S_e state_variables[12, ii, _I] = J_N_I * state_variables[0, ii, 0] # 13. I_NMDA = I_AMPA for mean field models state_variables[13, ii] = state_variables[13, ii] # 14. I_GABA # 3. s_GABA state_variables[14, ii, _E] = -self._x_E( self.J_i, ii) * state_variables[3, ii, _I[0]] # = -J_i*S_i state_variables[14, ii, _I] = -state_variables[3, ii, _I[0]] # = - S_i # 15. I_AMPA_ext large_scale_coupling += local_coupling * state_variables[0, ii, 0] # = G * J_N * coupling_ij = G * J_N * sum(C_ij * S_e(t-t_ij)) state_variables[15, ii, _E] = self._x_E( self.G, ii)[0] * J_N_E * large_scale_coupling # = lamda * G * J_N * coupling_ij = lamda * G * J_N * sum(C_ij * S_e(t-t_ij)) state_variables[15, ii, _I] = \ self._x_I(self.G, ii)[0] * self._x_I(self.lamda, ii)[0] * J_N_I * large_scale_coupling # 8. I_syn = I_E(NMDA) + I_I(GABA) + I_AMPA_ext # Note measuring twice I_AMPA and I_NMDA though, as they count as a single excitatory current: state_variables[10, ii, _E] = numpy.sum(state_variables[13:, ii, 0], axis=0) state_variables[10, ii, _I] = numpy.sum(state_variables[13:, ii, _I[0]], axis=0) # 6. rate sigmoidal of total current = I_syn + I_o total_current = \ state_variables[10, ii, 0] + self._region(self.W_e, ii) * self._region(self.I_o, ii) # Sigmoidal activation: (a*I_tot_current - b) / ( 1 - exp(-d*(a*I_tot_current - b))) total_current = self._region( self.a_e, ii) * total_current - self._region(self.b_e, ii) state_variables[9, ii, _E] = \ total_current / (1 - numpy.exp(-self._region(self.d_e, ii) * total_current)) total_current = \ state_variables[10, ii, _I[0]] + self._region(self.W_i, ii) * self._region(self.I_o, ii) # Sigmoidal activation: total_current = self._region( self.a_i, ii) * total_current - self._region(self.b_i, ii) state_variables[9, ii, _I] = \ total_current / (1 - numpy.exp(-self._region(self.d_i, ii) * total_current)) return state_variables
def update_state_variables_before_integration(self, state_variables, coupling, local_coupling=0.0, stimulus=0.0): if self._n_regions == 0: self._n_regions = state_variables.shape[1] self._prepare_indices() for ii in range(self._n_regions): # For every region node.... _E = self._E(ii) # excitatory neurons' indices _I = self._I(ii) # inhibitory neurons' indices # Make sure that all empty positions are set to 0.0, if any: self._zero_empty_positions(state_variables, _E, _I, ii) # Set inhibitory synapses for excitatory neurons & excitatory synapses for inhibitory neurons to 0.0... self._zero_cross_synapses(state_variables, _E, _I, ii) # -----------------------------------Updates after previous iteration:-------------------------------------- # Refractory neurons from past spikes if 6. t_ref > 0.0 self._refractory_neurons_E[ii] = state_variables[6, ii, _E] > 0.0 self._refractory_neurons_I[ii] = state_variables[6, ii, _I] > 0.0 # set 5. V_m for refractory neurons to V_reset state_variables[5, ii, _E] = numpy.where(self._refractory_neurons_E[ii], self._x_E(self.V_reset, ii), state_variables[5, ii, _E]) state_variables[5, ii, _I] = numpy.where(self._refractory_neurons_I[ii], self._x_I(self.V_reset, ii), state_variables[5, ii, _I]) # Compute spikes sent at time t: # 8. spikes state_variables[8, ii, _E] = numpy.where( state_variables[5, ii, _E] > self._x_E(self.V_thr, ii), 1.0, 0.0) state_variables[8, ii, _I] = numpy.where( state_variables[5, ii, _I] > self._x_I(self.V_thr, ii), 1.0, 0.0) exc_spikes = state_variables[8, ii, _E] # excitatory spikes inh_spikes = state_variables[8, ii, _I] # inhibitory spikes self._spikes_E[ii] = exc_spikes > 0.0 self._spikes_I[ii] = inh_spikes > 0.0 # set 5. V_m for spiking neurons to V_reset state_variables[5, ii, _E] = numpy.where(self._spikes_E[ii], self._x_E(self.V_reset, ii), state_variables[5, ii, _E]) state_variables[5, ii, _I] = numpy.where(self._spikes_I[ii], self._x_I(self.V_reset, ii), state_variables[5, ii, _I]) # set 6. t_ref to tau_ref for spiking neurons state_variables[6, ii, _E] = numpy.where(self._spikes_E[ii], self._x_E(self.tau_ref_E, ii), state_variables[6, ii, _E]) state_variables[6, ii, _I] = numpy.where(self._spikes_I[ii], self._x_I(self.tau_ref_I, ii), state_variables[6, ii, _I]) # Refractory neurons including current spikes sent at time t self._refractory_neurons_E[ii] = numpy.logical_or( self._refractory_neurons_E[ii], self._spikes_E[ii]) self._refractory_neurons_I[ii] = numpy.logical_or( self._refractory_neurons_I[ii], self._spikes_I[ii]) # -------------------------------------Updates before next iteration:--------------------------------------- # ----------------------------------First deal with inputs at time t:--------------------------------------- # Collect external spikes received at time t, and update the incoming s_AMPA_ext synapse: # 7. spikes_ext # get external spike stimulus 7. spike_ext, if any: state_variables[7, ii, _E] = self._x_E(self.spikes_ext, ii) state_variables[7, ii, _I] = self._x_I(self.spikes_ext, ii) # 4. s_AMPA_ext # ds_AMPA_ext/dt = -1/tau_AMPA * (s_AMPA_exc + spikes_ext) # Add the spike at this point to s_AMPA_ext state_variables[4, ii, _E] += state_variables[7, ii, _E] # spikes_ext state_variables[4, ii, _I] += state_variables[7, ii, _I] # spikes_ext # Compute currents based on synaptic gating variables at time t: # V_E_E = V_m - V_E V_E_E = state_variables[5, ii, _E] - self._x_E(self.V_E, ii) V_E_I = state_variables[5, ii, _I] - self._x_I(self.V_E, ii) # 9. I_L = g_m * (V_m - V_E) state_variables[9, ii, _E] = \ self._x_E(self.g_m_E, ii) * (state_variables[5, ii, _E] - self._x_E(self.V_L, ii)) state_variables[9, ii, _I] = \ self._x_I(self.g_m_I, ii) * (state_variables[5, ii, _I] - self._x_I(self.V_L, ii)) w_EE = self._x_E(self.w_EE, ii) w_EI = self._x_E(self.w_EI, ii) # 10. I_AMPA = g_AMPA * (V_m - V_E) * sum(w * s_AMPA_k) coupling_AMPA_E, coupling_AMPA_I = \ self._compute_region_exc_population_coupling(state_variables[0, ii, _E], w_EE, w_EI) state_variables[10, ii, _E] = self._x_E( self.g_AMPA_E, ii) * V_E_E * coupling_AMPA_E # s_AMPA state_variables[10, ii, _I] = self._x_I( self.g_AMPA_I, ii) * V_E_I * coupling_AMPA_I # 11. I_NMDA = g_NMDA * (V_m - V_E) / (1 + lamda_NMDA * exp(-beta*V_m)) * sum(w * s_NMDA_k) coupling_NMDA_E, coupling_NMDA_I = \ self._compute_region_exc_population_coupling(state_variables[2, ii, _E], w_EE, w_EI) state_variables[11, ii, _E] = \ self._x_E(self.g_NMDA_E, ii) * V_E_E \ / (self._x_E(self.lamda_NMDA, ii) * numpy.exp(-self._x_E(self.beta, ii) * state_variables[5, ii, _E])) \ * coupling_NMDA_E # s_NMDA state_variables[11, ii, _I] = \ self._x_I(self.g_NMDA_I, ii) * V_E_I \ / (self._x_I(self.lamda_NMDA, ii) * numpy.exp(-self._x_I(self.beta, ii) * state_variables[5, ii, _I])) \ * coupling_NMDA_I # s_NMDA # 0. s_AMPA # s_AMPA += exc_spikes state_variables[0, ii, _E] += exc_spikes # 1. x_NMDA # x_NMDA += exc_spikes state_variables[1, ii, _E] += exc_spikes # 12. I_GABA = g_GABA * (V_m - V_I) * sum(w_ij * s_GABA_k) w_IE = self._x_I(self.w_IE, ii) w_II = self._x_I(self.w_II, ii) coupling_GABA_E, coupling_GABA_I = \ self._compute_region_inh_population_coupling(state_variables[3, ii, _I], w_IE, w_II) state_variables[12, ii, _E] = self._x_E(self.g_GABA_E, ii) * \ (state_variables[5, ii, _E] - self._x_E(self.V_I, ii)) * \ coupling_GABA_E # s_GABA state_variables[12, ii, _I] = self._x_I(self.g_GABA_I, ii) * \ (state_variables[5, ii, _I] - self._x_I(self.V_I, ii)) * \ coupling_GABA_I # s_GABA # 3. s_GABA += inh_spikes state_variables[3, ii, _I] += inh_spikes # 13. I_AMPA_ext = g_AMPA_ext * (V_m - V_E) * ( G*sum{c_ij sum{s_AMPA_j(t-delay_ij)}} + s_AMPA_ext) # Compute large scale coupling_ij = sum(c_ij * S_e(t-t_ij)) large_scale_coupling = numpy.sum(coupling[0, ii, :self._N_E_max]) large_scale_coupling += numpy.sum(local_coupling * state_variables[0, ii, _E]) state_variables[13, ii, _E] = self._x_E(self.g_AMPA_ext_E, ii) * V_E_E * \ ( self._x_E(self.G, ii) * large_scale_coupling + state_variables[4, ii, _E] ) # # feedforward inhibition state_variables[13, ii, _I] = self._x_I(self.g_AMPA_ext_I, ii) * V_E_I * \ ( self._x_I(self.G, ii) * self._x_I(self.lamda, ii) * large_scale_coupling + state_variables[4, ii, _I] ) self._non_integrated_variables = state_variables[ self._non_integrated_variables_inds] return state_variables
def dfun(self, state_variables, coupling, local_coupling=0.00): r""" .. math:: \forall \mu,\lambda,\eta \in \{e,i\}^3\, , \left\{ \begin{split} T \, \frac{\partial \nu_\mu}{\partial t} = & (\mathcal{F}_\mu - \nu_\mu ) + \frac{1}{2} \, c_{\lambda \eta} \, \frac{\partial^2 \mathcal{F}_\mu}{\partial \nu_\lambda \partial \nu_\eta} \\ T \, \frac{\partial c_{\lambda \eta} }{\partial t} = & A_{\lambda \eta} + (\mathcal{F}_\lambda - \nu_\lambda ) \, (\mathcal{F}_\eta - \nu_\eta ) + \\ & c_{\lambda \mu} \frac{\partial \mathcal{F}_\mu}{\partial \nu_\lambda} + c_{\mu \eta} \frac{\partial \mathcal{F}_\mu}{\partial \nu_\eta} - 2 c_{\lambda \eta} \end{split} \right. dot{W}_k &= W_k/tau_w-b*E_k \\ with: A_{\lambda \eta} = \left\{ \begin{split} \frac{\mathcal{F}_\lambda \, (1/T - \mathcal{F}_\lambda)}{N_\lambda} \qquad & \textrm{if } \lambda=\eta \\ 0 \qquad & \textrm{otherwise} \end{split} \right. """ #number of neurons N_e = self.N_tot * (1 - self.g) N_i = self.N_tot * self.g #state variable E = state_variables[0, :] I = state_variables[1, :] C_ee = state_variables[2, :] C_ei = state_variables[3, :] C_ii = state_variables[4, :] W_e = state_variables[5, :] W_i = state_variables[6, :] noise = state_variables[7, :] derivative = numpy.empty_like(state_variables) # long-range coupling c_0 = coupling[0, :] # short-range (local) coupling lc_E = local_coupling * E lc_I = local_coupling * I # external firing rate for the different population E_input_excitatory = c_0 + lc_E + self.external_input_ex_ex + self.weight_noise * noise index_bad_input = numpy.where(E_input_excitatory < 0) E_input_excitatory[index_bad_input] = 0.0 E_input_inhibitory = c_0 + lc_E + self.external_input_in_ex + self.weight_noise * noise index_bad_input = numpy.where(E_input_inhibitory < 0) E_input_inhibitory[index_bad_input] = 0.0 I_input_excitatory = lc_I + self.external_input_ex_in I_input_inhibitory = lc_I + self.external_input_in_in # Transfer function of excitatory and inhibitory neurons _TF_e = self.TF_excitatory(E, I, E_input_excitatory, I_input_excitatory, W_e) _TF_i = self.TF_inhibitory(E, I, E_input_inhibitory, I_input_inhibitory, W_i) # Derivatives taken numerically : use a central difference formula with spacing `dx` df = 1e-7 def _diff_fe(TF, fe, fi, fe_ext, fi_ext, W, df=df): return (TF(fe + df, fi, fe_ext, fi_ext, W) - TF(fe - df, fi, fe_ext, fi_ext, W)) / (2 * df * 1e3) def _diff_fi(TF, fe, fi, fe_ext, fi_ext, W, df=df): return (TF(fe, fi + df, fe_ext, fi_ext, W) - TF(fe, fi - df, fe_ext, fi_ext, W)) / (2 * df * 1e3) def _diff2_fe_fe_e(fe, fi, fe_ext, fi_ext, W, df=df): TF = self.TF_excitatory return (TF(fe + df, fi, fe_ext, fi_ext, W) - 2 * _TF_e + TF(fe - df, fi, fe_ext, fi_ext, W)) / ((df * 1e3)**2) def _diff2_fe_fe_i(fe, fi, fe_ext, fi_ext, W, df=df): TF = self.TF_inhibitory return (TF(fe + df, fi, fe_ext, fi_ext, W) - 2 * _TF_i + TF(fe - df, fi, fe_ext, fi_ext, W)) / ((df * 1e3)**2) def _diff2_fi_fe(TF, fe, fi, fe_ext, fi_ext, W, df=df): return (_diff_fi(TF, fe + df, fi, fe_ext, fi_ext, W) - _diff_fi( TF, fe - df, fi, fe_ext, fi_ext, W)) / (2 * df * 1e3) def _diff2_fe_fi(TF, fe, fi, fe_ext, fi_ext, W, df=df): return (_diff_fe(TF, fe, fi + df, fe_ext, fi_ext, W) - _diff_fe( TF, fe, fi - df, fe_ext, fi_ext, W)) / (2 * df * 1e3) def _diff2_fi_fi_e(fe, fi, fe_ext, fi_ext, W, df=df): TF = self.TF_excitatory return (TF(fe, fi + df, fe_ext, fi_ext, W) - 2 * _TF_e + TF(fe, fi - df, fe_ext, fi_ext, W)) / ((df * 1e3)**2) def _diff2_fi_fi_i(fe, fi, fe_ext, fi_ext, W, df=df): TF = self.TF_inhibitory return (TF(fe, fi + df, fe_ext, fi_ext, W) - 2 * _TF_i + TF(fe, fi - df, fe_ext, fi_ext, W)) / ((df * 1e3)**2) #Precompute some result _diff_fe_TF_e = _diff_fe(self.TF_excitatory, E, I, E_input_excitatory, I_input_excitatory, W_e) _diff_fe_TF_i = _diff_fe(self.TF_inhibitory, E, I, E_input_inhibitory, I_input_inhibitory, W_i) _diff_fi_TF_e = _diff_fi(self.TF_excitatory, E, I, E_input_excitatory, I_input_excitatory, W_e) _diff_fi_TF_i = _diff_fi(self.TF_inhibitory, E, I, E_input_inhibitory, I_input_inhibitory, W_i) # equation is inspired from github of Zerlaut : # https://github.com/yzerlaut/notebook_papers/blob/master/modeling_mesoscopic_dynamics/mean_field/master_equation.py # Excitatory firing rate derivation derivative[0] = ( _TF_e - E + .5 * C_ee * _diff2_fe_fe_e( E, I, E_input_excitatory, I_input_excitatory, W_e) + .5 * C_ei * _diff2_fe_fi(self.TF_excitatory, E, I, E_input_excitatory, I_input_excitatory, W_e) + .5 * C_ei * _diff2_fi_fe(self.TF_excitatory, E, I, E_input_excitatory, I_input_excitatory, W_e) + .5 * C_ii * _diff2_fi_fi_e(E, I, E_input_excitatory, I_input_excitatory, W_e)) / self.T # Inhibitory firing rate derivation derivative[1] = ( _TF_i - I + .5 * C_ee * _diff2_fe_fe_i( E, I, E_input_inhibitory, I_input_inhibitory, W_i) + .5 * C_ei * _diff2_fe_fi(self.TF_inhibitory, E, I, E_input_inhibitory, I_input_inhibitory, W_i) + .5 * C_ei * _diff2_fi_fe(self.TF_inhibitory, E, I, E_input_inhibitory, I_input_inhibitory, W_i) + .5 * C_ii * _diff2_fi_fi_i(E, I, E_input_inhibitory, I_input_inhibitory, W_i)) / self.T # Covariance excitatory-excitatory derivation derivative[2] = (_TF_e * (1. / self.T - _TF_e) / N_e + (_TF_e - E)**2 + 2. * C_ee * _diff_fe_TF_e + 2. * C_ei * _diff_fi_TF_i - 2. * C_ee) / self.T # Covariance excitatory-inhibitory or inhibitory-excitatory derivation derivative[3] = ( (_TF_e - E) * (_TF_i - I) + C_ee * _diff_fe_TF_e + C_ei * _diff_fe_TF_i + C_ei * _diff_fi_TF_e + C_ii * _diff_fi_TF_i - 2. * C_ei) / self.T # Covariance inhibitory-inhibitory derivation derivative[4] = (_TF_i * (1. / self.T - _TF_i) / N_i + (_TF_i - I)**2 + 2. * C_ii * _diff_fi_TF_i + 2. * C_ei * _diff_fe_TF_e - 2. * C_ii) / self.T # Adaptation excitatory mu_V, sigma_V, T_V = self.get_fluct_regime_vars( E, I, E_input_excitatory, I_input_excitatory, W_e, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, self.E_L_e, self.N_tot, self.p_connect_e, self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i) derivative[5] = -W_e / self.tau_w_e + self.b_e * E + self.a_e * ( mu_V - self.E_L_e) / self.tau_w_e # Adaptation inhibitory mu_V, sigma_V, T_V = self.get_fluct_regime_vars( E, I, E_input_inhibitory, I_input_inhibitory, W_i, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, self.E_L_i, self.N_tot, self.p_connect_e, self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i) derivative[6] = -W_i / self.tau_w_i + self.b_i * I + self.a_i * ( mu_V - self.E_L_i) / self.tau_w_i derivative[7] = -noise / self.tau_OU return derivative
def update_state_variables_before_integration(self, state_variables, coupling, local_coupling=0.0, stimulus=0.0): self._stimulus = stimulus if self.use_numba: state_variables = \ _numba_update_non_state_variables_before_integration( state_variables.reshape(state_variables.shape[:-1]).T, coupling.reshape(coupling.shape[:-1]).T + local_coupling * state_variables[0], self.a_e, self.b_e, self.d_e, self.w_p, self.W_e, self.J_N, self.Rin_e, self.a_i, self.b_i, self.d_i, self.W_i, self.J_i, self.Rin_i, self.G, self.lamda, self.I_o, self.I_ext) return state_variables.T[..., numpy.newaxis] # In this case, rates (H_e, H_i) are non-state variables, # i.e., they form part of state_variables but have no dynamics assigned on them # Most of the computations of this dfun aim at computing rates, including coupling considerations. # Therefore, we compute and update them only once a new state is computed, # and we consider them constant for any subsequent possible call to this function, # by any integration scheme S = state_variables[:2, :] # synaptic gating dynamics R = state_variables[2:4, :] # Rates Rin = state_variables[4:6, :] # Input rates from spiking network c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) J_N_S_e = self.J_N * S[0] # TODO: Confirm that this computation is correct for this model depending on the r_e and r_i values! I_e = self.w_p * J_N_S_e - self.J_i * S[ 1] + self.W_e * self.I_o + coupling + self.I_ext x_e = self.a_e * I_e - self.b_e # Only rates with R_e <= 0 0 will be updated by TVB. R_e = numpy.where(self._Rin_e_mask, R[0], x_e / (1 - numpy.exp(-self.d_e * x_e))) # ...and their Rin_e should be zero: Rin_e = numpy.where(self._Rin_e_mask, Rin[0], 0.0) I_i = J_N_S_e - S[1] + self.W_i * self.I_o + self.lamda * coupling x_i = self.a_i * I_i - self.b_i # Only rates with R_i < 0 will be updated by TVB. R_i = numpy.where(self._Rin_i_mask, R[1], x_i / (1 - numpy.exp(-self.d_i * x_i))) # ...and their Rin_i should be zero: Rin_i = numpy.where(self._Rin_i_mask, Rin[1], 0.0) # We now update the state_variable vector with the new rates: state_variables[2, :] = R_e state_variables[3, :] = R_i state_variables[4, :] = Rin_e state_variables[5, :] = Rin_i state_variables[6, :] = I_e state_variables[7, :] = I_i # Keep them here so that they are not recomputed in the dfun self._Rin = numpy.copy(state_variables[4:6]) return state_variables