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]
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]
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
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))
class Baz(BaBaze): airplane_sweets = List(of=str, choices=('nuts', 'chocolate', 'prunes'))
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
% 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}"
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
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
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]
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
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]
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
class A(HasTraits): picked_dimensions = List(of=str, default=('time', 'space'), choices=('time', 'space', 'measurement'))
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
class A(HasTraits): picked_dimensions = List(of=str, default=('time', ), choices=('a', 'b', 'c'))
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)
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]
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
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
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]
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]
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))
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]
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]
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]