"""
NRV-myelinated
Authors: Florian Kolbl / Roland Giraud / Louis Regnacq / Thomas Couppey
(c) ETIS - University Cergy-Pontoise - CNRS
"""
import math

import numpy as np

from ..backend.log_interface import rise_warning
from .axons import *

MRG_fiberD = np.asarray([1, 2, 5.7, 7.3, 8.7, 10.0, 11.5, 12.8, 14.0, 15.0, 16.0])
MRG_g = np.asarray(
    [0.565, 0.585, 0.605, 0.630, 0.661, 0.690, 0.700, 0.719, 0.739, 0.767, 0.791]
)
MRG_axonD = np.asarray([0.8, 1.6, 3.4, 4.6, 5.8, 6.9, 8.1, 9.2, 10.4, 11.5, 12.7])
MRG_nodeD = np.asarray([0.7, 1.4, 1.9, 2.4, 2.8, 3.3, 3.7, 4.2, 4.7, 5.0, 5.5])
MRG_paraD1 = np.asarray([0.7, 1.4, 1.9, 2.4, 2.8, 3.3, 3.7, 4.2, 4.7, 5.0, 5.5])
MRG_paraD2 = np.asarray([0.8, 1.6, 3.4, 4.6, 5.8, 6.9, 8.1, 9.2, 10.4, 11.5, 12.7])
MRG_deltax = np.asarray([100, 200, 500, 750, 1000, 1150, 1250, 1350, 1400, 1450, 1500])
MRG_paralength2 = np.asarray([5, 10, 35, 38, 40, 46, 50, 54, 56, 58, 60])
MRG_nl = np.asarray([15, 20, 80, 100, 110, 120, 130, 135, 140, 145, 150])


def get_MRG_parameters(diameter):
    """
    Compute the MRG parameters

    Original parameters are listed and used if the input diameter is in the list,
    parameters are interpolated if the diameter is not in the original list.

    Attributes
    ----------
    diameter    : float
        diameter of the unmylinated axon to implement, in um

    Returns
    -------
    g           : float
    axonD       : float
    nodeD       : float
    paraD1      : float
    paraD2      : float
    deltax      : float
    paralength2 : float
    nl          : float
    """
    if diameter in MRG_fiberD:
        index = np.where(MRG_fiberD == diameter)[0]
        g = MRG_g[index]
        axonD = MRG_axonD[index]
        nodeD = MRG_nodeD[index]
        paraD1 = MRG_paraD1[index]
        paraD2 = MRG_paraD2[index]
        deltax = MRG_deltax[index]
        paralength2 = MRG_paralength2[index]
        nl = MRG_nl[index]
    else:
        # create fiting polynomyals
        g_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_g, 3))
        axonD_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_axonD, 3))
        nodeD_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_nodeD, 3))
        paraD1_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_paraD1, 3))
        paraD2_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_paraD2, 3))
        deltax_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_deltax, 4))
        if diameter < 1.0 or diameter > 16.0:
            # outside of the MRG originla limit, take 1st order approx,
            paralength2_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_paralength2, 1))
        else:
            # try to fit a bit better
            paralength2_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_paralength2, 5))
        nl_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_nl, 3))
        # evaluate for the requested diameter
        g = g_poly(diameter)
        axonD = axonD_poly(diameter)
        nodeD = nodeD_poly(diameter)
        paraD1 = paraD1_poly(diameter)
        paraD2 = paraD2_poly(diameter)
        deltax = deltax_poly(diameter)
        paralength2 = paralength2_poly(diameter)
        nl = nl_poly(diameter)
    return (
        float(g),
        float(axonD),
        float(nodeD),
        float(paraD1),
        float(paraD2),
        float(deltax),
        float(paralength2),
        float(nl),
    )


def get_length_from_nodes(diameter, nodes):
    """
    Function to compute the length of a myelinated axon to get the correct number of Nodes of Ranvier
    For Myelinated models only (not compatible with A delta thin myelinated models)

    Attributes
    ----------
    diameter    : float
        diameter of the axon in um
    nodes       : int
        number of nodes in the axon

    Returns
    -------
    length      : float
        lenth of the axon with the correct number of nodes in um
    """
    if diameter in MRG_fiberD:
        index = np.where(MRG_fiberD == diameter)[0]
        deltax = MRG_deltax[index]
    else:
        deltax_poly = np.poly1d(np.polyfit(MRG_fiberD, MRG_deltax, 4))
        deltax = deltax_poly(diameter)
    return float(math.ceil(deltax * (nodes - 1)))


class myelinated(axon):
    """
    Myelineated axon class. Automatic refinition of all neuron sections and properties. User-friendly object including model definition
    Inherit from axon class. see axon for further detail.
    """

    def __init__(
        self,
        y=0,
        z=0,
        d=10,
        L=10000,
        model="MRG",
        dt=0.001,
        node_shift=0,
        Nseg_per_sec=0,
        freq=100,
        freq_min=0,
        mesh_shape="plateau_sigmoid",
        alpha_max=0.3,
        d_lambda=0.1,
        rec="nodes",
        v_init=None,
        T=None,
        ID=0,
        threshold=-40,
        **kwargs,
    ):
        """
        initialisation of a myelinted axon

        Parameters
        ----------
        y               : float
            y coordinate for the axon, in um
        z               : float
            z coordinate for the axon, in um
        d               : float
            axon diameter, in um
        L               : float
            axon length along the x axins, in um
        model           : str
            choice of conductance double-cable based model, possibly:
                "MRG"           : see [1] for details
                "Gaines_motor"  : Gaines motor model, see [2]
                "Gaines_sensory": Gaines sensory model, see [2]
        dt              : float
            computation step for simulations, in ms. By default equal to 1 us
        node_shift      : float
            shift of the first node of Ranvier to zeros, as a fraction of internode length (0<= node_shift < 1)
        Nseg_per_sec    : int
            Number of segment per section in the axon. If set to 0, the number of segment is automatically computed using d-lambda rule and following paramters. If set by user, please use odd numbers
        freq            : float
            Frequency used for the d-lmbda rule, corresponding to the maximum membrane current frequency, by default set to 100 Hz
        freq_min        : float
            Minimal frequency fot the d-lambda rule when using an irregular number of segment along the axon, if set to 0, all sections have the same frequency determined by the previous parameter
        mesh_shape      : str
            Shape of the frequencial distribution for the dlmabda rule along the axon, pick between:
                "pyramidal"         -> min frequencies on both sides and linear increase up to the middle at the maximum frequency
                "sigmoid"           -> same a befor with sigmoid increase instead of linear
                "plateau"           -> sale as pyramidal except the max frequency is holded on a central plateau
                "plateau_sigmoid"   -> same as previous with sigmoid increase
        alpha_max       : float
            Proportion of the axon set to the maximum frequency for plateau shapes, by default set to 0.3
        d_lambda        : float
            value of d-lambda for the dlambda rule
        rec             : str
            recording zones for the membrane potential, eiter:
                "nodes" -> record only at the nodes of Ranvier
            or
                "all" -> all computation points in nodes of Ranvier and over myelin
        v_init          : float
            Initial value of the membrane voltage in mV, set None to get an automatically model attributed value
        T               : gloat
            temperature in C, set None to get an automatically model attributed value
        ID              : int
            axon ID, by default set to 0,
        threshold       : float
            voltage threshold in mV for further spike detection in post-processing, by defautl set to -40mV, see post-processing library for further help

        Note
        ----
        scientific sources for models:
        [1] McIntyre CC, Richardson AG, and Grill WM. Modeling the excitability of mammalian nerve fibers: influence of afterpotentials on the recovery cycle. Journal of Neurophysiology 87:995-1006, 2002.
        [2] Gaines, J. L., Finn, K. E., Slopsema, J. P., Heyboer, L. A.,  Polasek, K. H. (2018). A model of motor and sensory axon activation in the median nerve using surface electrical stimulation. Journal of computational neuroscience, 45(1), 29-43.
        """
        super().__init__(
            y,
            z,
            d,
            L,
            dt=dt,
            Nseg_per_sec=Nseg_per_sec,
            freq=freq,
            freq_min=freq_min,
            mesh_shape=mesh_shape,
            alpha_max=alpha_max,
            d_lambda=d_lambda,
            v_init=v_init,
            T=T,
            ID=ID,
            threshold=threshold,
            **kwargs,
        )
        self.myelinated = True
        self.thin = False
        self.rec = rec
        self.node_shift = node_shift

        if model in myelinated_models:
            self.model = model
        else:
            self.model = "MRG"
        self.__compute_axon_parameters()

    def __compute_axon_parameters(self):
        """
        generate axon from parameters set by user
        """
        if self.v_init is None:
            # model driven
            if self.model == "Gaines_sensory":
                self.v_init = -79.3565
            elif self.model == "Gaines_motor":
                self.v_init = -85.9411
            else:
                self.v_init = -80
        else:
            # user driven
            self.v_init = self.v_init
        ## Handling temperature
        if self.T is None:
            # model driven
            self.T = 37

        ############################################
        ## PARMETERS FOR THE COMPARTIMENTAL MODEL ##
        ############################################
        # compute variable MRG parameters, (usefull also if non MRG models)
        (
            self.g,
            self.axonD,
            self.nodeD,
            self.paraD1,
            self.paraD2,
            self.deltax,
            self.paralength2,
            self.nl,
        ) = get_MRG_parameters(self.d)
        # Morphological parameters
        self.paralength1 = 3
        self.nodelength = 1.0
        self.space_p1 = 0.002
        self.space_p2 = 0.004
        self.space_i = 0.004
        self.interlength = (
            self.deltax
            - self.nodelength
            - (2 * self.paralength1)
            - (2 * self.paralength2)
        ) / 6
        # electrical parameters
        self.rhoa = 0.7e6  # Ohm-um
        self.mycm = 0.1  # uF/cm2/lamella membrane
        self.mygm = 0.001  # S/cm2/lamella membrane
        self.Rpn0 = (self.rhoa * 0.01) / (
            math.pi
            * ((((self.nodeD / 2) + self.space_p1) ** 2) - ((self.nodeD / 2) ** 2))
        )
        self.Rpn1 = (self.rhoa * 0.01) / (
            math.pi
            * ((((self.paraD1 / 2) + self.space_p1) ** 2) - ((self.paraD1 / 2) ** 2))
        )
        self.Rpn2 = (self.rhoa * 0.01) / (
            math.pi
            * ((((self.paraD2 / 2) + self.space_p2) ** 2) - ((self.paraD2 / 2) ** 2))
        )
        self.Rpx = (self.rhoa * 0.01) / (
            math.pi
            * ((((self.axonD / 2) + self.space_i) ** 2) - ((self.axonD / 2) ** 2))
        )
        #########################
        ## morphology planning ##
        #########################
        # basic MRG sequence
        self.MRG_Sequence = [
            "node",
            "MYSA",
            "FLUT",
            "STIN",
            "STIN",
            "STIN",
            "STIN",
            "STIN",
            "STIN",
            "FLUT",
            "MYSA",
        ]
        # basic MRG starts with the node, if needed, adapt the sequence
        if self.node_shift == 0 or self.node_shift == 1:
            # no Rotation
            self.this_ax_sequence = self.MRG_Sequence
            self.first_section_size = self.nodelength

        elif self.node_shift < (self.paralength1) / self.deltax:
            # rotation of less than 1 MYSA
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 1)
            self.first_section_size = self.node_shift * self.deltax

        elif self.node_shift < (self.paralength1 + self.paralength2) / self.deltax:
            # rotation of 1 MYSA and less than one FLUT
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 2)
            self.first_section_size = self.node_shift * self.deltax - self.paralength1

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 3)
            self.first_section_size = (
                self.node_shift * self.deltax - self.paralength1 - self.paralength2
            )

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + 2 * self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 1 STIN and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 4)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - self.interlength
            )

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + 3 * self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 2 STIN and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 5)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - 2 * self.interlength
            )

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + 4 * self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 3 STIN and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 6)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - 3 * self.interlength
            )

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + 5 * self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 4 STIN and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 7)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - 4 * self.interlength
            )

        elif (
            self.node_shift
            < (self.paralength1 + self.paralength2 + 6 * self.interlength) / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 5 STIN and less than a STIN
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 8)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - 5 * self.interlength
            )

        elif (
            self.node_shift
            < (self.paralength1 + 2 * self.paralength2 + 6 * self.interlength)
            / self.deltax
        ):
            # rotation of 1 MYSA, 1 FLUT, 6 STIN and less than a FLUT
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 9)
            self.first_section_size = (
                self.node_shift * self.deltax
                - self.paralength1
                - self.paralength2
                - 6 * self.interlength
            )

        else:
            # rotation of 1 MYSA, 2 FLUT, 6 STIN and less than a MYSA
            # WARNING FOR DEV : the unprobable case of a node cut in two halfs is not considered...
            self.this_ax_sequence = rotate_list(self.MRG_Sequence, 10)
            self.first_section_size = self.deltax * (1 - self.node_shift)
        ################
        ## morphology ##
        ################
        self.axonnodes = 0  # number of nodes in the axon
        self.node = []  # list of nodes in the axon
        self.paranodes1 = 0  # number of MYSA in the axon
        self.MYSA = []  # list of MYSA in the axon
        self.paranodes2 = 0  # number of FLUT in the axon
        self.FLUT = []  # list of FLUT in the axon
        self.axoninter = 0  # number of STIN in the axon
        self.STIN = []  # list of STIN in the axon

        prov_length = 0
        self.Nsec = 0
        self.last_section_size = 0
        self.last_section_kind = None
        self.axon_path_type = []
        self.axon_path_index = []
        while prov_length < self.L:
            # axon is too short, create a section
            pos_in_sequence = (self.Nsec) % len(
                self.this_ax_sequence
            )  # find the current position in the node sequence
            if self.this_ax_sequence[pos_in_sequence] == "node":
                # you need to create a node
                self.node.append(neuron.h.Section(name="node[%d]" % self.axonnodes))
                self.axon_path_type.append("node")
                self.axon_path_index.append(self.axonnodes)
                self.axonnodes += 1
                self.Nsec += 1
                # increment the prov length and check it is not to much
                if prov_length + self.nodelength >= self.L:
                    # it will be the last section to add to the axon
                    self.last_section_kind = "node"
                    self.last_section_size = self.L - prov_length
                prov_length += self.nodelength
                # CONNECT THE NODE TO ITS PARENT: a node can only have a MYSA for parent, check it"s not the start...
                if self.paranodes1 != 0:
                    self.node[-1].connect(self.MYSA[-1], 1, 0)

            elif self.this_ax_sequence[pos_in_sequence] == "MYSA":
                # you need to create a MYSA
                self.MYSA.append(neuron.h.Section(name="MYSA[%d]" % self.paranodes1))
                self.axon_path_type.append("MYSA")
                self.axon_path_index.append(self.paranodes1)
                self.paranodes1 += 1
                self.Nsec += 1
                # increment the prov length and check it is not to much
                if prov_length + self.paralength1 >= self.L:
                    # it will be the last section to add to the axon
                    self.last_section_kind = "MYSA"
                    self.last_section_size = self.L - prov_length
                prov_length += self.paralength1
                # CONNECT THE MYSA TO ITS PARENT: a MYSA can have for parent a node or a FLUT, check it"s not the start...
                connect_to_type = self.this_ax_sequence[
                    pos_in_sequence - 1
                ]  # get parent type
                if connect_to_type == "node" and self.axonnodes != 0:
                    self.MYSA[-1].connect(self.node[-1], 1, 0)
                if connect_to_type == "FLUT" and self.paranodes2 != 0:
                    self.MYSA[-1].connect(self.FLUT[-1], 1, 0)

            elif self.this_ax_sequence[pos_in_sequence] == "FLUT":
                # you need to create a FLUT
                self.FLUT.append(neuron.h.Section(name="FLUT[%d]" % self.paranodes2))
                self.axon_path_type.append("FLUT")
                self.axon_path_index.append(self.paranodes2)
                self.paranodes2 += 1
                self.Nsec += 1
                # increment the prov length and check it is not to much
                if prov_length + self.paralength2 >= self.L:
                    # it will be the last section to add to the axon
                    self.last_section_kind = "FLUT"
                    self.last_section_size = self.L - prov_length
                prov_length += self.paralength2
                # CONNECT THE FLUT TO ITS PARENT: a FLUT can have for parent a MYSA of a STIN, check it"s not the start...
                connect_to_type = self.this_ax_sequence[
                    pos_in_sequence - 1
                ]  # get parent type
                if connect_to_type == "MYSA" and self.paranodes1 != 0:
                    self.FLUT[-1].connect(self.MYSA[-1], 1, 0)
                if connect_to_type == "STIN" and self.axoninter != 0:
                    self.FLUT[-1].connect(self.STIN[-1], 1, 0)

            else:
                # you need to create a STIN
                self.STIN.append(neuron.h.Section(name="STIN[%d]" % self.axoninter))
                self.axon_path_type.append("STIN")
                self.axon_path_index.append(self.axoninter)
                self.axoninter += 1
                self.Nsec += 1
                # increment the prov length and check it is not to much
                if prov_length + self.interlength >= self.L:
                    # it will be the last section to add to the axon
                    self.last_section_kind = "STIN"
                    self.last_section_size = self.L - prov_length
                prov_length += self.interlength
                # CONNECT THE STIN TO ITS PARENT: a STIN can have for parent a STIN od a FLUT, check it"s not the start...
                connect_to_type = self.this_ax_sequence[pos_in_sequence - 1]
                if connect_to_type == "STIN" and self.axoninter > 1:
                    self.STIN[-1].connect(self.STIN[-2], 1, 0)
                if connect_to_type == "FLUT" and self.paranodes2 != 0:
                    self.STIN[-1].connect(self.FLUT[-1], 1, 0)

        if self.node == []:
            rise_warning(
                "Warning, myelinated axon without node... this can cause latter errors and is maybe unwanted ?\n"
            )
            # logging.warning("Warning, myelinated axon without node... this can cause latter errors and is maybe unwanted ?\n")

        ####################
        ## programm model ##
        ####################
        self.__set_model(self.model)
        # adjust the length of the first section
        if self.this_ax_sequence[0] == "node":
            self.node[0].L = self.first_section_size
        elif self.this_ax_sequence[0] == "MYSA":
            self.MYSA[0].L = self.first_section_size
        elif self.this_ax_sequence[0] == "FLUT":
            self.FLUT[0].L = self.first_section_size
        else:  # should be a STIN
            self.STIN[0].L = self.first_section_size
        # adjust the length if the last section
        if self.last_section_kind == "node":
            self.node[-1].L = self.last_section_size
        elif self.last_section_kind == "MYSA":
            self.MYSA[-1].L = self.last_section_size
        elif self.last_section_kind == "FLUT":
            self.FLUT[-1].L = self.last_section_size
        else:  # should be a STIN
            self.STIN[-1].L = self.last_section_size
        # define the geometry of the axon
        self._axon__define_shape()
        # define the number of segments
        self.__set_Nseg()
        # get nodes positions
        self.__get_seg_positions()
        self.__get_rec_positions()

    def save(
        self,
        save=False,
        fname="axon.json",
        extracel_context=False,
        intracel_context=False,
        rec_context=False,
        blacklist=[],
    ):
        """
        Return axon as dictionary and eventually save it as json file

        Parameters
        ----------
        save    : bool
            if True, save in json files
        fname   : str
            Path and Name of the saving file, by default "axon.json"

        Returns
        -------
        ax_dic : dict
            dictionary containing all information
        """
        blacklist += ["node", "MYSA", "FLUT", "STIN"]
        return super().save(
            save=save,
            fname=fname,
            extracel_context=extracel_context,
            intracel_context=intracel_context,
            rec_context=rec_context,
            blacklist=blacklist,
        )

    def __set_model(self, model):
        """
        Set the double cable model. For internal use only.

        Parameters
        ----------
        model           : str
            choice of conductance double-cable based model, possibly:
                "MRG"           : see [1] for details
                "Gaines_motor"  : Gaines motor model, see [2]
                "Gaines_sensory": Gaines sensory model, see [2]

        Note
        ----
        scientific sources for models:
        [1] McIntyre CC, Richardson AG, and Grill WM. Modeling the excitability of mammalian nerve fibers: influence of afterpotentials on the recovery cycle. Journal of Neurophysiology 87:995-1006, 2002.
        [2] Gaines, J. L., Finn, K. E., Slopsema, J. P., Heyboer, L. A.,  Polasek, K. H. (2018). A model of motor and sensory axon activation in the median nerve using surface electrical stimulation. Journal of computational neuroscience, 45(1), 29-43.
        """
        for n in self.node:
            n.nseg = 1
            n.diam = self.nodeD
            n.L = self.nodelength
            n.Ra = self.rhoa / 10000
            n.cm = 2
            if model == "Gaines_sensory":
                n.insert("node_sensory")
            elif model == "Gaines_motor":
                n.insert("node_motor")
            else:
                n.insert("axnode")
            n.insert("extracellular")
            n.xraxial[0] = self.Rpn0
            n.xg[0] = 1e10
            n.xc[0] = 0
        for m in self.MYSA:
            m.nseg = 1
            m.diam = self.d
            m.L = self.paralength1
            m.Ra = self.rhoa * (1 / (self.paraD1 / self.d) ** 2) / 10000
            m.cm = 2 * self.paraD1 / self.d
            if model == "Gaines_sensory":
                m.insert("mysa_sensory")
            elif model == "Gaines_motor":
                m.insert("mysa_motor")
            else:
                m.insert("pas")
                m.g_pas = 0.001 * self.paraD1 / self.d
                m.e_pas = -80
            m.insert("extracellular")
            m.xraxial[0] = self.Rpn1
            m.xg[0] = self.mygm / (self.nl * 2)
            m.xc[0] = self.mycm / (self.nl * 2)
        for f in self.FLUT:
            f.nseg = 1
            f.diam = self.d
            f.L = self.paralength2
            f.Ra = self.rhoa * (1 / (self.paraD2 / self.d) ** 2) / 10000
            f.cm = 2 * self.paraD2 / self.d
            if model == "Gaines_sensory":
                f.insert("flut_sensory")
            elif model == "Gaines_motor":
                f.insert("flut_motor")
            else:
                f.insert("pas")
                f.g_pas = 0.0001 * self.paraD2 / self.d
                f.e_pas = -80
            f.insert("extracellular")
            f.xraxial[0] = self.Rpn2
            f.xg[0] = self.mygm / (self.nl * 2)
            f.xc[0] = self.mycm / (self.nl * 2)
        for s in self.STIN:
            s.nseg = 1
            s.diam = self.d
            s.L = self.interlength
            s.Ra = self.rhoa * (1 / (self.axonD / self.d) ** 2) / 10000
            s.cm = 2 * self.axonD / self.d
            if model == "Gaines_sensory":
                s.insert("stin_sensory")
            elif model == "Gaines_motor":
                s.insert("stin_motor")
            else:
                s.insert("pas")
                s.g_pas = 0.0001 * self.axonD / self.d
                s.e_pas = -80
            s.insert("extracellular")
            s.xraxial[0] = self.Rpx
            s.xg[0] = self.mygm / (self.nl * 2)
            s.xc[0] = self.mycm / (self.nl * 2)

    def set_Markov_Nav(self, list_of_nodes=[]):
        """
        Change typical particle-Na sodium in Hodgking-Huxley formalism to Markov-channel population model.

        Parameters
        ----------
        list_of_nodes   : list, array, np.array
            list of Nodes of Ranier to modify, if empty, all nodes sodium channels are changed

        Note
        ----
        based on:
        Yi, G., and Grill, W. M. (2020). Kilohertz waveforms optimized to produce closed-state Na+ channel inactivation eliminate onset response in nerve conduction block. PLoS computational biology, 16(6), e1007766.

        !!! TO USE WITH CAUTION !!!
        """
        if list_of_nodes == []:
            list_of_nodes = np.arange(self.axonnodes)
        if self.model == "MRG":
            for NoR in list_of_nodes:
                self.node[NoR].gnapbar_axnode = 0
                self.node[NoR].gnabar_axnode = 0
        elif self.model == "Gaines_motor":
            for NoR in list_of_nodes:
                self.node[NoR].gnapbar_node_motor = 0
                self.node[NoR].gnabar_node_motor = 0
        elif self.model == "Gaines_sensory":
            for NoR in list_of_nodes:
                self.node[NoR].gnapbar_node_sensory = 0
                self.node[NoR].gnabar_node_sensory = 0
        # insert channel specific mechanisms
        # Nav1.1 models fast sodium
        # Nav1.6 models persistant sodium, to be confirmed, Warning gnabar is different than MRG and Gaines
        for NoR in list_of_nodes:
            # insert Nav1.1 model
            self.node[NoR].insert("na11a")
            self.node[NoR].gbar_na11a = 11.9
            # insert Nav1.6 model
            self.node[NoR].insert("na16a")
            self.node[NoR].gbar_na16a = 0.01
            ## WARINING -  Sodium Nernst potential is defined for axnode as ena_axnode, and is common
            ## for fast and persistant sodium channels (see lines 98 and 99 in AXNOE.mod)
            ## This is not the case for Nav1.x, which uses the NEURON instruction USEION na READ ena
            ## meaning the following line is tuning directly the na11a and na16a mechanisms
            self.node[NoR].ena = 50
        self.Markov_Nav_modeled_NoR = list_of_nodes

    def __set_Nseg(self):
        """
        Set the number of segments automatically acording initialization. For internal use only.
        """

        if type(self.Nseg_per_sec) is dict:
            for n in self.node:
                n.nseg = self.Nseg_per_sec["node"]
            for m in self.MYSA:
                m.nseg = self.Nseg_per_sec["MYSA"]
            for f in self.FLUT:
                f.nseg = self.Nseg_per_sec["FLUT"]
            for s in self.STIN:
                s.nseg = self.Nseg_per_sec["STIN"]
            self.node_Nseg = self.axonnodes * self.Nseg_per_sec["node"]
            self.MYSA_Nseg = self.paranodes1 * self.Nseg_per_sec["MYSA"]
            self.FLUT_Nseg = self.paranodes2 * self.Nseg_per_sec["FLUT"]
            self.STIN_Nseg = self.axoninter * self.Nseg_per_sec["STIN"]

        elif self.Nseg_per_sec != 0:
            # all sections will have a fixed number of segment chosen by user
            for n in self.node:
                n.nseg = self.Nseg_per_sec
            for m in self.MYSA:
                m.nseg = self.Nseg_per_sec
            for f in self.FLUT:
                f.nseg = self.Nseg_per_sec
            for s in self.STIN:
                s.nseg = self.Nseg_per_sec
            self.node_Nseg = self.axonnodes * self.Nseg_per_sec
            self.MYSA_Nseg = self.paranodes1 * self.Nseg_per_sec
            self.FLUT_Nseg = self.paranodes2 * self.Nseg_per_sec
            self.STIN_Nseg = self.axoninter * self.Nseg_per_sec
        else:
            # here comes the dlambda rule
            self.node_Nseg = 0
            self.MYSA_Nseg = 0
            self.FLUT_Nseg = 0
            self.STIN_Nseg = 0
            if self.freq_min == 0:
                # uniform meshing
                for n in self.node:
                    Nseg = d_lambda_rule(n.L, self.d_lambda, self.freq, n)
                    n.nseg = Nseg
                    self.node_Nseg += Nseg
                for m in self.MYSA:
                    Nseg = Nseg = d_lambda_rule(m.L, self.d_lambda, self.freq, m)
                    m.nseg = Nseg
                    self.MYSA_Nseg += Nseg
                for f in self.FLUT:
                    Nseg = Nseg = d_lambda_rule(f.L, self.d_lambda, self.freq, f)
                    f.nseg = Nseg
                    self.FLUT_Nseg += Nseg
                for s in self.STIN:
                    Nseg = Nseg = d_lambda_rule(s.L, self.d_lambda, self.freq, s)
                    s.nseg = Nseg
                    self.STIN_Nseg += Nseg
            else:
                # non-uniform meshing
                freqs = create_Nseg_freq_shape(
                    self.Nsec, self.mesh_shape, self.freq, self.freq_min, self.alpha_max
                )
                for k in range(len(self.axon_path_type)):
                    sec_type = self.axon_path_type[k]
                    sec_index = self.axon_path_index[k]
                    if sec_type == "node":
                        Nseg = d_lambda_rule(
                            self.node[sec_index].L,
                            self.d_lambda,
                            freqs[k],
                            self.node[sec_index],
                        )
                        self.node[sec_index].nseg = Nseg
                        self.node_Nseg += Nseg
                    elif sec_type == "MYSA":
                        Nseg = d_lambda_rule(
                            self.MYSA[sec_index].L,
                            self.d_lambda,
                            freqs[k],
                            self.MYSA[sec_index],
                        )
                        self.MYSA[sec_index].nseg = Nseg
                        self.MYSA_Nseg += Nseg
                    elif sec_type == "FLUT":
                        Nseg = d_lambda_rule(
                            self.FLUT[sec_index].L,
                            self.d_lambda,
                            freqs[k],
                            self.FLUT[sec_index],
                        )
                        self.FLUT[sec_index].nseg = Nseg
                        self.FLUT_Nseg += Nseg
                    else:  # should be STIN
                        Nseg = d_lambda_rule(
                            self.STIN[sec_index].L,
                            self.d_lambda,
                            freqs[k],
                            self.STIN[sec_index],
                        )
                        self.STIN[sec_index].nseg = Nseg
                        self.STIN_Nseg += Nseg
        self.Nseg = self.node_Nseg + self.MYSA_Nseg + self.FLUT_Nseg + self.STIN_Nseg

    def __get_seg_positions(self):
        """
        Get segment positions, for internal use only.
        """
        x_offset = 0
        x = []
        x_nodes = []
        nodes_index = []
        self.rec_position_list = []
        for k in range(len(self.axon_path_type)):
            sec_type = self.axon_path_type[k]
            sec_index = self.axon_path_index[k]
            self.rec_position_list.append([])
            if sec_type == "node":
                x_nodes.append(x_offset + self.node[sec_index].L / 2)
                for seg in self.node[sec_index].allseg():
                    if x == []:
                        x.append(seg.x * (self.node[sec_index].L) + x_offset)
                        nodes_index.append(0)
                        self.rec_position_list[-1].append(seg.x)
                    else:
                        x_seg = seg.x * (self.node[sec_index].L) + x_offset
                        if x_seg != x[-1]:
                            x.append(x_seg)
                            nodes_index.append(len(x) - 1)
                            self.rec_position_list[-1].append(seg.x)
                x_offset += self.node[sec_index].L
            elif sec_type == "MYSA":
                for seg in self.MYSA[sec_index].allseg():
                    if x == []:
                        x.append(seg.x * (self.MYSA[sec_index].L) + x_offset)
                        self.rec_position_list[-1].append(seg.x)
                    else:
                        x_seg = seg.x * (self.MYSA[sec_index].L) + x_offset
                        if x_seg != x[-1]:
                            x.append(x_seg)
                            self.rec_position_list[-1].append(seg.x)
                x_offset += self.MYSA[sec_index].L
            elif sec_type == "FLUT":
                for seg in self.FLUT[sec_index].allseg():
                    if x == []:
                        x.append(seg.x * (self.FLUT[sec_index].L) + x_offset)
                        self.rec_position_list[-1].append(seg.x)
                    else:
                        x_seg = seg.x * (self.FLUT[sec_index].L) + x_offset
                        if x_seg != x[-1]:
                            x.append(x_seg)
                            self.rec_position_list[-1].append(seg.x)
                x_offset += self.FLUT[sec_index].L
            else:  # should be STIN
                for seg in self.STIN[sec_index].allseg():
                    if x == []:
                        x.append(seg.x * (self.STIN[sec_index].L) + x_offset)
                        self.rec_position_list[-1].append(seg.x)
                    else:
                        x_seg = seg.x * (self.STIN[sec_index].L) + x_offset
                        if x_seg != x[-1]:
                            x.append(x_seg)
                            self.rec_position_list[-1].append(seg.x)
                x_offset += self.STIN[sec_index].L
        self.x = np.asarray(x)
        self.x_nodes = np.asarray(x_nodes)
        self.node_index = np.asarray(nodes_index)

    def __get_rec_positions(self):
        """
        Get the position of points with voltage recording. For internal use only.
        """
        if self.rec == "nodes":
            self.x_rec = self.x_nodes
        else:
            self.x_rec = self.x

    ###############################
    ## Intracellular stimulation ##
    ###############################
    def insert_I_Clamp_node(self, index, t_start, duration, amplitude):
        """
        Insert a IC clamp stimulation on a Ranvier node at its midd point position

        Parameters
        ----------
        index       : int
            node number of the node to stimulate
        t_start     : float
            starting time (ms)
        duration    : float
            duration of the pulse(ms)
        amplitude   : float
            amplitude of the pulse (nA)
        """
        # add the stimulation to the axon
        self.intra_current_stim.append(neuron.h.IClamp(0.5, sec=self.node[index]))
        # modify the stimulation parameters
        self.intra_current_stim[-1].delay = t_start
        self.intra_current_stim[-1].dur = duration
        self.intra_current_stim[-1].amp = amplitude
        # save the stimulation parameter for results
        self.intra_current_stim_positions.append(self.x_nodes[index])
        self.intra_current_stim_starts.append(t_start)
        self.intra_current_stim_durations.append(duration)
        self.intra_current_stim_amplitudes.append(amplitude)

    def insert_I_Clamp(self, position, t_start, duration, amplitude):
        """
        Insert a IC clamp stimulation at the midd point of the nearest node to the specified position

        Parameters
        ----------
        position    : float
            relative position over the axon
        t_start     : float
            starting time (ms)
        duration    :
            duration of the pulse(ms)
        amplitude   :
            amplitude of the pulse (nA)
        """
        # adapt position to the number of sections
        index = round((position * (self.axonnodes - 1) + 0.5))
        self.insert_I_Clamp_node(index, t_start, duration, amplitude)

    def insert_V_Clamp_node(self, index, stimulus):
        """
        Insert a V clamp stimulation

        Parameters
        ----------
        index       : int
            node number of the node to stimulate
        stimulus    : stimulus object
            stimulus for the clamp, see Stimulus.py for more information
        """
        # add the stimulation to the axon
        self.intra_voltage_stim = neuron.h.VClamp(0.5, sec=self.node[index])
        # save the stimulation parameter for results
        self.intra_voltage_stim_position.append(self.x_nodes[index])
        # save the stimulus for later use
        self.intra_voltage_stim_stimulus = stimulus
        # set fake duration
        self.intra_voltage_stim.dur[0] = 1e9

    def insert_V_Clamp(self, position, stimulus):
        """
        Insert a V clamp stimulation at the midd point of the nearest node to the specified position

        Parameters
        ----------
        position    : float
            relative position over the axon
        stimulus    : stimulus object
            stimulus for the clamp, see Stimulus.py for more information
        """
        # adapt position to the number of sections
        index = round((position * (self.axonnodes - 1) + 0.5))
        self.insert_V_Clamp_node(index, stimulus)

    ##############################
    ## Result recording methods ##
    ##############################
    def __set_recorders_with_key(
        self,
        reclist,
        key=None,
        single_mod=True,
        key_node=None,
        key_MYSA=None,
        key_FLUT=None,
        key_STIN=None,
    ):
        """
        To automate the methods set_recorder. For internal use only.
        Parameters
        ----------
        reclist     : neuron.h.List
            List in witch the reccorders should be saved
        key         : str
            Name of the key which should be recorded, if None result of the recording set to [0],
            by default None
        single_mod  : bool
            if True key will be used for all the section, else specific key will be used in each
            section, by default True
        key_node    : str
            Name of the key which should be recorded in node, if None result of the recording set
            to [0], by default None
        key_MYSA    : str
            Name of the key which should be recorded in MYSA, if None result of the recording set
            to [0], by default None
        key_FLUT    : str
            Name of the key which should be recorded in FLUT, if None result of the recording set
            to [0], by default None
        key_STIN    : str
            Name of the key which should be recorded in STIN, if None result of the recording set
            to [0], by default None
        """
        if single_mod:
            key_node, key_MYSA, key_FLUT, key_STIN = key, key, key, key

        if self.rec == "nodes":
            # recording only on middle of all nodes
            for n in self.node:
                if key_node is not None:
                    rec = neuron.h.Vector().record(getattr(n(0.5), key_node), sec=n)
                else:
                    rec = neuron.h.Vector([0])
                reclist.append(rec)
        else:
            # recording on all segments
            for k in range(len(self.axon_path_type)):
                sec_type = self.axon_path_type[k]
                sec_index = self.axon_path_index[k]
                for position in self.rec_position_list[k]:
                    if sec_type == "node":
                        if key_node is not None:
                            rec = neuron.h.Vector().record(
                                getattr(self.node[sec_index](position), key_node),
                                sec=self.node[sec_index],
                            )
                        else:
                            rec = neuron.h.Vector([0])
                        reclist.append(rec)
                    elif sec_type == "MYSA":
                        if key_MYSA is not None:
                            rec = neuron.h.Vector().record(
                                getattr(self.MYSA[sec_index](position), key_MYSA),
                                sec=self.MYSA[sec_index],
                            )
                        else:
                            rec = neuron.h.Vector([0])
                        reclist.append(rec)
                    elif sec_type == "FLUT":
                        if key_FLUT is not None:
                            rec = neuron.h.Vector().record(
                                getattr(self.FLUT[sec_index](position), key_FLUT),
                                sec=self.FLUT[sec_index],
                            )
                        else:
                            rec = neuron.h.Vector([0])
                        reclist.append(rec)
                    else:  # should be STIN
                        if key_STIN is not None:
                            rec = neuron.h.Vector().record(
                                getattr(self.STIN[sec_index](position), key_STIN),
                                sec=self.STIN[sec_index],
                            )
                        else:
                            rec = neuron.h.Vector([0])
                        reclist.append(rec)

    def __get_recorders_from_list(self, reclist):
        """
        Convert reclist in np.array To automate methods set_recorder. For internal use only.
        Parameters
        ----------
        reclist     : neuron.h.List
            List in witch the reccorders are saved

        Returns
        -------
        val         : np.array
            array of every recorded value for all rec point and time
        """
        if self.rec == "nodes":
            dim = (self.axonnodes, self.t_len)
        else:
            dim = (len(self.x_rec), self.t_len)

        val = np.zeros(dim)
        for k in range(dim[0]):
            val[k, :] = np.asarray(reclist[k])
        return val

    def __get_var_from_mod(
        self,
        key=None,
        single_mod=True,
        key_node=None,
        key_MYSA=None,
        key_FLUT=None,
        key_STIN=None,
    ):
        """
        return a column with value in every recording point of a constant from a mod. For internal use only.
        """
        if single_mod:
            key_node, key_MYSA, key_FLUT, key_STIN = key, key, key, key

        if self.rec == "nodes":
            val = np.zeros(len(self.node))
            if key_node is not None:
                val[:] = getattr(self.node[0](0.5), key_node)[0]
        else:
            val = np.zeros((len(self.x_rec)))
            i = -1
            for k in range(len(self.axon_path_type)):
                sec_type = self.axon_path_type[k]
                sec_index = self.axon_path_index[k]
                for position in self.rec_position_list[k]:
                    i += 1
                    if sec_type == "node":
                        if key_node is not None:
                            v = getattr(self.node[sec_index](position), key_node)[0]
                    elif sec_type == "MYSA":
                        if key_MYSA is not None:
                            v = getattr(self.MYSA[sec_index](position), key_MYSA)[0]
                    elif sec_type == "FLUT":
                        if key_FLUT is not None:
                            v = getattr(self.FLUT[sec_index](position), key_FLUT)[0]
                    else:  # should be STIN
                        if key_STIN is not None:
                            v = getattr(self.STIN[sec_index](position), key_STIN)[0]
                    if isinstance(v, float):
                        val[i] = v
                    else:   # needed for xc, xg, xraxial (see if it can be simplify)
                        val[i] = v[0]
        return val

    def set_membrane_voltage_recorders(self):
        """
        Prepare the membrane voltage recording. For internal use only.
        """
        self.v_reclist = neuron.h.List()
        key = "_ref_v"
        self.__set_recorders_with_key(self.v_reclist, key)

    def get_membrane_voltage(self):
        """
        get the membrane voltage at the end of simulation. For internal use only.
        """
        return self.__get_recorders_from_list(self.v_reclist)

    def set_membrane_current_recorders(self):
        """
        Prepare the membrane current recording. For internal use only.
        """
        self.i_reclist = neuron.h.List()
        key = "_ref_i_membrane"
        self.__set_recorders_with_key(self.i_reclist, key)

    def get_membrane_current(self):
        """
        get the membrane current at the end of simulation. For internal use only.
        """
        return self.__get_recorders_from_list(self.i_reclist)

    def set_ionic_current_recorders(self):
        """
        Prepare the ionic channels current recording. For internal use only.
        """
        if self.model == "MRG":
            self.ina_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.ina_reclist, single_mod=False, key_node="_ref_ina_axnode"
            )
            self.inap_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.inap_reclist, single_mod=False, key_node="_ref_inap_axnode"
            )
            self.ik_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.ik_reclist, single_mod=False, key_node="_ref_ik_axnode"
            )
            self.il_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.il_reclist, single_mod=False, key_node="_ref_il_axnode"
            )
        else:  # should be Gaines, motor or sensory
            if self.model == "Gaines_motor":
                key_mod = "_motor"
            else:
                key_mod = "_sensory"
            self.ina_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.ina_reclist, single_mod=False, key_node="_ref_ina_node" + key_mod
            )

            self.inap_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.inap_reclist, single_mod=False, key_node="_ref_inap_node" + key_mod
            )

            self.ik_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.ik_reclist,
                single_mod=False,
                key_node="_ref_ik_node" + key_mod,
                key_MYSA="_ref_ik_mysa" + key_mod,
                key_FLUT="_ref_ik_flut" + key_mod,
                key_STIN="_ref_ik_stin" + key_mod,
            )

            self.ikf_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.ikf_reclist,
                single_mod=False,
                key_node="_ref_ikf_node" + key_mod,
                key_MYSA="_ref_ikf_mysa" + key_mod,
                key_FLUT="_ref_ikf_flut" + key_mod,
                key_STIN="_ref_ikf_stin" + key_mod,
            )

            self.iq_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.iq_reclist,
                single_mod=False,
                key_MYSA="_ref_iq_mysa" + key_mod,
                key_FLUT="_ref_iq_flut" + key_mod,
                key_STIN="_ref_iq_stin" + key_mod,
            )

            self.il_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.il_reclist,
                single_mod=False,
                key_node="_ref_il_node" + key_mod,
                key_MYSA="_ref_il_mysa" + key_mod,
                key_FLUT="_ref_il_flut" + key_mod,
                key_STIN="_ref_il_stin" + key_mod,
            )

    def get_ionic_current(self):
        """
        get the ionic channels currents at the end of simulation. For internal use only.
        """
        results = []
        results += [self.__get_recorders_from_list(self.ina_reclist)]
        results += [self.__get_recorders_from_list(self.inap_reclist)]
        results += [self.__get_recorders_from_list(self.ik_reclist)]
        if self.model == "Gaines_motor" or self.model == "Gaines_sensory":
            results += [self.__get_recorders_from_list(self.ikf_reclist)]
            results += [self.__get_recorders_from_list(self.iq_reclist)]
        results += [self.__get_recorders_from_list(self.il_reclist)]
        return results

    def set_particules_values_recorders(self):
        """
        Prepare the particules current recording. For internal use only.
        """
        if self.model == "MRG":
            self.m_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.m_reclist, single_mod=False, key_node="_ref_m_axnode"
            )
            self.mp_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.mp_reclist, single_mod=False, key_node="_ref_mp_axnode"
            )
            self.h_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.h_reclist, single_mod=False, key_node="_ref_h_axnode"
            )
            self.s_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.s_reclist, single_mod=False, key_node="_ref_s_axnode"
            )

        else:  # should be Gaines, motor or sensory
            if self.model == "Gaines_motor":
                key_mod = "_motor"
            else:
                key_mod = "_sensory"

            self.m_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.m_reclist, single_mod=False, key_node="_ref_m_node" + key_mod
            )

            self.mp_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.mp_reclist, single_mod=False, key_node="_ref_mp_node" + key_mod
            )

            self.h_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.h_reclist, single_mod=False, key_node="_ref_h_node" + key_mod
            )

            self.s_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.s_reclist,
                single_mod=False,
                key_node="_ref_s_node" + key_mod,
                key_MYSA="_ref_s_mysa" + key_mod,
                key_FLUT="_ref_s_flut" + key_mod,
                key_STIN="_ref_s_stin" + key_mod,
            )

            self.n_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.n_reclist,
                single_mod=False,
                key_node="_ref_n_node" + key_mod,
                key_MYSA="_ref_n_mysa" + key_mod,
                key_FLUT="_ref_n_flut" + key_mod,
                key_STIN="_ref_n_stin" + key_mod,
            )

            self.q_reclist = neuron.h.List()
            self.__set_recorders_with_key(
                self.q_reclist,
                single_mod=False,
                key_MYSA="_ref_q_mysa" + key_mod,
                key_FLUT="_ref_q_flut" + key_mod,
                key_STIN="_ref_q_stin" + key_mod,
            )

    def get_particles_values(self):
        """
        get the particules values at the end of simulation. For internal use only.
        """
        results = []
        results += [self.__get_recorders_from_list(self.m_reclist)]
        results += [self.__get_recorders_from_list(self.mp_reclist)]
        results += [self.__get_recorders_from_list(self.h_reclist)]
        results += [self.__get_recorders_from_list(self.s_reclist)]
        if self.model == "Gaines_motor" or self.model == "Gaines_sensory":
            results += [self.__get_recorders_from_list(self.n_reclist)]
            results += [self.__get_recorders_from_list(self.q_reclist)]
        return results

    def set_conductance_recorders(self):
        """
        Prepare the ionic channels conductance recording. For internal use only.
        """
        self.gna_reclist = neuron.h.List()
        self.gnap_reclist = neuron.h.List()
        self.gk_reclist = neuron.h.List()
        self.gl_reclist = neuron.h.List()
        if self.model == "MRG":
            self.__set_recorders_with_key(
                self.gna_reclist, single_mod=False, key_node="_ref_gna_axnode"
            )
            self.__set_recorders_with_key(
                self.gnap_reclist, single_mod=False, key_node="_ref_gnap_axnode"
            )
            self.__set_recorders_with_key(
                self.gk_reclist, single_mod=False, key_node="_ref_gk_axnode"
            )
            self.__set_recorders_with_key(
                self.gl_reclist, single_mod=False, key_node="_ref_gl_axnode"
            )
            if not self.rec == "nodes":
                self.gi_reclist = neuron.h.List()
                self.__set_recorders_with_key(
                    self.gi_reclist,
                    single_mod=False,
                    key_MYSA="_ref_g_pas",
                    key_FLUT="_ref_g_pas",
                    key_STIN="_ref_g_pas",
                )
        else:  # should be Gaines motor or sensory
            self.gkf_reclist = neuron.h.List()
            self.gq_reclist = neuron.h.List()
            if self.model == "Gaines_motor":
                key_mod = "_motor"
            else:
                key_mod = "_sensory"
            self.__set_recorders_with_key(
                self.gna_reclist,
                single_mod=False,
                key_node="_ref_gna_node" + key_mod,
            )
            self.__set_recorders_with_key(
                self.gnap_reclist,
                single_mod=False,
                key_node="_ref_gnap_node" + key_mod,
            )
            self.__set_recorders_with_key(
                self.gk_reclist,
                single_mod=False,
                key_node="_ref_gk_node" + key_mod,
                key_MYSA="_ref_gk_mysa" + key_mod,
                key_FLUT="_ref_gk_flut" + key_mod,
                key_STIN="_ref_gk_stin" + key_mod,
            )
            self.__set_recorders_with_key(
                self.gl_reclist,
                single_mod=False,
                key_node="_ref_gl_node" + key_mod,
                key_MYSA="_ref_gl_mysa" + key_mod,
                key_FLUT="_ref_gl_flut" + key_mod,
                key_STIN="_ref_gl_stin" + key_mod,
            )
            self.__set_recorders_with_key(
                self.gkf_reclist,
                single_mod=False,
                key_node="_ref_gkf_node" + key_mod,
                key_MYSA="_ref_gkf_mysa" + key_mod,
                key_FLUT="_ref_gkf_flut" + key_mod,
                key_STIN="_ref_gkf_stin" + key_mod,
            )
            if not self.rec == "nodes":
                self.__set_recorders_with_key(
                    self.gq_reclist,
                    single_mod=False,
                    key_MYSA="_ref_gq_mysa" + key_mod,
                    key_FLUT="_ref_gq_flut" + key_mod,
                    key_STIN="_ref_gq_stin" + key_mod,
                )

    def get_ionic_conductance(self):
        """
        get the ionic channels conductance at the end of simulation. For internal use only.
        """
        results = []
        results += [self.__get_recorders_from_list(self.gna_reclist)]
        results += [self.__get_recorders_from_list(self.gnap_reclist)]
        results += [self.__get_recorders_from_list(self.gk_reclist)]
        results += [self.__get_recorders_from_list(self.gl_reclist)]
        if self.model == "MRG":
            if self.rec == "nodes":
                results += [np.zeros((self.axonnodes, self.t_len))]
            else:
                results += [self.__get_recorders_from_list(self.gi_reclist)]
        else:  # should be Gaines, motor or sensory
            results += [self.__get_recorders_from_list(self.gkf_reclist)]
            if self.rec == "nodes":
                results += [np.zeros((self.axonnodes, self.t_len))]
            else:
                results += [self.__get_recorders_from_list(self.gq_reclist)]
        return results

    def get_membrane_conductance(self):
        """
        get the total membrane conductance at the end of simulation. For internal use only.
        """
        return sum(self.get_ionic_conductance())

    def get_membrane_capacitance(self):
        """
        get the membrane capacitance
        NB: [uF/cm^{2}] (see Neuron unit)
        """
        return self.__get_var_from_mod("_ref_cm")

    def get_myeline_conductance(self):
        """
        get the membrane capacitance
        NB: [S/cm^{2}] (see Neuron unit)
        """
        return self.__get_var_from_mod("_ref_xg")

    def get_myeline_capacitance(self):
        """
        get the membrane capacitance
        NB: [uF/cm^{2}] (see Neuron unit)
        """
        return self.__get_var_from_mod("_ref_xc")

    def set_Nav_recorders(self):
        """
        Prepare the markov model recording. For internal use only.
        """
        # Nav1.1 variables
        self.I_nav11_reclist = neuron.h.List()
        self.C1_nav11_reclist = neuron.h.List()
        self.C2_nav11_reclist = neuron.h.List()
        self.O1_nav11_reclist = neuron.h.List()
        self.O2_nav11_reclist = neuron.h.List()
        self.I1_nav11_reclist = neuron.h.List()
        self.I2_nav11_reclist = neuron.h.List()
        # Nav1.6 variables
        self.I_nav16_reclist = neuron.h.List()
        self.C1_nav16_reclist = neuron.h.List()
        self.C2_nav16_reclist = neuron.h.List()
        self.O1_nav16_reclist = neuron.h.List()
        self.O2_nav16_reclist = neuron.h.List()
        self.I1_nav16_reclist = neuron.h.List()
        self.I2_nav16_reclist = neuron.h.List()
        for k in self.Markov_Nav_modeled_NoR:
            # Nav1.1
            I_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_ina_na11a, sec=self.node[k]
            )
            C1_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_C1_na11a, sec=self.node[k]
            )
            C2_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_C2_na11a, sec=self.node[k]
            )
            O1_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_O1_na11a, sec=self.node[k]
            )
            O2_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_O2_na11a, sec=self.node[k]
            )
            I1_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_I1_na11a, sec=self.node[k]
            )
            I2_nav11 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_I2_na11a, sec=self.node[k]
            )
            self.I_nav11_reclist.append(I_nav11)
            self.C1_nav11_reclist.append(C1_nav11)
            self.C2_nav11_reclist.append(C2_nav11)
            self.O1_nav11_reclist.append(O1_nav11)
            self.O2_nav11_reclist.append(O2_nav11)
            self.I1_nav11_reclist.append(I1_nav11)
            self.I2_nav11_reclist.append(I2_nav11)
            # Nav1.6
            I_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_ina_na16a, sec=self.node[k]
            )
            C1_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_C1_na16a, sec=self.node[k]
            )
            C2_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_C2_na16a, sec=self.node[k]
            )
            O1_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_O1_na16a, sec=self.node[k]
            )
            O2_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_O2_na16a, sec=self.node[k]
            )
            I1_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_I1_na16a, sec=self.node[k]
            )
            I2_nav16 = neuron.h.Vector().record(
                self.node[k](0.5)._ref_I2_na16a, sec=self.node[k]
            )
            self.I_nav16_reclist.append(I_nav16)
            self.C1_nav16_reclist.append(C1_nav16)
            self.C2_nav16_reclist.append(C2_nav16)
            self.O1_nav16_reclist.append(O1_nav16)
            self.O2_nav16_reclist.append(O2_nav16)
            self.I1_nav16_reclist.append(I1_nav16)
            self.I2_nav16_reclist.append(I2_nav16)

    def get_Nav_values(self):
        """
        get the markov model at the end of simulation. For internal use only.
        """
        # Nav1.1 values
        I_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        C1_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        C2_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        O1_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        O2_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        I1_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        I2_nav11_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        for k in range(len(self.I_nav11_reclist)):
            I_nav11_ax[k, :] = np.asarray(self.I_nav11_reclist[k])
            C1_nav11_ax[k, :] = np.asarray(self.C1_nav11_reclist[k])
            C2_nav11_ax[k, :] = np.asarray(self.C2_nav11_reclist[k])
            O1_nav11_ax[k, :] = np.asarray(self.O1_nav11_reclist[k])
            O2_nav11_ax[k, :] = np.asarray(self.O2_nav11_reclist[k])
            I1_nav11_ax[k, :] = np.asarray(self.I1_nav11_reclist[k])
            I2_nav11_ax[k, :] = np.asarray(self.I2_nav11_reclist[k])
        # Nav1.6 values
        I_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        C1_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        C2_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        O1_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        O2_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        I1_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        I2_nav16_ax = np.zeros((len(self.Markov_Nav_modeled_NoR), self.t_len))
        for k in range(len(self.I_nav11_reclist)):
            I_nav16_ax[k, :] = np.asarray(self.I_nav16_reclist[k])
            C1_nav16_ax[k, :] = np.asarray(self.C1_nav16_reclist[k])
            C2_nav16_ax[k, :] = np.asarray(self.C2_nav16_reclist[k])
            O1_nav16_ax[k, :] = np.asarray(self.O1_nav16_reclist[k])
            O2_nav16_ax[k, :] = np.asarray(self.O2_nav16_reclist[k])
            I1_nav16_ax[k, :] = np.asarray(self.I1_nav16_reclist[k])
            I2_nav16_ax[k, :] = np.asarray(self.I2_nav16_reclist[k])
        return (
            I_nav11_ax,
            C1_nav11_ax,
            C2_nav11_ax,
            O1_nav11_ax,
            O2_nav11_ax,
            I1_nav11_ax,
            I2_nav11_ax,
            I_nav16_ax,
            C1_nav16_ax,
            C2_nav16_ax,
            O1_nav16_ax,
            O2_nav16_ax,
            I1_nav16_ax,
            I2_nav16_ax,
        )
