# encoding: utf-8
"""
Definition of cell classes for the neuron module.

:copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS.
:license: CeCILL, see LICENSE for details.

"""

import logging
from math import pi
from functools import reduce
import numpy as np
from neuron import h, nrn, hclass

from pyNN import errors
from pyNN.models import BaseCellType
from .recording import recordable_pattern
from .simulator import state

logger = logging.getLogger("PyNN")


def _new_property(obj_hierarchy, attr_name):
    """
    Returns a new property, mapping attr_name to obj_hierarchy.attr_name.

    For example, suppose that an object of class A has an attribute b which
    itself has an attribute c which itself has an attribute d. Then placing
      e = _new_property('b.c', 'd')
    in the class definition of A makes A.e an alias for A.b.c.d
    """

    def set(self, value):
        obj = reduce(getattr, [self] + obj_hierarchy.split('.'))
        setattr(obj, attr_name, value)

    def get(self):
        obj = reduce(getattr, [self] + obj_hierarchy.split('.'))
        return getattr(obj, attr_name)
    return property(fset=set, fget=get)


def guess_units(variable):
    # works with NEURON 7.3, not with 7.1, 7.2 not tested
    nrn_units = h.units(variable.split('.')[-1])
    pq_units = nrn_units.replace("2", "**2").replace("3", "**3")
    return pq_units


class NativeCellType(BaseCellType):

    def can_record(self, variable):
        # crude check, could be improved
        return bool(recordable_pattern.match(variable))

    # todo: use `guess_units` to construct "units" attribute


class BaseSingleCompartmentNeuron(nrn.Section):
    """docstring"""

    def __init__(self, c_m, i_offset):

        # initialise Section object with 'pas' mechanism
        nrn.Section.__init__(self)
        self.seg = self(0.5)
        self.L = 100
        self.seg.diam = 1000 / pi  # gives area = 1e-3 cm2

        self.source_section = self

        # insert current source
        self.stim = h.IClamp(0.5, sec=self)
        self.stim.delay = 0
        self.stim.dur = 1e12
        self.stim.amp = i_offset

        # for recording
        self.spike_times = h.Vector(0)
        self.traces = {}
        self.recording_time = 0

        self.v_init = None

    def area(self):
        """Membrane area in µm²"""
        return pi * self.L * self.seg.diam

    c_m = _new_property('seg', 'cm')
    i_offset = _new_property('stim', 'amp')

    def memb_init(self):
        assert self.v_init is not None, "cell is a %s" % self.__class__.__name__
        for seg in self:
            seg.v = self.v_init
        #self.seg.v = self.v_init

    def set_parameters(self, param_dict):
        for name in self.parameter_names:
            setattr(self, name, param_dict[name])


class SingleCompartmentNeuron(BaseSingleCompartmentNeuron):
    """Single compartment with excitatory and inhibitory synapses"""

    synapse_models = {
        'current': {'exp': h.ExpISyn, 'alpha': h.AlphaISyn},
        'conductance': {'exp': h.ExpSyn, 'alpha': h.AlphaSyn},
    }

    def __init__(self, syn_type, syn_shape, c_m, i_offset,
                 tau_e, tau_i, e_e, e_i):
        BaseSingleCompartmentNeuron.__init__(self, c_m, i_offset)

        self.syn_type = syn_type
        self.syn_shape = syn_shape

        # insert synapses
        assert syn_type in (
            'current', 'conductance'), "syn_type must be either 'current' or 'conductance'. Actual value is %s" % syn_type
        assert syn_shape in ('alpha', 'exp'), "syn_type must be either 'alpha' or 'exp'"
        synapse_model = self.synapse_models[syn_type][syn_shape]
        self.esyn = synapse_model(0.5, sec=self)
        self.isyn = synapse_model(0.5, sec=self)

    @property
    def excitatory(self):
        return self.esyn

    @property
    def inhibitory(self):
        return self.isyn

    def _get_tau_e(self):
        return self.esyn.tau

    def _set_tau_e(self, value):
        self.esyn.tau = value
    tau_e = property(fget=_get_tau_e, fset=_set_tau_e)

    def _get_tau_i(self):
        return self.isyn.tau

    def _set_tau_i(self, value):
        self.isyn.tau = value
    tau_i = property(fget=_get_tau_i, fset=_set_tau_i)

    def _get_e_e(self):
        return self.esyn.e

    def _set_e_e(self, value):
        self.esyn.e = value
    e_e = property(fget=_get_e_e, fset=_set_e_e)

    def _get_e_i(self):
        return self.isyn.e

    def _set_e_i(self, value):
        self.isyn.e = value
    e_i = property(fget=_get_e_i, fset=_set_e_i)


class LeakySingleCompartmentNeuron(SingleCompartmentNeuron):

    def __init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, i_offset,
                 tau_e, tau_i, e_e, e_i):
        SingleCompartmentNeuron.__init__(self, syn_type, syn_shape, c_m, i_offset,
                                         tau_e, tau_i, e_e, e_i)
        self.insert('pas')
        self.v_init = v_rest  # default value

    def __set_tau_m(self, value):
        # print("setting tau_m to", value, "cm =", self.seg.cm))
        # cm(nF)/tau_m(ms) = G(uS) = 1e-6G(S). Divide by area (1e-3) to get factor of 1e-3
        self.seg.pas.g = 1e-3 * self.seg.cm / value

    def __get_tau_m(self):
        #print("tau_m = ", 1e-3*self.seg.cm/self.seg.pas.g, "cm = ", self.seg.cm)
        return 1e-3 * self.seg.cm / self.seg.pas.g

    def __get_cm(self):
        #print("cm = ", self.seg.cm)
        return self.seg.cm

    def __set_cm(self, value):  # when we set cm, need to change g to maintain the same value of tau_m
        #print("setting cm to", value)
        tau_m = self.tau_m
        self.seg.cm = value
        self.tau_m = tau_m

    v_rest = _new_property('seg.pas', 'e')
    tau_m = property(fget=__get_tau_m, fset=__set_tau_m)
    c_m = property(fget=__get_cm, fset=__set_cm)  # if the property were called 'cm'
    # it would never get accessed as the
    # built-in Section.cm would always
    # be used first


class StandardIF(LeakySingleCompartmentNeuron):
    """docstring"""

    def __init__(self, syn_type, syn_shape, tau_m=20, c_m=1.0, v_rest=-65,
                 v_thresh=-55, t_refrac=2, i_offset=0, v_reset=None,
                 tau_e=5, tau_i=5, e_e=0, e_i=-70):
        if v_reset is None:
            v_reset = v_rest
        LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest,
                                              i_offset, tau_e, tau_i, e_e, e_i)
        # insert spike reset mechanism
        self.spike_reset = h.ResetRefrac(0.5, sec=self)
        self.spike_reset.vspike = 40  # (mV) spike height
        self.source = self.spike_reset
        self.rec = h.NetCon(self.source, None)

        # process arguments
        self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 't_refrac',   # 'c_m' must come before 'tau_m'
                                'i_offset', 'v_reset', 'tau_e', 'tau_i']
        if syn_type == 'conductance':
            self.parameter_names.extend(['e_e', 'e_i'])
        self.set_parameters(locals())

    v_thresh = _new_property('spike_reset', 'vthresh')
    v_reset = _new_property('spike_reset', 'vreset')
    t_refrac = _new_property('spike_reset', 'trefrac')


class BretteGerstnerIF(LeakySingleCompartmentNeuron):
    """docstring"""

    def __init__(self, syn_type, syn_shape, tau_m=20, c_m=1.0, v_rest=-65,
                 v_thresh=-55, t_refrac=2, i_offset=0,
                 tau_e=5, tau_i=5, e_e=0, e_i=-70,
                 v_spike=0.0, v_reset=-70.6, A=4.0, B=0.0805, tau_w=144.0,
                 delta=2.0):
        LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m,
                                              c_m, v_rest, i_offset,
                                              tau_e, tau_i, e_e, e_i)

        # insert Brette-Gerstner spike mechanism
        self.adexp = h.AdExpIF(0.5, sec=self)
        self.source = self.adexp
        self.rec = h.NetCon(self.source, None)

        self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 't_refrac',
                                'i_offset', 'v_reset', 'tau_e', 'tau_i',
                                'A', 'B', 'tau_w', 'delta', 'v_spike']
        if syn_type == 'conductance':
            self.parameter_names.extend(['e_e', 'e_i'])
        self.set_parameters(locals())
        self.w_init = None

    v_thresh = _new_property('adexp', 'vthresh')
    v_reset = _new_property('adexp', 'vreset')
    t_refrac = _new_property('adexp', 'trefrac')
    B = _new_property('adexp', 'b')
    A = _new_property('adexp', 'a')
    # using 'A' because for some reason, cell.a gives the error "NameError: a, the mechanism does not exist at PySec_170bb70(0.5)"
    tau_w = _new_property('adexp', 'tauw')
    delta = _new_property('adexp', 'delta')

    def __set_v_spike(self, value):
        self.adexp.vspike = value
        self.adexp.vpeak = value + 10.0

    def __get_v_spike(self):
        return self.adexp.vspike
    v_spike = property(fget=__get_v_spike, fset=__set_v_spike)

    def __set_tau_m(self, value):
        # cm(nF)/tau_m(ms) = G(uS) = 1e-6G(S). Divide by area (1e-3) to get factor of 1e-3
        self.seg.pas.g = 1e-3 * self.seg.cm / value
        self.adexp.GL = self.seg.pas.g * self.area() * 1e-2  # S/cm2 to uS

    def __get_tau_m(self):
        return 1e-3 * self.seg.cm / self.seg.pas.g

    def __set_v_rest(self, value):
        self.seg.pas.e = value
        self.adexp.EL = value

    def __get_v_rest(self):
        return self.seg.pas.e
    tau_m = property(fget=__get_tau_m, fset=__set_tau_m)
    v_rest = property(fget=__get_v_rest, fset=__set_v_rest)

    def get_threshold(self):
        if self.delta == 0:
            return self.adexp.vthresh
        else:
            return self.adexp.vspike

    def memb_init(self):
        assert self.v_init is not None, "cell is a %s" % self.__class__.__name__
        assert self.w_init is not None
        for seg in self:
            seg.v = self.v_init
        self.adexp.w = self.w_init


class Izhikevich_(BaseSingleCompartmentNeuron):
    """docstring"""

    def __init__(self, a_=0.02, b=0.2, c=-65.0, d=2.0, i_offset=0.0):
        BaseSingleCompartmentNeuron.__init__(self, 1.0, i_offset)
        self.L = 10
        self.seg.diam = 10 / pi
        self.c_m = 1.0

        # insert Izhikevich mechanism
        self.izh = h.Izhikevich(0.5, sec=self)
        self.source = self.izh
        self.rec = h.NetCon(self.seg._ref_v, None,
                            self.get_threshold(), 0.0, 0.0,
                            sec=self)
        self.excitatory = self.inhibitory = self.source

        self.parameter_names = ['a_', 'b', 'c', 'd', 'i_offset']
        self.set_parameters(locals())
        self.u_init = None

    a_ = _new_property('izh', 'a')
    b = _new_property('izh', 'b')
    c = _new_property('izh', 'c')
    d = _new_property('izh', 'd')
    # using 'a_' because for some reason, cell.a gives the error "NameError: a, the mechanism does not exist at PySec_170bb70(0.5)"

    def get_threshold(self):
        return self.izh.vthresh

    def memb_init(self):
        assert self.v_init is not None, "cell is a %s" % self.__class__.__name__
        assert self.u_init is not None
        for seg in self:
            seg.v = self.v_init
        self.izh.u = self.u_init


class GsfaGrrIF(StandardIF):
    """docstring"""

    def __init__(self, syn_type, syn_shape, tau_m=10.0, c_m=1.0, v_rest=-70.0,
                 v_thresh=-57.0, t_refrac=0.1, i_offset=0.0,
                 tau_e=1.5, tau_i=10.0, e_e=0.0, e_i=-75.0,
                 v_spike=0.0, v_reset=-70.0, q_rr=3214.0, q_sfa=14.48,
                 e_rr=-70.0, e_sfa=-70.0,
                 tau_rr=1.97, tau_sfa=110.0):

        StandardIF.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest,
                            v_thresh, t_refrac, i_offset, v_reset,
                            tau_e, tau_i, e_e, e_i)

        # insert GsfaGrr mechanism
        self.gsfa_grr = h.GsfaGrr(0.5, sec=self)
        self.v_thresh = v_thresh

        self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 'v_reset',
                                't_refrac', 'tau_e', 'tau_i', 'i_offset',
                                'e_rr', 'e_sfa', 'q_rr', 'q_sfa', 'tau_rr', 'tau_sfa']
        if syn_type == 'conductance':
            self.parameter_names.extend(['e_e', 'e_i'])
        self.set_parameters(locals())

    q_sfa = _new_property('gsfa_grr', 'q_s')
    q_rr = _new_property('gsfa_grr', 'q_r')
    tau_sfa = _new_property('gsfa_grr', 'tau_s')
    tau_rr = _new_property('gsfa_grr', 'tau_r')
    e_sfa = _new_property('gsfa_grr', 'E_s')
    e_rr = _new_property('gsfa_grr', 'E_r')

    def __set_v_thresh(self, value):
        self.spike_reset.vthresh = value
        # this can fail on constructor
        # todo: figure out why it is failing and fix in a way that does not require ignoring an Exception
        try:
            self.gsfa_grr.vthresh = value
        except AttributeError:
            pass

    def __get_v_thresh(self):
        return self.spike_reset.vthresh
    v_thresh = property(fget=__get_v_thresh, fset=__set_v_thresh)


class SingleCompartmentTraub(SingleCompartmentNeuron):

    def __init__(self, syn_type, syn_shape, c_m=1.0, e_leak=-65,
                 i_offset=0, tau_e=5, tau_i=5, e_e=0, e_i=-70,
                 gbar_Na=20e-3, gbar_K=6e-3, g_leak=0.01e-3, ena=50,
                 ek=-90, v_offset=-63):
        """
        Conductances are in millisiemens (S/cm2, since A = 1e-3)
        """
        SingleCompartmentNeuron.__init__(self, syn_type, syn_shape, c_m, i_offset,
                                         tau_e, tau_i, e_e, e_i)
        self.source = self.seg._ref_v
        self.source_section = self
        self.rec = h.NetCon(self.source, None, sec=self)
        self.insert('k_ion')
        self.insert('na_ion')
        self.insert('hh_traub')
        self.parameter_names = ['c_m', 'e_leak', 'i_offset', 'tau_e',
                                'tau_i', 'gbar_Na', 'gbar_K', 'g_leak', 'ena',
                                'ek', 'v_offset']
        if syn_type == 'conductance':
            self.parameter_names.extend(['e_e', 'e_i'])
        self.set_parameters(locals())
        self.v_init = e_leak  # default value

    # not sure ena and ek are handled correctly

    e_leak = _new_property('seg.hh_traub', 'el')
    v_offset = _new_property('seg.hh_traub', 'vT')
    gbar_Na = _new_property('seg.hh_traub', 'gnabar')
    gbar_K = _new_property('seg.hh_traub', 'gkbar')
    g_leak = _new_property('seg.hh_traub', 'gl')

    def get_threshold(self):
        return 10.0


class GIFNeuron(LeakySingleCompartmentNeuron):
    """
    to write...

    References:
      [1] Mensi, S., Naud, R., Pozzorini, C., Avermann, M., Petersen, C. C., &
      Gerstner, W. (2012). Parameter
      extraction and classification of three cortical neuron types reveals two
      distinct adaptation mechanisms.
      Journal of Neurophysiology, 107(6), 1756-1775.
      [2] Pozzorini, C., Mensi, S., Hagens, O., Naud, R., Koch, C., & Gerstner, W.
      (2015). Automated
      High-Throughput Characterization of Single Neurons by Means of Simplified
      Spiking Models. PLoS Comput Biol, 11(6), e1004275.
    """

    def __init__(self, syn_type, syn_shape,
                 tau_m=20, c_m=1.0, v_rest=-65,
                 t_refrac=2, i_offset=0,
                 v_reset=-55.0,
                 tau_e=5, tau_i=5, e_e=0, e_i=-70,
                 vt_star=-48.0, dV=0.5, lambda0=1.0,
                 tau_eta=(10.0, 50.0, 250.0),
                 a_eta=(0.2, 0.05, 0.025),
                 tau_gamma=(5.0, 200.0, 250.0),
                 a_gamma=(15.0, 3.0, 1.0)):

        LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m,
                                              c_m, v_rest, i_offset,
                                              tau_e, tau_i, e_e, e_i)

        self.gif_fun = h.GifCurrent(0.5, sec=self)
        self.source = self.gif_fun
        self.rec = h.NetCon(self.source, None)

        self.parameter_names = ['c_m', 'tau_m', 'v_rest', 't_refrac',
                                'i_offset', 'v_reset', 'tau_e', 'tau_i',
                                'vt_star', 'dV', 'lambda0',
                                'tau_eta', 'a_eta',
                                'tau_gamma', 'a_gamma']
        if syn_type == 'conductance':
            self.parameter_names.extend(['e_e', 'e_i'])
        self.set_parameters(locals())

    def __set_tau_eta(self, value):
        self.gif_fun.tau_eta1, self.gif_fun.tau_eta2, self.gif_fun.tau_eta3 = value.value

    def __get_tau_eta(self):
        return self.gif_fun.tau_eta1, self.gif_fun.tau_eta2, self.gif_fun.tau_eta3

    tau_eta = property(fset=__set_tau_eta, fget=__get_tau_eta)

    def __set_a_eta(self, value):
        self.gif_fun.a_eta1, self.gif_fun.a_eta2, self.gif_fun.a_eta3 = value.value

    def __get_a_eta(self):
        return self.gif_fun.a_eta1, self.gif_fun.a_eta2, self.gif_fun.a_eta3

    a_eta = property(fset=__set_a_eta, fget=__get_a_eta)

    def __set_tau_gamma(self, value):
        self.gif_fun.tau_gamma1, self.gif_fun.tau_gamma2, self.gif_fun.tau_gamma3 = value.value

    def __get_tau_gamma(self):
        return self.gif_fun.tau_gamma1, self.gif_fun.tau_gamma2, self.gif_fun.tau_gamma3

    tau_gamma = property(fset=__set_tau_gamma, fget=__get_tau_gamma)

    def __set_a_gamma(self, value):
        self.gif_fun.a_gamma1, self.gif_fun.a_gamma2, self.gif_fun.a_gamma3 = value.value

    def __get_a_gamma(self):
        return self.gif_fun.a_gamma1, self.gif_fun.a_gamma2, self.gif_fun.a_gamma3

    a_gamma = property(fset=__set_a_gamma, fget=__get_a_gamma)

    v_reset = _new_property('gif_fun', 'Vr')
    t_refrac = _new_property('gif_fun', 'Tref')
    vt_star = _new_property('gif_fun', 'Vt_star')
    dV = _new_property('gif_fun', 'DV')
    lambda0 = _new_property('gif_fun', 'lambda0')

    def memb_init(self):
        for state_var in ('v', 'v_t', 'i_eta'):
            initial_value = getattr(self, '{0}_init'.format(state_var))
            assert initial_value is not None
            if state_var == 'v':
                for seg in self:
                    seg.v = initial_value
            else:
                setattr(self.gif_fun, state_var, initial_value)


class RandomSpikeSource(hclass(h.NetStimFD)):

    parameter_names = ('start', '_interval', 'duration')

    def __init__(self, start=0, _interval=1e12, duration=0):
        self.start = start
        self.interval = _interval
        self.duration = duration
        self.noise = 1
        self.spike_times = h.Vector(0)
        self.source = self
        self.rec = h.NetCon(self, None)
        self.switch = h.NetCon(None, self)
        self.source_section = None
        # should allow user to set specific seeds somewhere, e.g. in setup()
        self.seed(state.mpi_rank + state.native_rng_baseseed)

    def __new__(cls, *arg, **kwargs):
        return super().__new__(cls, *arg, **kwargs)

    def _set_interval(self, value):
        self.switch.weight[0] = -1
        self.switch.event(h.t + 1e-12, 0)
        self.interval = value
        self.switch.weight[0] = 1
        self.switch.event(h.t + 2e-12, 1)

    def _get_interval(self):
        return self.interval
    _interval = property(fget=_get_interval, fset=_set_interval)


class RandomPoissonRefractorySpikeSource(hclass(h.PoissonStimRefractory)):

    parameter_names = ('rate', 'tau_refrac', 'start', 'duration')

    def __init__(self, rate=1, tau_refrac=0.0, start=0, duration=0):
        self.rate = rate
        self.tau_refrac = tau_refrac
        self.start = start
        self.duration = duration
        self.spike_times = h.Vector(0)
        self.source = self
        self.rec = h.NetCon(self, None)
        self.source_section = None
        self.seed(state.mpi_rank + state.native_rng_baseseed)

    def __new__(cls, *arg, **kwargs):
        return super().__new__(cls, *arg, **kwargs)


class RandomGammaSpikeSource(hclass(h.GammaStim)):

    parameter_names = ('alpha', 'beta', 'start', 'duration')

    def __init__(self, alpha=1, beta=0.1, start=0, duration=0):
        self.alpha = alpha
        self.beta = beta
        self.start = start
        self.duration = duration
        self.spike_times = h.Vector(0)
        self.source = self
        self.rec = h.NetCon(self, None)
        self.switch = h.NetCon(None, self)
        self.source_section = None
        self.seed(state.mpi_rank + state.native_rng_baseseed)

    def __new__(cls, *arg, **kwargs):
        return super().__new__(cls, *arg, **kwargs)


class VectorSpikeSource(hclass(h.VecStim)):

    parameter_names = ('spike_times',)

    def __init__(self, spike_times=[]):
        self.recording = False
        self.spike_times = spike_times
        self.source = self
        self.source_section = None
        self.rec = None
        self._recorded_spikes = np.array([])

    def __new__(cls, *arg, **kwargs):
        return super().__new__(cls, *arg, **kwargs)

    def _set_spike_times(self, spike_times):
        # spike_times should be a Sequence object
        try:
            self._spike_times = h.Vector(spike_times.value)
        except (RuntimeError, AttributeError):
            raise errors.InvalidParameterValueError("spike_times must be an array of floats")
        if np.any(spike_times.value[:-1] > spike_times.value[1:]):
            raise errors.InvalidParameterValueError(
                "Spike times given to SpikeSourceArray must be in increasing order")
        self.play(self._spike_times)
        if self.recording:
            self._recorded_spikes = np.hstack((self._recorded_spikes, spike_times.value))

    def _get_spike_times(self):
        return self._spike_times

    spike_times = property(fget=_get_spike_times,
                           fset=_set_spike_times)

    @property
    def recording(self):
        return self._recording

    @recording.setter
    def recording(self, value):
        self._recording = value
        if value:
            # when we turn recording on, the cell may already have had its spike times assigned
            self._recorded_spikes = np.hstack((self._recorded_spikes, self.spike_times))

    def get_recorded_spike_times(self):
        return self._recorded_spikes

    def clear_past_spikes(self):
        """If previous recordings are cleared, need to remove spikes from before the current time."""
        self._recorded_spikes = self._recorded_spikes[self._recorded_spikes > h.t]


class ArtificialCell(object):
    """Wraps NEURON 'ARTIFICIAL_CELL' models for PyNN"""

    def __init__(self, mechanism_name, **parameters):
        self.source = getattr(h, mechanism_name)()
        for name, value in parameters.items():
            setattr(self.source, name, value)
        dummy = nrn.Section()

        # needed for PyNN
        self.source_section = dummy  # todo: only need a single dummy for entire network, not one per cell
        self.parameter_names = ('tau', 'refrac')
        self.traces = {}
        self.spike_times = h.Vector(0)
        self.rec = h.NetCon(self.source, None)
        self.recording_time = False
        self.default = self.source

    def _set_tau(self, value):
        self.source.tau = value

    def _get_tau(self):
        return self.source.tau
    tau = property(fget=_get_tau, fset=_set_tau)

    def _set_refrac(self, value):
        self.source.refrac = value

    def _get_refrac(self):
        return self.source.refrac
    refrac = property(fget=_get_refrac, fset=_set_refrac)

    # ... gkbar and g_leak properties defined similarly

    def memb_init(self):
        self.source.m = self.m_init


class IntFire1(NativeCellType):
    default_parameters = {'tau': 10.0, 'refrac': 5.0}
    default_initial_values = {'m': 0.0}
    recordable = ['m']
    units = {'m': 'dimensionless'}
    receptor_types = ['default']
    model = ArtificialCell
    extra_parameters = {"mechanism_name": "IntFire1"}


class IntFire2(NativeCellType):
    default_parameters = {'taum': 10.0, 'taus': 20.0, 'ib': 0.0}
    default_initial_values = {'m': 0.0, 'i': 0.0}
    recordable = ['m', 'i']
    units = {'m': 'dimensionless', 'i': 'dimensionless'}
    receptor_types = ['default']
    model = ArtificialCell
    extra_parameters = {"mechanism_name": "IntFire2"}


class IntFire4(NativeCellType):
    default_parameters = {
        'taum': 50.0,
        'taue': 5.0,
        'taui1': 10.0,
        'taui2': 20.0,
    }
    default_initial_values = {'m': 0.0, 'e': 0.0, 'i1': 0.0, 'i2': 0.0}
    recordable = ['e', 'i1', 'i2', 'm']
    units = {'e': 'dimensionless',
             'i1': 'dimensionless',
             'i2': 'dimensionless',
             'm': 'dimensionless'}
    receptor_types = ['default']
    model = ArtificialCell
    extra_parameters = {"mechanism_name": "IntFire4"}
