class WilsonCowan(TVBWilsonCowan): r""" **References**: .. [WC_1972] Wilson, H.R. and Cowan, J.D. *Excitatory and inhibitory interactions in localized populations of model neurons*, Biophysical journal, 12: 1-24, 1972. .. [WC_1973] Wilson, H.R. and Cowan, J.D *A Mathematical Theory of the Functional Dynamics of Cortical and Thalamic Nervous Tissue* .. [D_2011] Daffertshofer, A. and van Wijk, B. *On the influence of amplitude on the connectivity between phases* Frontiers in Neuroinformatics, July, 2011 Used Eqns 11 and 12 from [WC_1972]_ in ``dfun``. P and Q represent external inputs, which when exploring the phase portrait of the local model are set to constant values. However in the case of a full network, P and Q are the entry point to our long range and local couplings, that is, the activity from all other nodes is the external input to the local population. The default parameters are taken from figure 4 of [WC_1972]_, pag. 10 In [WC_1973]_ they present a model of neural tissue on the pial surface is. See Fig. 1 in page 58. The following local couplings (lateral interactions) occur given a region i and a region j: E_i-> E_j E_i-> I_j I_i-> I_j I_i-> E_j +---------------------------+ | Table 1 | +--------------+------------+ | | | SanzLeonetAl, 2014 | +--------------+------------+ |Parameter | Value | +==============+============+ | k_e, k_i | 1.00 | +--------------+------------+ | r_e, r_i | 0.00 | +--------------+------------+ | tau_e, tau_i | 10.0 | +--------------+------------+ | c_1 | 10.0 | +--------------+------------+ | c_2 | 6.0 | +--------------+------------+ | c_3 | 1.0 | +--------------+------------+ | c_4 | 1.0 | +--------------+------------+ | a_e, a_i | 1.0 | +--------------+------------+ | b_e, b_i | 0.0 | +--------------+------------+ | theta_e | 2.0 | +--------------+------------+ | theta_i | 3.5 | +--------------+------------+ | alpha_e | 1.2 | +--------------+------------+ | alpha_i | 2.0 | +--------------+------------+ | P | 0.5 | +--------------+------------+ | Q | 0 | +--------------+------------+ | c_e, c_i | 1.0 | +--------------+------------+ | alpha_e | 1.2 | +--------------+------------+ | alpha_i | 2.0 | +--------------+------------+ | | | frequency peak at 20 Hz | | | +---------------------------+ The parameters in Table 1 reproduce Figure A1 in [D_2011]_ but set the limit cycle frequency to a sensible value (eg, 20Hz). Model bifurcation parameters: * :math:`c_1` * :math:`P` The builders (: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: .. _phase-plane-WC: .. figure :: img/WilsonCowan_01_mode_0_pplane.svg :alt: Wilson-Cowan phase plane (E, I) The (:math:`E`, :math:`I`) phase-plane for the Wilson-Cowan model. The general formulation for the \textit{\textbf{Wilson-Cowan}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: \dot{E}_k &= \dfrac{1}{\tau_e} (-E_k + (k_e - r_e E_k) \mathcal{S}_e (\alpha_e \left( c_{ee} E_k - c_{ei} I_k + P_k - \theta_e + \mathbf{\Gamma}(E_k, E_j, u_{kj}) + W_{\zeta}\cdot E_j + W_{\zeta}\cdot I_j\right) ))\\ \dot{I}_k &= \dfrac{1}{\tau_i} (-I_k + (k_i - r_i I_k) \mathcal{S}_i (\alpha_i \left( c_{ie} E_k - c_{ee} I_k + Q_k - \theta_i + \mathbf{\Gamma}(E_k, E_j, u_{kj}) + W_{\zeta}\cdot E_j + W_{\zeta}\cdot I_j\right) )), """ # Define traited attributes for this model, these represent possible kwargs. tau_Ein = NArray( label=r":math:`\tau_Ein`", default=numpy.array([ 50., ]), domain=Range(lo=1., hi=100., step=1.0), doc= """[ms]. Excitatory population instant spiking rate time constant.""") tau_Iin = NArray( label=r":math:`\tau_Iin`", default=numpy.array([ 50., ]), domain=Range(lo=1., hi=100., step=1.0), doc= """[ms]. Inhibitory population instant spiking rate time constant.""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( label="State Variable boundaries [lo, hi]", default={ "E": numpy.array([0.0, 1.0]), "I": numpy.array([0.0, 1.0]), "Ein": numpy.array([0.0, 1.0]), "Iin": 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.""") # 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.5]), "I": numpy.array([0.0, 0.5]), "Ein": numpy.array([0.0, 0.5]), "Iin": numpy.array([0.0, 0.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.""") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=('E', 'I', 'Ein', 'Iin'), default=('E', 'I', 'Ein', 'Iin'), doc="""default state variables to be monitored""") state_variables = ['E', 'I', 'Ein', 'Iin'] _nvar = 4 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. """ for var in ["Ein", "Iin"]: if hasattr(self, var): setattr(self, "_" + var, getattr(self, var) > 0) else: setattr(self, "_" + var, numpy.array([ False, ])) def dfun(self, state_variables, coupling, local_coupling=0.0): r""" .. math:: \tau \dot{x}(t) &= -z(t) + \phi(z(t)) \\ \phi(x) &= \frac{c}{1-exp(-a (x-b))} """ E = state_variables[0, :] I = state_variables[1, :] Ein = state_variables[2, :] # Input from Spiking Network Iin = state_variables[3, :] # Input from Spiking Network derivative = numpy.zeros(state_variables.shape) # long-range coupling c_0 = coupling[0, :] # short-range (local) coupling lc_0 = local_coupling * E lc_1 = local_coupling * I x_e = self.alpha_e * (self.c_ee * E - self.c_ei * I + self.P - self.theta_e + c_0 + lc_0 + lc_1) x_i = self.alpha_i * (self.c_ie * E - self.c_ii * I + self.Q - self.theta_i + lc_0 + lc_1) s_e = self.c_e / (1.0 + numpy.exp(-self.a_e * (x_e - self.b_e))) s_i = self.c_i / (1.0 + numpy.exp(-self.a_i * (x_i - self.b_i))) derivative[0] = numpy.where( self._Ein, (-E + Ein) / self.tau_Ein, # Update from Spiking Network (-E + (self.k_e - self.r_e * E) * s_e) / self.tau_e) derivative[1] = numpy.where( self._Iin, (-I + Iin) / self.tau_Iin, # Update from Spiking Network (-I + (self.k_i - self.r_i * I) * s_i) / self.tau_i) return derivative
class PreSigmoidal(Coupling): r""" Provides a pre-summation sigmoidal coupling function with a static or dynamic and local or global threshold. .. math:: H * (Q + \tanh(G * (P*x - \theta))) The dynamic threshold as state variable given by the second state variable. With the coupling term, returns the direct node output for the dynamic threshold. """ H = NArray( label="H", default=numpy.array([ 0.5, ]), domain=Range(lo=-100.0, hi=100.0, step=1.0), doc="Global Factor.", ) Q = NArray( label="Q", default=numpy.array([ 1., ]), domain=Range(lo=-100.0, hi=100.0, step=1.0), doc="Average.", ) G = NArray( label="G", default=numpy.array([ 60., ]), domain=Range(lo=-1000.0, hi=1000.0, step=1.), doc="Gain.", ) P = NArray( label="P", default=numpy.array([ 1., ]), domain=Range(lo=-100.0, hi=100.0, step=0.01), doc="Excitation-Inhibition ratio.", ) theta = NArray( label=":math:`\\theta`", default=numpy.array([ 0.5, ]), domain=Range(lo=-100.0, hi=100.0, step=0.01), doc="Threshold.", ) dynamic = Attr( field_type=bool, label="Dynamic", default=True, doc="Use dynamic threshold (otherwise static).", ) globalT = Attr( field_type=bool, label=":math:`global_{\\theta}`", default=False, doc="Use global threshold (otherwise local).", ) def __str__(self): return simple_gen_astr(self, 'H Q G P theta dynamic globalT') def configure(self): """Set the right indirect call.""" super(PreSigmoidal, self).configure() self.sliceT = 0 if self.globalT else slice(None) # override __call__ directly simpler than pre/post form # TODO check use of arrays dims here def __call__(self, step, history, na=numpy.newaxis): g_ij = history.es_weights x_i, x_j = history.query(step) if self.dynamic: _ = (self.P * x_j[:, 0] - x_j[:, 1, self.sliceT])[:, na] else: _ = self.P * x_j - self.theta[self.sliceT, na] A_j = self.H * (self.Q + numpy.tanh(self.G * _)) if self.dynamic: c_0 = (g_ij[:, 0] * A_j[:, 0]).sum(axis=0) c_1 = numpy.diag(A_j[:, 0, :, 0])[:, na] if self.globalT: c_1[:] = c_1.mean() return numpy.array([c_0, c_1]) else: # static threshold return (g_ij.transpose((2, 1, 0, 3)) * A_j).sum(axis=0)
class Linear(Linear): I_o = NArray( label=r":math:`I_o`", default=numpy.array([0.0]), domain=Range(lo=-100.0, hi=100.0, step=1.0), doc="External stimulus") G = NArray( label=r":math:`G`", default=numpy.array([0.0]), domain=Range(lo=-0.0, hi=100.0, step=1.0), doc="Global coupling scaling") tau = NArray( label=r":math:`\tau`", default=numpy.array([1.0]), domain=Range(lo=-0.1, hi=100.0, step=0.1), doc="Time constant") tau_rin = NArray( label=r":math:`\tau_rin_e`", default=numpy.array([10., ]), domain=Range(lo=1., hi=100., step=1.0), doc="""[ms]. Excitatory population instant spiking rate time constant.""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( default={"R": numpy.array([0.0, None]), "Rin": numpy.array([0.0, None])}, label="State Variable boundaries [lo, hi]", doc="""The values for each state-variable should be set to encompass the boundaries of the dynamic range of that state-variable. Set None for one-sided boundaries""") state_variable_range = Final( label="State Variable ranges [lo, hi]", default={"R": numpy.array([0, 100]), "Rin": numpy.array([0, 100])}, doc="Range used for state variable initialization and visualization.") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("R", 'Rin'), default=("R", 'Rin'), ) state_variables = ('R', 'Rin') integration_variables = ('R',) _nvar = 2 cvar = numpy.array([0], dtype=numpy.int32) 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. """ if hasattr(self, "Rin"): setattr(self, "_Rin", getattr(self, "Rin") > 0) else: setattr(self, "Rin", numpy.array([0.0, ])) setattr(self, "_Rin", numpy.array([False, ])) def update_non_state_variables_after_integration(self, state_variables): # Reset to 0 the Rin for nodes not updated by Spiking Network state_variables[1] = numpy.where(self._Rin, state_variables[1], 0.0) return state_variables def dfun(self, state, coupling, local_coupling=0.0): """ .. math:: dR/dt = (-R + G * coupling) / {\tau} + I_o """ dR = numpy.where(self._Rin, (- state[0] + state[1]) / self.tau_rin, (-state[0] + self.G * coupling[0] + local_coupling * state[0] ) / self.tau + self.I_o) return numpy.array([dR, 0.0*dR])
class EpileptorRestingState(ModelNumbaDfun): r""" EpileptorRestingState is an extension of the phenomenological neural mass model of partial seizures Epileptor [Jirsaetal_2014], tuned to express regionally specific physiological oscillations in addition to the epileptiform discharges. This extension was made using the Generic 2-dimensional Oscillator model (parametrized close to a supercritical Hopf Bifurcation) [SanzLeonetal_2013] to reproduce the spontaneous local field potential-like signal. This model, its motivation and derivation can be found in the published article [Courtioletal_2020]. .. Tutorial: Modeling_Resting-State_in_Epilepsy.ipynb .. References: [Jirsaetal_2014] Jirsa, V. K.; Stacey, W. C.; Quilichini, P. P.; Ivanov, A. I.; Bernard, C. *On the nature of seizure dynamics.* Brain, 2014. [SanzLeonetal_2013] Sanz Leon, P.; Knock, S. A.; Woodman, M. M.; Domide, L.; Mersmann, J.; McIntosh, A. R.; Jirsa, V. K. *The Virtual Brain: a simulator of primate brain network dynamics.* Front.Neuroinf., 2013. [Courtioletal_2020] Courtiol, J.; Guye, M.; Bartolomei, F.; Petkoski, S.; Jirsa, V. K. *Dynamical Mechanisms of Interictal Resting-State Functional Connectivity in Epilepsy.* J.Neurosci., 2020. Variables of interest to be used by monitors: p * (-x_{1} + x_{2}) + (1 - p) * x_{rs} .. 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} + b_{2} g(x_{1}) - 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})\\ \dot{x_{rs}} &=& d_{rs} \tau_{rs} (-f_{rs} x_{rs}^3 + e_{rs} x_{rs}^2 + \alpha_{rs} y_{rs} + \gamma_{rs} I_{rs}) \\ \dot{y_{rs}} &=& d_{rs} (b_{rs} x_{rs} - \beta_{rs} y_{rs} + a_{rs}) / \tau_{rs} 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} """ a = NArray( label=":math:`a`", default=numpy.array([1.0]), doc="Coefficient of the cubic term in the first state-variable x1.") b = NArray( label=":math:`b`", default=numpy.array([3.0]), doc="Coefficient of the squared term in the first state-variable x1.") c = NArray(label=":math:`c`", default=numpy.array([1.0]), doc="Additive coefficient for the second state-variable y1, \ called :math:'y_{0}' in Jirsa et al. (2014).") d = NArray( label=":math:`d`", default=numpy.array([5.0]), doc="Coefficient of the squared term in the second state-variable y1.") 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 z, \ called :math:'1/\tau_{0}' in Jirsa et al. (2014).") s = NArray(label=":math:`s`", default=numpy.array([4.0]), doc="Linear coefficient in the third state-variable z.") 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 (x1, y1).") 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 x1.") 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 (x2, y2).") tau = NArray( label=":math:`/tau`", default=numpy.array([10.0]), doc="Temporal scaling coefficient in the fifth state-variable y2.") aa = NArray(label=":math:`aa`", default=numpy.array([6.0]), doc="Linear coefficient in the fifth state-variable y2.") bb = NArray( label=":math:`bb`", default=numpy.array([2.0]), doc="Linear coefficient of lowpass excitatory coupling in the fourth \ state-variable x2.") 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="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 very fast time scale \ toward the slow time scale.") tt = NArray(label=":math:`tt`", default=numpy.array([1.0]), domain=Range(lo=0.001, hi=10.0, step=0.001), doc="Time scaling of the Epileptor.") # Generic-2D's parameters tau_rs = NArray( label=r":math:`\tau_{rs}`", default=numpy.array([1.0]), domain=Range(lo=1.0, hi=5.0, step=0.01), doc="Temporal scaling coefficient in the third population (x_rs, y_rs)." ) I_rs = NArray( label=":math:`I_{rs}`", default=numpy.array([0.0]), domain=Range(lo=-5.0, hi=5.0, step=0.01), doc="External input current to the third population (x_rs, y_rs).") a_rs = NArray(label=":math:`a_{rs}`", default=numpy.array([-2.0]), domain=Range(lo=-5.0, hi=5.0, step=0.01), doc="Vertical shift of the configurable nullcline \ in the state-variable y_rs.") b_rs = NArray(label=":math:`b_{rs}`", default=numpy.array([-10.0]), domain=Range(lo=-20.0, hi=15.0, step=0.01), doc="Linear coefficient of the state-variable y_rs.") d_rs = NArray( label=":math:`d_{rs}`", default=numpy.array([0.02]), domain=Range(lo=0.0001, hi=1.0, step=0.0001), doc="Temporal scaling of the whole third system (x_rs, y_rs).") e_rs = NArray( label=":math:`e_{rs}`", default=numpy.array([3.0]), domain=Range(lo=-5.0, hi=5.0, step=0.0001), doc="Coefficient of the squared term in the sixth state-variable x_rs." ) f_rs = NArray( label=":math:`f_{rs}`", default=numpy.array([1.0]), domain=Range(lo=-5.0, hi=5.0, step=0.0001), doc="Coefficient of the cubic term in the sixth state-variable x_rs.") alpha_rs = NArray( label=r":math:`\alpha_{rs}`", 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 y_rs to the fast variable x_rs.") beta_rs = NArray( label=r":math:`\beta_{rs}`", 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 y_rs to itself.") gamma_rs = NArray(label=r":math:`\gamma_{rs}`", 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.\ Note: It scales both I_rs and the long-range coupling term.") K_rs = NArray(label=r":math:`K_{rs}`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=10.0, step=0.001), doc="Coupling scaling on a fast time scale.") # Combination 2 models p = NArray(label=r":math:`p`", default=numpy.array([0.]), domain=Range(lo=-1.0, hi=1.0, step=0.1), doc="Linear coefficient.") # Initialization. # Epileptor model is set in a fixed point by default. state_variable_range = Final( label="State variable ranges [lo, hi]", default={ "x1": numpy.array([-1.8, -1.4]), "y1": numpy.array([-15, -10]), "z": numpy.array([3.6, 4.0]), "x2": numpy.array([-1.1, -0.9]), "y2": numpy.array([0.001, 0.01]), "g": numpy.array([-1., 1.]), "x_rs": numpy.array([-2.0, 4.0]), "y_rs": numpy.array([-6.0, 6.0]) }, doc="Typical bounds on state-variables in EpileptorRestingState model." ) variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("x1", "y1", "z", "x2", "y2", "g", "x_rs", "y_rs", "x2 - x1"), default=("x2 - x1", "z", "x_rs"), doc="Quantities of EpileptorRestingState available to monitor.") state_variables = ("x1", "y1", "z", "x2", "y2", "g", "x_rs", "y_rs") _nvar = 8 # number of state-variables cvar = numpy.array([0, 3, 6], 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): y = state_variables ydot = numpy.empty_like(state_variables) # long-range coupling c_pop1 = coupling[0] c_pop2 = coupling[1] c_pop3 = coupling[2] # short-range (local) coupling Iext = self.Iext + local_coupling * y[0] lc_1 = local_coupling * y[6] # Epileptor's equations: # 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 ydot[2] = self.tt * (self.r * (4 * (y[0] - self.x0) - y[2] + where( y[2] < 0., if_ydot2, else_ydot2) + 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])) # 0.01 = \gamma # G2D's equations: ydot[6] = self.d_rs * self.tau_rs * ( self.alpha_rs * y[7] - self.f_rs * y[6]**3 + self.e_rs * y[6]**2 + self.gamma_rs * self.I_rs + self.gamma_rs * self.K_rs * c_pop3 + lc_1) ydot[7] = self.d_rs * (self.a_rs + self.b_rs * y[6] - self.beta_rs * y[7]) / self.tau_rs # output: LFP self.output = self.p * (-y[0] + y[3]) + (1 - self.p) * y[6] return ydot def dfun(self, x, c, local_coupling=0.0): r""" Computes the derivatives of the state-variables of EpileptorRestingState with respect to time. """ x_ = x.reshape(x.shape[:-1]).T c_ = c.reshape(c.shape[:-1]).T Iext = self.Iext + local_coupling * x[0, :, 0] lc_1 = local_coupling * x[6, :, 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.tau_rs, self.I_rs, self.a_rs, self.b_rs, self.d_rs, self.e_rs, self.f_rs, self.beta_rs, self.alpha_rs, self.gamma_rs, self.K_rs, lc_1) return deriv.T[..., numpy.newaxis]
class Sigmoidal(Coupling): r""" Provides a sigmoidal coupling function of the form .. math:: c_{min} + (c_{max} - c_{min}) / (1.0 + \exp(-a(x-midpoint)/\sigma)) NB: using a = numpy.pi / numpy.sqrt(3.0) and the default parameter produces something close to the current default for Linear (a=0.00390625, b=0) over the linear portion of the sigmoid, with saturation at -1 and 1. """ cmin = NArray( label=":math:`c_{min}`", default=numpy.array([ -1.0, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="""Minimum of the sigmoid function""", ) cmax = NArray( label=":math:`c_{max}`", default=numpy.array([ 1.0, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="""Maximum of the sigmoid function""", ) midpoint = NArray( label="midpoint", default=numpy.array([ 0.0, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="Midpoint of the linear portion of the sigmoid", ) a = NArray( label=r":math:`a`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.01, hi=1000.0, step=10.0), doc="Scaling of sigmoidal", ) sigma = NArray( label=r":math:`\sigma`", default=numpy.array([ 230.0, ]), domain=Range(lo=0.01, hi=1000.0, step=10.0), doc="Standard deviation of the sigmoidal", ) parameter_names = 'cmin cmax midpoint a sigma'.split() pre_expr = 'x_j' post_expr = 'cmin + ((cmax - cmin) / (1.0 + exp(-a *((gx - midpoint) / sigma))))' def __str__(self): return simple_gen_astr(self, 'cmin cmax midpoint a sigma') def post(self, gx): return self.cmin + ((self.cmax - self.cmin) / (1.0 + numpy.exp(-self.a * ( (gx - self.midpoint) / self.sigma))))
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 def __init__(self, **kwargs): """ .. May need to put kwargs back if we can't get them from trait... """ super(LarterBreakspear, self).__init__(**kwargs) LOG.info('%s: initing...' % str(self)) 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_{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 ReducedSetHindmarshRose(ReducedSetBase): r""" .. [SJ_2008] Stefanescu and Jirsa, PLoS Computational Biology, *A Low Dimensional Description of Globally Coupled Heterogeneous Neural Networks of Excitatory and Inhibitory* 4, 11, 26--36, 2008. The models (:math:`\xi`, :math:`\eta`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. _phase-plane-rHR_0: .. figure :: img/ReducedSetHindmarshRose_01_mode_0_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 1st mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the first mode of a reduced set of Hindmarsh-Rose oscillators. .. _phase-plane-rHR_1: .. figure :: img/ReducedSetHindmarshRose_01_mode_1_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 2nd mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the second mode of a reduced set of Hindmarsh-Rose oscillators. .. _phase-plane-rHR_2: .. figure :: img/ReducedSetHindmarshRose_01_mode_2_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 3rd mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the third mode of a reduced set of Hindmarsh-Rose oscillators. The dynamic equations were orginally taken from [SJ_2008]_. The equations of the population model for i-th mode at node q are: .. math:: \dot{\xi}_i &= \eta_i-a_i\xi_i^3 + b_i\xi_i^2- \tau_i + K_{11} \left[\sum_{k=1}^{o} A_{ik} \xi_k - \xi_i \right] - K_{12} \left[\sum_{k=1}^{o} B_{ik} \alpha_k - \xi_i\right] + IE_i \\ &\, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr} \right] \\ & \\ \dot{\eta}_i &= c_i-d_i\xi_i^2 -\tau_i \\ & \\ \dot{\tau}_i &= rs\xi_i - r\tau_i -m_i \\ & \\ \dot{\alpha}_i &= \beta_i - e_i \alpha_i^3 + f_i \alpha_i^2 - \gamma_i + K_{21} \left[\sum_{k=1}^{o} C_{ik} \xi_k - \alpha_i \right] + II_i \\ &\, +\left[\sum_{k=1}^{o}\mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o}W_{\zeta}\cdot\xi_{kr}\right] \\ & \\ \dot{\beta}_i &= h_i - p_i \alpha_i^2 - \beta_i \\ \dot{\gamma}_i &= rs \alpha_i - r \gamma_i - n_i .. automethod:: ReducedSetHindmarshRose.update_derived_parameters #NOTE: In the Article this modelis called StefanescuJirsa3D """ # Define traited attributes for this model, these represent possible kwargs. r = NArray( label=":math:`r`", default=numpy.array([0.006]), domain=Range(lo=0.0, hi=0.1, step=0.0005), doc="""Adaptation parameter""") a = NArray( label=":math:`a`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Dimensionless parameter as in the Hindmarsh-Rose model""") b = NArray( label=":math:`b`", default=numpy.array([3.0]), domain=Range(lo=0.0, hi=3.0, step=0.01), doc="""Dimensionless parameter as in the Hindmarsh-Rose model""") c = NArray( label=":math:`c`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Dimensionless parameter as in the Hindmarsh-Rose model""") d = NArray( label=":math:`d`", default=numpy.array([5.0]), domain=Range(lo=2.5, hi=7.5, step=0.01), doc="""Dimensionless parameter as in the Hindmarsh-Rose model""") s = NArray( label=":math:`s`", default=numpy.array([4.0]), domain=Range(lo=2.0, hi=6.0, step=0.01), doc="""Adaptation paramters, governs feedback""") xo = NArray( label=":math:`x_{o}`", default=numpy.array([-1.6]), domain=Range(lo=-2.4, hi=-0.8, step=0.01), doc="""Leftmost equilibrium point of x""") K11 = NArray( label=":math:`K_{11}`", default=numpy.array([0.5]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, excitatory to excitatory""") K12 = NArray( label=":math:`K_{12}`", default=numpy.array([0.1]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, inhibitory to excitatory""") K21 = NArray( label=":math:`K_{21}`", default=numpy.array([0.15]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, excitatory to inhibitory""") sigma = NArray( label=r":math:`\sigma`", default=numpy.array([0.3]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Standard deviation of Gaussian distribution""") mu = NArray( label=r":math:`\mu`", default=numpy.array([3.3]), domain=Range(lo=1.1, hi=3.3, step=0.01), doc="""Mean of Gaussian distribution""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = Final( label="State Variable ranges [lo, hi]", default={"xi": numpy.array([-4.0, 4.0]), "eta": numpy.array([-25.0, 20.0]), "tau": numpy.array([2.0, 10.0]), "alpha": numpy.array([-4.0, 4.0]), "beta": numpy.array([-20.0, 20.0]), "gamma": numpy.array([2.0, 10.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.""") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("xi", "eta", "tau", "alpha", "beta", "gamma"), default=("xi", "eta", "tau"), doc=r"""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:`\xi = 0`, :math:`\eta = 1`, :math:`\tau = 2`, :math:`\alpha = 3`, :math:`\beta = 4`, and :math:`\gamma = 5`""") state_variables = 'xi eta tau alpha beta gamma'.split() _nvar = 6 cvar = numpy.array([0, 3], dtype=numpy.int32) # derived parameters A_ik = None B_ik = None C_ik = None a_i = None b_i = None c_i = None d_i = None e_i = None f_i = None h_i = None p_i = None IE_i = None II_i = None m_i = None n_i = None def dfun(self, state_variables, coupling, local_coupling=0.0): r""" The equations of the population model for i-th mode at node q are: .. math:: \dot{\xi}_i &= \eta_i-a_i\xi_i^3 + b_i\xi_i^2- \tau_i + K_{11} \left[\sum_{k=1}^{o} A_{ik} \xi_k - \xi_i \right] - K_{12} \left[\sum_{k=1}^{o} B_{ik} \alpha_k - \xi_i\right] + IE_i \\ &\, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr} \right] \\ & \\ \dot{\eta}_i &= c_i-d_i\xi_i^2 -\tau_i \\ & \\ \dot{\tau}_i &= rs\xi_i - r\tau_i -m_i \\ & \\ \dot{\alpha}_i &= \beta_i - e_i \alpha_i^3 + f_i \alpha_i^2 - \gamma_i + K_{21} \left[\sum_{k=1}^{o} C_{ik} \xi_k - \alpha_i \right] + II_i \\ &\, +\left[\sum_{k=1}^{o}\mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o}W_{\zeta}\cdot\xi_{kr}\right] \\ & \\ \dot{\beta}_i &= h_i - p_i \alpha_i^2 - \beta_i \\ \dot{\gamma}_i &= rs \alpha_i - r \gamma_i - n_i """ xi = state_variables[0, :] eta = state_variables[1, :] tau = state_variables[2, :] alpha = state_variables[3, :] beta = state_variables[4, :] gamma = state_variables[5, :] derivative = numpy.empty_like(state_variables) c_0 = coupling[0, :].sum(axis=1)[:, numpy.newaxis] # c_1 = coupling[1, :] derivative[0] = (eta - self.a_i * xi ** 3 + self.b_i * xi ** 2 - tau + self.K11 * (numpy.dot(xi, self.A_ik) - xi) - self.K12 * (numpy.dot(alpha, self.B_ik) - xi) + self.IE_i + c_0 + local_coupling * xi) derivative[1] = self.c_i - self.d_i * xi ** 2 - eta derivative[2] = self.r * self.s * xi - self.r * tau - self.m_i derivative[3] = (beta - self.e_i * alpha ** 3 + self.f_i * alpha ** 2 - gamma + self.K21 * (numpy.dot(xi, self.C_ik) - alpha) + self.II_i + c_0 + local_coupling * xi) derivative[4] = self.h_i - self.p_i * alpha ** 2 - beta derivative[5] = self.r * self.s * alpha - self.r * gamma - self.n_i return derivative def update_derived_parameters(self, corrected_d_p=True): """ Calculate coefficients for the neural field model based on a Reduced set of Hindmarsh-Rose oscillators. Specifically, this method implements equations for calculating coefficients found in the supplemental material of [SJ_2008]_. Include equations here... """ newaxis = numpy.newaxis trapz = scipy_integrate_trapz stepu = 1.0 / (self.nu + 2 - 1) stepv = 1.0 / (self.nv + 2 - 1) norm = scipy_stats_norm(loc=self.mu, scale=self.sigma) Iu = norm.ppf(numpy.arange(stepu, 1.0, stepu)) Iv = norm.ppf(numpy.arange(stepv, 1.0, stepv)) # Define the modes V = numpy.zeros((self.number_of_modes, self.nv)) U = numpy.zeros((self.number_of_modes, self.nu)) nv_per_mode = self.nv // self.number_of_modes nu_per_mode = self.nu // self.number_of_modes for i in range(self.number_of_modes): V[i, i * nv_per_mode:(i + 1) * nv_per_mode] = numpy.ones(nv_per_mode) U[i, i * nu_per_mode:(i + 1) * nu_per_mode] = numpy.ones(nu_per_mode) # Normalise the modes V = V / numpy.tile(numpy.sqrt(trapz(V * V, Iv, axis=1)), (self.nv, 1)).T U = U / numpy.tile(numpy.sqrt(trapz(U * U, Iu, axis=1)), (self.nu, 1)).T # Get Normal PDF's evaluated with sampling Zv and Zu g1 = norm.pdf(Iv) g2 = norm.pdf(Iu) G1 = numpy.tile(g1, (self.number_of_modes, 1)) G2 = numpy.tile(g2, (self.number_of_modes, 1)) cV = numpy.conj(V) cU = numpy.conj(U) #import pdb; pdb.set_trace() intcVdI = trapz(cV, Iv, axis=1)[:, newaxis] intG1VdI = trapz(G1 * V, Iv, axis=1)[newaxis, :] intcUdI = trapz(cU, Iu, axis=1)[:, newaxis] #Calculate coefficients self.A_ik = numpy.dot(intcVdI, intG1VdI).T self.B_ik = numpy.dot(intcVdI, trapz(G2 * U, Iu, axis=1)[newaxis, :]) self.C_ik = numpy.dot(intcUdI, intG1VdI).T self.a_i = self.a * trapz(cV * V ** 3, Iv, axis=1)[newaxis, :] self.e_i = self.a * trapz(cU * U ** 3, Iu, axis=1)[newaxis, :] self.b_i = self.b * trapz(cV * V ** 2, Iv, axis=1)[newaxis, :] self.f_i = self.b * trapz(cU * U ** 2, Iu, axis=1)[newaxis, :] self.c_i = (self.c * intcVdI).T self.h_i = (self.c * intcUdI).T self.IE_i = trapz(Iv * cV, Iv, axis=1)[newaxis, :] self.II_i = trapz(Iu * cU, Iu, axis=1)[newaxis, :] if corrected_d_p: # correction identified by Shrey Dutta & Arpan Bannerjee, confirmed by RS self.d_i = self.d * trapz(cV * V ** 2, Iv, axis=1)[newaxis, :] self.p_i = self.d * trapz(cU * U ** 2, Iu, axis=1)[newaxis, :] else: # typo in the original paper by RS & VJ, kept for comparison purposes. self.d_i = (self.d * intcVdI).T self.p_i = (self.d * intcUdI).T self.m_i = (self.r * self.s * self.xo * intcVdI).T self.n_i = (self.r * self.s * self.xo * intcUdI).T
class HMJEpileptor(models.Model): """ The Epileptor is a composite neural mass model of six dimensions which has be crafted to model the phenomenology of epileptic seizures. This model, its motivation and derivation are currently in preparation for publication (Jirsa et al, 2013) .. automethod:: HMJEpileptor.dfun """ a = NArray(label="a", default=numpy.array([1]), doc="n/a") b = NArray(label="b", default=numpy.array([3]), doc="n/a") c = NArray(label="c", default=numpy.array([1]), doc="n/a") d = NArray(label="d", default=numpy.array([5]), doc="n/a") r = NArray(label="r", domain=Range(lo=0.0, hi=0.001, step=0.00005), default=numpy.array([0.00035]), doc="n/a") s = NArray(label="s", default=numpy.array([4]), doc="n/a") x0 = NArray(label="x0", domain=Range(lo=-3.0, hi=0.0, step=0.1), default=numpy.array([-1.6]), doc="n/a") Iext = NArray(label="Iext", domain=Range(lo=1.5, hi=5.0, step=0.1), default=numpy.array([3.1]), doc="n/a") omega2 = NArray(label="omega2", default=numpy.array([0.1]), doc="n/a") slope = NArray(label="slope", default=numpy.array([0.]), doc="n/a") Iext2 = NArray(label="Iext2", domain=Range(lo=0.0, hi=1.0, step=0.05), default=numpy.array([0.45]), doc="n/a") tau = NArray(label="tau", default=numpy.array([10]), doc="n/a") aa = NArray(label="aa", default=numpy.array([6]), doc="n/a") Kpop1 = NArray( label="K_11", default=numpy.array([0.5]), domain=Range(lo=0.0, hi=4.0, step=0.5), doc= '''Test parameter. Correspond to the coupling scaling. Move outside to be consistent with the general TVB implementation.''') Kpop2 = NArray( label="K_22", default=numpy.array([0.2]), domain=Range(lo=0.0, hi=1.0, step=0.5), doc= '''Test parameter. Correspond to the coupling scaling. Move outside to be consistent with the general TVB implementation.''') state_variable_range = Final( { "y0": numpy.array([0., 1e-10]), "y1": numpy.array([-5., 0.]), "y2": numpy.array([3., 4.]), "y3": numpy.array([0., 1e-10]), "y4": numpy.array([0., 1e-10]), "y5": numpy.array([0., 1e-2]) }, label="State variable ranges [lo, hi]", doc="n/a") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("y0", "y1", "y2", "y3", "y4", "y5"), default=("y0", "y3"), doc="""default state variables to be monitored""") # variables_of_interest = arrays.IntegerArray( # label="Variables watched by Monitors", # range=basic.Range(lo=0.0, hi=6.0, step=1.0), # default=numpy.array([0], dtype=numpy.int32), # doc="default state variables to be monitored", # order=10) def __init__(self, **kwargs): """ """ LOG.info("%s: init'ing..." % (str(self), )) super(HMJEpileptor, self).__init__(**kwargs) #self._state_variables = ["y%d" % i for i in range(6)] #self._state_variables = ["y%d" % i for i in range(6)] self._nvar = 6 self.cvar = numpy.array([0, 3], dtype=numpy.int32) LOG.debug("%s: init'ed." % (repr(self), )) def dfun(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): """ 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] """ """ First population with high frequency burst and baseline jump - mechanisms is similar to a Hindmarsh-Rose scenario with two régimes connected by a slow trajectory (here y(3)). """ y = state_variables n = y.shape[1] Iext = self.Iext + coupling[0, :] + local_coupling c_pop1 = coupling[0, :] c_pop2 = coupling[1, :] # if y(1)<0. # ydot1 = y(2)-a*y(1)^3 + b*y(1)^2-y(3)+iext; # ydot2 = c-d*y(1)^2-y(2); # ydot3 = r*(s*(y(1)-x0) - y(3)); % energy consumption = 1 - available energy if_y1_lt_0 = concat([ (y[1] - self.a * y[0]**3 + self.b * y[0]**2 - y[2] + Iext).reshape( (1, n, 1)), (self.c - self.d * y[0]**2 - y[1]).reshape( (1, n, 1)), (self.r * (self.s * (y[0] - self.x0) - y[2] - self.Kpop1 * (c_pop1 - y[0]))).reshape((1, n, 1)) ]) # else # % ydot1 = y(2) + (slope - y(4) -1.0*(y(3)-4))*y(1) - y(3)+iext; % this is just an # % alternative representation, which worked well # ydot1 = y(2) + (slope - y(4) + 0.6*(y(3)-4)^2)*y(1) -y(3)+iext; # % here the high energy burst is being generated through variation of the slope: # % 1. via y(4) within the epileptic spike complex; # % 2. via the expression with y(3), which causes more # % oscillations at the beginning of the seizure (effect of the # % energy available) # ydot2 = c-d*y(1)^2-y(2); # ydot3 = r*(s*(y(1)-x0) - y(3)); # end else_pop1 = concat([ (y[1] + (self.slope - y[3] + 0.6 * (y[2] - 4.0)**2) * y[0] - y[2] + Iext).reshape((1, n, 1)), (self.c - self.d * y[0]**2 - y[1]).reshape((1, n, 1)), (self.r * (self.s * (y[0] - self.x0) - y[2] - self.Kpop1 * (c_pop1 - y[0]))).reshape((1, n, 1)) ]) pop1 = where(y[0] < 0., if_y1_lt_0, else_pop1) # % istim= 0*block(t,150,1); # # % this is the second population that generates the big spike-wave complex # % preictally and within the seizure via a morris-lecar-jirsa (mlj) structure # # if y(4)<-0.25 # ydot4 = -y(5)+ y(4)-y(4)^3 + iext2 + 2*y(6)-0.3*(y(3)-3.5) ; % these last two terms # % put the population dynamics into the critical regime. in particular, # % y(6) turns the oscillator on and off, whereas the y(3) term helps it to become precritical (critical fluctuations). # ydot5 = -y(5)/tau ; if_ = concat([(-y[4] + y[3] - y[3]**3 + self.Iext2 + 2 * y[5] - 0.3 * (y[2] - 3.5) + self.Kpop2 * (c_pop2 - y[3])).reshape( (1, n, 1)), (-y[4] / self.tau).reshape((1, n, 1))]) # else # ydot4 = -y(5)+ y(4)-y(4)^3 + iext2+ 2*y(6)-0.3*(y(3)-3.5); # ydot5 = (-y(5) + aa*(y(4)+0.25))/tau; % here is the mlj structure # end else_pop2 = concat([ (-y[4] + y[3] - y[3]**3 + self.Iext2 + 2 * y[5] - 0.3 * (y[2] - 3.5) + self.Kpop2 * (c_pop2 - y[3])).reshape((1, n, 1)), ((-y[4] + self.aa * (y[3] + 0.25)) / self.tau).reshape((1, n, 1)) ]) pop2 = where(y[3] < -0.25, if_, else_pop2) # # ydot6 = -0.01*(y(6)-0.1*y(1)) ; energy = array([-0.01 * (y[5] - 0.1 * y[0])]) # # ydot = [ydot1;ydot2;ydot3;ydot4;ydot5;ydot6]; return concat((pop1, pop2, energy))
def evaluate(self): """ Calculate the continuous wavelet transform of time_series. """ ts_shape = self.time_series.data.shape if self.frequencies.step == 0: self.log.warning("Frequency step can't be 0! Trying default step, 2e-3.") self.frequencies.step = 0.002 freqs = numpy.arange(self.frequencies.lo, self.frequencies.hi, self.frequencies.step) if (freqs.size == 0) or any(freqs <= 0.0): # TODO: Maybe should limit number of freqs... ~100 is probably a reasonable upper bound. self.log.warning("Invalid frequency range! Falling back to default.") self.log.debug("freqs") self.log.debug(narray_describe(freqs)) self.frequencies = Range(lo=0.008, hi=0.060, step=0.002) freqs = numpy.arange(self.frequencies.lo, self.frequencies.hi, self.frequencies.step) self.log.debug("freqs") self.log.debug(narray_describe(freqs)) sample_rate = self.time_series.sample_rate # Duke: code below is as given by Andreas Spiegler, I've just wrapped # some of the original argument names nf = len(freqs) temporal_step = max((1, iround(self.sample_period / self.time_series.sample_period))) nt = int(numpy.ceil(ts_shape[0] / temporal_step)) if not isinstance(self.q_ratio, numpy.ndarray): q_ratio = self.q_ratio * numpy.ones((1, nf)) if numpy.nanmin(q_ratio) < 5: msg = "q_ratio must be not lower than 5 !" self.log.error(msg) raise Exception(msg) if numpy.nanmax(freqs) > sample_rate / 2.0: msg = "Sampling rate is too low for the requested frequency range !" self.log.error(msg) raise Exception(msg) # TODO: This isn't used, but min frequency seems like it should be important... Check with A.S. # fmin = 3.0 * numpy.nanmin(q_ratio) * sample_rate / numpy.pi / nt sigma_f = freqs / q_ratio sigma_t = 1.0 / (2.0 * numpy.pi * sigma_f) if self.normalisation == 'energy': Amp = 1.0 / numpy.sqrt(sample_rate * numpy.sqrt(numpy.pi) * sigma_t) elif self.normalisation == 'gabor': Amp = numpy.sqrt(2.0 / numpy.pi) / sample_rate / sigma_t coef_shape = (nf, nt, ts_shape[1], ts_shape[2], ts_shape[3]) coef = numpy.zeros(coef_shape, dtype = numpy.complex128) self.log.debug("coef") self.log.debug(narray_describe(coef)) scales = numpy.arange(0, nf, 1) for i in scales: f0 = freqs[i] SDt = sigma_t[(0, i)] A = Amp[(0, i)] x = numpy.arange(0, 4.0 * SDt * sample_rate, 1) / sample_rate wvlt = A * numpy.exp(-x**2 / (2.0 * SDt**2) ) * numpy.exp(2j * numpy.pi * f0 * x ) wvlt = numpy.hstack((numpy.conjugate(wvlt[-1:0:-1]), wvlt)) #util.self.log_debug_array(self.log, wvlt, "wvlt") for var in range(ts_shape[1]): for node in range(ts_shape[2]): for mode in range(ts_shape[3]): data = self.time_series.data[:, var, node, mode] wt = signal.convolve(data, wvlt, 'same') #util.self.log_debug_array(self.log, wt, "wt") res = wt[0::temporal_step] # NOTE: this is a horrible horrible quick hack (alas, a solution) to avoid broadcasting errors # when using dt and sample periods which are not powers of 2. coef[i, :, var, node, mode] = res if len(res) == nt else res[:coef.shape[1]] self.log.debug("coef") self.log.debug(narray_describe(coef)) spectra = spectral.WaveletCoefficients( source=self.time_series, mother=self.mother, sample_period=self.sample_period, frequencies=self.frequencies.to_array(), normalisation=self.normalisation, q_ratio=self.q_ratio, array_data=coef) return spectra
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 Hopfield(Model): r""" The Hopfield neural network is a discrete time dynamical system composed of multiple binary nodes, with a connectivity matrix built from a predetermined set of patterns. The update, inspired from the spin-glass model (used to describe magnetic properties of dilute alloys), is based on a random scanning of every node. The existence of a fixed point dynamics is guaranteed by a Lyapunov function. The Hopfield network is expected to have those multiple patterns as attractors (multistable dynamical system). When the initial conditions are close to one of the 'learned' patterns, the dynamical system is expected to relax on the corresponding attractor. A possible output of the system is the final attractive state (interpreted as an associative memory). Various extensions of the initial model have been proposed, among which a noiseless and continuous version [Hopfield 1984] having a slightly different Lyapunov function, but essentially the same dynamical properties, with more straightforward physiological interpretation. A continuous Hopfield neural network (with a sigmoid transfer function) can indeed be interpreted as a network of neural masses with every node corresponding to the mean field activity of a local brain region, with many bridges with the Wilson Cowan model [WC_1972]. **References**: .. [Hopfield1982] Hopfield, J. J., *Neural networks and physical systems with emergent collective computational abilities*, Proc. Nat. Acad. Sci. (USA) 79, 2554-2558, 1982. .. [Hopfield1984] Hopfield, J. J., *Neurons with graded response have collective computational properties like those of two-sate neurons*, Proc. Nat. Acad. Sci. (USA) 81, 3088-3092, 1984. See also, http://www.scholarpedia.org/article/Hopfield_network .. #This model can use a global threshold permitting multistable dynamic for .. #a positive structural connectivity matrix. .. automethod:: Hopfield.configure Dynamic equations: dfun equation .. math:: \dot{x_{i}} &= 1 / \tau_{x} (-x_{i} + c_0) dfun dynamic equation .. math:: \dot{x_{i}} &= 1 / \tau_{x} (-x_{i} + c_0(i)) \\ \dot{\\theta_{i}} &= 1 / \tau_{\theta_{i}} (-\theta + c_1(i)) .. figure :: img/Hopfield_01_mode_0_pplane.svg The phase-plane for the Hopfield model. """ # Define traited attributes for this model, these represent possible kwargs. taux = NArray( label=":math:`\\tau_{x}`", default=numpy.array([1.]), domain=Range(lo=0.01, hi=100., step=0.01), doc= """The fast time-scale for potential calculus :math:`x`, state-variable of the model.""" ) tauT = NArray( label=":math:`\\tau_{\\theta}`", default=numpy.array([5.]), domain=Range(lo=0.01, hi=100., step=0.01), doc= """The slow time-scale for threshold calculus :math:`\theta`, state-variable of the model.""" ) dynamic = NArray( dtype=int, label="Dynamic", default=numpy.array([0]), domain=Range(lo=0, hi=1., step=1), doc="""Boolean value for static/dynamic threshold theta for (0/1).""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = Final( label="State Variable ranges [lo, hi]", default={ "x": numpy.array([-1., 2.]), "theta": numpy.array([0., 1.]) }, doc="""The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random inital conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.""" ) variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("x", "theta"), default=("x", ), 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.""" ) state_variables = ('x', 'theta') _nvar = 2 cvar = numpy.array([0], dtype=numpy.int32) def configure(self): """Set the threshold as a state variable for a dynamical threshold.""" super(Hopfield, self).configure() if self.dynamic: self.dfun = self.dfunDyn self._nvar = 2 self.cvar = numpy.array([0, 1], dtype=numpy.int32) # self.variables_of_interest = ["x", "theta"] def dfun(self, state_variables, coupling, local_coupling=0.0): r""" The fast, :math:`x`, and slow, :math:`\theta`, state variables are typically considered to represent a membrane potentials of nodes and the global inhibition term, respectively: .. math:: \dot{x_{i}} &= 1 / \tau_{x} (-x_{i} + c_0) """ x = state_variables[0, :] dx = (-x + coupling[0]) / self.taux # todo: display dependent hack. It returns dx twice to be compatible with dfunDyn # We return 2 arrays here, because we have 2 possible state Variable, even if not dynamic # Otherwise the phase-plane display will fail. derivative = numpy.array([dx, dx]) return derivative def dfunDyn(self, state_variables, coupling, local_coupling=0.0): r""" The fast, :math:`x`, and slow, :math:`\theta`, state variables are typically considered to represent a membrane potentials of nodes and the inhibition term(s), respectively: .. math:: \dot{x_{i}} &= 1 / \tau_{x} (-x_{i} + c_0(i)) \\ \dot{\theta_{i}} &= 1 / \tau_{\theta_{i}} (-\theta + c_1(i)) where c_0 is the coupling term and c_1 should be the direct output. """ x = state_variables[0, :] theta = state_variables[1, :] dx = (-x + coupling[0]) / self.taux dtheta = (-theta + coupling[1]) / self.tauT derivative = numpy.array([dx, dtheta]) return derivative
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 ZerlautAdaptationFirstOrder(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_e | 60.0 | nS | +--------------+------------+------------+ | b_i | 0.0 | nS | +--------------+------------+------------+ | a_e | 4.0 | nS | +--------------+------------+------------+ | a_i | 0.0 | nS | +--------------+------------+------------+ | tau_w_e | 500.0 | ms | +--------------+------------+------------+ | tau_w_i | 0.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 transfer 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 null-clines, 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) """ # 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 resistance 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:`b_e`", 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:`a_e`", 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:`b_i`", 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:`a_i`", 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_we`", 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_wi`", 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 = 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""") # 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([ 1e-3, 250.e-3 ]), # actually the 100Hz should be replaced by 1/T_refrac "I": numpy.array([1e-3, 250.e-3]), "W_e": numpy.array([0.0, 200.0]), "W_i": 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"), 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'.split() _nvar = 4 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, :] 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 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, 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, 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 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, 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, 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: connectivity probability :param g: fraction of inhibitory cells :return: mean and variance of membrane voltage of neurons and autocorrelation time constant """ # firing rate # 1e-6 represent spontaneous release of synaptic neurotransmitter or some intrinsic currents of neurons fe = (Fe + 1.0e-6) * (1. - g) * p_connect * N_tot + Fe_ext * K_ext_e fi = (Fi + 1.0e-6) * g * p_connect * 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 BalloonModel(HasTraits): """ A class for calculating the simulated BOLD signal given a TimeSeries object of TVB and returning another TimeSeries object. The haemodynamic model parameters based on constants for a 1.5 T scanner. """ # NOTE: a potential problem when the input is a TimeSeriesSurface. # TODO: add an spatial averaging for TimeSeriesSurface. time_series = Attr( field_type=time_series.TimeSeries, label="Time Series", required=True, doc="""The timeseries that represents the input neural activity""") # it also sets the bold sampling period. dt = Float(label=":math:`dt`", default=0.002, required=True, doc="""The integration time step size for the balloon model (s). If none is provided, by default, the TimeSeries sample period is used.""" ) integrator = Attr(field_type=integrators_module.Integrator, label="Integration scheme", default=integrators_module.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 balloon model state variables.""") bold_model = Attr( field_type=str, label="Select BOLD model equations", choices=("linear", "nonlinear"), default="nonlinear", doc="""Select the set of equations for the BOLD model.""") RBM = Attr(field_type=bool, label="Revised BOLD Model", default=True, required=True, doc="""Select classical vs revised BOLD model (CBM or RBM). Coefficients k1, k2 and k3 will be derived accordingly.""") neural_input_transformation = Attr( field_type=str, label="Neural input transformation", choices=("none", "abs_diff", "sum"), default="none", doc= """ This represents the operation to perform on the state-variable(s) of the model used to generate the input TimeSeries. ``none`` takes the first state-variable as neural input; `` abs_diff`` is the absolute value of the derivative (first order difference) of the first state variable; ``sum``: sum all the state-variables of the input TimeSeries.""") tau_s = Float(label=r":math:`\tau_s`", default=1.54, required=True, doc="""Balloon model parameter. Time of signal decay (s)""") tau_f = Float( label=r":math:`\tau_f`", default=1.44, required=True, doc=""" Balloon model parameter. Time of flow-dependent elimination or feedback regulation (s). The average time blood take to traverse the venous compartment. It is the ratio of resting blood volume (V0) to resting blood flow (F0).""") tau_o = Float(label=r":math:`\tau_o`", default=0.98, required=True, doc=""" Balloon model parameter. Haemodynamic transit time (s). The average time blood take to traverse the venous compartment. It is the ratio of resting blood volume (V0) to resting blood flow (F0).""") alpha = Float( label=r":math:`\tau_f`", default=0.32, required=True, doc= """Balloon model parameter. Stiffness parameter. Grubb's exponent.""") TE = Float(label=":math:`TE`", default=0.04, required=True, doc="""BOLD parameter. Echo Time""") V0 = Float(label=":math:`V_0`", default=4.0, required=True, doc="""BOLD parameter. Resting blood volume fraction.""") E0 = Float(label=":math:`E_0`", default=0.4, required=True, doc="""BOLD parameter. Resting oxygen extraction fraction.""") epsilon = NArray( label=":math:`\epsilon`", default=numpy.array([0.5]), domain=Range(lo=0.5, hi=2.0, step=0.25), required=True, doc= """ BOLD parameter. Ratio of intra- and extravascular signals. In principle this parameter could be derived from empirical data and spatialized.""") nu_0 = Float( label=r":math:`\nu_0`", default=40.3, required=True, doc= """BOLD parameter. Frequency offset at the outer surface of magnetized vessels (Hz).""" ) r_0 = Float( label=":math:`r_0`", default=25., required=True, doc= """ BOLD parameter. Slope r0 of intravascular relaxation rate (Hz). Only used for ``revised`` coefficients. """) def evaluate(self): """ Calculate simulated BOLD signal """ cls_attr_name = self.__class__.__name__ + ".time_series" # self.time_series.trait["data"].log_debug(owner=cls_attr_name) # NOTE: Just using the first state variable, although in the Bold monitor # input is the sum over the state-variables. Only time-series # from basic monitors should be used as inputs. neural_activity, t_int = self.input_transformation( self.time_series, self.neural_input_transformation) input_shape = neural_activity.shape result_shape = self.result_shape(input_shape) self.log.debug("Result shape will be: %s" % str(result_shape)) if self.dt is None: self.dt = self.time_series.sample_period / 1000. # (s) integration time step msg = "Integration time step size for the balloon model is %s seconds" % str( self.dt) self.log.debug(msg) # NOTE: Avoid upsampling ... if self.dt < (self.time_series.sample_period / 1000.): msg = "Integration time step shouldn't be smaller than the sampling period of the input signal." self.log.error(msg) balloon_nvar = 4 # NOTE: hard coded initial conditions state = numpy.zeros((input_shape[0], balloon_nvar, input_shape[2], input_shape[3])) # s state[0, 1, :] = 1. # f state[0, 2, :] = 1. # v state[0, 3, :] = 1. # q # BOLD model coefficients k = self.compute_derived_parameters() k1, k2, k3 = k[0], k[1], k[2] # prepare integrator self.integrator.dt = self.dt self.integrator.configure() self.log.debug("Integration time step size will be: %s seconds" % str(self.integrator.dt)) scheme = self.integrator.scheme # NOTE: the following variables are not used in this integration but # required due to the way integrators scheme has been defined. local_coupling = 0.0 stimulus = 0.0 # Do some checks: if numpy.isnan(neural_activity).any(): self.log.warning("NaNs detected in the neural activity!!") # normalise the time-series. neural_activity = neural_activity - neural_activity.mean( axis=0)[numpy.newaxis, :] # solve equations for step in range(1, t_int.shape[0]): state[step, :] = scheme(state[step - 1, :], self.balloon_dfun, neural_activity[step, :], local_coupling, stimulus) if numpy.isnan(state[step, :]).any(): self.log.warning("NaNs detected...") # NOTE: just for the sake of clarity, define the variables used in the BOLD model s = state[:, 0, :] f = state[:, 1, :] v = state[:, 2, :] q = state[:, 3, :] # import pdb; pdb.set_trace() # BOLD models if self.bold_model == "nonlinear": """ Non-linear BOLD model equations. Page 391. Eq. (13) top in [Stephan2007]_ """ y_bold = numpy.array(self.V0 * (k1 * (1. - q) + k2 * (1. - q / v) + k3 * (1. - v))) y_b = y_bold[:, numpy.newaxis, :, :] self.log.debug("Max value: %s" % str(y_b.max())) else: """ Linear BOLD model equations. Page 391. Eq. (13) bottom in [Stephan2007]_ """ y_bold = numpy.array(self.V0 * ((k1 + k2) * (1. - q) + (k3 - k2) * (1. - v))) y_b = y_bold[:, numpy.newaxis, :, :] sample_period = 1. / self.dt bold_signal = time_series.TimeSeriesRegion(data=y_b, time=t_int, sample_period=sample_period, sample_period_unit='s') return bold_signal def compute_derived_parameters(self): """ Compute derived parameters :math:`k_1`, :math:`k_2` and :math:`k_3`. """ if not self.RBM: """ Classical BOLD Model Coefficients [Obata2004]_ Page 389 in [Stephan2007]_, Eq. (3) """ k1 = 7. * self.E0 k2 = 2. * self.E0 k3 = 1. - self.epsilon else: """ Revised BOLD Model Coefficients. Generalized BOLD signal model. Page 400 in [Stephan2007]_, Eq. (12) """ k1 = 4.3 * self.nu_0 * self.E0 * self.TE k2 = self.epsilon * self.r_0 * self.E0 * self.TE k3 = 1 - self.epsilon return numpy.array([k1, k2, k3]) def input_transformation(self, time_series, mode): """ Perform an operation on the input time-series. """ self.log.debug("Computing: %s on the input time series" % str(mode)) if mode == "none": ts = time_series.data[:, 0, :, :] ts = ts[:, numpy.newaxis, :, :] t_int = time_series.time / 1000. # (s) elif mode == "abs_diff": ts = abs(numpy.diff(time_series.data, axis=0)) t_int = (time_series.time[1:] - time_series.time[0:-1]) / 1000. # (s) elif mode == "sum": ts = numpy.sum(time_series.data, axis=1) ts = ts[:, numpy.newaxis, :, :] t_int = time_series.time / 1000. # (s) else: self.log.error( "Bad operation/transformation mode, must be one of:") self.log.error("('abs_diff', 'sum', 'none')") raise Exception("Bad transformation mode") return ts, t_int def balloon_dfun(self, state_variables, neural_input, local_coupling=0.0): r""" The Balloon model equations. See Eqs. (4-10) in [Stephan2007]_ .. math:: \frac{ds}{dt} &= x - \kappa\,s - \gamma \,(f-1) \\ \frac{df}{dt} &= s \\ \frac{dv}{dt} &= \frac{1}{\tau_o} \, (f - v^{1/\alpha})\\ \frac{dq}{dt} &= \frac{1}{\tau_o}(f \, \frac{1-(1-E_0)^{1/\alpha}}{E_0} - v^{&/\alpha} \frac{q}{v})\\ \kappa &= \frac{1}{\tau_s}\\ \gamma &= \frac{1}{\tau_f} """ s = state_variables[0, :] f = state_variables[1, :] v = state_variables[2, :] q = state_variables[3, :] x = neural_input[0, :] ds = x - (1. / self.tau_s) * s - (1. / self.tau_f) * (f - 1) df = s dv = (1. / self.tau_o) * (f - v**(1. / self.alpha)) dq = (1. / self.tau_o) * ((f * (1. - (1. - self.E0)**(1. / f)) / self.E0) - (v**(1. / self.alpha)) * (q / v)) return numpy.array([ds, df, dv, dq]) def result_shape(self, input_shape): """Returns the shape of the main result of fmri balloon ...""" result_shape = (input_shape[0], input_shape[1], input_shape[2], input_shape[3]) return result_shape def result_size(self, input_shape): """ Returns the storage size in Bytes of the main result of . """ result_size = numpy.sum( list(map(numpy.prod, self.result_shape(input_shape)))) * 8.0 # Bytes return result_size def extended_result_size(self, input_shape): """ Returns the storage size in Bytes of the extended result of the .... That is, it includes storage of the evaluated ... attributes such as ..., etc. """ extend_size = self.result_size( input_shape) # Currently no derived attributes. return extend_size
class ReducedWongWangExcInh(ModelNumbaDfun): r""" .. [WW_2006] Kong-Fatt Wong and Xiao-Jing Wang, *A Recurrent Network Mechanism of Time Integration in Perceptual Decisions*. Journal of Neuroscience 26(4), 1314-1328, 2006. .. [DPA_2014] Deco Gustavo, Ponce Alvarez Adrian, Patric Hagmann, Gian Luca Romani, Dante Mantini, and Maurizio Corbetta. *How Local Excitation–Inhibition Ratio Impacts the Whole Brain Dynamics*. The Journal of Neuroscience 34(23), 7886 –7898, 2014. Equations taken from [DPA_2013]_ , page 11242 .. math:: x_{ek} &= w_p\,J_N \, S_{ek} - J_iS_{ik} + W_eI_o + GJ_N \mathbf\Gamma(S_{ek}, S_{ej}, u_{kj}) \\ H(x_{ek}) &= \dfrac{a_ex_{ek}- b_e}{1 - \exp(-d_e(a_ex_{ek} -b_e))} \\ \dot{S}_{ek} &= -\dfrac{S_{ek}}{\tau_e} + (1 - S_{ek}) \, {\gamma}H(x_{ek}) \\ x_{ik} &= J_N \, S_{ek} - S_{ik} + W_iI_o + {\lambda}GJ_N \mathbf\Gamma(S_{ik}, S_{ej}, u_{kj}) \\ H(x_{ik}) &= \dfrac{a_ix_{ik} - b_i}{1 - \exp(-d_i(a_ix_{ik} -b_i))} \\ \dot{S}_{ik} &= -\dfrac{S_{ik}}{\tau_i} + \gamma_iH(x_{ik}) \ """ # Define traited attributes for this model, these represent possible kwargs. 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""") a_i = NArray( label=":math:`a_i`", default=numpy.array([ 615., ]), domain=Range(lo=0., hi=1000., step=1.), doc= "[n/C]. Inhibitory population input gain parameter, chosen to fit numerical solutions." ) b_i = NArray( label=":math:`b_i`", default=numpy.array([ 177.0, ]), domain=Range(lo=0.0, hi=200.0, step=1.0), doc= "[Hz]. Inhibitory population input shift parameter chosen to fit numerical solutions." ) d_i = NArray( label=":math:`d_i`", default=numpy.array([ 0.087, ]), domain=Range(lo=0.0, hi=0.2, step=0.001), doc= """[s]. Inhibitory population input scaling parameter chosen to fit numerical solutions.""" ) gamma_i = NArray(label=r":math:`\gamma_i`", default=numpy.array([ 1.0 / 1000, ]), domain=Range(lo=0.0, hi=2.0 / 1000, step=0.01 / 1000), doc="""Inhibitory population kinetic parameter""") tau_i = NArray( label=r":math:`\tau_i`", default=numpy.array([ 10., ]), domain=Range(lo=5., hi=100., step=1.0), doc="""[ms]. Inhibitory population NMDA decay time constant.""") J_i = NArray(label=r":math:`J_{i}`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.001, hi=2.0, step=0.001), doc="""[nA] Local inhibitory current""") W_i = NArray(label=r":math:`W_i`", default=numpy.array([ 0.7, ]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Inhibitory population external input scaling weight""") I_o = NArray(label=":math:`I_{o}`", default=numpy.array([ 0.382, ]), domain=Range(lo=0.0, hi=1.0, step=0.001), doc="""[nA]. Effective external input""") I_ext = NArray(label=":math:`I_{ext}`", default=numpy.array([ 0.0, ]), domain=Range(lo=0.0, hi=1.0, step=0.001), doc="""[nA]. Effective external stimulus 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(default={ "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") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_boundaries = Final( label="State Variable boundaries [lo, hi]", default={ "S_e": numpy.array([0.0, 1.0]), "S_i": 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_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(ReducedWongWangExcInh, self).configure() self.update_derived_parameters() def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0): S = state_variables[:, :] c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) J_N_S_e = self.J_N * S[0] x_e = self.w_p * J_N_S_e - self.J_i * S[ 1] + self.W_e * self.I_o + coupling + self.I_ext x_e = self.a_e * x_e - self.b_e H_e = x_e / (1 - numpy.exp(-self.d_e * x_e)) dS_e = -(S[0] / self.tau_e) + (1 - S[0]) * H_e * self.gamma_e x_i = J_N_S_e - S[1] + self.W_i * self.I_o + self.lamda * coupling x_i = self.a_i * x_i - self.b_i H_i = x_i / (1 - numpy.exp(-self.d_i * x_i)) dS_i = -(S[1] / self.tau_i) + H_i * self.gamma_i derivative = numpy.array([dS_e, dS_i]) return derivative def dfun(self, x, c, local_coupling=0.0, **kwargs): 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}){\gamma}H(x_{ek}) \\ x_{ik} &= J_N \, S_{ek} - S_{ik} + W_iI_o + {\lambda}GJ_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}) \ """ x_ = x.reshape(x.shape[:-1]).T c_ = c.reshape(c.shape[:-1]).T + local_coupling * x[0] deriv = _numba_dfun(x_, c_, self.a_e, self.b_e, self.d_e, self.gamma_e, self.tau_e, self.w_p, self.W_e, self.J_N, self.a_i, self.b_i, self.d_i, self.gamma_i, self.tau_i, self.W_i, self.J_i, self.G, self.lamda, self.I_o, self.I_ext) return deriv.T[..., numpy.newaxis]
class ContinuousWaveletTransform(HasTraits): """ A class for calculating the wavelet transform of a TimeSeries object of TVB and returning a WaveletSpectrum object. The sampling period and frequency range of the result can be specified. The mother wavelet can also be specified... (So far, only Morlet.) References: .. [TBetal_1996] C. Tallon-Baudry et al, *Stimulus Specificity of Phase-Locked and Non-Phase-Locked 40 Hz Visual Responses in Human.*, J Neurosci 16(13):4240-4249, 1996. .. [Mallat_1999] S. Mallat, *A wavelet tour of signal processing.*, book, Academic Press, 1999. """ time_series = Attr( field_type=time_series.TimeSeries, label="Time Series", doc="""The timeseries to which the wavelet is to be applied.""") mother = Attr( field_type=str, label="Wavelet function", default="morlet", doc="""The mother wavelet function used in the transform. Default is 'morlet', possibilities are: 'morlet'...""") sample_period = Float( label="Sample period of result (ms)", default=7.8125, #7.8125 => 128 Hz doc="""The sampling period of the computed wavelet spectrum. NOTE: This should be an integral multiple of the of the sampling period of the source time series, otherwise the actual resulting sample period will be the first correct value below that requested.""") frequencies = Attr( field_type=Range, label="Frequency range of result (kHz).", default=Range(lo=0.008, hi=0.060, step=0.002), doc="""The frequency resolution and range returned. Requested frequencies are converted internally into appropriate scales.""") normalisation = Attr( field_type=str, label="Normalisation", default="energy", doc="""The type of normalisation for the resulting wavet spectrum. Default is 'energy', options are: 'energy'; 'gabor'.""") q_ratio = Float( label="Q-ratio", default=5.0, doc="""NFC. Must be greater than 5. Ratios of the center frequencies to bandwidths.""") def evaluate(self): """ Calculate the continuous wavelet transform of time_series. """ ts_shape = self.time_series.data.shape if self.frequencies.step == 0: self.log.warning("Frequency step can't be 0! Trying default step, 2e-3.") self.frequencies.step = 0.002 freqs = numpy.arange(self.frequencies.lo, self.frequencies.hi, self.frequencies.step) if (freqs.size == 0) or any(freqs <= 0.0): # TODO: Maybe should limit number of freqs... ~100 is probably a reasonable upper bound. self.log.warning("Invalid frequency range! Falling back to default.") self.log.debug("freqs") self.log.debug(narray_describe(freqs)) self.frequencies = Range(lo=0.008, hi=0.060, step=0.002) freqs = numpy.arange(self.frequencies.lo, self.frequencies.hi, self.frequencies.step) self.log.debug("freqs") self.log.debug(narray_describe(freqs)) sample_rate = self.time_series.sample_rate # Duke: code below is as given by Andreas Spiegler, I've just wrapped # some of the original argument names nf = len(freqs) temporal_step = max((1, iround(self.sample_period / self.time_series.sample_period))) nt = int(numpy.ceil(ts_shape[0] / temporal_step)) if not isinstance(self.q_ratio, numpy.ndarray): q_ratio = self.q_ratio * numpy.ones((1, nf)) if numpy.nanmin(q_ratio) < 5: msg = "q_ratio must be not lower than 5 !" self.log.error(msg) raise Exception(msg) if numpy.nanmax(freqs) > sample_rate / 2.0: msg = "Sampling rate is too low for the requested frequency range !" self.log.error(msg) raise Exception(msg) # TODO: This isn't used, but min frequency seems like it should be important... Check with A.S. # fmin = 3.0 * numpy.nanmin(q_ratio) * sample_rate / numpy.pi / nt sigma_f = freqs / q_ratio sigma_t = 1.0 / (2.0 * numpy.pi * sigma_f) if self.normalisation == 'energy': Amp = 1.0 / numpy.sqrt(sample_rate * numpy.sqrt(numpy.pi) * sigma_t) elif self.normalisation == 'gabor': Amp = numpy.sqrt(2.0 / numpy.pi) / sample_rate / sigma_t coef_shape = (nf, nt, ts_shape[1], ts_shape[2], ts_shape[3]) coef = numpy.zeros(coef_shape, dtype = numpy.complex128) self.log.debug("coef") self.log.debug(narray_describe(coef)) scales = numpy.arange(0, nf, 1) for i in scales: f0 = freqs[i] SDt = sigma_t[(0, i)] A = Amp[(0, i)] x = numpy.arange(0, 4.0 * SDt * sample_rate, 1) / sample_rate wvlt = A * numpy.exp(-x**2 / (2.0 * SDt**2) ) * numpy.exp(2j * numpy.pi * f0 * x ) wvlt = numpy.hstack((numpy.conjugate(wvlt[-1:0:-1]), wvlt)) #util.self.log_debug_array(self.log, wvlt, "wvlt") for var in range(ts_shape[1]): for node in range(ts_shape[2]): for mode in range(ts_shape[3]): data = self.time_series.data[:, var, node, mode] wt = signal.convolve(data, wvlt, 'same') #util.self.log_debug_array(self.log, wt, "wt") res = wt[0::temporal_step] # NOTE: this is a horrible horrible quick hack (alas, a solution) to avoid broadcasting errors # when using dt and sample periods which are not powers of 2. coef[i, :, var, node, mode] = res if len(res) == nt else res[:coef.shape[1]] self.log.debug("coef") self.log.debug(narray_describe(coef)) spectra = spectral.WaveletCoefficients( source=self.time_series, mother=self.mother, sample_period=self.sample_period, frequencies=self.frequencies.to_array(), normalisation=self.normalisation, q_ratio=self.q_ratio, array_data=coef) return spectra def result_shape(self, input_shape, input_sample_period): """ Returns the shape of the main result (complex array) of the continuous wavelet transform. """ freq_len = int((self.frequencies.hi - self.frequencies.lo) / self.frequencies.step) temporal_step = max((1, self.sample_period / input_sample_period)) nt = int(round(input_shape[0] / temporal_step)) result_shape = (freq_len, nt, ) + input_shape[1:] return result_shape def result_size(self, input_shape, input_sample_period): """ Returns the storage size in Bytes of the main result (complex array) of the continuous wavelet transform. """ result_size = numpy.prod(self.result_shape(input_shape, input_sample_period)) * 2.0 * 8.0 #complex*Bytes return result_size def extended_result_size(self, input_shape, input_sample_period): """ Returns the storage size in Bytes of the extended result of the continuous wavelet transform. That is, it includes storage of the evaluated WaveletCoefficients attributes such as power, phase, amplitude, etc. """ result_shape = self.result_shape(input_shape, input_sample_period) result_size = self.result_size(input_shape, input_sample_period) extend_size = result_size #Main array extend_size = extend_size + 0.5 * result_size #Amplitude extend_size = extend_size + 0.5 * result_size #Phase extend_size = extend_size + 0.5 * result_size #Power extend_size = extend_size + result_shape[0] * 8.0 #Frequency return extend_size
class LinearReducedWongWangExcIO(ReducedWongWangExcIO): d = NArray(label=":math:`d`", default=numpy.array([ 0.2, ]), domain=Range(lo=0.0, hi=0.200, step=0.001), doc="""[s]. Parameter chosen to fit numerical solutions.""") non_integrated_variables = ["R", "Rin", "I"] def update_state_variables_before_integration(self, state_variables, coupling, local_coupling=0.0, stimulus=0.0): if self.use_numba: state_variables = \ _numba_update_non_state_variables(state_variables.reshape(state_variables.shape[:-1]).T, coupling.reshape(coupling.shape[:-1]).T + local_coupling * state_variables[0], self.a, self.b, self.d, self.w, self.J_N, self.Rin, self.G, self.I_o) return state_variables.T[..., numpy.newaxis] # In this case, rates (H_e, H_i) are non-state variables, # i.e., they form part of state_variables but have no dynamics assigned on them # Most of the computations of this dfun aim at computing rates, including coupling considerations. # Therefore, we compute and update them only once a new state is computed, # and we consider them constant for any subsequent possible call to this function, # by any integration scheme S = state_variables[0, :] # synaptic gating dynamics Rint = state_variables[1, :] # Rates from Spiking Network, integrated Rin = state_variables[3, :] # Input rates from Spiking Network c_0 = coupling[0, :] # if applicable lc_0 = local_coupling * S[0] coupling = self.G * self.J_N * (c_0 + lc_0) # Currents I = self.w * self.J_N * S + self.I_o + coupling x = self.a * I - self.b # Rates # Only rates with _Rin <= 0 0 will be updated by TVB. # The rest, are updated from the Spiking Network R = numpy.where(self._Rin, Rint, x / (1 - numpy.exp(-self.d * x))) Rin = numpy.where( self._Rin, Rin, 0.0) # Reset to 0 the Rin for nodes not updated by Spiking Network # We now update the state_variable vector with the new rates and currents: state_variables[2, :] = R state_variables[3, :] = Rin state_variables[4, :] = I # Keep them here so that they are not recomputed in the dfun self._R = numpy.copy(R) self._Rin = numpy.copy(Rin) return state_variables def _numpy_dfun(self, integration_variables, R, Rin): r""" Equations taken from [DPA_2013]_ , page 11242 .. math:: x_k &= w\,J_N \, S_k + I_o + J_N \mathbf\Gamma(S_k, S_j, u_{kj}),\\ H(x_k) &= \dfrac{ax_k - b}{1 - \exp(-d(ax_k -b))},\\ \dot{S}_k &= -\dfrac{S_k}{\tau_s} + (1 - S_k) \, H(x_k) \, \gamma """ S = integration_variables[0, :] # Synaptic gating dynamics Rint = integration_variables[ 1, :] # Rates from Spiking Network, integrated # Synaptic gating dynamics dS = -(S / self.tau_s) + R * self.gamma # Rates # Low pass filtering, linear dynamics for rates updated from the spiking network # No dynamics in the case of TVB rates dRint = numpy.where(self._Rin_mask, (-Rint + Rin) / self.tau_rin, 0.0) return numpy.array([dS, dRint]) def dfun(self, x, c, local_coupling=0.0): if self._R is None or self._Rin is None: state_variables = self._integration_to_state_variables(x) state_variables = \ self.update_state_variables_before_integration(state_variables, c, local_coupling, self._stimulus) R = state_variables[2] # Rates Rin = state_variables[3] # input instant spiking rates else: R = self._R Rin = self._Rin if self.use_numba: deriv = _numba_dfun( x.reshape(x.shape[:-1]).T, R, Rin, self.gamma, self.tau_s, self.Rin, self.tau_rin).T[..., numpy.newaxis] else: deriv = self._numpy_dfun(x, R, Rin) # Set them to None so that they are recomputed on subsequent steps # for multistep integration schemes such as Runge-Kutta: self._R = None self._Rin = None return deriv
class WilsonCowan(Model): r""" **References**: .. [WC_1972] Wilson, H.R. and Cowan, J.D. *Excitatory and inhibitory interactions in localized populations of model neurons*, Biophysical journal, 12: 1-24, 1972. .. [WC_1973] Wilson, H.R. and Cowan, J.D *A Mathematical Theory of the Functional Dynamics of Cortical and Thalamic Nervous Tissue* .. [D_2011] Daffertshofer, A. and van Wijk, B. *On the influence of amplitude on the connectivity between phases* Frontiers in Neuroinformatics, July, 2011 Used Eqns 11 and 12 from [WC_1972]_ in ``dfun``. P and Q represent external inputs, which when exploring the phase portrait of the local model are set to constant values. However in the case of a full network, P and Q are the entry point to our long range and local couplings, that is, the activity from all other nodes is the external input to the local population. The default parameters are taken from figure 4 of [WC_1972]_, pag. 10 In [WC_1973]_ they present a model of neural tissue on the pial surface is. See Fig. 1 in page 58. The following local couplings (lateral interactions) occur given a region i and a region j: E_i-> E_j E_i-> I_j I_i-> I_j I_i-> E_j +---------------------------+ | Table 1 | +--------------+------------+ | | | SanzLeonetAl, 2014 | +--------------+------------+ |Parameter | Value | +==============+============+ | k_e, k_i | 1.00 | +--------------+------------+ | r_e, r_i | 0.00 | +--------------+------------+ | tau_e, tau_i | 10.0 | +--------------+------------+ | c_1 | 10.0 | +--------------+------------+ | c_2 | 6.0 | +--------------+------------+ | c_3 | 1.0 | +--------------+------------+ | c_4 | 1.0 | +--------------+------------+ | a_e, a_i | 1.0 | +--------------+------------+ | b_e, b_i | 0.0 | +--------------+------------+ | theta_e | 2.0 | +--------------+------------+ | theta_i | 3.5 | +--------------+------------+ | alpha_e | 1.2 | +--------------+------------+ | alpha_i | 2.0 | +--------------+------------+ | P | 0.5 | +--------------+------------+ | Q | 0 | +--------------+------------+ | c_e, c_i | 1.0 | +--------------+------------+ | alpha_e | 1.2 | +--------------+------------+ | alpha_i | 2.0 | +--------------+------------+ | | | frequency peak at 20 Hz | | | +---------------------------+ The parameters in Table 1 reproduce Figure A1 in [D_2011]_ but set the limit cycle frequency to a sensible value (eg, 20Hz). Model bifurcation parameters: * :math:`c_1` * :math:`P` 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: .. _phase-plane-WC: .. figure :: img/WilsonCowan_01_mode_0_pplane.svg :alt: Wilson-Cowan phase plane (E, I) The (:math:`E`, :math:`I`) phase-plane for the Wilson-Cowan model. The general formulation for the \textit{\textbf{Wilson-Cowan}} model as a dynamical unit at a node $k$ in a BNM with $l$ nodes reads: .. math:: \dot{E}_k &= \dfrac{1}{\tau_e} (-E_k + (k_e - r_e E_k) \mathcal{S}_e (\alpha_e \left( c_{ee} E_k - c_{ei} I_k + P_k - \theta_e + \mathbf{\Gamma}(E_k, E_j, u_{kj}) + W_{\zeta}\cdot E_j + W_{\zeta}\cdot I_j\right) ))\\ \dot{I}_k &= \dfrac{1}{\tau_i} (-I_k + (k_i - r_i I_k) \mathcal{S}_i (\alpha_i \left( c_{ie} E_k - c_{ee} I_k + Q_k - \theta_i + \mathbf{\Gamma}(E_k, E_j, u_{kj}) + W_{\zeta}\cdot E_j + W_{\zeta}\cdot I_j\right) )) """ # Define traited attributes for this model, these represent possible kwargs. c_ee = NArray(label=":math:`c_{ee}`", default=numpy.array([12.0]), domain=Range(lo=11.0, hi=16.0, step=0.01), doc="""Excitatory to excitatory coupling coefficient""") c_ei = NArray(label=":math:`c_{ei}`", default=numpy.array([4.0]), domain=Range(lo=2.0, hi=15.0, step=0.01), doc="""Inhibitory to excitatory coupling coefficient""") c_ie = NArray(label=":math:`c_{ie}`", default=numpy.array([13.0]), domain=Range(lo=2.0, hi=22.0, step=0.01), doc="""Excitatory to inhibitory coupling coefficient.""") c_ii = NArray(label=":math:`c_{ii}`", default=numpy.array([11.0]), domain=Range(lo=2.0, hi=15.0, step=0.01), doc="""Inhibitory to inhibitory coupling coefficient.""") tau_e = NArray( label=r":math:`\tau_e`", default=numpy.array([10.0]), domain=Range(lo=0.0, hi=150.0, step=0.01), doc="""Excitatory population, membrane time-constant [ms]""") tau_i = NArray( label=r":math:`\tau_i`", default=numpy.array([10.0]), domain=Range(lo=0.0, hi=150.0, step=0.01), doc="""Inhibitory population, membrane time-constant [ms]""") a_e = NArray( label=":math:`a_e`", default=numpy.array([1.2]), domain=Range(lo=0.0, hi=1.4, step=0.01), doc="""The slope parameter for the excitatory response function""") b_e = NArray( label=":math:`b_e`", default=numpy.array([2.8]), domain=Range(lo=1.4, hi=6.0, step=0.01), doc= """Position of the maximum slope of the excitatory sigmoid function""") c_e = NArray( label=":math:`c_e`", default=numpy.array([1.0]), domain=Range(lo=1.0, hi=20.0, step=1.0), doc="""The amplitude parameter for the excitatory response function""") theta_e = NArray(label=r":math:`\theta_e`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=60., step=0.01), doc="""Excitatory threshold""") a_i = NArray( label=":math:`a_i`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=2.0, step=0.01), doc="""The slope parameter for the inhibitory response function""") b_i = NArray(label=r":math:`b_i`", default=numpy.array([4.0]), domain=Range(lo=2.0, hi=6.0, step=0.01), doc="""Position of the maximum slope of a sigmoid function [in threshold units]""") theta_i = NArray(label=r":math:`\theta_i`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=60.0, step=0.01), doc="""Inhibitory threshold""") c_i = NArray( label=":math:`c_i`", default=numpy.array([1.0]), domain=Range(lo=1.0, hi=20.0, step=1.0), doc="""The amplitude parameter for the inhibitory response function""") r_e = NArray(label=":math:`r_e`", default=numpy.array([1.0]), domain=Range(lo=0.5, hi=2.0, step=0.01), doc="""Excitatory refractory period""") r_i = NArray(label=":math:`r_i`", default=numpy.array([1.0]), domain=Range(lo=0.5, hi=2.0, step=0.01), doc="""Inhibitory refractory period""") k_e = NArray(label=":math:`k_e`", default=numpy.array([1.0]), domain=Range(lo=0.5, hi=2.0, step=0.01), doc="""Maximum value of the excitatory response function""") k_i = NArray(label=":math:`k_i`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=2.0, step=0.01), doc="""Maximum value of the inhibitory response function""") P = NArray(label=":math:`P`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=20.0, step=0.01), doc="""External stimulus to the excitatory population. Constant intensity.Entry point for coupling.""") Q = NArray(label=":math:`Q`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=20.0, step=0.01), doc="""External stimulus to the inhibitory population. Constant intensity.Entry point for coupling.""") alpha_e = NArray(label=r":math:`\alpha_e`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=20.0, step=0.01), doc="""External stimulus to the excitatory population. Constant intensity.Entry point for coupling.""") alpha_i = NArray(label=r":math:`\alpha_i`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=20.0, step=0.01), doc="""External stimulus to the inhibitory population. Constant intensity.Entry point for coupling.""") # 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, 1.0]), "I": numpy.array([0.0, 1.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.""") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("E", "I", "E + I", "E - I"), 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` and :math:`I = 1`.""") state_variables = 'E I'.split() _nvar = 2 cvar = numpy.array([0, 1], dtype=numpy.int32) def dfun(self, state_variables, coupling, local_coupling=0.0): r""" .. math:: \tau \dot{x}(t) &= -z(t) + \phi(z(t)) \\ \phi(x) &= \frac{c}{1-exp(-a (x-b))} """ E = state_variables[0, :] I = state_variables[1, :] derivative = numpy.empty_like(state_variables) # long-range coupling c_0 = coupling[0, :] # short-range (local) coupling lc_0 = local_coupling * E lc_1 = local_coupling * I x_e = self.alpha_e * (self.c_ee * E - self.c_ei * I + self.P - self.theta_e + c_0 + lc_0 + lc_1) x_i = self.alpha_i * (self.c_ie * E - self.c_ii * I + self.Q - self.theta_i + lc_0 + lc_1) s_e = self.c_e / (1.0 + numpy.exp(-self.a_e * (x_e - self.b_e))) s_i = self.c_i / (1.0 + numpy.exp(-self.a_i * (x_i - self.b_i))) derivative[0] = (-E + (self.k_e - self.r_e * E) * s_e) / self.tau_e derivative[1] = (-I + (self.k_i - self.r_i * I) * s_i) / self.tau_i return derivative
class MorrisLecar(models.Model): """ The Morris-Lecar model is a mathematically simple excitation model having two nonlinear, non-inactivating conductances. .. [ML_1981] Morris, C. and Lecar, H. *Voltage oscillations in the Barnacle giant muscle fibre*, Biophysical Journal 35: 193, 1981. See also, http://www.scholarpedia.org/article/Morris-Lecar_model .. figure :: img/MorrisLecar_01_mode_0_pplane.svg :alt: Morris-Lecar phase plane (V, N) The (:math:`V`, :math:`N`) phase-plane for the Morris-Lecar model. .. automethod:: MorrisLecar.dfun """ # Define traited attributes for this model, these represent possible kwargs. gCa = NArray( label=":math:`g_{Ca}`", default=numpy.array([4.0]), domain=Range(lo=2.0, hi=6.0, step=0.01), doc="""Conductance of population of Ca++ channels [mmho/cm2]""") gK = NArray(label=":math:`g_K`", default=numpy.array([8.0]), domain=Range(lo=4.0, hi=12.0, step=0.01), doc="""Conductance of population of K+ channels [mmho/cm2]""") gL = NArray( label=":math:`g_L`", default=numpy.array([2.0]), domain=Range(lo=1.0, hi=3.0, step=0.01), doc="""Conductance of population of leak channels [mmho/cm2]""") C = NArray(label=":math:`C`", default=numpy.array([20.0]), domain=Range(lo=10.0, hi=30.0, step=0.01), doc="""Membrane capacitance [uF/cm2]""") lambda_Nbar = NArray(label=":math:`\\lambda_{Nbar}`", default=numpy.array([0.06666667]), domain=Range(lo=0.0, hi=1.0, step=0.00000001), doc="""Maximum rate for K+ channel opening [1/s]""") V1 = NArray( label=":math:`V_1`", default=numpy.array([10.0]), domain=Range(lo=5.0, hi=15.0, step=0.01), doc="""Potential at which half of the Ca++ channels are open at steady state [mV]""") V2 = NArray( label=":math:`V_2`", default=numpy.array([15.0]), domain=Range(lo=7.5, hi=22.5, step=0.01), doc="""1/slope of voltage dependence of the fraction of Ca++ channels that are open at steady state [mV].""") V3 = NArray( label=":math:`V_3`", default=numpy.array([-1.0]), domain=Range(lo=-1.5, hi=-0.5, step=0.01), doc="""Potential at which half of the K+ channels are open at steady state [mV].""") V4 = NArray( label=":math:`V_4`", default=numpy.array([14.5]), domain=Range(lo=7.25, hi=22.0, step=0.01), doc="""1/slope of voltage dependence of the fraction of K+ channels that are open at steady state [mV].""") VCa = NArray(label=":math:`V_{Ca}`", default=numpy.array([100.0]), domain=Range(lo=50.0, hi=150.0, step=0.01), doc="""Ca++ Nernst potential [mV]""") VK = NArray(label=":math:`V_K`", default=numpy.array([-70.0]), domain=Range(lo=-105.0, hi=-35.0, step=0.01), doc="""K+ Nernst potential [mV]""") VL = NArray(label=":math:`V_L`", default=numpy.array([-50.0]), domain=Range(lo=-75.0, hi=-25.0, step=0.01), doc="""Nernst potential leak channels [mV]""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = Final( { "V": numpy.array([-70.0, 50.0]), "N": numpy.array([-0.2, 0.8]) }, 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 = NArray( dtype=numpy.int, label="Variables watched by Monitors", domain=Range(lo=0, hi=2, step=1), default=numpy.array([0], 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`, and :math:`N = 1`.""") # coupling_variables = arrays.IntegerArray( # label = "Variables to couple activity through", # default = numpy.array([0], dtype=numpy.int32)) # nsig = arrays.FloatArray( # label = "Noise dispersion", # default = numpy.array([0.0]), # range = basic.Range(lo = 0.0, hi = 1.0)) def __init__(self, **kwargs): """ Initialize the MorrisLecar model's traited attributes, any provided as keywords will overide their traited default. """ LOG.info('%s: initing...' % str(self)) super(MorrisLecar, self).__init__(**kwargs) self._state_variables = ["V", "N"] self._nvar = 2 self.cvar = numpy.array([0], dtype=numpy.int32) LOG.debug('%s: inited.' % repr(self)) def dfun(self, state_variables, coupling, local_coupling=0.0): """ The dynamics of the membrane potential :math:`V` rely on the fraction of Ca++ channels :math:`M` and K+ channels :math:`N` open at a given time. In order to have a planar model, we make the simplifying assumption (following [ML_1981]_, Equation 9) that Ca++ system is much faster than K+ system so that :math:`M = M_{\\infty}` at all times: .. math:: C \\, \\dot{V} &= I - g_{L}(V - V_L) - g_{Ca} \\, M_{\\infty}(V) (V - V_{Ca}) - g_{K} \\, N \\, (V - V_{K}) \\\\ \\dot{N} &= \\lambda_{N}(V) \\, (N_{\\infty}(V) - N) \\\\ M_{\\infty}(V) &= 1/2 \\, (1 + \\tanh((V - V_{1})/V_{2}))\\\\ N_{\\infty}(V) &= 1/2 \\, (1 + \\tanh((V - V_{3})/V_{4}))\\\\ \\lambda_{N}(V) &= \\overline{\\lambda_{N}} \\cosh((V - V_{3})/2V_{4}) where external currents :math:`I` provide the entry point for local and long-range connectivity. Default parameters are set as per Figure 9 of [ML_1981]_ so that the model shows oscillatory behaviour as :math:`I` is varied. """ V = state_variables[0, :] N = state_variables[1, :] c_0 = coupling[0, :] M_inf = 0.5 * (1 + numpy.tanh((V - self.V1) / self.V2)) N_inf = 0.5 * (1 + numpy.tanh((V - self.V3) / self.V4)) lambda_N = self.lambda_Nbar * numpy.cosh( (V - self.V3) / (2.0 * self.V4)) dV = (1.0 / self.C) * (c_0 + (local_coupling * V) - self.gL * (V - self.VL) - self.gCa * M_inf * (V - self.VCa) - self.gK * N * (V - self.VK)) dN = lambda_N * (N_inf - N) derivative = numpy.array([dV, dN]) return derivative
class Generic2dOscillator(models.Model): """ The Generic2dOscillator model is ... .. [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. See also, http://www.scholarpedia.org/article/FitzHugh-Nagumo_model The models (:math:`V`, :math:`W`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. _phase-plane-FHN: .. figure :: img/Generic2dOscillator_01_mode_0_pplane.svg :alt: Fitzhugh-Nagumo phase plane (V, W) The (:math:`V`, :math:`W`) phase-plane for the Fitzhugh-Nagumo model. .. #Currently there seems to be a clash betwen traits and autodoc, autodoc .. #can't find the methods of the class, the class specific names below get .. #us around this... .. automethod:: Generic2dOscillator.__init__ .. automethod:: Generic2dOscillator.dfun """ # 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.""") a = NArray( label=":math:`a`", default=numpy.array([1.05]), domain=Range(lo=-1.0, hi=1.5, step=0.01), doc="""ratio a/b gives W-nullcline slope""") b = NArray( label=":math:`b`", default=numpy.array([0.2]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""dimensionless parameter""") omega = NArray( label=":math:`\\omega`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""dimensionless parameter""") upsilon = NArray( label=":math:`\\upsilon`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""dimensionless parameter""") gamma = NArray( label=":math:`\\gamma`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""dimensionless parameter""") eta = NArray( label=":math:`\\eta`", default=numpy.array([1.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""ratio :math:`\\eta/b` gives W-nullcline intersect(V=0)""") variables_of_interest = List( of=str, label="Variables watched by Monitors.", choices=("V", "W"), 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( { "V": numpy.array([-2.0, 4.0]), "W": numpy.array([-6.0, 6.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.""") state_variables = ["V", "W"] _nvar = 2 cvar = numpy.array([0], dtype=numpy.int32) def dfun(self, state_variables, coupling, local_coupling=0.0): """ The fast, :math:`V`, and slow, :math:`W`, state variables are typically considered to represent a membrane potential and recovery variable, respectively. Based on equations 1 and 2 of [FH_1961]_, but relabelling c as :math:`\\tau` due to its interpretation as a time-scale separation, and adding parameters :math:`\\upsilon`, :math:`\\omega`, :math:`\\eta`, and :math:`\\gamma`, for flexibility, here we implement: .. math:: \\dot{V} &= \\tau (\\omega \\, W + \\upsilon \\, V - \\gamma \\, \\frac{V^3}{3} + I) \\\\ \\dot{W} &= -(\\eta \\, V - a + b \\, W) / \\tau where external currents :math:`I` provide the entry point for local and long-range connectivity. For strict consistency with [FH_1961]_, parameters :math:`\\upsilon`, :math:`\\omega`, :math:`\\eta`, and :math:`\\gamma` should be set to 1.0, with :math:`a`, :math:`b`, and :math:`\\tau` set in the range defined by equation 3 of [FH_1961]_: .. math:: 0 \\le b \\le 1 \\\\ 1 - 2 b / 3 \\le a \\le 1 \\\\ \\tau^2 \\ge b The default state of these equations can be seen in the :ref:`Fitzhugh-Nagumo phase-plane <phase-plane-FHN>`. """ V = state_variables[0, :] W = state_variables[1, :] # [State_variables, nodes] c_0 = coupling[0, :] dV = self.tau * (self.omega * W + self.upsilon * V - self.gamma * V**3.0 / 3.0 + c_0 + local_coupling * V) dW = (self.a - self.eta * V - self.b * W) / self.tau derivative = numpy.array([dV, dW]) return derivative
class ReducedSetFitzHughNagumo(ReducedSetBase): r""" A reduced representation of a set of Fitz-Hugh Nagumo oscillators, [SJ_2008]_. The models (:math:`\xi`, :math:`\eta`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. _phase-plane-rFHN_0: .. figure :: img/ReducedSetFitzHughNagumo_01_mode_0_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 1st mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the first mode of a reduced set of Fitz-Hugh Nagumo oscillators. .. _phase-plane-rFHN_1: .. figure :: img/ReducedSetFitzHughNagumo_01_mode_1_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 2nd mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the second mode of a reduced set of Fitz-Hugh Nagumo oscillators. .. _phase-plane-rFHN_2: .. figure :: img/ReducedSetFitzHughNagumo_01_mode_2_pplane.svg :alt: Reduced set of FitzHughNagumo phase plane (xi, eta), 3rd mode. The (:math:`\xi`, :math:`\eta`) phase-plane for the third mode of a reduced set of Fitz-Hugh Nagumo oscillators. The system's equations for the i-th mode at node q are: .. math:: \dot{\xi}_{i} &= c\left(\xi_i-e_i\frac{\xi_{i}^3}{3} -\eta_{i}\right) + K_{11}\left[\sum_{k=1}^{o} A_{ik}\xi_k-\xi_i\right] - K_{12}\left[\sum_{k =1}^{o} B_{i k}\alpha_k-\xi_i\right] + cIE_i \\ &\, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr} \right] \\ \dot{\eta}_i &= \frac{1}{c}\left(\xi_i-b\eta_i+m_i\right) \\ & \\ \dot{\alpha}_i &= c\left(\alpha_i-f_i\frac{\alpha_i^3}{3}-\beta_i\right) + K_{21}\left[\sum_{k=1}^{o} C_{ik}\xi_i-\alpha_i\right] + cII_i \\ & \, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr}\right] \\ & \\ \dot{\beta}_i &= \frac{1}{c}\left(\alpha_i-b\beta_i+n_i\right) .. automethod:: ReducedSetFitzHughNagumo.update_derived_parameters #NOTE: In the Article this modelis called StefanescuJirsa2D """ # Define traited attributes for this model, these represent possible kwargs. tau = NArray( label=r":math:`\tau`", default=numpy.array([3.0]), domain=Range(lo=1.5, hi=4.5, step=0.01), doc="""doc...(prob something about timescale seperation)""") a = NArray( label=":math:`a`", default=numpy.array([0.45]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""doc...""") b = NArray( label=":math:`b`", default=numpy.array([0.9]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""doc...""") K11 = NArray( label=":math:`K_{11}`", default=numpy.array([0.5]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, excitatory to excitatory""") K12 = NArray( label=":math:`K_{12}`", default=numpy.array([0.15]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, inhibitory to excitatory""") K21 = NArray( label=":math:`K_{21}`", default=numpy.array([0.15]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Internal coupling, excitatory to inhibitory""") sigma = NArray( label=r":math:`\sigma`", default=numpy.array([0.35]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Standard deviation of Gaussian distribution""") mu = NArray( label=r":math:`\mu`", default=numpy.array([0.0]), domain=Range(lo=0.0, hi=1.0, step=0.01), doc="""Mean of Gaussian distribution""") # Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = Final( label="State Variable ranges [lo, hi]", default={"xi": numpy.array([-4.0, 4.0]), "eta": numpy.array([-3.0, 3.0]), "alpha": numpy.array([-4.0, 4.0]), "beta": numpy.array([-3.0, 3.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.""") variables_of_interest = List( of=str, label="Variables watched by Monitors", choices=("xi", "eta", "alpha", "beta"), default=("xi", "alpha"), doc=r"""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:`\xi = 0`, :math:`\eta = 1`, :math:`\alpha = 2`, and :math:`\beta= 3`.""") state_variables = tuple('xi eta alpha beta'.split()) _nvar = 4 cvar = numpy.array([0, 2], dtype=numpy.int32) # Derived parameters Aik = None Bik = None Cik = None e_i = None f_i = None IE_i = None II_i = None m_i = None n_i = None def dfun(self, state_variables, coupling, local_coupling=0.0): r""" The system's equations for the i-th mode at node q are: .. math:: \dot{\xi}_{i} &= c\left(\xi_i-e_i\frac{\xi_{i}^3}{3} -\eta_{i}\right) + K_{11}\left[\sum_{k=1}^{o} A_{ik}\xi_k-\xi_i\right] - K_{12}\left[\sum_{k =1}^{o} B_{i k}\alpha_k-\xi_i\right] + cIE_i \\ &\, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr} \right] \\ \dot{\eta}_i &= \frac{1}{c}\left(\xi_i-b\eta_i+m_i\right) \\ & \\ \dot{\alpha}_i &= c\left(\alpha_i-f_i\frac{\alpha_i^3}{3}-\beta_i\right) + K_{21}\left[\sum_{k=1}^{o} C_{ik}\xi_i-\alpha_i\right] + cII_i \\ & \, + \left[\sum_{k=1}^{o} \mathbf{\Gamma}(\xi_{kq}, \xi_{kr}, u_{qr})\right] + \left[\sum_{k=1}^{o} W_{\zeta}\cdot\xi_{kr}\right] \\ & \\ \dot{\beta}_i &= \frac{1}{c}\left(\alpha_i-b\beta_i+n_i\right) """ xi = state_variables[0, :] eta = state_variables[1, :] alpha = state_variables[2, :] beta = state_variables[3, :] derivative = numpy.empty_like(state_variables) # sum the activity from the modes c_0 = coupling[0, :].sum(axis=1)[:, numpy.newaxis] # TODO: generalize coupling variables to a matrix form # c_1 = coupling[1, :] # this cv represents alpha derivative[0] = (self.tau * (xi - self.e_i * xi ** 3 / 3.0 - eta) + self.K11 * (numpy.dot(xi, self.Aik) - xi) - self.K12 * (numpy.dot(alpha, self.Bik) - xi) + self.tau * (self.IE_i + c_0 + local_coupling * xi)) derivative[1] = (xi - self.b * eta + self.m_i) / self.tau derivative[2] = (self.tau * (alpha - self.f_i * alpha ** 3 / 3.0 - beta) + self.K21 * (numpy.dot(xi, self.Cik) - alpha) + self.tau * (self.II_i + c_0 + local_coupling * xi)) derivative[3] = (alpha - self.b * beta + self.n_i) / self.tau return derivative def update_derived_parameters(self): """ Calculate coefficients for the Reduced FitzHugh-Nagumo oscillator based neural field model. Specifically, this method implements equations for calculating coefficients found in the supplemental material of [SJ_2008]_. Include equations here... """ newaxis = numpy.newaxis trapz = scipy_integrate_trapz stepu = 1.0 / (self.nu + 2 - 1) stepv = 1.0 / (self.nv + 2 - 1) norm = scipy_stats_norm(loc=self.mu, scale=self.sigma) Zu = norm.ppf(numpy.arange(stepu, 1.0, stepu)) Zv = norm.ppf(numpy.arange(stepv, 1.0, stepv)) # Define the modes V = numpy.zeros((self.number_of_modes, self.nv)) U = numpy.zeros((self.number_of_modes, self.nu)) nv_per_mode = self.nv // self.number_of_modes nu_per_mode = self.nu // self.number_of_modes for i in range(self.number_of_modes): V[i, i * nv_per_mode:(i + 1) * nv_per_mode] = numpy.ones(nv_per_mode) U[i, i * nu_per_mode:(i + 1) * nu_per_mode] = numpy.ones(nu_per_mode) # Normalise the modes V = V / numpy.tile(numpy.sqrt(trapz(V * V, Zv, axis=1)), (self.nv, 1)).T U = U / numpy.tile(numpy.sqrt(trapz(U * U, Zu, axis=1)), (self.nv, 1)).T # Get Normal PDF's evaluated with sampling Zv and Zu g1 = norm.pdf(Zv) g2 = norm.pdf(Zu) G1 = numpy.tile(g1, (self.number_of_modes, 1)) G2 = numpy.tile(g2, (self.number_of_modes, 1)) cV = numpy.conj(V) cU = numpy.conj(U) intcVdZ = trapz(cV, Zv, axis=1)[:, newaxis] intG1VdZ = trapz(G1 * V, Zv, axis=1)[newaxis, :] intcUdZ = trapz(cU, Zu, axis=1)[:, newaxis] # import pdb; pdb.set_trace() # Calculate coefficients self.Aik = numpy.dot(intcVdZ, intG1VdZ).T self.Bik = numpy.dot(intcVdZ, trapz(G2 * U, Zu, axis=1)[newaxis, :]) self.Cik = numpy.dot(intcUdZ, intG1VdZ).T self.e_i = trapz(cV * V ** 3, Zv, axis=1)[newaxis, :] self.f_i = trapz(cU * U ** 3, Zu, axis=1)[newaxis, :] self.IE_i = trapz(Zv * cV, Zv, axis=1)[newaxis, :] self.II_i = trapz(Zu * cU, Zu, axis=1)[newaxis, :] self.m_i = (self.a * intcVdZ).T self.n_i = (self.a * intcUdZ).T
class Generic2dOscillator(TVBGeneric2dOscillator): r""" The Generic2dOscillator model is a generic dynamic system with two state variables. The dynamic equations of this model are composed of two ordinary differential equations comprising two nullclines. The first nullcline is a cubic function as it is found in most neuron and population builders; 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 builders of nerve membrane*, Biophysical Journal 1: 445, 1961. .. [Nagumo_1962] Nagumo et.al, *An Active Pulse Transmission Line Simulating Nerve Axon*, Proceedings of the IRE 50: 2061, 1962. .. [SJ_2011] Stefanescu, R., Jirsa, V.K. *Reduced representations of heterogeneous mixed neural networks with synaptic coupling*. Physical Review E, 83, 2011. .. [SJ_2010] Jirsa VK, Stefanescu R. *Neural population modes capture biologically realistic large-scale network dynamics*. Bulletin of Mathematical Biology, 2010. .. [SJ_2008_a] Stefanescu, R., Jirsa, V.K. *A low dimensional description of globally coupled heterogeneous neural networks of excitatory and inhibitory neurons*. PLoS Computational Biology, 4(11), 2008). The model's (:math:`V`, :math:`W`) time series and phase-plane its nullclines can be seen in the figure below. The model with its default parameters exhibits FitzHugh-Nagumo like dynamics. +---------------------------+ | Table 1 | +--------------+------------+ | EXCITABLE CONFIGURATION | +--------------+------------+ |Parameter | Value | +==============+============+ | a | -2.0 | +--------------+------------+ | b | -10.0 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | limit cycle if a is 2.0 | +---------------------------+ +---------------------------+ | Table 2 | +--------------+------------+ | BISTABLE CONFIGURATION | +--------------+------------+ |Parameter | Value | +==============+============+ | a | 1.0 | +--------------+------------+ | b | 0.0 | +--------------+------------+ | c | -5.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | monostable regime: | | fixed point if Iext=-2.0 | | limit cycle if Iext=-1.0 | +---------------------------+ +---------------------------+ | Table 3 | +--------------+------------+ | EXCITABLE CONFIGURATION | +--------------+------------+ | (similar to Morris-Lecar)| +--------------+------------+ |Parameter | Value | +==============+============+ | a | 0.5 | +--------------+------------+ | b | 0.6 | +--------------+------------+ | c | -4.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | excitable regime if b=0.6 | | oscillatory if b=0.4 | +---------------------------+ +---------------------------+ | Table 4 | +--------------+------------+ | GhoshetAl, 2008 | | KnocketAl, 2009 | +--------------+------------+ |Parameter | Value | +==============+============+ | a | 1.05 | +--------------+------------+ | b | -1.00 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.1 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | alpha | 1.0 | +--------------+------------+ | beta | 0.2 | +--------------+------------+ | gamma | -1.0 | +--------------+------------+ | e | 0.0 | +--------------+------------+ | g | 1.0 | +--------------+------------+ | f | 1/3 | +--------------+------------+ | tau | 1.25 | +--------------+------------+ | | | frequency peak at 10Hz | | | +---------------------------+ +---------------------------+ | Table 5 | +--------------+------------+ | SanzLeonetAl 2013 | +--------------+------------+ |Parameter | Value | +==============+============+ | a | - 0.5 | +--------------+------------+ | b | -10.0 | +--------------+------------+ | c | 0.0 | +--------------+------------+ | d | 0.02 | +--------------+------------+ | I | 0.0 | +--------------+------------+ | | | intrinsic frequency is | | approx 10 Hz | | | +---------------------------+ NOTE: This regime, if I = 2.1, is called subthreshold regime. Unstable oscillations appear through a subcritical Hopf bifurcation. .. figure :: img/Generic2dOscillator_01_mode_0_pplane.svg .. _phase-plane-Generic2D: :alt: Phase plane of the generic 2D population model with (V, W) The (:math:`V`, :math:`W`) phase-plane for the generic 2D population model for default parameters. The dynamical system has an equilibrium point. .. automethod:: Generic2dOscillator.dfun """ V_m = NArray( label=r":math:`V_m`", default=numpy.array([0.0]), domain=Range(lo=-10.0, hi=10.0, step=0.1), doc="""NEST membrane potential input""") def _numpy_dfun(self, state_variables, coupling, local_coupling=0.0, ev=numexpr.evaluate): r""" The two state variables :math:`V` and :math:`W` are typically considered to represent a function of the neuron's membrane potential, such as the firing rate or dendritic currents, and a recovery variable, respectively. If there is a time scale hierarchy, then typically :math:`V` is faster than :math:`W` corresponding to a value of :math:`\tau` greater than 1. The equations of the generic 2D population model read .. math:: \dot{V} &= d \, \tau (-f V^3 + e V^2 + g V + \alpha W + \gamma I), \\ \dot{W} &= \dfrac{d}{\tau}\,\,(c V^2 + b V - \beta W + a), where external currents :math:`I` provide the entry point for local, long-range connectivity and stimulation. """ V = state_variables[0, :] V = numpy.where(self.V_m == 0, V, self.V_m) W = state_variables[1, :] #[State_variables, nodes] c_0 = coupling[0, :] tau = self.tau I = self.I a = self.a b = self.b c = self.c d = self.d e = self.e f = self.f g = self.g beta = self.beta alpha = self.alpha gamma = self.gamma lc_0 = local_coupling * V # Pre-allocate the result array then instruct numexpr to use it as output. # This avoids an expensive array concatenation derivative = numpy.empty_like(state_variables) ev('d * tau * (alpha * W - f * V**3 + e * V**2 + g * V + gamma * I + gamma *c_0 + lc_0)', out=derivative[0]) ev('d * (a + b * V + c * V**2 - beta * W) / tau', out=derivative[1]) return derivative def dfun(self, vw, c, local_coupling=0.0): vw[0, :] = numpy.where(self.V_m == 0, vw[0, :].squeeze(), self.V_m)[:, numpy.newaxis] lc_0 = local_coupling * vw[0, :, 0] vw_ = vw.reshape(vw.shape[:-1]).T c_ = c.reshape(c.shape[:-1]).T deriv = _numba_dfun_g2d(vw_, c_, self.tau, self.I, self.a, self.b, self.c, self.d, self.e, self.f, self.g, self.beta, self.alpha, self.gamma, lc_0) return deriv.T[..., numpy.newaxis]
class 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([0.0]), "y1": numpy.array([0.0]), "z": numpy.array([0.0]), "x2": numpy.array([0.0]), "y2": numpy.array([0.0]), "g": numpy.array([0.0]) }, doc="""state variables""") state_variable_boundaries = Final( label="State Variable boundaries [lo, hi]", default={ "x1": numpy.array([-2.0, 1.0]), "y1": numpy.array([-20.0, 2.0]), "z": numpy.array([-2.0, 5.0]), "x2": numpy.array([-2.0, 0.0]), "y2": numpy.array([0.0, 2.0]), "g": numpy.array([-1.0, 1.0]) }, ) variables_of_interest = List( of=str, label="Variables or quantities available to Monitors", choices=( 'x1 ** x2', 'x2', ), default=( 'x1', 'y1', 'z', 'x2', 'y2', 'g', ), 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]
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 SigmoidalJansenRit(Coupling): r""" Provides a sigmoidal coupling function as described in the Jansen and Rit model, of the following form .. math:: c_{min} + (c_{max} - c_{min}) / (1.0 + \exp(-a(x-midpoint)/\sigma)) Assumes that x has have two state variables. """ cmin = NArray( label=":math:`c_{min}`", default=numpy.array([ 0.0, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="Minimum of the sigmoid function", ) cmax = NArray( label=":math:`c_{max}`", default=numpy.array([ 2.0 * 0.0025, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="Maximum of the sigmoid function", ) midpoint = NArray( label="midpoint", default=numpy.array([ 6.0, ]), domain=Range(lo=-1000.0, hi=1000.0, step=10.0), doc="Midpoint of the linear portion of the sigmoid", ) r = NArray( label=r":math:`r`", default=numpy.array([ 1.0, ]), domain=Range(lo=0.01, hi=1000.0, step=10.0), doc="the steepness of the sigmoidal transformation", ) a = NArray( label=r":math:`a`", default=numpy.array([ 0.56, ]), domain=Range(lo=0.01, hi=1000.0, step=10.0), doc="Scaling of the coupling term", ) def __str__(self): return simple_gen_astr(self, 'cmin cmax midpoint a r') def pre(self, x_i, x_j): pre = self.cmax / (1.0 + numpy.exp(self.r * (self.midpoint - (x_j[:, 0] - x_j[:, 1])))) return pre[:, numpy.newaxis] def post(self, gx): return self.a * gx
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]
def get_range_parameters(self): ntau_range_param = RangeParameter(Noise.ntau.field_name, float, Range(lo=0.0, hi=20.0, step=1.0)) params_with_range_defined = super(NoiseForm, self).get_range_parameters() params_with_range_defined.append(ntau_range_param) return params_with_range_defined
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 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]), "V": numpy.array([0.0]) }, doc="""state variables""") state_variable_boundaries = Final( label="State Variable boundaries [lo, hi]", default={ "r": numpy.array([0.0, np.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 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