示例#1
0
class ReducedWongWang(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_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 32(27), 11239-11252, 2013.


    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

    """

    # Define traited attributes for this model, these represent possible kwargs.
    a = NArray(
        label=":math:`a`",
        default=numpy.array([0.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([0.108, ]),
        domain=Range(lo=0.0, hi=1.0, step=0.01),
        doc="[kHz]. Input shift parameter chosen to fit numerical solutions.")

    d = NArray(
        label=":math:`d`",
        default=numpy.array([154., ]),
        domain=Range(lo=0.0, hi=200.0, step=0.01),
        doc="""[ms]. Parameter chosen to fit numerical solutions.""")

    gamma = NArray(
        label=r":math:`\gamma`",
        default=numpy.array([0.641, ]),
        domain=Range(lo=0.0, hi=1.0, step=0.01),
        doc="""Kinetic parameter""")

    tau_s = NArray(
        label=r":math:`\tau_S`",
        default=numpy.array([100., ]),
        domain=Range(lo=50.0, hi=150.0, step=1.0),
        doc="""Kinetic parameter. NMDA decay time constant.""")

    w = NArray(
        label=r":math:`w`",
        default=numpy.array([0.6, ]),
        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.2609, hi=0.5, step=0.001),
        doc="""Excitatory recurrence""")

    I_o = NArray(
        label=":math:`I_{o}`",
        default=numpy.array([0.33, ]),
        domain=Range(lo=0.0, hi=1.0, step=0.01),
        doc="""[nA] Effective external input""")

    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.""")

    state_variable_range = Final(
        label="State variable ranges [lo, hi]",
        default={"S": numpy.array([0.0, 1.0])},
        doc="Population firing rate")

    state_variable_boundaries = Final(
        label="State Variable boundaries [lo, hi]",
        default={"S": numpy.array([0.0, 1.0])},
        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""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("S",),
        default=("S",),
        doc="""default state variables to be monitored""")

    state_variables = ['S']
    _nvar = 1
    cvar = numpy.array([0], dtype=numpy.int32)

    def configure(self):
        """  """
        super(ReducedWongWang, 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_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   = state_variables[0, :]

        c_0 = coupling[0, :]


        # if applicable
        lc_0 = local_coupling * S

        x  = self.w * self.J_N * S + self.I_o + self.J_N * c_0 + self.J_N * lc_0
        H = (self.a * x - self.b) / (1 - numpy.exp(-self.d * (self.a * x - self.b)))
        dS = - (S / self.tau_s) + (1 - S) * H * self.gamma

        derivative = numpy.array([dS])
        return derivative

    def dfun(self, x, c, local_coupling=0.0):
        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, self.b, self.d, self.gamma,
                        self.tau_s, self.w, self.J_N, self.I_o)
        return deriv.T[..., numpy.newaxis]
示例#2
0
class Epileptor(ModelNumbaDfun):
    r"""
    The Epileptor is a composite neural mass model of six dimensions which
    has been crafted to model the phenomenology of epileptic seizures.
    (see [Jirsaetal_2014]_)

    Equations and default parameters are taken from [Jirsaetal_2014]_.

          +------------------------------------------------------+
          |                         Table 1                      |
          +----------------------+-------------------------------+
          |        Parameter     |           Value               |
          +======================+===============================+
          |         I_rest1      |              3.1              |
          +----------------------+-------------------------------+
          |         I_rest2      |              0.45             |
          +----------------------+-------------------------------+
          |         r            |            0.00035            |
          +----------------------+-------------------------------+
          |         x_0          |             -1.6              |
          +----------------------+-------------------------------+
          |         slope        |              0.0              |
          +----------------------+-------------------------------+
          |             Integration parameter                    |
          +----------------------+-------------------------------+
          |           dt         |              0.1              |
          +----------------------+-------------------------------+
          |  simulation_length   |              4000             |
          +----------------------+-------------------------------+
          |                    Noise                             |
          +----------------------+-------------------------------+
          |         nsig         | [0., 0., 0., 1e-3, 1e-3, 0.]  |
          +----------------------+-------------------------------+
          |              Jirsa et al. 2014                       |
          +------------------------------------------------------+


    .. figure :: img/Epileptor_01_mode_0_pplane.svg
        :alt: Epileptor phase plane

    .. [Jirsaetal_2014] Jirsa, V. K.; Stacey, W. C.; Quilichini, P. P.;
        Ivanov, A. I.; Bernard, C. *On the nature of seizure dynamics.* Brain,
        2014.


    Variables of interest to be used by monitors: -y[0] + y[3]

        .. math::
            \dot{x_{1}} &=& y_{1} - f_{1}(x_{1}, x_{2}) - z + I_{ext1} \\
            \dot{y_{1}} &=& c - d x_{1}^{2} - y{1} \\
            \dot{z} &=&
            \begin{cases}
            r(4 (x_{1} - x_{0}) - z-0.1 z^{7}) & \text{if } x<0 \\
            r(4 (x_{1} - x_{0}) - z) & \text{if } x \geq 0
            \end{cases} \\
            \dot{x_{2}} &=& -y_{2} + x_{2} - x_{2}^{3} + I_{ext2} + 0.002 g - 0.3 (z-3.5) \\
            \dot{y_{2}} &=& 1 / \tau (-y_{2} + f_{2}(x_{2}))\\
            \dot{g} &=& -0.01 (g - 0.1 x_{1})

    where:
        .. math::
            f_{1}(x_{1}, x_{2}) =
            \begin{cases}
            a x_{1}^{3} - b x_{1}^2 & \text{if } x_{1} <0\\
            -(slope - x_{2} + 0.6(z-4)^2) x_{1} &\text{if }x_{1} \geq 0
            \end{cases}

    and:

        .. math::
            f_{2}(x_{2}) =
            \begin{cases}
            0 & \text{if } x_{2} <-0.25\\
            a_{2}(x_{2} + 0.25) & \text{if } x_{2} \geq -0.25
            \end{cases}

    Note Feb. 2017: the slow permittivity variable can be modify to account for the time
    difference between interictal and ictal states (see [Proixetal_2014]).

    .. [Proixetal_2014] Proix, T.; Bartolomei, F; Chauvel, P; Bernard, C; Jirsa, V.K. *
        Permittivity coupling across brain regions determines seizure recruitment in
        partial epilepsy.* J Neurosci 2014, 34:15009-21.

    """

    a = NArray(label=":math:`a`",
               default=numpy.array([1.0]),
               doc="Coefficient of the cubic term in the first state variable")

    b = NArray(
        label=":math:`b`",
        default=numpy.array([3.0]),
        doc="Coefficient of the squared term in the first state variabel")

    c = NArray(label=":math:`c`",
               default=numpy.array([1.0]),
               doc="Additive coefficient for the second state variable, \
        called :math:`y_{0}` in Jirsa paper")

    d = NArray(
        label=":math:`d`",
        default=numpy.array([5.0]),
        doc="Coefficient of the squared term in the second state variable")

    r = NArray(label=":math:`r`",
               domain=Range(lo=0.0, hi=0.001, step=0.00005),
               default=numpy.array([0.00035]),
               doc="Temporal scaling in the third state variable, \
        called :math:`1/\\tau_{0}` in Jirsa paper")

    s = NArray(label=":math:`s`",
               default=numpy.array([4.0]),
               doc="Linear coefficient in the third state variable")

    x0 = NArray(label=":math:`x_0`",
                domain=Range(lo=-3.0, hi=-1.0, step=0.1),
                default=numpy.array([-1.6]),
                doc="Epileptogenicity parameter")

    Iext = NArray(label=":math:`I_{ext}`",
                  domain=Range(lo=1.5, hi=5.0, step=0.1),
                  default=numpy.array([3.1]),
                  doc="External input current to the first population")

    slope = NArray(label=":math:`slope`",
                   domain=Range(lo=-16.0, hi=6.0, step=0.1),
                   default=numpy.array([0.]),
                   doc="Linear coefficient in the first state variable")

    Iext2 = NArray(label=":math:`I_{ext2}`",
                   domain=Range(lo=0.0, hi=1.0, step=0.05),
                   default=numpy.array([0.45]),
                   doc="External input current to the second population")

    tau = NArray(label=":math:`\\tau`",
                 default=numpy.array([10.0]),
                 doc="Temporal scaling coefficient in fifth state variable")

    aa = NArray(label=":math:`aa`",
                default=numpy.array([6.0]),
                doc="Linear coefficient in fifth state variable")

    bb = NArray(
        label=":math:`bb`",
        default=numpy.array([2.0]),
        doc=
        "Linear coefficient of lowpass excitatory coupling in fourth state variable"
    )

    Kvf = NArray(label=":math:`K_{vf}`",
                 default=numpy.array([0.0]),
                 domain=Range(lo=0.0, hi=4.0, step=0.5),
                 doc="Coupling scaling on a very fast time scale.")

    Kf = NArray(label=":math:`K_{f}`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=4.0, step=0.5),
                doc="Correspond to the coupling scaling on a fast time scale.")

    Ks = NArray(
        label=":math:`K_{s}`",
        default=numpy.array([0.0]),
        domain=Range(lo=-4.0, hi=4.0, step=0.1),
        doc=
        "Permittivity coupling, that is from the fast time scale toward the slow time scale"
    )

    tt = NArray(label=":math:`K_{tt}`",
                default=numpy.array([1.0]),
                domain=Range(lo=0.001, hi=10.0, step=0.001),
                doc="Time scaling of the whole system")

    modification = NArray(
        dtype=bool,
        label=":math:`modification`",
        default=numpy.array([False]),
        doc="When modification is True, then use nonlinear influence on z. \
        The default value is False, i.e., linear influence.")

    state_variable_range = Final(
        default={
            "x1": numpy.array([-2., 1.]),
            "y1": numpy.array([-20., 2.]),
            "z": numpy.array([2.0, 5.0]),
            "x2": numpy.array([-2., 0.]),
            "y2": numpy.array([0., 2.]),
            "g": numpy.array([-1., 1.])
        },
        label="State variable ranges [lo, hi]",
        doc="Typical bounds on state variables in the Epileptor model.")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=('x1', 'y1', 'z', 'x2', 'y2', 'g', 'x2 - x1'),
        default=("x2 - x1", 'z'),
        doc="Quantities of the Epileptor available to monitor.",
    )

    state_variables = ('x1', 'y1', 'z', 'x2', 'y2', 'g')

    _nvar = 6
    cvar = numpy.array(
        [0, 3], dtype=numpy.int32)  # should these not be constant Attr's?
    cvar.setflags(write=False)  # todo review this

    def _numpy_dfun(self,
                    state_variables,
                    coupling,
                    local_coupling=0.0,
                    array=numpy.array,
                    where=numpy.where,
                    concat=numpy.concatenate):

        y = state_variables
        ydot = numpy.empty_like(state_variables)

        Iext = self.Iext + local_coupling * y[0]
        c_pop1 = coupling[0, :]
        c_pop2 = coupling[1, :]

        # population 1
        if_ydot0 = -self.a * y[0]**2 + self.b * y[0]
        else_ydot0 = self.slope - y[3] + 0.6 * (y[2] - 4.0)**2
        ydot[0] = self.tt * (y[1] - y[2] + Iext + self.Kvf * c_pop1 +
                             where(y[0] < 0., if_ydot0, else_ydot0) * y[0])
        ydot[1] = self.tt * (self.c - self.d * y[0]**2 - y[1])

        # energy
        if_ydot2 = -0.1 * y[2]**7
        else_ydot2 = 0
        if self.modification:
            h = self.x0 + 3. / (1. + numpy.exp(-(y[0] + 0.5) / 0.1))
        else:
            h = 4 * (y[0] - self.x0) + where(y[2] < 0., if_ydot2, else_ydot2)
        ydot[2] = self.tt * (self.r * (h - y[2] + self.Ks * c_pop1))

        # population 2
        ydot[3] = self.tt * (-y[4] + y[3] - y[3]**3 + self.Iext2 +
                             self.bb * y[5] - 0.3 *
                             (y[2] - 3.5) + self.Kf * c_pop2)
        if_ydot4 = 0
        else_ydot4 = self.aa * (y[3] + 0.25)
        ydot[4] = self.tt * (
            (-y[4] + where(y[3] < -0.25, if_ydot4, else_ydot4)) / self.tau)

        # filter
        ydot[5] = self.tt * (-0.01 * (y[5] - 0.1 * y[0]))

        return ydot

    def dfun(self, x, c, local_coupling=0.0):
        r"""
        Computes the derivatives of the state variables of the Epileptor
        with respect to time.

        Implementation note: we expect this version of the Epileptor to be used
        in a vectorized manner. Concretely, y has a shape of (6, n) where n is
        the number of nodes in the network. An consequence is that
        the original use of if/else is translated by calculated both the true
        and false forms and mixing them using a boolean mask.

        Variables of interest to be used by monitors: -y[0] + y[3]

            .. math::
                \dot{x_{1}} &=& y_{1} - f_{1}(x_{1}, x_{2}) - z + I_{ext1} \\
                \dot{y_{1}} &=& c - d x_{1}^{2} - y{1} \\
                \dot{z} &=&
                \begin{cases}
                r(4 (x_{1} - x_{0}) - z-0.1 z^{7}) & \text{if } x<0 \\
                r(4 (x_{1} - x_{0}) - z) & \text{if } x \geq 0
                \end{cases} \\
                \dot{x_{2}} &=& -y_{2} + x_{2} - x_{2}^{3} + I_{ext2} + 0.002 g - 0.3 (z-3.5) \\
                \dot{y_{2}} &=& 1 / \tau (-y_{2} + f_{2}(x_{2}))\\
                \dot{g} &=& -0.01 (g - 0.1 x_{1})

        where:
            .. math::
                f_{1}(x_{1}, x_{2}) =
                \begin{cases}
                a x_{1}^{3} - b x_{1}^2 & \text{if } x_{1} <0\\
                -(slope - x_{2} + 0.6(z-4)^2) x_{1} &\text{if }x_{1} \geq 0
                \end{cases}

        and:
            .. math::
                f_{2}(x_{2}) =
                \begin{cases}
                0 & \text{if } x_{2} <-0.25\\
                a_{2}(x_{2} + 0.25) & \text{if } x_{2} \geq -0.25
                \end{cases}

        """
        x_ = x.reshape(x.shape[:-1]).T
        c_ = c.reshape(c.shape[:-1]).T
        Iext = self.Iext + local_coupling * x[0, :, 0]
        deriv = _numba_dfun(x_, c_, self.x0, Iext, self.Iext2, self.a, self.b,
                            self.slope, self.tt, self.Kvf, self.c, self.d,
                            self.r, self.Ks, self.Kf, self.aa, self.bb,
                            self.tau, self.modification)
        return deriv.T[..., numpy.newaxis]
示例#3
0
class JansenRitDavid(models.Model):
    """
    The Jansen and Rit models as studied by David et al., 2005
    #TODO: finish this model
    """

    # Define traited attributes for this model, these represent possible kwargs.
    He = NArray(
        label=":math:`He`",
        default=numpy.array([3.25]),
        domain=Range(lo=2.6, hi=9.75, step=0.05),
        doc="""Maximum amplitude of EPSP [mV]. Also called average synaptic gain.""")

    Hi = NArray(
        label=":math:`B`",
        default=numpy.array([29.3]),
        domain=Range(lo=17.6, hi=110.0, step=0.2),
        doc="""Maximum amplitude of IPSP [mV]. Also called average synaptic gain.""")

    tau_e = NArray(
        label=":math:`a`",
        default=numpy.array([0.1]),
        domain=Range(lo=0.05, hi=0.15, step=0.01),
        doc="""time constant""")

    tau_i = NArray(
        label=":math:`b`",
        default=numpy.array([0.15]),
        domain=Range(lo=0.025, hi=0.075, step=0.005),
        doc="""time constant""")

    eo = NArray(
        label=":math:`v_0`",
        default=numpy.array([0.0025]),
        domain=Range(lo=3.12, hi=6.0, step=0.02),
        doc="""Firing threshold (PSP) for which a 50% firing rate is achieved.
        In other words, it is the value of the average membrane potential
        corresponding to the inflection point of the sigmoid [mV].""")

    r = NArray(
        label=":math:`r`",
        default=numpy.array([0.56]),
        domain=Range(lo=0.28, hi=0.84, step=0.01),
        doc="""Steepness of the sigmoidal transformation [mV^-1].""")

    gamma_1 = NArray(
        label=r":math:`\alpha_1`",
        default=numpy.array([50.0]),
        domain=Range(lo=0.5, hi=1.5, step=0.1),
        doc="""Average probability of synaptic contacts in the feedback
        excitatory loop.""")

    gamma_2 = NArray(
        label=r":math:`\alpha_2`",
        default=numpy.array([40.]),
        domain=Range(lo=0.4, hi=1.2, step=0.1),
        doc="""Average probability of synaptic contacts in the feedback
        excitatory loop.""")

    gamma_3 = NArray(
        label=r":math:`\alpha_3`",
        default=numpy.array([12.]),
        domain=Range(lo=0.125, hi=0.375, step=0.005),
        doc="""Average probability of synaptic contacts in the feedback
        excitatory loop.""")

    gamma_4 = NArray(
        label=r":math:`\alpha_4`",
        default=numpy.array([12.]),
        domain=Range(lo=0.125, hi=0.375, step=0.005),
        doc="""Average probability of synaptic contacts in the slow feedback
        inhibitory loop.""")

    # Used for phase-plane axis ranges and to bound random initial() conditions.
    state_variable_range = Final(
        {
            "x0": numpy.array([-1.0, 1.0]),
            "x1": numpy.array([-1.0, 1.0]),
            "x2": numpy.array([-5.0, 5.0]),
            "x3": numpy.array([-6.0, 6.0]),
            "x4": numpy.array([-2.0, 2.0]),
            "x5": numpy.array([-5.0, 5.0]),
            "x6": numpy.array([-5.0, 5.0]),
            "x7": numpy.array([-5.0, 5.0])
        },
        label="State Variable ranges [lo, hi]",
        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.""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"),
        default=("x0", "x1", "x2", "x3"),
        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:`y0 = 0`,
                                    :math:`y1 = 1`, :math:`y2 = 2`, :math:`y3 = 3`, :math:`y4 = 4`, and
                                    :math:`y5 = 5`""")

    state_variables = ["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"]
    _nvar = 8
    cvar = numpy.array([1, 2], dtype=numpy.int32)

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        r"""
        The dynamic equations were taken from:
        TODO: add equations and finish the model ...
        """

        magic_exp_number = 709
        AF = 0.1
        AB = 0.2
        AL = 0.05
        x0 = state_variables[0, :]
        x1 = state_variables[1, :]
        x2 = state_variables[2, :]
        x3 = state_variables[3, :]
        x4 = state_variables[4, :]
        x5 = state_variables[5, :]
        x6 = state_variables[6, :]
        x7 = state_variables[7, :]

        y   = x1 - x2
        # delayed activity x1 - x2
        c_12   = coupling[0, :] -  coupling[1, :]
        c_12_f = AF * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo)
        c_12_b = AB * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo)
        c_12_l = AL * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo)

        lc_f = (local_coupling * y) * AF
        lc_l = (local_coupling * y) * AL
        lc_b = (local_coupling * y) * AB

        S_y = (2 * self.eo) / (1 + numpy.exp(self.r * y)) - self.eo
        S_x0 = (2 * self.eo) / (1 + numpy.exp(self.r * x0)) - self.eo
        S_x6 = (2 * self.eo) / (1 + numpy.exp(self.r * x6)) - self.eo

        # NOTE: for local couplings
        # 0:3 pyramidal cells
        # 1:4 excitatory interneurons
        # 2:5 inhibitory interneurons
        # 3:7 

        dx0 = x3
        dx3 = self.He / self.tau_e * (c_12_f + c_12_l + self.gamma_1 * S_y) - (2 * x3) / self.tau_e - (x0 / self.tau_e**2)
        dx1 = x4
        dx4 = self.He / self.tau_e * (c_12_b + c_12_l + self.gamma_2 * S_x0) - (2 * x4) / self.tau_e - (x1 / self.tau_e**2)
        dx2 = x5
        dx5 = self.Hi / self.tau_i * (self.gamma_4 * S_x6) - (2 * x5) / self.tau_i - (x2 / self.tau_i**2)
        dx6 = x7
        dx7 = self.He / self.tau_e * (c_12_b + c_12_l + self.gamma_3 * S_y) - (2 * x7) / self.tau_e - (x6 / self.tau_e**2)

        derivative = numpy.array([dx0, dx1, dx2, dx3, dx4, dx5, dx6, dx7])

        return derivative
示例#4
0
class LarterBreakspear(Model):
    r"""
    A modified Morris-Lecar model that includes a third equation which simulates
    the effect of a population of inhibitory interneurons synapsing on
    the pyramidal cells.

    .. [Larteretal_1999] Larter et.al. *A coupled ordinary differential equation
        lattice model for the simulation of epileptic seizures.* Chaos. 9(3):
        795, 1999.

    .. [Breaksetal_2003_a] Breakspear, M.; Terry, J. R. & Friston, K. J.  *Modulation of excitatory
        synaptic coupling facilitates synchronization and complex dynamics in an
        onlinear model of neuronal dynamics*. Neurocomputing 52–54 (2003).151–158

    .. [Breaksetal_2003_b] M. J. Breakspear et.al. *Modulation of excitatory
        synaptic coupling facilitates synchronization and complex dynamics in a
        biophysical model of neuronal dynamics.* Network: Computation in Neural
        Systems 14: 703-732, 2003.

    .. [Honeyetal_2007] Honey, C.; Kötter, R.; Breakspear, M. & Sporns, O. * Network structure of
        cerebral cortex shapes functional connectivity on multiple time scales*. (2007)
        PNAS, 104, 10240

    .. [Honeyetal_2009] Honey, C. J.; Sporns, O.; Cammoun, L.; Gigandet, X.; Thiran, J. P.; Meuli,
        R. & Hagmann, P. *Predicting human resting-state functional connectivity
        from structural connectivity.* (2009), PNAS, 106, 2035-2040

    .. [Alstottetal_2009] Alstott, J.; Breakspear, M.; Hagmann, P.; Cammoun, L. & Sporns, O.
        *Modeling the impact of lesions in the human brain*. (2009)),  PLoS Comput Biol, 5, e1000408

    Equations and default parameters are taken from [Breaksetal_2003_b]_.
    All equations and parameters are non-dimensional and normalized.
    For values of d_v  < 0.55, the dynamics of a single column settles onto a
    solitary fixed point attractor.


    Parameters used for simulations in [Breaksetal_2003_a]_ Table 1. Page 153.
    Two nodes were coupled. C=0.1

    +---------------------------+
    |          Table 1          |
    +--------------+------------+
    |Parameter     |  Value     |
    +==============+============+
    | I            |      0.3   |
    +--------------+------------+
    | a_ee         |      0.4   |
    +--------------+------------+
    | a_ei         |      0.1   |
    +--------------+------------+
    | a_ie         |      1.0   |
    +--------------+------------+
    | a_ne         |      1.0   |
    +--------------+------------+
    | a_ni         |      0.4   |
    +--------------+------------+
    | r_NMDA       |      0.2   |
    +--------------+------------+
    | delta        |      0.001 |
    +--------------+------------+
    |   Breakspear et al. 2003  |
    +---------------------------+


    +---------------------------+
    |          Table 2          |
    +--------------+------------+
    |Parameter     |  Value     |
    +==============+============+
    | gK           |      2.0   |
    +--------------+------------+
    | gL           |      0.5   |
    +--------------+------------+
    | gNa          |      6.7   |
    +--------------+------------+
    | gCa          |      1.0   |
    +--------------+------------+
    | a_ne         |      1.0   |
    +--------------+------------+
    | a_ni         |      0.4   |
    +--------------+------------+
    | a_ee         |      0.36  |
    +--------------+------------+
    | a_ei         |      2.0   |
    +--------------+------------+
    | a_ie         |      2.0   |
    +--------------+------------+
    | VK           |     -0.7   |
    +--------------+------------+
    | VL           |     -0.5   |
    +--------------+------------+
    | VNa          |      0.53  |
    +--------------+------------+
    | VCa          |      1.0   |
    +--------------+------------+
    | phi          |      0.7   |
    +--------------+------------+
    | b            |      0.1   |
    +--------------+------------+
    | I            |      0.3   |
    +--------------+------------+
    | r_NMDA       |      0.25  |
    +--------------+------------+
    | C            |      0.1   |
    +--------------+------------+
    | TCa          |     -0.01  |
    +--------------+------------+
    | d_Ca         |      0.15  |
    +--------------+------------+
    | TK           |      0.0   |
    +--------------+------------+
    | d_K          |      0.3   |
    +--------------+------------+
    | VT           |      0.0   |
    +--------------+------------+
    | ZT           |      0.0   |
    +--------------+------------+
    | TNa          |      0.3   |
    +--------------+------------+
    | d_Na         |      0.15  |
    +--------------+------------+
    | d_V          |      0.65  |
    +--------------+------------+
    | d_Z          |      d_V   |
    +--------------+------------+
    | QV_max       |      1.0   |
    +--------------+------------+
    | QZ_max       |      1.0   |
    +--------------+------------+
    |   Alstott et al. 2009     |
    +---------------------------+


    NOTES about parameters

    :math:`\delta_V` : for :math:`\delta_V` < 0.55, in an uncoupled network,
    the system exhibits fixed point dynamics; for 0.55 < :math:`\delta_V` < 0.59,
    limit cycle attractors; and for :math:`\delta_V` > 0.59 chaotic attractors
    (eg, d_V=0.6,aee=0.5,aie=0.5, gNa=0, Iext=0.165)

    :math:`\delta_Z`
    this parameter might be spatialized: ones(N,1).*0.65 + modn*(rand(N,1)-0.5);

    :math:`C`
    The long-range coupling :math:`\delta_C` is ‘weak’ in the sense that
    the model is well behaved for parameter values for which C < a_ee and C << a_ie.



    .. figure :: img/LarterBreakspear_01_mode_0_pplane.svg
            :alt: Larter-Breaskpear phase plane (V, W)

            The (:math:`V`, :math:`W`) phase-plane for the Larter-Breakspear model.


    Dynamic equations:

    .. math::
            \dot{V}_k & = - (g_{Ca} + (1 - C) \, r_{NMDA} \, a_{ee} \, Q_V + C \, r_{NMDA} \, a_{ee} \, \langle Q_V\rangle^{k}) \, m_{Ca} \, (V - VCa) \\
                           & \,\,- g_K \, W \, (V - VK) -  g_L \, (V - VL) \\
                           & \,\,- (g_{Na} \, m_{Na} + (1 - C) \, a_{ee} \, Q_V + C \, a_{ee} \, \langle Q_V\rangle^{k}) \,(V - VNa) \\
                           & \,\,- a_{ie} \, Z \, Q_Z + a_{ne} \, I, \\
                           & \\
            \dot{W}_k & = \phi \, \dfrac{m_K - W}{\tau_{K}},\\
                           & \nonumber\\
            \dot{Z}_k &= b (a_{ni}\, I + a_{ei}\,V\,Q_V),\\
            Q_{V}   &= Q_{V_{max}} \, (1 + \tanh\left(\dfrac{V_{k} - VT}{\delta_{V}}\right)),\\
            Q_{Z}   &= Q_{Z_{max}} \, (1 + \tanh\left(\dfrac{Z_{k} - ZT}{\delta_{Z}}\right)),

        See Equations (7), (3), (6) and (2) respectively in [Breaksetal_2003_a]_.
        Pag: 705-706

    """

    # Define traited attributes for this model, these represent possible kwargs.
    gCa = NArray(label=":math:`g_{Ca}`",
                 default=numpy.array([1.1]),
                 domain=Range(lo=0.9, hi=1.5, step=0.1),
                 doc="""Conductance of population of Ca++ channels.""")

    gK = NArray(label=":math:`g_{K}`",
                default=numpy.array([2.0]),
                domain=Range(lo=1.95, hi=2.05, step=0.025),
                doc="""Conductance of population of K channels.""")

    gL = NArray(label=":math:`g_{L}`",
                default=numpy.array([0.5]),
                domain=Range(lo=0.45, hi=0.55, step=0.05),
                doc="""Conductance of population of leak channels.""")

    phi = NArray(label=r":math:`\phi`",
                 default=numpy.array([0.7]),
                 domain=Range(lo=0.3, hi=0.9, step=0.1),
                 doc="""Temperature scaling factor.""")

    gNa = NArray(label=":math:`g_{Na}`",
                 default=numpy.array([6.7]),
                 domain=Range(lo=0.0, hi=10.0, step=0.1),
                 doc="""Conductance of population of Na channels.""")

    TK = NArray(label=":math:`T_{K}`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=0.0001, step=0.00001),
                doc="""Threshold value for K channels.""")

    TCa = NArray(label=":math:`T_{Ca}`",
                 default=numpy.array([-0.01]),
                 domain=Range(lo=-0.02, hi=-0.01, step=0.0025),
                 doc="Threshold value for Ca channels.")

    TNa = NArray(label=":math:`T_{Na}`",
                 default=numpy.array([0.3]),
                 domain=Range(lo=0.25, hi=0.3, step=0.025),
                 doc="Threshold value for Na channels.")

    VCa = NArray(label=":math:`V_{Ca}`",
                 default=numpy.array([1.0]),
                 domain=Range(lo=0.9, hi=1.1, step=0.05),
                 doc="""Ca Nernst potential.""")

    VK = NArray(label=":math:`V_{K}`",
                default=numpy.array([-0.7]),
                domain=Range(lo=-0.8, hi=1., step=0.1),
                doc="""K Nernst potential.""")

    VL = NArray(label=":math:`V_{L}`",
                default=numpy.array([-0.5]),
                domain=Range(lo=-0.7, hi=-0.4, step=0.1),
                doc="""Nernst potential leak channels.""")

    VNa = NArray(label=":math:`V_{Na}`",
                 default=numpy.array([0.53]),
                 domain=Range(lo=0.51, hi=0.55, step=0.01),
                 doc="""Na Nernst potential.""")

    d_K = NArray(label=r":math:`\delta_{K}`",
                 default=numpy.array([0.3]),
                 domain=Range(lo=0.1, hi=0.4, step=0.1),
                 doc="""Variance of K channel threshold.""")

    tau_K = NArray(label=r":math:`\tau_{K}`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=1.0, hi=10.0, step=1.0),
                   doc="""Time constant for K relaxation time (ms)""")

    d_Na = NArray(label=r":math:`\delta_{Na}`",
                  default=numpy.array([0.15]),
                  domain=Range(lo=0.1, hi=0.2, step=0.05),
                  doc="Variance of Na channel threshold.")

    d_Ca = NArray(label=r":math:`\delta_{Ca}`",
                  default=numpy.array([0.15]),
                  domain=Range(lo=0.1, hi=0.2, step=0.05),
                  doc="Variance of Ca channel threshold.")

    aei = NArray(label=":math:`a_{ei}`",
                 default=numpy.array([2.0]),
                 domain=Range(lo=0.1, hi=2.0, step=0.1),
                 doc="""Excitatory-to-inhibitory synaptic strength.""")

    aie = NArray(label=":math:`a_{ie}`",
                 default=numpy.array([2.0]),
                 domain=Range(lo=0.5, hi=2.0, step=0.1),
                 doc="""Inhibitory-to-excitatory synaptic strength.""")

    b = NArray(
        label=":math:`b`",
        default=numpy.array([0.1]),
        domain=Range(lo=0.0001, hi=1.0, step=0.0001),
        doc="""Time constant scaling factor. The original value is 0.1""")

    C = NArray(
        label=":math:`C`",
        default=numpy.array([0.1]),
        domain=Range(lo=0.0, hi=1.0, step=0.01),
        doc="""Strength of excitatory coupling. Balance between internal and
        local (and global) coupling strength. C > 0 introduces interdependences between
        consecutive columns/nodes. C=1 corresponds to maximum coupling between node and no self-coupling.
        This strenght should be set to sensible values when a whole network is connected. """
    )

    ane = NArray(label=":math:`a_{ne}`",
                 default=numpy.array([1.0]),
                 domain=Range(lo=0.4, hi=1.0, step=0.05),
                 doc="""Non-specific-to-excitatory synaptic strength.""")

    ani = NArray(label=":math:`a_{ni}`",
                 default=numpy.array([0.4]),
                 domain=Range(lo=0.3, hi=0.5, step=0.05),
                 doc="""Non-specific-to-inhibitory synaptic strength.""")

    aee = NArray(label=":math:`a_{ee}`",
                 default=numpy.array([0.4]),
                 domain=Range(lo=0.0, hi=0.6, step=0.05),
                 doc="""Excitatory-to-excitatory synaptic strength.""")

    Iext = NArray(
        label=":math:`I_{ext}`",
        default=numpy.array([0.3]),
        domain=Range(lo=0.165, hi=0.3, step=0.005),
        doc="""Subcortical input strength. It represents a non-specific
       excitation or thalamic inputs.""")

    rNMDA = NArray(label=":math:`r_{NMDA}`",
                   default=numpy.array([0.25]),
                   domain=Range(lo=0.2, hi=0.3, step=0.05),
                   doc="""Ratio of NMDA to AMPA receptors.""")

    VT = NArray(label=":math:`V_{T}`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=0.7, step=0.01),
                doc="""Threshold potential (mean) for excitatory neurons.
        In [Breaksetal_2003_b]_ this value is 0.""")

    d_V = NArray(
        label=r":math:`\delta_{V}`",
        default=numpy.array([0.65]),
        domain=Range(lo=0.49, hi=0.7, step=0.01),
        doc="""Variance of the excitatory threshold. It is one of the main
        parameters explored in [Breaksetal_2003_b]_.""")

    ZT = NArray(label=":math:`Z_{T}`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=0.1, step=0.005),
                doc="""Threshold potential (mean) for inihibtory neurons.""")

    d_Z = NArray(label=r":math:`\delta_{Z}`",
                 default=numpy.array([0.7]),
                 domain=Range(lo=0.001, hi=0.75, step=0.05),
                 doc="""Variance of the inhibitory threshold.""")

    # NOTE: the values were not in the article.
    QV_max = NArray(
        label=":math:`Q_{max}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.1, hi=1., step=0.001),
        doc="""Maximal firing rate for excitatory populations (kHz)""")

    QZ_max = NArray(
        label=":math:`Q_{max}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.1, hi=1., step=0.001),
        doc="""Maximal firing rate for excitatory populations (kHz)""")

    t_scale = NArray(label=":math:`t_{scale}`",
                     default=numpy.array([1.0]),
                     domain=Range(lo=0.1, hi=1., step=0.001),
                     doc="""Time scale factor""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("V", "W", "Z"),
        default=("V", ),
        doc="""This represents the default state-variables of this Model to be
        monitored. It can be overridden for each Monitor if desired.""")

    #Informational attribute, used for phase-plane and initial()
    state_variable_range = Final(
        label="State Variable ranges [lo, hi]",
        default={
            "V": numpy.array([-1.5, 1.5]),
            "W": numpy.array([-1.5, 1.5]),
            "Z": numpy.array([-1.5, 1.5])
        },
        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."""
    )

    state_variables = tuple('V W Z'.split())
    _state_variables = ("V", "W", "Z")
    _nvar = 3
    cvar = numpy.array([0], dtype=numpy.int32)

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        r"""
        Dynamic equations:

        .. math::
            \dot{V}_k & = - (g_{Ca} + (1 - C) \, r_{NMDA} \, a_{ee} \, Q_V + C \, r_{NMDA} \, a_{ee} \, \langle Q_V\rangle^{k}) \, m_{Ca} \, (V - VCa) \\
                           & \,\,- g_K \, W \, (V - VK) -  g_L \, (V - VL) \\
                           & \,\,- (g_{Na} \, m_{Na} + (1 - C) \, a_{ee} \, Q_V + C \, a_{ee} \, \langle Q_V\rangle^{k}) \,(V - VNa) \\
                           & \,\,- a_{ie} \, Z \, Q_Z + a_{ne} \, I, \\
                           & \\
            \dot{W}_k & = \phi \, \dfrac{m_K - W}{\tau_{K}},\\
                           & \nonumber\\
            \dot{Z}_k &= b (a_{ni}\, I + a_{ei}\,V\,Q_V),\\
            Q_{V}   &= Q_{V_{max}} \, (1 + \tanh\left(\dfrac{V_{k} - VT}{\delta_{V}}\right)),\\
            Q_{Z}   &= Q_{Z_{max}} \, (1 + \tanh\left(\dfrac{Z_{k} - ZT}{\delta_{Z}}\right)),

        """
        V, W, Z = state_variables
        derivative = numpy.empty_like(state_variables)
        c_0 = coupling[0, :]
        # relationship between membrane voltage and channel conductance
        m_Ca = 0.5 * (1 + numpy.tanh((V - self.TCa) / self.d_Ca))
        m_Na = 0.5 * (1 + numpy.tanh((V - self.TNa) / self.d_Na))
        m_K = 0.5 * (1 + numpy.tanh((V - self.TK) / self.d_K))
        # voltage to firing rate
        QV = 0.5 * self.QV_max * (1 + numpy.tanh((V - self.VT) / self.d_V))
        QZ = 0.5 * self.QZ_max * (1 + numpy.tanh((Z - self.ZT) / self.d_Z))
        lc_0 = local_coupling * QV
        derivative[0] = self.t_scale * (
            -(self.gCa + (1.0 - self.C) * (self.rNMDA * self.aee) *
              (QV + lc_0) + self.C * self.rNMDA * self.aee * c_0) * m_Ca *
            (V - self.VCa) - self.gK * W * (V - self.VK) - self.gL *
            (V - self.VL) - (self.gNa * m_Na + (1.0 - self.C) * self.aee *
                             (QV + lc_0) + self.C * self.aee * c_0) *
            (V - self.VNa) - self.aie * Z * QZ + self.ane * self.Iext)
        derivative[1] = self.t_scale * self.phi * (m_K - W) / self.tau_K
        derivative[2] = self.t_scale * self.b * (self.ani * self.Iext +
                                                 self.aei * V * QV)
        return derivative
class SpikingWongWangExcIOInhI(Model):
    _N_E_max = 200
    _n_regions = 0
    __n_E = []
    __n_I = []
    __E = {}
    __I = {}

    _auto_connection = True
    _refractory_neurons_E = {}
    _refractory_neurons_I = {}
    _spikes_E = {}
    _spikes_I = {}
    r"""
    .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang,  *A Recurrent Network
                Mechanism of Time Integration in Perceptual Decisions*.
                Journal of Neuroscience 26(4), 1314-1328, 2006.

    .. [DPA_2013] Deco Gustavo, Ponce Alvarez Adrian, Dante Mantini,
                  Gian Luca Romani, Patric Hagmann, and Maurizio Corbetta.
                  *Resting-State Functional Connectivity Emerges from
                  Structurally and Dynamically Shaped Slow Linear Fluctuations*.
                  The Journal of Neuroscience 33(27), 11239 –11252, 2013.


    .. automethod:: ReducedWongWang.__init__

    Equations taken from [DPA_2013]_ , page 11241

    .. math::


    """

    # Define traited attributes for this model, these represent possible kwargs.

    V_L = NArray(label=":math:`V_L`",
                 default=numpy.array([
                     -70.,
                 ]),
                 domain=Range(lo=-100., hi=0., step=1.),
                 doc="[mV]. Resting membrane potential.")

    V_thr = NArray(label=":math:`V_thr`",
                   default=numpy.array([
                       -50.,
                   ]),
                   domain=Range(lo=-100., hi=0., step=1.),
                   doc="[mV]. Threshold membrane potential.")

    V_reset = NArray(label=":math:`V_reset`",
                     default=numpy.array([
                         -55.,
                     ]),
                     domain=Range(lo=-100., hi=0., step=1.),
                     doc="[mV]. Refractory membrane potential.")

    tau_AMPA = NArray(label=":math:`tau_AMPA`",
                      default=numpy.array([
                          2.,
                      ]),
                      domain=Range(lo=0., hi=10., step=0.1),
                      doc="[ms]. AMPA synapse time constant.")

    tau_NMDA_rise = NArray(label=":math:`tau_NMDA_rise`",
                           default=numpy.array([
                               2.,
                           ]),
                           domain=Range(lo=0., hi=10., step=0.1),
                           doc="[ms]. NMDA synapse rise time constant.")

    tau_NMDA_decay = NArray(label=":math:`tau_NMDA_decay`",
                            default=numpy.array([
                                100.,
                            ]),
                            domain=Range(lo=0., hi=1000., step=10.),
                            doc="[ms]. NMDA synapse decay time constant.")

    tau_GABA = NArray(label=":math:`tau_GABA`",
                      default=numpy.array([
                          10.,
                      ]),
                      domain=Range(lo=0., hi=100., step=1.),
                      doc="[ms]. GABA synapse time constant.")

    alpha = NArray(label=":math:`alpha`",
                   default=numpy.array([
                       0.5,
                   ]),
                   domain=Range(lo=0., hi=10., step=0.1),
                   doc="[kHz]. NMDA synapse rate constant.")

    beta = NArray(label=":math:`beta`",
                  default=numpy.array([
                      0.062,
                  ]),
                  domain=Range(lo=0., hi=0.1, step=0.001),
                  doc="[]. NMDA synapse exponential constant.")

    lamda_NMDA = NArray(label=":math:`lamda_NMDA`",
                        default=numpy.array([
                            0.28,
                        ]),
                        domain=Range(lo=0., hi=1., step=0.01),
                        doc="[]. NMDA synapse constant.")

    lamda = NArray(label=":math:`lamda`",
                   default=numpy.array([
                       0.,
                   ]),
                   domain=Range(lo=0., hi=1., step=0.01),
                   doc="[kHz]. Feedforward inhibition parameter.")

    I_ext = NArray(label=":math:`I_ext`",
                   default=numpy.array([
                       0.,
                   ]),
                   domain=Range(lo=0., hi=1000., step=10.),
                   doc="[pA]. External current stimulation.")

    spikes_ext = NArray(label=":math:`spikes_ext`",
                        default=numpy.array([
                            0.,
                        ]),
                        domain=Range(lo=0., hi=100., step=1.),
                        doc="[spike weight]. External spikes' stimulation.")

    G = NArray(label=":math:`G`",
               default=numpy.array([
                   2.0,
               ]),
               domain=Range(lo=0.0, hi=100.0, step=1.0),
               doc="""Global coupling scaling""")

    N_E = NArray(label=":math:`N_E`",
                 default=numpy.array([
                     100,
                 ]),
                 domain=Range(lo=0, hi=1000000, step=1),
                 doc="Number of excitatory population neurons.")

    V_E = NArray(label=":math:`V_E`",
                 default=numpy.array([
                     0.,
                 ]),
                 domain=Range(lo=-50., hi=50., step=1.),
                 doc="[mV]. Excitatory reversal potential.")

    w_EE = NArray(
        label=":math:`w_EE`",
        default=numpy.array([
            1.55,
        ]),  # 1.4 for Deco et al. 2014
        domain=Range(lo=0., hi=2.0, step=0.01),
        doc="Excitatory within population synapse weight.")

    w_EI = NArray(label=":math:`w_EI`",
                  default=numpy.array([
                      1.,
                  ]),
                  domain=Range(lo=0., hi=10., step=0.1),
                  doc="Excitatory to inhibitory population synapse weight.")

    C_m_E = NArray(label=":math:`C_m_E`",
                   default=numpy.array([
                       500.0,
                   ]),
                   domain=Range(lo=0., hi=10., step=1000.0),
                   doc="[pF]. Excitatory population membrane capacitance.")

    g_m_E = NArray(label=":math:`g_m_E`",
                   default=numpy.array([
                       25.,
                   ]),
                   domain=Range(lo=0., hi=100., step=1.0),
                   doc="[nS]. Excitatory population membrane conductance.")

    g_AMPA_ext_E = NArray(
        label=":math:`g_AMPA_ext_E`",
        default=numpy.array([
            2.496,
        ]),  # 3.37 for Deco et al. 2014
        domain=Range(lo=0., hi=10., step=0.001),
        doc="[nS]. Excitatory population external AMPA conductance.")

    g_AMPA_E = NArray(
        label=":math:`g_AMPA_E`",
        default=numpy.array([
            0.104,
        ]),  # 0.065 for Deco et al. 2014
        domain=Range(lo=0., hi=0.1, step=0.001),
        doc="[nS]. Excitatory population AMPA conductance.")

    g_NMDA_E = NArray(
        label=":math:`g_NMDA_E`",
        default=numpy.array([
            0.327,
        ]),  # 0.20 for Deco et al. 2014
        domain=Range(lo=0., hi=1., step=0.001),
        doc="[nS]. Excitatory population NMDA conductance.")

    g_GABA_E = NArray(
        label=":math:`g_GABA_E`",
        default=numpy.array([
            4.375,
        ]),  # 10.94 for Deco et al. 2014
        domain=Range(lo=0., hi=10., step=0.001),
        doc="[nS]. Excitatory population GABA conductance.")

    tau_ref_E = NArray(label=":math:`tau_ref_E`",
                       default=numpy.array([
                           2.,
                       ]),
                       domain=Range(lo=0., hi=10., step=0.1),
                       doc="[ms]. Excitatory population refractory time.")

    N_I = NArray(label=":math:`N_I`",
                 default=numpy.array([
                     100,
                 ]),
                 domain=Range(lo=0, hi=1000000, step=10),
                 doc="Number of inhibitory population neurons.")

    V_I = NArray(label=":math:`V_I`",
                 default=numpy.array([
                     -70.,
                 ]),
                 domain=Range(lo=-100., hi=0., step=1.),
                 doc="[mV]. Inhibitory reversal potential.")

    w_II = NArray(label=":math:`w_II`",
                  default=numpy.array([
                      1.,
                  ]),
                  domain=Range(lo=0., hi=10., step=0.1),
                  doc="Inhibitory within population synapse weight.")

    w_IE = NArray(
        label=":math:`w_IE`",
        default=numpy.array([
            1.,
        ]),
        domain=Range(lo=0., hi=10., step=0.1),
        doc=
        "Feedback inhibition: Inhibitory to excitatory population synapse weight."
    )

    C_m_I = NArray(label=":math:`C_m_I`",
                   default=numpy.array([
                       200.0,
                   ]),
                   domain=Range(lo=0., hi=10., step=1000.0),
                   doc="[pF]. Inhibitory population membrane capacitance.")

    g_m_I = NArray(label=":math:`g_m_I`",
                   default=numpy.array([
                       20.,
                   ]),
                   domain=Range(lo=0., hi=100., step=1.),
                   doc="[nS]. Inhibitory population membrane conductance.")

    g_AMPA_ext_I = NArray(
        label=":math:`g_AMPA_ext_I`",
        default=numpy.array([
            1.944,
        ]),  # 2.59 for Deco et al. 2014
        domain=Range(lo=0., hi=10., step=0.001),
        doc="[nS]. Inhibitory population external AMPA conductance.")

    g_AMPA_I = NArray(
        label=":math:`g_AMPA_I`",
        default=numpy.array([
            0.081,
        ]),  # 0.051 for Deco et al. 2014
        domain=Range(lo=0., hi=0.1, step=0.001),
        doc="[nS]. Inhibitory population AMPA conductance.")

    g_NMDA_I = NArray(
        label=":math:`g_NMDA_I`",
        default=numpy.array([
            0.258,
        ]),  # 0.16 for Deco et al. 2014
        domain=Range(lo=0., hi=1.0, step=0.001),
        doc="[nS]. Inhibitory population NMDA conductance.")

    g_GABA_I = NArray(
        label=":math:`g_GABA_I`",
        default=numpy.array([
            3.4055,
        ]),  # 8.51 for Deco et al. 2014
        domain=Range(lo=0., hi=10., step=0.0001),
        doc="[nS]. Inhibitory population GABA conductance.")

    tau_ref_I = NArray(label=":math:`tau_ref_I`",
                       default=numpy.array([
                           1,
                       ]),
                       domain=Range(lo=0., hi=10., step=0.1),
                       doc="[ms]. Inhibitory population refractory time.")

    # Used for phase-plane axis ranges and to bound random initial() conditions.
    state_variable_boundaries = Final(
        default={
            "s_AMPA": numpy.array([0., 1.]),  # 0
            "x_NMDA": numpy.array([0., None]),  # 1
            "s_NMDA": numpy.array([0., 1.]),  # 2
            "s_GABA": numpy.array([0., 1.]),  # 3
            "s_AMPA_ext": numpy.array([
                0., 800.0
            ]),  # 4  It is assumed that 800 neurons project to this synapse
            "V_m": numpy.array([None, None]),  # 5
            "t_ref": numpy.array([0., None]),  # 6
            "spikes_ext": numpy.array([0., None]),  # 7
            "spikes": numpy.array([0., 1.]),  # 8
            "I_L": numpy.array([None, None]),  # 9
            "I_AMPA": numpy.array([None, None]),  # 10
            "I_GABA": numpy.array([None, None]),  # 11
            "I_NMDA": numpy.array([None, None]),  # 12
            "I_AMPA_ext": numpy.array([None, None])
        },  # 13
        label="State Variable boundaries [lo, hi]",
        doc="""The values for each state-variable should be set to encompass
            the boundaries of the dynamic range of that state-variable. 
            Set None for one-sided boundaries""")

    # Choosing default initial conditions such as that there are no spikes initialy:
    state_variable_range = Final(
        default={
            "s_AMPA": numpy.array([0., 1.]),  # 0
            "x_NMDA": numpy.array([0., 200.]),  # 1
            "s_NMDA": numpy.array([0., 1.]),  # 2
            "s_GABA": numpy.array([0., 1.]),  # 3
            "s_AMPA_ext": numpy.array([0., 1.]),  # 4
            "V_m": numpy.array([-70., -50.]),  # 5
            "t_ref": numpy.array([0., 1.]),  # 6
            "spikes_ext": numpy.array([0., 0.5]),  # 7
            "spikes": numpy.array([0., 0.5]),  # 8
            "I_L": numpy.array([0., 1000.]),  # 9
            "I_AMPA": numpy.array([-1000., 0.]),  # 10
            "I_NMDA": numpy.array([-1000., 0.]),  # 11
            "I_GABA": numpy.array([0., 1000.]),  # 12
            "I_AMPA_ext": numpy.array([-10., 0.])
        },  # 13
        label="State variable ranges [lo, hi]",
        doc="Population firing rate")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=(
            "s_AMPA",
            "x_NMDA",
            "s_NMDA",
            "s_GABA",
            "s_AMPA_ext",
            "V_m",
            "t_ref",  # state variables
            "spikes_ext",
            "spikes",
            "I_L",
            "I_AMPA",
            "I_NMDA",
            "I_GABA",
            "I_AMPA_ext"),  # non-state variables
        default=(
            "s_AMPA",
            "x_NMDA",
            "s_NMDA",
            "s_GABA",
            "s_AMPA_ext",
            "V_m",
            "t_ref",  # state variables
            "spikes_ext",
            "spikes",
            "I_L",
            "I_AMPA",
            "I_NMDA",
            "I_GABA",
            "I_AMPA_ext"),  # non-state variables
        doc="""default state variables to be monitored""")

    state_variables = [
        "s_AMPA",
        "x_NMDA",
        "s_NMDA",
        "s_GABA",
        "s_AMPA_ext",
        "V_m",
        "t_ref",  # state variables
        "spikes_ext",
        "spikes",
        "I_L",
        "I_AMPA",
        "I_NMDA",
        "I_GABA",
        "I_AMPA_ext"
    ]  # non-state variables
    non_integrated_variables = [
        "spikes_ext", "spikes", "I_L", "I_AMPA", "I_NMDA", "I_GABA",
        "I_AMPA_ext"
    ]
    _nvar = 14
    cvar = numpy.array([0], dtype=numpy.int32)
    number_of_modes = 200  # assuming that 0...N_E-1 are excitatory and N_E ... number_of_modes-1 are inhibitory
    _stimulus = 0.0
    _non_integrated_variables = None

    def update_derived_parameters(self):
        """
        When needed, this should be a method for calculating parameters that are
        calculated based on paramaters directly set by the caller. For example,
        see, ReducedSetFitzHughNagumo. When not needed, this pass simplifies
        code that updates an arbitrary models parameters -- ie, this can be
        safely called on any model, whether it's used or not.
        """
        self._stimulus = 0.0
        self._non_integrated_variables = None
        self.n_nonintvar = self.nvar - self.nintvar
        self._non_integrated_variables_inds = list(
            range(self.nintvar, self.nvar))
        self._N_E_max = int(numpy.max(
            self.N_E))  # maximum number of excitatory neurons/modes
        self.number_of_modes = int(self._N_E_max + numpy.max(self.N_I))
        # # todo: this exclusion list is fragile, consider excluding declarative attrs that are not arrays
        # excluded_params = ("state_variable_range", "state_variable_boundaries", "variables_of_interest",
        #                    "noise", "psi_table", "nerf_table", "gid")
        # for param in type(self).declarative_attrs:
        #     if param in excluded_params:
        #         continue
        #     region_parameters = getattr(self, param)
        #     try:
        #         region_parameters[0, 0]
        #     except:
        #         new_parameters = numpy.reshape(region_parameters, (-1, 1))
        #         setattr(self, param, new_parameters)
        #         region_parameters = getattr(self, param)
        #     assert (region_parameters.shape[1] == self.number_of_modes) or (region_parameters.shape[1] == 1)

    def _region(self, x, i_region):
        try:
            return numpy.array(x[i_region])
        except:
            return numpy.array(x[0])

    def _prepare_indices(self):
        __n_E = []
        __n_I = []
        __E = {}
        __I = {}
        self.__n_E = None
        self.__n_I = None
        self.__E = None
        self.__I = None
        # Initialize all non-state variables, as well as t_ref, to 0, i.e., assuming no spikes in history.
        for i_region in range(self._n_regions):  # For every region node....
            __n_E.append(int(self._region(self.N_E, i_region).item()))
            __n_I.append(int(self._region(self.N_I, i_region).item()))
            __E[i_region] = numpy.arange(__n_E[-1]).astype(
                'i')  # excitatory neurons' indices
            __I[i_region] = numpy.arange(
                self._N_E_max, self._N_E_max + __n_I[-1]).astype(
                    'i')  # inhibitory neurons' indices
        self.__n_E = __n_E
        self.__n_I = __n_I
        self.__E = __E
        self.__I = __I

    # Return number of excitatory neurons/modes per region
    def _n_E(self, i_region):
        # if self.__n_E is not None:
        return self.__n_E[i_region]
        # else:
        #     return int(self._region(self.N_E, i_region).item())

    # Return number of inhibitory neurons/modes per region
    def _n_I(self, i_region):
        # if self.__n_I is not None:
        return self.__n_I[i_region]
        # else:
        #     return int(self._region(self.N_I, i_region).item())

    # Return indices of excitatory neurons/modes per region
    def _E(self, i_region):
        # if self.__E is not None:
        return self.__E[i_region]
        # else:
        #     return numpy.arange(self._n_E(i_region)).astype('i')

    # Return indices of inhibitory neurons/modes per region
    def _I(self, i_region):
        # if self.__I is not None:
        return self.__I[i_region]
        # else:
        #     return numpy.arange(self._N_E_max, self._N_E_max + self._n_I(i_region)).astype('i')

    # Return x variables of excitatory/inhibitory neurons/modes per region
    def _x_E_I(self, x, i_region, E_I):
        x_E_I = self._region(x, i_region)
        x_E_I_shape = x_E_I.shape
        if x_E_I_shape == (self.number_of_modes, ):
            return x_E_I[E_I(
                i_region)]  # if region parameter shape is (n_neurons,)
        else:
            return numpy.array([
                x_E_I.item(),
            ])  # if region parameter shape is (1,) or (1, 1)

    # Return x variables of excitatory neurons/modes per region
    def _x_E(self, x, i_region):
        return self._x_E_I(x, i_region, self._E)

    # Return  x variables of inhibitory neurons/modes per region
    def _x_I(self, x, i_region):
        return self._x_E_I(x, i_region, self._I)

    # Return x variables of refractory excitatory neurons/modes per region
    def _x_E_ref(self, x, i_region, ref):
        x_E = self._x_E(x, i_region)
        try:
            return x_E[ref]
        except:
            return x_E

    # Return x variables of refractory inhibitory neurons/modes per region
    def _x_I_ref(self, x, i_region, ref):
        x_I = self._x_I(x, i_region)
        try:
            return x_I[ref]
        except:
            return x_I

    def _compute_region_exc_population_coupling(self, s_E, w_EE, w_EI):
        # Scale w_EE, w_EI per region accordingly,
        # to implement coupling schemes that depend on the total number of neurons
        # exc -> exc
        c_ee = w_EE * s_E
        c_ee = numpy.sum(c_ee) \
               - numpy.where(self._auto_connection, 0.0, c_ee)  # optionally remove auto-connections
        #                exc -> inh:
        return c_ee, numpy.sum(w_EI * s_E)

    def _compute_region_inh_population_coupling(self, s_I, w_IE, w_II):
        # Scale w_IE, w_II per region accordingly,
        # to implement coupling schemes that depend on the total number of neurons
        # inh -> inh
        c_ii = w_II * s_I
        c_ii = numpy.sum(c_ii) \
               - numpy.where(self._auto_connection, 0.0, c_ii)  # optionally remove auto-connections
        #           inh -> exc:
        return numpy.sum(w_IE * s_I), c_ii

    def _zero_empty_positions(self, state_variables, _E, _I, ii):
        # Make sure that all empty positions are set to 0.0, if any:
        _empty = numpy.unique(_E.tolist() +
                              _I.tolist())  # all indices occupied by neurons
        if len(_empty) < state_variables[0].shape[1]:
            _empty = numpy.delete(numpy.arange(state_variables[0].shape[1]),
                                  _empty)  # all empty indices
            # Set all empty positions to 0
            state_variables[:, ii, _empty] = 0.0

    def _zero_cross_synapses(self, state_variables, _E, _I, ii):
        # Set excitatory synapses for inhibitory neurons to 0.0...
        # 0. s_AMPA, 1. x_NMDA and 2. s_NMDA
        state_variables[:3][:, ii, _I] = 0.0
        # ...and  inhibitory synapses for excitatory neurons to 0.0
        # 4. s_GABA
        state_variables[3, ii, _E] = 0.0

    def update_state_variables_before_integration(self,
                                                  state_variables,
                                                  coupling,
                                                  local_coupling=0.0,
                                                  stimulus=0.0):
        if self._n_regions == 0:
            self._n_regions = state_variables.shape[1]
            self._prepare_indices()
        for ii in range(self._n_regions):  # For every region node....
            _E = self._E(ii)  # excitatory neurons' indices
            _I = self._I(ii)  # inhibitory neurons' indices

            # Make sure that all empty positions are set to 0.0, if any:
            self._zero_empty_positions(state_variables, _E, _I, ii)

            # Set  inhibitory synapses for excitatory neurons & excitatory synapses for inhibitory neurons to 0.0...
            self._zero_cross_synapses(state_variables, _E, _I, ii)

            # -----------------------------------Updates after previous iteration:--------------------------------------

            # Refractory neurons from past spikes if 6. t_ref > 0.0
            self._refractory_neurons_E[ii] = state_variables[6, ii, _E] > 0.0
            self._refractory_neurons_I[ii] = state_variables[6, ii, _I] > 0.0

            # set 5. V_m for refractory neurons to V_reset
            state_variables[5, ii,
                            _E] = numpy.where(self._refractory_neurons_E[ii],
                                              self._x_E(self.V_reset, ii),
                                              state_variables[5, ii, _E])

            state_variables[5, ii,
                            _I] = numpy.where(self._refractory_neurons_I[ii],
                                              self._x_I(self.V_reset, ii),
                                              state_variables[5, ii, _I])

            # Compute spikes sent at time t:
            # 8. spikes
            state_variables[8, ii, _E] = numpy.where(
                state_variables[5, ii, _E] > self._x_E(self.V_thr, ii), 1.0,
                0.0)
            state_variables[8, ii, _I] = numpy.where(
                state_variables[5, ii, _I] > self._x_I(self.V_thr, ii), 1.0,
                0.0)
            exc_spikes = state_variables[8, ii, _E]  # excitatory spikes
            inh_spikes = state_variables[8, ii, _I]  # inhibitory spikes
            self._spikes_E[ii] = exc_spikes > 0.0
            self._spikes_I[ii] = inh_spikes > 0.0

            # set 5. V_m for spiking neurons to V_reset
            state_variables[5, ii,
                            _E] = numpy.where(self._spikes_E[ii],
                                              self._x_E(self.V_reset, ii),
                                              state_variables[5, ii, _E])

            state_variables[5, ii,
                            _I] = numpy.where(self._spikes_I[ii],
                                              self._x_I(self.V_reset, ii),
                                              state_variables[5, ii, _I])

            # set 6. t_ref  to tau_ref for spiking neurons
            state_variables[6, ii,
                            _E] = numpy.where(self._spikes_E[ii],
                                              self._x_E(self.tau_ref_E, ii),
                                              state_variables[6, ii, _E])

            state_variables[6, ii,
                            _I] = numpy.where(self._spikes_I[ii],
                                              self._x_I(self.tau_ref_I, ii),
                                              state_variables[6, ii, _I])

            # Refractory neurons including current spikes sent at time t
            self._refractory_neurons_E[ii] = numpy.logical_or(
                self._refractory_neurons_E[ii], self._spikes_E[ii])
            self._refractory_neurons_I[ii] = numpy.logical_or(
                self._refractory_neurons_I[ii], self._spikes_I[ii])

            # -------------------------------------Updates before next iteration:---------------------------------------

            # ----------------------------------First deal with inputs at time t:---------------------------------------

            # Collect external spikes received at time t, and update the incoming s_AMPA_ext synapse:

            # 7. spikes_ext
            # get external spike stimulus 7. spike_ext, if any:
            state_variables[7, ii, _E] = self._x_E(self.spikes_ext, ii)
            state_variables[7, ii, _I] = self._x_I(self.spikes_ext, ii)

            # 4. s_AMPA_ext
            # ds_AMPA_ext/dt = -1/tau_AMPA * (s_AMPA_exc + spikes_ext)
            # Add the spike at this point to s_AMPA_ext
            state_variables[4, ii, _E] += state_variables[7, ii,
                                                          _E]  # spikes_ext
            state_variables[4, ii, _I] += state_variables[7, ii,
                                                          _I]  # spikes_ext

            # Compute currents based on synaptic gating variables at time t:

            # V_E_E = V_m - V_E
            V_E_E = state_variables[5, ii, _E] - self._x_E(self.V_E, ii)
            V_E_I = state_variables[5, ii, _I] - self._x_I(self.V_E, ii)

            # 9. I_L = g_m * (V_m - V_E)
            state_variables[9, ii, _E] = \
                self._x_E(self.g_m_E, ii) * (state_variables[5, ii, _E] - self._x_E(self.V_L, ii))
            state_variables[9, ii, _I] = \
                self._x_I(self.g_m_I, ii) * (state_variables[5, ii, _I] - self._x_I(self.V_L, ii))

            w_EE = self._x_E(self.w_EE, ii)
            w_EI = self._x_E(self.w_EI, ii)

            # 10. I_AMPA = g_AMPA * (V_m - V_E) * sum(w * s_AMPA_k)
            coupling_AMPA_E, coupling_AMPA_I = \
                self._compute_region_exc_population_coupling(state_variables[0, ii, _E], w_EE, w_EI)
            state_variables[10, ii, _E] = self._x_E(
                self.g_AMPA_E, ii) * V_E_E * coupling_AMPA_E
            # s_AMPA
            state_variables[10, ii, _I] = self._x_I(
                self.g_AMPA_I, ii) * V_E_I * coupling_AMPA_I

            # 11. I_NMDA = g_NMDA * (V_m - V_E) / (1 + lamda_NMDA * exp(-beta*V_m)) * sum(w * s_NMDA_k)
            coupling_NMDA_E, coupling_NMDA_I = \
                self._compute_region_exc_population_coupling(state_variables[2, ii, _E], w_EE, w_EI)
            state_variables[11, ii, _E] = \
                self._x_E(self.g_NMDA_E, ii) * V_E_E \
                / (self._x_E(self.lamda_NMDA, ii) * numpy.exp(-self._x_E(self.beta, ii) *
                                                              state_variables[5, ii, _E])) \
                * coupling_NMDA_E  # s_NMDA
            state_variables[11, ii, _I] = \
                self._x_I(self.g_NMDA_I, ii) * V_E_I \
                / (self._x_I(self.lamda_NMDA, ii) * numpy.exp(-self._x_I(self.beta, ii) *
                                                              state_variables[5, ii, _I])) \
                * coupling_NMDA_I  # s_NMDA

            # 0. s_AMPA
            # s_AMPA += exc_spikes
            state_variables[0, ii, _E] += exc_spikes

            # 1. x_NMDA
            # x_NMDA += exc_spikes
            state_variables[1, ii, _E] += exc_spikes

            # 12. I_GABA = g_GABA * (V_m - V_I) * sum(w_ij * s_GABA_k)
            w_IE = self._x_I(self.w_IE, ii)
            w_II = self._x_I(self.w_II, ii)
            coupling_GABA_E, coupling_GABA_I = \
                self._compute_region_inh_population_coupling(state_variables[3, ii, _I], w_IE, w_II)
            state_variables[12, ii, _E] = self._x_E(self.g_GABA_E, ii) * \
                                          (state_variables[5, ii, _E] - self._x_E(self.V_I, ii)) * \
                                          coupling_GABA_E  # s_GABA
            state_variables[12, ii, _I] = self._x_I(self.g_GABA_I, ii) * \
                                          (state_variables[5, ii, _I] - self._x_I(self.V_I, ii)) * \
                                          coupling_GABA_I  # s_GABA

            # 3. s_GABA += inh_spikes
            state_variables[3, ii, _I] += inh_spikes

            # 13. I_AMPA_ext = g_AMPA_ext * (V_m - V_E) * ( G*sum{c_ij sum{s_AMPA_j(t-delay_ij)}} + s_AMPA_ext)
            # Compute large scale coupling_ij = sum(c_ij * S_e(t-t_ij))
            large_scale_coupling = numpy.sum(coupling[0, ii, :self._N_E_max])
            large_scale_coupling += numpy.sum(local_coupling *
                                              state_variables[0, ii, _E])
            state_variables[13, ii, _E] = self._x_E(self.g_AMPA_ext_E, ii) * V_E_E * \
                                          ( self._x_E(self.G, ii) * large_scale_coupling
                                            + state_variables[4, ii, _E] )
            #                                          # feedforward inhibition
            state_variables[13, ii, _I] = self._x_I(self.g_AMPA_ext_I, ii) * V_E_I * \
                                          ( self._x_I(self.G, ii) * self._x_I(self.lamda, ii) * large_scale_coupling
                                            + state_variables[4, ii, _I] )

        self._non_integrated_variables = state_variables[
            self._non_integrated_variables_inds]

        return state_variables

    def _numpy_dfun(self, integration_variables, coupling, local_coupling=0.0):
        r"""
        Equations taken from [DPA_2013]_ , page 11242

        .. math::
                 x_{ek}       &=   w_p\,J_N \, S_{ek} - J_iS_{ik} + W_eI_o + GJ_N \mathbf\Gamma(S_{ek}, S_{ej}, u_{kj}),\\
                 H(x_{ek})    &=  \dfrac{a_ex_{ek}- b_e}{1 - \exp(-d_e(a_ex_{ek} -b_e))},\\
                 \dot{S}_{ek} &= -\dfrac{S_{ek}}{\tau_e} + (1 - S_{ek}) \, \gammaH(x_{ek}) \,

                 x_{ik}       &=   J_N \, S_{ek} - S_{ik} + W_iI_o + \lambdaGJ_N \mathbf\Gamma(S_{ik}, S_{ej}, u_{kj}),\\
                 H(x_{ik})    &=  \dfrac{a_ix_{ik} - b_i}{1 - \exp(-d_i(a_ix_{ik} -b_i))},\\
                 \dot{S}_{ik} &= -\dfrac{S_{ik}}{\tau_i} + \gamma_iH(x_{ik}) \,

        """

        derivative = 0.0 * integration_variables

        if self._non_integrated_variables is None:
            state_variables = np.array(integration_variables.tolist() +
                                       [0.0 * integration_variables[0]] *
                                       self.n_nonintvar)
            state_variables = self.update_state_variables_before_integration(
                state_variables, coupling, local_coupling, self._stimulus)

        if self._n_regions == 0:
            self._n_regions = integration_variables.shape[1]
            self._prepare_indices()
        for ii in range(self._n_regions):  # For every region node....

            # Excitatory neurons:

            _E = self._E(ii)  # excitatory neurons indices
            tau_AMPA_E = self._x_E(self.tau_AMPA, ii)

            # 0. s_AMPA
            # ds_AMPA/dt = -1/tau_AMPA * s_AMPA
            derivative[0, ii,
                       _E] = -integration_variables[0, ii, _E] / tau_AMPA_E

            # 1. x_NMDA
            # dx_NMDA/dt = -x_NMDA/tau_NMDA_rise
            derivative[1, ii,
                       _E] = -integration_variables[1, ii, _E] / self._x_E(
                           self.tau_NMDA_rise, ii)

            # 2. s_NMDA
            # ds_NMDA/dt = -1/tau_NMDA_decay * s_NMDA + alpha*x_NMDA*(1-s_NMDA)
            derivative[2, ii, _E] = \
                -integration_variables[2, ii, _E] / self._x_E(self.tau_NMDA_decay, ii) \
                + self._x_E(self.alpha, ii) * integration_variables[1, ii, _E] * (1 - integration_variables[2, ii, _E])

            # 4. s_AMPA_ext
            # ds_AMPA_ext/dt = -s_AMPA_exc/tau_AMPA
            derivative[4, ii,
                       _E] = -integration_variables[4, ii, _E] / tau_AMPA_E

            # excitatory refractory neurons:
            ref = self._refractory_neurons_E[ii]
            not_ref = numpy.logical_not(ref)

            # 5. Integrate only non-refractory V_m
            # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA  - I_AMPA_EXT + I_ext
            _E_not_ref = _E[not_ref]
            derivative[5, ii, _E_not_ref] = (
                                                    - self._non_integrated_variables[2, ii, _E_not_ref]  # 9. I_L
                                                    - self._non_integrated_variables[3, ii, _E_not_ref]  # 10. I_AMPA
                                                    - self._non_integrated_variables[4, ii, _E_not_ref]  # 11. I_NMDA
                                                    - self._non_integrated_variables[5, ii, _E_not_ref]  # 12. I_GABA
                                                    - self._non_integrated_variables[6, ii, _E_not_ref]  # 13. I_AMPA_ext
                                                    + self._x_E_ref(self.I_ext, ii, not_ref)  # I_ext
                                            ) \
                                            / self._x_E_ref(self.C_m_E, ii, not_ref)

            # 6...and only refractory t_ref:
            # dt_ref/dt = -1 for t_ref > 0  so that t' = t - dt
            # and 0 otherwise
            derivative[6, ii, _E[ref]] = -1.0

            # Inhibitory neurons:

            _I = self._I(ii)  # inhibitory neurons indices

            # 3. s_GABA/dt = - s_GABA/tau_GABA
            derivative[3, ii,
                       _I] = -integration_variables[3, ii, _I] / self._x_I(
                           self.tau_GABA, ii)

            # 4. s_AMPA_ext
            # ds_AMPA_ext/dt = -s_AMPA_exc/tau_AMPA
            derivative[4, ii,
                       _I] = -integration_variables[4, ii, _I] / self._x_I(
                           self.tau_AMPA, ii)

            # inhibitory refractory neurons:
            ref = self._refractory_neurons_I[ii]
            not_ref = numpy.logical_not(ref)

            # 5. Integrate only non-refractory V_m
            # C_m*dV_m/dt = - I_L - I_AMPA_EXT - I_AMPA - I_GABA - I_NMDA + I_ext
            # 5. Integrate only non-refractory V_m
            # C_m*dV_m/dt = - I_L- I_AMPA - I_NMDA - I_GABA  - I_AMPA_EXT + I_ext
            _I_not_ref = _I[not_ref]
            derivative[5, ii, _I_not_ref] = (
                                                    - self._non_integrated_variables[2, ii, _I_not_ref]  # 9. I_L
                                                    - self._non_integrated_variables[3, ii, _I_not_ref]  # 10. I_AMPA
                                                    - self._non_integrated_variables[4, ii, _I_not_ref]  # 11. I_NMDA
                                                    - self._non_integrated_variables[5, ii, _I_not_ref]  # 12. I_GABA
                                                    - self._non_integrated_variables[6, ii, _I_not_ref]  # 13. I_AMPA_ext
                                                    + self._x_I_ref(self.I_ext, ii, not_ref)  # I_ext
                                             ) \
                                             / self._x_I_ref(self.C_m_I, ii, not_ref)

            # 6...and only refractory t_ref:
            # dt_ref/dt = -1 for t_ref > 0  so that t' = t - dt
            # and 0 otherwise
            derivative[6, ii, _I[ref]] = -1.0

        self._non_integrated_variables = None

        return derivative

    def dfun(self, x, c, local_coupling=0.0):
        return self._numpy_dfun(x, c, local_coupling)
 class A(HasTraits):
     picked_dimensions = List(of=str, default=('time', 42.24))
示例#7
0
class Baz(BaBaze):
    airplane_sweets = List(of=str, choices=('nuts', 'chocolate', 'prunes'))
示例#8
0
class BrunelWang(models.Model):
    """
    .. [DJ_2012] Deco G and Jirsa V. *Ongoing Cortical
        Activity at Rest: Criticality, Multistability, and Ghost Attractors*.
        Journal of Neuroscience 32, 3366-3375, 2012.

    .. [BW_2001] Brunel N and Wang X-J. *Effects of neuromodulation in a cortical
       network model of object working memory dominated by recurrent inhibition*.
       Journal of Computational Neuroscience 11, 63–85, 2001.

    Each node consists of one excitatory (E) and one inhibitory (I) pool.

    At a global level, it uses Hagmann's 2008 connectome 66 areas(hagmann_struct.csv)
    with a global scaling weight (W) of 1.65.


    """

    # Define traited attributes for this model, these represent possible kwargs.
    tau = NArray(
        label=":math:`\\tau`",
        default=numpy.array([
            1.25,
        ]),
        domain=Range(lo=0.01, hi=5.0, step=0.01),
        doc="""A time-scale separation between the fast, :math:`V`, and slow,
        :math:`W`, state-variables of the model.""")

    calpha = NArray(label=":math:`c_{\\alpha}`",
                    default=numpy.array([
                        0.5,
                    ]),
                    domain=Range(lo=0.4, hi=0.5, step=0.05),
                    doc="""NMDA saturation parameter (kHz)""")

    cbeta = NArray(label=":math:`c_{\\beta}`",
                   default=numpy.array([
                       0.062,
                   ]),
                   domain=Range(lo=0.06, hi=0.062, step=0.002),
                   doc="""Inverse MG2+ blockade potential(mV-1)""")

    cgamma = NArray(label=":math:`c_{\\gamma}`",
                    default=numpy.array([
                        0.2801120448,
                    ]),
                    domain=Range(lo=0.2801120440,
                                 hi=0.2801120448,
                                 step=0.0000000001),
                    doc="""Strength of Mg2+ blockade""")

    tauNMDArise = NArray(label=":math:`\\tau_{NMDA_{rise}}`",
                         default=numpy.array([
                             2.0,
                         ]),
                         domain=Range(lo=0.0, hi=2.0, step=0.5),
                         doc="""NMDA time constant (rise) (ms)""")

    tauNMDAdecay = NArray(label=":math:`\\tau_{NMDA_{decay}}`",
                          default=numpy.array([
                              100.,
                          ]),
                          domain=Range(lo=50.0, hi=100.0, step=10.0),
                          doc="""NMDA time constant (decay) (ms)""")

    tauAMPA = NArray(label=":math:`\\tau_{AMPA}`",
                     default=numpy.array([
                         2.0,
                     ]),
                     domain=Range(lo=1.0, hi=2.0, step=1.0),
                     doc="""AMPA time constant (decay) (ms)""")

    tauGABA = NArray(label=":math:`\\tau_{GABA}`",
                     default=numpy.array([
                         10.0,
                     ]),
                     domain=Range(lo=5.0, hi=15.0, step=1.0),
                     doc="""GABA time constant (decay) (ms)""")

    VE = NArray(label=":math:`V_E`",
                default=numpy.array([
                    0.0,
                ]),
                domain=Range(lo=0.0, hi=10.0, step=2.0),
                doc="""Extracellular potential (mV)""")

    VI = NArray(label=":math:`V_I`",
                default=numpy.array([
                    -70.0,
                ]),
                domain=Range(lo=-70.0, hi=-50.0, step=5.0),
                doc=""".""")

    VL = NArray(label=":math:`V_L`",
                default=numpy.array([
                    -70.0,
                ]),
                domain=Range(lo=-70.0, hi=-50.0, step=5.0),
                doc="""Resting potential (mV)""")

    Vthr = NArray(label=":math:`V_{thr}`",
                  default=numpy.array([
                      -50.0,
                  ]),
                  domain=Range(lo=-50.0, hi=-30.0, step=5.0),
                  doc="""Threshold potential (mV)""")

    Vreset = NArray(label=":math:`V_{reset}`",
                    default=numpy.array([
                        -55.0,
                    ]),
                    domain=Range(lo=-70.0, hi=-30.0, step=5.0),
                    doc="""Reset potential (mV)""")

    gNMDA_e = NArray(
        label=":math:`g_{NMDA_{e}}`",
        default=numpy.array([
            0.327,
        ]),
        domain=Range(lo=0.320, hi=0.350, step=0.0035),
        doc="""NMDA conductance on post-synaptic excitatory (nS)""")

    gNMDA_i = NArray(
        label=":math:`g_{NMDA_{i}}`",
        default=numpy.array([
            0.258,
        ]),
        domain=Range(lo=0.250, hi=0.270, step=0.002),
        doc="""NMDA conductance on post-synaptic inhibitory (nS)""")

    gGABA_e = NArray(
        label=":math:`g_{GABA_{e}}`",
        default=numpy.array([
            1.25 * 3.5,
        ]),
        domain=Range(lo=1.25, hi=4.375, step=0.005),
        doc="""GABA conductance on excitatory post-synaptic (nS)""")

    gGABA_i = NArray(
        label=":math:`g_{GABA_{i}}`",
        default=numpy.array([
            0.973 * 3.5,
        ]),
        domain=Range(lo=0.9730, hi=3.4055, step=0.0005),
        doc="""GABA conductance on inhibitory post-synaptic (nS)""")

    gAMPArec_e = NArray(label=":math:`g_{AMPA_{rec_e}}`",
                        default=numpy.array([
                            0.104,
                        ]),
                        domain=Range(lo=0.1, hi=0.11, step=0.001),
                        doc="""AMPA(recurrent) cond on post-synaptic (nS)""")

    gAMPArec_i = NArray(label=":math:`g_{AMPA_{rec_i}}`",
                        default=numpy.array([
                            0.081,
                        ]),
                        domain=Range(lo=0.081, hi=0.1, step=0.001),
                        doc="""AMPA(recurrent) cond on post-synaptic (nS)""")

    gAMPAext_e = NArray(label=":math:`g_{AMPA_{ext_e}}`",
                        default=numpy.array([
                            2.08 * 1.2,
                        ]),
                        domain=Range(lo=2.08, hi=2.496, step=0.004),
                        doc="""AMPA(external) cond on post-synaptic (nS)""")

    gAMPAext_i = NArray(label=":math:`g_{AMPA_{ext_i}}`",
                        default=numpy.array([
                            1.62 * 1.2,
                        ]),
                        domain=Range(lo=1.62, hi=1.944, step=0.004),
                        doc="""AMPA(external) cond on post-synaptic (nS)""")

    gm_e = NArray(label=":math:`gm_e`",
                  default=numpy.array([
                      25.0,
                  ]),
                  domain=Range(lo=20.0, hi=25.0, step=1.0),
                  doc="""Excitatory membrane conductance (nS)""")

    gm_i = NArray(label=":math:`gm_i`",
                  default=numpy.array([
                      20.,
                  ]),
                  domain=Range(lo=15.0, hi=21.0, step=1.0),
                  doc="""Inhibitory membrane conductance (nS)""")

    Cm_e = NArray(label=":math:`Cm_e`",
                  default=numpy.array([
                      500.,
                  ]),
                  domain=Range(lo=200.0, hi=600.0, step=50.0),
                  doc="""Excitatory membrane capacitance (mF)""")

    Cm_i = NArray(label=":math:`Cm_i`",
                  default=numpy.array([
                      200.,
                  ]),
                  domain=Range(lo=150.0, hi=250.0, step=50.0),
                  doc="""Inhibitory membrane capacitance (mF)""")

    taum_e = NArray(label=":math:`\\tau_{m_{e}}`",
                    default=numpy.array([
                        20.,
                    ]),
                    domain=Range(lo=10.0, hi=25.0, step=5.0),
                    doc="""Excitatory membrane leak time (ms)""")

    taum_i = NArray(label=":math:`\\tau_{m_{i}}`",
                    default=numpy.array([
                        10.0,
                    ]),
                    domain=Range(lo=5.0, hi=15.0, step=5.),
                    doc="""Inhibitory Membrane leak time (ms)""")

    taurp_e = NArray(label=":math:`\\tau_{rp_{e}}`",
                     default=numpy.array([
                         2.0,
                     ]),
                     domain=Range(lo=0.0, hi=4.0, step=1.),
                     doc="""Excitatory absolute refractory period (ms)""")

    taurp_i = NArray(label=":math:`\\tau_{rp_{i}}`",
                     default=numpy.array([
                         1.0,
                     ]),
                     domain=Range(lo=0.0, hi=2.0, step=0.5),
                     doc="""Inhibitory absolute refractory period (ms)""")

    Cext = NArray(dtype=numpy.int,
                  label=":math:`C_{ext}`",
                  default=numpy.array([
                      800,
                  ]),
                  domain=Range(lo=500, hi=1200, step=100),
                  doc="""Number of external (excitatory) connections""")

    C = NArray(dtype=numpy.int,
               label=":math:`C`",
               default=numpy.array([
                   200,
               ]),
               domain=Range(lo=100, hi=500, step=100),
               doc="Number of neurons for each node")

    nuext = NArray(label=":math:`\\nu_{ext}`",
                   default=numpy.array([
                       0.003,
                   ]),
                   domain=Range(lo=0.002, hi=0.01, step=0.001),
                   doc="""External firing rate (kHz)""")

    wplus = NArray(label=":math:`w_{+}`",
                   default=numpy.array([
                       1.5,
                   ]),
                   domain=Range(lo=0.5, hi=3., step=0.05),
                   doc="""Synaptic coupling strength [w+] (dimensionless)""")

    wminus = NArray(label=":math:`w_{-}`",
                    default=numpy.array([
                        1.,
                    ]),
                    domain=Range(lo=0.2, hi=2., step=0.05),
                    doc="""Synaptic coupling strength [w-] (dimensionless)""")

    NMAX = NArray(dtype=numpy.int,
                  label=":math:`N_{MAX}`",
                  default=numpy.array([
                      8,
                  ], dtype=numpy.int32),
                  domain=Range(lo=2, hi=8, step=1),
                  doc="""This is a magic number as given in the original code.
        It is used to compute the phi and psi -- computationally expensive --
        functions""")

    pool_nodes = NArray(
        label=":math:`p_{nodes}`",
        default=numpy.array([
            74.0,
        ]),
        domain=Range(lo=1.0, hi=74.0, step=1.0),
        doc="""Scale coupling weight sby the number of nodes in the network""")

    a = NArray(label=":math:`a`",
               default=numpy.array([
                   0.80823563,
               ]),
               domain=Range(lo=0.80, hi=0.88, step=0.01),
               doc=""".""")

    b = NArray(label=":math:`b`",
               default=numpy.array([
                   67.06177975,
               ]),
               domain=Range(lo=66.0, hi=69.0, step=0.5),
               doc=""".""")

    ve = NArray(label=":math:`ve`",
                default=numpy.array([
                    -52.5,
                ]),
                domain=Range(lo=-50.0, hi=-45.0, step=0.2),
                doc=""".""")

    vi = NArray(label=":math:`vi`",
                default=numpy.array([
                    -52.5,
                ]),
                domain=Range(lo=-50.0, hi=-45.0, step=0.2),
                doc=""".""")

    W = NArray(label=":math:`W`",
               default=numpy.array([
                   1.65,
               ]),
               domain=Range(lo=1.4, hi=1.9, step=0.05),
               doc="""Global scaling weight [W] (dimensionless)""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("E", "I"),
        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`
                                    and :math:`I = 1`.""")

    # Informational attribute, used for phase-plane and initial()
    state_variable_range = Final(
        {
            "E": numpy.array([0.001, 0.01]),
            "I": numpy.array([0.001, 0.01])
        },
        label="State Variable ranges [lo, hi]",
        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.
            The corresponding state-variable units for this model are kHz.""")

    state_variables = ["E", "I"]
    _nvar = 2
    cvar = numpy.array([0, 1], dtype=numpy.int32)

    def configure(self):
        """  """
        super(BrunelWang, self).configure()
        self.update_derived_parameters()

        self.psi_table = PsiTable.from_file()
        self.nerf_table = NerfTable.from_file()

        self.psi_table.configure()
        self.nerf_table.configure()

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        """
        .. math::
             \tau_e*\\dot{\nu_e}(t) &= -\nu_e(t) + \\phi_e \\\\
             \tau_i*\\dot{\nu_i}(t) &= -\nu_i(t) + \\phi_i \\\\
             ve &= - (V_thr - V_reset) \\, \nu_e \\, \tau_e + \\mu_e \\\\
             vi &= - (V_thr - V_reset) \\, \nu_i \\, \tau_i + \\mu_i \\\\

             \tau_X &= \\frac{C_m_X}{g_m_x  \\, S_X} \\\\
             S_X &= 1 + Text \\, \nu_ext + T_ampa \\, \nu_X + (rho_1 + rho_2)
                     \\, \\psi(\nu_X) + T_XI \\, \\nu_I \\\\
             \\mu_X &= \\frac{(Text \\, \\nu_X + T_AMPA \\, \\nu_X + \\rho_1 \\,
                        \\psi(\nu_X)) \\, (V_E - V_L)}{S_X} +
                        \\frac{\\rho_2 \\, \\psi(\nu_X) \\,(\\bar{V_X} - V_L) +
                        T_xI \\, \\nu_I \\, (V_I - V_L)}{S_X} \\\\
            sigma_X^2 &= \\frac{g_AMPA_ext^2(\\bar{V_X} - V_X)^2 \\, C_ext \\, nu_ext
                        \\tau_AMPA^2 \\, \\tau_X}{g_m_X^2 * \\tau_m_X^2} \\\\
            \\rho_1 &= {g_NMDA * C}{g_m_X * J} \\\\
            \\rho_2 &= \\beta \\frac{g_NMDA * C (\\bar{V_X} - V_E)(J - 1)}
                        {g_m_X * J^2} \\\\
            J_X &= 1 + \\gamma \\,\exp(-\\beta*\\bar{V_X}) \\\\
            \\phi(\mu_X, \\sigma_X) &= (\\tau_rp_X + \\tau_X \\, \\int
                                         \exp(u^2) * (\\erf(u) + 1))^-1

        The NMDA gating variable
        .. math::
            \\psi(\\nu)
        has been approximated by the exponential function:
        .. math::
            \\psi(\\nu) &= a * (1 - \exp(-b * \\nu)) \\\\
            a &= 0.80823563 \\\\
            b &= 67.06177975

        The post-synaptic rate as described by the :math:`\\phi` function
        constitutes a non-linear input-output relationship between the firing
        rate of the post-synaptic neuron and the average firing rates
        :math:`\\nu_{E}` and :math:`\\nu_{I}` of the pre-synaptic excitatory and
        inhibitory neural populations. This input-output function is
        conceptually equivalent to the simple threshold-linear or sigmoid
        input-output functions routinely used in firing-rate models. What it is
        gained from using the integral form is a firing-rate model that captures
        many of the underlying biophysics of the real spiking neurons.[BW_2001]_

        """

        E = state_variables[0, :]
        I = state_variables[1, :]
        # A = state_variables[2, :]

        # where and how to add local coupling

        c_0 = coupling[0, :]
        c_2 = coupling[1, :]

        # AMPA synapses (E --> E, and E --> I)
        vn_e = c_0
        vn_i = E * self.wminus * self.pool_fractions

        # NMDA synapses (E --> E, and E --> I)
        vN_e = c_2
        vN_i = E * self.wminus * self.pool_fractions

        # GABA (A) synapses (I --> E, and I --> I)
        vni_e = self.wminus * I  # I --> E
        vni_i = self.wminus * I  # I --> I

        J_e = 1 + self.cgamma * numpy.exp(-self.cbeta * self.ve)
        J_i = 1 + self.cgamma * numpy.exp(-self.cbeta * self.vi)

        rho1_e = self.crho1_e / J_e
        rho1_i = self.crho1_i / J_i
        rho2_e = self.crho2_e * (self.ve - self.VE) * (J_e - 1) / J_e**2
        rho2_i = self.crho2_i * (self.vi - self.VI) * (J_i - 1) / J_i**2

        vS_e = 1 + self.Text_e * self.nuext + self.TAMPA_e * vn_e + \
               (rho1_e + rho2_e) * vN_e + self.T_ei * vni_e
        vS_i = 1 + self.Text_i * self.nuext + self.TAMPA_i * vn_i + \
               (rho1_i + rho2_i) * vN_i + self.T_ii * vni_i

        vtau_e = self.Cm_e / (self.gm_e * vS_e)
        vtau_i = self.Cm_i / (self.gm_i * vS_i)

        vmu_e = (rho2_e * vN_e * self.ve + self.T_ei * vni_e * self.VI + \
                 self.VL) / vS_e

        vmu_i = (rho2_i * vN_i * self.vi + self.T_ii * vni_i * self.VI + \
                 self.VL) / vS_i

        vsigma_e = numpy.sqrt((self.ve - self.VE) ** 2 * vtau_e * \
                              self.csigma_e * self.nuext)
        vsigma_i = numpy.sqrt((self.vi - self.VE) ** 2 * vtau_i * \
                              self.csigma_i * self.nuext)

        # tauAMPA_over_vtau_e
        k_e = self.tauAMPA / vtau_e
        k_i = self.tauAMPA / vtau_i

        # integration limits
        alpha_e = (self.Vthr - vmu_e) / vsigma_e * (1.0 + 0.5 * k_e) + \
                  1.03 * numpy.sqrt(k_e) - 0.5 * k_e
        alpha_e = numpy.where(alpha_e > 19, 19, alpha_e)
        alpha_i = (self.Vthr - vmu_i) / vsigma_i * (1.0 + 0.5 * k_i) + \
                  1.03 * numpy.sqrt(k_i) - 0.5 * k_i
        alpha_i = numpy.where(alpha_i > 19, 19, alpha_i)

        beta_e = (self.Vreset - vmu_e) / vsigma_e
        beta_e = numpy.where(beta_e > 19, 19, beta_e)

        beta_i = (self.Vreset - vmu_i) / vsigma_i
        beta_i = numpy.where(beta_i > 19, 19, beta_i)

        v_ae = self.nerf_table.search_value(alpha_e)
        v_ai = self.nerf_table.search_value(alpha_i)
        v_be = self.nerf_table.search_value(beta_e)
        v_bi = self.nerf_table.search_value(beta_e)

        v_integral_e = v_ae - v_be
        v_integral_i = v_ai - v_bi

        Phi_e = 1 / (self.taurp_e +
                     vtau_e * numpy.sqrt(numpy.pi) * v_integral_e)
        Phi_i = 1 / (self.taurp_i +
                     vtau_i * numpy.sqrt(numpy.pi) * v_integral_i)

        self.ve = -(self.Vthr - self.Vreset) * E * vtau_e + vmu_e
        self.vi = -(self.Vthr - self.Vreset) * I * vtau_i + vmu_i

        dE = (-E + Phi_e) / vtau_e
        dI = (-I + Phi_i) / vtau_i

        derivative = numpy.array([dE, dI])
        return derivative

    def update_derived_parameters(self):
        """
        Derived parameters

        """

        self.pool_fractions = 1. / (self.pool_nodes * 2)

        self.tauNMDA = self.calpha * self.tauNMDArise * self.tauNMDAdecay
        self.Text_e = (self.gAMPAext_e * self.Cext * self.tauAMPA) / self.gm_e
        self.Text_i = (self.gAMPAext_i * self.Cext * self.tauAMPA) / self.gm_i
        self.TAMPA_e = (self.gAMPArec_e * self.C * self.tauAMPA) / self.gm_e
        self.TAMPA_i = (self.gAMPArec_i * self.C * self.tauAMPA) / self.gm_i
        self.T_ei = (self.gGABA_e * self.C * self.tauGABA) / self.gm_e
        self.T_ii = (self.gGABA_i * self.C * self.tauGABA) / self.gm_i

        self.crho1_e = (self.gNMDA_e * self.C) / self.gm_e
        self.crho1_i = (self.gNMDA_i * self.C) / self.gm_i
        self.crho2_e = self.cbeta * self.crho1_e
        self.crho2_i = self.cbeta * self.crho1_i

        self.csigma_e = (self.gAMPAext_e ** 2 * self.Cext * self.tauAMPA ** 2) / \
                        (self.gm_e * self.taum_e) ** 2
        self.csigma_i = (self.gAMPAext_i ** 2 * self.Cext * self.tauAMPA ** 2) / \
                        (self.gm_i * self.taum_i) ** 2
示例#9
0
% if svboundaries:
    state_variable_boundaries = Final(
        label="State Variable boundaries [lo, hi]",
        default={\
%for limit in dynamics.state_variables:
% if (limit.boundaries!='None' and limit.boundaries!=''):
"${limit.name}": numpy.array([${limit.boundaries}])\
% endif
%endfor
},
    )
% endif \

    variables_of_interest = List(
        of=str,
        label="Variables or quantities available to Monitors",
        choices=(\
%for itemJ in exposures:
%if {loop.first}:
%for choice in (itemJ.choices):
'${choice}', \
%endfor
),
        default=(\
%for defa in (itemJ.default):
'${defa}', \
%endfor
%endif
%endfor
),
        doc="${itemJ.description}"
示例#10
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 = 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]),
            "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_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 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
        """)

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("E", "I", "C_ee", "C_ei", "C_ii", "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`, :math:`C_ee = 2`, :math:`C_ei = 3`, :math:`C_ii = 4` and :math:`W = 5`."""
    )

    state_variables = 'E I C_ee C_ei C_ii W_e W_i noise'.split()
    _nvar = 8

    def dfun(self, state_variables, coupling, local_coupling=0.00):
        r"""
        .. math::
            \forall \mu,\lambda,\eta \in \{e,i\}^3\, ,
            \left\{
            \begin{split}
            T \, \frac{\partial \nu_\mu}{\partial t} = & (\mathcal{F}_\mu - \nu_\mu )
            + \frac{1}{2} \, c_{\lambda \eta} \,
            \frac{\partial^2 \mathcal{F}_\mu}{\partial \nu_\lambda \partial \nu_\eta} \\
            T \, \frac{\partial c_{\lambda \eta} }{\partial t}  =  & A_{\lambda \eta} +
            (\mathcal{F}_\lambda - \nu_\lambda ) \, (\mathcal{F}_\eta - \nu_\eta ) + \\
            & c_{\lambda \mu} \frac{\partial \mathcal{F}_\mu}{\partial \nu_\lambda} +
            c_{\mu \eta} \frac{\partial \mathcal{F}_\mu}{\partial \nu_\eta}
            - 2  c_{\lambda \eta}
            \end{split}
            \right.
            dot{W}_k &= W_k/tau_w-b*E_k  \\

            with:
            A_{\lambda \eta} =
            \left\{
            \begin{split}
            \frac{\mathcal{F}_\lambda \, (1/T - \mathcal{F}_\lambda)}{N_\lambda}
            \qquad & \textrm{if  } \lambda=\eta \\
            0 \qquad & \textrm{otherwise}
            \end{split}
            \right.

        """
        #number of neurons
        N_e = self.N_tot * (1 - self.g)
        N_i = self.N_tot * self.g
        #state variable
        E = state_variables[0, :]
        I = state_variables[1, :]
        C_ee = state_variables[2, :]
        C_ei = state_variables[3, :]
        C_ii = state_variables[4, :]
        W_e = state_variables[5, :]
        W_i = state_variables[6, :]
        noise = state_variables[7, :]
        derivative = numpy.empty_like(state_variables)

        # long-range coupling
        c_0 = coupling[0, :]

        # short-range (local) coupling
        lc_E = local_coupling * E
        lc_I = local_coupling * I

        # external firing rate for the different population
        E_input_excitatory = c_0 + lc_E + self.external_input_ex_ex + self.weight_noise * noise
        index_bad_input = numpy.where(E_input_excitatory < 0)
        E_input_excitatory[index_bad_input] = 0.0
        E_input_inhibitory = c_0 + lc_E + self.external_input_in_ex + self.weight_noise * noise
        index_bad_input = numpy.where(E_input_inhibitory < 0)
        E_input_inhibitory[index_bad_input] = 0.0
        I_input_excitatory = lc_I + self.external_input_ex_in
        I_input_inhibitory = lc_I + self.external_input_in_in

        # Transfer function of excitatory and inhibitory neurons
        _TF_e = self.TF_excitatory(E, I, E_input_excitatory,
                                   I_input_excitatory, W_e)
        _TF_i = self.TF_inhibitory(E, I, E_input_inhibitory,
                                   I_input_inhibitory, W_i)

        # Derivatives taken numerically : use a central difference formula with spacing `dx`
        df = 1e-7

        def _diff_fe(TF, fe, fi, fe_ext, fi_ext, W, df=df):
            return (TF(fe + df, fi, fe_ext, fi_ext, W) -
                    TF(fe - df, fi, fe_ext, fi_ext, W)) / (2 * df * 1e3)

        def _diff_fi(TF, fe, fi, fe_ext, fi_ext, W, df=df):
            return (TF(fe, fi + df, fe_ext, fi_ext, W) -
                    TF(fe, fi - df, fe_ext, fi_ext, W)) / (2 * df * 1e3)

        def _diff2_fe_fe_e(fe, fi, fe_ext, fi_ext, W, df=df):
            TF = self.TF_excitatory
            return (TF(fe + df, fi, fe_ext, fi_ext, W) - 2 * _TF_e +
                    TF(fe - df, fi, fe_ext, fi_ext, W)) / ((df * 1e3)**2)

        def _diff2_fe_fe_i(fe, fi, fe_ext, fi_ext, W, df=df):
            TF = self.TF_inhibitory
            return (TF(fe + df, fi, fe_ext, fi_ext, W) - 2 * _TF_i +
                    TF(fe - df, fi, fe_ext, fi_ext, W)) / ((df * 1e3)**2)

        def _diff2_fi_fe(TF, fe, fi, fe_ext, fi_ext, W, df=df):
            return (_diff_fi(TF, fe + df, fi, fe_ext, fi_ext, W) - _diff_fi(
                TF, fe - df, fi, fe_ext, fi_ext, W)) / (2 * df * 1e3)

        def _diff2_fe_fi(TF, fe, fi, fe_ext, fi_ext, W, df=df):
            return (_diff_fe(TF, fe, fi + df, fe_ext, fi_ext, W) - _diff_fe(
                TF, fe, fi - df, fe_ext, fi_ext, W)) / (2 * df * 1e3)

        def _diff2_fi_fi_e(fe, fi, fe_ext, fi_ext, W, df=df):
            TF = self.TF_excitatory
            return (TF(fe, fi + df, fe_ext, fi_ext, W) - 2 * _TF_e +
                    TF(fe, fi - df, fe_ext, fi_ext, W)) / ((df * 1e3)**2)

        def _diff2_fi_fi_i(fe, fi, fe_ext, fi_ext, W, df=df):
            TF = self.TF_inhibitory
            return (TF(fe, fi + df, fe_ext, fi_ext, W) - 2 * _TF_i +
                    TF(fe, fi - df, fe_ext, fi_ext, W)) / ((df * 1e3)**2)

        #Precompute some result
        _diff_fe_TF_e = _diff_fe(self.TF_excitatory, E, I, E_input_excitatory,
                                 I_input_excitatory, W_e)
        _diff_fe_TF_i = _diff_fe(self.TF_inhibitory, E, I, E_input_inhibitory,
                                 I_input_inhibitory, W_i)
        _diff_fi_TF_e = _diff_fi(self.TF_excitatory, E, I, E_input_excitatory,
                                 I_input_excitatory, W_e)
        _diff_fi_TF_i = _diff_fi(self.TF_inhibitory, E, I, E_input_inhibitory,
                                 I_input_inhibitory, W_i)

        # equation is inspired from github of Zerlaut :
        # https://github.com/yzerlaut/notebook_papers/blob/master/modeling_mesoscopic_dynamics/mean_field/master_equation.py
        # Excitatory firing rate derivation
        derivative[0] = (
            _TF_e - E + .5 * C_ee * _diff2_fe_fe_e(
                E, I, E_input_excitatory, I_input_excitatory, W_e) + .5 *
            C_ei * _diff2_fe_fi(self.TF_excitatory, E, I, E_input_excitatory,
                                I_input_excitatory, W_e) + .5 * C_ei *
            _diff2_fi_fe(self.TF_excitatory, E, I, E_input_excitatory,
                         I_input_excitatory, W_e) +
            .5 * C_ii * _diff2_fi_fi_e(E, I, E_input_excitatory,
                                       I_input_excitatory, W_e)) / self.T
        # Inhibitory firing rate derivation
        derivative[1] = (
            _TF_i - I + .5 * C_ee * _diff2_fe_fe_i(
                E, I, E_input_inhibitory, I_input_inhibitory, W_i) + .5 *
            C_ei * _diff2_fe_fi(self.TF_inhibitory, E, I, E_input_inhibitory,
                                I_input_inhibitory, W_i) + .5 * C_ei *
            _diff2_fi_fe(self.TF_inhibitory, E, I, E_input_inhibitory,
                         I_input_inhibitory, W_i) +
            .5 * C_ii * _diff2_fi_fi_i(E, I, E_input_inhibitory,
                                       I_input_inhibitory, W_i)) / self.T
        # Covariance excitatory-excitatory derivation
        derivative[2] = (_TF_e * (1. / self.T - _TF_e) / N_e +
                         (_TF_e - E)**2 + 2. * C_ee * _diff_fe_TF_e +
                         2. * C_ei * _diff_fi_TF_i - 2. * C_ee) / self.T
        # Covariance excitatory-inhibitory or inhibitory-excitatory derivation
        derivative[3] = (
            (_TF_e - E) *
            (_TF_i - I) + C_ee * _diff_fe_TF_e + C_ei * _diff_fe_TF_i +
            C_ei * _diff_fi_TF_e + C_ii * _diff_fi_TF_i - 2. * C_ei) / self.T
        # Covariance inhibitory-inhibitory derivation
        derivative[4] = (_TF_i * (1. / self.T - _TF_i) / N_i +
                         (_TF_i - I)**2 + 2. * C_ii * _diff_fi_TF_i +
                         2. * C_ei * _diff_fe_TF_e - 2. * C_ii) / self.T

        # Adaptation excitatory
        mu_V, sigma_V, T_V = self.get_fluct_regime_vars(
            E, I, E_input_excitatory, I_input_excitatory, W_e, self.Q_e,
            self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L,
            self.C_m, self.E_L_e, self.N_tot, self.p_connect_e,
            self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i)
        derivative[5] = -W_e / self.tau_w_e + self.b_e * E + self.a_e * (
            mu_V - self.E_L_e) / self.tau_w_e

        # Adaptation inhibitory
        mu_V, sigma_V, T_V = self.get_fluct_regime_vars(
            E, I, E_input_inhibitory, I_input_inhibitory, W_i, self.Q_e,
            self.tau_e, self.E_e, self.Q_i, self.tau_i, self.E_i, self.g_L,
            self.C_m, self.E_L_i, self.N_tot, self.p_connect_e,
            self.p_connect_i, self.g, self.K_ext_e, self.K_ext_i)
        derivative[6] = -W_i / self.tau_w_i + self.b_i * I + self.a_i * (
            mu_V - self.E_L_i) / self.tau_w_i

        derivative[7] = -noise / self.tau_OU
        return derivative
示例#11
0
class HindmarshRose(models.Model):
    """
    The Hindmarsh-Rose model is a mathematically simple model for repetitive
    bursting.
    
    .. [HR_1984] Hindmarsh, J. L., and Rose, R. M., *A model of neuronal 
        bursting using three coupled first order differential equations*, 
        Proceedings of the Royal society of London. Series B. Biological 
        sciences 221: 87, 1984.
    
    The models (:math:`x`, :math:`y`) phase-plane, including a representation of
    the vector field as well as its nullclines, using default parameters, can be
    seen below:
        
        .. _phase-plane-HMR:
        .. figure :: img/HindmarshRose_01_mode_0_pplane.svg
            :alt: Hindmarsh-Rose phase plane (x, y)
            
            The (:math:`x`, :math:`y`) phase-plane for the Hindmarsh-Rose model.
    

    .. automethod:: HindmarshRose.dfun
    
    """

    # Define traited attributes for this model, these represent possible kwargs.
    r = NArray(
        label=":math:`r`",
        default=numpy.array([0.001]),
        domain=Range(lo=0.0, hi=1.0, step=0.001),
        doc="""Adaptation parameter, governs time-scale of the state variable
        :math:`z`.""")

    a = NArray(label=":math:`a`",
               default=numpy.array([1.0]),
               domain=Range(lo=0.0, hi=1.0, step=0.01),
               doc="""Dimensionless parameter, governs x-nullcline""")

    b = NArray(label=":math:`b`",
               default=numpy.array([3.0]),
               domain=Range(lo=0.0, hi=3.0, step=0.01),
               doc="""Dimensionless parameter, governs x-nullcline""")

    c = NArray(label=":math:`c`",
               default=numpy.array([1.0]),
               domain=Range(lo=0.0, hi=1.0, step=0.01),
               doc="""Dimensionless parameter, governs y-nullcline""")

    d = NArray(label=":math:`d`",
               default=numpy.array([5.0]),
               domain=Range(lo=0.0, hi=5.0, step=0.01),
               doc="""Dimensionless parameter, governs y-nullcline""")

    s = NArray(label=":math:`s`",
               default=numpy.array([1.0]),
               domain=Range(lo=0.0, hi=1.0, step=0.01),
               doc="""Adaptation parameter, governs feedback""")

    x_1 = NArray(label=":math:`x_{1}`",
                 default=numpy.array([-1.6]),
                 domain=Range(lo=-1.6, hi=1.0, step=0.01),
                 doc="""Governs leftmost equilibrium point of x""")

    # Used for phase-plane axis ranges and to bound random initial() conditions.
    state_variable_range = Final(
        {
            "x": numpy.array([-4.0, 4.0]),
            "y": numpy.array([-60.0, 20.0]),
            "z": numpy.array([-2.0, 18.0])
        },
        label="State Variable ranges [lo, hi]",
        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.""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("x", "y", "z"),
        default="x",
        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:`x = 0`,
        :math:`y = 1`,and :math:`z = 2`.""")

    state_variables = ["x", "y", "z"]
    _nvar = 3
    cvar = numpy.array([0], dtype=numpy.int32)

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        """
        As in the FitzHugh-Nagumo model ([FH_1961]_), :math:`x` and :math:`y`
        signify the membrane potential and recovery variable respectively.
        Unlike FitzHugh-Nagumo model, the recovery variable :math:`y` is
        quadratic, modelling subthreshold inward current. The third
        state-variable, :math:`z` signifies a slow outward current which leads
        to adaptation ([HR_1984]_):
            
            .. math::
                \\dot{x} &= y - a \\, x^3 + b \\, x^2 - z + I \\\\
                \\dot{y} &= c - d \\, x^2 - y \\\\
                \\dot{z} &= r \\, ( s \\, (x - x_1) - z )
        
        where external currents :math:`I` provide the entry point for local and
        long-range connectivity. Default parameters are set as per Figure 6 of
        [HR_1984]_ so that the model shows repetitive bursting when :math:`I=2`.
        
        """

        x = state_variables[0, :]
        y = state_variables[1, :]
        z = state_variables[2, :]

        c_0 = coupling[0, :]

        dx = y - self.a * x**3 + self.b * x**2 - z + c_0 + local_coupling * x
        dy = self.c - self.d * x**2 - y
        dz = self.r * (self.s * (x - self.x_1) - z)

        derivative = numpy.array([dx, dy, dz])

        return derivative
示例#12
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', '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.

    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=50., 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=50., hi=150., 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""")

    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([
                       0.0,
                   ]),
                   domain=Range(lo=0.0, hi=1.0, step=0.01),
                   doc="""Inhibitory global coupling scaling""")

    state_variable_range = Final(
        {
            "S_e": numpy.array([0.0, 1.0]),
            "S_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'),
        default=('S_e', 'S_i'),
        doc="""default state variables to be monitored""")

    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.0
        S[S > 1] = 1.0

        c_0 = coupling[0, :]

        # if applicable
        lc_0 = local_coupling * S[0]

        coupling = self.G * self.J_N * (c_0 + lc_0)

        J_N_S_e = self.J_N * S[0]

        # TODO: Confirm that this combutation is correct for this model depending on the r_e and r_i values!
        x_e = self.w_p * J_N_S_e - self.J_i * S[
            1] + self.W_e * self.I_o + coupling

        x_e = self.a_e * x_e - self.b_e
        H_e = numpy.where(self.r_e >= 0, self.r_e,
                          x_e / (1 - numpy.exp(-self.d_e * x_e)))

        dS_e = -(S[0] / self.tau_e) + (1 - S[0]) * H_e * self.gamma_e

        x_i = J_N_S_e - S[1] + self.W_i * self.I_o + self.lamda * coupling

        x_i = self.a_i * x_i - self.b_i
        H_i = numpy.where(self.r_i >= 0, self.r_i,
                          x_i / (1 - numpy.exp(-self.d_i * x_i)))

        dS_i = -(S[1] / self.tau_i) + H_i * self.gamma_i

        derivative = numpy.array([dS_e, dS_i])

        return derivative

    def dfun(self, 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.r_e,
                            self.a_i, self.b_i, self.d_i, self.gamma_i,
                            self.tau_i, self.W_i, self.J_i, self.r_i, self.G,
                            self.lamda, self.I_o)
        return deriv.T[..., numpy.newaxis]
示例#14
0
class SimulatorAdapterModel(ViewModel, Simulator):

    @property
    def linked_has_traits(self):
        return Simulator

    connectivity = DataTypeGidAttr(
        linked_datatype=Connectivity,
        required=Simulator.connectivity.required,
        label=Simulator.connectivity.label,
        doc=Simulator.connectivity.doc
    )

    surface = Attr(
        field_type=CortexViewModel,
        label=Simulator.surface.label,
        default=Simulator.surface.default,
        required=Simulator.surface.required,
        doc=Simulator.surface.doc
    )

    stimulus = DataTypeGidAttr(
        linked_datatype=SpatioTemporalPattern,
        label=Simulator.stimulus.label,
        default=Simulator.stimulus.default,
        required=Simulator.stimulus.required,
        doc=Simulator.stimulus.doc
    )

    history_gid = DataTypeGidAttr(
        linked_datatype=SimulationHistory,
        required=False
    )

    integrator = Attr(
        field_type=IntegratorViewModel,
        label=Simulator.integrator.label,
        default=HeunDeterministicViewModel(),
        required=Simulator.integrator.required,
        doc=Simulator.integrator.doc
    )

    monitors = List(
        of=MonitorViewModel,
        label=Simulator.monitors.label,
        default=(TemporalAverageViewModel(),),
        doc=Simulator.monitors.doc
    )

    def __init__(self):
        super(SimulatorAdapterModel, self).__init__()
        self.coupling = type(self.coupling)()
        self.model = type(self.model)()
        self.integrator = type(self.integrator)()
        self.monitors = (type(self.monitors[0])(),)

    @property
    def first_monitor(self):
        if isinstance(self.monitors[0], RawViewModel):
            if len(self.monitors) > 1:
                return self.monitors[1]
            else:
                return None
        return self.monitors[0]

    def determine_indexes_for_chosen_vars_of_interest(self):
        all_variables = self.model.__class__.variables_of_interest.element_choices
        chosen_variables = self.model.variables_of_interest
        indexes = self.get_variables_of_interest_indexes(all_variables, chosen_variables)
        return indexes

    @staticmethod
    def get_variables_of_interest_indexes(all_variables, chosen_variables):
        variables_of_interest_indexes = {}

        if not isinstance(chosen_variables, (list, tuple)):
            chosen_variables = [chosen_variables]

        for variable in chosen_variables:
            variables_of_interest_indexes[variable] = all_variables.index(variable)
        return variables_of_interest_indexes
示例#15
0
class EpileptorCodim3(ModelNumbaDfun):
    r"""
    .. [Saggioetal_2017] Saggio ML, Spiegler A, Bernard C, Jirsa VK.
    *Fast–Slow Bursters in the Unfolding of a High Codimension Singularity
    and the Ultra-slow Transitions of Classes.* Journal of Mathematical
    Neuroscience. 2017;7:7. doi:10.1186/s13408-017-0050-8.

    .. The Epileptor codim 3 model is a neural mass model which contains two
    subsystems acting at different timescales. For the fast subsystem we use
    the unfolding of a degenerate Takens-Bogdanov bifucation of codimension
    3. The slow subsystem steers the fast one back and forth along paths
    leading to bursting behavior. The model is able to produce almost all the
    classes of bursting predicted for systems with a planar fast subsystem.

    .. In this implementation the model can produce Hysteresis-Loop bursters
    of classes c0, c0', c2s, c3s, c4s, c10s, c11s, c2b, c4b, c8b, c14b and
    c16b as classified by [Saggioetal_2017] Table 2. The default model
    parameters correspond to class c2s.

    """

    mu1_start = NArray(
        label=":math:`mu_1 start`",
        default=numpy.array([-0.02285]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu1 at the offset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    mu2_start = NArray(
        label=":math:`mu_2 start`",
        default=numpy.array([0.3448]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation mu2 parameter at the offset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    nu_start = NArray(
        label=":math:`nu start`",
        default=numpy.array([0.2014]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation nu parameter at the offset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    mu1_stop = NArray(
        label=":math:`mu_1 stop`",
        default=numpy.array([-0.07465]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation mu1 parameter at the onset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    mu2_stop = NArray(
        label=":math:`mu_2 stop`",
        default=numpy.array([0.3351]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation mu2 parameter at the onset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    nu_stop = NArray(
        label=":math:`nu stop`",
        default=numpy.array([0.2053]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation nu parameter at the onset point for the given class, default for class c2s "
            "(Saddle-Node at onset and Saddle-Homoclinic at offset)")

    b = NArray(
        label=":math:`b`",
        default=numpy.array([1.0]),
        doc="Unfolding type of the degenerate Takens-Bogdanov bifurcation, default is a focus type")

    R = NArray(
        label=":math:`R`",
        default=numpy.array([0.4]),
        domain=Range(lo=0.0, hi=2.5),
        doc="Radius in unfolding")

    c = NArray(
        label=":math:`c`",
        default=numpy.array([0.001]),
        domain=Range(lo=0.0, hi=0.01),
        doc="Speed of the slow variable")

    dstar = NArray(
        label=":math:`d^*`",
        default=numpy.array([0.3]),
        domain=Range(lo=-0.1, hi=0.5),
        doc="Threshold for the inversion of the slow variable")

    Ks = NArray(
        label=":math:`K_s`",
        default=numpy.array([0.0]),
        doc="Slow permittivity coupling strength, the default is no coupling")

    N = NArray(
        dtype=int,
        label=":math:`N`",
        default=numpy.array([1]),
        doc="The branch of the resting state, default is 1")

    modification = NArray(
        dtype=bool,
        label=":math:`modification`",
        default=numpy.array([True]),
        doc="When modification is True, then use the modification to stabilise the system for negative values of "
            "dstar. If modification is False, then don't use the modification. The default value is True ")

    state_variable_range = Final(
        label="State variable ranges [lo, hi]",
        default={"x": numpy.array([0.4, 0.6]),
                 "y": numpy.array([-0.1, 0.1]),
                 "z": numpy.array([0.0, 0.15])},
        doc="Typical bounds on state variables.")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=('x', 'y', 'z'),
        default=('x', 'z'),
        doc="Quantities available to monitor.")

    # state variables names
    state_variables = ('x', 'y', 'z')

    # number of state variables
    _nvar = 3
    cvar = numpy.array([0], dtype=numpy.int32)

    # If there are derived parameters from the predefined parameters, then initialize them to None
    G = None
    H = None
    L = None
    M = None

    def update_derived_parameters(self):
        r"""
        The equations were taken from [Saggioetal_2017]
        cf. Eqn. (7), page 17

        Here we parametrize the great arc which lies on a sphere of radius R
        between the points A and B, which are given by:

            .. math::
                A &= \begin{pmatrix}\mu_{2,start} & -\mu_{1,start} & \nu_{start} \end{pmatrix} \\
                B &= \begin{pmatrix}\mu_{2,stop} & -\mu_{1,stop} & \nu_{stop} \end{pmatrix}

        Then we parametrize this great arc with z as parameter by :math:`R(E \cos z + F \sin z)`
            where the unit vectors E and F are given by:

            .. math::
                E &= A/\|A\| \\
                F &= ((A \times B) \times A)/\|(A \times B) \times A\|
        """

        A = numpy.array(
            [self.mu2_start[0], -self.mu1_start[0], self.nu_start[0]])
        B = numpy.array([self.mu2_stop[0], -self.mu1_stop[0], self.nu_stop[0]])

        self.E = A / numpy.linalg.norm(A)
        self.F = numpy.cross(numpy.cross(A, B), A)
        self.F = self.F / numpy.linalg.norm(self.F)

    def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0):
        x = state_variables[0, :]
        y = state_variables[1, :]
        z = state_variables[2, :]

        # Computes the values of mu2,mu1 and nu given the great arc (E,F,R) and the value of the slow variable z
        mu2 = self.R * (self.E[0] * numpy.cos(z) + self.F[0] * numpy.sin(z))
        mu1 = -self.R * (self.E[1] * numpy.cos(z) + self.F[1] * numpy.sin(z))
        nu = self.R * (self.E[2] * numpy.cos(z) + self.F[2] * numpy.sin(z))

        # Computes x_s, which is the solution to x_s^3 - mu2*x_s - mu1 = 0
        if self.N == 1:
            xs = (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) + (mu1 / 2.0 - numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0)
        elif self.N == 2:
            xs = -1.0 / 2.0 * (1.0 - 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) - 1.0 / 2.0 * (
                         1.0 + 1j * 3 ** (1.0 / 2.0)) * (
                             mu1 / 2.0 - numpy.sqrt(mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (
                         1.0 / 3.0)
        elif self.N == 3:
            xs = -1.0 / 2.0 * (1.0 + 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) - 1.0 / 2.0 * (
                         1.0 - 1j * 3 ** (1.0 / 2.0)) * (
                             mu1 / 2.0 - numpy.sqrt(mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (
                         1.0 / 3.0)
        xs = numpy.real(xs)

        xdot = -y
        ydot = x ** 3 - mu2 * x - mu1 - y * (nu + self.b * x + x ** 2)
        if self.modification:
            zdot = -self.c * (
                    numpy.sqrt((x - xs) ** 2 + y ** 2) - self.dstar + 0.1 * (z - 0.5) ** 7 + self.Ks * coupling[0, :])
        else:
            zdot = -self.c * (numpy.sqrt(
                (x - xs) ** 2 + y ** 2) - self.dstar + self.Ks * coupling[0, :])

        derivative = numpy.array([xdot, ydot, zdot])
        return derivative

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        r"""
        The equations were taken from [Saggioetal_2017]
        cf. Eqns. (4) and (7), page 17

        The state variables :math:`x` and :math:`y` correspond to the fast subsystem and the
        state variable :math:`z` corresponds to the slow subsystem.

        .. math::
                \dot{x} &= -y \\
                \dot{y} &= x^3 - \mu_{2} x - \mu_{1} - y(\nu + b x + x^2) \\
                \dot{z} &= -c(\sqrt{x-x_{s}^2+y^2} - d^*)

        If the bool modification is True, then the equation for :math:`\dot{z}` will
        been modified to ensure stability for negative :math:`d^x`

        .. math::
            \dot{z} = -c(\sqrt{x-x_{s}^2+y^2} - d^* + 0.1(z-0.5)^7)

        Where :math:`\mu_1, \mu_2` and :math:`\nu` lie on a great arc of a
        sphere of radius :math:`R` parametrised by the unit vectors :math:`E` and :math:`F`.

        .. math::
            \begin{pmatrix}\mu_2 & -\mu_1 & \nu \end{pmatrix} = R(E \cos z + F \sin z)

        And where :math:`x_s` is the x-coordinate of the resting state
        (stable equilibrium). This is computed by finding the solution of

            .. math::
                x_s^3 - mu_2*x_s - mu_1 = 0

        And taking the branch which corresponds to the resting state.
        If :math:`x_s` is complex, we take the real part.
        """
        state_variables_ = state_variables.reshape(state_variables.shape[:-1]).T
        coupling_ = coupling.reshape(coupling.shape[:-1]).T
        derivative = _numba_dfun(state_variables_, coupling_, self.E[0], self.E[1], self.E[2], self.F[0], self.F[1],
                                 self.F[2], self.b, self.R, self.c, self.dstar, self.Ks, self.modification, self.N)
        return derivative.T[..., numpy.newaxis]
示例#16
0
class LarterBreakspear(models.Model):
    """
    A modified Morris-Lecar model that includes a third equation which simulates
    the effect of a population of inhibitory interneurons synapsing on
    the pyramidal cells.
    
    .. [Larteretal_1999] Larter et.al. *A coupled ordinary differential equation
        lattice model for the simulation of epileptic seizures.* Chaos. 9(3):
        795, 1999.
    

    .. [Breaksetal_2003_a] Breakspear, M.; Terry, J. R. & Friston, K. J.  *Modulation of excitatory
        synaptic coupling facilitates synchronization and complex dynamics in an
        onlinear model of neuronal dynamics*. Neurocomputing 52–54 (2003).151–158

    .. [Breaksetal_2003_b] M. J. Breakspear et.al. *Modulation of excitatory 
        synaptic coupling facilitates synchronization and complex dynamics in a
        biophysical model of neuronal dynamics.* Network: Computation in Neural
        Systems 14: 703-732, 2003.
    
    Equations and default parameters are taken from [Breaksetal_2003_b]_. 
    All equations and parameters are non-dimensional and normalized.
    For values of d_v  < 0.55, the dynamics of a single column settles onto a 
    solitary fixed point attractor.


    Parameters used for simulations in [Breaksetal_2003_a]_ Table 1. Page 153.
    Two nodes were coupled.

    +---------------------------+
    |          Table 1          | 
    +--------------+------------+
    |Parameter     |  Value     |
    +--------------+------------+
    | I            |      0.3   |
    | a_ee         |      0.4   |
    | a_ei         |      0.1   |
    | a_ie         |      1.0   |
    | a_ne         |      1.0   |
    | a_ni         |      0.4   |
    | r_NMDA       |      0.2   |
    | delta        |      0.001 |
    +---------------------------+



    +---------------------------+
    |          Table 2          | 
    +--------------+------------+
    |Parameter     |  Value     |
    +--------------+------------+
    | gK           |      2.0   |
    | gL           |      0.5   |
    | gNa          |      6.7   |
    | gCa          |      1.0   |
    | a_ne         |      1.0   |
    | a_ni         |      0.4   |
    | a_ee         |      0.36  |
    | a_ei         |      2.0   |
    | a_ie         |      2.0   |
    | VK           |     -0.7   |
    | VL           |     -0.5   |
    | VNa          |      0.53  |
    | VCa          |      1.0   |
    | phi          |      0.7   | 
    | b            |      0.1   |
    | I            |      0.3   |
    | r_NMDA       |      0.25  |
    | C            |      0.1   |
    | TCa          |     -0.01  |
    | d_Ca         |      0.15  |
    | TK           |      0.0   |
    | d_K          |      0.3   |
    | VT           |      0.0   |
    | ZT           |      0.0   |
    | TNa          |      0.3   |
    | d_Na         |      0.15  |
    | d_V          |      0.65  |
    | d_Z          |      d_V   |  # note, this parameter might be spatialized: ones(N,1).*0.65 + modn*(rand(N,1)-0.5);
    | QV_max       |      1.0   |
    | QZ_max       |      1.0   |
    +---------------------------+
    |   Alstott et al. 2009     |
    +---------------------------+


    NOTES about parameters

    d_V
    For d_V < 0.55, uncoupled network, the system exhibits fixed point dynamics; 
    for 55 < lb.d_V < 0.59, limit cycle atractors; 
    and for d_V > 0.59 chaotic attractors (eg, d_V=0.6,aee=0.5,aie=0.5, 
                                               gNa=0, Iext=0.165)

    C
    The long-range coupling 'C' is ‘weak’ in the sense that 
    they investigated parameter values for which C < a_ee and C << a_ie.


    
    .. figure :: img/LarterBreakspear_01_mode_0_pplane.svg
            :alt: Larter-Breaskpear phase plane (V, W)
            
            The (:math:`V`, :math:`W`) phase-plane for the Larter-Breakspear model.
    
    """

    # Define traited attributes for this model, these represent possible kwargs.
    gCa = NArray(
        label=":math:`g_{Ca}`",
        default=numpy.array([1.1]),
        domain=Range(lo=0.9, hi=1.5, step=0.1),
        doc="""Conductance of population of Ca++ channels.""")

    gK = NArray(
        label=":math:`g_{K}`",
        default=numpy.array([2.0]),
        domain=Range(lo=1.95, hi=2.05, step=0.025),
        doc="""Conductance of population of K channels.""")

    gL = NArray(
        label=":math:`g_{L}`",
        default=numpy.array([0.5]),
        domain=Range(lo=0.45, hi=0.55, step=0.05),
        doc="""Conductance of population of leak channels.""")

    phi = NArray(
        label=":math:`\\phi`",
        default=numpy.array([0.7]),
        domain=Range(lo=0.3, hi=0.9, step=0.1),
        doc="""Temperature scaling factor.""")

    gNa = NArray(
        label=":math:`g_{Na}`",
        default=numpy.array([6.7]),
        domain=Range(lo=0.0, hi=10.0, step=0.1),
        doc="""Conductance of population of Na channels.""")

    TK = NArray(
        label=":math:`T_{K}`",
        default=numpy.array([0.0]),
        domain=Range(lo=0.0, hi=0.0001, step=0.00001),
        doc="""Threshold value for K channels.""")

    TCa = NArray(
        label=":math:`T_{Ca}`",
        default=numpy.array([-0.01]),
        domain=Range(lo=-0.02, hi=-0.01, step=0.0025),
        doc="Threshold value for Ca channels.")

    TNa = NArray(
        label=":math:`T_{Na}`",
        default=numpy.array([0.3]),
        domain=Range(lo=0.25, hi=0.3, step=0.025),
        doc="Threshold value for Na channels.")

    VCa = NArray(
        label=":math:`V_{Ca}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.9, hi=1.1, step=0.05),
        doc="""Ca Nernst potential.""")

    VK = NArray(
        label=":math:`V_{K}`",
        default=numpy.array([-0.7]),
        domain=Range(lo=-0.8, hi=1., step=0.1),
        doc="""K Nernst potential.""")

    VL = NArray(
        label=":math:`V_{L}`",
        default=numpy.array([-0.5]),
        domain=Range(lo=-0.7, hi=-0.4, step=0.1),
        doc="""Nernst potential leak channels.""")

    VNa = NArray(
        label=":math:`V_{Na}`",
        default=numpy.array([0.53]),
        domain=Range(lo=0.51, hi=0.55, step=0.01),
        doc="""Na Nernst potential.""")

    d_K = NArray(
        label=":math:`\\delta_{K}`",
        default=numpy.array([0.3]),
        domain=Range(lo=0.1, hi=0.4, step=0.1),
        doc="""Variance of K channel threshold.""")

    tau_K = NArray(
        label=":math:`\\tau_{K}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.01, hi=0.0, step=0.1),
        doc="""Time constant for K relaxation time (ms)""")

    d_Na = NArray(
        label=":math:`\\delta_{Na}`",
        default=numpy.array([0.15]),
        domain=Range(lo=0.1, hi=0.2, step=0.05),
        doc="Variance of Na channel threshold.")

    d_Ca = NArray(
        label=":math:`\\delta_{Ca}`",
        default=numpy.array([0.15]),
        domain=Range(lo=0.1, hi=0.2, step=0.05),
        doc="Variance of Ca channel threshold.")

    aei = NArray(
        label=":math:`a_{ei}`",
        default=numpy.array([2.0]),
        domain=Range(lo=0.1, hi=2.0, step=0.1),
        doc="""Excitatory-to-inhibitory synaptic strength.""")

    aie = NArray(
        label=":math:`a_{ie}`",
        default=numpy.array([2.0]),
        domain=Range(lo=0.5, hi=2.0, step=0.1),
        doc="""Inhibitory-to-excitatory synaptic strength.""")

    b = NArray(
        label=":math:`b`",
        default=numpy.array([0.1]),
        domain=Range(lo=0.0001, hi=1.0, step=0.0001),
        doc="""Time constant scaling factor. The original value is 0.1""")

    C = NArray(
        label=":math:`c`",
        default=numpy.array([0.0]),
        domain=Range(lo=0.0, hi=0.2, step=0.05),
        doc="""Strength of excitatory coupling. Balance between internal and
        local (and global) coupling strength. C > 0 introduces interdependences between 
        consecutive columns/nodes. C=1 corresponds to maximum coupling.
        This strenght should be set to sensible values when a whole network is connected. """)

    ane = NArray(
        label=":math:`a_{ne}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.4, hi=1.0, step=0.05),
        doc="""Non-specific-to-excitatory synaptic strength.""")

    ani = NArray(
        label=":math:`a_{ni}`",
        default=numpy.array([0.4]),
        domain=Range(lo=0.3, hi=0.5, step=0.05),
        doc="""Non-specific-to-inhibitory synaptic strength.""")

    aee = NArray(
        label=":math:`a_{ee}`",
        default=numpy.array([0.4]),
        domain=Range(lo=0.4, hi=0.6, step=0.05),
        doc="""Excitatory-to-excitatory synaptic strength.""")

    Iext = NArray(
        label=":math:`I_{ext}`",
        default=numpy.array([0.3]),
        domain=Range(lo=0.165, hi=0.3, step=0.005),
        doc="""Subcortical input strength. It represents a non-specific
       excitation or thalamic inputs.""")

    rNMDA = NArray(
        label=":math:`r_{NMDA}`",
        default=numpy.array([0.25]),
        domain=Range(lo=0.2, hi=0.3, step=0.05),
        doc="""Ratio of NMDA to AMPA receptors.""")

    VT = NArray(
        label=":math:`V_{T}`",
        default=numpy.array([0.0]),
        domain=Range(lo=0.0, hi=0.7, step=0.01),
        doc="""Threshold potential (mean) for excitatory neurons. 
        In [Breaksetal_2003_b]_ this values is 0.""")

    d_V = NArray(
        label=":math:`\\delta_{V}`",
        default=numpy.array([0.65]),
        domain=Range(lo=0.49, hi=0.7, step=0.01),
        doc="""Variance of the excitatory threshold. It is one of the main
        parameters explored in [Breaksetal_2003_b]_.""")

    ZT = NArray(
        label=":math:`Z_{T}`",
        default=numpy.array([0.0]),
        domain=Range(lo=0.0, hi=0.1, step=0.005),
        doc="""Threshold potential (mean) for inihibtory neurons.""")

    d_Z = NArray(
        label=":math:`\\delta_{Z}`",
        default=numpy.array([0.7]),
        domain=Range(lo=0.001, hi=0.75, step=0.05),
        doc="""Variance of the inhibitory threshold.""")

    # NOTE: the values were not in the article. 
    # I took these ones from DESTEXHE 2001
    QV_max = NArray(
        label=":math:`Q_{max}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.1, hi=1., step=0.001),
        doc="""Maximal firing rate for excitatory populations (kHz)""")

    QZ_max = NArray(
        label=":math:`Q_{max}`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.1, hi=1., step=0.001),
        doc="""Maximal firing rate for excitatory populations (kHz)""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("V", "W", "Z"),
        default=("V", "W", "Z"),
        doc="""This represents the default state-variables of this Model to be
        monitored. It can be overridden for each Monitor if desired.""")

    # Informational attribute, used for phase-plane and initial()
    state_variable_range = Final(
        {
            "V": numpy.array([-1.5, 1.5]),
            "W": numpy.array([-1.0, 1.0]),
            "Z": numpy.array([-1.5, 1.5])
        },
        label="State Variable ranges [lo, hi]",
        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.""")

    state_variables = ["V", "W", "Z"]
    _nvar = 3
    cvar = numpy.array([0], dtype=numpy.int32)

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        """
        .. math::
             \\dot{V} &= - (g_{Ca} + (1 - C) \\, r_{NMDA} \\, a_{ee} Q_V^i +
            C \\, r_{NMDA} \\, a_{ee} \\langle Q_V \\rangle) \\, m_{Ca} \\,(V - V_{Ca})
            - g_K\\, W\\, (V - V_K) - g_L\\, (V - V_L)
            - (g_{Na} m_{Na} + (1 - C) \\, a_{ee} Q_V^i + 
            C \\, a_{ee} \\langle Q_V \\rangle) \\, (V - V_{Na})
            - a_{ie}\\, Z \\, Q_Z^i + a_{ne} \\, I_{\\delta}
            
            \\dot{W} &= \\frac{\\phi \\, (m_K - W)}{\\tau_K} \\\\
            \\dot{Z} &= b \\, (a_{ni} \\, I_{\\delta} + a_{ei} \\, V \\, Q_V)\\\\
            
            m_{ion}(X) &= 0.5 \\, (1 + tanh(\\frac{V-T_{ion}}{\\delta_{ion}})
            
        See Equations (7), (3), (6) and (2) respectively in [Breaksetal_2003]_.
        Pag: 705-706

        NOTE: Equation (8) has an error the sign before the term :math:`a_{ie}\\, Z \\, Q_Z^i`
        should be a minus (-) and not a plus (+).
        
        """
        V = state_variables[0, :]
        W = state_variables[1, :]
        Z = state_variables[2, :]

        c_0 = coupling[0, :]
        lc_0 = local_coupling

        # relationship between membrane voltage and channel conductance
        m_Ca = 0.5 * (1 + numpy.tanh((V - self.TCa) / self.d_Ca))
        m_Na = 0.5 * (1 + numpy.tanh((V - self.TNa) / self.d_Na))
        m_K = 0.5 * (1 + numpy.tanh((V - self.TK) / self.d_K))

        # voltage to firing rate
        QV = 0.5 * self.QV_max * (1 + numpy.tanh((V - self.VT) / self.d_V))
        QZ = 0.5 * self.QZ_max * (1 + numpy.tanh((Z - self.ZT) / self.d_Z))

        dV = (- (self.gCa + (
                    1.0 - self.C) * self.rNMDA * self.aee * QV + self.C * self.rNMDA * self.aee * c_0) * m_Ca * (
                          V - self.VCa) - self.gK * W * (V - self.VK) - self.gL * (V - self.VL) - (
                          self.gNa * m_Na + (1.0 - self.C) * self.aee * QV + self.C * self.aee * c_0) * (
                          V - self.VNa) - self.aei * Z * QZ + self.ane * self.Iext)

        dW = (self.phi * (m_K - W) / self.tau_K)

        dZ = (self.b * (self.ani * self.Iext + self.aei * V * QV))

        derivative = numpy.array([dV, dW, dZ])

        return derivative
示例#17
0
 class A(HasTraits):
     picked_dimensions = List(of=str,
                              default=('time', 'space'),
                              choices=('time', 'space', 'measurement'))
示例#18
0
class Simulator(SimulatorTVB):

    tvb_nest_interface = None
    run_spiking_simulator = None

    model = Attr(
        field_type=models.Model,
        label="Local dynamic model",
        default=ReducedWongWangExcIOInhI(),
        required=True,
        doc="""A tvb.simulator.Model object which describes the local dynamic
            equations, their parameters, and, to some extent, where connectivity
            (local and long-range) enters and which state-variables the Monitors
            monitor. By default the 'ReducedWongWang' model is used. Read the 
            Scientific documentation to learn more about this model.""")

    monitors = List(
        of=monitors.Monitor,
        label="Monitor(s)",
        default=(monitors.Raw(), ),
        doc="""A tvb.simulator.Monitor or a list of tvb.simulator.Monitor
            objects that 'know' how to record relevant data from the simulation. Two
            main types exist: 1) simple, spatial and temporal, reductions (subsets
            or averages); 2) physiological measurements, such as EEG, MEG and fMRI.
            By default the Model's specified variables_of_interest are returned,
            temporally downsampled from the raw integration rate to a sample rate of
            1024Hz.""")

    integrator = Attr(
        field_type=integrators.Integrator,
        label="Integration scheme",
        default=integrators.HeunStochastic(
            dt=float(int(numpy.round(0.1 / CONFIGURED.NEST_MIN_DT))) *
            CONFIGURED.NEST_MIN_DT),
        required=True,
        doc="""A tvb.simulator.Integrator object which is
                an integration scheme with supporting attributes such as 
                integration step size and noise specification for stochastic 
                methods. It is used to compute the time courses of the model state 
                variables.""")

    connectivity = Attr(
        field_type=connectivity.Connectivity,
        label="Long-range connectivity",
        default=CONFIGURED.DEFAULT_SUBJECT["connectivity"],
        required=True,
        doc="""A tvb.datatypes.Connectivity object which contains the
        structural long-range connectivity data (i.e., white-matter tracts). In
        combination with the ``Long-range coupling function`` it defines the inter-regional
        connections. These couplings undergo a time delay via signal propagation
        with a propagation speed of ``Conduction Speed``""")

    @property
    def config(self):
        try:
            return self.tvb_nest_interface.config
        except:
            return CONFIGURED

    def _configure_integrator_noise(self):
        """
        This enables having noise to be state variable specific and/or to enter
        only via specific brain structures, for example it we only want to
        consider noise as an external input entering the brain via appropriate
        thalamic nuclei.

        Support 3 possible shapes:
            1) number_of_nodes;

            2) number_of_state_variables; and

            3) (number_of_state_variables, number_of_nodes).

        """

        if self.integrator.noise.ntau > 0.0:
            LOG.warning(
                "Colored noise is currently not supported for tvb-nest simulations!\n"
                +
                "Setting integrator.noise.ntau = 0.0 and configuring white noise!"
            )
            self.integrator.noise.ntau = 0.0

        super(Simulator, self)._configure_integrator_noise()

    def bound_and_clamp(self, state):
        # If there is a state boundary...
        if self.integrator.state_variable_boundaries is not None:
            # ...use the integrator's bound_state
            self.integrator.bound_state(state)
        # If there is a state clamping...
        if self.integrator.clamped_state_variable_values is not None:
            # ...use the integrator's clamp_state
            self.integrator.clamp_state(state)

    def _update_and_bound_history(self, history):
        self.bound_and_clamp(history)
        # If there are non-state variables, they need to be updated for history:
        try:
            # Assuming that node_coupling can have a maximum number of dimensions equal to the state variables,
            # in the extreme case where all state variables are cvars as well, we set:
            node_coupling = numpy.zeros(
                (history.shape[0], 1, history.shape[2], 1))
            for i_time in range(history.shape[1]):
                self.model.update_non_state_variables(history[:, i_time],
                                                      node_coupling[:, 0], 0.0)
            self.bound_and_clamp(history)
        except:
            pass

    def _configure_history(self, initial_conditions):
        """
        Set initial conditions for the simulation using either the provided
        initial_conditions or, if none are provided, the model's initial()
        method. This method is called durin the Simulator's __init__().

        Any initial_conditions that are provided as an argument are expected
        to have dimensions 1, 2, and 3 with shapse corresponding to the number
        of state_variables, nodes and modes, respectively. If the provided
        inital_conditions are shorter in time (dim=0) than the required history
        the model's initial() method is called to make up the difference.

        """
        rng = numpy.random
        if hasattr(self.integrator, 'noise'):
            rng = self.integrator.noise.random_stream
        # Default initial conditions
        if initial_conditions is None:
            n_time, n_svar, n_node, n_mode = self.good_history_shape
            LOG.info(
                'Preparing initial history of shape %r using model.initial()',
                self.good_history_shape)
            if self.surface is not None:
                n_node = self.number_of_nodes
            history = self.model.initial(self.integrator.dt,
                                         (n_time, n_svar, n_node, n_mode), rng)
        # ICs provided
        else:
            # history should be [timepoints, state_variables, nodes, modes]
            LOG.info('Using provided initial history of shape %r',
                     initial_conditions.shape)
            n_time, n_svar, n_node, n_mode = ic_shape = initial_conditions.shape
            nr = self.connectivity.number_of_regions
            if self.surface is not None and n_node == nr:
                initial_conditions = initial_conditions[:, :, self._regmap]
                return self._configure_history(initial_conditions)
            elif ic_shape[1:] != self.good_history_shape[1:]:
                raise_value_error(
                    "Incorrect history sample shape %s, expected %s" %
                    ic_shape[1:], self.good_history_shape[1:])
            else:
                if ic_shape[0] >= self.horizon:
                    LOG.debug("Using last %d time-steps for history.",
                              self.horizon)
                    history = initial_conditions[
                        -self.horizon:, :, :, :].copy()
                else:
                    LOG.debug('Padding initial conditions with model.initial')
                    history = self.model.initial(self.integrator.dt,
                                                 self.good_history_shape, rng)
                    shift = self.current_step % self.horizon
                    history = numpy.roll(history, -shift, axis=0)
                    history[:ic_shape[0], :, :, :] = initial_conditions
                    history = numpy.roll(history, shift, axis=0)
                self.current_step += ic_shape[0] - 1
        # Make sure that history values are bounded,
        # and any possible non-state variables are initialized
        # based on state variable ones (but with no coupling yet...)
        self._update_and_bound_history(numpy.swapaxes(history, 0, 1))
        LOG.info('Final initial history shape is %r', history.shape)
        # create initial state from history
        self.current_state = history[self.current_step % self.horizon].copy()
        LOG.debug('initial state has shape %r' % (self.current_state.shape, ))
        if self.surface is not None and history.shape[
                2] > self.connectivity.number_of_regions:
            n_reg = self.connectivity.number_of_regions
            (nt, ns, _, nm), ax = history.shape, (2, 0, 1, 3)
            region_history = numpy.zeros((nt, ns, n_reg, nm))
            numpy_add_at(region_history.transpose(ax), self._regmap,
                         history.transpose(ax))
            region_history /= numpy.bincount(self._regmap).reshape((-1, 1))
            history = region_history
        # create history query implementation
        self.history = SparseHistory(self.connectivity.weights,
                                     self.connectivity.idelays,
                                     self.model.cvar,
                                     self.model.number_of_modes)
        # initialize its buffer
        self.history.initialize(history)

    def configure(self, tvb_nest_interface, full_configure=True):
        """Configure simulator and its components.

        The first step of configuration is to run the configure methods of all
        the Simulator's components, ie its traited attributes.

        Configuration of a Simulator primarily consists of calculating the
        attributes, etc, which depend on the combinations of the Simulator's
        traited attributes (keyword args).

        Converts delays from physical time units into integration steps
        and updates attributes that depend on combinations of the 6 inputs.

        Returns
        -------
        sim: Simulator
            The configured Simulator instance.

        """
        if full_configure:
            # When run from GUI, preconfigure is run separately, and we want to avoid running that part twice
            self.preconfigure()
            # Make sure spatialised model parameters have the right shape (number_of_nodes, 1)

        self.integrator.dt = float(
            int(numpy.round(
                0.1 / CONFIGURED.NEST_MIN_DT))) * CONFIGURED.NEST_MIN_DT

        excluded_params = ("state_variable_range", "state_variable_boundaries",
                           "variables_of_interest", "noise", "psi_table",
                           "nerf_table", "gid")
        spatial_reshape = self.model.spatial_param_reshape
        for param in type(self.model).declarative_attrs:
            if param in excluded_params:
                continue
            # If it's a surface sim and model parameters were provided at the region level
            region_parameters = getattr(self.model, param)
            if self.surface is not None:
                if region_parameters.size == self.connectivity.number_of_regions:
                    new_parameters = region_parameters[
                        self.surface.region_mapping].reshape(spatial_reshape)
                    setattr(self.model, param, new_parameters)
            region_parameters = getattr(self.model, param)
            if hasattr(
                    region_parameters,
                    "size") and region_parameters.size == self.number_of_nodes:
                new_parameters = region_parameters.reshape(spatial_reshape)
                setattr(self.model, param, new_parameters)
        # Configure spatial component of any stimuli
        self._configure_stimuli()
        # Set delays, provided in physical units, in integration steps.
        self.connectivity.set_idelays(self.integrator.dt)
        self.horizon = self.connectivity.idelays.max() + 1
        # Reshape integrator.noise.nsig, if necessary.
        if isinstance(self.integrator, integrators.IntegratorStochastic):
            self._configure_integrator_noise()

        # Configure Monitors to work with selected Model, etc...
        self._configure_monitors()

        self.tvb_nest_interface = tvb_nest_interface
        # TODO: find out why the model instance is different in simulator and interface...
        self.tvb_nest_interface.configure(self.model)
        dummy = -numpy.ones((self.connectivity.number_of_regions, ))
        dummy[self.tvb_nest_interface.nest_nodes_ids] = 0.0
        # Create TVB model parameter for NEST to target
        for param in self.tvb_nest_interface.nest_to_tvb_params:
            setattr(self.model, param, dummy)

        nest_min_delay = self.tvb_nest_interface.nest_instance.GetKernelStatus(
            "min_delay")
        if self.integrator.dt < nest_min_delay:
            raise_value_error(
                "TVB integration time step dt=%f "
                "is not greater than NEST minimum delay min_delay=%f!" %
                (self.integrator.dt, nest_min_delay))

        self.run_spiking_simulator = self.tvb_nest_interface.nest_instance.Run

        # If there are NEST nodes and are represented exclusively in NEST...
        if self.tvb_nest_interface.exclusive_nodes and len(
                self.tvb_nest_interface.nest_nodes_ids) > 0:
            # ...zero coupling interface_weights among NEST nodes:
            self.connectivity.weights[self.tvb_nest_interface.nest_nodes_ids] \
                [:, self.tvb_nest_interface.nest_nodes_ids] = 0.0

        # Setup history
        # TODO: Reflect upon the idea to allow NEST initialization and history setting via TVB
        self._configure_history(self.initial_conditions)

        # TODO: Shall we implement a parallel implentation for multiple modes for NEST as well?!
        if self.current_state.shape[2] > 1:
            raise_value_error(
                "Multiple modes' simulation not supported for TVB-NEST interface!\n"
                "Current modes number is %d." %
                self.initial_conditions.shape[3])

        # Estimate of memory usage.
        self._census_memory_requirement()

        return self

    # TODO: update all those functions below to compute the fine scale requirements as well, ...if you can! :)

    # used by simulator adaptor
    def memory_requirement(self):
        """
        Return an estimated of the memory requirements (Bytes) for this
        simulator's current configuration.
        """
        return super(Simulator, self).memory_requirement()

    # appears to be unused
    def runtime(self, simulation_length):
        """
        Return an estimated run time (seconds) for the simulator's current
        configuration and a specified simulation length.

        """
        return super(Simulator, self).runtime(simulation_length)

    # used by simulator adaptor
    def storage_requirement(self, simulation_length):
        """
        Return an estimated storage requirement (Bytes) for the simulator's
        current configuration and a specified simulation length.

        """
        return super(Simulator, self).storage_requirement(simulation_length)

    def update_state(self, state, node_coupling, local_coupling=0.0):
        # If there are non-state variables, they need to be updated for the initial condition:
        try:
            self.model.update_non_state_variables(state, node_coupling,
                                                  local_coupling)
            self.bound_and_clamp(state)
        except:
            # If not, the kwarg will fail and nothing will happen
            pass

    def __call__(self, simulation_length=None, random_state=None):
        """
        Return an iterator which steps through simulation time, generating monitor outputs.

        See the run method for a convenient way to collect all output in one call.

        :param simulation_length: Length of the simulation to perform in ms.
        :param random_state:  State of NumPy RNG to use for stochastic integration.
        :return: Iterator over monitor outputs.
        """

        self.calls += 1
        if simulation_length is not None:
            self.simulation_length = simulation_length

        # Intialization
        self._guesstimate_runtime()
        self._calculate_storage_requirement()
        self._handle_random_state(random_state)
        n_reg = self.connectivity.number_of_regions
        local_coupling = self._prepare_local_coupling()
        stimulus = self._prepare_stimulus()
        state = self.current_state
        # Do for initial condition:
        step = self.current_step + 1  # the first step in the loop
        node_coupling = self._loop_compute_node_coupling(step)
        self._loop_update_stimulus(step, stimulus)
        # This is not necessary in most cases
        # if update_non_state_variables=True in the model dfun by default
        self.update_state(state, node_coupling, local_coupling)

        # NEST simulation preparation:
        self.tvb_nest_interface.nest_instance.Prepare()

        # A flag to skip unnecessary steps when NEST does NOT update TVB state
        updateTVBstateFromNEST = len(
            self.tvb_nest_interface.nest_to_tvb_sv_interfaces_ids) > 0

        # integration loop
        n_steps = int(math.ceil(self.simulation_length / self.integrator.dt))
        tic = time.time()
        tic_ratio = 0.1
        tic_point = tic_ratio * n_steps
        for step in range(self.current_step + 1,
                          self.current_step + n_steps + 1):
            # TVB state -> NEST (state or parameter)
            # Communicate TVB state to some NEST device (TVB proxy) or TVB coupling to NEST nodes,
            # including any necessary conversions from TVB state to NEST variables,
            # in a model specific manner
            # TODO: find what is the general treatment of local coupling, if any!
            #  Is this addition correct in all cases for all models?
            self.tvb_nest_interface.tvb_state_to_nest(
                state, node_coupling + local_coupling, stimulus, self.model)
            # NEST state -> TVB model parameter
            # Couple the NEST state to some TVB model parameter,
            # including any necessary conversions in a model specific manner
            self.model = self.tvb_nest_interface.nest_state_to_tvb_parameter(
                self.model)
            # Integrate TVB to get the new TVB state
            state = self.integrator.scheme(state, self.model.dfun,
                                           node_coupling, local_coupling,
                                           stimulus)
            if numpy.any(numpy.isnan(state)) or numpy.any(numpy.isinf(state)):
                raise ValueError(
                    "NaN or Inf values detected in simulator state!:\n%s" %
                    str(state))
            # Integrate NEST to get the new NEST state
            self.run_spiking_simulator(self.integrator.dt)
            if updateTVBstateFromNEST:
                # NEST state -> TVB state
                # Update the new TVB state variable with the new NEST state,
                # including any necessary conversions from NEST variables to TVB state,
                # in a model specific manner
                state = self.tvb_nest_interface.nest_state_to_tvb_state(state)
                self.bound_and_clamp(state)
            # Prepare coupling and stimulus for next time step
            # and, therefore, for the new TVB state:
            node_coupling = self._loop_compute_node_coupling(step)
            self._loop_update_stimulus(step, stimulus)
            # Update any non-state variables and apply any boundaries again to the new state:
            self.update_state(state, node_coupling, local_coupling)
            # Now direct the new state to history buffer and monitors
            self._loop_update_history(step, n_reg, state)
            output = self._loop_monitor_output(step, state)
            if output is not None:
                yield output
            if step - self.current_step >= tic_point:
                toc = time.time() - tic
                if toc > 600:
                    if toc > 7200:
                        time_string = "%0.1f hours" % (toc / 3600)
                    else:
                        time_string = "%0.1f min" % (toc / 60)
                else:
                    time_string = "%0.1f sec" % toc
                print_this = "\r...%0.1f%% done in %s" % \
                            (100.0 * (step - self.current_step) / n_steps, time_string)
                sys.stdout.write(print_this)
                sys.stdout.flush()
                tic_point += tic_ratio * n_steps

        self.current_state = state
        self.current_step = self.current_step + n_steps - 1  # -1 : don't repeat last point
示例#19
0
 class A(HasTraits):
     picked_dimensions = List(of=str,
                              default=('time', ),
                              choices=('a', 'b', 'c'))
示例#20
0
class LileySteynRoss(models.Model):
    """
    Liley lumped model as presented in Steyn-Ross et al 1999.

    This  model is to be use for modelling cortical dynamics in which "inputs"
    to neuronal assemblies are treated as random Gaussian fluctuations about a
    mean value. Anesthetic agent effects are modelled as as a modulation of the
    inhibitory neurotransmitter rate constant.

    The main state variable is h_e, the average excitatory soma potential,
    coherent fluctuations of which are believed to be the source of scalp-measured
    electroencephalogram ͑EEG͒ signals.

    Parameters are taken from Table 1 [Steyn-Ross_1999]_


    State variables:

    h_e: exc population mean soma potential [mV]
    h_i: exc population mean soma potential [mV]

    I_ee: total 'exc' current input to 'exc' synapses [mV]
    I_ie: total 'inh' current input to 'exc' synapses [mV]
    I_ei: total 'exc' current input to 'inh' synapses [mV]
    I_ii: total 'inh' current input to 'inh' synapses [mV]

    :math:`\Psi_{jk}`:  weighting factors for the I_jk inputs [dimensionless]

    :math:`phi_e`: long-range (cortico-cortical) spike input to exc population
    :math:`phi_i`: long-range (cortico-cortical) spike input to inh population [ms-1]

    EPSP: exc post-synaptic potential [mV]
    IPSP: inh post-synaptic potential [mV]

    Mean axonal conduction speed: 7 mm/ms

    S_e(h_e): sigmoid function mapping soma potential to firing rate [ms]-1
    S_i(h_i): sigmoid function mapping soma potential to firing rate [ms]-1



    The models (:math:`h_e`, :math:h_i`) phase-plane, including a representation of
    the vector field as well as its nullclines, using default parameters, can be
    seen below:

        .. _phase-plane-LSR:
        .. figure :: img/LileySteynRoss_01_mode_0_pplane.svg
            :alt: LileySteynRoss phase plane (E, I)

            The (:math:`h_e`, :math:`hi`) phase-plane for the LileySteynRoss model.


    References
    ----------

    .. [Steyn-Ross et al 1999] Theoretical electroencephalogram stationary spectrum for a 
       white-noise-driven cortex: Evidence for a general anesthetic-induced phase transition.

    .. [Liley et al 2013] The mesoscopic modelin of burst supression during anesthesia.


    """

    # Define traited attributes for this model, these represent possible kwargs.

    tau_e = NArray(
        label=r":math:`\tau_e`",
        default=numpy.array([40.0]),
        domain=Range(lo=5.0, hi=50.0, step=1.00),
        doc="""Excitatory population, membrane time-constant [ms]""")

    tau_i = NArray(
        label=r":math:`\tau_i`",
        default=numpy.array([40.0]),
        domain=Range(lo=5.0, hi=50.0, step=1.0),
        doc="""Inhibitory population, membrane time-constant [ms]""")

    h_e_rest = NArray(
        label=r":math:`h_e^{rest}`",
        default=numpy.array([-70.0]),
        domain=Range(lo=-90.0, hi=-50.0, step=10.00),
        doc="""Excitatory population, cell resting potential [mV]""")

    h_i_rest = NArray(
        label=r":math:`h_i^{rest}`",
        default=numpy.array([-70.0]),
        domain=Range(lo=-90.0, hi=-50.0, step=10.0),
        doc="""Inhibitory population, cell resting potential [mV]""")

    h_e_rev = NArray(
        label=r":math:`h_e^{rev}`",
        default=numpy.array([45.0]),
        domain=Range(lo=0.0, hi=50.0, step=5.00),
        doc="""Excitatory population, cell reversal potential [mV]""")

    h_i_rev = NArray(
        label=r":math:`h_i^{rev}`",
        default=numpy.array([-90.0]),
        domain=Range(lo=-90.0, hi=-50.0, step=10.0),
        doc="""Inhibitory population, cell reversal potential [mV]""")

    p_ee = NArray(
        label=":math:`p_{ee}`",
        default=numpy.array([1.1]),
        domain=Range(lo=1.0, hi=1.8, step=0.1),
        doc=
        """Exogenous (subcortical) spike input to exc population [ms]-1 [kHz]. 
               This could be replaced by a noise term""")

    p_ie = NArray(
        label=":math:`p_{ie}`",
        default=numpy.array([1.6]),
        domain=Range(lo=1.0, hi=1.8, step=0.1),
        doc=
        """Exogenous (subcortical) spike input to exc population [ms]-1 [kHz]. 
               This could be replaced by a noise term""")

    p_ei = NArray(
        label=":math:`p_{ei}`",
        default=numpy.array([1.6]),
        domain=Range(lo=1.0, hi=1.8, step=0.1),
        doc=
        """Exogenous (subcortical) spike input to inh population [ms]-1 [kHz]. 
               This could be replaced by a noise term""")

    p_ii = NArray(
        label=":math:`p_{ii}`",
        default=numpy.array([1.1]),
        domain=Range(lo=1.0, hi=1.8, step=0.1),
        doc=
        """Exogenous (subcortical) spike input to inh population [ms]-1 [kHz]. 
               This could be replaced by a noise term""")

    A_ee = NArray(
        label=r":math:`\alpha_{ee}`",
        default=numpy.array([0.04]),
        domain=Range(lo=0.02, hi=0.06, step=0.01),
        doc=
        """Characteristic cortico-cortical inverse length scale [mm]-1. Original: 0.4 cm-1"""
    )

    A_ei = NArray(
        label=r":math:`\alpha_{ei}`",
        default=numpy.array([0.065]),
        domain=Range(lo=0.02, hi=0.08, step=0.01),
        doc=
        """Characteristic cortico-cortical inverse length scale [mm]-1. Original: 0.4 cm-1"""
    )

    gamma_e = NArray(label=r":math:`\gamma_e`",
                     default=numpy.array([0.3]),
                     domain=Range(lo=0.1, hi=0.4, step=0.01),
                     doc="""Neurotransmitter rate constant"for EPSP [ms]-1""")

    gamma_i = NArray(label=r":math:`\gamma_i`",
                     default=numpy.array([0.065]),
                     domain=Range(lo=0.005, hi=0.1, step=0.005),
                     doc="""Neurotransmitter rate constant"for IPSP [ms]-1""")

    G_e = NArray(label=":math:`G_e`",
                 default=numpy.array([0.18]),
                 domain=Range(lo=0.1, hi=0.5, step=0.01),
                 doc="""peak ampplitude of EPSP [mV]""")

    G_i = NArray(label=":math:`G_i`",
                 default=numpy.array([0.37]),
                 domain=Range(lo=0.1, hi=0.5, step=0.01),
                 doc="""peak ampplitude of IPSP [mV]""")

    N_b_ee = NArray(
        label=":math:`N_{ee}^{\beta}`",
        default=numpy.array([3034.0]),
        domain=Range(lo=3000., hi=3050., step=10.0),
        doc="""Total number of local exc to exc synaptic connections.""")

    N_b_ei = NArray(
        label=r":math:`N_{ei}^{\beta}`",
        default=numpy.array([3034.0]),
        domain=Range(lo=3000., hi=3050., step=10.0),
        doc="""Total number of local exc to inh synaptic connections.""")

    N_b_ie = NArray(
        label=r":math:`N_{ie}^{\beta}`",
        default=numpy.array([536.0]),
        domain=Range(lo=500., hi=550., step=1.0),
        doc="""Total number of local inh to exc synaptic connections.""")

    N_b_ii = NArray(
        label=r":math:`N_{ii}^{\beta}`",
        default=numpy.array([536.0]),
        domain=Range(lo=500., hi=550., step=1.0),
        doc="""Total number of local inh to inh synaptic connections.""")

    N_a_ee = NArray(
        label=r":math:`N_{ee}^{\alpha}`",
        default=numpy.array([4000.0]),
        domain=Range(lo=3000., hi=5000., step=10.0),
        doc=
        """Total number of synaptic connections from distant exc populations"""
    )

    N_a_ei = NArray(
        label=r":math:`N_{ei}^{\alpha}`",
        default=numpy.array([2000.0]),
        domain=Range(lo=1000., hi=3000., step=1.0),
        doc=
        """Total number of synaptic connections from distant exc populations"""
    )

    theta_e = NArray(
        label=r":math:`\theta_e`",
        default=numpy.array([-60.0]),
        domain=Range(lo=-90.0, hi=-40.0, step=5.0),
        doc="""inflection point voltage for sigmoid function [mV]""")

    theta_i = NArray(
        label=r":math:`\theta_i`",
        default=numpy.array([-60.0]),
        domain=Range(lo=-90.0, hi=-40.0, step=5.0),
        doc="""inflection point voltage for sigmoid function [mV]""")

    g_e = NArray(
        label=":math:`g_e`",
        default=numpy.array([0.28]),
        domain=Range(lo=0.0, hi=2.0, step=0.01),
        doc="""Sigmoid slope at inflection point exc population [mV]-1""")

    g_i = NArray(
        label=":math:`g_i`",
        default=numpy.array([0.14]),
        domain=Range(lo=0.0, hi=2.0, step=0.01),
        doc="""Sigmoid slope at inflection point inh population [mV]-1""")

    lambd = NArray(label=":math:`\lambda`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=0.0, hi=2.0, step=0.01),
                   doc="""Anesthetic effects""")

    # Used for phase-plane axis ranges and to bound random initial() conditions.
    state_variable_range = Final(
        {
            "he": numpy.array([-90.0, 70.0]),
            "hi": numpy.array([-90.0, 70.0])
        },
        label="State Variable ranges [lo, hi]",
        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.""")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("he", "hi"),
        default=("he", ),
        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`.""")

    def __init__(self, **kwargs):
        """
        Initialize the Liley Steyn-Ross model's traited attributes, any provided as
        keywords will overide their traited default.

        """
        LOG.info('%s: initing...' % str(self))
        super(LileySteynRoss, self).__init__(**kwargs)
        #self._state_variables = ["E", "I"]
        self._nvar = 2
        self.cvar = numpy.array([0, 1], dtype=numpy.int32)
        LOG.debug('%s: inited.' % repr(self))

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        r"""

        TODO:  include equations here and see how to add the local connectivity or the 
        the laplacian oeprator. This model is the one used in Bojak 2011.

        """

        he = state_variables[0, :]
        hi = state_variables[1, :]

        # long-range coupling - phi_e -> he / phi_e -> h_i
        c_0 = coupling[0, :]

        # short-range (local) coupling - local coupling functions should be different for exc and inh
        #lc_0 = local_coupling * he
        #lc_1 = local_coupling * hi

        #
        psi_ee = (self.h_e_rev - he) / abs(
            self.h_e_rev - self.h_e_rest)  # usually > 0
        psi_ei = (self.h_e_rev - hi) / abs(
            self.h_e_rev - self.h_i_rest)  # usually > 0
        psi_ie = (self.h_i_rev - he) / abs(
            self.h_i_rev - self.h_e_rest)  # usually < 0
        psi_ii = (self.h_i_rev - hi) / abs(
            self.h_i_rev - self.h_i_rest)  # usually < 0

        S_e = 1.0 / (1.0 + numpy.exp(-self.g_e * (he + c_0 - self.theta_e)))
        S_i = 1.0 / (1.0 + numpy.exp(-self.g_i * (hi + c_0 - self.theta_i)))

        F1 = ((self.h_e_rest - he) + psi_ee * ((self.N_a_ee + self.N_b_ee) * S_e  + self.p_ee) * (self.G_e/self.gamma_e) +\
                                  self.lambd * psi_ie * (self.N_b_ie * S_i + self.p_ie) * (self.G_i/self.gamma_i)) / self.tau_e

        F2 = ((self.h_i_rest - hi) + psi_ei * ((self.N_a_ei + self.N_b_ei) * S_e  + self.p_ei) * (self.G_e/self.gamma_e) +\
                                  self.lambd * psi_ii * (self.N_b_ii * S_i + self.p_ii) * (self.G_i/self.gamma_i)) / self.tau_i

        dhe = F1
        dhi = F2

        derivative = numpy.array([dhe, dhi])

        return derivative
class PhasePlaneInteractive(HasTraits):
    """
    The GUI for the interactive phase-plane viewer provides sliders for setting:
        - The value of all parameters of the Model.
        - The extent of the axes.
        - A fixed value for the state-variables which aren't currently selected.
        - The noise strength, if a stocahstic integrator is specified.

    and radio buttons for selecting:
        - Which state-variables to show on each axis.
        - Which mode to show, if the Model has them.

    Clicking on the phase-plane will generate a sample trajectory, originating
    from where you clicked.

    """

    model = Attr(
        field_type=models_module.Model,
        label="Model",
        default=models_module.Generic2dOscillator(),
        doc="""An instance of the local dynamic model to be investigated with
        PhasePlaneInteractive.""")

    integrator = Attr(
        field_type=integrators_module.Integrator,
        label="Integrator",
        default=integrators_module.RungeKutta4thOrderDeterministic(),
        doc="""The integration scheme used to for generating sample
        trajectories on the phase-plane. NOTE: This is not used for generating
        the phase-plane itself, ie the vector field and nulclines.""")

    exclude_sliders = List(
        of=str)

    def __init__(self, **kwargs):
        """
        Initialise based on provided keywords or their traited defaults. Also,
        initialise the place-holder attributes that aren't filled until the
        show() method is called.
        """
        super(PhasePlaneInteractive, self).__init__(**kwargs)
        LOG.debug(str(kwargs))

        # figure
        self.ipp_fig = None

        # p hase-plane
        self.pp_ax = None
        self.X = None
        self.Y = None
        self.U = None
        self.V = None
        self.UVmag = None
        self.nullcline_x = None
        self.nullcline_y = None
        self.pp_quivers = None

        # Current state
        self.svx = None
        self.svy = None
        self.default_sv = None
        self.no_coupling = None
        self.mode = None

        # Selectors
        self.state_variable_x = None
        self.state_variable_y = None
        self.mode_selector = None

        # Sliders
        self.param_sliders = None
        self.axes_range_sliders = None
        self.sv_sliders = None
        self.noise_slider = None

        # Reset buttons
        self.reset_param_button = None
        self.reset_sv_button = None
        self.reset_axes_button = None
        self.reset_noise_button = None
        self.reset_seed_button = None

    def show(self):
        """ Generate the interactive phase-plane figure. """
        model_name = self.model.__class__.__name__
        msg = "Generating an interactive phase-plane plot for %s"
        LOG.info(msg % model_name)

        # Make sure the model is fully configured...
        self.model.configure()

        # Setup the inital(current) state
        self.svx = self.model.state_variables[0]  # x-axis: 1st state variable
        self.svy = self.model.state_variables[1]  # y-axis: 2nd state variable
        self.mode = 0
        self.set_state_vector()

        # Make the figure:
        self.create_figure()

        # Selectors
        self.add_state_variable_selector()
        self.add_mode_selector()

        # Sliders
        self.add_axes_range_sliders()
        self.add_state_variable_sliders()
        self.add_param_sliders()
        if isinstance(self.integrator, integrators_module.IntegratorStochastic):
            if self.integrator.noise.ntau > 0.0:
                self.integrator.noise.configure_coloured(self.integrator.dt,
                                                         (1, self.model.nvar, 1,
                                                          self.model.number_of_modes))
            else:
                self.integrator.noise.configure_white(self.integrator.dt,
                                                      (1, self.model.nvar, 1,
                                                       self.model.number_of_modes))

            self.add_noise_slider()
            self.add_reset_noise_button()
            self.add_reset_seed_button()

        # Reset buttons
        self.add_reset_param_button()
        self.add_reset_sv_button()
        self.add_reset_axes_button()

        # Calculate the phase plane
        self.set_mesh_grid()
        self.calc_phase_plane()

        # Plot phase plane
        self.plot_phase_plane()

        # add mouse handler for trajectory clicking
        self.ipp_fig.canvas.mpl_connect('button_press_event',
                                        self.click_trajectory)
        # import pdb; pdb.set_trace()

        pylab.show()

    ##------------------------------------------------------------------------##
    ##----------------- Functions for building the figure --------------------##
    ##------------------------------------------------------------------------##

    def create_figure(self):
        """ Create the figure and phase-plane axes. """
        # Figure and main phase-plane axes
        model_name = self.model.__class__.__name__
        integrator_name = self.integrator.__class__.__name__
        figsize = 10, 5
        try:
            figure_window_title = "Interactive phase-plane: " + model_name
            figure_window_title += "   --   %s" % integrator_name
            self.ipp_fig = pylab.figure(num=figure_window_title,
                                        figsize=figsize,
                                        facecolor=BACKGROUNDCOLOUR,
                                        edgecolor=EDGECOLOUR)
        except ValueError:
            LOG.info("My life would be easier if you'd update your PyLab...")
            self.ipp_fig = pylab.figure(num=42, figsize=figsize,
                                        facecolor=BACKGROUNDCOLOUR,
                                        edgecolor=EDGECOLOUR)

        self.pp_ax = self.ipp_fig.add_axes([0.265, 0.2, 0.5, 0.75])

        self.pp_splt = self.ipp_fig.add_subplot(212)
        self.ipp_fig.subplots_adjust(left=0.265, bottom=0.02, right=0.765,
                                     top=0.3, wspace=0.1, hspace=None)
        self.pp_splt.set_prop_cycle(color=get_color(self.model.nvar))
        self.pp_splt.plot(numpy.arange(TRAJ_STEPS + 1) * self.integrator.dt,
                          numpy.zeros((TRAJ_STEPS + 1, self.model.nvar)))
        if hasattr(self.pp_splt, 'autoscale'):
            self.pp_splt.autoscale(enable=True, axis='y', tight=True)
        self.pp_splt.legend(self.model.state_variables)

    def add_state_variable_selector(self):
        """
        Generate radio selector buttons to set which state variable is displayed
        on the x and y axis of the phase-plane plot.
        """
        svx_ind = self.model.state_variables.index(self.svx)
        svy_ind = self.model.state_variables.index(self.svy)

        # State variable for the x axis
        pos_shp = [0.07, 0.05, 0.065, 0.12 + 0.006 * self.model.nvar]
        rax = self.ipp_fig.add_axes(pos_shp, facecolor=AXCOLOUR, title="x-axis")
        self.state_variable_x = widgets.RadioButtons(rax,
                                                     tuple(self.model.state_variables),
                                                     active=svx_ind)
        self.state_variable_x.on_clicked(self.update_svx)

        # State variable for the y axis
        pos_shp = [0.14, 0.05, 0.065, 0.12 + 0.006 * self.model.nvar]
        rax = self.ipp_fig.add_axes(pos_shp, facecolor=AXCOLOUR, title="y-axis")
        self.state_variable_y = widgets.RadioButtons(rax,
                                                     tuple(self.model.state_variables),
                                                     active=svy_ind)
        self.state_variable_y.on_clicked(self.update_svy)

    def add_mode_selector(self):
        """
        Add a radio button to the figure for selecting which mode of the model
        should be displayed.
        """
        pos_shp = [0.02, 0.07, 0.04, 0.1 + 0.002 * self.model.number_of_modes]
        rax = self.ipp_fig.add_axes(pos_shp, facecolor=AXCOLOUR, title="Mode")
        mode_tuple = tuple(range(self.model.number_of_modes))
        self.mode_selector = widgets.RadioButtons(rax, mode_tuple, active=0)
        self.mode_selector.on_clicked(self.update_mode)

    def add_axes_range_sliders(self):
        """
        Add sliders to the figure to allow the phase-planes axes to be set.
        """
        self.axes_range_sliders = dict()

        default_range_x = (self.model.state_variable_range[self.svx][1] -
                           self.model.state_variable_range[self.svx][0])
        default_range_y = (self.model.state_variable_range[self.svy][1] -
                           self.model.state_variable_range[self.svy][0])
        min_val_x = self.model.state_variable_range[self.svx][0] - 4.0 * default_range_x
        max_val_x = self.model.state_variable_range[self.svx][1] + 4.0 * default_range_x
        min_val_y = self.model.state_variable_range[self.svy][0] - 4.0 * default_range_y
        max_val_y = self.model.state_variable_range[self.svy][1] + 4.0 * default_range_y

        sax = self.ipp_fig.add_axes([0.04, 0.835, 0.125, 0.025],
                                    facecolor=AXCOLOUR)
        sl_x_min = widgets.Slider(sax, "xlo", min_val_x, max_val_x,
                                  valinit=self.model.state_variable_range[self.svx][0])
        sl_x_min.on_changed(self.update_range)

        sax = self.ipp_fig.add_axes([0.04, 0.8, 0.125, 0.025], facecolor=AXCOLOUR)
        sl_x_max = widgets.Slider(sax, "xhi", min_val_x, max_val_x,
                                  valinit=self.model.state_variable_range[self.svx][1])
        sl_x_max.on_changed(self.update_range)

        sax = self.ipp_fig.add_axes([0.04, 0.765, 0.125, 0.025],
                                    facecolor=AXCOLOUR)
        sl_y_min = widgets.Slider(sax, "ylo", min_val_y, max_val_y,
                                  valinit=self.model.state_variable_range[self.svy][0])
        sl_y_min.on_changed(self.update_range)

        sax = self.ipp_fig.add_axes([0.04, 0.73, 0.125, 0.025], facecolor=AXCOLOUR)
        sl_y_max = widgets.Slider(sax, "yhi", min_val_y, max_val_y,
                                  valinit=self.model.state_variable_range[self.svy][1])
        sl_y_max.on_changed(self.update_range)

        self.axes_range_sliders["sl_x_min"] = sl_x_min
        self.axes_range_sliders["sl_x_max"] = sl_x_max
        self.axes_range_sliders["sl_y_min"] = sl_y_min
        self.axes_range_sliders["sl_y_max"] = sl_y_max

    def add_state_variable_sliders(self):
        """
        Add sliders to the figure to allow default values for the models state
        variable to be set.
        """
        msv_range = self.model.state_variable_range
        offset = 0.0
        self.sv_sliders = dict()
        for sv in range(self.model.nvar):
            offset += 0.035
            pos_shp = [0.04, 0.6 - offset, 0.125, 0.025]
            sax = self.ipp_fig.add_axes(pos_shp, facecolor=AXCOLOUR)
            sv_str = self.model.state_variables[sv]
            self.sv_sliders[sv_str] = widgets.Slider(sax, sv_str,
                                                     msv_range[sv_str][0],
                                                     msv_range[sv_str][1],
                                                     valinit=self.default_sv[sv, 0, 0])
            self.sv_sliders[sv_str].on_changed(self.update_state_variables)

    # Traited paramaters as sliders
    def add_param_sliders(self):
        """
        Add sliders to the figure to allow the models parameters to be set.
        """
        offset = 0.0
        self.param_sliders = dict()
        # import pdb; pdb.set_trace()
        for param_name in type(self.model).declarative_attrs:
            if self.exclude_sliders is not None and param_name in self.exclude_sliders:
                continue
            param_def = getattr(type(self.model), param_name)
            if not isinstance(param_def, NArray) or not param_def.dtype == numpy.float:
                continue
            param_range = param_def.domain
            if param_range is None:
                continue
            offset += 0.035
            sax = self.ipp_fig.add_axes([0.825, 0.865 - offset, 0.125, 0.025],
                                        facecolor=AXCOLOUR)
            param_value = getattr(self.model, param_name)
            param_value = param_value[0] if len(param_value) > 0 else 0
            self.param_sliders[param_name] = widgets.Slider(sax, param_name,
                                                            param_range.lo,
                                                            param_range.hi,
                                                            valinit=param_value)
            self.param_sliders[param_name].on_changed(self.update_parameters)

    def add_noise_slider(self):
        """
        Add a slider to the figure to allow the integrators noise strength to
        be set.
        """
        pos_shp = [0.825, 0.1, 0.125, 0.025]
        sax = self.ipp_fig.add_axes(pos_shp, facecolor=AXCOLOUR)

        self.noise_slider = widgets.Slider(sax, "Log Noise", -9.0, 1.0,
                                           valinit=self.integrator.noise.nsig)
        self.noise_slider.on_changed(self.update_noise)

    def add_reset_param_button(self):
        """
        Add a button to the figure for reseting the model parameter values to
        their original values.
        """
        bax = self.ipp_fig.add_axes([0.825, 0.865, 0.125, 0.04])
        self.reset_param_button = widgets.Button(bax, 'Reset parameters',
                                                 color=BUTTONCOLOUR,
                                                 hovercolor=HOVERCOLOUR)

        def reset_parameters(event):
            for param_slider in self.param_sliders:
                self.param_sliders[param_slider].reset()

        self.reset_param_button.on_clicked(reset_parameters)

    def add_reset_sv_button(self):
        """
        Add a button to the figure for reseting the model state variables to
        their default values.
        """
        bax = self.ipp_fig.add_axes([0.04, 0.60, 0.125, 0.04])
        self.reset_sv_button = widgets.Button(bax, 'Reset state-variables',
                                              color=BUTTONCOLOUR,
                                              hovercolor=HOVERCOLOUR)

        def reset_state_variables(event):
            for svsl in self.sv_sliders.values():
                svsl.reset()

        self.reset_sv_button.on_clicked(reset_state_variables)

    def add_reset_noise_button(self):
        """
        Add a button to the figure for reseting the noise to its default value.
        """
        bax = self.ipp_fig.add_axes([0.825, 0.135, 0.125, 0.04])
        self.reset_noise_button = widgets.Button(bax, 'Reset noise strength',
                                                 color=BUTTONCOLOUR,
                                                 hovercolor=HOVERCOLOUR)

        def reset_noise(event):
            self.noise_slider.reset()

        self.reset_noise_button.on_clicked(reset_noise)

    def add_reset_seed_button(self):
        """
        Add a button to the figure for reseting the random number generator to
        its intial state. For reproducible noise...
        """
        bax = self.ipp_fig.add_axes([0.825, 0.05, 0.125, 0.04])
        self.reset_seed_button = widgets.Button(bax, 'Reset random stream',
                                                color=BUTTONCOLOUR,
                                                hovercolor=HOVERCOLOUR)

        def reset_seed(event):
            self.integrator.noise.trait["random_stream"].reset()

        self.reset_seed_button.on_clicked(reset_seed)

    def add_reset_axes_button(self):
        """
        Add a button to the figure for reseting the phase-plane axes to their
        default ranges.
        """
        bax = self.ipp_fig.add_axes([0.04, 0.87, 0.125, 0.04])
        self.reset_axes_button = widgets.Button(bax, 'Reset axes',
                                                color=BUTTONCOLOUR,
                                                hovercolor=HOVERCOLOUR)

        def reset_ranges(event):
            self.axes_range_sliders["sl_x_min"].reset()
            self.axes_range_sliders["sl_x_max"].reset()
            self.axes_range_sliders["sl_y_min"].reset()
            self.axes_range_sliders["sl_y_max"].reset()

        self.reset_axes_button.on_clicked(reset_ranges)

    ##------------------------------------------------------------------------##
    ##------------------- Functions for updating the figure ------------------##
    ##------------------------------------------------------------------------##

    # NOTE: All the ax.set_xlim, poly.xy, etc, garbage below is fragile. It works
    #      at the moment, but there are currently bugs in Slider and the hackery
    #      below takes these into account... If the bugs are fixed/changed then
    #      this could break. As an example, the Slider doc says poly is a
    #      Rectangle, but it's actually a Polygon. The Slider set_val method 
    #      assumes a Rectangle even though this is not the case, so the array 
    #      Slider.poly.xy is corrupted by that method. The corruption isn't 
    #      visible in the plot, which is probably why it hasn't been fixed...

    def update_xrange_sliders(self):
        """
        A hacky update of the x-axis range sliders that is called when the
        state-variable selected for the x-axis is changed.
        """
        default_range_x = (self.model.state_variable_range[self.svx][1] -
                           self.model.state_variable_range[self.svx][0])
        min_val_x = self.model.state_variable_range[self.svx][0] - 4.0 * default_range_x
        max_val_x = self.model.state_variable_range[self.svx][1] + 4.0 * default_range_x
        self.axes_range_sliders["sl_x_min"].valinit = self.model.state_variable_range[self.svx][0]
        self.axes_range_sliders["sl_x_min"].valmin = min_val_x
        self.axes_range_sliders["sl_x_min"].valmax = max_val_x
        self.axes_range_sliders["sl_x_min"].ax.set_xlim(min_val_x, max_val_x)
        self.axes_range_sliders["sl_x_min"].poly.axes.set_xlim(min_val_x, max_val_x)
        self.axes_range_sliders["sl_x_min"].poly.xy[[0, 1], 0] = min_val_x
        self.axes_range_sliders["sl_x_min"].vline.set_data(
            ([self.axes_range_sliders["sl_x_min"].valinit, self.axes_range_sliders["sl_x_min"].valinit], [0, 1]))
        self.axes_range_sliders["sl_x_max"].valinit = self.model.state_variable_range[self.svx][1]
        self.axes_range_sliders["sl_x_max"].valmin = min_val_x
        self.axes_range_sliders["sl_x_max"].valmax = max_val_x
        self.axes_range_sliders["sl_x_max"].ax.set_xlim(min_val_x, max_val_x)
        self.axes_range_sliders["sl_x_max"].poly.axes.set_xlim(min_val_x, max_val_x)
        self.axes_range_sliders["sl_x_max"].poly.xy[[0, 1], 0] = min_val_x
        self.axes_range_sliders["sl_x_max"].vline.set_data(
            ([self.axes_range_sliders["sl_x_max"].valinit, self.axes_range_sliders["sl_x_max"].valinit], [0, 1]))
        self.axes_range_sliders["sl_x_min"].reset()
        self.axes_range_sliders["sl_x_max"].reset()

    def update_yrange_sliders(self):
        """
        A hacky update of the y-axis range sliders that is called when the
        state-variable selected for the y-axis is changed.
        """
        # svy_ind = self.model.state_variables.index(self.svy)
        default_range_y = (self.model.state_variable_range[self.svy][1] -
                           self.model.state_variable_range[self.svy][0])
        min_val_y = self.model.state_variable_range[self.svy][0] - 4.0 * default_range_y
        max_val_y = self.model.state_variable_range[self.svy][1] + 4.0 * default_range_y
        self.axes_range_sliders["sl_y_min"].valinit = self.model.state_variable_range[self.svy][0]
        self.axes_range_sliders["sl_y_min"].valmin = min_val_y
        self.axes_range_sliders["sl_y_min"].valmax = max_val_y
        self.axes_range_sliders["sl_y_min"].ax.set_xlim(min_val_y, max_val_y)
        self.axes_range_sliders["sl_y_min"].poly.axes.set_xlim(min_val_y, max_val_y)
        self.axes_range_sliders["sl_y_min"].poly.xy[[0, 1], 0] = min_val_y
        self.axes_range_sliders["sl_y_min"].vline.set_data(
            ([self.axes_range_sliders["sl_y_min"].valinit, self.axes_range_sliders["sl_y_min"].valinit], [0, 1]))
        self.axes_range_sliders["sl_y_max"].valinit = self.model.state_variable_range[self.svy][1]
        self.axes_range_sliders["sl_y_max"].valmin = min_val_y
        self.axes_range_sliders["sl_y_max"].valmax = max_val_y
        self.axes_range_sliders["sl_y_max"].ax.set_xlim(min_val_y, max_val_y)
        self.axes_range_sliders["sl_y_max"].poly.axes.set_xlim(min_val_y, max_val_y)
        self.axes_range_sliders["sl_y_max"].poly.xy[[0, 1], 0] = min_val_y
        self.axes_range_sliders["sl_y_max"].vline.set_data(
            ([self.axes_range_sliders["sl_y_max"].valinit, self.axes_range_sliders["sl_y_max"].valinit], [0, 1]))
        self.axes_range_sliders["sl_y_min"].reset()
        self.axes_range_sliders["sl_y_max"].reset()

    def update_svx(self, label):
        """ 
        Update state variable used for x-axis based on radio buttton selection.
        """
        self.svx = label
        self.update_xrange_sliders()
        self.set_mesh_grid()
        self.calc_phase_plane()
        self.update_phase_plane()

    def update_svy(self, label):
        """ 
        Update state variable used for y-axis based on radio buttton selection.
        """
        self.svy = label
        self.update_yrange_sliders()
        self.set_mesh_grid()
        self.calc_phase_plane()
        self.update_phase_plane()

    def update_mode(self, label):
        """ Update the visualised mode based on radio button selection. """
        self.mode = label
        self.update_phase_plane()

    def update_parameters(self, val):
        """
        Update model parameters based on the current parameter slider values.

        NOTE: Haven't figured out how to update independantly, so just update
            everything.
        """
        # TODO: Grab caller and use val directly, ie independent parameter update.
        # import pdb; pdb.set_trace()
        for param in self.param_sliders:
            setattr(self.model, param, numpy.array([self.param_sliders[param].val]))

        self.model.update_derived_parameters()
        self.calc_phase_plane()
        self.update_phase_plane()

    def update_noise(self, nsig):
        """ Update integrator noise based on the noise slider value. """
        self.integrator.noise.nsig = numpy.array([10 ** nsig, ])

    def update_range(self, val):
        """
        Update the axes ranges based on the current axes slider values.

        NOTE: Haven't figured out how to update independantly, so just update
            everything.

        """
        # TODO: Grab caller and use val directly, ie independent range update.
        self.axes_range_sliders["sl_x_min"].ax.set_facecolor(AXCOLOUR)
        self.axes_range_sliders["sl_x_max"].ax.set_facecolor(AXCOLOUR)
        self.axes_range_sliders["sl_y_min"].ax.set_facecolor(AXCOLOUR)
        self.axes_range_sliders["sl_y_max"].ax.set_facecolor(AXCOLOUR)

        if (self.axes_range_sliders["sl_x_min"].val >=
                self.axes_range_sliders["sl_x_max"].val):
            LOG.error("X-axis min must be less than max...")
            self.axes_range_sliders["sl_x_min"].ax.set_facecolor("Red")
            self.axes_range_sliders["sl_x_max"].ax.set_facecolor("Red")
            return
        if (self.axes_range_sliders["sl_y_min"].val >=
                self.axes_range_sliders["sl_y_max"].val):
            LOG.error("Y-axis min must be less than max...")
            self.axes_range_sliders["sl_y_min"].ax.set_facecolor("Red")
            self.axes_range_sliders["sl_y_max"].ax.set_facecolor("Red")
            return

        msv_range = self.model.state_variable_range
        msv_range[self.svx][0] = self.axes_range_sliders["sl_x_min"].val
        msv_range[self.svx][1] = self.axes_range_sliders["sl_x_max"].val
        msv_range[self.svy][0] = self.axes_range_sliders["sl_y_min"].val
        msv_range[self.svy][1] = self.axes_range_sliders["sl_y_max"].val
        self.set_mesh_grid()
        self.calc_phase_plane()
        self.update_phase_plane()

    def update_phase_plane(self):
        """ Clear the axes and redraw the phase-plane. """
        self.pp_ax.clear()
        self.pp_splt.clear()
        self.pp_splt.set_prop_cycle('color', get_color(self.model.nvar))
        self.pp_splt.plot(numpy.arange(TRAJ_STEPS + 1) * self.integrator.dt,
                          numpy.zeros((TRAJ_STEPS + 1, self.model.nvar)))
        if hasattr(self.pp_splt, 'autoscale'):
            self.pp_splt.autoscale(enable=True, axis='y', tight=True)
        self.pp_splt.legend(self.model.state_variables)
        self.plot_phase_plane()

    def update_state_variables(self, val):
        """
        Update the default state-variable values, used for non-visualised state 
        variables, based of the current slider values.
        """
        for sv in self.sv_sliders:
            k = self.model.state_variables.index(sv)
            self.default_sv[k] = self.sv_sliders[sv].val

        self.calc_phase_plane()
        self.update_phase_plane()

    def set_mesh_grid(self):
        """
        Generate the phase-plane gridding based on currently selected 
        state-variables and their range values.
        """
        xlo = self.model.state_variable_range[self.svx][0]
        xhi = self.model.state_variable_range[self.svx][1]
        ylo = self.model.state_variable_range[self.svy][0]
        yhi = self.model.state_variable_range[self.svy][1]

        self.X = numpy.mgrid[xlo:xhi:(NUMBEROFGRIDPOINTS * 1j)]
        self.Y = numpy.mgrid[ylo:yhi:(NUMBEROFGRIDPOINTS * 1j)]

    def set_state_vector(self):
        """
        Set up a vector containing the default state-variable values and create
        a filler(all zeros) for the coupling arg of the Model's dfun method.
        This method is called once at initialisation (show()).
        """
        # import pdb; pdb.set_trace()
        sv_mean = numpy.array([self.model.state_variable_range[key].mean() for key in self.model.state_variables])
        sv_mean = sv_mean.reshape((self.model.nvar, 1, 1))
        self.default_sv = sv_mean.repeat(self.model.number_of_modes, axis=2)
        self.no_coupling = numpy.zeros((self.model.nvar, 1,
                                        self.model.number_of_modes))

    def calc_phase_plane(self):
        """ Calculate the vector field. """
        svx_ind = self.model.state_variables.index(self.svx)
        svy_ind = self.model.state_variables.index(self.svy)

        # Calculate the vector field discretely sampled at a grid of points
        grid_point = self.default_sv.copy()
        self.U = numpy.zeros((NUMBEROFGRIDPOINTS, NUMBEROFGRIDPOINTS,
                              self.model.number_of_modes))
        self.V = numpy.zeros((NUMBEROFGRIDPOINTS, NUMBEROFGRIDPOINTS,
                              self.model.number_of_modes))
        for ii in range(NUMBEROFGRIDPOINTS):
            grid_point[svy_ind] = self.Y[ii]
            for jj in range(NUMBEROFGRIDPOINTS):
                # import pdb; pdb.set_trace()
                grid_point[svx_ind] = self.X[jj]

                d = self.model.dfun(grid_point, self.no_coupling)

                for kk in range(self.model.number_of_modes):
                    self.U[ii, jj, kk] = d[svx_ind, 0, kk]
                    self.V[ii, jj, kk] = d[svy_ind, 0, kk]

        # Colours for the vector field quivers
        # self.UVmag = numpy.sqrt(self.U**2 + self.V**2)

        # import pdb; pdb.set_trace()
        if numpy.isnan(self.U).any() or numpy.isnan(self.V).any():
            LOG.error("NaN")

    def plot_phase_plane(self):
        """ Plot the vector field and its nullclines. """
        # Set title and axis labels
        model_name = self.model.__class__.__name__
        self.pp_ax.set(title=model_name + " mode " + str(self.mode))
        self.pp_ax.set(xlabel="State Variable " + self.svx)
        self.pp_ax.set(ylabel="State Variable " + self.svy)

        # import pdb; pdb.set_trace()
        # Plot a discrete representation of the vector field
        if numpy.all(self.U[:, :, self.mode] + self.V[:, :, self.mode] == 0):
            self.pp_ax.set(title=model_name + " mode " + str(self.mode) + ": NO MOTION IN THIS PLANE")
            X, Y = numpy.meshgrid(self.X, self.Y)
            self.pp_quivers = self.pp_ax.scatter(X, Y, s=8, marker=".", c="k")
        else:
            self.pp_quivers = self.pp_ax.quiver(self.X, self.Y,
                                                self.U[:, :, self.mode],
                                                self.V[:, :, self.mode],
                                                # self.UVmag[:, :, self.mode],
                                                width=0.001, headwidth=8)

        # Plot the nullclines
        self.nullcline_x = self.pp_ax.contour(self.X, self.Y,
                                              self.U[:, :, self.mode],
                                              [0], colors="r")
        self.nullcline_y = self.pp_ax.contour(self.X, self.Y,
                                              self.V[:, :, self.mode],
                                              [0], colors="g")
        pylab.draw()

    def plot_trajectory(self, x, y):
        """
        Plot a sample trajectory, starting at the position x,y in the
        phase-plane. This method is called as a result of a mouse click on the 
        phase-plane.
        """
        svx_ind = self.model.state_variables.index(self.svx)
        svy_ind = self.model.state_variables.index(self.svy)

        # Calculate an example trajectory
        state = self.default_sv.copy()
        self.integrator.clamped_state_variable_indices = numpy.setdiff1d(
            numpy.r_[:len(self.model.state_variables)], numpy.r_[svx_ind, svy_ind])
        self.integrator.clamped_state_variable_values = self.default_sv[self.integrator.clamped_state_variable_indices]
        state[svx_ind] = x
        state[svy_ind] = y
        scheme = self.integrator.scheme
        traj = numpy.zeros((TRAJ_STEPS + 1, self.model.nvar, 1,
                            self.model.number_of_modes))
        traj[0, :] = state
        for step in range(TRAJ_STEPS):
            # import pdb; pdb.set_trace()
            state = scheme(state, self.model.dfun, self.no_coupling, 0.0, 0.0)
            traj[step + 1, :] = state

        self.pp_ax.scatter(x, y, s=42, c='g', marker='o', edgecolor=None)
        self.pp_ax.plot(traj[:, svx_ind, 0, self.mode],
                        traj[:, svy_ind, 0, self.mode])

        # Plot the selected state variable trajectories as a function of time
        self.pp_splt.plot(numpy.arange(TRAJ_STEPS + 1) * self.integrator.dt,
                          traj[:, :, 0, self.mode])

        pylab.draw()

    def click_trajectory(self, event):
        """
        This method captures mouse clicks on the phase-plane and then uses the 
        plot_trajectory() method to generate a sample trajectory.
        """
        if event.inaxes is self.pp_ax:
            x, y = event.xdata, event.ydata
            LOG.info('trajectory starting at (%f, %f)', x, y)
            self.plot_trajectory(x, y)
示例#22
0
class Generic2dOscillator(ModelNumbaDfun):
    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

    """

    # Define traited attributes for this model, these represent possible kwargs.
    tau = NArray(label=r":math:`\tau`",
                 default=numpy.array([1.0]),
                 domain=Range(lo=1.0, hi=5.0, step=0.01),
                 doc="""A time-scale hierarchy can be introduced for the state
        variables :math:`V` and :math:`W`. Default parameter is 1, which means
        no time-scale hierarchy.""")

    I = NArray(label=":math:`I_{ext}`",
               default=numpy.array([0.0]),
               domain=Range(lo=-5.0, hi=5.0, step=0.01),
               doc="""Baseline shift of the cubic nullcline""")

    a = NArray(label=":math:`a`",
               default=numpy.array([-2.0]),
               domain=Range(lo=-5.0, hi=5.0, step=0.01),
               doc="""Vertical shift of the configurable nullcline""")

    b = NArray(label=":math:`b`",
               default=numpy.array([-10.0]),
               domain=Range(lo=-20.0, hi=15.0, step=0.01),
               doc="""Linear slope of the configurable nullcline""")

    c = NArray(label=":math:`c`",
               default=numpy.array([0.0]),
               domain=Range(lo=-10.0, hi=10.0, step=0.01),
               doc="""Parabolic term of the configurable nullcline""")

    d = NArray(label=":math:`d`",
               default=numpy.array([0.02]),
               domain=Range(lo=0.0001, hi=1.0, step=0.0001),
               doc="""Temporal scale factor. Warning: do not use it unless
        you know what you are doing and know about time tides.""")

    e = NArray(
        label=":math:`e`",
        default=numpy.array([3.0]),
        domain=Range(lo=-5.0, hi=5.0, step=0.0001),
        doc="""Coefficient of the quadratic term of the cubic nullcline.""")

    f = NArray(label=":math:`f`",
               default=numpy.array([1.0]),
               domain=Range(lo=-5.0, hi=5.0, step=0.0001),
               doc="""Coefficient of the cubic term of the cubic nullcline.""")

    g = NArray(
        label=":math:`g`",
        default=numpy.array([0.0]),
        domain=Range(lo=-5.0, hi=5.0, step=0.5),
        doc="""Coefficient of the linear term of the cubic nullcline.""")

    alpha = NArray(
        label=r":math:`\alpha`",
        default=numpy.array([1.0]),
        domain=Range(lo=-5.0, hi=5.0, step=0.0001),
        doc="""Constant parameter to scale the rate of feedback from the
            slow variable to the fast variable.""")

    beta = NArray(
        label=r":math:`\beta`",
        default=numpy.array([1.0]),
        domain=Range(lo=-5.0, hi=5.0, step=0.0001),
        doc="""Constant parameter to scale the rate of feedback from the
            slow variable to itself""")

    # This parameter is basically a hack to avoid having a negative lower boundary in the global coupling strength.
    gamma = NArray(label=r":math:`\gamma`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=-1.0, hi=1.0, step=0.1),
                   doc="""Constant parameter to reproduce FHN dynamics where
               excitatory input currents are negative.
               It scales both I and the long range coupling term.""")

    state_variable_range = Final(
        label="State Variable ranges [lo, hi]",
        default={
            "V": numpy.array([-2.0, 4.0]),
            "W": numpy.array([-6.0, 6.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."""
    )

    variables_of_interest = List(
        of=str,
        label="Variables or quantities available to Monitors",
        choices=("V", "W", "V + W", "V - W"),
        default=("V", ),
        doc=
        "The quantities of interest for monitoring for the generic 2D oscillator."
    )

    state_variables = ('V', 'W')
    _nvar = 2
    cvar = numpy.array([0], dtype=numpy.int32)

    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, :]
        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):
        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]
示例#23
0
文件: larter.py 项目: nedkab/tvb-root
class Larter(models.Model):
    """
    A modified Morris-Lecar model that includes a third equation which simulates
    the effect of a population of inhibitory interneurons synapsing on
    the pyramidal cells.
    
    .. [Larteretal_1999] Larter et.al. *A coupled ordinary differential equation
        lattice model for the simulation of epileptic seizures.* Chaos. 9(3):
        795, 1999.
    
    .. [Breaksetal_2003] M. J. Breakspear et.al. *Modulation of excitatory
        synaptic coupling facilitates synchronization and complex dynamics in a
        biophysical model of neuronal dynamics.* Network: Computation in Neural
        Systems 14: 703-732, 2003.
    
    Equations are taken from [Larteretal_1999]_. Parameter values are taken from
    Table I, page 799.
    
    Regarding the choice of coupling: the three biophysically reasonable
    mechanisms (diffusive; delay line axonal/ synaptic or glial; and
    extracellular current flow) are dependent of K++. This is reflected in the
    potassium equilibrium potential (:math:`V_{K}`). Thus, this variable is
    chosen as the coupling element between nodes.
    
        .. figure :: img/Larter_01_mode_0_pplane.svg
            :alt: Larter phase plane (V, W)
            
            The (:math:`V`, :math:`W`) phase-plane for the Larter model.

    .. automethod:: Larter.dfun
    
    """

    # Define traited attributes for this model, these represent possible kwargs.
    gCa = NArray(label=":math:`g_{Ca}`",
                 default=numpy.array([1.1]),
                 domain=Range(lo=0.9, hi=1.5, step=0.01),
                 doc="""Conductance of population of Ca++ channels""")

    gK = NArray(label=":math:`g_K`",
                default=numpy.array([2.0]),
                domain=Range(lo=1.5, hi=2.5, step=0.01),
                doc="""Conductance of population of K channels""")

    gL = NArray(label=":math:`g_L`",
                default=numpy.array([0.5]),
                domain=Range(lo=0.25, hi=0.75, step=0.01),
                doc="""Conductance of population of leak channels""")

    phi = NArray(label=":math:`\\phi`",
                 default=numpy.array([0.7]),
                 domain=Range(lo=0.35, hi=1.05, step=0.01),
                 doc="""Temperature scaling factor""")

    V1 = NArray(label=":math:`V_1`",
                default=numpy.array([-0.01]),
                domain=Range(lo=-0.1, hi=0.1, step=0.01),
                doc="""Threshold value for :math:`M_{\\infty}`""")

    V2 = NArray(label=":math:`V_2`",
                default=numpy.array([0.15]),
                domain=Range(lo=0.01, hi=1.0, step=0.01),
                doc="""Steepness parameter for :math:`M_{\\infty}`""")

    V3 = NArray(label=":math:`V_3`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=1.0, step=0.01),
                doc="""Threshold value for :math:`W_{\\infty}`""")

    V4 = NArray(label=":math:`V_4`",
                default=numpy.array([0.3]),
                domain=Range(lo=0.01, hi=1.0, step=0.01),
                doc="""Steepness parameter for :math:`W_{\\infty}`""")

    V5 = NArray(label=":math:`V_5`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=1.0, step=0.01),
                doc="""Threshold value for :math:`a_{exc}`""")

    V6 = NArray(label=":math:`V_6`",
                default=numpy.array([0.6]),
                domain=Range(lo=0.01, hi=1.0, step=0.01),
                doc="""Steepness parameter for a_exc and :math:`a_{inh}`""")

    V7 = NArray(label=":math:`V_7`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=1.0, step=0.01),
                doc="""Threshold value for :math:`a_{inh}`""")

    VK = NArray(label=":math:`V_K`",
                default=numpy.array([-0.7]),
                domain=Range(lo=-0.8, hi=1.0, step=0.01),
                doc="""K Nernst potential""")

    VL = NArray(label=":math:`V_L`",
                default=numpy.array([-0.5]),
                domain=Range(lo=-0.75, hi=-0.25, step=0.01),
                doc="""Nernst potential leak channels""")

    tau_K = NArray(label=":math:`\\tau_K`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=0.5, hi=1.5, step=0.01),
                   doc="""Time constant for K relaxation time""")

    a_exc = NArray(label=":math:`a_{exc}`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=0.7, hi=1.3, step=0.01),
                   doc="""strength of excitatory synapse""")

    a_inh = NArray(label=":math:`a_{ie}`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=0.7, hi=1.3, step=0.01),
                   doc="""strength of inhibitory synapse""")

    b = NArray(label=":math:`b`",
               default=numpy.array([0.1]),
               domain=Range(lo=0.05, hi=0.15, step=0.01),
               doc="""Time constant scaling factor""")

    c = NArray(label=":math:`c`",
               default=numpy.array([0.165]),
               domain=Range(lo=0.0, hi=0.2, step=0.01),
               doc="""strength of feedforward inhibition""")

    Iext = NArray(
        label=":math:`I_{ext}`",
        default=numpy.array([0.3]),
        domain=Range(lo=0.15, hi=0.45, step=0.01),
        doc="""Subcortical input strength. It represents a non-specific
       excitation of both the excitatory and inhibitory populations.""")

    # Used for phase-plane axis ranges and to bound random initial() conditions.
    state_variable_range = Final(
        {
            "V": numpy.array([-0.3, 0.1]),
            "W": numpy.array([0.0, 0.6]),
            "Z": numpy.array([-0.02, 0.08])
        },
        label="State Variable ranges [lo, hi]",
        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.""")

    # variables_of_interest = arrays.IntegerArray(
    #     label = "Variables watched by Monitors",
    #     range = basic.Range(lo = 0, hi = 3, step=1),
    #     default = numpy.array([0, 2], dtype=numpy.int32),
    #     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:`V = 0`,
    #     :math:`W = 1`, and :math:`Z = 2`.""",
    #     order = 21)

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("V", "W", "Z"),
        default=("V", "W", "Z"),
        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:`V = 0`,
        :math:`W = 1`, and :math:`Z = 2`.""")

    def __init__(self, **kwargs):
        """
        Initialize the Larter model's traited attributes, any provided as
        keywords will overide their traited default.
        
        """
        LOG.info('%s: initing...' % str(self))
        super(Larter, self).__init__(**kwargs)

        #self._state_variables = ["V", "W", "Z"]
        self._nvar = 3

        self.cvar = numpy.array([0], dtype=numpy.int32)

        LOG.debug('%s: inited.' % repr(self))

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        """
        .. math::
             \\dot{V} &= - g_L \\, (V - V_L) - g_K\\, Z \\, (V - V_K) -
                 g_{Ca} \\, m_{\\infty} \\, (V - 1) + I - \\alpha_{inh}\\,Z  \\\\
             \\dot{W} &= \\frac{\\phi \\, (w_{\\infty} - W)}{\\tau_w} \\\\
             \\dot{Z}(t) &= b ( c \\, I_{ext} + \\alpha_{exc} \\,V ) \\\\
             m_{\\infty} &= 0.5 \\, \\left(1 + \\tanh\\left(\\frac{V - V_1}{V_2}\\right)\\right) \\\\
             w_{\\infty} &= 0.5 \\, \\left(1 + \\tanh\\left(\\frac{V - V_3}{V_4}\\right)\\right)\\\\
             tau_{w} &= \\left[ \\cosh\\left(\\frac{V - V_3}{2 \\,V_4}\\right) \\right]^{-1} \\\\
             \\alpha_{exc} &= a_{exc} \\,\\left(1 + \\tanh\\left(\\frac{V - V_5}{V_6}\\right)\\right)\\\\
             \\alpha_{inh} &= a_{inh} \\,\\left(1 + \\tanh\\left(\\frac{V - V_7}{V_6}\\right)\\right)
             
        See Eqs (1)-(8) in [Larteretal_1999]_
        """
        ##--------------------- As in Larter 1999 ----------------------------##
        V = state_variables[0, :]
        W = state_variables[1, :]
        Z = state_variables[2, :]

        c_0 = coupling[0, :]

        M_inf = 0.5 * (1 + numpy.tanh((V - self.V1) / self.V2))
        W_inf = 0.5 * (1 + numpy.tanh((V - self.V3) / self.V4))
        tau_Winv = numpy.cosh((V - self.V3) / (2 * self.V4))
        alpha_exc = self.a_exc * (1 + numpy.tanh((V - self.V5) / self.V6))
        alpha_inh = self.a_inh * (1 + numpy.tanh((V - self.V7) / self.V6))

        #import pdb; pdb.set_trace()
        dV = (local_coupling * V - alpha_inh * Z - self.gL * (V - self.VL) -
              self.gCa * M_inf * (V - 1) - self.gK * W * (V - self.VK + c_0) +
              self.Iext)

        dW = self.phi * tau_Winv * (W_inf - W)

        dZ = self.b * ((self.c * self.Iext) + (alpha_exc * V))

        derivative = numpy.array([dV, dW, dZ])

        return derivative
示例#24
0
class Kuramoto(Model):
    r"""
    The Kuramoto model is a model of synchronization phenomena derived by
    Yoshiki Kuramoto in 1975 which has since been applied to diverse domains
    including the study of neuronal oscillations and synchronization.

    See:

        .. [YK_1975] Y. Kuramoto, in: H. Arakai (Ed.), International Symposium
            on Mathematical Problems in Theoretical Physics, *Lecture Notes in
            Physics*, page 420, vol. 39, 1975.

        .. [SS_2000] S. H. Strogatz. *From Kuramoto to Crawford: exploring the
            onset of synchronization in populations of coupled oscillators*.
            Physica D, 143, 2000.

        .. [JC_2011] J. Cabral, E. Hugues, O. Sporns, G. Deco. *Role of local
            network oscillations in resting-state functional connectivity*.
            NeuroImage, 57, 1, 2011.

    The :math:`\theta` variable is the phase angle of the oscillation.

    Dynamic equations:
        .. math::

                \dot{\theta}_{k} = \omega_{k} + \mathbf{\Gamma}(\theta_k, \theta_j, u_{kj}) + \sin(W_{\zeta}\theta)

    """

    # Define traited attributes for this model, these represent possible kwargs.
    omega = NArray(label=r":math:`\omega`",
                   default=numpy.array([1.0]),
                   domain=Range(lo=0.01, hi=200.0, step=0.1),
                   doc=""":math:`\omega` sets the base line frequency for the
            Kuramoto oscillator in [rad/ms]""")

    state_variable_range = Final(
        label="State Variable ranges [lo, hi]",
        default={
            "theta": numpy.array([0.0, numpy.pi * 2.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."""
    )

    variables_of_interest = List(
        label="Variables watched by Monitors",
        choices=("theta", ),
        default=("theta", ),
        doc="""This represents the default state-variables of this Model to be
                            monitored. It can be overridden for each Monitor if desired. The Kuramoto
                            model, however, only has one state variable with and index of 0, so it
                            is not necessary to change the default here.""")

    state_variables = ['theta']
    _nvar = 1
    cvar = numpy.array([0], dtype=numpy.int32)

    def dfun(self,
             state_variables,
             coupling,
             local_coupling=0.0,
             ev=numexpr.evaluate,
             sin=numpy.sin,
             pi2=numpy.pi * 2):
        r"""
        The :math:`\theta` variable is the phase angle of the oscillation.

        .. math::
            \dot{\theta}_{k} = \omega_{k} + \mathbf{\Gamma}(\theta_k, \theta_j, u_{kj}) + \sin(W_{\zeta}\theta)

        where :math:`I` is the input via local and long range connectivity,
        passing first through the Kuramoto coupling function,
        :py:class:tvb.simulator.coupling.Kuramoto.

        """

        theta = state_variables[0, :]
        #import pdb; pdb.set_trace()

        #A) Distribution of phases according to the local connectivity kernel
        local_range_coupling = numpy.sin(local_coupling * theta)

        # NOTE: To evaluate.
        #B) Strength of the interactions
        #local_range_coupling = local_coupling * numpy.sin(theta)

        I = coupling[0, :] + local_range_coupling

        if not hasattr(self, 'derivative'):
            self.derivative = numpy.empty((1, ) + theta.shape)

        # phase update
        self.derivative[0] = self.omega + I

        # all this pi makeh me have great hungary, can has sum NaN?
        return self.derivative
示例#25
0
class Epileptor2D(ModelNumbaDfun):
    r"""
        Two-dimensional reduction of the Epileptor.
        
        .. moduleauthor:: [email protected]
        
        Taking advantage of time scale separation and focusing on the slower time scale, 
        the five-dimensional Epileptor reduces to a two-dimensional system (see [Proixetal_2014, 
        Proixetal_2017]).
        
        Note: the slow permittivity variable can be modify to account for the time
        difference between interictal and ictal states (see [Proixetal_2014]).
        
        Equations and default parameters are taken from [Proixetal_2014]:
        
        .. math::
            \dot{x_{1,i}} &=& - x_{1,i}^{3} - 2x_{1,i}^{2}  + 1 - z_{i} + I_{ext1,i} \\
            \dot{z_{i}} &=& r(h - z_{i})
        
        with
            h =
            \begin{cases}
            x_{0} + 3 / (exp((x_{1} + 0.5)/0.1)) & \text{if } modification\\
            4 (x_{1,i} - x_{0}) & \text{else }
            \end{cases}

        References:
            [Proixetal_2014] Proix, T.; Bartolomei, F; Chauvel, P; Bernard, C; Jirsa, V.K. *
            Permittivity coupling across brain regions determines seizure recruitment in 
            partial epilepsy.* J Neurosci 2014, 34:15009-21.
            
            [Proixetal_2017] Proix, T.; Bartolomei, F; Guye, M.; Jirsa, V.K. *Individual brain 
            structure and modelling predict seizure propagation.* Brain 2017, 140; 641–654.
    """

    a = NArray(
        label=":math:`a`",
        default=numpy.array([1.0]),
        doc="Coefficient of the cubic term in the first state-variable.")

    b = NArray(
        label=":math:`b`",
        default=numpy.array([3.0]),
        doc="Coefficient of the squared term in the first state-variable.")

    c = NArray(label=":math:`c`",
               default=numpy.array([1.0]),
               doc="Additive coefficient for the second state-variable x_{2}, \
        called :math:`y_{0}` in Jirsa paper.")

    d = NArray(
        label=":math:`d`",
        default=numpy.array([5.0]),
        doc=
        "Coefficient of the squared term in the second state-variable x_{2}.")

    r = NArray(label=":math:`r`",
               domain=Range(lo=0.0, hi=0.001, step=0.00005),
               default=numpy.array([0.00035]),
               doc="Temporal scaling in the slow state-variable, \
        called :math:`1\\tau_{0}` in Jirsa paper (see class Epileptor).")

    x0 = NArray(label=":math:`x_0`",
                domain=Range(lo=-3.0, hi=-1.0, step=0.1),
                default=numpy.array([-1.6]),
                doc="Epileptogenicity parameter.")

    Iext = NArray(label=":math:`I_{ext}`",
                  domain=Range(lo=1.5, hi=5.0, step=0.1),
                  default=numpy.array([3.1]),
                  doc="External input current to the first state-variable.")

    slope = NArray(label=":math:`slope`",
                   domain=Range(lo=-16.0, hi=6.0, step=0.1),
                   default=numpy.array([0.]),
                   doc="Linear coefficient in the first state-variable.")

    Kvf = NArray(label=":math:`K_{vf}`",
                 default=numpy.array([0.0]),
                 domain=Range(lo=0.0, hi=4.0, step=0.5),
                 doc="Coupling scaling on a very fast time scale.")

    Ks = NArray(
        label=":math:`K_{s}`",
        default=numpy.array([0.0]),
        domain=Range(lo=-4.0, hi=4.0, step=0.1),
        doc=
        "Permittivity coupling, that is from the fast time scale toward the slow time scale."
    )

    tt = NArray(
        label=":math:`tt`",
        default=numpy.array([1.0]),
        domain=Range(lo=0.001, hi=1.0, step=0.001),
        doc="Time scaling of the whole system to the system in real time.")

    modification = NArray(
        dtype=bool,
        label=":math:`modification`",
        default=numpy.array([False]),
        doc="When modification is True, then use nonlinear influence on z. \
        The default value is False, i.e., linear influence.")

    state_variable_range = Final(
        default={
            "x1": numpy.array([-2., 1.]),
            "z": numpy.array([2.0, 5.0])
        },
        label="State variable ranges [lo, hi]",
        doc="Typical bounds on state-variables in the Epileptor 2D model.")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=('x1', 'z'),
        default=('x1', ),
        doc="Quantities of the Epileptor 2D available to monitor.")

    state_variables = ('x1', 'z')

    _nvar = 2
    cvar = numpy.array([0], dtype=numpy.int32)

    def _numpy_dfun(self,
                    state_variables,
                    coupling,
                    local_coupling=0.0,
                    array=numpy.array,
                    where=numpy.where,
                    concat=numpy.concatenate):

        y = state_variables
        ydot = numpy.empty_like(state_variables)

        Iext = self.Iext + local_coupling * y[0]
        c_pop = coupling[0, :]

        # population 1
        if_ydot0 = self.a * y[0]**2 + (self.d - self.b) * y[0]
        else_ydot0 = -self.slope - 0.6 * (y[1] - 4.0)**2 + self.d * y[0]

        ydot[0] = self.tt * (self.c - y[1] + Iext + self.Kvf * c_pop -
                             (where(y[0] < 0., if_ydot0, else_ydot0)) * y[0])

        # energy
        if_ydot1 = -0.1 * y[1]**7
        else_ydot1 = 0

        if self.modification:
            h = self.x0 + 3. / (1. + numpy.exp(-(y[0] + 0.5) / 0.1))
        else:
            h = 4 * (y[0] - self.x0) + where(y[1] < 0., if_ydot1, else_ydot1)

        ydot[1] = self.tt * (self.r * (h - y[1] + self.Ks * c_pop))

        return ydot

    def dfun(self, x, c, local_coupling=0.0):
        r"""
        Computes the derivatives of the state-variables of the Epileptor 2D
        with respect to time.

        Equations and default parameters are taken from [Proixetal_2014]:

        .. math::
            \dot{x_{1,i}} &=& - x_{1,i}^{3} - 2x_{1,i}^{2}  + 1 - z_{i} + I_{ext1,i} \\
            \dot{z_{i}} &=& r(h - z_{i})

        with
            h =
            \begin{cases}
            x_{0} + 3 / (exp((x_{1} + 0.5)/0.1)) & \text{if } modification\\
            4 (x_{1,i} - x_{0}) & \text{else }
            \end{cases}
        """

        x_ = x.reshape(x.shape[:-1]).T
        c_ = c.reshape(c.shape[:-1]).T
        Iext = self.Iext + local_coupling * x[0, :, 0]
        deriv = _numba_dfun_epi2d(x_, c_, self.x0, Iext, self.a, self.b,
                                  self.slope, self.c, self.d, self.r, self.Kvf,
                                  self.Ks, self.tt, self.modification)
        return deriv.T[..., numpy.newaxis]
示例#26
0
class SupHopf(ModelNumbaDfun):
    r"""
    The supHopf model describes the normal form of a supercritical Hopf bifurcation in Cartesian coordinates.
    This normal form has a supercritical bifurcation at a=0 with a the bifurcation parameter in the model. So 
    for a < 0, the local dynamics has a stable fixed point and the system corresponds to a damped oscillatory 
    state, whereas for a > 0, the local dynamics enters in a stable limit cycle and the system switches to an 
    oscillatory state.
    
    See for examples:
    
    .. [Kuznetsov_2013] Kuznetsov, Y.A. *Elements of applied bifurcation theory.* Springer Sci & Business
        Media, 2013, vol. 112.
    
    .. [Deco_2017a] Deco, G., Kringelbach, M.L., Jirsa, V.K., Ritter, P. *The dynamics of resting fluctuations
       in the brain: metastability and its dynamical cortical core* Sci Reports, 2017, 7: 3095.
    
    The equations of the supHopf equations read as follows:
    
    .. math::
        \dot{x}_{i} &= (a_{i} - x_{i}^{2} - y_{i}^{2})x_{i} - omega{i}y_{i} \\
        \dot{y}_{i} &= (a_{i} - x_{i}^{2} - y_{i}^{2})y_{i} + omega{i}x_{i}
    
    where a is the local bifurcation parameter and omega the angular frequency.
    """

    a = NArray(label=r":math:`a`",
               default=numpy.array([-0.5]),
               domain=Range(lo=-10.0, hi=10.0, step=0.01),
               doc="""Local bifurcation parameter.""")

    omega = NArray(label=r":math:`\omega`",
                   default=numpy.array([1.]),
                   domain=Range(lo=0.05, hi=630.0, step=0.01),
                   doc="""Angular frequency.""")

    # Initialization.
    state_variable_range = Final(
        label="State Variable ranges [lo, hi]",
        default={
            "x": numpy.array([-5.0, 5.0]),
            "y": numpy.array([-5.0, 5.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."""
    )

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=("x", "y"),
        default=("x", ),
        doc="Quantities of supHopf available to monitor.")

    state_variables = ["x", "y"]

    _nvar = 2  # number of state-variables
    cvar = numpy.array([0, 1], dtype=numpy.int32)  # coupling variables

    def _numpy_dfun(self,
                    state_variables,
                    coupling,
                    local_coupling=0.0,
                    array=numpy.array,
                    where=numpy.where,
                    concat=numpy.concatenate):
        r"""
        Computes the derivatives of the state-variables of supHopf
        with respect to time.
        """

        y = state_variables
        ydot = numpy.empty_like(state_variables)

        # long-range coupling
        c_0 = coupling[0]
        c_1 = coupling[1]

        # short-range (local) coupling
        lc_0 = local_coupling * y[0]

        #supHopf's equations in Cartesian coordinates:
        ydot[0] = (self.a - y[0]**2 -
                   y[1]**2) * y[0] - self.omega * y[1] + c_0 + lc_0
        ydot[1] = (self.a - y[0]**2 - y[1]**2) * y[1] + self.omega * y[0] + c_1

        return ydot

    def dfun(self, x, c, local_coupling=0.0):
        x_ = x.reshape(x.shape[:-1]).T
        c_ = c.reshape(c.shape[:-1]).T
        lc_0 = local_coupling * x[0, :, 0]
        deriv = _numba_dfun_supHopf(x_, c_, self.a, self.omega, lc_0)

        return deriv.T[..., numpy.newaxis]
示例#27
0
class Simulator(HasTraits):
    """A Simulator assembles components required to perform simulations."""

    connectivity = Attr(
        field_type=connectivity.Connectivity,
        label="Long-range connectivity",
        default=None,
        required=True,
        doc="""A tvb.datatypes.Connectivity object which contains the
         structural long-range connectivity data (i.e., white-matter tracts). In
         combination with the ``Long-range coupling function`` it defines the inter-regional
         connections. These couplings undergo a time delay via signal propagation
         with a propagation speed of ``Conduction Speed``""")

    conduction_speed = Float(
        label="Conduction Speed",
        default=3.0,
        required=False,
        # range=basic.Range(lo=0.01, hi=100.0, step=1.0),
        doc="""Conduction speed for ``Long-range connectivity`` (mm/ms)""")

    coupling = Attr(
        field_type=coupling.Coupling,
        label="Long-range coupling function",
        default=coupling.Linear(),
        required=True,
        doc="""The coupling function is applied to the activity propagated
        between regions by the ``Long-range connectivity`` before it enters the local
        dynamic equations of the Model. Its primary purpose is to 'rescale' the
        incoming activity to a level appropriate to Model.""")

    surface: cortex.Cortex = Attr(
        field_type=cortex.Cortex,
        label="Cortical surface",
        default=None,
        required=False,
        doc="""By default, a Cortex object which represents the
        cortical surface defined by points in the 3D physical space and their
        neighborhood relationship. In the current TVB version, when setting up a
        surface-based simulation, the option to configure the spatial spread of
        the ``Local Connectivity`` is available.""")

    stimulus = Attr(
        field_type=patterns.SpatioTemporalPattern,
        label="Spatiotemporal stimulus",
        default=None,
        required=False,
        doc=
        """A ``Spatiotemporal stimulus`` can be defined at the region or surface level.
        It's composed of spatial and temporal components. For region defined stimuli
        the spatial component is just the strength with which the temporal
        component is applied to each region. For surface defined stimuli,  a
        (spatial) function, with finite-support, is used to define the strength
        of the stimuli on the surface centred around one or more focal points.
        In the current version of TVB, stimuli are applied to the first state
        variable of the ``Local dynamic model``.""")

    model: Model = Attr(
        field_type=models.Model,
        label="Local dynamic model",
        default=models.Generic2dOscillator(),
        required=True,
        doc="""A tvb.simulator.Model object which describe the local dynamic
        equations, their parameters, and, to some extent, where connectivity
        (local and long-range) enters and which state-variables the Monitors
        monitor. By default the 'Generic2dOscillator' model is used. Read the
        Scientific documentation to learn more about this model.""")

    integrator = Attr(field_type=integrators.Integrator,
                      label="Integration scheme",
                      default=integrators.HeunDeterministic(),
                      required=True,
                      doc="""A tvb.simulator.Integrator object which is
            an integration scheme with supporting attributes such as
            integration step size and noise specification for stochastic
            methods. It is used to compute the time courses of the model state
            variables.""")

    initial_conditions = NArray(
        label="Initial Conditions",
        required=False,
        doc="""Initial conditions from which the simulation will begin. By
        default, random initial conditions are provided. Needs to be the same shape
        as simulator 'history', ie, initial history function which defines the 
        minimal initial state of the network with time delays before time t=0. 
        If the number of time points in the provided array is insufficient the 
        array will be padded with random values based on the 'state_variables_range'
        attribute.""")

    monitors = List(
        of=monitors.Monitor,
        label="Monitor(s)",
        default=(monitors.TemporalAverage(), ),
        doc="""A tvb.simulator.Monitor or a list of tvb.simulator.Monitor
        objects that 'know' how to record relevant data from the simulation. Two
        main types exist: 1) simple, spatial and temporal, reductions (subsets
        or averages); 2) physiological measurements, such as EEG, MEG and fMRI.
        By default the Model's specified variables_of_interest are returned,
        temporally downsampled from the raw integration rate to a sample rate of
        1024Hz.""")

    simulation_length = Float(
        label="Simulation Length (ms, s, m, h)",
        default=1000.0,  # ie 1 second
        required=True,
        doc="""The length of a simulation (default in milliseconds).""")

    backend = ReferenceBackend()

    history = None  # type: SparseHistory

    @property
    def good_history_shape(self):
        """Returns expected history shape."""
        n_reg = self.connectivity.number_of_regions
        shape = self.connectivity.horizon, len(
            self.model.state_variables), n_reg, self.model.number_of_modes
        return shape

    calls = 0
    current_step = 0
    number_of_nodes = None
    _memory_requirement_guess = None
    _memory_requirement_census = None
    _storage_requirement = None
    _runtime = None

    integrate_next_step = None

    # methods consist of
    # 1) generic configure
    # 2) component specific configure
    # 3) loop preparation
    # 4) loop step
    # 5) estimations

    @property
    def is_surface_simulation(self):
        if self.surface:
            return True
        return False

    def configure_integration_for_model(self):
        self.integrator.configure_boundaries(self.model)
        if self.model.has_nonint_vars:
            self.integrate_next_step = self.integrator.integrate_with_update
            self.integrator. \
                reconfigure_boundaries_and_clamping_for_integration_state_variables(self.model)
        else:
            self.integrate_next_step = self.integrator.integrate

    def preconfigure(self):
        """Configure just the basic fields, so that memory can be estimated."""
        self.connectivity.configure()
        if self.surface:
            self.surface.configure()
        if self.stimulus:
            self.stimulus.configure()
        self.coupling.configure()
        # ------- Keep this order of configurations ----
        self.model.configure()  # 1
        self.integrator.configure()  # 2
        # Configure integrators' next step computation
        # and state variables' boundaries and clamping,
        # based on model attributes  # 3
        self.configure_integration_for_model()
        # ----------------------------------------------
        # monitors needs to be a list or tuple, even if there is only one...
        if not isinstance(self.monitors, (list, tuple)):
            self.monitors = [self.monitors]
        # Configure monitors
        for monitor in self.monitors:
            monitor.configure()
        self._set_number_of_nodes()
        self._guesstimate_memory_requirement()

    def _set_number_of_nodes(self):
        # "Nodes" refers to either regions or vertices + non-cortical regions.
        if self.surface is None:
            self.number_of_nodes = self.connectivity.number_of_regions
            self.log.info('Region simulation with %d ROI nodes',
                          self.number_of_nodes)
        else:
            self._regmap, nc, nsc = self.backend.full_region_map(
                self.surface, self.connectivity)
            self.number_of_nodes = nc + nsc
            self.log.info(
                'Surface simulation with %d vertices + %d non-cortical, %d total nodes',
                nc, nsc, self.number_of_nodes)

    def configure(self, full_configure=True):
        """Configure simulator and its components.

        The first step of configuration is to run the configure methods of all
        the Simulator's components, ie its traited attributes.

        Configuration of a Simulator primarily consists of calculating the
        attributes, etc, which depend on the combinations of the Simulator's
        traited attributes (keyword args).

        Converts delays from physical time units into integration steps
        and updates attributes that depend on combinations of the 6 inputs.

        Returns
        -------
        sim: Simulator
            The configured Simulator instance.

        """
        if full_configure:
            # When run from GUI, preconfigure is run separately, and we want to avoid running that part twice
            self.preconfigure()
        self.model._spatialize_model_parameters(sim=self)
        # Configure spatial component of any stimuli
        self._configure_stimuli()
        # Set delays, provided in physical units, in integration steps.
        self.connectivity.set_idelays(self.integrator.dt)
        # Reshape integrator.noise.nsig, if necessary.
        if isinstance(self.integrator, integrators.IntegratorStochastic):
            self._configure_integrator_noise()
        # create history
        # TODO refactor history impl to backend
        self._configure_history()
        # Configure Monitors to work with selected Model, etc...
        self._configure_monitors()
        # Estimate of memory usage.
        self._census_memory_requirement()
        # Allow user to chain configure to another call or assignment.
        return self

    def _prepare_local_coupling(self):
        if self.surface is None:
            return 0.0
        return self.surface.prepare_local_coupling(self.number_of_nodes)

    def _loop_compute_node_coupling(self, step):
        """Compute delayed node coupling values."""
        coupling = self.coupling(step, self.history)
        if self.surface is not None:
            coupling = coupling[:, self._regmap]
        return coupling

    def _prepare_stimulus(self):
        if self.stimulus is None:
            stimulus = 0.0
        else:
            # TODO time grid wrong for continuations
            time = numpy.r_[0.0:self.simulation_length:self.integrator.dt]
            self.stimulus.configure_time(time.reshape((1, -1)))
            stimulus = numpy.zeros((self.model.nvar, self.number_of_nodes, 1))
            self.log.debug("stimulus shape is: %s", stimulus.shape)
        return stimulus

    def _loop_update_stimulus(self, step, stimulus):
        """Update stimulus values for current time step."""
        if self.stimulus is not None:
            # TODO stim_step != current step
            stim_step = step - (self.current_step + 1)
            stimulus[self.model.stvar, :, :] = self.stimulus(
                stim_step).reshape((1, -1, 1))

    def _loop_update_history(self, step, state):
        """Update history."""
        if self.surface is not None and state.shape[
                1] > self.connectivity.number_of_regions:
            state = self.backend.surface_state_to_rois(
                self._regmap, self.connectivity.number_of_regions, state)
        self.history.update(step, state)

    def _loop_monitor_output(self, step, state, node_coupling):
        observed = self.model.observe(state)
        output = [
            monitor.record(
                step, node_coupling if isinstance(
                    monitor, monitors.AfferentCoupling) else observed)
            for monitor in self.monitors
        ]
        if any(outputi is not None for outputi in output):
            return output

    def __call__(self,
                 simulation_length=None,
                 random_state=None,
                 n_steps=None):
        """
        Return an iterator which steps through simulation time, generating monitor outputs.

        See the run method for a convenient way to collect all output in one call.

        :param simulation_length: Length of the simulation to perform in ms.
        :param random_state:  State of NumPy RNG to use for stochastic integration.
        :param n_steps: Length of the simulation to perform in integration steps. Overrides simulation_length.
        :return: Iterator over monitor outputs.
        """

        self.calls += 1
        if simulation_length is not None:
            self.simulation_length = float(simulation_length)

        # initialization
        self._guesstimate_runtime()
        self._calculate_storage_requirement()
        # TODO a provided random_state should be used for history init
        self.integrator.set_random_state(random_state)
        local_coupling = self._prepare_local_coupling()
        stimulus = self._prepare_stimulus()
        state = self.current_state
        start_step = self.current_step + 1
        node_coupling = self._loop_compute_node_coupling(start_step)

        # integration loop
        if n_steps is None:
            n_steps = int(
                math.ceil(self.simulation_length / self.integrator.dt))
        else:
            if not numpy.issubdtype(type(n_steps), numpy.integer):
                raise TypeError(
                    "Incorrect type for n_steps: %s, expected integer" %
                    type(n_steps))

        for step in range(start_step, start_step + n_steps):
            self._loop_update_stimulus(step, stimulus)
            state = self.integrate_next_step(state, self.model, node_coupling,
                                             local_coupling, stimulus)
            self._loop_update_history(step, state)
            node_coupling = self._loop_compute_node_coupling(step + 1)
            output = self._loop_monitor_output(step, state, node_coupling)
            if output is not None:
                yield output

        self.current_state = state
        self.current_step = self.current_step + n_steps

    def _configure_history(self, initial_conditions=None):
        self.history = SparseHistory.from_simulator(self, initial_conditions)

    def _configure_integrator_noise(self):
        """
        This enables having noise to be state variable specific and/or to enter
        only via specific brain structures, for example it we only want to
        consider noise as an external input entering the brain via appropriate
        thalamic nuclei.

        Support 3 possible shapes:
            1) number_of_nodes;

            2) number_of_state_variables or number_of_integrated_state_variables; and

            3) (number_of_state_variables or number_of_integrated_state_variables, number_of_nodes).

        """
        # Noise has to have a shape corresponding to only the integrated state variables!
        good_history_shape = list(self.good_history_shape[1:])
        good_history_shape[0] = self.model.nintvar
        if self.integrator.noise.ntau > 0.0:
            self.integrator.noise.configure_coloured(self.integrator.dt,
                                                     tuple(good_history_shape))
        else:
            self.integrator.noise.configure_white(self.integrator.dt,
                                                  tuple(good_history_shape))

        if self.surface is not None:
            if self.integrator.noise.nsig.size == self.connectivity.number_of_regions:
                self.integrator.noise.nsig = self.integrator.noise.nsig[
                    self.surface.region_mapping]
            elif self.integrator.noise.nsig.size == self.model.nvar * self.connectivity.number_of_regions:
                self.integrator.noise.nsig = \
                    self.integrator.noise.nsig[self.model.state_variable_mask][:, self.surface.region_mapping]
            elif self.integrator.noise.nsig.size == self.model.nintvar * self.connectivity.number_of_regions:
                self.integrator.noise.nsig = self.integrator.noise.nsig[:,
                                                                        self.
                                                                        surface
                                                                        .
                                                                        region_mapping]

        good_nsig_shape = (self.model.nintvar, self.number_of_nodes,
                           self.model.number_of_modes)
        nsig = self.integrator.noise.nsig
        self.log.debug("Given noise shape is %s", nsig.shape)
        if nsig.shape in (good_nsig_shape, (1, )):
            return
        elif nsig.shape == (self.model.nvar, ):
            nsig = nsig[self.model.state_variable_mask].reshape(
                (self.model.nintvar, 1, 1))
        elif nsig.shape == (self.model.nintvar, ):
            nsig = nsig.reshape((self.model.nintvar, 1, 1))
        elif nsig.shape == (self.number_of_nodes, ):
            nsig = nsig.reshape((1, self.number_of_nodes, 1))
        elif nsig.shape == (self.model.nvar, self.number_of_nodes):
            nsig = nsig[self.model.state_variable_mask].reshape(
                (self.n_intvar, self.number_of_nodes, 1))
        elif nsig.shape == (self.model.nintvar, self.number_of_nodes):
            nsig = nsig.reshape((self.model.nintvar, self.number_of_nodes, 1))
        else:
            msg = "Bad Simulator.integrator.noise.nsig shape: %s"
            self.log.error(msg % str(nsig.shape))

        self.log.debug("Corrected noise shape is %s", nsig.shape)
        self.integrator.noise.nsig = nsig

    def _configure_monitors(self):
        """ Configure the requested Monitors for this Simulator """
        # Coerce to list if required
        if not isinstance(self.monitors, (list, tuple)):
            self.monitors = [self.monitors]
        # Configure monitors
        for monitor in self.monitors:
            monitor.config_for_sim(self)

    def _configure_stimuli(self):
        """ Configure the defined Stimuli for this Simulator """
        if self.stimulus is not None:
            if self.surface:
                # NOTE the region mapping of the stimuli should also include the subcortical areas
                self.stimulus.configure_space(region_mapping=numpy.r_[
                    self.surface.region_mapping,
                    self.connectivity.unmapped_indices(self.surface.
                                                       region_mapping)])
            else:
                self.stimulus.configure_space()

    # used by simulator adaptor
    def memory_requirement(self):
        """
        Return an estimated of the memory requirements (Bytes) for this
        simulator's current configuration.
        """
        self._guesstimate_memory_requirement()
        return self._memory_requirement_guess

    # appears to be unused
    def runtime(self, simulation_length):
        """
        Return an estimated run time (seconds) for the simulator's current
        configuration and a specified simulation length.

        """
        self.simulation_length = simulation_length
        self._guesstimate_runtime()
        return self._runtime

    # used by simulator adaptor
    def storage_requirement(self):
        """
        Return an estimated storage requirement (Bytes) for the simulator's
        current configuration and a specified simulation length.

        """
        self._calculate_storage_requirement()
        return self._storage_requirement

    def _guesstimate_memory_requirement(self):
        """
        guesstimate the memory required for this simulator.

        Guesstimate is based on the shape of the dominant arrays, and as such
        can operate before configuration.

        NOTE: Assumes returned/yeilded data is in some sense "taken care of" in
            the world outside the simulator, and so doesn't consider it, making
            the simulator's history, and surface if present, the dominant
            memory pigs...

        """
        if self.surface:
            number_of_nodes = self.surface.number_of_vertices
        else:
            number_of_nodes = self.connectivity.number_of_regions

        number_of_regions = self.connectivity.number_of_regions

        magic_number = 2.42  # Current guesstimate is low by about a factor of 2, seems safer to over estimate...
        bits_64 = 8.0  # Bytes
        bits_32 = 4.0  # Bytes
        # NOTE: The speed hack for getting the first element of hist shape should
        #      partially resolves calling of this method with a non-configured
        #     connectivity, there remains the less common issue if no tract_lengths...
        hist_shape = (
            self.connectivity.tract_lengths.max() /
            (self.conduction_speed or self.connectivity.speed or 3.0) /
            self.integrator.dt, self.model.nvar, number_of_nodes,
            self.model.number_of_modes)
        self.log.debug("Estimated history shape is %r", hist_shape)

        memreq = numpy.prod(hist_shape) * bits_64
        if self.surface:
            memreq += self.surface.number_of_triangles * 3 * bits_32 * 2  # normals
            memreq += self.surface.number_of_vertices * 3 * bits_64 * 2  # normals
            memreq += number_of_nodes * number_of_regions * bits_64 * 4  # region_mapping, region_average, region_sum
            # ???memreq += self.surface.local_connectivity.matrix.nnz * 8

        if not hasattr(self.monitors, '__len__'):
            self.monitors = [self.monitors]

        for monitor in self.monitors:
            if not isinstance(monitor, monitors.Bold):
                stock_shape = (monitor.period / self.integrator.dt,
                               len(self.model.variables_of_interest),
                               number_of_nodes, self.model.number_of_modes)
                memreq += numpy.prod(stock_shape) * bits_64
                if hasattr(monitor, "sensors"):
                    try:
                        memreq += number_of_nodes * monitor.sensors.number_of_sensors * bits_64  # projection_matrix
                    except AttributeError:
                        self.log.debug(
                            "No sensors specified, guessing memory based on default EEG."
                        )
                        memreq += number_of_nodes * 62.0 * bits_64

            else:
                stock_shape = (monitor.hrf_length * monitor._stock_sample_rate,
                               len(self.model.variables_of_interest),
                               number_of_nodes, self.model.number_of_modes)
                interim_stock_shape = (1.0 / (2.0**-2 * self.integrator.dt),
                                       len(self.model.variables_of_interest),
                                       number_of_nodes,
                                       self.model.number_of_modes)
                memreq += numpy.prod(stock_shape) * bits_64
                memreq += numpy.prod(interim_stock_shape) * bits_64

        if psutil and memreq > psutil.virtual_memory().total:
            self.log.warning(
                "There may be insufficient memory for this simulation.")

        self._memory_requirement_guess = magic_number * memreq
        msg = "Memory requirement estimate: simulation will need about %.1f MB"
        self.log.info(msg, self._memory_requirement_guess / 2**20)

    def _census_memory_requirement(self):
        """
        Guesstimate the memory required for this simulator.

        Guesstimate is based on a census of the dominant arrays after the
        simulator has been configured.

        NOTE: Assumes returned/yeilded data is in some sense "taken care of" in
            the world outside the simulator, and so doesn't consider it, making
            the simulator's history, and surface if present, the dominant
            memory pigs...

        """
        magic_number = 2.42  # Current guesstimate is low by about a factor of 2, seems safer to over estimate...
        memreq = self.history.nbytes
        try:
            memreq += self.surface.triangles.nbytes * 2
            memreq += self.surface.vertices.nbytes * 2
            memreq += self.surface.region_mapping.nbytes * self.number_of_nodes * 8. * 4  # region_average, region_sum
            memreq += self.surface.local_connectivity.matrix.nnz * 8
        except AttributeError:
            pass

        for monitor in self.monitors:
            memreq += monitor._stock.nbytes
            if isinstance(monitor, monitors.Bold):
                memreq += monitor._interim_stock.nbytes

        if psutil and memreq > psutil.virtual_memory().total:
            self.log.warning("Memory estimate exceeds total available RAM.")

        self._memory_requirement_census = magic_number * memreq
        # import pdb; pdb.set_trace()
        msg = "Memory requirement census: simulation will need about %.1f MB"
        self.log.info(msg % (self._memory_requirement_census / 1048576.0))

    def _guesstimate_runtime(self):
        """
        Estimate the runtime for this simulator.

        Spread in parallel executions of larger arrays means this will be an over-estimation,
        or rather a single threaded estimation...
        Different choice of integrators and monitors has an additional effect,
        on the magic number though relatively minor

        """
        magic_number = 6.57e-06  # seconds
        self._runtime = (magic_number * self.number_of_nodes *
                         self.model.nvar * self.model.number_of_modes *
                         self.simulation_length / self.integrator.dt)
        msg = "Simulation runtime should be about %0.3f seconds"
        self.log.info(msg, self._runtime)

    def _calculate_storage_requirement(self):
        """
        Calculate the storage requirement for the simulator, configured with
        models, monitors, etc being run for a particular simulation length.
        While this is only approximate, it is far more reliable/accurate than
        the memory and runtime guesstimates.
        """
        self.log.info("Calculating storage requirement for ...")
        strgreq = 0
        for monitor in self.monitors:
            # Avoid division by zero for monitor not yet configured
            # (in framework this is executed, when only preconfigure has been called):
            current_period = monitor.period or self.integrator.dt
            strgreq += (TvbProfile.current.MAGIC_NUMBER *
                        self.simulation_length * self.number_of_nodes *
                        self.model.nvar * self.model.number_of_modes /
                        current_period)
        self.log.info("Calculated storage requirement for simulation: %d " %
                      int(strgreq))
        self._storage_requirement = int(strgreq)

    def run(self, **kwds):
        """Convenience method to call the simulator with **kwds and collect output data."""
        ts, xs = [], []
        for _ in self.monitors:
            ts.append([])
            xs.append([])
        wall_time_start = time.time()
        for data in self(**kwds):
            for tl, xl, t_x in zip(ts, xs, data):
                if t_x is not None:
                    t, x = t_x
                    tl.append(t)
                    xl.append(x)
        elapsed_wall_time = time.time() - wall_time_start
        self.log.info("%.3f s elapsed, %.3fx real time", elapsed_wall_time,
                      elapsed_wall_time * 1e3 / self.simulation_length)
        for i in range(len(ts)):
            ts[i] = numpy.array(ts[i])
            xs[i] = numpy.array(xs[i])
        return list(zip(ts, xs))
示例#28
0
class EpileptorCodim3SlowMod(ModelNumbaDfun):
    r"""
    .. [Saggioetal_2017] Saggio ML, Spiegler A, Bernard C, Jirsa VK.
    *Fast–Slow Bursters in the Unfolding of a High Codimension Singularity
    and the Ultra-slow Transitions of Classes.* Journal of Mathematical
    Neuroscience. 2017;7:7. doi:10.1186/s13408-017-0050-8.

    .. The Epileptor codim 3 model is a neural mass model which contains two
    subsystems acting at different timescales. For the fast subsystem we use
    the unfolding of a degenerate Takens-Bogdanov bifucation of codimension
    3. The slow subsystem steers the fast one back and forth along these
    paths leading to bursting behavior. The model is able to produce almost
    all the classes of bursting predicted for systems with a planar fast
    subsystem.

    .. In this implementation the model can produce Hysteresis-Loop bursters
    of classes c0, c0', c2s, c3s, c4s, c10s, c11s, c2b, c4b, c8b, c14b and
    c16b as classified by [Saggioetal_2017] Table 2. Through ultra-slow
    modulation of the path through the parameter space we can switch between
    different classes of bursters.

    """

    mu1_Ain = NArray(
        label="mu1 Ain",
        default=numpy.array([0.05494]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu1 at the initial point at bursting offset.")

    mu2_Ain = NArray(
        label="mu2 Ain",
        default=numpy.array([0.2731]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu2 at the initial point at bursting offset.")

    nu_Ain = NArray(
        label="nu Ain",
        default=numpy.array([0.287]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter nu at the initial point at bursting offset.")

    mu1_Bin = NArray(
        label="mu1 Bin",
        default=numpy.array([-0.0461]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu1 at the initial point at bursting onset.")

    mu2_Bin = NArray(
        label="mu2 Bin",
        default=numpy.array([0.243]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu2 at the initial point at bursting onset.")

    nu_Bin = NArray(
        label="nu Bin",
        default=numpy.array([0.3144]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter nu at the initial point at bursting onset.")

    mu1_Aend = NArray(
        label="mu1 Aend",
        default=numpy.array([0.06485]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu1 at the initial point at bursting offset.")

    mu2_Aend = NArray(
        label="mu2 Aend",
        default=numpy.array([0.07337]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu2 at the initial point at bursting offset.")

    nu_Aend = NArray(
        label="nu Aend",
        default=numpy.array([-0.3878]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter nu at the initial point at bursting offset.")

    mu1_Bend = NArray(
        label="mu1 Bend",
        default=numpy.array([0.03676]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu1 at the initial point at bursting onset.")

    mu2_Bend = NArray(
        label="mu2 Bend",
        default=numpy.array([-0.02792]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter mu2 at the initial point at bursting onset.")

    nu_Bend = NArray(
        label="nu Bend",
        default=numpy.array([-0.3973]),
        domain=Range(lo=-1.0, hi=1.0),
        doc="The bifurcation parameter nu at the initial point at bursting onset.")

    b = NArray(
        label="b",
        default=numpy.array([1.0]),
        doc="Unfolding type of the degenerate Takens-Bogdanov bifurcation, default is a focus type")

    R = NArray(
        label="R",
        default=numpy.array([0.4]),
        domain=Range(lo=0.0, hi=2.5),
        doc="Radius in unfolding")

    c = NArray(
        label="c",
        default=numpy.array([0.002]),
        domain=Range(lo=0.0, hi=0.01),
        doc="Speed of the slow variable")

    cA = NArray(
        label="cA",
        default=numpy.array([0.0001]),
        domain=Range(lo=0.0, hi=0.001),
        doc="Speed of the ultra-slow transition of the initial point")

    cB = NArray(
        label="cB",
        default=numpy.array([0.00012]),
        domain=Range(lo=0.0, hi=0.001),
        doc="Speed of the ultra-slow transition of the final point")

    dstar = NArray(
        label="dstar",
        default=numpy.array([0.3]),
        domain=Range(lo=-0.1, hi=0.5),
        doc="Threshold for the inversion of the slow variable")

    Ks = NArray(
        label="Ks",
        default=numpy.array([0.0]),
        doc="Slow permittivity coupling strength, the default is no coupling")

    N = NArray(
        dtype=int,
        label="N",
        default=numpy.array([1]),
        doc="The branch of the resting state, default is 1")

    modification = NArray(
        dtype=bool,
        label="modification",
        default=numpy.array([True]),
        doc="When modification is True, then use the modification to stabilise the system for negative values of "
            "dstar. If modification is False, then don't use the modification. The default value is True ")

    state_variable_range = Final(
        label="State variable ranges [lo, hi]",
        default={"x": numpy.array([0.4, 0.6]),
                 "y": numpy.array([-0.1, 0.1]),
                 "z": numpy.array([0.0, 0.1]),
                 "uA": numpy.array([0.0, 0.0]),
                 "uB": numpy.array([0.0, 0.0])},
        doc="Typical bounds on state variables.")

    variables_of_interest = List(
        of=str,
        label="Variables watched by Monitors",
        choices=('x', 'y', 'z'),
        default=('x', 'z'),
        doc="Quantities available to monitor.")

    # state variables names
    state_variables = ('x', 'y', 'z', 'uA', 'uB')

    # number of state variables
    _nvar = 5
    cvar = numpy.array([0], dtype=numpy.int32)

    # If there are derived parameters from the predefined parameters, then initialize them to None
    G = None
    H = None
    L = None
    M = None

    def update_derived_parameters(self):
        r"""
        The equations were adapted from [Saggioetal_2017]
        cf. Eqn. (7), page 17 and page 21

        We parametrize the great arc on the sphere of radius R between the
        points Ain and Aend with the vectors G and H. This great arc is used
        for the offset point of the burster, given by the vector A.

            .. math::
                G &= Ain/\|Ain\| \\
                H &= ((Ain \times Aend) \times Ain)/\|(Ain \times Aend) \times Ain\|

        We also parametrize the great arc on the sphere of radius R between the
        points Bin and Bend with the vectors L and M. This great arc is used
        for the onset point of the burster, given by the vector B.

            .. math::
                L &= Bin/\|Bin\| \\
                M &= ((Bin \times Bend) \times Bin)/\|(Bin \times Bend) \times Bin\|
        """

        Ain = numpy.array([self.mu2_Ain[0], -self.mu1_Ain[0], self.nu_Ain[0]])
        Bin = numpy.array([self.mu2_Bin[0], -self.mu1_Bin[0], self.nu_Bin[0]])
        Aend = numpy.array(
            [self.mu2_Aend[0], -self.mu1_Aend[0], self.nu_Aend[0]])
        Bend = numpy.array(
            [self.mu2_Bend[0], -self.mu1_Bend[0], self.nu_Bend[0]])

        self.G = Ain / numpy.linalg.norm(Ain)
        self.H = numpy.cross(numpy.cross(Ain, Aend), Ain)
        self.H = self.H / numpy.linalg.norm(self.H)

        self.L = Bin / numpy.linalg.norm(Bin)
        self.M = numpy.cross(numpy.cross(Bin, Bend), Bin)
        self.M = self.M / numpy.linalg.norm(self.M)

    def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0):
        x = state_variables[0, :]
        y = state_variables[1, :]
        z = state_variables[2, :]
        uA = state_variables[3, :]
        uB = state_variables[4, :]

        A = self.R * (self.G * numpy.cos(uA) + self.H * numpy.sin(uA))
        B = self.R * (self.L * numpy.cos(uB) + self.M * numpy.sin(uB))

        E = A / (numpy.linalg.norm(A, axis=1)).reshape(-1, 1)
        C = numpy.cross(A,B)
        F = numpy.cross(numpy.cross(A, B), A)
        F = F / (numpy.linalg.norm(F, axis=1)).reshape(-1, 1)

        # Computes the values of mu2,mu1 and nu given the great arc (E,F,R) and the value of the slow variable z
        mu2 = self.R * (numpy.array([E[:, 0]]).T * numpy.cos(z) + numpy.array(
            [F[:, 0]]).T * numpy.sin(z))
        mu1 = -self.R * (numpy.array([E[:, 1]]).T * numpy.cos(z) + numpy.array(
            [F[:, 1]]).T * numpy.sin(z))
        nu = self.R * (numpy.array([E[:, 2]]).T * numpy.cos(z) + numpy.array(
            [F[:, 2]]).T * numpy.sin(z))

        # Computes x_s, which is the solution to x_s^3 - mu2*x_s - mu1 = 0
        if self.N == 1:
            xs = (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) + (mu1 / 2.0 - numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0)
        elif self.N == 2:
            xs = -1.0 / 2.0 * (1.0 - 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) - 1.0 / 2.0 * (
                1.0 + 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 - numpy.sqrt(mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (
                1.0 / 3.0)
        elif self.N == 3:
            xs = -1.0 / 2.0 * (1.0 + 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 + numpy.sqrt(
                mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (1.0 / 3.0) - 1.0 / 2.0 * (
                1.0 - 1j * 3 ** (1.0 / 2.0)) * (mu1 / 2.0 - numpy.sqrt(mu1 ** 2 / 4.0 - mu2 ** 3 / 27.0 + 0 * 1j)) ** (
                1.0 / 3.0)
        xs = numpy.real(xs)

        # global coupling: To be implemented

        xdot = -y
        ydot = x ** 3 - mu2 * x - mu1 - y * (nu + self.b * x + x ** 2)
        if self.modification:
            zdot = -self.c * (
                numpy.sqrt((x - xs) ** 2 + y ** 2) - self.dstar + 0.1 * (
                    z - 0.5) ** 7 + self.Ks * coupling[0, :])
        else:
            zdot = -self.c * (numpy.sqrt((x - xs) ** 2 + y ** 2) - self.dstar + self.Ks * coupling[0, :])
        uAdot = numpy.full_like(uA, self.cA)
        uBdot = numpy.full_like(uB, self.cB)

        derivative = numpy.array([xdot, ydot, zdot, uAdot, uBdot])
        return derivative

    def dfun(self, state_variables, coupling, local_coupling=0.0):
        r"""
        The equations were taken from [Saggioetal_2017]
        cf. Eqns. (4) and (7), page 17 and 21

        The state variables :math:`x` and :math:`y` correspond to the fast subsystem and the
        state variable z corresponds to the slow subsystem. The state
        variables :math:`uA` and :math:`uB` correspond to the transition of the offset and
        onset bifurcations.

            .. math::
                \dot{x} &= -y \\
                \dot{y} &= x^3 - \mu_2 x - \mu_1 - y(\nu + b x + x^2) \\
                \dot{z} &= -c(\sqrt{x-x_{s}^2+y^2} - d^*)\\
                (\dot{u}A) &= cA\\
                (\dot{u}B) &= cB\\

        If the bool modification is True, then the equation for :math:`\dot{z}` will
        been modified to ensure stability for negative :math:`d^*`

            .. math::
                    \dot{z} = -c(\sqrt{x-x_{s}^2+y^2} - d^* + 0.1(z-0.5)^7)

        Where :math:`\mu_1, \mu_2` and :math:`\nu` lie on a great arc of a
        sphere of radius R parametrised by the unit vectors E and F.

            .. math::
                \begin{pmatrix}\mu_2 & -\mu_1 & \nu \end{pmatrix} = R(E \cos z + F \sin z)

        Where the unit vectors E and F are given by:

            .. math::
                E &= A/\|A\| \\
                F &= ((A \times B) \times A)/\|(A \times B) \times A\|

        The vectors A and B transition across a great arc of the same sphere
        of radius R parametrised by G, H and L, M respectively.

            .. math::
                A &= R(G \cos(uA) + H \sin(uA))
                B &= R(L \cos(uB) + M \sin(uB))

        Finally :math:`x_s` is the x-coordinate of the resting state
        (stable equilibrium). This is computed by finding the solution of

            .. math::
                x_s^3 - mu_2*x_s - mu_1 = 0

        And taking the branch which corresponds to the resting state.
        If :math:`x_s` is complex, we take the real part.

        """
        state_variables_ = state_variables.reshape(state_variables.shape[:-1]).T
        coupling_ = coupling.reshape(coupling.shape[:-1]).T
        derivative = _numba_dfun_slowmod(state_variables_, coupling_, self.G[0], self.G[1], self.G[2], self.H[0],
                                         self.H[1], self.H[2], self.L[0], self.L[1], self.L[2], self.M[0], self.M[1],
                                         self.M[2], self.b, self.R, self.c, self.cA, self.cB, self.dstar, self.Ks,
                                         self.modification, self.N)
        return derivative.T[..., numpy.newaxis]
示例#29
0
class MontbrioT(ModelNumbaDfun):

    tau = NArray(label=":math:`tau`",
                 default=numpy.array([1.0]),
                 domain=Range(lo=-10.0, hi=10.0, step=0.01),
                 doc="""""")

    I = NArray(label=":math:`I`",
               default=numpy.array([0.0]),
               domain=Range(lo=-10.0, hi=10.0, step=0.01),
               doc="""""")

    Delta = NArray(label=":math:`Delta`",
                   default=numpy.array([0.7]),
                   domain=Range(lo=0.0, hi=10.0, step=0.01),
                   doc="""""")

    J = NArray(label=":math:`J`",
               default=numpy.array([14.5]),
               domain=Range(lo=-25.0, hi=25.0, step=0.0001),
               doc="""""")

    eta = NArray(label=":math:`eta`",
                 default=numpy.array([-4.6]),
                 domain=Range(lo=-10.0, hi=10.0, step=0.0001),
                 doc="""""")

    Gamma = NArray(label=":math:`Gamma`",
                   default=numpy.array([5.0]),
                   domain=Range(lo=0., hi=10.0, step=0.1),
                   doc="""""")

    cr = NArray(label=":math:`cr`",
                default=numpy.array([1.0]),
                domain=Range(lo=0., hi=1, step=0.1),
                doc="""""")

    cv = NArray(label=":math:`cv`",
                default=numpy.array([1.0]),
                domain=Range(lo=0., hi=1, step=0.1),
                doc="""""")

    state_variable_range = Final(label="State Variable ranges [lo, hi]",
                                 default={
                                     "r": numpy.array([0.0, -2.0]),
                                     "V": numpy.array([-2.0, 1.5])
                                 },
                                 doc="""state variables""")

    state_variable_boundaries = Final(
        label="State Variable boundaries [lo, hi]",
        default={
            "r": numpy.array([0.0, inf]),
        },
    )
    variables_of_interest = List(
        of=str,
        label="Variables or quantities available to Monitors",
        choices=(
            'r',
            'V',
        ),
        default=(
            'r',
            'V',
        ),
        doc="Variables to monitor")

    state_variables = ['r', 'V']

    _nvar = 2
    cvar = numpy.array([
        0,
        1,
    ], dtype=numpy.int32)

    def dfun(self, vw, c, local_coupling=0.0):
        vw_ = vw.reshape(vw.shape[:-1]).T
        c_ = c.reshape(c.shape[:-1]).T
        deriv = _numba_dfun_MontbrioT(vw_, c_, self.tau, self.I, self.Delta,
                                      self.J, self.eta, self.Gamma, self.cr,
                                      self.cv, local_coupling)

        return deriv.T[..., numpy.newaxis]
示例#30
0
class EpileptorT(ModelNumbaDfun):

    a = NArray(label=":math:`a`", default=numpy.array([1.0]), doc="""""")

    b = NArray(label=":math:`b`", default=numpy.array([3.0]), doc="""""")

    c = NArray(label=":math:`c`", default=numpy.array([1.0]), doc="""""")

    d = NArray(label=":math:`d`", default=numpy.array([5.0]), doc="""""")

    r = NArray(label=":math:`r`",
               default=numpy.array([0.00035]),
               domain=Range(lo=0.0, hi=0.001, step=0.00005),
               doc="""""")

    s = NArray(label=":math:`s`", default=numpy.array([4.0]), doc="""""")

    x0 = NArray(label=":math:`x0`",
                default=numpy.array([-1.6]),
                domain=Range(lo=-3.0, hi=-1.0, step=0.1),
                doc="""""")

    Iext = NArray(label=":math:`Iext`",
                  default=numpy.array([3.1]),
                  domain=Range(lo=1.5, hi=5.0, step=0.1),
                  doc="""""")

    slope = NArray(label=":math:`slope`",
                   default=numpy.array([0.]),
                   domain=Range(lo=-16.0, hi=6.0, step=0.1),
                   doc="""""")

    Iext2 = NArray(label=":math:`Iext2`",
                   default=numpy.array([0.45]),
                   domain=Range(lo=0.0, hi=1.0, step=0.05),
                   doc="""""")

    tau = NArray(label=":math:`tau`", default=numpy.array([10.0]), doc="""""")

    aa = NArray(label=":math:`aa`", default=numpy.array([6.0]), doc="""""")

    bb = NArray(label=":math:`bb`", default=numpy.array([2.0]), doc="""""")

    Kvf = NArray(label=":math:`Kvf`",
                 default=numpy.array([0.0]),
                 domain=Range(lo=0.0, hi=4.0, step=0.5),
                 doc="""""")

    Kf = NArray(label=":math:`Kf`",
                default=numpy.array([0.0]),
                domain=Range(lo=0.0, hi=4.0, step=0.5),
                doc="""""")

    Ks = NArray(label=":math:`Ks`",
                default=numpy.array([0.0]),
                domain=Range(lo=-4.0, hi=4.0, step=0.1),
                doc="""""")

    tt = NArray(label=":math:`tt`",
                default=numpy.array([1.0]),
                domain=Range(lo=0.001, hi=10.0, step=0.001),
                doc="""""")

    modification = NArray(label=":math:`modification`",
                          default=numpy.array([0]),
                          doc="""""")

    state_variable_range = Final(label="State Variable ranges [lo, hi]",
                                 default={
                                     "x1": numpy.array([-2., 1.]),
                                     "y1": numpy.array([-20., 2.]),
                                     "z": numpy.array([2.0, 5.0]),
                                     "x2": numpy.array([-2., 0.]),
                                     "y2": numpy.array([0., 2.]),
                                     "g": numpy.array([-1., 1.])
                                 },
                                 doc="""state variables""")

    variables_of_interest = List(
        of=str,
        label="Variables or quantities available to Monitors",
        choices=(
            'x1',
            'x2',
        ),
        default=(
            'x1',
            'x2',
        ),
        doc="Variables to monitor")

    state_variables = ['x1', 'y1', 'z', 'x2', 'y2', 'g']

    _nvar = 6
    cvar = numpy.array([
        0,
        1,
        2,
        3,
        4,
        5,
    ], dtype=numpy.int32)

    def dfun(self, vw, c, local_coupling=0.0):
        vw_ = vw.reshape(vw.shape[:-1]).T
        c_ = c.reshape(c.shape[:-1]).T
        deriv = _numba_dfun_EpileptorT(vw_, c_, self.a, self.b, self.c, self.d,
                                       self.r, self.s, self.x0, self.Iext,
                                       self.slope, self.Iext2, self.tau,
                                       self.aa, self.bb, self.Kvf, self.Kf,
                                       self.Ks, self.tt, self.modification,
                                       local_coupling)

        return deriv.T[..., numpy.newaxis]