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, ]))
Example #4
0
 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)
Example #8
0
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)
Example #10
0
 def _region(self, x, i_region):
     try:
         return numpy.array(x[i_region])
     except:
         return numpy.array(x[0])
Example #11
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)
Example #12
0
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]
Example #13
0
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
Example #14
0
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)
Example #15
0
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
Example #16
0
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
Example #21
0
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