class ProjectionSurfaceMEGData(ProjectionMatrixData): """ Specific projection, from a CorticalSurface to MEG sensors. ... warning :: PLACEHOLDER """ brain_skull = surfaces_module.BrainSkull(label = "Brain Skull", default = None, required = False, doc = """Boundary between skull and cortex domains.""") skull_skin = surfaces_module.SkullSkin(label = "Skull Skin", default = None, required = False, doc = """Boundary between skull and skin domains.""") skin_air = surfaces_module.SkinAir( label = "Skin Air", default = None, required = False, doc = """Boundary between skin and air domains.""") conductances = basic.Dict(label = "Domain conductances", required = False, default = {'air': 0.0, 'skin': 1.0, 'skull': 0.01, 'brain': 1.0}, doc = """ A dictionary representing the conductances of ... """) sensors = sensors_module.SensorsMEG sources = surfaces_module.CorticalSurface
class DoubleGaussianData(EquationData): """ A Mexican-hat function approximated by the difference of Gaussians functions. """ _ui_name = "Mexican-hat" equation = basic.String( label="Double Gaussian Equation", default= "(amp_1 * exp(-((var-midpoint_1)**2 / (2.0 * sigma_1**2)))) - (amp_2 * exp(-((var-midpoint_2)**2 / (2.0 * sigma_2**2))))", locked=True, doc=""":math:`amp_1 \\exp\\left(-\\left((x-midpoint_1)^2 / \\left(2.0 \\sigma_1^2\\right)\\right)\\right) - amp_2 \\exp\\left(-\\left((x-midpoint_2)^2 / \\left(2.0 \\sigma_2^2\\right)\\right)\\right)`""") parameters = basic.Dict(label="Double Gaussian Parameters", default={ "amp_1": 0.5, "sigma_1": 20.0, "midpoint_1": 0.0, "amp_2": 1.0, "sigma_2": 10.0, "midpoint_2": 0.0 })
class PulseTrainData(EquationData): """ A pulse train , offseted with respect to the time axis. **Parameters**: * :math:`\\tau` : pulse width or pulse duration * :math:`T` : pulse repetition period * :math:`f` : pulse repetition frequency (1/T) * duty cycle : :math:``\\frac{\\tau}{T}`` (for a square wave: 0.5) * onset time : """ equation = basic.String(label="Pulse Train", default="where((var % T) < tau, amp, 0)", locked=True, doc=""":math:`\\frac{\\tau}{T} +\\sum_{n=1}^{\\infty}\\frac{2}{n\\pi} \\sin\\left(\\frac{\\pi\\,n\\tau}{T}\\right) \\cos\\left(\\frac{2\\pi\\,n}{T} var\\right)`. The starting time is halfway through the first pulse. The phase can be offset t with t - tau/2""") # onset is in milliseconds # T and tau are in milliseconds as well parameters = basic.Dict(default={ "T": 42.0, "tau": 13.0, "amp": 1.0, "onset": 30.0 }, label="Pulse Train Parameters")
class TimeSeriesData(MappedType): """ Base time-series dataType. """ title = basic.String data = arrays.FloatArray( label="Time-series data", file_storage=core.FILE_STORAGE_EXPAND, doc= """An array of time-series data, with a shape of [tpts, :], where ':' represents 1 or more dimensions""" ) nr_dimensions = basic.Integer(label="Number of dimension in timeseries", default=4) length_1d, length_2d, length_3d, length_4d = [basic.Integer] * 4 labels_ordering = basic.List( default=["Time", "State Variable", "Space", "Mode"], label="Dimension Names", doc="""List of strings representing names of each data dimension""") labels_dimensions = basic.Dict( default={}, label= "Specific labels for each dimension for the data stored in this timeseries.", doc= """ A dictionary containing mappings of the form {'dimension_name' : [labels for this dimension] }""" ) ## TODO (for Stuart) : remove TimeLine and make sure the correct Period/start time is returned by different monitors in the simulator time = arrays.FloatArray( file_storage=core.FILE_STORAGE_EXPAND, label="Time-series time", required=False, doc= """An array of time values for the time-series, with a shape of [tpts,]. This is 'time' as returned by the simulator's monitors.""") start_time = basic.Float(label="Start Time:") sample_period = basic.Float(label="Sample period", default=1.0) # Specify the measure unit for sample period (e.g sec, msec, usec, ...) sample_period_unit = basic.String(label="Sample Period Measure Unit", default="ms") sample_rate = basic.Float(label="Sample rate", doc="""The sample rate of the timeseries""") has_surface_mapping = basic.Bool(default=True) has_volume_mapping = basic.Bool(default=False)
class ProjectionData(MappedType): """ Base DataType for representing a ProjectionMatrix. The projection is between a source of type CorticalSurface and a set of Sensors. """ projection_type = basic.String __mapper_args__ = {'polymorphic_on': 'projection_type'} brain_skull = surfaces.BrainSkull( label="Brain Skull", default=None, required=False, doc="""Boundary between skull and cortex domains.""") skull_skin = surfaces.SkullSkin( label="Skull Skin", default=None, required=False, doc="""Boundary between skull and skin domains.""") skin_air = surfaces.SkinAir( label="Skin Air", default=None, required=False, doc="""Boundary between skin and air domains.""") conductances = basic.Dict( label="Domain conductances", required=False, default={ 'air': 0.0, 'skin': 1.0, 'skull': 0.01, 'brain': 1.0 }, doc=""" A dictionary representing the conductances of ... """) sources = surfaces.CorticalSurface(label="surface or region", default=None, required=True) sensors = sensors.Sensors( label="Sensors", default=None, required=False, doc=""" A set of sensors to compute projection matrix for them. """) projection_data = arrays.FloatArray(label="Projection Matrix Data", default=None, required=True)
class CosineData(EquationData): """ A Cosine equation. """ equation = basic.String( label = "Cosine Equation", default = "amp * cos(6.283185307179586 * frequency * var)", locked = True, doc = """:math:`amp \\cos(2.0 \\pi frequency x)` """) parameters = basic.Dict( label = "Cosine Parameters", default = {"amp": 1.0, "frequency": 0.01}) #kHz #"pi": numpy.pi,
class SinusoidData(EquationData): """ A Sinusoid equation. """ equation = basic.String( label = "Sinusoid Equation", default = "amp * sin(6.283185307179586 * frequency * var)", locked = True, doc = """:math:`amp \\sin(2.0 \\pi frequency x)` """) parameters = basic.Dict( label = "Sinusoid Parameters", default = {"amp": 1.0, "frequency": 0.01}) #kHz #"pi": numpy.pi,
class AlphaData(EquationData): """ An Alpha function belonging to the Exponential function family. """ equation = basic.String( label = "Alpha Equation", default = "where((var-onset) > 0, (alpha * beta) / (beta - alpha) * (exp(-alpha * (var-onset)) - exp(-beta * (var-onset))), 0.0 * var)", locked = True, doc = """:math:`(\\alpha * \\beta) / (\\beta - \\alpha) * (\\exp(-\\alpha * (x-onset)) - \\exp(-\\beta * (x-onset)))` for :math:`(x-onset) > 0`""") parameters = basic.Dict( label = "Alpha Parameters", default = {"onset": 0.5, "alpha": 13.0, "beta": 42.0})
class GeneralizedSigmoidData(EquationData): """ A General Sigmoid equation. """ equation = basic.String( label = "Generalized Sigmoid Equation", default = "low + (high - low) / (1.0 + exp(-1.8137993642342178 * (var-midpoint)/sigma))", locked = True, doc = """:math:`low + (high - low) / (1.0 + \\exp(-\\pi/\\sqrt(3.0) (x-midpoint)/\\sigma))`""") parameters = basic.Dict( label = "Sigmoid Parameters", default = {"low": 0.0, "high": 1.0, "midpoint": 1.0, "sigma": 0.3}) #,
class GaussianData(FiniteSupportEquationData): """ A Gaussian equation. """ equation = basic.String( label = "Gaussian Equation", default = "amp * exp(-((var-midpoint)**2 / (2.0 * sigma**2)))", locked = True, doc = """:math:`amp \\exp\\left(-\\left(\\left(x-midpoint\\right)^2 / \\left(2.0 \\sigma^2\\right)\\right)\\right)`""") parameters = basic.Dict( label = "Gaussian Parameters", default = {"amp": 1.0, "sigma": 1.0, "midpoint": 0.0})
class SigmoidData(FiniteSupportEquationData): """ A Sigmoid equation. """ equation = basic.String( label = "Sigmoid Equation", default = "amp / (1.0 + exp(-1.8137993642342178 * (radius-var)/sigma))", locked = True, doc = """:math:`amp / (1.0 + \\exp(-\\pi/\\sqrt(3.0) (radius-x)/\\sigma))`""") parameters = basic.Dict( label = "Sigmoid Parameters", default = {"amp": 1.0, "radius": 5.0, "sigma": 1.0}) #"pi": numpy.pi,
class LinearData(EquationData): """ A linear equation. """ equation = basic.String(label="Linear Equation", default="a * var + b", locked=True, doc=""":math:`result = a * x + b`""") parameters = basic.Dict(label="Linear Parameters", default={ "a": 1.0, "b": 0.0 })
class LinearCouplingData(equations.Linear): """ The equation for representing a Linear Coupling """ _ui_name = "Linear Coupling" parameters = basic.Dict( label="Linear Coupling Parameters a and b", doc="""a: rescales connection strengths and maintains ratio and b: shifts the base strength (maintain absolute difference)""", default={ "a": 0.00390625, "b": 0.0 }) __generate_table__ = True
class GammaData(EquationData): """ A Gamma function for the bold monitor. It belongs to the family of Exponential functions. **Parameters**: * :math:`\\tau` : Exponential time constant of the gamma function [seconds]. * :math:`n` : The phase delay of the gamma function. * :math: `factorial` : (n-1)!. numexpr does not support factorial yet. * :math: `a` : Amplitude factor after normalization. **Reference**: .. [B_1996] Geoffrey M. Boynton, Stephen A. Engel, Gary H. Glover and David J. Heeger (1996). Linear Systems Analysis of Functional Magnetic Resonance Imaging in Human V1. J Neurosci 16: 4207-4221 .. note:: might be filtered from the equations used in Stimulus and Local Connectivity. """ _ui_name = "HRF kernel: Gamma kernel" # TODO: Introduce a time delay in the equation (shifts the hrf onset) # """:math:`h(t) = \frac{(\frac{t-\delta}{\tau})^{(n-1)} e^{-(\frac{t-\delta}{\tau})}}{\tau(n-1)!}""" # delta = 2.05 seconds -- Additional delay in seconds from the onset of the # time-series to the beginning of the gamma hrf. # delay cannot be negative or greater than the hrf duration. equation = basic.String( label="Gamma Equation", default= "((var / tau) ** (n - 1) * exp(-(var / tau)) )/ (tau * factorial)", locked=True, doc= """:math:`h(var) = \\frac{(\\frac{var}{\\tau})^{(n-1)}\\exp{-(\\frac{var}{\\tau})}}{\\tau(n-1)!}`.""" ) parameters = basic.Dict(label="Gamma Parameters", default={ "tau": 1.08, "n": 3.0, "factorial": 2.0, "a": 0.1 })
class FirstOrderVolterraData(EquationData): """ Integral form of the first Volterra kernel of the three used in the Ballon Windekessel model for computing the Bold signal. This function describes a damped Oscillator. **Parameters** : * :math:`\\tau_s`: Dimensionless? exponential decay parameter. * :math:`\\tau_f`: Dimensionless? oscillatory parameter. * :math:`k_1` : First Volterra kernel coefficient. * :math:`V_0` : Resting blood volume fraction. **References** : .. [F_2000] Friston, K., Mechelli, A., Turner, R., and Price, C., *Nonlinear Responses in fMRI: The Balloon Model, Volterra Kernels, and Other Hemodynamics*, NeuroImage, 12, 466 - 477, 2000. """ _ui_name = "HRF kernel: Volterra Kernel" equation = basic.String( label="First Order Volterra Kernel", default= "1/3. * exp(-0.5*(var / tau_s)) * (sin(sqrt(1./tau_f - 1./(4.*tau_s**2)) * var)) / (sqrt(1./tau_f - 1./(4.*tau_s**2)))", locked=True, doc=""":math:`G(t - t^{\\prime}) = e^{\\frac{1}{2} \\left(\\frac{t - t^{\\prime}}{\\tau_s} \\right)} \\frac{\sin\\left((t - t^{\\prime}) \\sqrt{\\frac{1}{\\tau_f} - \\frac{1}{4 \\tau_s^2}}\\right)} {\\sqrt{\\frac{1}{\\tau_f} - \\frac{1}{4 \\tau_s^2}}} \\; \\; \\; \\; \\; \\; for \\; \\; \\; t \\geq t^{\\prime} = 0 \\; \\; \\; \\; \\; \\; for \\; \\; \\; t < t^{\\prime}`.""" ) parameters = basic.Dict(label="Mixture of Gammas Parameters", default={ "tau_s": 0.8, "tau_f": 0.4, "k_1": 5.6, "V_0": 0.02 })
class DoubleExponentialData(EquationData): """ A difference of two exponential functions to define a kernel for the bold monitor. **Parameters** : * :math:`\\tau_1`: Time constant of the second exponential function [s] * :math:`\\tau_2`: Time constant of the first exponential function [s]. * :math:`f_1` : Frequency of the first sine function [Hz]. * :math:`f_2` : Frequency of the second sine function [Hz]. * :math:`amp_1`: Amplitude of the first exponential function. * :math:`amp_2`: Amplitude of the second exponential function. * :math:`a` : Amplitude factor after normalization. **Reference**: .. [P_2000] Alex Polonsky, Randolph Blake, Jochen Braun and David J. Heeger (2000). Neuronal activity in human primary visual cortex correlates with perception during binocular rivalry. Nature Neuroscience 3: 1153-1159 """ _ui_name = "HRF kernel: Difference of Exponentials" equation = basic.String( label="Double Exponential Equation", default= "((amp_1 * exp(-var/tau_1) * sin(2.*pi*f_1*var)) - (amp_2 * exp(-var/ tau_2) * sin(2.*pi*f_2*var)))", locked=True, doc=""":math:`h(var) = amp_1\\exp(\\frac{-var}{\tau_1}) \\sin(2\\cdot\\pi f_1 \\cdot var) - amp_2\\cdot \\exp(-\\frac{var} {\\tau_2})*\\sin(2\\pi f_2 var)`.""") parameters = basic.Dict(label="Double Exponential Parameters", default={ "tau_1": 7.22, "f_1": 0.03, "amp_1": 0.1, "tau_2": 7.4, "f_2": 0.12, "amp_2": 0.1, "a": 0.1, "pi": numpy.pi })
class Sigmoid(SpatialApplicableEquation, FiniteSupportEquation): """ A Sigmoid equation. offset: parameter to extend the behaviour of this function when spatializing model parameters. """ equation = basic.String( label="Sigmoid Equation", default= "(amp / (1.0 + exp(-1.8137993642342178 * (radius-var)/sigma))) + offset", locked=True, doc=""":math:`(amp / (1.0 + \\exp(-\\pi/\\sqrt(3.0) (radius-x)/\\sigma))) + offset`""") parameters = basic.Dict(label="Sigmoid Parameters", default={ "amp": 1.0, "radius": 5.0, "sigma": 1.0, "offset": 0.0 }) #"pi": numpy.pi,
class GaussianData(EquationData): """ A Gaussian equation. offset: parameter to extend the behaviour of this function when spatializing model parameters. """ equation = basic.String( label="Gaussian Equation", default="(amp * exp(-((var-midpoint)**2 / (2.0 * sigma**2))))+offset", locked=True, doc=""":math:`(amp \\exp\\left(-\\left(\\left(x-midpoint\\right)^2 / \\left(2.0 \\sigma^2\\right)\\right)\\right)) + offset`""") parameters = basic.Dict(label="Gaussian Parameters", default={ "amp": 1.0, "sigma": 1.0, "midpoint": 0.0, "offset": 0.0 })
class EquationData(basic.MapAsJson, core.Type): """ Within the UI we'll access via the specific Equation subclasses implemented below. """ _base_classes = [ 'Equation', 'FiniteSupportEquation', "DiscreteEquation", "TemporalApplicableEquation", "SpatialApplicableEquation", "HRFKernelEquation", #TODO: There should be a refactor of Coupling which may make these unnecessary 'Coupling', 'CouplingData', 'CouplingScientific', 'CouplingFramework', 'LinearCoupling', 'LinearCouplingData', 'LinearCouplingScientific', 'LinearCouplingFramework', 'SigmoidalCoupling', 'SigmoidalCouplingData', 'SigmoidalCouplingScientific', 'SigmoidalCouplingFramework' ] equation = basic.String( label="Equation as a string", doc="""A latex representation of the equation, with the extra escaping needed for interpretation via sphinx.""") parameters = basic.Dict( label="Parameters in a dictionary.", default={}, doc="""Should be a list of the parameters and their meaning, Traits should be able to take defaults and sensible ranges from any traited information that was provided.""")
class EpileptorDPrealistic(Model): 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]_). ->x0 parameters are shifted for the bifurcation to be at x0=1, where x0>1 is the supercritical region. ->there is a choice for linear or sigmoidal z dynamics (see [Proixetal_2014]_). Equations and default parameters are taken from [Jirsaetal_2014]_. The realistic Epileptor allows for state variables I_{ext1}, I_{ext2}, x0, slope and K to fluctuate as linear dynamical equations, driven by the corresponding parameter values. It could be combined with multiplicative and/or pink noise. +------------------------------------------------------+ | Table 1 | +----------------------+-------------------------------+ | Parameter | Value | +======================+===============================+ | I_ext1 | 3.1 | +----------------------+-------------------------------+ | I_ext2 | 0.45 | +----------------------+-------------------------------+ | tau0 | 2857.0 | +----------------------+-------------------------------+ | x_0 | 0.0 | +----------------------+-------------------------------+ | 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, Proix 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. .. [Proixetal_2014] Proix, T., Bartolomei, F., Chauvel, P., Bernard, C., & Jirsa, V. K. (2014). Permitau1ivity Coupling across Brain Regions Determines Seizure Recruitment in Partial Epilepsy. Journal of Neuroscience, 34(45), 15009–15021. htau1p://doi.org/10.1523/JNEUROSCI.1570-14.2014 .. automethod:: EpileptorDP.__init__ 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}} &=& yc - d x_{1}^{2} - y{1} \\ \dot{z} &=& \begin{cases} (f_z(x_{1}) - z-0.1 z^{7})/tau0 & \text{if } x<0 \\ (f_z(x_{1}) - z)/tau0 & \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 / \tau2 (-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\\ (x_{2} - 0.6(z-4)^2 -slope) x_{1} &\text{if }x_{1} \geq 0 \end{cases} .. math:: f_z(x_{1}) = \begin{cases} 4 * (x_{1} - x0) & \text{linear} \\ \frac{3}{1+e^{-10*(x_{1}+0.5)}} - x0 & \text{sigmoidal} \\ \end{cases} and: .. math:: f_{2}(x_{2}) = \begin{cases} 0 & \text{if } x_{2} <-0.25\\ s*(x_{2} + 0.25) & \text{if } x_{2} \geq -0.25 \end{cases} """ _ui_name = "EpileptorDPrealistic" ui_configurable_parameters = ["Iext1", "Iext2", "tau0", "x0", "slope"] zmode = arrays.FloatArray( label="zmode", default=numpy.array("lin"), doc="zmode = np.array(""lin"") for linear and numpy.array(""sig"") for sigmoidal z dynamics", order=-1) pmode = arrays.FloatArray( label="pmode", default=numpy.array("const"), doc="pmode = numpy.array(""g""), numpy.array(""z""), numpy.array(""z*g"") or numpy.array(""const"") parameters following the g, z, z*g dynamics or staying constamt, respectively", order=-1) # a = arrays.FloatArray( # label="a", # default=numpy.array([1]), # doc="Coefficient of the cubic term in the first state variable", # order=-1) # b = arrays.FloatArray( # label="b", # default=numpy.array([3]), # doc="Coefficient of the squared term in the first state variabel", # order=-1) yc = arrays.FloatArray( label="yc", default=numpy.array([1]), doc="Additive coefficient for the second state variable", order=-1) # d = arrays.FloatArray( # label="d", # default=numpy.array([5]), # doc="Coefficient of the squared term in the second state variable", # order=-1) tau0 = arrays.FloatArray( label="r", range=basic.Range(lo=100.0, hi=5000, step=10), default=numpy.array([10000.0]), doc="Temporal scaling in the third state variable", order=4) # s = arrays.FloatArray( # label="s", # default=numpy.array([4]), # doc="Linear coefficient in the third state variable", # order=-1) x0 = arrays.FloatArray( label="x0", range=basic.Range(lo=-0.5, hi=1.5, step=0.1), default=numpy.array([0.0]), doc="Excitability parameter", order=3) Iext1 = arrays.FloatArray( label="Iext1", range=basic.Range(lo=1.5, hi=5.0, step=0.1), default=numpy.array([3.1]), doc="External input current to the first population", order=1) slope = arrays.FloatArray( label="slope", range=basic.Range(lo=-16.0, hi=6.0, step=0.1), default=numpy.array([0.]), doc="Linear coefficient in the first state variable", order=5) Iext2 = arrays.FloatArray( label="Iext2", range=basic.Range(lo=0.0, hi=1.0, step=0.05), default=numpy.array([0.45]), doc="External input current to the second population", order=2) tau2 = arrays.FloatArray( label="tau2", default=numpy.array([10]), doc="Temporal scaling coefficient in fifth state variable", order=-1) Kvf = arrays.FloatArray( label="K_vf", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=4.0, step=0.5), doc="Coupling scaling on a very fast time scale.", order=6) Kf = arrays.FloatArray( label="K_f", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=4.0, step=0.5), doc="Correspond to the coupling scaling on a fast time scale.", order=7) K = arrays.FloatArray( label="K", default=numpy.array([0.0]), range=basic.Range(lo=-4.0, hi=4.0, step=0.1), doc="Permitau1ivity coupling, that is from the fast time scale toward the slow time scale", order=8) tau1 = arrays.FloatArray( label="tau1", default=numpy.array([0.25]), range=basic.Range(lo=0.001, hi=10.0, step=0.001), doc="Time scaling of the whole system", order=9) state_variable_range = basic.Dict( label="State variable ranges [lo, hi]", default={"y0": numpy.array([-2., 2.]), #x1 "y1": numpy.array([-20., 2.]), #y1 "y2": numpy.array([2.0, 20.0]), #z "y3": numpy.array([-2., 0.]), #x2 "y4": numpy.array([0., 2.]), #y2 "y5": numpy.array([-1., 1.]), #g "y6": numpy.array([-2, 2]), #x0 "y7": numpy.array([-20., 6.]), #slope "y8": numpy.array([1.5, 5.]), #Iext1 "y9": numpy.array([0., 1.]), #Iext2 "y10": numpy.array([-50., 50.])},#K doc="n/a", order=16 ) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["y0", "y1", "y2", "y3", "y4", "y5", "y6", "y7", "y8", "y9", "y10", "y3 - y0"], default=["y3 - y0", "y2"], select_multiple=True, doc="""default state variables to be monitored""", order=-1) state_variables = ["y0", "y1", "y2", "y3", "y4", "y5", "y6", "y7", "y8", "y9", "y10"] _nvar = 11 cvar = numpy.array([0, 3], dtype=numpy.int32) @staticmethod def fun_slope_Iext2(z, g, pmode, slope, Iext2): from tvb_epilepsy.base.utils import linear_scaling if (pmode == numpy.array(['g','z','z*g'])).any(): if pmode == 'g': xp = 1.0/ (1.0 + numpy.exp(1) ** (-10 * (g + 0.0))) xp1 = 0#-0.175 xp2 = 1#0.025 elif pmode == 'z': xp = 1.0 / (1.0 + numpy.exp(1) ** (-10 * (z - 3.00))) xp1 = 0 xp2 = 1 elif pmode == 'z*g': xp = z * g xp1 = -0.7 xp2 = 0.1 slope_eq = linear_scaling(xp, xp1, xp2, 1.0, slope) #slope_eq = self.slope Iext2_eq = linear_scaling(xp, xp1, xp2, 0.0, Iext2) else: slope_eq = slope Iext2_eq = Iext2 return slope_eq, Iext2_eq def dfun(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): r""" Computes the derivatives of the state variables of 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{y_{0}} &=& y_{1} - f_{1}(y_{0}, y_{3}) - y_{2} + I_{ext1} \\ \dot{y_{1}} &=& yc - d (y_{0} -5/3)^{2} - y{1} \\ \dot{y_{2}} &=& \begin{cases} (f_z(y_{0}) - y_{2}-0.1 y_{2}^{7})/tau0 & \text{if } y_{0}<5/3 \\ (f_z(y_{0}) - y_{2})/tau0 & \text{if } y_{0} \geq 5/3 \end{cases} \\ \dot{y_{3}} &=& -y_{4} + y_{3} - y_{3}^{3} + I_{ext2} + 0.002 y_{5} - 0.3 (y_{2}-3.5) \\ \dot{y_{4}} &=& 1 / \tau2 (-y_{4} + f_{2}(y_{3}))\\ \dot{y_{5}} &=& -0.01 (y_{5} - 0.1 ( y_{0} -5/3 ) ) where: .. math:: f_{1}(y_{0}, y_{3}) = \begin{cases} a ( y_{0} -5/3 )^{3} - b ( y_{0} -5/3 )^2 & \text{if } y_{0} <5/3\\ ( y_{3} - 0.6(y_{2}-4)^2 -slope ) ( y_{0} - 5/3 ) &\text{if }y_{0} \geq 5/3 \end{cases} .. math:: f_z(y_{0}) = \begin{cases} 4 * (y_{0} - x0) & \text{linear} \\ \frac{3}{1+e^{-10*(y_{0}-7/6)}} - x0 & \text{sigmoidal} \\ \end{cases} and: .. math:: f_{2}(y_{3}) = \begin{cases} 0 & \text{if } y_{3} <-0.25\\ s*(y_{3} + 0.25) & \text{if } y_{3} \geq -0.25 \end{cases} """ y = state_variables ydot = numpy.empty_like(state_variables) #To use later: x0=y[6] slope = y[7] Iext1 = y[8] Iext2 = y[9] K = y[10] Iext1 = self.Iext1 + local_coupling * y[0] c_pop1 = coupling[0, :] c_pop2 = coupling[1, :] # population 1 if_ydot0 = -y[0]**2 + 3.0*y[0] #self.a=1.0, self.b=3.0 else_ydot0 = slope - y[3] + 0.6*(y[2]-4.0)**2 ydot[0] = self.tau1 * (y[1] - y[2] + Iext1 + self.Kvf * c_pop1 + where(y[0] < 0.0, if_ydot0, else_ydot0) * y[0]) ydot[1] = self.tau1 * (self.yc - 5.0 * y[0] ** 2 - y[1]) #self.d=5 # energy if_ydot2 = - 0.1*y[2]**7 else_ydot2 = 0 if self.zmode == 'lin': fz = 4*(y[0] - x0) + where(y[2] < 0., if_ydot2, else_ydot2) elif self.zmode == 'sig': fz = 3.0 / (1.0 + numpy.exp(-10 * (y[0] + 0.5))) - x0 else: raise ValueError("zmode has to be either ""lin"" or ""sig"" for linear and sigmoidal fz(), respectively") ydot[2] = self.tau1 * ((fz - y[2] + K * c_pop1) / self.tau0) # population 2 ydot[3] = self.tau1 * (-y[4] + y[3] - y[3] ** 3 + Iext2 + 2*y[5] - 0.3 * (y[2] - 3.5) + self.Kf * c_pop2) if_ydot4 = 0 else_ydot4 = 6.0 * (y[3] + 0.25) #self.s = 6.0 ydot[4] = self.tau1 * ((-y[4] + where(y[3] < -0.25, if_ydot4, else_ydot4)) / self.tau2) # filter ydot[5] = self.tau1 * (-0.01 * (y[5] - 0.1 * y[0])) slope_eq, Iext2_eq = self.fun_slope_Iext2(y[2], y[5], self.pmode, self.slope, self.Iext2) # x0 ydot[6] = self.tau1 * (-y[6] + self.x0) # slope ydot[7] = 10 * self.tau1 * (-y[7] + slope_eq) #5* # Iext1 ydot[8] = self.tau1 * (-y[8] + self.Iext1) / self.tau0 # Iext2 ydot[9] = 5 * self.tau1*(-y[9] + Iext2_eq) # K ydot[10] = self.tau1*(-y[10] + self.K) / self.tau0 return ydot def jacobian(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): return None
class EpileptorDP(Model): 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]_). ->x0 parameters are shifted for the bifurcation to be at x0=1, where x0>1 is the supercritical region. ->there is a choice for linear or sigmoidal z dynamics (see [Proixetal_2014]_) ->some parameters change their names to be more similar to the equations. Equations and default parameters are taken from [Jirsaetal_2014]_. +------------------------------------------------------+ | Table 1 | +----------------------+-------------------------------+ | Parameter | Value | +======================+===============================+ | I_ext1 | 3.1 | +----------------------+-------------------------------+ | I_ext2 | 0.45 | +----------------------+-------------------------------+ | tau0 | 2857.0 | +----------------------+-------------------------------+ | x_0 | 0.0 | +----------------------+-------------------------------+ | 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, Proix 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. .. [Proixetal_2014] Proix, T., Bartolomei, F., Chauvel, P., Bernard, C., & Jirsa, V. K. (2014). Permitivity Coupling across Brain Regions Determines Seizure Recruitment in Partial Epilepsy. Journal of Neuroscience, 34(45), 15009–15021. htau1p://doi.org/10.1523/JNEUROSCI.1570-14.2014 .. automethod:: EpileptorDP.__init__ 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}} &=& yc - d x_{1}^{2} - y{1} \\ \dot{z} &=& \begin{cases} (f_z(x_{1}) - z-0.1 z^{7})/tau0 & \text{if } x<0 \\ (f_z(x_{1}) - z)/tau0 & \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 / \tau2 (-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\\ ( x_{2} - 0.6(z-4)^2 -slope ) x_{1} &\text{if }x_{1} \geq 0 \end{cases} .. math:: f_z(x_{1}) = \begin{cases} 4 * (x_{1} - x0) & \text{linear} \\ \frac{3}{1+e^{-10*(x_{1}+0.5)}} - x0 & \text{sigmoidal} \\ \end{cases} and: .. math:: f_{2}(x_{2}) = \begin{cases} 0 & \text{if } x_{2} <-0.25\\ s*(x_{2} + 0.25) & \text{if } x_{2} \geq -0.25 \end{cases} """ _ui_name = "EpileptorDP" ui_configurable_parameters = ["Iext1", "Iext2", "tau0", "x0", "slope"] zmode = arrays.FloatArray( label="zmode", default=numpy.array("lin"), doc="zmode = numpy.array(""lin"") for linear and numpy.array(""sig"") for sigmoidal z dynamics", order=-1) # a = arrays.FloatArray( # label="a", # default=numpy.array([1]), # doc="Coefficient of the cubic term in the first state variable", # order=-1) # b = arrays.FloatArray( # label="b", # default=numpy.array([3]), # doc="Coefficient of the squared term in the first state variabel", # order=-1) yc = arrays.FloatArray( label="yc", default=numpy.array([1]), doc="Additive coefficient for the second state variable", order=-1) # d = arrays.FloatArray( # label="d", # default=numpy.array([5]), # doc="Coefficient of the squared term in the second state variable", # order=-1) tau0 = arrays.FloatArray( label="r", range=basic.Range(lo=100.0, hi=5000, step=10), default=numpy.array([2857.0]), doc="Temporal scaling in the third state variable", order=4) # s = arrays.FloatArray( # label="s", # default=numpy.array([4]), # doc="Linear coefficient in the third state variable", # order=-1) x0 = arrays.FloatArray( label="x0", range=basic.Range(lo=-0.5, hi=1.5, step=0.1), default=numpy.array([0.0]), doc="Excitability parameter", order=3) Iext1 = arrays.FloatArray( label="Iext1", range=basic.Range(lo=1.5, hi=5.0, step=0.1), default=numpy.array([3.1]), doc="External input current to the first population", order=1) slope = arrays.FloatArray( label="slope", range=basic.Range(lo=-16.0, hi=6.0, step=0.1), default=numpy.array([0.]), doc="Linear coefficient in the first state variable", order=5) Iext2 = arrays.FloatArray( label="Iext2", range=basic.Range(lo=0.0, hi=1.0, step=0.05), default=numpy.array([0.45]), doc="External input current to the second population", order=2) tau2 = arrays.FloatArray( label="tau2", default=numpy.array([10]), doc="Temporal scaling coefficient in fifth state variable", order=-1) Kvf = arrays.FloatArray( label="K_vf", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=4.0, step=0.5), doc="Coupling scaling on a very fast time scale.", order=6) Kf = arrays.FloatArray( label="K_f", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=4.0, step=0.5), doc="Correspond to the coupling scaling on a fast time scale.", order=7) K = arrays.FloatArray( label="K", default=numpy.array([0.0]), range=basic.Range(lo=-4.0, hi=4.0, step=0.1), doc="Permitau1ivity coupling, that is from the fast time scale toward the slow time scale", order=8) tau1 = arrays.FloatArray( label="tau1", default=numpy.array([0.25]), range=basic.Range(lo=0.001, hi=10.0, step=0.001), doc="Time scaling of the whole system", order=9) state_variable_range = basic.Dict( label="State variable ranges [lo, hi]", default={"y0": numpy.array([-2., 2.]), "y1": numpy.array([-20., 2.]), "y2": numpy.array([2.0, 20.0]), "y3": numpy.array([-2., 0.]), "y4": numpy.array([0., 2.]), "y5": numpy.array([-1., 1.])}, doc="n/a", order=16 ) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["y0", "y1", "y2", "y3", "y4", "y5", "y3 - y0"], default=["y3 - y0", "y2"], select_multiple=True, doc="""default state variables to be monitored""", order=-1) state_variables = ["y0", "y1", "y2", "y3", "y4", "y5"] _nvar = 6 cvar = numpy.array([0, 3], dtype=numpy.int32) def dfun(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): r""" Computes the derivatives of the state variables of 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{y_{0}} &=& y_{1} - f_{1}(y_{0}, y_{3}) - y_{2} + I_{ext1} \\ \dot{y_{1}} &=& yc - d y_{0}^{2} - y{1} \\ \dot{y_{2}} &=& \begin{cases} (f_z(y_{0}) - y_{2}-0.1 y_{2}^{7})/tau0 & \text{if } y_{0}<0 \\ (f_z(y_{0}) - y_{2})/tau0 & \text{if } y_{0} \geq 0 \end{cases} \\ \dot{y_{3}} &=& -y_{4} + y_{3} - y_{3}^{3} + I_{ext2} + 0.002 y_{5} - 0.3 (y_{2}-3.5) \\ \dot{y_{4}} &=& 1 / \tau2 (-y_{4} + f_{2}(y_{3}))\\ \dot{y_{5}} &=& -0.01 (y_{5} - 0.1 y_{0} ) where: .. math:: f_{1}(y_{0}, y_{3}) = \begin{cases} a y_{0}^{3} - by_{0}^2 & \text{if } y_{0} <0\\ (y_{3} - 0.6(y_{2}-4)^2 slope)y_{0} &\text{if }y_{0} \geq 0 \end{cases} .. math:: f_z(y_{0}) = \begin{cases} 4 * (y_{0} - x0) & \text{linear} \\ \frac{3}{1+e^{-10*(y_{0}+0.5)}} - x0 & \text{sigmoidal} \\ \end{cases} and: .. math:: f_{2}(y_{3}) = \begin{cases} 0 & \text{if } y_{3} <-0.25\\ s*(y_{3} + 0.25) & \text{if } y_{3} \geq -0.25 \end{cases} """ y = state_variables ydot = numpy.empty_like(state_variables) Iext1 = self.Iext1 + local_coupling * y[0] c_pop1 = coupling[0, :] c_pop2 = coupling[1, :] #TVB Epileptor in commented lines below # population 1 #if_ydot0 = - self.a * y[0] ** 2 + self.b * y[0] if_ydot0 = -y[0]**2 + 3.0*y[0] #self.a=1.0, self.b=3.0 #else_ydot0 = self.slope - y[3] + 0.6 * (y[2] - 4.0) ** 2 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[0] = self.tau1 * (y[1] - y[2] + Iext1 + self.Kvf * c_pop1 + where(y[0] < 0.0, if_ydot0, else_ydot0) * y[0]) # ydot[1] = self.tt * (self.c - self.d * y[0] ** 2 - y[1]) ydot[1] = self.tau1 * (self.yc - 5.0 * y[0] ** 2 - y[1]) #self.d=5 # energy #if_ydot2 = - 0.1 * y[2] ** 7 if_ydot2 = - 0.1 * y[2] ** 7 #else_ydot2 = 0 else_ydot2 = 0 if self.zmode == 'lin': # self.r * (4 * (y[0] - self.x0) - y[2] + where(y[2] < 0., if_ydot2, else_ydot2) fz = 4*(y[0] - self.x0) + where(y[2] < 0., if_ydot2, else_ydot2) elif self.zmode == 'sig': fz = 3.0 / (1.0 + numpy.exp(-10 * (y[0] + 0.5))) - self.x0 else: raise ValueError("zmode has to be either ""lin"" or ""sig"" for linear and sigmoidal fz(), " + "respectively") # ydot[2] = self.tt * ( ...+ self.Ks * c_pop1)) ydot[2] = self.tau1 * ((fz - y[2] + self.K * c_pop1) / self.tau0) # population 2 # ydot[3] = self.tt * (-y[4] + y[3] - y[3] ** 3 + self.Iext2 + 2 * y[5] - 0.3 * (y[2] - 3.5) + self.Kf * c_pop2) ydot[3] = self.tau1 * (-y[4] + y[3] - y[3] ** 3 + self.Iext2 + 2 * y[5] - 0.3 * (y[2] - 3.5) + self.Kf * c_pop2) # if_ydot4 = 0 if_ydot4 = 0 # else_ydot4 = self.aa * (y[3] + 0.25) else_ydot4 = 6.0 * (y[3] + 0.25) #self.s = 6.0 # ydot[4] = self.tt * ((-y[4] + where(y[3] < -0.25, if_ydot4, else_ydot4)) / self.tau) ydot[4] = self.tau1 * ((-y[4] + where(y[3] < -0.25, if_ydot4, else_ydot4)) / self.tau2) # filter #ydot[5] = self.tt * (-0.01 * (y[5] - 0.1 * y[0])) ydot[5] = self.tau1*(-0.01 * (y[5] - 0.1 * y[0])) return ydot def jacobian(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): return None
class HindmarshRose(models.Model): """ The Hindmarsh-Rose model is a mathematically simple model for repetitive bursting. .. [HR_1984] Hindmarsh, J. L., and Rose, R. M., *A model of neuronal bursting using three coupled first order differential equations*, Proceedings of the Royal society of London. Series B. Biological sciences 221: 87, 1984. The models (:math:`x`, :math:`y`) phase-plane, including a representation of the vector field as well as its nullclines, using default parameters, can be seen below: .. _phase-plane-HMR: .. figure :: img/HindmarshRose_01_mode_0_pplane.svg :alt: Hindmarsh-Rose phase plane (x, y) The (:math:`x`, :math:`y`) phase-plane for the Hindmarsh-Rose model. .. #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:: HindmarshRose.__init__ .. automethod:: HindmarshRose.dfun """ _ui_name = "Hindmarsh-Rose" ui_configurable_parameters = ['r', 'a', 'b', 'c', 'd', 's', 'x_1'] #Define traited attributes for this model, these represent possible kwargs. r = arrays.FloatArray( label=":math:`r`", default=numpy.array([0.001]), range=basic.Range(lo=0.0, hi=1.0, step=0.001), doc="""Adaptation parameter, governs time-scale of the state variable :math:`z`.""", order=1) a = arrays.FloatArray( label=":math:`a`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=1.0, step=0.01), doc="""Dimensionless parameter, governs x-nullcline""", order=2) b = arrays.FloatArray( label=":math:`b`", default=numpy.array([3.0]), range=basic.Range(lo=0.0, hi=3.0, step=0.01), doc="""Dimensionless parameter, governs x-nullcline""", order=3) c = arrays.FloatArray( label=":math:`c`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=1.0, step=0.01), doc="""Dimensionless parameter, governs y-nullcline""", order=4) d = arrays.FloatArray( label=":math:`d`", default=numpy.array([5.0]), range=basic.Range(lo=0.0, hi=5.0, step=0.01), doc="""Dimensionless parameter, governs y-nullcline""", order=5) s = arrays.FloatArray(label=":math:`s`", default=numpy.array([1.0]), range=basic.Range(lo=0.0, hi=1.0, step=0.01), doc="""Adaptation parameter, governs feedback""", order=6) x_1 = arrays.FloatArray(label=":math:`x_{1}`", default=numpy.array([-1.6]), range=basic.Range(lo=-1.6, hi=1.0, step=0.01), doc="""Governs leftmost equilibrium point of x""", order=7) #Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "x": numpy.array([-4.0, 4.0]), "y": numpy.array([-60.0, 20.0]), "z": numpy.array([-2.0, 18.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.""", order=8) variables_of_interest = arrays.IntegerArray( label="Variables watched by Monitors", range=basic.Range(lo=0, hi=3, 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:`x = 0`, :math:`y = 1`,and :math:`z = 2`.""", order=9) # 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 HindmarshRose model's traited attributes, any provided as keywords will overide their traited default. """ LOG.info('%s: initing...' % str(self)) super(HindmarshRose, self).__init__(**kwargs) self._state_variables = ["x", "y", "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): """ As in the FitzHugh-Nagumo model ([FH_1961]_), :math:`x` and :math:`y` signify the membrane potential and recovery variable respectively. Unlike FitzHugh-Nagumo model, the recovery variable :math:`y` is quadratic, modelling subthreshold inward current. The third state-variable, :math:`z` signifies a slow outward current which leads to adaptation ([HR_1984]_): .. math:: \\dot{x} &= y - a \\, x^3 + b \\, x^2 - z + I \\\\ \\dot{y} &= c - d \\, x^2 - y \\\\ \\dot{z} &= r \\, ( s \\, (x - x_1) - z ) where external currents :math:`I` provide the entry point for local and long-range connectivity. Default parameters are set as per Figure 6 of [HR_1984]_ so that the model shows repetitive bursting when :math:`I=2`. """ x = state_variables[0, :] y = state_variables[1, :] z = state_variables[2, :] c_0 = coupling[0, :] dx = y - self.a * x**3 + self.b * x**2 - z + c_0 + local_coupling * x dy = self.c - self.d * x**2 - y dz = self.r * (self.s * (x - self.x_1) - z) derivative = numpy.array([dx, dy, dz]) return derivative
class ProjectionMatrix(core.Type): """ Provides the mechanisms necessary to access OpenMEEG for the calculation of EEG and MEG projection matrices, ie matrices that map source activity to sensor activity. It is initialised with datatypes of TVB and ultimately returns the projection matrix as a Numpy ndarray. """ brain_skull = surfaces_module.BrainSkull( label="Boundary between skull and skin domains", default=None, required=True, doc="""A ... surface on which ... including ...""") skull_skin = surfaces_module.SkullSkin( label="surface and auxillary for surface sim", default=None, required=True, doc="""A ... surface on which ... including ...""") skin_air = surfaces_module.SkinAir( label="surface and auxillary for surface sim", default=None, required=True, doc="""A ... surface on which ... including ...""") conductances = basic.Dict( label="Domain conductances", default={ 'air': 0.0, 'skin': 1.0, 'skull': 0.01, 'brain': 1.0 }, required=True, doc="""A dictionary representing the conductances of ...""") sources = surfaces_module.Cortex( label="surface and auxillary for surface sim", default=None, required=True, doc="""A cortical surface on which ... including ...""") sensors = sensors_module.Sensors( label="surface and auxillary for surface sim", default=None, required=False, doc="""A cortical surface on which ... including ... If left as None then EEG is assumed and skin_air is expected to already has sensors associated""") def __init__(self, **kwargs): """ Initialse traited attributes and attributes that will hold OpenMEEG objects. """ super(ProjectionMatrix, self).__init__(**kwargs) LOG.debug(str(kwargs)) #OpenMEEG attributes self.om_head = None self.om_sources = None self.om_sensors = None self.om_head2sensor = None self.om_inverse_head = None self.om_source_matrix = None self.om_source2sensor = None #For MEG, not used for EEG def configure(self): """ Converts TVB objects into a for accessible to OpenMEEG, then uses the OpenMEEG library to calculate the intermediate matrices needed in obtaining the final projection matrix. """ super(ProjectionMatrix, self).configure() if self.sensors is None: self.sensors = self.skin_air.sensors if isinstance(self.sensors, sensors_module.SensorsEEG): self.skin_air.sensors = self.sensors self.skin_air.sensor_locations = self.sensors.sensors_to_surface( self.skin_air) # Create OpenMEEG objects from TVB objects. self.om_head = self.create_om_head() self.om_sources = self.create_om_sources() self.om_sensors = self.create_om_sensors() # Calculate based on type of sources if isinstance(self.sources, surfaces_module.Cortex): self.om_source_matrix = self.surface_source() #NOTE: ~1 hr elif isinstance(self.sources, connectivity_module.Connectivity): self.om_source_matrix = self.dipole_source() # Calculate based on type of sensors if isinstance(self.sensors, sensors_module.SensorsEEG): self.om_head2sensor = self.head2eeg() elif isinstance(self.sensors, sensors_module.SensorsMEG): self.om_head2sensor = self.head2meg() if isinstance(self.sources, surfaces_module.Cortex): self.om_source2sensor = self.surf2meg() elif isinstance(self.sources, connectivity_module.Connectivity): self.om_source2sensor = self.dip2meg() #NOTE: ~1 hr self.om_inverse_head = self.inverse_head(inv_head_mat_file="hminv_uid") def __call__(self): """ Having configured the ProjectionMatrix instance, that is having run the configure() method or otherwise provided the intermedite OpenMEEG (om_*) attributes, the oblect can be called as a function -- returning a projection matrix as a Numpy array. """ #Check source type and sensor type, then call appripriate methods to #generate intermediate data, cascading all the way back to geometry #calculation if it wasn't already done. #Then return a projection matrix... # NOTE: returned projection_matrix is a numpy.ndarray if isinstance(self.sensors, sensors_module.SensorsEEG): projection_matrix = self.eeg_gain() elif isinstance(self.sensors, sensors_module.SensorsMEG): projection_matrix = self.meg_gain() return projection_matrix ##------------------------------------------------------------------------## ##--------------- Methods for creating openmeeg objects ------------------## ##------------------------------------------------------------------------## def create_om_head(self): #TODO: Prob. need to make file names specifiable """ Generates 5 files:: skull_skin.tri skin_air.tri brain_skull.tri head_model.geom head_model.cond Containing the specification of a head in a form that can be read by OpenMEEG, then creates and returns an OpenMEEG Geometry object containing this information. """ surface_files = [] surface_files.append(self._tvb_surface_to_tri("skull_skin.tri")) surface_files.append(self._tvb_surface_to_tri("brain_skull.tri")) surface_files.append(self._tvb_surface_to_tri("skin_air.tri")) geometry_file = self._write_head_geometry(surface_files, "head_model.geom") conductances_file = self._write_conductances("head_model.cond") LOG.info("Creating OpenMEEG Geometry object for the head...") om_head = om.Geometry() om_head.read(geometry_file, conductances_file) #om_head.selfCheck() #Didn't catch bad order... LOG.info("OpenMEEG Geometry object for the head successfully created.") return om_head def create_om_sources( self): #TODO: Prob. should make file names specifiable """ Take a TVB Connectivity or Cortex object and return an OpenMEEG object that specifies sources, a Matrix object for region level sources or a Mesh object for a cortical surface source. """ if isinstance(self.sources, connectivity_module.Connectivity): sources_file = self._tvb_connectivity_to_txt("sources.txt") om_sources = om.Matrix() elif isinstance(self.sources, surfaces_module.Cortex): sources_file = self._tvb_surface_to_tri("sources.tri") om_sources = om.Mesh() else: LOG.error("sources must be either a Connectivity or Cortex.") om_sources.load(sources_file) return om_sources def create_om_sensors(self, file_name=None): """ Take a TVB Sensors object and return an OpenMEEG Sensors object. """ if isinstance(self.sensors, sensors_module.SensorsEEG): file_name = file_name or "eeg_sensors.txt" sensors_file = self._tvb_eeg_sensors_to_txt(file_name) elif isinstance(self.sensors, sensors_module.SensorsMEG): file_name = file_name or "meg_sensors.squid" sensors_file = self._tvb_meg_sensors_to_squid(file_name) else: LOG.error("sensors should be either SensorsEEG or SensorsMEG") LOG.info("Wrote sensors to temporary file: %s" % str(file_name)) om_sensors = om.Sensors() om_sensors.load(sensors_file) return om_sensors ##------------------------------------------------------------------------## ##--------- Methods for calling openmeeg methods, with logging. ----------## ##------------------------------------------------------------------------## def surf2meg(self): """ Create a matrix that can be used to map an OpenMEEG surface source to an OpenMEEG MEG Sensors object. NOTE: This source to sensor mapping is not required for EEG. """ LOG.info("Computing DipSource2MEGMat...") surf2meg_mat = om.SurfSource2MEGMat(self.om_sources, self.om_sensors) LOG.info("surf2meg: %d x %d" % (surf2meg_mat.nlin(), surf2meg_mat.ncol())) return surf2meg_mat def dip2meg(self): """ Create an OpenMEEG Matrix that can be used to map OpenMEEG dipole sources to an OpenMEEG MEG Sensors object. NOTE: This source to sensor mapping is not required for EEG. """ LOG.info("Computing DipSource2MEGMat...") dip2meg_mat = om.DipSource2MEGMat(self.om_sources, self.om_sensors) LOG.info("dip2meg: %d x %d" % (dip2meg_mat.nlin(), dip2meg_mat.ncol())) return dip2meg_mat def head2eeg(self): """ Call OpenMEEG's Head2EEGMat method to calculate the head to EEG sensor matrix. """ LOG.info("Computing Head2EEGMat...") h2s_mat = om.Head2EEGMat(self.om_head, self.om_sensors) LOG.info("head2eeg: %d x %d" % (h2s_mat.nlin(), h2s_mat.ncol())) return h2s_mat def head2meg(self): """ Call OpenMEEG's Head2MEGMat method to calculate the head to MEG sensor matrix. """ LOG.info("Computing Head2MEGMat...") h2s_mat = om.Head2MEGMat(self.om_head, self.om_sensors) LOG.info("head2meg: %d x %d" % (h2s_mat.nlin(), h2s_mat.ncol())) return h2s_mat def surface_source(self, gauss_order=3, surf_source_file=None): """ Call OpenMEEG's SurfSourceMat method to calculate a surface source matrix. Optionaly saving the matrix for later use. """ LOG.info("Computing SurfSourceMat...") ssm = om.SurfSourceMat(self.om_head, self.om_sources, gauss_order) LOG.info("surface_source_mat: %d x %d" % (ssm.nlin(), ssm.ncol())) if surf_source_file is not None: LOG.info("Saving surface_source matrix as %s..." % surf_source_file) ssm.save( os.path.join(OM_STORAGE_DIR, surf_source_file + OM_SAVE_SUFFIX)) #~3GB return ssm def dipole_source(self, gauss_order=3, use_adaptive_integration=True, dip_source_file=None): """ Call OpenMEEG's DipSourceMat method to calculate a dipole source matrix. Optionaly saving the matrix for later use. """ LOG.info("Computing DipSourceMat...") dsm = om.DipSourceMat(self.om_head, self.om_sources, gauss_order, use_adaptive_integration) LOG.info("dipole_source_mat: %d x %d" % (dsm.nlin(), dsm.ncol())) if dip_source_file is not None: LOG.info("Saving dipole_source matrix as %s..." % dip_source_file) dsm.save( os.path.join(OM_STORAGE_DIR, dip_source_file + OM_SAVE_SUFFIX)) return dsm def inverse_head(self, gauss_order=3, inv_head_mat_file=None): """ Call OpenMEEG's HeadMat method to calculate a head matrix. The inverse method of the head matrix is subsequently called to invert the matrix. Optionaly saving the inverted matrix for later use. Runtime ~8 hours, mostly in martix inverse as I just use a stock ATLAS install which doesn't appear to be multithreaded (custom building ATLAS should sort this)... Under Windows it should use MKL, not sure for Mac For reg13+potato surfaces, saved file size: hminv ~ 5GB, ssm ~ 3GB. """ LOG.info("Computing HeadMat...") head_matrix = om.HeadMat(self.om_head, gauss_order) LOG.info("head_matrix: %d x %d" % (head_matrix.nlin(), head_matrix.ncol())) LOG.info("Inverting HeadMat...") hminv = head_matrix.inverse() LOG.info("inverse head_matrix: %d x %d" % (hminv.nlin(), hminv.ncol())) if inv_head_mat_file is not None: LOG.info("Saving inverse_head matrix as %s..." % inv_head_mat_file) hminv.save( os.path.join(OM_STORAGE_DIR, inv_head_mat_file + OM_SAVE_SUFFIX)) #~5GB return hminv def eeg_gain(self, eeg_file=None): """ Call OpenMEEG's GainEEG method to calculate the final projection matrix. Optionaly saving the matrix for later use. The OpenMEEG matrix is converted to a Numpy array before return. """ LOG.info("Computing GainEEG...") eeg_gain = om.GainEEG(self.om_inverse_head, self.om_source_matrix, self.om_head2sensor) LOG.info("eeg_gain: %d x %d" % (eeg_gain.nlin(), eeg_gain.ncol())) if eeg_file is not None: LOG.info("Saving eeg_gain as %s..." % eeg_file) eeg_gain.save( os.path.join(OM_STORAGE_DIR, eeg_file + OM_SAVE_SUFFIX)) return om.asarray(eeg_gain) def meg_gain(self, meg_file=None): """ Call OpenMEEG's GainMEG method to calculate the final projection matrix. Optionaly saving the matrix for later use. The OpenMEEG matrix is converted to a Numpy array before return. """ LOG.info("Computing GainMEG...") meg_gain = om.GainMEG(self.om_inverse_head, self.om_source_matrix, self.om_head2sensor, self.om_source2sensor) LOG.info("meg_gain: %d x %d" % (meg_gain.nlin(), meg_gain.ncol())) if meg_file is not None: LOG.info("Saving meg_gain as %s..." % meg_file) meg_gain.save( os.path.join(OM_STORAGE_DIR, meg_file + OM_SAVE_SUFFIX)) return om.asarray(meg_gain) ##------------------------------------------------------------------------## ##------- Methods for writting temporary files loaded by openmeeg --------## ##------------------------------------------------------------------------## def _tvb_meg_sensors_to_squid(self, sensors_file_name): """ Write a tvb meg_sensor datatype to a .squid file, so that OpenMEEG can read it and compute the projection matrix for MEG... """ sensors_file_path = os.path.join(OM_STORAGE_DIR, sensors_file_name) meg_sensors = numpy.hstack( (self.sensors.locations, self.sensors.orientations)) numpy.savetxt(sensors_file_path, meg_sensors) return sensors_file_path def _tvb_connectivity_to_txt(self, dipoles_file_name): """ Write position and orientation information from a TVB connectivity object to a text file that can be read as source dipoles by OpenMEEG. NOTE: Region level simulations lack sufficient detail of source orientation, etc, to provide anything but superficial relevance. It's probably better to do a mapping of region level simulations to a surface and then perform the EEG projection from the mapped data... """ NotImplementedError def _tvb_surface_to_tri(self, surface_file_name): """ Write a tvb surface datatype to .tri format, so that OpenMEEG can read it and compute projection matrices for EEG/MEG/... """ surface_file_path = os.path.join(OM_STORAGE_DIR, surface_file_name) #TODO: check file doesn't already exist LOG.info("Writing TVB surface to .tri file: %s" % surface_file_path) file_handle = file(surface_file_path, "a") file_handle.write("- %d \n" % self.sources.number_of_vertices) verts_norms = numpy.hstack( (self.sources.vertices, self.sources.vertex_normals)) numpy.savetxt(file_handle, verts_norms) tri_str = "- " + (3 * (str(self.sources.number_of_triangles) + " ")) + "\n" file_handle.write(tri_str) numpy.savetxt(file_handle, self.sources.triangles, fmt="%d") file_handle.close() LOG.info("%s written successfully." % surface_file_name) return surface_file_path def _tvb_eeg_sensors_to_txt(self, sensors_file_name): """ Write a tvb eeg_sensor datatype (after mapping to the head surface to be used) to a .txt file, so that OpenMEEG can read it and compute leadfield/projection/forward_solution matrices for EEG... """ sensors_file_path = os.path.join(OM_STORAGE_DIR, sensors_file_name) LOG.info("Writing TVB sensors to .txt file: %s" % sensors_file_path) numpy.savetxt(sensors_file_path, self.skin_air.sensor_locations) LOG.info("%s written successfully." % sensors_file_name) return sensors_file_path #TODO: enable specifying ?or determining? domain surface relationships... def _write_head_geometry(self, boundary_file_names, geom_file_name): """ Write a geometry file that is read in by OpenMEEG, this file specifies the files containng the boundary surfaces and there relationship to the domains that comprise the head. NOTE: Currently the list of files is expected to be in a specific order, namely:: skull_skin brain_skull skin_air which is reflected in the static setting of domains. Should be generalised. """ geom_file_path = os.path.join(OM_STORAGE_DIR, geom_file_name) #TODO: Check that the file doesn't already exist. LOG.info("Writing head geometry file: %s" % geom_file_path) file_handle = file(geom_file_path, "a") file_handle.write("# Domain Description 1.0\n\n") file_handle.write("Interfaces %d Mesh\n\n" % len(boundary_file_names)) for file_name in boundary_file_names: file_handle.write("%s\n" % file_name) file_handle.write("\nDomains %d\n\n" % (len(boundary_file_names) + 1)) file_handle.write("Domain Scalp %s %s\n" % (1, -3)) file_handle.write("Domain Brain %s %s\n" % ("-2", "shared")) file_handle.write("Domain Air %s\n" % 3) file_handle.write("Domain Skull %s %s\n" % (2, -1)) file_handle.close() LOG.info("%s written successfully." % geom_file_path) return geom_file_path def _write_conductances(self, cond_file_name): """ Write a conductance file that is read in by OpenMEEG, this file specifies the conductance of each of the domains making up the head. NOTE: Vaules are restricted to have 2 decimal places, ie #.##, setting values of the form 0.00# will result in 0.01 or 0.00, for numbers greater or less than ~0.00499999999999999967, respecitvely... """ cond_file_path = os.path.join(OM_STORAGE_DIR, cond_file_name) #TODO: Check that the file doesn't already exist. LOG.info("Writing head conductance file: %s" % cond_file_path) file_handle = file(cond_file_path, "a") file_handle.write("# Properties Description 1.0 (Conductivities)\n\n") file_handle.write("Air %4.2f\n" % self.conductances["air"]) file_handle.write("Scalp %4.2f\n" % self.conductances["skin"]) file_handle.write("Brain %4.2f\n" % self.conductances["brain"]) file_handle.write("Skull %4.2f\n" % self.conductances["skull"]) file_handle.close() LOG.info("%s written successfully." % cond_file_path) return cond_file_path #TODO: Either make these utility functions or have them load directly into # the appropriate attribute... ##------------------------------------------------------------------------## ##---- Methods for loading precomputed matrices into openmeeg objects ----## ##------------------------------------------------------------------------## def _load_om_inverse_head_mat(self, file_name): """ Load a previously stored inverse head matrix into an OpenMEEG SymMatrix object. """ inverse_head_martix = om.SymMatrix() inverse_head_martix.load(file_name) return inverse_head_martix def _load_om_source_mat(self, file_name): """ Load a previously stored source matrix into an OpenMEEG Matrix object. """ source_matrix = om.Matrix() source_matrix.load(file_name) return source_matrix
class DoubleExponential(HRFKernelEquation): """ A difference of two exponential functions to define a kernel for the bold monitor. **Parameters** : * :math:`\\tau_1`: Time constant of the second exponential function [s] * :math:`\\tau_2`: Time constant of the first exponential function [s]. * :math:`f_1` : Frequency of the first sine function [Hz]. * :math:`f_2` : Frequency of the second sine function [Hz]. * :math:`amp_1`: Amplitude of the first exponential function. * :math:`amp_2`: Amplitude of the second exponential function. * :math:`a` : Amplitude factor after normalization. **Reference**: .. [P_2000] Alex Polonsky, Randolph Blake, Jochen Braun and David J. Heeger (2000). Neuronal activity in human primary visual cortex correlates with perception during binocular rivalry. Nature Neuroscience 3: 1153-1159 """ _ui_name = "HRF kernel: Difference of Exponentials" equation = basic.String( label="Double Exponential Equation", default= "((amp_1 * exp(-var/tau_1) * sin(2.*pi*f_1*var)) - (amp_2 * exp(-var/ tau_2) * sin(2.*pi*f_2*var)))", locked=True, doc=""":math:`h(var) = amp_1\\exp(\\frac{-var}{\tau_1}) \\sin(2\\cdot\\pi f_1 \\cdot var) - amp_2\\cdot \\exp(-\\frac{var} {\\tau_2})*\\sin(2\\pi f_2 var)`.""") parameters = basic.Dict(label="Double Exponential Parameters", default={ "tau_1": 7.22, "f_1": 0.03, "amp_1": 0.1, "tau_2": 7.4, "f_2": 0.12, "amp_2": 0.1, "a": 0.1, "pi": numpy.pi }) def _get_pattern(self): """ Return a discrete representation of the equation. """ return self._pattern def _set_pattern(self, var): """ Generate a discrete representation of the equation for the space represented by ``var``. """ self._pattern = numexpr.evaluate(self.equation, global_dict=self.parameters) self._pattern /= max(self._pattern) self._pattern *= self.parameters["a"] pattern = property(fget=_get_pattern, fset=_set_pattern)
class Generic2dOscillator(models.Model): """ The Generic2dOscillator model is a generic dynamic system with two state variables. The dynamic equations of this model are composed of two ordinary differential equations comprising two nullclines. The first nullcline is a cubic function as it is found in most neuron and population models; the second nullcline is arbitrarily configurable as a polynomial function up to second order. The manipulation of the latter nullcline's parameters allows to generate a wide range of different behaviors. See: .. [FH_1961] FitzHugh, R., *Impulses and physiological states in theoretical models of nerve membrane*, Biophysical Journal 1: 445, 1961. .. [Nagumo_1962] Nagumo et.al, *An Active Pulse Transmission Line Simulating Nerve Axon*, Proceedings of the IRE 50: 2061, 1962. .. [SJ_2011] Stefanescu, R., Jirsa, V.K. *Reduced representations of heterogeneous mixed neural networks with synaptic coupling*. Physical Review E, 83, 2011. .. [SJ_2010] Jirsa VK, Stefanescu R. *Neural population modes capture biologically realistic large-scale network dynamics*. Bulletin of Mathematical Biology, 2010. .. [SJ_2008_a] Stefanescu, R., Jirsa, V.K. *A low dimensional description of globally coupled heterogeneous neural networks of excitatory and inhibitory neurons*. PLoS Computational Biology, 4(11), 2008). The model's (:math:`V`, :math:`W`) time series and phase-plane its nullclines can be seen in the figure below. The model with its default parameters exhibits FitzHugh-Nagumo like dynamics. --------------------------- | EXCITABLE CONFIGURATION | --------------------------- |Parameter | Value | ----------------------------- | a | -2.0 | | b | -10.0 | | c | 0.0 | | d | 0.1 | | I | 0.0 | ----------------------------- |* limit cylce if a = 2.0 | ----------------------------- --------------------------- | BISTABLE CONFIGURATION | --------------------------- |Parameter | Value | ----------------------------- | a | 1.0 | | b | 0.0 | | c | -5.0 | | d | 0.1 | | I | 0.0 | ----------------------------- |* monostable regime: | |* fixed point if Iext=-2.0 | |* limit cycle if Iext=-1.0 | ----------------------------- --------------------------- | EXCITABLE CONFIGURATION | (similar to Morris-Lecar) --------------------------- |Parameter | Value | ----------------------------- | a | 0.5 | | b | 0.6 | | c | -4.0 | | d | 0.1 | | I | 0.0 | ----------------------------- |* excitable regime if b=0.6| |* oscillatory if b=0.4 | ----------------------------- --------------------------- | SanzLeonetAl 2013 | --------------------------- |Parameter | Value | ----------------------------- | a | - 0.5 | | b | -15.0 | | c | 0.0 | | d | 0.02 | | I | 0.0 | ----------------------------- |* excitable regime if | |* intrinsic frequency is | | approx 10 Hz | ----------------------------- .. 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. """ _ui_name = "Generic 2d Oscillator" ui_configurable_parameters = ['tau', 'a', 'b', 'c', 'd', 'I'] #Define traited attributes for this model, these represent possible kwargs. tau = arrays.FloatArray( label = r":math:`\tau`", default = numpy.array([1.0]), range = basic.Range(lo = 0.00001, hi = 5.0, step = 0.01), doc = """A time-scale hierarchy can be introduced for the state variables :math:`V` and :math:`W`. Default parameter is 1, which means no time-scale hierarchy.""", order = 1) I = arrays.FloatArray( label = ":math:`I_{ext}`", default = numpy.array([0.0]), range = basic.Range(lo = -2.0, hi = 2.0, step = 0.01), doc = """Baseline shift of the cubic nullcline""", order = 2) a = arrays.FloatArray( label = ":math:`a`", default = numpy.array([-2.0]), range = basic.Range(lo = -5.0, hi = 5.0, step = 0.01), doc = """Vertical shift of the configurable nullcline""", order = 3) b = arrays.FloatArray( label = ":math:`b`", default = numpy.array([-10.0]), range = basic.Range(lo = -20.0, hi = 15.0, step = 0.01), doc = """Linear slope of the configurable nullcline""", order = 4) c = arrays.FloatArray( label = ":math:`c`", default = numpy.array([0.0]), range = basic.Range(lo = -10.0, hi = 10.0, step = 0.01), doc = """Parabolic term of the configurable nullcline""", order = 5) d = arrays.FloatArray( label = ":math:`d`", default = numpy.array([0.1]), range = basic.Range(lo = 0.0001, hi = 1.0, step = 0.0001), doc = """Temporal scale factor.""", order = -1) e = arrays.FloatArray( label = ":math:`e`", default = numpy.array([3.0]), range = basic.Range(lo = -5.0, hi = 5.0, step = 0.0001), doc = """Coefficient of the quadratic term of the cubic nullcline.""", order = -1) f = arrays.FloatArray( label = ":math:`f`", default = numpy.array([1.0]), range = basic.Range(lo = -5.0, hi = 5.0, step = 0.0001), doc = """Coefficient of the cubic term of the cubic nullcline.""", order = -1) alpha = arrays.FloatArray( label = ":math:`\alpha`", default = numpy.array([1.0]), range = basic.Range(lo = -5.0, hi = 5.0, step = 0.0001), doc = """Constant parameter to scale the rate of feedback from the slow variable to the fast variable.""", order = -1) beta = arrays.FloatArray( label = ":math:`\beta`", default = numpy.array([1.0]), range = basic.Range(lo = -5.0, hi = 5.0, step = 0.0001), doc = """Constant parameter to scale the rate of feedback from the slow variable to itself""", order = -1) #Informational attribute, used for phase-plane and initial() state_variable_range = basic.Dict( label = "State Variable ranges [lo, hi]", default = {"V": numpy.array([-2.0, 4.0]), "W": numpy.array([-6.0, 6.0])}, doc = """The values for each state-variable should be set to encompass the expected dynamic range of that state-variable for the current parameters, it is used as a mechanism for bounding random initial conditions when the simulation isn't started from an explicit history, it is also provides the default range of phase-plane plots.""", order = 6) variables_of_interest = basic.Enumerate( label = "Variables watched by Monitors", options = ["V", "W"], default = ["V",], select_multiple = True, doc = """This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`V = 0`and :math:`W = 1`.""", order = 7) def __init__(self, **kwargs): """ Intialise Model """ LOG.info("%s: initing..." % str(self)) super(Generic2dOscillator, self).__init__(**kwargs) self._nvar = 2 # long range coupling variables self.cvar = numpy.array([0], dtype=numpy.int32) LOG.debug("%s: inited." % repr(self)) def 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} &= \tau (\alpha W - V^3 +3 V^2 + I) \\ \dot{W} &= (a\, + b\, V + c\, V^2 - \, beta W) / \tau where external currents :math:`I` provide the entry point for local, long-range connectivity and stimulation. """ V = state_variables[0, :] W = state_variables[1, :] #[State_variables, nodes] c_0 = coupling[0, :] tau = self.tau I = self.I a = self.a b = self.b c = self.c d = self.d e = self.e f = self.f beta = self.beta alpha = self.alpha lc_0 = local_coupling*V ## numexpr dV = ev('d * tau * (alpha * W - f * V**3 + e * V**2 + I + c_0 + lc_0)') dW = ev('d * (a + b * V + c * V**2 - beta * W) / tau') self.derivative = numpy.array([dV, dW]) return self.derivative
class EpileptorDP2D(Model): r""" The Epileptor 2D is a composite neural mass model of two dimensions which has been crafted to model the phenomenology of epileptic seizures in a reduced form. This model is used for Linear Stability Analysis (see [Proixetal_2014]_). ->x0 parameters are shifted for the bifurcation to be at x0=1, where x0>1 is the supercritical region. ->there is a choice for linear or sigmoidal z dynamics (see [Proixetal_2014]_) ->some parameters change their names to be more similar to the equations. Equations and default parameters are taken from [Jirsaetal_2014]_. +------------------------------------------------------+ | Table 1 | +----------------------+-------------------------------+ | Parameter | Value | +======================+===============================+ | I_ext1 | 3.1 | +----------------------+-------------------------------+ | tau0 | 2857.0 | +----------------------+-------------------------------+ | x_0 | 0.0 | +----------------------+-------------------------------+ | 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, Proix 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. .. [Proixetal_2014] Proix, T., Bartolomei, F., Chauvel, P., Bernard, C., & Jirsa, V. K. (2014). Permitau1ivity Coupling across Brain Regions Determines Seizure Recruitment in Partial Epilepsy. Journal of Neuroscience, 34(45), 15009–15021. htau1p://doi.org/10.1523/JNEUROSCI.1570-14.2014 .. automethod:: EpileptorDP.__init__ Variables of interest to be used by monitors: -y[0] + y[3] .. math:: \dot{x_{1}} &=& yc - f_{1}(x_{1},z) - z + I_{ext1} \\ \dot{z} &=& \begin{cases} (f_z(x_{1}) - z-0.1 z^{7})/tau0 & \text{if } x<5/3 \\ (f_z(x_{1}) - z)/tau0 & \text{if } x \geq 5/3 \end{cases} \\ where: .. math:: f_{1}(x_{1},z) = \begin{cases} a ( x_{1} -5/3 )^{3} - b ( x_{1} -5/3 )^2 & \text{if } x_{1} <5/3\\ ( 5*( x_{1} -5/3 ) - 0.6(z-4)^2 -slope) ( x_{1} - 5/3 ) &\text{if }x_{1} \geq 5/3 \end{cases} and: .. math:: f_z(x_{1}) = \begin{cases} 4 * (x_{1} - r_{x0}*x0 + x0_{cr}) & \text{linear} \\ \frac{3}{1+e^{-10*(x_{1}-7/6)}} - r_{x0}*x0 + x0_{cr} & \text{sigmoidal} \\ \end{cases} """ _ui_name = "EpileptorDP2D" ui_configurable_parameters = ["Iext1", "tau0", "x0", "slope"] zmode = arrays.FloatArray( label="zmode", default=numpy.array("lin"), doc="zmode = numpy.array(""lin"") for linear and numpy.array(""sig"") for sigmoidal z dynamics", order=-1) # a = arrays.FloatArray( # label="a", # default=numpy.array([1]), # doc="Coefficient of the cubic term in the first state variable", # order=-1) # b = arrays.FloatArray( # label="b", # default=numpy.array([3]), # doc="Coefficient of the squared term in the first state variabel", # order=-1) yc = arrays.FloatArray( label="yc", default=numpy.array([1]), doc="Additive coefficient for the second state variable", order=-1) # d = arrays.FloatArray( # label="d", # default=numpy.array([5]), # doc="Coefficient of the squared term in the second state variable", # order=-1) tau0 = arrays.FloatArray( label="tau0", range=basic.Range(lo=100.0, hi=5000, step=10), default=numpy.array([2857.0]), doc="Temporal scaling in the z state variable", order=4) # s = arrays.FloatArray( # label="s", # default=numpy.array([4]), # doc="Linear coefficient in the third state variable", # order=-1) x0 = arrays.FloatArray( label="x0", range=basic.Range(lo=-0.5, hi=1.5, step=0.1), default=numpy.array([0.0]), doc="Excitability parameter", order=3) x0cr = arrays.FloatArray( label="x0cr", range=basic.Range(lo=-1.0, hi=1.0, step=0.1), default=numpy.array([2.46018518518519]), doc="Critical excitability parameter", order=-1) r = arrays.FloatArray( label="r", range=basic.Range(lo=0.0, hi=1.0, step=0.1), default=numpy.array([43.0/108.0]), doc="Excitability parameter scaling", order=-1) Iext1 = arrays.FloatArray( label="Iext1", range=basic.Range(lo=1.5, hi=5.0, step=0.1), default=numpy.array([3.1]), doc="External input current to the first population", order=1) slope = arrays.FloatArray( label="slope", range=basic.Range(lo=-16.0, hi=6.0, step=0.1), default=numpy.array([0.]), doc="Linear coefficient in the first state variable", order=5) Kvf = arrays.FloatArray( label="K_vf", default=numpy.array([0.0]), range=basic.Range(lo=0.0, hi=4.0, step=0.5), doc="Coupling scaling on a very fast time scale.", order=6) K = arrays.FloatArray( label="K", default=numpy.array([0.0]), range=basic.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", order=8) tau1 = arrays.FloatArray( label="tau1", default=numpy.array([0.25]), range=basic.Range(lo=0.001, hi=10.0, step=0.001), doc="Time scaling of the whole system", order=9) state_variable_range = basic.Dict( label="State variable ranges [lo, hi]", default={"y0": numpy.array([-2., 2.]), "y1": numpy.array([-2.0, 5.0])}, doc="n/a", order=16 ) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["y0", "y1"], default=["y0", "y1"], select_multiple=True, doc="""default state variables to be monitored""", order=-1) state_variables = ["y0", "y1"] _nvar = 2 cvar = numpy.array([0, 1], dtype=numpy.int32) def dfun(self, state_variables, coupling, local_coupling=0.0, array=numpy.array, where=numpy.where, concat=numpy.concatenate): r""" Computes the derivatives of the state variables of 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 (2, 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{y_{0}} &=& yc - f_{1}(y_{0}, y_{1}) - y_{2} + I_{ext1} \\ \dot{y_{1}} &=& \begin{cases} (f_z(y_{0}) - y_{1}-0.1 y_{1}^{7})/tau0 & \text{if } y_{0}<5/3 \\ (f_z(y_{0}) - y_{1})/tau0 & \text{if } y_{0} \geq 5/3 \end{cases} \\ where: .. math:: f_{1}(y_{0}, y_{3}) = \begin{cases} a ( y_{0} -5/3 )^{3} - b ( y_{0} -5/3 )^2 & \text{if } y_{0} <5/3\\ ( 5*( y_{0} -5/3 ) - 0.6(y_{1}-4)^2 -slope) ( y_{0} - 5/3 ) &\text{if }y_{0} \geq 5/3 \end{cases} and: .. math:: f_z(y_{0}) = \begin{cases} 4 * (y_{0} - r*x0 + x0_{cr}) & \text{linear} \\ \frac{3}{1+e^{-10*(y_{0}-7/6)}} - r*x0 + x0_{cr} & \text{sigmoidal} \\ \end{cases} """ y = state_variables ydot = numpy.empty_like(state_variables) Iext1 = self.Iext1 + local_coupling * y[0] c_pop1 = coupling[0, :] # population 1 if_ydot0 = y[0] ** 2 + 2.0 * y[0] #self.a=1.0, self.b=-2.0 else_ydot0 = 5 * y[0] - 0.6 * (y[1] - 4.0) ** 2 - self.slope ydot[0] = self.tau1 * (self.yc - y[1] + Iext1 + self.Kvf * c_pop1 - where(y[0] < 0.0, if_ydot0, else_ydot0) * y[0]) # energy if_ydot1 = - 0.1 * y[1] ** 7 else_ydot1 = 0 if self.zmode == 'lin': fz = 4 * (y[0] - self.r * self.x0 + self.x0cr) + where(y[1] < 0.0, if_ydot1, else_ydot1) #self.x0 elif self.zmode == 'sig': fz = 3.0 / (1.0 + numpy.exp(-10*(y[0] + 0.5))) - self.r * self.x0 + self.x0cr else: raise ValueError('zmode has to be either ""lin"" or ""sig"" for linear and sigmoidal fz(), respectively') ydot[1] = self.tau1*(fz - y[1] + self.K * c_pop1)/self.tau0 return ydot
class JansenRitDavid(models.Model): """ The Jansen and Rit models as studied by David et al., 2005 #TODO: finish this model """ _ui_name = "Jansen-Rit (David et al., 2005)" #Define traited attributes for this model, these represent possible kwargs. He = arrays.FloatArray( label=":math:`He`", default=numpy.array([3.25]), range=basic.Range(lo=2.6, hi=9.75, step=0.05), doc= """Maximum amplitude of EPSP [mV]. Also called average synaptic gain.""", order=1) Hi = arrays.FloatArray( label=":math:`B`", default=numpy.array([29.3]), range=basic.Range(lo=17.6, hi=110.0, step=0.2), doc= """Maximum amplitude of IPSP [mV]. Also called average synaptic gain.""", order=2) tau_e = arrays.FloatArray(label=":math:`a`", default=numpy.array([0.1]), range=basic.Range(lo=0.05, hi=0.15, step=0.01), doc="""time constant""", order=3) tau_i = arrays.FloatArray(label=":math:`b`", default=numpy.array([0.15]), range=basic.Range(lo=0.025, hi=0.075, step=0.005), doc="""time constant""", order=4) eo = arrays.FloatArray( label=":math:`v_0`", default=numpy.array([0.0025]), range=basic.Range(lo=3.12, hi=6.0, step=0.02), doc="""Firing threshold (PSP) for which a 50% firing rate is achieved. In other words, it is the value of the average membrane potential corresponding to the inflection point of the sigmoid [mV].""", order=5) r = arrays.FloatArray( label=":math:`r`", default=numpy.array([0.56]), range=basic.Range(lo=0.28, hi=0.84, step=0.01), doc="""Steepness of the sigmoidal transformation [mV^-1].""", order=7) gamma_1 = arrays.FloatArray( label=r":math:`\alpha_1`", default=numpy.array([50.0]), range=basic.Range(lo=0.5, hi=1.5, step=0.1), doc="""Average probability of synaptic contacts in the feedback excitatory loop.""", order=9) gamma_2 = arrays.FloatArray( label=r":math:`\alpha_2`", default=numpy.array([40.]), range=basic.Range(lo=0.4, hi=1.2, step=0.1), doc="""Average probability of synaptic contacts in the feedback excitatory loop.""", order=10) gamma_3 = arrays.FloatArray( label=r":math:`\alpha_3`", default=numpy.array([12.]), range=basic.Range(lo=0.125, hi=0.375, step=0.005), doc="""Average probability of synaptic contacts in the feedback excitatory loop.""", order=11) gamma_4 = arrays.FloatArray( label=r":math:`\alpha_4`", default=numpy.array([12.]), range=basic.Range(lo=0.125, hi=0.375, step=0.005), doc="""Average probability of synaptic contacts in the slow feedback inhibitory loop.""", order=12) #Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "x0": numpy.array([-1.0, 1.0]), "x1": numpy.array([-1.0, 1.0]), "x2": numpy.array([-5.0, 5.0]), "x3": numpy.array([-6.0, 6.0]), "x4": numpy.array([-2.0, 2.0]), "x5": numpy.array([-5.0, 5.0]), "x6": numpy.array([-5.0, 5.0]), "x7": numpy.array([-5.0, 5.0]) }, 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.""", order=16) variables_of_interest = basic.Enumerate( label="Variables watched by Monitors", options=["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"], default=["x0", "x1", "x2", "x3"], select_multiple=True, doc="""This represents the default state-variables of this Model to be monitored. It can be overridden for each Monitor if desired. The corresponding state-variable indices for this model are :math:`y0 = 0`, :math:`y1 = 1`, :math:`y2 = 2`, :math:`y3 = 3`, :math:`y4 = 4`, and :math:`y5 = 5`""", order=17) def __init__(self, **kwargs): """ Initialise parameters for the Jansen Rit column, [JR_1995]_. """ LOG.info("%s: initing..." % str(self)) super(JansenRitDavid, self).__init__(**kwargs) #self._state_variables = ["y0", "y1", "y2", "y3", "y4", "y5"] self._nvar = 8 self.cvar = numpy.array([1, 2], dtype=numpy.int32) LOG.debug('%s: inited.' % repr(self)) def dfun(self, state_variables, coupling, local_coupling=0.0): r""" The dynamic equations were taken from: TODO: add equations and finish the model ... """ magic_exp_number = 709 AF = 0.1 AB = 0.2 AL = 0.05 x0 = state_variables[0, :] x1 = state_variables[1, :] x2 = state_variables[2, :] x3 = state_variables[3, :] x4 = state_variables[4, :] x5 = state_variables[5, :] x6 = state_variables[6, :] x7 = state_variables[7, :] y = x1 - x2 #delayed activity x1 - x2 c_12 = coupling[0, :] - coupling[1, :] c_12_f = AF * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo) c_12_b = AB * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo) c_12_l = AL * ((2 * self.eo) / (1 + numpy.exp(self.r * c_12)) - self.eo) lc_f = (local_coupling * y) * AF lc_l = (local_coupling * y) * AL lc_b = (local_coupling * y) * AB S_y = (2 * self.eo) / (1 + numpy.exp(self.r * y)) - self.eo S_x0 = (2 * self.eo) / (1 + numpy.exp(self.r * x0)) - self.eo S_x6 = (2 * self.eo) / (1 + numpy.exp(self.r * x6)) - self.eo # NOTE: for local couplings # 0:3 pyramidal cells # 1:4 excitatory interneurons # 2:5 inhibitory interneurons # 3:7 dx0 = x3 dx3 = self.He / self.tau_e * (c_12_f + c_12_l + self.gamma_1 * S_y) - ( 2 * x3) / self.tau_e - (x0 / self.tau_e**2) dx1 = x4 dx4 = self.He / self.tau_e * ( c_12_b + c_12_l + self.gamma_2 * S_x0) - (2 * x4) / self.tau_e - ( x1 / self.tau_e**2) dx2 = x5 dx5 = self.Hi / self.tau_i * (self.gamma_4 * S_x6) - ( 2 * x5) / self.tau_i - (x2 / self.tau_i**2) dx6 = x7 dx7 = self.He / self.tau_e * (c_12_b + c_12_l + self.gamma_3 * S_y) - ( 2 * x7) / self.tau_e - (x6 / self.tau_e**2) derivative = numpy.array([dx0, dx1, dx2, dx3, dx4, dx5, dx6, dx7]) return derivative
class MixtureOfGammasData(EquationData): """ A mixture of two gamma distributions to create a kernel similar to the one used in SPM. >> import scipy.stats as sp_stats >> import numpy >> t = numpy.linspace(1,20,100) >> a1, a2 = 6., 10. >> lambda = 1. >> c = 0.5 >> hrf = sp_stats.gamma.pdf(t, a1, lambda) - c * sp_stats.gamma.pdf(t, a2, lambda) gamma.pdf(x, a, theta) = (lambda*x)**(a-1) * exp(-lambda*x) / gamma(a) a : shape parameter theta: 1 / lambda : scale parameter **References**: .. [G_1999] Glover, G. *Deconvolution of Impulse Response in Event-Related BOLD fMRI*. NeuroImage 9, 416-429, 1999. **Parameters**: * :math:`a_{1}` : shape parameter first gamma pdf. * :math:`a_{2}` : shape parameter second gamma pdf. * :math:`\\lambda` : scale parameter first gamma pdf. Default values are based on [G_1999]_: * :math:`a_{1} - 1 = n_{1} = 5.0` * :math:`a_{2} - 1 = n_{2} = 12.0` * :math:`c \\equiv a_{2} = 0.4` Alternative values :math:`a_{2}=10` and :math:`c=0.5` NOTE: gamma_a_1 and gamma_a_2 are placeholders, the true values are computed before evaluating the expression, because numexpr does not support certain functions. NOTE: [G_1999]_ used a different analytical function that can be approximated by this difference of gamma pdfs """ _ui_name = "HRF kernel: Mixture of Gammas" equation = basic.String( label="Mixture of Gammas", default= "(l * var)**(a_1-1) * exp(-l*var) / gamma_a_1 - c * (l*var)**(a_2-1) * exp(-l*var) / gamma_a_2", locked=True, doc= """:math:`\\frac{\\lambda \\,t^{a_{1} - 1} \\,\\, \\exp^{-\\lambda \\,t}}{\\Gamma(a_{1})} - 0.5 \\frac{\\lambda \\,t^{a_{2} - 1} \\,\\, \\exp^{-\\lambda \\,t}}{\\Gamma(a_{2})}`.""" ) parameters = basic.Dict(label="Double Exponential Parameters", default={ "a_1": 6.0, "a_2": 13.0, "l": 1.0, "c": 0.4, "gamma_a_1": 1.0, "gamma_a_2": 1.0 })
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. .. #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:: MorrisLecar.__init__ .. automethod:: MorrisLecar.dfun """ _ui_name = "Morris-Lecar" ui_configurable_parameters = [ 'gCa', 'gK', 'gL', 'C', 'lambda_Nbar', 'V1', 'V2', 'V3', 'V4', 'VCa', 'VK', 'VL' ] #Define traited attributes for this model, these represent possible kwargs. gCa = arrays.FloatArray( label=":math:`g_{Ca}`", default=numpy.array([4.0]), range=basic.Range(lo=2.0, hi=6.0, step=0.01), doc="""Conductance of population of Ca++ channels [mmho/cm2]""", order=1) gK = arrays.FloatArray( label=":math:`g_K`", default=numpy.array([8.0]), range=basic.Range(lo=4.0, hi=12.0, step=0.01), doc="""Conductance of population of K+ channels [mmho/cm2]""", order=2) gL = arrays.FloatArray( label=":math:`g_L`", default=numpy.array([2.0]), range=basic.Range(lo=1.0, hi=3.0, step=0.01), doc="""Conductance of population of leak channels [mmho/cm2]""", order=3) C = arrays.FloatArray(label=":math:`C`", default=numpy.array([20.0]), range=basic.Range(lo=10.0, hi=30.0, step=0.01), doc="""Membrane capacitance [uF/cm2]""", order=4) lambda_Nbar = arrays.FloatArray( label=":math:`\\lambda_{Nbar}`", default=numpy.array([0.06666667]), range=basic.Range(lo=0.0, hi=1.0, step=0.00000001), doc="""Maximum rate for K+ channel opening [1/s]""", order=5) V1 = arrays.FloatArray( label=":math:`V_1`", default=numpy.array([10.0]), range=basic.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]""", order=6) V2 = arrays.FloatArray( label=":math:`V_2`", default=numpy.array([15.0]), range=basic.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].""", order=7) V3 = arrays.FloatArray( label=":math:`V_3`", default=numpy.array([-1.0]), range=basic.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].""", order=8) V4 = arrays.FloatArray( label=":math:`V_4`", default=numpy.array([14.5]), range=basic.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].""", order=9) VCa = arrays.FloatArray(label=":math:`V_{Ca}`", default=numpy.array([100.0]), range=basic.Range(lo=50.0, hi=150.0, step=0.01), doc="""Ca++ Nernst potential [mV]""", order=10) VK = arrays.FloatArray(label=":math:`V_K`", default=numpy.array([-70.0]), range=basic.Range(lo=-105.0, hi=-35.0, step=0.01), doc="""K+ Nernst potential [mV]""", order=11) VL = arrays.FloatArray(label=":math:`V_L`", default=numpy.array([-50.0]), range=basic.Range(lo=-75.0, hi=-25.0, step=0.01), doc="""Nernst potential leak channels [mV]""", order=12) #Used for phase-plane axis ranges and to bound random initial() conditions. state_variable_range = basic.Dict( label="State Variable ranges [lo, hi]", default={ "V": numpy.array([-70.0, 50.0]), "N": numpy.array([-0.2, 0.8]) }, 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.""", order=13) variables_of_interest = arrays.IntegerArray( label="Variables watched by Monitors", range=basic.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`.""", order=14) # 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 Gamma(HRFKernelEquation): """ A Gamma function for the bold monitor. It belongs to the family of Exponential functions. **Parameters**: * :math:`\\tau` : Exponential time constant of the gamma function [seconds]. * :math:`n` : The phase delay of the gamma function. * :math: `factorial` : (n-1)!. numexpr does not support factorial yet. * :math: `a` : Amplitude factor after normalization. **Reference**: .. [B_1996] Geoffrey M. Boynton, Stephen A. Engel, Gary H. Glover and David J. Heeger (1996). Linear Systems Analysis of Functional Magnetic Resonance Imaging in Human V1. J Neurosci 16: 4207-4221 .. note:: might be filtered from the equations used in Stimulus and Local Connectivity. """ _ui_name = "HRF kernel: Gamma kernel" # TODO: Introduce a time delay in the equation (shifts the hrf onset) # """:math:`h(t) = \frac{(\frac{t-\delta}{\tau})^{(n-1)} e^{-(\frac{t-\delta}{\tau})}}{\tau(n-1)!}""" # delta = 2.05 seconds -- Additional delay in seconds from the onset of the # time-series to the beginning of the gamma hrf. # delay cannot be negative or greater than the hrf duration. equation = basic.String( label="Gamma Equation", default= "((var / tau) ** (n - 1) * exp(-(var / tau)) )/ (tau * factorial)", locked=True, doc= """:math:`h(var) = \\frac{(\\frac{var}{\\tau})^{(n-1)}\\exp{-(\\frac{var}{\\tau})}}{\\tau(n-1)!}`.""" ) parameters = basic.Dict(label="Gamma Parameters", default={ "tau": 1.08, "n": 3.0, "factorial": 2.0, "a": 0.1 }) def _get_pattern(self): """ Return a discrete representation of the equation. """ return self._pattern def _set_pattern(self, var): """ Generate a discrete representation of the equation for the space represented by ``var``. .. note: numexpr doesn't support factorial yet """ # compute the factorial n = int(self.parameters["n"]) product = 1 for i in range(n - 1): product *= i + 1 self.parameters["factorial"] = product self._pattern = numexpr.evaluate(self.equation, global_dict=self.parameters) self._pattern /= max(self._pattern) self._pattern *= self.parameters["a"] pattern = property(fget=_get_pattern, fset=_set_pattern)