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 _numpy_dfun(self, state_variables, coupling, local_coupling=0.0, update_non_state_variables=True): 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}) \, """ if update_non_state_variables: state_variables = \ self.update_non_state_variables(state_variables, coupling, local_coupling, use_numba=False) S = state_variables[:2, :] # synaptic gating dynamics R = state_variables[2:, :] # rates 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 are non-state variables: dummy = 0.0 * dS_e derivative = numpy.array([dS_e, dS_i, dummy, dummy]) return derivative
def update_derived_parameters(self): """ When needed, this should be a method for calculating parameters that are calculated based on paramaters directly set by the caller. For example, see, ReducedSetFitzHughNagumo. When not needed, this pass simplifies code that updates an arbitrary models parameters -- ie, this can be safely called on any model, whether it's used or not. """ self.n_nonintvar = self.nvar - self.nintvar self._Rin = None self._stimulus = 0.0 for var in ["Rin_e", "Rin_i"]: if hasattr(self, var): setattr(self, "_%s_mask" % var, getattr(self, var) > 0) else: setattr(self, var, numpy.array([0.0, ])) setattr(self, "_%s_mask" % var, numpy.array([False, ]))
def _x_E_I(self, x, i_region, E_I): x_E_I = self._region(x, i_region) x_E_I_shape = x_E_I.shape if x_E_I_shape == (self.number_of_modes, ): return x_E_I[E_I( i_region)] # if region parameter shape is (n_neurons,) else: return numpy.array([ x_E_I.item(), ]) # if region parameter shape is (1,) or (1, 1)
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 _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])
class MultiscaleWongWangExcIOInhI(ReducedWongWangExcIOInhI, SpikingWongWangExcIOInhI): _spiking_regions_inds = [] r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2014] Deco Gustavo, Ponce Alvarez Adrian, Patric Hagmann, Gian Luca Romani, Dante Mantini, and Maurizio Corbetta. *How Local Excitation–Inhibition Ratio Impacts the Whole Brain Dynamics*. The Journal of Neuroscience 34(23), 7886 –7898, 2014. .. automethod:: ReducedWongWang.__init__ 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}) \, """ # Define traited attributes for this model, these represent possible kwargs. # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={ "s_AMPA": numpy.array([0., 1.]), # 0 "x_NMDA": numpy.array([0., None]), # 1 "s_NMDA": numpy.array([0., 1.]), # 2 "s_GABA": numpy.array([0., 1.]), # 3 "s_AMPA_ext": numpy.array([0., 800.]), # 4 "V_m": numpy.array([None, None]), # 5 "t_ref": numpy.array([0., None]), # 6 "spikes_ext": numpy.array([0., None]), # 7 "spikes": numpy.array([0., None]), # 8 "rate": numpy.array([0., None]), # 9 "I_syn": numpy.array([None, None]), # 10 "I_L": numpy.array([None, None]), # 11 "I_AMPA": numpy.array([None, None]), # 12 "I_NMDA": numpy.array([None, None]), # 13 "I_GABA": numpy.array([None, None]), # 14 "I_AMPA_ext": numpy.array([None, None]) }, # 15 label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") # Choosing default initial conditions such as that there are no spikes initialy: state_variable_range = Final( default={ "s_AMPA": numpy.array([0., 1.]), # 0 "x_NMDA": numpy.array([0., 200.]), # 1 "s_NMDA": numpy.array([0., 1.]), # 2 "s_GABA": numpy.array([0., 1.]), # 3 "s_AMPA_ext": numpy.array([0., 1.]), # 4 "V_m": numpy.array([-70., -50.]), # 5 "t_ref": numpy.array([0., 1.]), # 6 "spikes_ext": numpy.array([0., 0.5]), # 7 "spikes": numpy.array([0., 0.5]), # 8 "rate": numpy.array([0., 1000.]), # 9 "I_syn": numpy.array([0., 1000.]), # 10 "I_L": numpy.array([0., 1000.]), # 11 "I_AMPA": numpy.array([-1000., 0.]), # 12 "I_NMDA": numpy.array([-1000., 0.]), # 13 "I_GABA": numpy.array([0., 1000.]), # 14 "I_AMPA_ext": numpy.array([-1000., 0.]) }, # 15 label="State variable ranges [lo, hi]", doc= """State variable ranges [lo, hi] for random initial condition construction""" ) variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "V_m", "t_ref", "spikes_ext", "spikes", "rate", "I_syn", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext"), default=("s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "V_m", "t_ref", "spikes_ext", "spikes", "rate", "I_syn", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext"), doc="""default state variables to be monitored""") state_variables = [ "s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "V_m", "t_ref", # state variables "spikes_ext", "spikes", "rate", "I_syn", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext" ] # non-state variables integration_variables = [ "s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "V_m", "t_ref" ] # non-state variables _nvar = 16 cvar = numpy.array([0], dtype=numpy.int32) number_of_modes = 200 # assuming that 0...N_E-1 are excitatory and N_E ... number_of_modes-1 are inhibitory # Return number of excitatory neurons/modes per region def _n_E(self, i_region): if i_region in self._spiking_regions_inds: return super(MultiscaleWongWangExcIOInhI, self)._n_E(i_region) else: return 1 # Return number of inhibitory neurons/modes per region def _n_I(self, i_region): if i_region in self._spiking_regions_inds: return super(MultiscaleWongWangExcIOInhI, self)._n_I(i_region) else: return 1 # Return indices of excitatory neurons/modes per region def _E(self, i_region): if i_region in self._spiking_regions_inds: return super(MultiscaleWongWangExcIOInhI, self)._E(i_region) else: return numpy.arange(self._N_E_max).astype('i') # Return indices of inhibitory neurons/modes per region def _I(self, i_region): if i_region in self._spiking_regions_inds: return super(MultiscaleWongWangExcIOInhI, self)._I(i_region) else: return numpy.arange(self._N_E_max, self.number_of_modes).astype('i') def update_initial_conditions_non_state_variables(self, state_variables, coupling, local_coupling=0.0, use_numba=False): __n_E = [] __n_I = [] __E = {} __I = {} self.__n_E = None self.__n_I = None self.__E = None self.__I = None # Initialize all non-state variables, as well as t_ref, to 0, i.e., assuming no spikes in history. state_variables[6:] = 0.0 for ii in range(state_variables.shape[1]): # For every region node.... __n_E.append(self._n_E(ii)) __n_I.append(self._n_I(ii)) __E[ii] = numpy.arange(__n_E[-1]).astype( 'i') # excitatory neurons' indices __I[ii] = numpy.arange(self._N_E_max, self._N_E_max + __n_I[-1]).astype( 'i') # inhibitory neurons' indices # Make sure that all empty positions are set to 0.0, if any: self._zero_empty_positions(state_variables, __E[ii], __I[ii], ii) # Set inhibitory synapses for excitatory neurons & excitatory synapses for inhibitory neurons to 0.0... self._zero_cross_synapses(state_variables, __E[ii], __I[ii], ii) self.__n_E = __n_E self.__n_I = __n_I self.__E = __E self.__I = __I return state_variables 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 _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}) \, """ derivative = 0.0 * state_variables for ii in range(state_variables[0].shape[0]): _E = self._E(ii) # excitatory neurons/populations indices _I = self._I(ii) # inhibitory neurons/populations indices if ii in self._spiking_regions_inds: exc_spikes = state_variables[8, ii, _E] # excitatory spikes tau_AMPA_E = self._x_E(self.tau_AMPA, ii) # 0. s_AMPA # ds_AMPA/dt = -1/tau_AMPA * s_AMPA + exc_spikes derivative[ 0, ii, _E] = -state_variables[0, ii, _E] / tau_AMPA_E + exc_spikes # 1. x_NMDA # dx_NMDA/dt = -x_NMDA/tau_NMDA_rise + exc_spikes derivative[1, ii, _E] = \ -state_variables[1, ii, _E] / self._x_E(self.tau_NMDA_rise, ii) + exc_spikes # 2. s_NMDA # ds_NMDA/dt = -1/tau_NMDA_decay * s_NMDA + alpha*x_NMDA*(1-s_NMDA) derivative[2, ii, _E] = \ -state_variables[2, ii, _E] / self._x_E(self.tau_NMDA_decay, ii) \ + self._x_E(self.alpha, ii) * state_variables[1, ii, _E] * (1 - state_variables[2, ii, _E]) # 4. s_AMPA_ext # ds_AMPA_ext/dt = -1/tau_AMPA * (s_AMPA_exc + spikes_ext) derivative[4, ii, _E] = -state_variables[4, ii, _E] / tau_AMPA_E # excitatory refractory neurons: ref = self._refractory_neurons_E[ii] not_ref = numpy.logical_not(ref) # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA - I_AMPA_EXT + I_ext _E_not_ref = _E[not_ref] derivative[5, ii, _E_not_ref] = ( - state_variables[11, ii, _E_not_ref] # 11. I_L - state_variables[12, ii, _E_not_ref] # 12. I_AMPA - state_variables[13, ii, _E_not_ref] # 13. I_NMDA - state_variables[14, ii, _E_not_ref] # 14. I_GABA - state_variables[15, ii, _E_not_ref] # 15. I_AMPA_ext + self._x_E_ref(self.I_ext, ii, not_ref) # I_ext ) \ / self._x_E_ref(self.C_m_E, ii, not_ref) # 6...and only refractory t_ref: # dt_ref/dt = -1 for t_ref > 0 so that t' = t - dt # and 0 otherwise derivative[6, ii, _E[ref]] = -1.0 # Inhibitory neurons: _I = self._I(ii) # inhibitory neurons indices # 3. s_GABA/dt = - s_GABA/tau_GABA + inh_spikes derivative[3, ii, _I] = \ -state_variables[3, ii, _I] / self._x_I(self.tau_GABA, ii) + state_variables[8, ii, _I] # 4. s_AMPA_ext # ds_AMPA_ext/dt = -1/tau_AMPA * (s_AMPA_exc + spikes_ext) derivative[4, ii, _I] = -state_variables[4, ii, _I] / self._x_I( self.tau_AMPA, ii) # inhibitory refractory neurons: ref = self._refractory_neurons_I[ii] not_ref = numpy.logical_not(ref) # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L - I_AMPA_EXT - I_AMPA - I_GABA - I_NMDA + I_ext # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA - I_AMPA_EXT + I_ext _I_not_ref = _I[not_ref] derivative[5, ii, _I_not_ref] = ( - state_variables[11, ii, _I_not_ref] # 11. I_L - state_variables[12, ii, _I_not_ref] # 12. I_AMPA - state_variables[13, ii, _I_not_ref] # 13. I_NMDA - state_variables[14, ii, _I_not_ref] # 14. I_GABA - state_variables[15, ii, _I_not_ref] # 15. I_AMPA_ext + self._x_I_ref(self.I_ext, ii, not_ref) # I_ext ) \ / self._x_I_ref(self.C_m_I, ii, not_ref) # 6...and only refractory t_ref: # dt_ref/dt = -1 for t_ref > 0 so that t' = t - dt # and 0 otherwise derivative[6, ii, _I[ref]] = -1.0 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 derivative[0, ii, _E] = \ - (state_variables[0, ii, 0] / self._region(self.tau_e, ii)) \ + (1 - state_variables[0, ii, 0]) * state_variables[9, ii, 0] * self._region(self.gamma_e, ii) # s_NMDA <= s_AMPA derivative[2, ii, _E] = derivative[0, ii, 0] # S_i = s_GABA derivative[1, ii, _I] = \ - (state_variables[1, ii, _I[0]] / self._region(self.tau_i, ii)) \ + state_variables[9, ii, _I[0]] * self._region(self.gamma_i, ii) return derivative def dfun(self, x, c, local_coupling=0.0): return self._numpy_dfun(x, c, local_coupling)
class ReducedWongWangExcIOInhI(ModelNumbaDfun): r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2014] Deco Gustavo, Ponce Alvarez Adrian, Patric Hagmann, Gian Luca Romani, Dante Mantini, and Maurizio Corbetta. *How Local Excitation–Inhibition Ratio Impacts the Whole Brain Dynamics*. The Journal of Neuroscience 34(23), 7886 –7898, 2014. 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}) \, """ _ui_name = "Reduced Wong-Wang with Excitatory and Inhibitory Coupled Populations" ui_configurable_parameters = [ 'a_e', 'b_e', 'd_e', 'gamma_e', 'tau_e', 'W_e', 'w_p', 'J_N', 'a_i', 'b_i', 'd_i', 'gamma_i', 'tau_i', 'W_i', 'J_i', 'I_o', 'G', 'lamda' ] # Define traited attributes for this model, these represent possible kwargs. a_e = arrays.FloatArray( label=":math:`a_e`", default=numpy.array([ 310., ]), range=basic.Range(lo=0., hi=500., step=1.), doc= "[n/C]. Excitatory population input gain parameter, chosen to fit numerical solutions.", order=1) b_e = arrays.FloatArray( label=":math:`b_e`", default=numpy.array([ 125., ]), range=basic.Range(lo=0., hi=200., step=1.), doc= "[Hz]. Excitatory population input shift parameter chosen to fit numerical solutions.", order=2) d_e = arrays.FloatArray( label=":math:`d_e`", default=numpy.array([ 0.160, ]), range=basic.Range(lo=0.0, hi=0.2, step=0.001), doc= """[s]. Excitatory population input scaling parameter chosen to fit numerical solutions.""", order=3) gamma_e = arrays.FloatArray( label=r":math:`\gamma_e`", default=numpy.array([ 0.641 / 1000, ]), range=basic.Range(lo=0.0, hi=1.0 / 1000, step=0.01 / 1000), doc="""Excitatory population kinetic parameter""", order=4) tau_e = arrays.FloatArray( label=r":math:`\tau_e`", default=numpy.array([ 100., ]), range=basic.Range(lo=50., hi=150., step=1.), doc="""[ms]. Excitatory population NMDA decay time constant.""", order=5) w_p = arrays.FloatArray(label=r":math:`w_p`", default=numpy.array([ 1.4, ]), range=basic.Range(lo=0.0, hi=2.0, step=0.01), doc="""Excitatory population recurrence weight""", order=6) J_N = arrays.FloatArray(label=r":math:`J_{N}`", default=numpy.array([ 0.15, ]), range=basic.Range(lo=0.001, hi=0.5, step=0.001), doc="""[nA] NMDA current""", order=7) W_e = arrays.FloatArray( label=r":math:`W_e`", default=numpy.array([ 1.0, ]), range=basic.Range(lo=0.0, hi=2.0, step=0.01), doc="""Excitatory population external input scaling weight""", order=8) a_i = arrays.FloatArray( label=":math:`a_i`", default=numpy.array([ 615., ]), range=basic.Range(lo=0., hi=1000., step=1.), doc= "[n/C]. Inhibitory population input gain parameter, chosen to fit numerical solutions.", order=9) b_i = arrays.FloatArray( label=":math:`b_i`", default=numpy.array([ 177.0, ]), range=basic.Range(lo=0.0, hi=200.0, step=1.0), doc= "[Hz]. Inhibitory population input shift parameter chosen to fit numerical solutions.", order=10) d_i = arrays.FloatArray( label=":math:`d_i`", default=numpy.array([ 0.087, ]), range=basic.Range(lo=0.0, hi=0.2, step=0.001), doc= """[s]. Inhibitory population input scaling parameter chosen to fit numerical solutions.""", order=11) gamma_i = arrays.FloatArray( label=r":math:`\gamma_i`", default=numpy.array([ 1.0 / 1000, ]), range=basic.Range(lo=0.0, hi=2.0 / 1000, step=0.01 / 1000), doc="""Inhibitory population kinetic parameter""", order=12) tau_i = arrays.FloatArray( label=r":math:`\tau_i`", default=numpy.array([ 10., ]), range=basic.Range(lo=50., hi=150., step=1.0), doc="""[ms]. Inhibitory population NMDA decay time constant.""", order=13) J_i = arrays.FloatArray(label=r":math:`J_{i}`", default=numpy.array([ 1.0, ]), range=basic.Range(lo=0.001, hi=2.0, step=0.001), doc="""[nA] Local inhibitory current""", order=14) W_i = arrays.FloatArray( label=r":math:`W_i`", default=numpy.array([ 0.7, ]), range=basic.Range(lo=0.0, hi=1.0, step=0.01), doc="""Inhibitory population external input scaling weight""", order=15) I_o = arrays.FloatArray(label=":math:`I_{o}`", default=numpy.array([ 0.382, ]), range=basic.Range(lo=0.0, hi=1.0, step=0.001), doc="""[nA]. Effective external input""", order=16) G = arrays.FloatArray(label=":math:`G`", default=numpy.array([ 2.0, ]), range=basic.Range(lo=0.0, hi=10.0, step=0.01), doc="""Global coupling scaling""", order=17) lamda = arrays.FloatArray(label=":math:`\lambda`", default=numpy.array([ 0.0, ]), range=basic.Range(lo=0.0, hi=1.0, step=0.01), doc="""Inhibitory global coupling scaling""", order=18) state_variable_range = basic.Dict(label="State variable ranges [lo, hi]", default={ "S_e": numpy.array([0.0, 1.0]), "S_i": numpy.array([0.0, 1.0]) }, doc="Population firing rate", order=22) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=['S_e', 'S_i'], default=['S_e', 'S_i'], select_multiple=True, doc="""default state variables to be monitored""", order=23) state_variables = ['S_e', 'S_i'] _nvar = 2 cvar = numpy.array([0], dtype=numpy.int32) def configure(self): """ """ super(ReducedWongWangExcIOInhI, self).configure() self.update_derived_parameters() 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. S[S > 1] = 1. 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] 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 = 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 = 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, x, c, local_coupling=0.0, **kwargs): x_ = x.reshape(x.shape[:-1]).T c_ = c.reshape(c.shape[:-1]).T + local_coupling * x[0] deriv = _numba_dfun(x_, c_, self.a_e, self.b_e, self.d_e, self.gamma_e, self.tau_e, self.w_p, self.W_e, self.J_N, self.a_i, self.b_i, self.d_i, self.gamma_i, self.tau_i, self.W_i, self.J_i, self.G, self.lamda, self.I_o) return deriv.T[..., numpy.newaxis]
class Zerlaut_model_second_order(Model): r""" **References**: .. [ZD_2018] Modeling mesoscopic cortical dynamics using a mean-field model of conductance-based networks of adaptive exponential integrate-and-fire neurons Used Eqns 19 from [ZD_2018]_ in ``dfun``. #TODO : need to describe a little bit the connectivity between the two populations The default parameters are taken from table 1 of [ZD_2018]_, pag.3 +---------------------------+------------+ | Table 1 | +--------------+------------+------------+ |Parameter | Value | Unit | +==============+============+============+ | cellular property | +--------------+------------+------------+ | g_l | 1.00 | nS | +--------------+------------+------------+ | E_l | -65.00 | mV | +--------------+------------+------------+ | C_m | 150.0 | pF | +--------------+------------+------------+ | V_thre | -50.0 | mV | +--------------+------------+------------+ | tau_refrec | 5.0 | ms | +--------------+------------+------------+ | tau_w | 500.0 | ms | +--------------+------------+------------+ | excitatory cell | +--------------+------------+------------+ | k_a | 2.0 | mV | +--------------+------------+------------+ | b | 20.0 | pA | +--------------+------------+------------+ | a | 4.0 | pA | +--------------+------------+------------+ | inhibitory cell | +--------------+------------+------------+ | k_a | 0.5 | mV | +--------------+------------+------------+ | a | 0.0 | pA | +--------------+------------+------------+ | b | 0.0 | ns | +--------------+------------+------------+ | synaptic properties | +--------------+------------+------------+ | E_e | 0.0 | mV | +--------------+------------+------------+ | E_i | -80.0 | mV | +--------------+------------+------------+ | Q_e | 1.0 | nS | +--------------+------------+------------+ | Q_i | 5.0 | nS | +--------------+------------+------------+ | tau_e | 5.0 | ms | +--------------+------------+------------+ | tau_i | 5.0 | ms | +--------------+------------+------------+ | numerical network | +--------------+------------+------------+ | N_tot | 10000 | | +--------------+------------+------------+ | epsilon | 5.0 % | | +--------------+------------+------------+ | g | 20.0 % | | +--------------+------------+------------+ | nu_ext | 4.0 | Hz | +--------------+------------+------------+ The models (:math:`E`, :math:`I`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. automethod:: Zerlaut_model_first_order.__init__ The general formulation for the \textit{\textbf{Zerlaut_model_first_order}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: #TODO need to give the equation of the second order """ _ui_name = "Zerlaut_model_2" ui_configurable_parameters = [ 'tau_e', 'tau_i', 'a_e', 'b_e', 'c_e', 'a_i', 'b_i', 'c_i', 'r_e', 'r_i', 'k_e', 'k_i', 'P', 'Q', 'theta_e', 'theta_i', 'alpha_e', 'alpha_i' ] # Define the attributes for this model, these represent possible kwargs. g_l = arrays.FloatArray( label=":math:`g_{l}`", default=numpy.array( [10.]), # 10 nS by default, i.e. ~ 100MOhm input resitance at rest range=basic.Range( lo=0.1, hi=100.0, step=0.1 ), # 0.1nS would be a very small cell, 100nS a very big one doc="""leak conductance [nS]""", order=1) E_L = arrays.FloatArray( label=":math:`E_{L}`", default=numpy.array([-65.0]), range=basic.Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential [mV]""", order=2) # N.B. Not independent of g_L, C_m should scale linearly with g_L C_m = arrays.FloatArray( label=":math:`C_{m}`", default=numpy.array([150]), range=basic.Range(lo=10.0, hi=500.0, step=10.0), # 20pF very small cell, 400pF very doc="""membrane capacitance [pF]""", order=3) V_th = arrays.FloatArray(label=":math:`V_{thre}`", default=numpy.array([-50.0]), range=basic.Range(lo=-60.0, hi=-40.0, step=0.1), doc="""AP threshold [mV]""", order=4) tau_ref = arrays.FloatArray(label=r":math:`\tau_{refrec}`", default=numpy.array([5.0]), range=basic.Range(lo=0.1, hi=10.0, step=0.1), doc="""refractory period [ms]""", order=5) tau_w = arrays.FloatArray( label=r":math:`\tau_w`", default=numpy.array([500.0]), range=basic.Range( lo=0.0, hi=1000.0, step=10.0 ), # from 0ms to 1s, sometimes you might want to remove this feature doc="""adaptation time constant [ms]""", order=6) k_a_e = arrays.FloatArray(label=":math:`Excitatory k_a`", default=numpy.array([2.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""Excitatory sodium sharpness [mV]""", order=7) b_e = arrays.FloatArray( label=":math:`Excitatory b`", default=numpy.array([20.0]), range=basic.Range( lo=0.0, hi=100.0, step=0.1), # here also from 0, to remove it if one wants doc="""Excitatory adaptation current increment [pA]""", order=8) a_e = arrays.FloatArray(label=":math:`Excitatory a`", default=numpy.array([4.0]), range=basic.Range(lo=1.0, hi=20.0, step=0.1), doc="""Excitatory adaptation conductance [nS]""", order=9) k_a_i = arrays.FloatArray(label=":math:`Inhibitory k_a`", default=numpy.array([0.5]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""Inhibitory sodium sharpness [mV]""", order=10) b_i = arrays.FloatArray( label=":math:`Inhibitory b`", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=100.0, step=0.1), doc="""Inhibitory adaptation current increment [pA]""", order=11) a_i = arrays.FloatArray(label=":math:`Inhibitory a`", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=20.0, step=0.1), doc="""Inhibitory adaptation conductance [nS]""", order=12) E_e = arrays.FloatArray(label=r":math:`E_e`", default=numpy.array([0.0]), range=basic.Range(lo=-20., hi=20., step=0.01), doc="""excitatory reversal potential [mV]""", order=13) E_i = arrays.FloatArray(label=":math:`E_i`", default=numpy.array([-80.0]), range=basic.Range(lo=-100.0, hi=-60.0, step=1.0), doc="""inhibitory reversal potential [mV]""", order=14) Q_e = arrays.FloatArray(label=r":math:`Q_e`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""excitatory quantal conductance [nS]""", order=15) Q_i = arrays.FloatArray(label=r":math:`Q_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.0, hi=10.0, step=0.1), doc="""inhibitory quantal conductance [nS]""", order=16) tau_e = arrays.FloatArray(label=":math:`\tau_e`", default=numpy.array([5.0]), range=basic.Range(lo=1.0, hi=10.0, step=1.0), doc="""excitatory decay [ms]""", order=17) tau_i = arrays.FloatArray(label=":math:`\tau_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.5, hi=10.0, step=0.01), doc="""inhibitory decay [ms]""", order=18) N_tot = arrays.IntegerArray(label=":math:`N_{tot}`", default=numpy.array([10000]), range=basic.Range(lo=1000, hi=50000, step=1000), doc="""cell number""", order=19) p_connect = arrays.FloatArray( label=":math:`\epsilon`", default=numpy.array([0.05]), range=basic.Range( lo=0.001, hi=0.2, step=0.001), # valid only for relatively sparse connectivities doc="""connectivity probability""", order=20) g = arrays.FloatArray( label=":math:`g`", default=numpy.array([0.2]), range=basic.Range( lo=0.01, hi=0.4, step=0.01 ), # inhibitory cell number never overcomes excitatry ones doc="""fraction of inhibitory cells""", order=21) nu_ext = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=100.0, step=0.01), doc="""external drive""", order=22) T = arrays.FloatArray(label=":math:`T`", default=numpy.array([5.0]), range=basic.Range(lo=1.0, hi=20.0, step=0.1), doc="""time scale of describing network activity""", order=22) P_e = arrays.IndexArray( label= ":math:`P_e`", #TODO need to check the size of the array when it's used # required=False, # file_storage=core.FILE_STORAGE_NONE, #TODO need to verified if the value is correct or not default=numpy.array([ -5.14155819e-02, 6.11046069e-03, 7.44015476e-03, 5.76905434e-05, -1.52902668e-04, 5.63364410e-04, 2.72814525e-04, 5.27870901e-04, -6.77080411e-04, 4.89796440e-04, 1.15073958e-03 ]), doc="""Polynome of excitatory phenomenological threshold (order 10)""", order=23) P_i = arrays.IndexArray( label= ":math:`P_i`", #TODO need to check the size of the array when it's used # required=False, # file_storage=core.FILE_STORAGE_NONE, #TODO need to verified if the value is correct or not default=numpy.array([ -5.46160664e-02, 4.58415750e-03, -1.77303201e-03, 6.64785219e-04, -3.00637490e-04, 3.93520293e-04, -5.14454957e-04, -6.39186948e-06, -1.39021341e-03, -4.85663596e-04, -3.63617754e-04 ]), doc="""Polynome of inhibitory phenomenological threshold (order 10)""", order=24) external_input = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.004]), range=basic.Range(lo=0.001, hi=0.1, step=0.001), doc="""external drive""", order=23) # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "E": numpy.array( [0.0, 0.2] ), # actually the 200Hz should be replaced by 1/T_refrac, but let's take that "I": numpy.array([0.0, 0.2]), "C_ee": numpy.array([0.0, 0.0]), # variance is positive or null "C_ei": numpy.array([0.0, 0.0]), # the co-variance is in [-c_ee*c_ii,c_ee*c_ii] "C_ii": numpy.array([0.0, 0.0]) # variance is positive or null }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random inital conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.\n E: firing rate of excitatory population in KHz\n I: firing rate of inhibitory population in KHz\n C_ee: the variance of the excitatory population activity \n C_ei: the covariance between the excitatory and inhibitory population activities (always symetric) \n C_ie: the variance of the inhibitory population activity """, order=24) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["E", "I", "C_ee", "C_ei", "C_ii"], #TODO need to define the recordable variable default=["E"], #TODO need to define the default recordable variables select_multiple=True, doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`E = 0`, :math:`I = 1`, :math:`C_ee = 2`, :math:`C_ei = 3`, :math:`C_ii = 4`.""", order=25) state_variables = 'E I C_ee C_ei C_ii'.split() _nvar = 5 cvar = numpy.array([0, 1, 2, 3, 4], dtype=numpy.int32) def dfun(self, state_variables, coupling, local_coupling=0.00): r""" .. math:: #TODO need to write the equation """ #this variable can be internal varible for optimization TF = Transfer_Function(self, self.P_e, self.P_i) Ne = self.N_tot * (1 - self.g) Ni = self.N_tot * self.g E = state_variables[0, :] I = state_variables[1, :] C_ee = state_variables[2, :] C_ei = state_variables[3, :] C_ii = 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 #equation is inspired from github of Zerlaut : # function from github of Zerlaut : # https://github.com/yzerlaut/notebook_papers/blob/master/modeling_mesoscopic_dynamics/mean_field/master_equation.py # Excitatory firing rate derivation # #TODO : need to check the accuracy of all equations derivative[0] = 1./self.T*(\ .5*C_ee*self._diff2_fe_fe(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ei*self._diff2_fe_fi(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ei*self._diff2_fi_fe(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ii*self._diff2_fi_fi(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ TF.excitatory(E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)-E) #Inhibitory firing rate derivation derivative[1] = 1./self.T*(\ .5*C_ee*self._diff2_fe_fe(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ei*self._diff2_fe_fi(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ei*self._diff2_fi_fe(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ .5*C_ii*self._diff2_fi_fi(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ TF.inhibitory(E+lc_E+ self.external_input, I+lc_I+ self.external_input)-I) #Covariance excitatory-excitatory derivation derivative[2] = 1./self.T*(\ 1./Ne*TF.excitatory(E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)*\ (1./self.T-TF.excitatory(E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input))+\ (TF.excitatory(E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)-E)**2+\ 2.*C_ee*self._diff_fe(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ 2.*C_ei*self._diff_fi(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ -2.*C_ee) #Covariance excitatory-inhibitory or inhibitory-excitatory derivation derivative[3] = 1./self.T*\ ((TF.excitatory(E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)-E)*\ (TF.inhibitory(E+lc_E+ self.external_input, I+lc_I+ self.external_input)-I)+\ C_ee*self._diff_fe(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ C_ei*self._diff_fe(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ C_ei*self._diff_fi(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ C_ii*self._diff_fi(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ -2.*C_ei) #Covariance inhibitory-inhibitory derivation derivative[4] = 1./self.T*(\ 1./Ni*TF.inhibitory(E+lc_E+ self.external_input, I+lc_I+ self.external_input)*\ (1./self.T-TF.inhibitory(E+lc_E+ self.external_input, I+lc_I+ self.external_input))+\ (TF.inhibitory(E+lc_E+ self.external_input, I+lc_I+ self.external_input)-I)**2+\ 2.*C_ii*self._diff_fi(TF.inhibitory, E+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ 2.*C_ei*self._diff_fe(TF.excitatory, E+c_0+lc_E+ self.external_input, I+lc_I+ self.external_input)+\ -2.*C_ii) return derivative #equation is inspired from github of Zerlaut : # function from github of Zerlaut : # https://github.com/yzerlaut/notebook_papers/blob/master/modeling_mesoscopic_dynamics/mean_field/master_equation.py ##### Derivatives taken numerically : use a central difference formula with spacing `dx` ## to be implemented analitically ! not hard... def _diff_fe(self, TF, fe, fi, df=1e-7): return (TF(fe + df, fi) - TF(fe - df, fi)) / (2 * df * 1e3) def _diff_fi(self, TF, fe, fi, df=1e-7): return (TF(fe, fi + df) - TF(fe, fi - df)) / (2 * df * 1e3) def _diff2_fe_fe(self, TF, fe, fi, df=1e-7): return (TF(fe + df, fi) - 2 * TF(fe, fi) + TF(fe - df, fi)) / ( (df * 1e3)**2) def _diff2_fi_fe(self, TF, fe, fi, df=1e-7): return (self._diff_fi(TF, fe + df, fi) - self._diff_fi(TF, fe - df, fi)) / (2 * df * 1e3) def _diff2_fe_fi(self, TF, fe, fi, df=1e-7): return (self._diff_fe(TF, fe, fi + df) - self._diff_fe(TF, fe, fi - df)) / (2 * df * 1e3) def _diff2_fi_fi(self, TF, fe, fi, df=1e-7): return (TF(fe, fi + df) - 2 * TF(fe, fi) + TF(fe, fi - df)) / ( (df * 1e3)**2)
def _region(self, x, i_region): try: return numpy.array(x[i_region]) except: return numpy.array(x[0])
class SpikingWongWangExcIOInhI(Model): _N_E_max = 200 _n_regions = 0 __n_E = [] __n_I = [] __E = {} __I = {} _auto_connection = True _refractory_neurons_E = {} _refractory_neurons_I = {} _spikes_E = {} _spikes_I = {} r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2013] Deco Gustavo, Ponce Alvarez Adrian, Dante Mantini, Gian Luca Romani, Patric Hagmann, and Maurizio Corbetta. *Resting-State Functional Connectivity Emerges from Structurally and Dynamically Shaped Slow Linear Fluctuations*. The Journal of Neuroscience 33(27), 11239 –11252, 2013. .. automethod:: ReducedWongWang.__init__ Equations taken from [DPA_2013]_ , page 11241 .. math:: """ # Define traited attributes for this model, these represent possible kwargs. V_L = NArray(label=":math:`V_L`", default=numpy.array([ -70., ]), domain=Range(lo=-100., hi=0., step=1.), doc="[mV]. Resting membrane potential.") V_thr = NArray(label=":math:`V_thr`", default=numpy.array([ -50., ]), domain=Range(lo=-100., hi=0., step=1.), doc="[mV]. Threshold membrane potential.") V_reset = NArray(label=":math:`V_reset`", default=numpy.array([ -55., ]), domain=Range(lo=-100., hi=0., step=1.), doc="[mV]. Refractory membrane potential.") tau_AMPA = NArray(label=":math:`tau_AMPA`", default=numpy.array([ 2., ]), domain=Range(lo=0., hi=10., step=0.1), doc="[ms]. AMPA synapse time constant.") tau_NMDA_rise = NArray(label=":math:`tau_NMDA_rise`", default=numpy.array([ 2., ]), domain=Range(lo=0., hi=10., step=0.1), doc="[ms]. NMDA synapse rise time constant.") tau_NMDA_decay = NArray(label=":math:`tau_NMDA_decay`", default=numpy.array([ 100., ]), domain=Range(lo=0., hi=1000., step=10.), doc="[ms]. NMDA synapse decay time constant.") tau_GABA = NArray(label=":math:`tau_GABA`", default=numpy.array([ 10., ]), domain=Range(lo=0., hi=100., step=1.), doc="[ms]. GABA synapse time constant.") alpha = NArray(label=":math:`alpha`", default=numpy.array([ 0.5, ]), domain=Range(lo=0., hi=10., step=0.1), doc="[kHz]. NMDA synapse rate constant.") beta = NArray(label=":math:`beta`", default=numpy.array([ 0.062, ]), domain=Range(lo=0., hi=0.1, step=0.001), doc="[]. NMDA synapse exponential constant.") lamda_NMDA = NArray(label=":math:`lamda_NMDA`", default=numpy.array([ 0.28, ]), domain=Range(lo=0., hi=1., step=0.01), doc="[]. NMDA synapse constant.") lamda = NArray(label=":math:`lamda`", default=numpy.array([ 0., ]), domain=Range(lo=0., hi=1., step=0.01), doc="[kHz]. Feedforward inhibition parameter.") I_ext = NArray(label=":math:`I_ext`", default=numpy.array([ 0., ]), domain=Range(lo=0., hi=1000., step=10.), doc="[pA]. External current stimulation.") spikes_ext = NArray(label=":math:`spikes_ext`", default=numpy.array([ 0., ]), domain=Range(lo=0., hi=100., step=1.), doc="[spike weight]. External spikes' stimulation.") G = NArray(label=":math:`G`", default=numpy.array([ 2.0, ]), domain=Range(lo=0.0, hi=100.0, step=1.0), doc="""Global coupling scaling""") N_E = NArray(label=":math:`N_E`", default=numpy.array([ 100, ]), domain=Range(lo=0, hi=1000000, step=1), doc="Number of excitatory population neurons.") V_E = NArray(label=":math:`V_E`", default=numpy.array([ 0., ]), domain=Range(lo=-50., hi=50., step=1.), doc="[mV]. Excitatory reversal potential.") w_EE = NArray( label=":math:`w_EE`", default=numpy.array([ 1.55, ]), # 1.4 for Deco et al. 2014 domain=Range(lo=0., hi=2.0, step=0.01), doc="Excitatory within population synapse weight.") w_EI = NArray(label=":math:`w_EI`", default=numpy.array([ 1., ]), domain=Range(lo=0., hi=10., step=0.1), doc="Excitatory to inhibitory population synapse weight.") C_m_E = NArray(label=":math:`C_m_E`", default=numpy.array([ 500.0, ]), domain=Range(lo=0., hi=10., step=1000.0), doc="[pF]. Excitatory population membrane capacitance.") g_m_E = NArray(label=":math:`g_m_E`", default=numpy.array([ 25., ]), domain=Range(lo=0., hi=100., step=1.0), doc="[nS]. Excitatory population membrane conductance.") g_AMPA_ext_E = NArray( label=":math:`g_AMPA_ext_E`", default=numpy.array([ 2.496, ]), # 3.37 for Deco et al. 2014 domain=Range(lo=0., hi=10., step=0.001), doc="[nS]. Excitatory population external AMPA conductance.") g_AMPA_E = NArray( label=":math:`g_AMPA_E`", default=numpy.array([ 0.104, ]), # 0.065 for Deco et al. 2014 domain=Range(lo=0., hi=0.1, step=0.001), doc="[nS]. Excitatory population AMPA conductance.") g_NMDA_E = NArray( label=":math:`g_NMDA_E`", default=numpy.array([ 0.327, ]), # 0.20 for Deco et al. 2014 domain=Range(lo=0., hi=1., step=0.001), doc="[nS]. Excitatory population NMDA conductance.") g_GABA_E = NArray( label=":math:`g_GABA_E`", default=numpy.array([ 4.375, ]), # 10.94 for Deco et al. 2014 domain=Range(lo=0., hi=10., step=0.001), doc="[nS]. Excitatory population GABA conductance.") tau_ref_E = NArray(label=":math:`tau_ref_E`", default=numpy.array([ 2., ]), domain=Range(lo=0., hi=10., step=0.1), doc="[ms]. Excitatory population refractory time.") N_I = NArray(label=":math:`N_I`", default=numpy.array([ 100, ]), domain=Range(lo=0, hi=1000000, step=10), doc="Number of inhibitory population neurons.") V_I = NArray(label=":math:`V_I`", default=numpy.array([ -70., ]), domain=Range(lo=-100., hi=0., step=1.), doc="[mV]. Inhibitory reversal potential.") w_II = NArray(label=":math:`w_II`", default=numpy.array([ 1., ]), domain=Range(lo=0., hi=10., step=0.1), doc="Inhibitory within population synapse weight.") w_IE = NArray( label=":math:`w_IE`", default=numpy.array([ 1., ]), domain=Range(lo=0., hi=10., step=0.1), doc= "Feedback inhibition: Inhibitory to excitatory population synapse weight." ) C_m_I = NArray(label=":math:`C_m_I`", default=numpy.array([ 200.0, ]), domain=Range(lo=0., hi=10., step=1000.0), doc="[pF]. Inhibitory population membrane capacitance.") g_m_I = NArray(label=":math:`g_m_I`", default=numpy.array([ 20., ]), domain=Range(lo=0., hi=100., step=1.), doc="[nS]. Inhibitory population membrane conductance.") g_AMPA_ext_I = NArray( label=":math:`g_AMPA_ext_I`", default=numpy.array([ 1.944, ]), # 2.59 for Deco et al. 2014 domain=Range(lo=0., hi=10., step=0.001), doc="[nS]. Inhibitory population external AMPA conductance.") g_AMPA_I = NArray( label=":math:`g_AMPA_I`", default=numpy.array([ 0.081, ]), # 0.051 for Deco et al. 2014 domain=Range(lo=0., hi=0.1, step=0.001), doc="[nS]. Inhibitory population AMPA conductance.") g_NMDA_I = NArray( label=":math:`g_NMDA_I`", default=numpy.array([ 0.258, ]), # 0.16 for Deco et al. 2014 domain=Range(lo=0., hi=1.0, step=0.001), doc="[nS]. Inhibitory population NMDA conductance.") g_GABA_I = NArray( label=":math:`g_GABA_I`", default=numpy.array([ 3.4055, ]), # 8.51 for Deco et al. 2014 domain=Range(lo=0., hi=10., step=0.0001), doc="[nS]. Inhibitory population GABA conductance.") tau_ref_I = NArray(label=":math:`tau_ref_I`", default=numpy.array([ 1, ]), domain=Range(lo=0., hi=10., step=0.1), doc="[ms]. Inhibitory population refractory time.") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={ "s_AMPA": numpy.array([0., 1.]), # 0 "x_NMDA": numpy.array([0., None]), # 1 "s_NMDA": numpy.array([0., 1.]), # 2 "s_GABA": numpy.array([0., 1.]), # 3 "s_AMPA_ext": numpy.array([ 0., 800.0 ]), # 4 It is assumed that 800 neurons project to this synapse "V_m": numpy.array([None, None]), # 5 "t_ref": numpy.array([0., None]), # 6 "spikes_ext": numpy.array([0., None]), # 7 "spikes": numpy.array([0., 1.]), # 8 "I_L": numpy.array([None, None]), # 9 "I_AMPA": numpy.array([None, None]), # 10 "I_GABA": numpy.array([None, None]), # 11 "I_NMDA": numpy.array([None, None]), # 12 "I_AMPA_ext": numpy.array([None, None]) }, # 13 label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") # Choosing default initial conditions such as that there are no spikes initialy: state_variable_range = Final( default={ "s_AMPA": numpy.array([0., 1.]), # 0 "x_NMDA": numpy.array([0., 200.]), # 1 "s_NMDA": numpy.array([0., 1.]), # 2 "s_GABA": numpy.array([0., 1.]), # 3 "s_AMPA_ext": numpy.array([0., 1.]), # 4 "V_m": numpy.array([-70., -50.]), # 5 "t_ref": numpy.array([0., 1.]), # 6 "spikes_ext": numpy.array([0., 0.5]), # 7 "spikes": numpy.array([0., 0.5]), # 8 "I_L": numpy.array([0., 1000.]), # 9 "I_AMPA": numpy.array([-1000., 0.]), # 10 "I_NMDA": numpy.array([-1000., 0.]), # 11 "I_GABA": numpy.array([0., 1000.]), # 12 "I_AMPA_ext": numpy.array([-10., 0.]) }, # 13 label="State variable ranges [lo, hi]", doc="Population firing rate") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=( "s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "s_AMPA_ext", "V_m", "t_ref", # state variables "spikes_ext", "spikes", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext"), # non-state variables default=( "s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "s_AMPA_ext", "V_m", "t_ref", # state variables "spikes_ext", "spikes", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext"), # non-state variables doc="""default state variables to be monitored""") state_variables = [ "s_AMPA", "x_NMDA", "s_NMDA", "s_GABA", "s_AMPA_ext", "V_m", "t_ref", # state variables "spikes_ext", "spikes", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext" ] # non-state variables non_integrated_variables = [ "spikes_ext", "spikes", "I_L", "I_AMPA", "I_NMDA", "I_GABA", "I_AMPA_ext" ] _nvar = 14 cvar = numpy.array([0], dtype=numpy.int32) number_of_modes = 200 # assuming that 0...N_E-1 are excitatory and N_E ... number_of_modes-1 are inhibitory _stimulus = 0.0 _non_integrated_variables = None def update_derived_parameters(self): """ When needed, this should be a method for calculating parameters that are calculated based on paramaters directly set by the caller. For example, see, ReducedSetFitzHughNagumo. When not needed, this pass simplifies code that updates an arbitrary models parameters -- ie, this can be safely called on any model, whether it's used or not. """ self._stimulus = 0.0 self._non_integrated_variables = None self.n_nonintvar = self.nvar - self.nintvar self._non_integrated_variables_inds = list( range(self.nintvar, self.nvar)) self._N_E_max = int(numpy.max( self.N_E)) # maximum number of excitatory neurons/modes self.number_of_modes = int(self._N_E_max + numpy.max(self.N_I)) # # todo: this exclusion list is fragile, consider excluding declarative attrs that are not arrays # excluded_params = ("state_variable_range", "state_variable_boundaries", "variables_of_interest", # "noise", "psi_table", "nerf_table", "gid") # for param in type(self).declarative_attrs: # if param in excluded_params: # continue # region_parameters = getattr(self, param) # try: # region_parameters[0, 0] # except: # new_parameters = numpy.reshape(region_parameters, (-1, 1)) # setattr(self, param, new_parameters) # region_parameters = getattr(self, param) # assert (region_parameters.shape[1] == self.number_of_modes) or (region_parameters.shape[1] == 1) def _region(self, x, i_region): try: return numpy.array(x[i_region]) except: return numpy.array(x[0]) def _prepare_indices(self): __n_E = [] __n_I = [] __E = {} __I = {} self.__n_E = None self.__n_I = None self.__E = None self.__I = None # Initialize all non-state variables, as well as t_ref, to 0, i.e., assuming no spikes in history. for i_region in range(self._n_regions): # For every region node.... __n_E.append(int(self._region(self.N_E, i_region).item())) __n_I.append(int(self._region(self.N_I, i_region).item())) __E[i_region] = numpy.arange(__n_E[-1]).astype( 'i') # excitatory neurons' indices __I[i_region] = numpy.arange( self._N_E_max, self._N_E_max + __n_I[-1]).astype( 'i') # inhibitory neurons' indices self.__n_E = __n_E self.__n_I = __n_I self.__E = __E self.__I = __I # Return number of excitatory neurons/modes per region def _n_E(self, i_region): # if self.__n_E is not None: return self.__n_E[i_region] # else: # return int(self._region(self.N_E, i_region).item()) # Return number of inhibitory neurons/modes per region def _n_I(self, i_region): # if self.__n_I is not None: return self.__n_I[i_region] # else: # return int(self._region(self.N_I, i_region).item()) # Return indices of excitatory neurons/modes per region def _E(self, i_region): # if self.__E is not None: return self.__E[i_region] # else: # return numpy.arange(self._n_E(i_region)).astype('i') # Return indices of inhibitory neurons/modes per region def _I(self, i_region): # if self.__I is not None: return self.__I[i_region] # else: # return numpy.arange(self._N_E_max, self._N_E_max + self._n_I(i_region)).astype('i') # Return x variables of excitatory/inhibitory neurons/modes per region def _x_E_I(self, x, i_region, E_I): x_E_I = self._region(x, i_region) x_E_I_shape = x_E_I.shape if x_E_I_shape == (self.number_of_modes, ): return x_E_I[E_I( i_region)] # if region parameter shape is (n_neurons,) else: return numpy.array([ x_E_I.item(), ]) # if region parameter shape is (1,) or (1, 1) # Return x variables of excitatory neurons/modes per region def _x_E(self, x, i_region): return self._x_E_I(x, i_region, self._E) # Return x variables of inhibitory neurons/modes per region def _x_I(self, x, i_region): return self._x_E_I(x, i_region, self._I) # Return x variables of refractory excitatory neurons/modes per region def _x_E_ref(self, x, i_region, ref): x_E = self._x_E(x, i_region) try: return x_E[ref] except: return x_E # Return x variables of refractory inhibitory neurons/modes per region def _x_I_ref(self, x, i_region, ref): x_I = self._x_I(x, i_region) try: return x_I[ref] except: return x_I 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 _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 _zero_empty_positions(self, state_variables, _E, _I, ii): # Make sure that all empty positions are set to 0.0, if any: _empty = numpy.unique(_E.tolist() + _I.tolist()) # all indices occupied by neurons if len(_empty) < state_variables[0].shape[1]: _empty = numpy.delete(numpy.arange(state_variables[0].shape[1]), _empty) # all empty indices # Set all empty positions to 0 state_variables[:, ii, _empty] = 0.0 def _zero_cross_synapses(self, state_variables, _E, _I, ii): # Set excitatory synapses for inhibitory neurons to 0.0... # 0. s_AMPA, 1. x_NMDA and 2. s_NMDA state_variables[:3][:, ii, _I] = 0.0 # ...and inhibitory synapses for excitatory neurons to 0.0 # 4. s_GABA state_variables[3, ii, _E] = 0.0 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 _numpy_dfun(self, integration_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}) \, """ derivative = 0.0 * integration_variables if self._non_integrated_variables is None: state_variables = np.array(integration_variables.tolist() + [0.0 * integration_variables[0]] * self.n_nonintvar) state_variables = self.update_state_variables_before_integration( state_variables, coupling, local_coupling, self._stimulus) if self._n_regions == 0: self._n_regions = integration_variables.shape[1] self._prepare_indices() for ii in range(self._n_regions): # For every region node.... # Excitatory neurons: _E = self._E(ii) # excitatory neurons indices tau_AMPA_E = self._x_E(self.tau_AMPA, ii) # 0. s_AMPA # ds_AMPA/dt = -1/tau_AMPA * s_AMPA derivative[0, ii, _E] = -integration_variables[0, ii, _E] / tau_AMPA_E # 1. x_NMDA # dx_NMDA/dt = -x_NMDA/tau_NMDA_rise derivative[1, ii, _E] = -integration_variables[1, ii, _E] / self._x_E( self.tau_NMDA_rise, ii) # 2. s_NMDA # ds_NMDA/dt = -1/tau_NMDA_decay * s_NMDA + alpha*x_NMDA*(1-s_NMDA) derivative[2, ii, _E] = \ -integration_variables[2, ii, _E] / self._x_E(self.tau_NMDA_decay, ii) \ + self._x_E(self.alpha, ii) * integration_variables[1, ii, _E] * (1 - integration_variables[2, ii, _E]) # 4. s_AMPA_ext # ds_AMPA_ext/dt = -s_AMPA_exc/tau_AMPA derivative[4, ii, _E] = -integration_variables[4, ii, _E] / tau_AMPA_E # excitatory refractory neurons: ref = self._refractory_neurons_E[ii] not_ref = numpy.logical_not(ref) # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA - I_AMPA_EXT + I_ext _E_not_ref = _E[not_ref] derivative[5, ii, _E_not_ref] = ( - self._non_integrated_variables[2, ii, _E_not_ref] # 9. I_L - self._non_integrated_variables[3, ii, _E_not_ref] # 10. I_AMPA - self._non_integrated_variables[4, ii, _E_not_ref] # 11. I_NMDA - self._non_integrated_variables[5, ii, _E_not_ref] # 12. I_GABA - self._non_integrated_variables[6, ii, _E_not_ref] # 13. I_AMPA_ext + self._x_E_ref(self.I_ext, ii, not_ref) # I_ext ) \ / self._x_E_ref(self.C_m_E, ii, not_ref) # 6...and only refractory t_ref: # dt_ref/dt = -1 for t_ref > 0 so that t' = t - dt # and 0 otherwise derivative[6, ii, _E[ref]] = -1.0 # Inhibitory neurons: _I = self._I(ii) # inhibitory neurons indices # 3. s_GABA/dt = - s_GABA/tau_GABA derivative[3, ii, _I] = -integration_variables[3, ii, _I] / self._x_I( self.tau_GABA, ii) # 4. s_AMPA_ext # ds_AMPA_ext/dt = -s_AMPA_exc/tau_AMPA derivative[4, ii, _I] = -integration_variables[4, ii, _I] / self._x_I( self.tau_AMPA, ii) # inhibitory refractory neurons: ref = self._refractory_neurons_I[ii] not_ref = numpy.logical_not(ref) # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L - I_AMPA_EXT - I_AMPA - I_GABA - I_NMDA + I_ext # 5. Integrate only non-refractory V_m # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA - I_AMPA_EXT + I_ext _I_not_ref = _I[not_ref] derivative[5, ii, _I_not_ref] = ( - self._non_integrated_variables[2, ii, _I_not_ref] # 9. I_L - self._non_integrated_variables[3, ii, _I_not_ref] # 10. I_AMPA - self._non_integrated_variables[4, ii, _I_not_ref] # 11. I_NMDA - self._non_integrated_variables[5, ii, _I_not_ref] # 12. I_GABA - self._non_integrated_variables[6, ii, _I_not_ref] # 13. I_AMPA_ext + self._x_I_ref(self.I_ext, ii, not_ref) # I_ext ) \ / self._x_I_ref(self.C_m_I, ii, not_ref) # 6...and only refractory t_ref: # dt_ref/dt = -1 for t_ref > 0 so that t' = t - dt # and 0 otherwise derivative[6, ii, _I[ref]] = -1.0 self._non_integrated_variables = None return derivative def dfun(self, x, c, local_coupling=0.0): return self._numpy_dfun(x, c, local_coupling)
class Generic2dOscillator(TVBGeneric2dOscillator): r""" The Generic2dOscillator model is a generic dynamic system with two state variables. The dynamic equations of this model are composed of two ordinary differential equations comprising two nullclines. The first nullcline is a cubic function as it is found in most neuron and population models; the second nullcline is arbitrarily configurable as a polynomial function up to second order. The manipulation of the latter nullcline's parameters allows to generate a wide range of different behaviours. Equations: .. 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), See: .. [FH_1961] FitzHugh, R., *Impulses and physiological states in theoretical models of nerve membrane*, Biophysical Journal 1: 445, 1961. .. [Nagumo_1962] Nagumo et.al, *An Active Pulse Transmission Line Simulating Nerve Axon*, Proceedings of the IRE 50: 2061, 1962. .. [SJ_2011] Stefanescu, R., Jirsa, V.K. *Reduced representations of heterogeneous mixed neural networks with synaptic coupling*. Physical Review E, 83, 2011. .. [SJ_2010] Jirsa VK, Stefanescu R. *Neural population modes capture biologically realistic large-scale network dynamics*. Bulletin of Mathematical Biology, 2010. .. [SJ_2008_a] Stefanescu, R., Jirsa, V.K. *A low dimensional description of globally coupled heterogeneous neural networks of excitatory and inhibitory neurons*. PLoS Computational Biology, 4(11), 2008). The model's (:math:`V`, :math:`W`) time series and phase-plane its nullclines can be seen in the figure below. The model with its default parameters exhibits FitzHugh-Nagumo like dynamics. +---------------------------+ | Table 1 | +--------------+------------+ | EXCITABLE CONFIGURATION | +--------------+------------+ |Parameter | Value | +==============+============+ | a | -2.0 | +--------------+------------+ | b | -10.0 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | limit cycle if a is 2.0 | +---------------------------+ +---------------------------+ | Table 2 | +--------------+------------+ | BISTABLE CONFIGURATION | +--------------+------------+ |Parameter | Value | +==============+============+ | a | 1.0 | +--------------+------------+ | b | 0.0 | +--------------+------------+ | c | -5.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | monostable regime: | | fixed point if Iext=-2.0 | | limit cycle if Iext=-1.0 | +---------------------------+ +---------------------------+ | Table 3 | +--------------+------------+ | EXCITABLE CONFIGURATION | +--------------+------------+ | (similar to Morris-Lecar)| +--------------+------------+ |Parameter | Value | +==============+============+ | a | 0.5 | +--------------+------------+ | b | 0.6 | +--------------+------------+ | c | -4.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | excitable regime if b=0.6 | | oscillatory if b=0.4 | +---------------------------+ +---------------------------+ | Table 4 | +--------------+------------+ | GhoshetAl, 2008 | | KnocketAl, 2009 | +--------------+------------+ |Parameter | Value | +==============+============+ | a | 1.05 | +--------------+------------+ | b | -1.00 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.1 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | alpha | 1.0 | +--------------+------------+ | beta | 0.2 | +--------------+------------+ | gamma | -1.0 | +--------------+------------+ | e | 0.0 | +--------------+------------+ | g | 1.0 | +--------------+------------+ | f | 1/3 | +--------------+------------+ | tau | 1.25 | +--------------+------------+ | | | frequency peak at 10Hz | | | +---------------------------+ +---------------------------+ | Table 5 | +--------------+------------+ | SanzLeonetAl 2013 | +--------------+------------+ |Parameter | Value | +==============+============+ | a | - 0.5 | +--------------+------------+ | b | -10.0 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | | | intrinsic frequency is | | approx 10 Hz | | | +---------------------------+ NOTE: This regime, if I = 2.1, is called subthreshold regime. Unstable oscillations appear through a subcritical Hopf bifurcation. .. figure :: img/Generic2dOscillator_01_mode_0_pplane.svg .. _phase-plane-Generic2D: :alt: Phase plane of the generic 2D population model with (V, W) The (:math:`V`, :math:`W`) phase-plane for the generic 2D population model for default parameters. The dynamical system has an equilibrium point. .. automethod:: Generic2dOscillator.dfun """ V_m = NArray( label=r":math:`V_m`", default=numpy.array([0.0]), domain=Range(lo=-10.0, hi=10.0, step=0.1), doc="""NEST membrane potential input""") 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 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]
class Zerlaut_model_first_order(Model): r""" **References**: .. [ZD_2017] Zerlaut Yann and Destexhe Alain *A mean-field model for conductance-based networks of adaptive exponential integrate-and-fire neurons*, arXiv:1703.00698 Used Eqns 20 from [ZD_2017]_ in ``dfun``. #TODO : need to discribe a little bit the connectivity between two populations The default parameters are taken from table 1 of [ZD_20107]_, pag.3 +---------------------------+------------+ | Table 1 | +--------------+------------+------------+ |Parameter | Value | Unit | +==============+============+============+ | cellular property | +--------------+------------+------------+ | g_l | 1.00 | nS | +--------------+------------+------------+ | E_l | -65.00 | mV | +--------------+------------+------------+ | C_m | 150.0 | pF | +--------------+------------+------------+ | V_thre | -50.0 | mV | +--------------+------------+------------+ | tau_refrec | 5.0 | ms | +--------------+------------+------------+ | tau_w | 500.0 | ms | +--------------+------------+------------+ | excitatory cell | +--------------+------------+------------+ | k_a | 2.0 | mV | +--------------+------------+------------+ | b | 20.0 | pA | +--------------+------------+------------+ | a | 4.0 | pA | +--------------+------------+------------+ | inhibitory cell | +--------------+------------+------------+ | k_a | 0.5 | mV | +--------------+------------+------------+ | a | 0.0 | pA | +--------------+------------+------------+ | b | 0.0 | ns | +--------------+------------+------------+ | synaptic properties | +--------------+------------+------------+ | E_e | 0.0 | mV | +--------------+------------+------------+ | E_i | -80.0 | mV | +--------------+------------+------------+ | Q_e | 1.0 | nS | +--------------+------------+------------+ | Q_i | 5.0 | nS | +--------------+------------+------------+ | tau_e | 5.0 | ms | +--------------+------------+------------+ | tau_i | 5.0 | ms | +--------------+------------+------------+ | numerical network | +--------------+------------+------------+ | N_tot | 10000 | | +--------------+------------+------------+ | epsilon | 5.0 % | | +--------------+------------+------------+ | g | 20.0 % | | +--------------+------------+------------+ | nu_ext | 4.0 | Hz | +--------------+------------+------------+ The models (:math:`E`, :math:`I`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. automethod:: Zerlaut_model_first_order.__init__ The general formulation for the \textit{\textbf{Zerlaut_model_first_order}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: T\dot{E}_k &= F_e-E_k \\ T\dot{I}_k &= F_i-I_k \\ F_\lambda = #TODO need to give the expression of F """ _ui_name = "Zerlaut_model_1" ui_configurable_parameters = [ 'c_ee', 'c_ei', 'c_ie', 'c_ii', 'tau_e', 'tau_i', 'a_e', 'b_e', 'c_e', 'a_i', 'b_i', 'c_i', 'r_e', 'r_i', 'k_e', 'k_i', 'P', 'Q', 'theta_e', 'theta_i', 'alpha_e', 'alpha_i' ] # Define traited attributes for this model, these represent possible kwargs. g_l = arrays.FloatArray( label=":math:`g_{l}`", default=numpy.array( [10.]), # 10 nS by default, i.e. ~ 100MOhm input resitance at rest range=basic.Range( lo=0.1, hi=100.0, step=0.1 ), # 0.1nS would be a very small cell, 100nS a very big one doc="""leak conductance [nS]""", order=1) E_L = arrays.FloatArray( label=":math:`E_{L}`", default=numpy.array([-65.0]), range=basic.Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential [mV]""", order=2) # N.B. Not independent of g_L, C_m should scale linearly with g_L C_m = arrays.FloatArray( label=":math:`C_{m}`", default=numpy.array([150]), range=basic.Range(lo=10.0, hi=500.0, step=10.0), # 20pF very small cell, 400pF very doc="""membrane capacitance [pF]""", order=3) V_th = arrays.FloatArray(label=":math:`V_{thre}`", default=numpy.array([-50.0]), range=basic.Range(lo=-60.0, hi=-40.0, step=0.1), doc="""AP threshold [mV]""", order=4) tau_ref = arrays.FloatArray(label=r":math:`\tau_{refrec}`", default=numpy.array([5.0]), range=basic.Range(lo=0.1, hi=10.0, step=0.1), doc="""refractory period [ms]""", order=5) tau_w = arrays.FloatArray( label=r":math:`\tau_w`", default=numpy.array([500.0]), range=basic.Range( lo=0.0, hi=1000.0, step=10.0 ), # from 0ms to 1s, sometimes you might want to remove this feature doc="""adaptation time constant [ms]""", order=6) k_a_e = arrays.FloatArray(label=":math:`Excitatory k_a`", default=numpy.array([2.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""Excitatory sodium sharpness [mV]""", order=7) b_e = arrays.FloatArray( label=":math:`Excitatory b`", default=numpy.array([20.0]), range=basic.Range( lo=0.0, hi=100.0, step=0.1), # here also from 0, to remove it if one wants doc="""Excitatory adaptation current increment [pA]""", order=8) a_e = arrays.FloatArray(label=":math:`Excitatory a`", default=numpy.array([4.0]), range=basic.Range(lo=1.0, hi=20.0, step=0.1), doc="""Excitatory adaptation conductance [nS]""", order=9) k_a_i = arrays.FloatArray(label=":math:`Inhibitory k_a`", default=numpy.array([0.5]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""Inhibitory sodium sharpness [mV]""", order=10) b_i = arrays.FloatArray( label=":math:`Inhibitory b`", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=100.0, step=0.1), doc="""Inhibitory adaptation current increment [pA]""", order=11) a_i = arrays.FloatArray(label=":math:`Inhibitory a`", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=20.0, step=0.1), doc="""Inhibitory adaptation conductance [nS]""", order=12) E_e = arrays.FloatArray(label=r":math:`E_e`", default=numpy.array([0.0]), range=basic.Range(lo=-20., hi=20., step=0.01), doc="""excitatory reversal potential [mV]""", order=13) E_i = arrays.FloatArray(label=":math:`E_i`", default=numpy.array([-80.0]), range=basic.Range(lo=-100.0, hi=-60.0, step=1.0), doc="""inhibitory reversal potential [mV]""", order=14) Q_e = arrays.FloatArray(label=r":math:`Q_e`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""excitatory quantal conductance [nS]""", order=15) Q_i = arrays.FloatArray(label=r":math:`Q_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.0, hi=10.0, step=0.1), doc="""inhibitory quantal conductance [nS]""", order=16) tau_e = arrays.FloatArray(label=":math:`\tau_e`", default=numpy.array([5.0]), range=basic.Range(lo=1.0, hi=10.0, step=1.0), doc="""excitatory decay [ms]""", order=17) tau_i = arrays.FloatArray(label=":math:`\tau_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.5, hi=10.0, step=0.01), doc="""inhibitory decay [ms]""", order=18) N_tot = arrays.IntegerArray(label=":math:`N_{tot}`", default=numpy.array([10000]), range=basic.Range(lo=1000, hi=50000, step=1000), doc="""cell number""", order=19) p_connect = arrays.FloatArray( label=":math:`\epsilon`", default=numpy.array([0.05]), range=basic.Range( lo=0.001, hi=0.2, step=0.001), # valid only for relatively sparse connectivities doc="""connectivity probability""", order=20) g = arrays.FloatArray( label=":math:`g`", default=numpy.array([0.2]), range=basic.Range( lo=0.01, hi=0.4, step=0.01 ), # inhibitory cell number never overcomes excitatry ones doc="""fraction of inhibitory cells""", order=21) nu_ext = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([4.0]), range=basic.Range(lo=0.0, hi=100.0, step=0.01), doc="""external drive""", order=22) T = arrays.FloatArray(label=":math:`T`", default=numpy.array([5.0]), range=basic.Range(lo=1., hi=20.0, step=0.1), doc="""time scale of describing network activity""", order=22) P_e = arrays.IndexArray( label= ":math:`P_e`", #TODO need to check the size of the array when it's used # required=False, # file_storage=core.FILE_STORAGE_NONE, #TODO need to verified if the value is correct or not default=numpy.array([ -5.14155819e-02, 6.11046069e-03, 7.44015476e-03, 5.76905434e-05, -1.52902668e-04, 5.63364410e-04, 2.72814525e-04, 5.27870901e-04, -6.77080411e-04, 4.89796440e-04, 1.15073958e-03 ]), doc="""Polynome of excitatory phenomenological threshold (order 10)""", order=23) P_i = arrays.IndexArray( label= ":math:`P_i`", #TODO need to check the size of the array when it's used # required=False, # file_storage=core.FILE_STORAGE_NONE, #TODO need to verified if the value is correct or not default=numpy.array([ -5.46160664e-02, 4.58415750e-03, -1.77303201e-03, 6.64785219e-04, -3.00637490e-04, 3.93520293e-04, -5.14454957e-04, -6.39186948e-06, -1.39021341e-03, -4.85663596e-04, -3.63617754e-04 ]), doc="""Polynome of inhibitory phenomenological threshold (order 10)""", order=24) external_input = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.004]), range=basic.Range(lo=0.001, hi=0.1, step=0.001), doc="""external drive""", order=23) # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "E": numpy.array( [0.00001, 0.1] ), # actually the 200Hz should be replaced by 1/T_refrac, but let's take that "I": numpy.array([0.00001, 0.1]) }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random inital conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.\n E: firing rate of excitatory population in KHz\n I: firing rate of inhibitory population in kHz """, order=23) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["E", "I"], #TODO need to define the recordable variable default=["E"], #TODO need to define the default recordable variables select_multiple=True, doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`E = 0` and :math:`I = 1`.""", order=24) state_variables = 'E I'.split() _nvar = 2 cvar = numpy.array([0, 1], dtype=numpy.int32) 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\} """ #this variable can be internal variable for optimization TF = Transfer_Function(self, self.P_e, self.P_i) E = state_variables[0, :] I = state_variables[1, :] 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 #equation is inspired from github of Zerlaut : # https://bitbucket.org/yzerlaut/mean_field_for_multi_input_integration # ./mean_field/master_equation.py # Excitatory firing rate derivation derivative[0] = ( TF.excitatory(E + c_0 + lc_E + self.external_input, I + lc_I + self.external_input) - E) / self.T #TODO : need to check the accuracy of the equation # Inhibitory firing rate derivation derivative[1] = ( TF.inhibitory(E + self.external_input, I + lc_I + self.external_input) - I) / self.T #TODO : need to check the accuracy of the equation return derivative
class Zerlaut_adaptation_first_order(Model): r""" **References**: .. [ZD_2018] Zerlaut, Y., Chemla, S., Chavane, F. et al. *Modeling mesoscopic cortical dynamics using a mean-field model of conductance-based networks of adaptive exponential integrate-and-fire neurons*, J Comput Neurosci (2018) 44: 45. https://doi-org.lama.univ-amu.fr/10.1007/s10827-017-0668-2 .. [MV_2018] Matteo di Volo, Alberto Romagnoni, Cristiano Capone, Alain Destexhe (2018) *Mean-field model for the dynamics of conductance-based networks of excitatory and inhibitory spiking neurons with adaptation*, bioRxiv, doi: https://doi.org/10.1101/352393 Used Eqns 4 from [MV_2018]_ in ``dfun``. The default parameters are taken from table 1 of [ZD_2018]_, pag.47 and modify for the adaptation [MV_2018] +---------------------------+------------+ | Table 1 | +--------------+------------+------------+ |Parameter | Value | Unit | +==============+============+============+ | cellular property | +--------------+------------+------------+ | g_L | 10.00 | nS | +--------------+------------+------------+ | E_L_e | -67.00 | mV | +--------------+------------+------------+ | E_L_i | -63.00 | mV | +--------------+------------+------------+ | C_m | 150.0 | pF | +--------------+------------+------------+ | b | 60.0 | nS | +--------------+------------+------------+ | tau_w | 500.0 | ms | +--------------+------------+------------+ | T | 5.0 | ms | +--------------+------------+------------+ | synaptic properties | +--------------+------------+------------+ | E_e | 0.0 | mV | +--------------+------------+------------+ | E_i | -80.0 | mV | +--------------+------------+------------+ | Q_e | 1.0 | nS | +--------------+------------+------------+ | Q_i | 5.0 | nS | +--------------+------------+------------+ | tau_e | 5.0 | ms | +--------------+------------+------------+ | tau_i | 5.0 | ms | +--------------+------------+------------+ | numerical network | +--------------+------------+------------+ | N_tot | 10000 | | +--------------+------------+------------+ | p_connect | 5.0 % | | +--------------+------------+------------+ | g | 20.0 % | | +--------------+------------+------------+ |external_input| 0.001 | Hz | +--------------+------------+------------+ The default coefficients of the transfert function are taken from table I of [MV_2018]_, pag.49 +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | excitatory cell | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | -4.98e-02 | 5.06e-03 | -2.5e-02 | 1.4e-03 | -4.1e-04 | 1.05e-02 | -3.6e-02 | 7.4e-03 | 1.2e-03 | -4.07e-02 | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | inhibitory cell | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | -5.14e-02 | 4.0e-03 | -8.3e-03 | 2.0e-04 | -5.0e-04 | 1.4e-03 | -1.46e-02 | 4.5e-03 | 2.8e-03 | -1.53e-02 | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ The models (:math:`E`, :math:`I`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. automethod:: Zerlaut_adaptation_first_order.__init__ The general formulation for the \textit{\textbf{Zerlaut_adaptation_first_order}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: T\dot{E}_k &= F_e-E_k \\ T\dot{I}_k &= F_i-I_k \\ dot{W}_k &= W_k/tau_w-b*E_k \\ F_\lambda = Erfc(V^{eff}_{thre}-\mu_V/\sqrt(2)\sigma_V) """ _ui_name = "Zerlaut_adaptation_first_order" ui_configurable_parameters = [ 'g_L', 'E_L_e', 'E_L_i', 'C_m', 'b', 'tau_w', 'E_e', 'E_i', 'Q_e', 'Q_i', 'tau_e', 'tau_i', 'N_tot', 'p_connect', 'g', 'T', 'external_input' ] # Define traited attributes for this model, these represent possible kwargs. g_L = arrays.FloatArray( label=":math:`g_{L}`", default=numpy.array( [10.]), # 10 nS by default, i.e. ~ 100MOhm input resitance at rest range=basic.Range( lo=0.1, hi=100.0, step=0.1 ), # 0.1nS would be a very small cell, 100nS a very big one doc="""leak conductance [nS]""", order=1) E_L_e = arrays.FloatArray( label=":math:`E_{L}`", default=numpy.array([-63.0]), range=basic.Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential for excitatory [mV]""", order=2) E_L_i = arrays.FloatArray( label=":math:`E_{L}`", default=numpy.array([-65.0]), range=basic.Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential for inhibitory [mV]""", order=3) # N.B. Not independent of g_L, C_m should scale linearly with g_L C_m = arrays.FloatArray( label=":math:`C_{m}`", default=numpy.array([150]), range=basic.Range(lo=10.0, hi=500.0, step=10.0), # 20pF very small cell, 400pF very doc="""membrane capacitance [pF]""", order=4) b = arrays.FloatArray(label=":math:`b`", default=numpy.array([60.0]), range=basic.Range(lo=0.0, hi=150.0, step=1.0), doc="""Adaptation [nS]""", order=5) tau_w = arrays.FloatArray(label=":math:`tau_w`", default=numpy.array([500.0]), range=basic.Range(lo=5.0, hi=1000.0, step=1.0), doc="""Adaptation time constant [ms]""", order=6) E_e = arrays.FloatArray(label=r":math:`E_e`", default=numpy.array([0.0]), range=basic.Range(lo=-20., hi=20., step=0.01), doc="""excitatory reversal potential [mV]""", order=7) E_i = arrays.FloatArray(label=":math:`E_i`", default=numpy.array([-80.0]), range=basic.Range(lo=-100.0, hi=-60.0, step=1.0), doc="""inhibitory reversal potential [mV]""", order=8) Q_e = arrays.FloatArray(label=r":math:`Q_e`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.1), doc="""excitatory quantal conductance [nS]""", order=9) Q_i = arrays.FloatArray(label=r":math:`Q_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.0, hi=10.0, step=0.1), doc="""inhibitory quantal conductance [nS]""", order=10) tau_e = arrays.FloatArray(label=":math:`\tau_e`", default=numpy.array([5.0]), range=basic.Range(lo=1.0, hi=10.0, step=1.0), doc="""excitatory decay [ms]""", order=11) tau_i = arrays.FloatArray(label=":math:`\tau_i`", default=numpy.array([5.0]), range=basic.Range(lo=0.5, hi=10.0, step=0.01), doc="""inhibitory decay [ms]""", order=12) N_tot = arrays.IntegerArray(label=":math:`N_{tot}`", default=numpy.array([10000]), range=basic.Range(lo=1000, hi=50000, step=1000), doc="""cell number""", order=13) p_connect = arrays.FloatArray( label=":math:`\epsilon`", default=numpy.array([0.05]), range=basic.Range( lo=0.001, hi=0.2, step=0.001), # valid only for relatively sparse connectivities doc="""connectivity probability""", order=14) g = arrays.FloatArray( label=":math:`g`", default=numpy.array([0.2]), range=basic.Range( lo=0.01, hi=0.4, step=0.01 ), # inhibitory cell number never overcomes excitatory ones doc="""fraction of inhibitory cells""", order=15) T = arrays.FloatArray(label=":math:`T`", default=numpy.array([5.0]), range=basic.Range(lo=1., hi=20.0, step=0.1), doc="""time scale of describing network activity""", order=16) P_e = arrays.IndexArray( label= ":math:`P_e`", # TODO need to check the size of the array when it's used default=numpy.array([ -4.98e-02, 5.06e-03, -2.5e-02, 1.4e-03, -4.1e-04, 1.05e-02, -3.6e-02, 7.4e-03, 1.2e-03, -4.07e-02 ]), doc="""Polynome of excitatory phenomenological threshold (order 9)""", order=17) P_i = arrays.IndexArray( label= ":math:`P_i`", # TODO need to check the size of the array when it's used default=numpy.array([ -5.14e-02, 4.0e-03, -8.3e-03, 2.0e-04, -5.0e-04, 1.4e-03, -1.46e-02, 4.5e-03, 2.8e-03, -1.53e-02 ]), doc="""Polynome of inhibitory phenomenological threshold (order 9)""", order=18) external_input_ex_ex = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.0001]), range=basic.Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""", order=19) external_input_ex_in = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), range=basic.Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""", order=20) external_input_in_ex = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), range=basic.Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""", order=21) external_input_in_in = arrays.FloatArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), range=basic.Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""", order=22) # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "E": numpy.array( [0.0, 0.1]), # actually the 100Hz should be replaced by 1/T_refrac "I": numpy.array([0.0, 0.1]), "W": numpy.array([0.0, 100.0]) }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random initial conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.\n E: firing rate of excitatory population in KHz\n I: firing rate of inhibitory population in KHz\N W: level of adaptation """, order=23) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["E", "I", "W"], default=["E"], select_multiple=True, doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`E = 0`, :math:`I = 1` and :math:`W = 2`.""", order=24) state_variables = 'E I W'.split() _nvar = 3 cvar = numpy.array([0, 1, 2], dtype=numpy.int32) 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 = state_variables[2, :] 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 # Excitatory firing rate derivation derivative[0] = (self.TF_excitatory( E + c_0 + lc_E + self.external_input_ex_ex, I + lc_I + self.external_input_ex_in, W) - E) / self.T # Inhibitory firing rate derivation derivative[1] = ( self.TF_inhibitory(E + lc_E + self.external_input_in_ex, I + lc_I + self.external_input_in_in, W) - I) / self.T # Adaptation derivative[2] = -W / self.tau_w + self.b * E return derivative def TF_excitatory(self, fe, fi, W): """ transfer function for excitatory population :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param W: level of adaptation :return: result of transfer function """ return self.TF(fe, fi, W, self.P_e, self.E_L_e) def TF_inhibitory(self, fe, fi, W): """ transfer function for inhibitory population :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param W: level of adaptation :return: result of transfer function """ return self.TF(fe, fi, W, self.P_i, self.E_L_i) def TF(self, fe, fi, W, P, E_L): """ transfer function for inhibitory population Inspired from the next repository : https://github.com/yzerlaut/notebook_papers/tree/master/modeling_mesoscopic_dynamics :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param W: level of adaptation :param P: Polynome of neurons phenomenological threshold (order 9) :param E_L: leak reversal potential :return: result of transfer function """ mu_V, sigma_V, T_V = self.get_fluct_regime_vars( fe, fi, W, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, E_L, self.N_tot, self.p_connect, self.g) V_thre = self.threshold_func(mu_V, sigma_V, T_V * self.g_L / self.C_m, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]) V_thre *= 1e3 # the threshold need to be in mv and not in Volt f_out = self.estimate_firing_rate(mu_V, sigma_V, T_V, V_thre) return f_out @staticmethod def get_fluct_regime_vars(Fe, Fi, W, Q_e, tau_e, E_e, Q_i, tau_i, E_i, g_L, C_m, E_L, N_tot, p_connect, g): """ Compute the mean characteristic of neurons. Inspired from the next repository : https://github.com/yzerlaut/notebook_papers/tree/master/modeling_mesoscopic_dynamics :param Fe: firing rate of excitatory population :param Fi: firing rate of inhibitory population :param W: level of adaptation :param Q_e: excitatory quantal conductance :param tau_e: excitatory decay :param E_e: excitatory reversal potential :param Q_i: inhibitory quantal conductance :param tau_i: inhibitory decay :param E_i: inhibitory reversal potential :param E_L: leakage reversal voltage of neurons :param g_L: leak conductance :param C_m: membrane capacitance :param E_L: leak reversal potential :param N_tot: cell number :param p_connect: connectivity probability :param g: fraction of inhibitory cells :return: mean and variance of membrane voltage of neurons and autocorrelation time constant """ # firing rate # 1e-6 represent spontaneous release of synaptic neurotransmitter or some intrinsic currents of neurons fe = (Fe + 1e-6) * (1. - g) * p_connect * N_tot fi = (Fi + 1e-6) * g * p_connect * N_tot # conductance fluctuation and effective membrane time constant mu_Ge, mu_Gi = Q_e * tau_e * fe, Q_i * tau_i * fi # Eqns 5 from [MV_2018] mu_G = g_L + mu_Ge + mu_Gi # Eqns 6 from [MV_2018] T_m = C_m / mu_G # Eqns 6 from [MV_2018] # membrane potential mu_V = (mu_Ge * E_e + mu_Gi * E_i + g_L * E_L - W) / mu_G # Eqns 7 from [MV_2018] # post-synaptic membrane potential event s around muV U_e, U_i = Q_e / mu_G * (E_e - mu_V), Q_i / mu_G * (E_i - mu_V) # Standard deviation of the fluctuations # Eqns 8 from [MV_2018] sigma_V = numpy.sqrt(fe * (U_e * tau_e)**2 / (2. * (tau_e + T_m)) + fi * (U_i * tau_i)**2 / (2. * (tau_i + T_m))) # Autocorrelation-time of the fluctuations Eqns 9 from [MV_2018] T_V_numerator = (fe * (U_e * tau_e)**2 + fi * (U_i * tau_i)**2) T_V_denominator = (fe * (U_e * tau_e)**2 / (tau_e + T_m) + fi * (U_i * tau_i)**2 / (tau_i + T_m)) T_V = numpy.divide(T_V_numerator, T_V_denominator, out=numpy.ones_like(T_V_numerator), where=T_V_denominator != 0.0) return mu_V, sigma_V, T_V @staticmethod def threshold_func(muV, sigmaV, TvN, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9): """ The threshold function of the neurons :param muV: mean of membrane voltage :param sigmaV: variance of membrane voltage :param TvN: autocorrelation time constant :param P: Fitted coefficients of the transfer functions :return: threshold of neurons """ # Normalization factors page 48 after the equation 4 from [ZD_2018] muV0, DmuV0 = -60.0, 10.0 sV0, DsV0 = 4.0, 6.0 TvN0, DTvN0 = 0.5, 1. V = (muV - muV0) / DmuV0 S = (sigmaV - sV0) / DsV0 T = (TvN - TvN0) / DTvN0 # Eqns 11 from [MV_2018] return P0 + P1 * V + P2 * S + P3 * T + P4 * V**2 + P5 * S**2 + P6 * T**2 + P7 * V * S + P8 * V * T + P9 * S * T @staticmethod def estimate_firing_rate(muV, sigmaV, Tv, Vthre): # Eqns 10 from [MV_2018] return sp_spec.erfc( (Vthre - muV) / (numpy.sqrt(2) * sigmaV)) / (2 * Tv)
class Zerlaut_adaptation_second_order(Zerlaut_adaptation_first_order): r""" **References**: .. [ZD_2018] Zerlaut, Y., Chemla, S., Chavane, F. et al. *Modeling mesoscopic cortical dynamics using a mean-field model of conductance-based networks of adaptive exponential integrate-and-fire neurons*, J Comput Neurosci (2018) 44: 45. https://doi-org.lama.univ-amu.fr/10.1007/s10827-017-0668-2 .. [MV_2018] Matteo di Volo, Alberto Romagnoni, Cristiano Capone, Alain Destexhe (2018) *Mean-field model for the dynamics of conductance-based networks of excitatory and inhibitory spiking neurons with adaptation*, bioRxiv, doi: https://doi.org/10.1101/352393 Used Eqns 4 from [MV_2018]_ in ``dfun``. (See Zerlaut_adaptation_first_order for the default value) The models (:math:`E`, :math:`I`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. automethod:: Zerlaut_adaptation_second_order.__init__ The general formulation for the \textit{\textbf{Zerlaut_adaptation_second_order}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. 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. """ _ui_name = "Zerlaut_adaptation_second_order" # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "E": numpy.array( [0.0, 0.1]), # actually the 100Hz should be replaced by 1/T_refrac "I": numpy.array([0.0, 0.1]), "C_ee": numpy.array([0.0, 0.0]), # variance is positive or null "C_ei": numpy.array([0.0, 0.0]), # the co-variance is in [-c_ee*c_ii,c_ee*c_ii] "C_ii": numpy.array([0.0, 0.0]), # variance is positive or null "W": numpy.array([0.0, 100.0]), }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random inital conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.\n E: firing rate of excitatory population in KHz\n I: firing rate of inhibitory population in KHz\n C_ee: the variance of the excitatory population activity \n C_ei: the covariance between the excitatory and inhibitory population activities (always symetric) \n C_ie: the variance of the inhibitory population activity \n W: level of adaptation """, order=23) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["E", "I", "C_ee", "C_ei", "C_ii"], default=["E"], select_multiple=True, doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`E = 0`, :math:`I = 1`, :math:`C_ee = 2`, :math:`C_ei = 3`, :math:`C_ii = 4` and :math:`W = 5`.""", order=24) state_variables = 'E I C_ee C_ei C_ii W'.split() _nvar = 6 cvar = numpy.array([0, 1, 2, 3, 4, 5], dtype=numpy.int32) 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. """ N_e = self.N_tot * (1 - self.g) N_i = self.N_tot * self.g E = state_variables[0, :] I = state_variables[1, :] C_ee = state_variables[2, :] C_ei = state_variables[3, :] C_ii = state_variables[4, :] W = state_variables[5, :] 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 E_input_excitatory = E + c_0 + lc_E + self.external_input_ex_ex E_input_inhibitory = E + lc_E + self.external_input_in_ex I_input_excitatory = I + lc_I + self.external_input_ex_in I_input_inhibitory = I + lc_I + self.external_input_in_in # Transfer function of excitatory and inhibitory neurons _TF_e = self.TF_excitatory(E_input_excitatory, I_input_excitatory, W) _TF_i = self.TF_inhibitory(E_input_inhibitory, I_input_inhibitory, W) # Derivatives taken numerically : use a central difference formula with spacing `dx` def _diff_fe(TF, fe, fi, W, df=1e-7): return (TF(fe + df, fi, W) - TF(fe - df, fi, W)) / (2 * df * 1e3) def _diff_fi(TF, fe, fi, W, df=1e-7): return (TF(fe, fi + df, W) - TF(fe, fi - df, W)) / (2 * df * 1e3) def _diff2_fe_fe_e(fe, fi, W, df=1e-7): TF = self.TF_excitatory return (TF(fe + df, fi, W) - 2 * _TF_e + TF(fe - df, fi, W)) / ( (df * 1e3)**2) def _diff2_fe_fe_i(fe, fi, W, df=1e-7): TF = self.TF_inhibitory return (TF(fe + df, fi, W) - 2 * _TF_i + TF(fe - df, fi, W)) / ( (df * 1e3)**2) def _diff2_fi_fe(TF, fe, fi, W, df=1e-7): return (_diff_fi(TF, fe + df, fi, W) - _diff_fi(TF, fe - df, fi, W)) / (2 * df * 1e3) def _diff2_fe_fi(TF, fe, fi, W, df=1e-7): return (_diff_fe(TF, fe, fi + df, W) - _diff_fe(TF, fe, fi - df, W)) / (2 * df * 1e3) def _diff2_fi_fi_e(fe, fi, W, df=1e-7): TF = self.TF_excitatory return (TF(fe, fi + df, W) - 2 * _TF_e + TF(fe, fi - df, W)) / ( (df * 1e3)**2) def _diff2_fi_fi_i(fe, fi, W, df=1e-7): TF = self.TF_inhibitory return (TF(fe, fi + df, W) - 2 * _TF_i + TF(fe, fi - df, W)) / ( (df * 1e3)**2) #Precompute some result _diff_fe_TF_e = _diff_fe(self.TF_excitatory, E_input_excitatory, I_input_excitatory, W) _diff_fe_TF_i = _diff_fe(self.TF_inhibitory, E_input_inhibitory, I_input_inhibitory, W) _diff_fi_TF_e = _diff_fi(self.TF_excitatory, E_input_excitatory, I_input_excitatory, W) _diff_fi_TF_i = _diff_fi(self.TF_inhibitory, E_input_inhibitory, I_input_inhibitory, W) # 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_input_excitatory, I_input_excitatory, W) + .5 * C_ei * _diff2_fe_fi(self.TF_excitatory, E_input_excitatory, I_input_excitatory, W) + .5 * C_ei * _diff2_fi_fe(self.TF_excitatory, E_input_excitatory, I_input_excitatory, W) + .5 * C_ii * _diff2_fi_fi_e(E_input_excitatory, I_input_excitatory, W)) / self.T # Inhibitory firing rate derivation derivative[1] = ( _TF_i - I + .5 * C_ee * _diff2_fe_fe_i(E_input_inhibitory, I_input_inhibitory, W) + .5 * C_ei * _diff2_fe_fi(self.TF_inhibitory, E_input_inhibitory, I_input_inhibitory, W) + .5 * C_ei * _diff2_fi_fe(self.TF_inhibitory, E_input_inhibitory, I_input_inhibitory, W) + .5 * C_ii * _diff2_fi_fi_i(E_input_inhibitory, I_input_inhibitory, W)) / 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 derivative[5] = -W / self.tau_w + self.b * E return derivative
class Zerlaut_adaptation_first_order(Model): r""" **References**: .. [ZD_2018] Zerlaut, Y., Chemla, S., Chavane, F. et al. *Modeling mesoscopic cortical dynamics using a mean-field model of conductance-based networks of adaptive exponential integrate-and-fire neurons*, J Comput Neurosci (2018) 44: 45. https://doi-org.lama.univ-amu.fr/10.1007/s10827-017-0668-2 .. [MV_2018] Matteo di Volo, Alberto Romagnoni, Cristiano Capone, Alain Destexhe (2018) *Mean-field model for the dynamics of conductance-based networks of excitatory and inhibitory spiking neurons with adaptation*, bioRxiv, doi: https://doi.org/10.1101/352393 Used Eqns 4 from [MV_2018]_ in ``dfun``. The default parameters are taken from table 1 of [ZD_2018]_, pag.47 and modify for the adaptation [MV_2018] +---------------------------+------------+ | Table 1 | +--------------+------------+------------+ |Parameter | Value | Unit | +==============+============+============+ | cellular property | +--------------+------------+------------+ | g_L | 10.00 | nS | +--------------+------------+------------+ | E_L_e | -60.00 | mV | +--------------+------------+------------+ | E_L_i | -65.00 | mV | +--------------+------------+------------+ | C_m | 200.0 | pF | +--------------+------------+------------+ | b | 60.0 | nS | +--------------+------------+------------+ | a | 4.0 | nS | +--------------+------------+------------+ | tau_w | 500.0 | ms | +--------------+------------+------------+ | T | 20.0 | ms | +--------------+------------+------------+ | synaptic properties | +--------------+------------+------------+ | E_e | 0.0 | mV | +--------------+------------+------------+ | E_i | -80.0 | mV | +--------------+------------+------------+ | Q_e | 1.0 | nS | +--------------+------------+------------+ | Q_i | 5.0 | nS | +--------------+------------+------------+ | tau_e | 5.0 | ms | +--------------+------------+------------+ | tau_i | 5.0 | ms | +--------------+------------+------------+ | numerical network | +--------------+------------+------------+ | N_tot | 10000 | | +--------------+------------+------------+ | p_connect | 5.0 % | | +--------------+------------+------------+ | g | 20.0 % | | +--------------+------------+------------+ | K_e_ext | 400 | | +--------------+------------+------------+ | K_i_ext | 0 | | +--------------+------------+------------+ |external_input| 0.000 | Hz | +--------------+------------+------------+ The default coefficients of the transfert function are taken from table I of [MV_2018]_, pag.49 +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | excitatory cell | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | -4.98e-02 | 5.06e-03 | -2.5e-02 | 1.4e-03 | -4.1e-04 | 1.05e-02 | -3.6e-02 | 7.4e-03 | 1.2e-03 | -4.07e-02 | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | inhibitory cell | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ | -5.14e-02 | 4.0e-03 | -8.3e-03 | 2.0e-04 | -5.0e-04 | 1.4e-03 | -1.46e-02 | 4.5e-03 | 2.8e-03 | -1.53e-02 | +-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+-------------+ The models (:math:`E`, :math:`I`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. automethod:: Zerlaut_adaptation_first_order.__init__ The general formulation for the \textit{\textbf{Zerlaut_adaptation_first_order}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: T\dot{E}_k &= F_e-E_k \\ T\dot{I}_k &= F_i-I_k \\ dot{W}_k &= W_k/tau_w-b*E_k \\ F_\lambda = Erfc(V^{eff}_{thre}-\mu_V/\sqrt(2)\sigma_V) """ _ui_name = "Zerlaut_adaptation_first_order" ui_configurable_parameters = [ 'g_L', 'E_L_e', 'E_L_i', 'C_m', 'b', 'tau_w', 'E_e', 'E_i', 'Q_e', 'Q_i', 'tau_e', 'tau_i', 'N_tot', 'p_connect', 'g', 'T', 'external_input' ] # Define traited attributes for this model, these represent possible kwargs. g_L = NArray( label=":math:`g_{L}`", default=numpy.array( [10.]), # 10 nS by default, i.e. ~ 100MOhm input resitance at rest domain=Range( lo=0.1, hi=100.0, step=0.1 ), # 0.1nS would be a very small cell, 100nS a very big one doc="""leak conductance [nS]""") E_L_e = NArray( label=":math:`E_{L}`", default=numpy.array([-65.0]), domain=Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential for excitatory [mV]""") E_L_i = NArray( label=":math:`E_{L}`", default=numpy.array([-65.0]), domain=Range( lo=-90.0, hi=-60.0, step=0.1), # resting potential, usually between -85mV and -65mV doc="""leak reversal potential for inhibitory [mV]""") # N.B. Not independent of g_L, C_m should scale linearly with g_L C_m = NArray( label=":math:`C_{m}`", default=numpy.array([200.0]), domain=Range(lo=10.0, hi=500.0, step=10.0), # 20pF very small cell, 400pF very doc="""membrane capacitance [pF]""") b_e = NArray(label=":math:`Excitatory b`", default=numpy.array([60.0]), domain=Range(lo=0.0, hi=150.0, step=1.0), doc="""Excitatory adaptation current increment [pA]""") a_e = NArray(label=":math:`Excitatory a`", default=numpy.array([4.0]), domain=Range(lo=0.0, hi=20.0, step=0.1), doc="""Excitatory adaptation conductance [nS]""") b_i = NArray(label=":math:`Inhibitory b`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=100.0, step=0.1), doc="""Inhibitory adaptation current increment [pA]""") a_i = NArray(label=":math:`Inhibitory a`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=20.0, step=0.1), doc="""Inhibitory adaptation conductance [nS]""") tau_w_e = NArray( label=":math:`tau_w_e`", default=numpy.array([500.0]), domain=Range(lo=1.0, hi=1000.0, step=1.0), doc="""Adaptation time constant of excitatory neurons [ms]""") tau_w_i = NArray( label=":math:`tau_w_e`", default=numpy.array([1.0]), domain=Range(lo=1.0, hi=1000.0, step=1.0), doc="""Adaptation time constant of inhibitory neurons [ms]""") E_e = NArray(label=r":math:`E_e`", default=numpy.array([0.0]), domain=Range(lo=-20., hi=20., step=0.01), doc="""excitatory reversal potential [mV]""") E_i = NArray(label=":math:`E_i`", default=numpy.array([-80.0]), domain=Range(lo=-100.0, hi=-60.0, step=1.0), doc="""inhibitory reversal potential [mV]""") Q_e = NArray(label=r":math:`Q_e`", default=numpy.array([1.5]), domain=Range(lo=0.0, hi=5.0, step=0.1), doc="""excitatory quantal conductance [nS]""") Q_i = NArray(label=r":math:`Q_i`", default=numpy.array([5.0]), domain=Range(lo=0.0, hi=10.0, step=0.1), doc="""inhibitory quantal conductance [nS]""") tau_e = NArray(label=":math:`\tau_e`", default=numpy.array([5.0]), domain=Range(lo=1.0, hi=10.0, step=1.0), doc="""excitatory decay [ms]""") tau_i = NArray(label=":math:`\tau_i`", default=numpy.array([5.0]), domain=Range(lo=0.5, hi=10.0, step=0.01), doc="""inhibitory decay [ms]""") N_tot = NArray(dtype=numpy.int, label=":math:`N_{tot}`", default=numpy.array([10000]), domain=Range(lo=1000, hi=50000, step=1000), doc="""cell number""") p_connect_e = NArray( label=":math:`\epsilon`", default=numpy.array([0.05]), domain=Range( lo=0.001, hi=0.2, step=0.001), # valid only for relatively sparse connectivities doc="""connectivity probability""") p_connect_i = NArray( label=":math:`\epsilon`", default=numpy.array([0.05]), domain=Range( lo=0.001, hi=0.2, step=0.001), # valid only for relatively sparse connectivities doc="""connectivity probability""") g = NArray( label=":math:`g`", default=numpy.array([0.2]), domain=Range( lo=0.01, hi=0.4, step=0.01 ), # inhibitory cell number never overcomes excitatory ones doc="""fraction of inhibitory cells""") K_ext_e = NArray( dtype=numpy.int, label=":math:`K_ext_e`", default=numpy.array([400]), domain=Range( lo=0, hi=10000, step=1), # inhibitory cell number never overcomes excitatory ones doc="""Number of excitatory connexions from external population""") K_ext_i = NArray( dtype=numpy.int, label=":math:`K_ext_i`", default=numpy.array([0]), domain=Range( lo=0, hi=10000, step=1), # inhibitory cell number never overcomes excitatory ones doc="""Number of inhibitory connexions from external population""") T = NArray(label=":math:`T`", default=numpy.array([20.0]), domain=Range(lo=1., hi=20.0, step=0.1), doc="""time scale of describing network activity""") P_e = NArray( label= ":math:`P_e`", # TODO need to check the size of the array when it's used default=numpy.array([ -0.04983106, 0.005063550882777035, -0.023470121807314552, 0.0022951513725067503, -0.0004105302652029825, 0.010547051343547399, -0.03659252821136933, 0.007437487505797858, 0.001265064721846073, -0.04072161294490446 ]), doc="""Polynome of excitatory phenomenological threshold (order 9)""") P_i = NArray( label= ":math:`P_i`", # TODO need to check the size of the array when it's used default=numpy.array([ -0.05149122024209484, 0.004003689190271077, -0.008352013668528155, 0.0002414237992765705, -0.0005070645080016026, 0.0014345394104282397, -0.014686689498949967, 0.004502706285435741, 0.0028472190352532454, -0.015357804594594548 ]), doc="""Polynome of inhibitory phenomenological threshold (order 9)""") external_input_ex_ex = NArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), domain=Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""") external_input_ex_in = NArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), domain=Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""") external_input_in_ex = NArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), domain=Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""") external_input_in_in = NArray(label=":math:`\nu_e^{drive}`", default=numpy.array([0.000]), domain=Range(lo=0.00, hi=0.1, step=0.001), doc="""external drive""") tau_OU = NArray(label=":math:`\ntau noise`", default=numpy.array([5.0]), domain=Range(lo=0.10, hi=10.0, step=0.01), doc="""time constant noise""") weight_noise = NArray(label=":math:`\nweight noise`", default=numpy.array([10.5]), domain=Range(lo=0., hi=50.0, step=1.0), doc="""weight noise""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = Final( label="State Variable ranges [lo, hi]", default={ "E": numpy.array( [0.0, 0.0]), # actually the 100Hz should be replaced by 1/T_refrac "I": numpy.array([0.0, 0.0]), "W_e": numpy.array([0.0, 0.0]), "W_i": numpy.array([0.0, 0.0]), "noise": numpy.array([0.0, 0.0]), }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random initial conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.\n E: firing rate of excitatory population in KHz\n I: firing rate of inhibitory population in KHz\n W_e: level of adaptation of excitatory in pA\n W_i: level of adaptation of inhibitory in pA\n """) variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("E", "I", "W_e", "W_i", "noise"), default=("E", ), doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`E = 0`, :math:`I = 1` and :math:`W = 2`.""") state_variable_boundaries = Final( label="Firing rate of population is always positive", default={ "E": numpy.array([0.0, None]), "I": numpy.array([0.0, None]) }, doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""" ) state_variables = 'E I W_e W_i noise'.split() _nvar = 5 cvar = numpy.array([0], dtype=numpy.int32) 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 TF_excitatory(self, fe, fi, fe_ext, fi_ext, W): """ transfer function for excitatory population :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param fe_ext: external excitatory input :param fi_ext: external inhibitory input :param W: level of adaptation :return: result of transfer function """ return self.TF(fe, fi, fe_ext, fi_ext, W, self.P_e, self.E_L_e) def TF_inhibitory(self, fe, fi, fe_ext, fi_ext, W): """ transfer function for inhibitory population :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param fe_ext: external excitatory input :param fi_ext: external inhibitory input :param W: level of adaptation :return: result of transfer function """ return self.TF(fe, fi, fe_ext, fi_ext, W, self.P_i, self.E_L_i) def TF(self, fe, fi, fe_ext, fi_ext, W, P, E_L): """ transfer function for inhibitory population Inspired from the next repository : https://github.com/yzerlaut/notebook_papers/tree/master/modeling_mesoscopic_dynamics :param fe: firing rate of excitatory population :param fi: firing rate of inhibitory population :param fe_ext: external excitatory input :param fi_ext: external inhibitory input :param W: level of adaptation :param P: Polynome of neurons phenomenological threshold (order 9) :param E_L: leak reversal potential :return: result of transfer function """ mu_V, sigma_V, T_V = self.get_fluct_regime_vars( fe, fi, fe_ext, fi_ext, W, self.Q_e, self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L, self.C_m, E_L, self.N_tot, self.p_connect_e, self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i) V_thre = self.threshold_func(mu_V, sigma_V, T_V * self.g_L / self.C_m, P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]) V_thre *= 1e3 # the threshold need to be in mv and not in Volt f_out = self.estimate_firing_rate(mu_V, sigma_V, T_V, V_thre) return f_out @staticmethod @jit(nopython=True, cache=True) def get_fluct_regime_vars(Fe, Fi, Fe_ext, Fi_ext, W, Q_e, tau_e, E_e, Q_i, tau_i, E_i, g_L, C_m, E_L, N_tot, p_connect_e, p_connect_i, g, K_ext_e, K_ext_i): """ Compute the mean characteristic of neurons. Inspired from the next repository : https://github.com/yzerlaut/notebook_papers/tree/master/modeling_mesoscopic_dynamics :param Fe: firing rate of excitatory population :param Fi: firing rate of inhibitory population :param Fe_ext: external excitatory input :param Fi_ext: external inhibitory input :param W: level of adaptation :param Q_e: excitatory quantal conductance :param tau_e: excitatory decay :param E_e: excitatory reversal potential :param Q_i: inhibitory quantal conductance :param tau_i: inhibitory decay :param E_i: inhibitory reversal potential :param E_L: leakage reversal voltage of neurons :param g_L: leak conductance :param C_m: membrane capacitance :param E_L: leak reversal potential :param N_tot: cell number :param p_connect_e: connectivity probability of excitatory neurons :param p_connect_i: connectivity probability of inhibitory neurons :param g: fraction of inhibitory cells :return: mean and variance of membrane voltage of neurons and autocorrelation time constant """ # firing rate # 1e-6 represent spontaneous release of synaptic neurotransmitter or some intrinsic currents of neurons fe = (Fe + 1.0e-6) * (1. - g) * p_connect_e * N_tot + Fe_ext * K_ext_e fi = (Fi + 1.0e-6) * g * p_connect_i * N_tot + Fi_ext * K_ext_i # conductance fluctuation and effective membrane time constant mu_Ge, mu_Gi = Q_e * tau_e * fe, Q_i * tau_i * fi # Eqns 5 from [MV_2018] mu_G = g_L + mu_Ge + mu_Gi # Eqns 6 from [MV_2018] T_m = C_m / mu_G # Eqns 6 from [MV_2018] # membrane potential mu_V = (mu_Ge * E_e + mu_Gi * E_i + g_L * E_L - W) / mu_G # Eqns 7 from [MV_2018] # post-synaptic membrane potential event s around muV U_e, U_i = Q_e / mu_G * (E_e - mu_V), Q_i / mu_G * (E_i - mu_V) # Standard deviation of the fluctuations # Eqns 8 from [MV_2018] sigma_V = numpy.sqrt(fe * (U_e * tau_e)**2 / (2. * (tau_e + T_m)) + fi * (U_i * tau_i)**2 / (2. * (tau_i + T_m))) # Autocorrelation-time of the fluctuations Eqns 9 from [MV_2018] T_V_numerator = (fe * (U_e * tau_e)**2 + fi * (U_i * tau_i)**2) T_V_denominator = (fe * (U_e * tau_e)**2 / (tau_e + T_m) + fi * (U_i * tau_i)**2 / (tau_i + T_m)) # T_V = numpy.divide(T_V_numerator, T_V_denominator, out=numpy.ones_like(T_V_numerator), # where=T_V_denominator != 0.0) # avoid numerical error but not use with numba T_V = T_V_numerator / T_V_denominator return mu_V, sigma_V, T_V @staticmethod @jit(nopython=True, cache=True) def threshold_func(muV, sigmaV, TvN, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9): """ The threshold function of the neurons :param muV: mean of membrane voltage :param sigmaV: variance of membrane voltage :param TvN: autocorrelation time constant :param P: Fitted coefficients of the transfer functions :return: threshold of neurons """ # Normalization factors page 48 after the equation 4 from [ZD_2018] muV0, DmuV0 = -60.0, 10.0 sV0, DsV0 = 4.0, 6.0 TvN0, DTvN0 = 0.5, 1. V = (muV - muV0) / DmuV0 S = (sigmaV - sV0) / DsV0 T = (TvN - TvN0) / DTvN0 # Eqns 11 from [MV_2018] return P0 + P1 * V + P2 * S + P3 * T + P4 * V**2 + P5 * S**2 + P6 * T**2 + P7 * V * S + P8 * V * T + P9 * S * T @staticmethod def estimate_firing_rate(muV, sigmaV, Tv, Vthre): """ The threshold function of the neurons :param muV: mean of membrane voltage :param sigmaV: variance of membrane voltage :param Tv: autocorrelation time constant :param Vthre:threshold of neurons """ # Eqns 10 from [MV_2018] return sp_spec.erfc( (Vthre - muV) / (numpy.sqrt(2) * sigmaV)) / (2 * Tv)
class ReducedWongWangExcIOInhI(ModelNumbaDfun): r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2014] Deco Gustavo, Ponce Alvarez Adrian, Patric Hagmann, Gian Luca Romani, Dante Mantini, and Maurizio Corbetta. *How Local Excitation–Inhibition Ratio Impacts the Whole Brain Dynamics*. The Journal of Neuroscience 34(23), 7886 –7898, 2014. .. automethod:: ReducedWongWang.__init__ 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}) \, """ _ui_name = "Reduced Wong-Wang" ui_configurable_parameters = [ 'a_e', 'b_e', 'd_e', 'gamma_e', 'tau_e', 'W_e', 'w_p', 'J_N', "R_e", 'a_i', 'b_i', 'd_i', 'gamma_i', 'tau_i', 'W_i', 'J_i', "R_i", 'I_o', 'G', 'lamda' ] # Define traited attributes for this model, these represent possible kwargs. R_e = NArray(label=":math:`R_e`", default=numpy.array([ -1., ]), domain=Range(lo=-1., hi=10000., step=1.), doc="[Hz]. Excitatory population firing rate.") a_e = NArray( label=":math:`a_e`", default=numpy.array([ 310., ]), domain=Range(lo=0., hi=500., step=1.), doc= "[n/C]. Excitatory population input gain parameter, chosen to fit numerical solutions." ) b_e = NArray( label=":math:`b_e`", default=numpy.array([ 125., ]), domain=Range(lo=0., hi=200., step=1.), doc= "[Hz]. Excitatory population input shift parameter chosen to fit numerical solutions." ) d_e = NArray( label=":math:`d_e`", default=numpy.array([ 0.160, ]), domain=Range(lo=0.0, hi=0.2, step=0.001), doc= """[s]. Excitatory population input scaling parameter chosen to fit numerical solutions.""" ) gamma_e = NArray(label=r":math:`\gamma_e`", default=numpy.array([ 0.641 / 1000, ]), domain=Range(lo=0.0, hi=1.0 / 1000, step=0.01 / 1000), doc="""Excitatory population kinetic parameter""") tau_e = NArray( label=r":math:`\tau_e`", default=numpy.array([ 100., ]), domain=Range(lo=10., hi=150., step=1.), doc="""[ms]. Excitatory population NMDA decay time constant.""") w_p = NArray(label=r":math:`w_p`", default=numpy.array([ 1.4, ]), domain=Range(lo=0.0, hi=2.0, step=0.01), doc="""Excitatory population recurrence weight""") J_N = NArray(label=r":math:`J_{N}`", default=numpy.array([ 0.15, ]), domain=Range(lo=0.001, hi=0.5, step=0.001), doc="""[nA] NMDA current""") W_e = NArray(label=r":math:`W_e`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.0, hi=2.0, step=0.01), doc="""Excitatory population external input scaling weight""") R_i = NArray(label=":math:`R_i`", default=numpy.array([ -1., ]), domain=Range(lo=-1., hi=10000., step=1.), doc="[Hz]. Inhibitory population firing rate.") a_i = NArray( label=":math:`a_i`", default=numpy.array([ 615., ]), domain=Range(lo=0., hi=1000., step=1.), doc= "[n/C]. Inhibitory population input gain parameter, chosen to fit numerical solutions." ) b_i = NArray( label=":math:`b_i`", default=numpy.array([ 177.0, ]), domain=Range(lo=0.0, hi=200.0, step=1.0), doc= "[Hz]. Inhibitory population input shift parameter chosen to fit numerical solutions." ) d_i = NArray( label=":math:`d_i`", default=numpy.array([ 0.087, ]), domain=Range(lo=0.0, hi=0.2, step=0.001), doc= """[s]. Inhibitory population input scaling parameter chosen to fit numerical solutions.""" ) gamma_i = NArray(label=r":math:`\gamma_i`", default=numpy.array([ 1.0 / 1000, ]), domain=Range(lo=0.0, hi=2.0 / 1000, step=0.01 / 1000), doc="""Inhibitory population kinetic parameter""") tau_i = NArray( label=r":math:`\tau_i`", default=numpy.array([ 10., ]), domain=Range(lo=5., hi=100., step=1.0), doc="""[ms]. Inhibitory population NMDA decay time constant.""") J_i = NArray(label=r":math:`J_{i}`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.001, hi=2.0, step=0.001), doc="""[nA] Local inhibitory current""") W_i = NArray(label=r":math:`W_i`", default=numpy.array([ 0.7, ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Inhibitory population external input scaling weight""") I_o = NArray(label=":math:`I_{o}`", default=numpy.array([ 0.382, ]), domain=Range(lo=0.0, hi=1.0, step=0.001), doc="""[nA]. Effective external input""") # r_o = NArray( # label=":math:`r_o`", # default=numpy.array([0., ]), # domain=Range(lo=0., hi=10000., step=1.), # doc="[Hz]. Excitatory population output firing rate.") G = NArray(label=":math:`G`", default=numpy.array([ 2.0, ]), domain=Range(lo=0.0, hi=10.0, step=0.01), doc="""Global coupling scaling""") lamda = NArray(label=":math:`\lambda`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Inhibitory global coupling scaling""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={ "S_e": numpy.array([0.0, 1.0]), "S_i": numpy.array([0.0, 1.0]), "R_e": numpy.array([0.0, None]), "R_i": numpy.array([0.0, None]) }, label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") state_variable_range = Final(default={ "S_e": numpy.array([0.0, 1.0]), "S_i": numpy.array([0.0, 1.0]), "R_e": numpy.array([0.0, 1000.0]), "R_i": numpy.array([0.0, 1000.0]) }, label="State variable ranges [lo, hi]", doc="Population firing rate") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=('S_e', 'S_i', 'R_e', 'R_i'), default=('S_e', 'S_i', 'R_e', 'R_i'), doc="""default state variables to be monitored""") state_variables = ['S_e', 'S_i', 'R_e', 'R_i'] _nvar = 4 cvar = numpy.array([0], dtype=numpy.int32) def configure(self): """ """ super(ReducedWongWangExcIOInhI, self).configure() self.update_derived_parameters() 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, update_non_state_variables=True): 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}) \, """ if update_non_state_variables: state_variables = \ self.update_non_state_variables(state_variables, coupling, local_coupling, use_numba=False) S = state_variables[:2, :] # synaptic gating dynamics R = state_variables[2:, :] # rates 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 are non-state variables: dummy = 0.0 * dS_e derivative = numpy.array([dS_e, dS_i, dummy, dummy]) return derivative def dfun(self, x, c, local_coupling=0.0, update_non_state_variables=True): if update_non_state_variables: self.update_non_state_variables(x, c, local_coupling, use_numba=True) deriv = _numba_dfun( x.reshape(x.shape[:-1]).T, self.gamma_e, self.tau_e, self.gamma_i, self.tau_i) return deriv.T[..., numpy.newaxis]
class ReducedWongWangExcIO(TVBReducedWongWang): r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2013] Deco Gustavo, Ponce Alvarez Adrian, Dante Mantini, Gian Luca Romani, Patric Hagmann, and Maurizio Corbetta. *Resting-State Functional Connectivity Emerges from Structurally and Dynamically Shaped Slow Linear Fluctuations*. The Journal of Neuroscience 33(27), 11239 –11252, 2013. .. automethod:: ReducedWongWangExcIO.__init__ 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}) \, """ # Define traited attributes for this model, these represent possible kwargs. a = NArray( label=":math:`a`", default=numpy.array([ 270., ]), domain=Range(lo=0.0, hi=0.270, step=0.01), doc="[n/C]. Input gain parameter, chosen to fit numerical solutions.") b = NArray( label=":math:`b`", default=numpy.array([ 108., ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="[Hz]. Input shift parameter chosen to fit numerical solutions.") d = NArray(label=":math:`d`", default=numpy.array([ 0.154, ]), domain=Range(lo=0.0, hi=0.200, step=0.001), doc="""[s]. Parameter chosen to fit numerical solutions.""") gamma = NArray(label=r":math:`\gamma`", default=numpy.array([ 0.641 / 1000, ]), domain=Range(lo=0.0, hi=1.0 / 1000, step=0.01 / 1000), doc="""Kinetic parameter""") tau_s = NArray(label=r":math:`\tau_S`", default=numpy.array([ 100., ]), domain=Range(lo=1.0, hi=150.0, step=1.0), doc="""[ms]. NMDA decay time constant.""") w = NArray(label=r":math:`w`", default=numpy.array([ 0.9, ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Excitatory recurrence""") J_N = NArray(label=r":math:`J_{N}`", default=numpy.array([ 0.2609, ]), domain=Range(lo=0.0000, hi=0.5, step=0.0001), doc="""Excitatory recurrence""") I_o = NArray(label=":math:`I_{o}`", default=numpy.array([ 0.3, ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""[nA] Effective external input""") G = NArray(label=":math:`G`", default=numpy.array([ 2.0, ]), domain=Range(lo=0.0, hi=10.0, step=0.01), doc="""Global coupling scaling""") sigma_noise = NArray( label=r":math:`\sigma_{noise}`", default=numpy.array([ 0.000000001, ]), domain=Range(lo=0.0, hi=0.005, step=0.0001), doc="""[nA] Noise amplitude. Take this value into account for stochatic integration schemes.""") tau_rin = NArray( label=r":math:`\tau_rin_e`", default=numpy.array([ 100., ]), domain=Range(lo=1., hi=100., step=1.0), doc= """[ms]. Excitatory population instant spiking rate time constant.""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={ "S": numpy.array([0.0, 1.0]), "Rint": numpy.array([0.0, None]), "R": numpy.array([0.0, None]), "Rin": numpy.array([0.0, None]), "I": numpy.array([None, None]) }, label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") state_variable_range = Final(default={ "S": numpy.array([0.0, 1.0]), "Rint": numpy.array([0.0, 1000.0]), "R": numpy.array([0.0, 1000.0]), "Rin": numpy.array([0.0, 1000.0]), "I": numpy.array([0.0, 2.0]) }, label="State variable ranges [lo, hi]", doc="Population firing rate") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=('S', 'Rint', 'R', 'Rin', 'I'), default=('S', 'Rint', 'R', 'Rin', 'I'), doc="""default state variables to be monitored""") state_variables = ['S', 'Rint', 'R', 'Rin', 'I'] non_integrated_variables = ['R', 'Rin', 'I'] _nvar = 5 cvar = numpy.array([0], dtype=numpy.int32) _R = None _Rin = None _stimulus = 0.0 use_numba = True def update_derived_parameters(self): """ When needed, this should be a method for calculating parameters that are calculated based on paramaters directly set by the caller. For example, see, ReducedSetFitzHughNagumo. When not needed, this pass simplifies code that updates an arbitrary models parameters -- ie, this can be safely called on any model, whether it's used or not. """ self.n_nonintvar = self.nvar - self.nintvar self._R = None self._Rin = None self._stimulus = 0.0 if hasattr(self, "Rin"): setattr(self, "_Rin_mask", getattr(self, "Rin") > 0) else: setattr(self, "Rin", numpy.array([ 0.0, ])) setattr(self, "_Rin_mask", numpy.array([ False, ])) 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, 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_mask, # Downscale rates coming from the Spiking Network numpy.where( Rint < 40, numpy.sqrt(Rint), # Low activity scaling 0.0000050 * self.G * Rint**2), # High activity scaling, x / (1 - numpy.exp(-self.d * x))) Rin = numpy.where( self._Rin_mask, 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 _integration_to_state_variables(self, integration_variables): return numpy.array(integration_variables.tolist() + [0.0 * integration_variables[0]] * self.n_nonintvar) 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 dfun(self, x, c, local_coupling=0.0): if self._R is None or self._Rin is None: state_variables = self._integration_to_state_variables(x) state_variables = \ self.update_state_variables_before_integration(state_variables, c, local_coupling, self._stimulus) R = state_variables[2] # Rates Rin = state_variables[3] # input instant spiking rates else: R = self._R Rin = self._Rin if self.use_numba: deriv = _numba_dfun( x.reshape(x.shape[:-1]).T, R, Rin, self.gamma, self.tau_s, self.Rin, self.tau_rin) deriv = deriv.T[..., numpy.newaxis] else: deriv = self._numpy_dfun(x, R, Rin) # Set them to None so that they are recomputed on subsequent steps # for multistep integration schemes such as Runge-Kutta: self._R = None self._Rin = None return deriv
def _integration_to_state_variables(self, integration_variables): return numpy.array(integration_variables.tolist() + [0.0 * integration_variables[0]] * self.n_nonintvar)
class ReducedWongWangExcIOInhI(TVBReducedWongWangExcInh): r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2014] Deco Gustavo, Ponce Alvarez Adrian, Patric Hagmann, Gian Luca Romani, Dante Mantini, and Maurizio Corbetta. *How Local Excitation–Inhibition Ratio Impacts the Whole Brain Dynamics*. The Journal of Neuroscience 34(23), 7886 –7898, 2014. .. automethod:: ReducedWongWangExcIOInhI.__init__ Equations taken from [DPA_2014]_ .. 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}) \, """ # Define traited attributes for this model, these represent possible kwargs. tau_rin_e = NArray( label=r":math:`\tau_rin_e`", default=numpy.array([ 1., ]), domain=Range(lo=1., hi=100., step=1.0), doc= """[ms]. Excitatory population instant spiking rate time constant.""") tau_rin_i = NArray( label=r":math:`\tau_rin_i`", default=numpy.array([ 1., ]), domain=Range(lo=1., hi=100., step=1.0), doc= """[ms]. Inhibitory population instant spiking rate time constant.""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={ "S_e": numpy.array([0.0, 1.0]), "S_i": numpy.array([0.0, 1.0]), "R_e": numpy.array([0.0, None]), "R_i": numpy.array([0.0, None]), "Rin_e": numpy.array([0.0, None]), "Rin_i": numpy.array([0.0, None]), "I_e": numpy.array([None, None]), "I_i": numpy.array([None, None]) }, label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") state_variable_range = Final(default={ "S_e": numpy.array([0.0, 1.0]), "S_i": numpy.array([0.0, 1.0]), "R_e": numpy.array([0.0, 1000.0]), "R_i": numpy.array([0.0, 1000.0]), "Rin_e": numpy.array([0.0, 1000.0]), "Rin_i": numpy.array([0.0, 1000.0]), "I_e": numpy.array([0.0, 2.0]), "I_i": numpy.array([0.0, 1.0]) }, label="State variable ranges [lo, hi]", doc="Population firing rate") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=('S_e', 'S_i', 'R_e', 'R_i', 'Rin_e', 'Rin_i', 'I_e', 'I_i'), default=('S_e', 'S_i', 'R_e', 'R_i', 'Rin_e', 'Rin_i', 'I_e', 'I_i'), doc="""default state variables to be monitored""") state_variables = [ 'S_e', 'S_i', 'R_e', 'R_i', 'Rin_e', 'Rin_i', 'I_e', 'I_i' ] non_integrated_variables = ['Rin_e', 'Rin_i', 'I_e', 'I_i'] _nvar = 8 cvar = numpy.array([0], dtype=numpy.int32) _Rin = None _stimulus = 0.0 use_numba = True def update_derived_parameters(self): """ When needed, this should be a method for calculating parameters that are calculated based on paramaters directly set by the caller. For example, see, ReducedSetFitzHughNagumo. When not needed, this pass simplifies code that updates an arbitrary models parameters -- ie, this can be safely called on any model, whether it's used or not. """ self.n_nonintvar = self.nvar - self.nintvar self._Rin = None self._stimulus = 0.0 for var in ["Rin_e", "Rin_i"]: if hasattr(self, var): setattr(self, "_%s_mask" % var, getattr(self, var) > 0) else: setattr(self, var, numpy.array([ 0.0, ])) setattr(self, "_%s_mask" % var, numpy.array([ False, ])) 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 def _integration_to_state_variables(self, integration_variables): return numpy.array(integration_variables.tolist() + [0.0 * integration_variables[0]] * self.n_nonintvar) 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 dfun(self, x, c, local_coupling=0.0): if self._Rin is None: state_variables = self._integration_to_state_variables(x) state_variables = \ self.update_state_variables_before_integration(state_variables, c, local_coupling, self._stimulus) x[2:4] = state_variables[2:4] # Rates Rin = state_variables[4:6] # Exc input instant spiking rates else: Rin = self._Rin if self.use_numba: deriv = _numba_dfun( x.reshape(x.shape[:-1]).T, Rin[0], Rin[1], self.gamma_e, self.tau_e, self.Rin_e, self.tau_rin_e, self.gamma_i, self.tau_i, self.Rin_i, self.tau_rin_i).T[..., numpy.newaxis] else: deriv = self._numpy_dfun(x, Rin) # Set them to None so that they are recomputed on subsequent steps # for multistep integration schemes such as Runge-Kutta: self._Rin = None return deriv
class LinearReducedWongWangExcIO(ReducedWongWangExcIO): d = NArray(label=":math:`d`", default=numpy.array([ 0.2, ]), domain=Range(lo=0.0, hi=0.200, step=0.001), doc="""[s]. Parameter chosen to fit numerical solutions.""") non_integrated_variables = ["R", "Rin", "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 _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) + 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 dfun(self, x, c, local_coupling=0.0): if self._R is None or self._Rin is None: state_variables = self._integration_to_state_variables(x) state_variables = \ self.update_state_variables_before_integration(state_variables, c, local_coupling, self._stimulus) R = state_variables[2] # Rates Rin = state_variables[3] # input instant spiking rates else: R = self._R Rin = self._Rin if self.use_numba: deriv = _numba_dfun( x.reshape(x.shape[:-1]).T, R, Rin, self.gamma, self.tau_s, self.Rin, self.tau_rin).T[..., numpy.newaxis] else: deriv = self._numpy_dfun(x, R, Rin) # Set them to None so that they are recomputed on subsequent steps # for multistep integration schemes such as Runge-Kutta: self._R = None self._Rin = None return deriv