model of DCN pyramidal neuron
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

1200 lines
46 KiB

from __future__ import print_function
import weakref
import numpy as np
import scipy.optimize
from collections import OrderedDict
import neuron
from neuron import h
from ..util import nstomho, mho2ns
from ..util import custom_init
from .. import synapses
from .. import data
from .. import morphology
from .. import decorator
"""
Term definitions:
cell class is the class of morphological cell: bushy, tstellate, etc.
Each cell class is implmeneted as a separate python class (no pun)
modelName is name of the source model used so it is like the type, but one level up).
ModelNames are RM03, XM13, and for other cell types may refer to the original model,
such as POK (Kanold pyramidal cell), MCG (McGinley octopus), Eager, etc.
These model designations may have only one model type (POK), or may have multiple types (RM03, XM13)
modelType refers to the Rothman and Manis 2003 model classes (I, II, I-c, I-t, II-1, I-II, etc)
These are physiologically based, but in the ion channel tables are mapped to morphological classes sort of,
"""
class Cell(object):
"""
Base class for all cell types.
"""
type = None
# create a lookup table to map sections to their parent cell
sec_lookup = weakref.WeakValueDictionary()
@classmethod
def from_section(cls, sec):
return cls.sec_lookup[sec.name()]
def __init__(self):
# dictionary of all sections associated with this cell
self.hr = None # hoc reader - e.g., we have read a morphology file.
self.all_sections = {}
# the following section types (parts) are known to us:
for k in [
"soma",
"maindend",
"secdend",
"dend",
"dendrite",
"primarydendrite",
"secondarydendrite",
"internode",
"initialsegment",
"axonnode",
"axon",
"unmyelinatedaxon",
"myelinatedaxon",
"hillock",
]:
self.all_sections[k] = [] # initialize to an empty list
self.species = "mouse"
self.status = {} # dictionary of parameters used to instantiate the cell.
# Record synaptic inputs and projections
self.inputs = [] # inputs are recorded - synapse object, post_opts and kwds
self.outputs = []
self.initial_mechanisms = None
# each cell has the following parameters:
self.totcap = None # total membrane capacitance (somatic)
self.somaarea = None # total soma area
self.initsegment = None # hold initial segment sections
self.axnode = None # hold nodes of ranvier sections
self.internode = None # hold internode sections
self.maindend = None # hold main dendrite sections
self.secdend = None # hold secondary dendrite sections
self.dendrite = None
self.axon = None
self.axonsf = None # axon diameter scale factor
# define defaults for these parameters (RM03 model defaults)
self.e_k = -70 # potassium reversal potential, mV
self.e_na = 55
self.e_h = -43
self.c_m = 0.9 # specific membrane capacitance, uf/cm^2
self.R_a = 150 # axial resistivity of cytoplasm/axoplasm, ohm.cm
self.e_leak = -65
# Recommended current (min, max, step) for testing this cell
self.i_test_range = (
-0.5,
0.5,
0.05,
) # defines default current steps for IC curve
# Recommended threshold for detecting spikes from this cell
self.spike_threshold = -40
# Resting potential for this cell, determined by calling
# self.find_i0()
self.vm0 = None
def check_temperature(self):
if self.status["temperature"] not in self._valid_temperatures:
tstring = ", ".join("%3.1f " % t for t in self._valid_temperatures)
raise ValueError(
"Cell %s %s %s temperature %3.1f is invalid; must be in: [%s]"
% (
self.type,
self.status["species"],
self.status["modelType"],
self.status["temperature"],
tstring,
)
)
def set_temperature(self, temperature):
"""
Set the temperature setting for this cell.
"""
if self.status["decorator"] is None:
if self.status["temperature"] is None: # only if not already set
self.status["temperature"] = temperature
self.species_scaling(
species=self.status["species"], modelType=self.status["modelType"]
)
else:
self.status["temperature"] = temperature
# self.decorate() # call the decorator
def set_morphology(self, morphology_file=None):
"""
Set the cell's morphological structure from a file that defines sections
(for example, a morphology file read by neuronvis), or from a morphology
object that has already been retrieved/created.
Parameters
----------
morphology_file : string or morphology object (default: None)
File name/path for the morphology file (for example, .hoc or .swc file)
Alternatively, this can be a morphology object returned by the morphology class.
Returns
-------
nothing
"""
self.morphology_file = morphology_file # save the source file name
if isinstance(morphology_file, str):
if morphology_file.endswith(".hoc"):
self.morphology = morphology.HocReader(morphology_file)
elif morphology_file.endswith(".swc"):
self.morphology = morphology.SwcReader(morphology_file)
else:
raise ValueError("Unknown morphology file type [must be .hoc or .swc]")
elif isinstance(morphology_file, morphology.Morphology):
self.morphology = morphology_file
else:
print(morphology_file)
raise TypeError("Invalid morphology type: must be filename(str) or ")
self.hr = (
self.morphology
) # extensive renaming required in calling classes, temporary fix.
self.morphology.read_section_info() # not sure this is necessary...
# these were not instantiated when the file was read, but when the decorator was run.
for s in self.hr.sec_groups.keys():
for sec in self.hr.sec_groups[s]:
section = self.hr.get_section(sec)
mechs = self.hr.get_mechanisms(sec)
if s == "myelinatedaxon":
section.cm = 0.002
self.add_section(section, s) # add the section to the cell.
# print '\nmechanisms for section: %s', section
# self.print_mechs(section)
self.set_soma_size_from_Section(
self.soma
) # this is used for reporting and setting g values...
if isinstance(self.soma, list):
self.distances(self.soma[1])
else:
self.distances(self.soma)
self.hr.distanceMap = self.distanceMap
def add_section(self, sec, sec_type):
"""
Add a section (or list of sections) to this cell.
This adds the section to self.all_sections[sec_type] and also allows
the cell to be accessed from the section using
cells.cell_from_section().
Notes:
*sec_type* must be one of the keys already in self.all_sections.
This method does not connect sections together; that must be
done manually.
"""
if not isinstance(sec, list):
sec = [sec]
self.all_sections[sec_type].extend(sec)
for s in sec:
Cell.sec_lookup[s.name()] = self
def list_sections(self):
# print self.all_sections
print("Known Section names:")
for sec in self.all_sections:
print(" %s" % sec)
s = self.all_sections[sec]
# print 's: ', s
if len(s) > 0:
print(" ------------------------------------------")
print(" Sections present:")
for u in s:
print(
" Type: %s (%s, %s): %s"
% (
sec,
u.name(),
str(self.hr.get_section(u.name())),
Cell.sec_lookup[u.name()],
)
)
print(" ------------------------------------------")
else:
print(" No section of this type in cell")
def get_section_type(self, sec):
for s in self.all_sections:
if sec in self.all_sections[s]:
return s
return None
def get_post_sec(self, kwds):
"""
Get the postsynaptic section from the value of postsite
in kwds. This is typically called from the cell-specific make_psd method.
If the key 'postsite' is in the kwds dict, we look it up.
If not, then we use the soma section as a default instead.
Parameters
----------
kwds : dict
dictionary of keywords, may have a key 'postsite'
Returns:
loc, post_sec
the location (0-1) of the desired point process insertion, and
post_sec, the neuron section where that insertion will take place
"""
if (
"postsite" in kwds
): # use a defined location instead of the default (soma(0.5)
postsite = kwds["postsite"]
loc = postsite[1] # where on the section?
uname = (
"sections[%d]" % postsite[0]
) # make a name to look up the neuron section object
post_sec = self.hr.get_section(uname) # Tell us where to put the synapse.
else:
loc = 0.5
post_sec = self.soma
return loc, post_sec
def set_d_lambda(self, freq=100, d_lambda=0.1):
"""
Sets nseg in each section to an odd value so that its segments are no longer than
d_lambda x the AC length constant at frequency freq in that section.
The defaults are reasonable values for most models
Be sure to specify your own Ra and cm before calling geom_nseg()
To understand why this works,
and the advantages of using an odd value for nseg,
see Hines, M.L. and Carnevale, N.T. NEURON: a tool for neuroscientists. The Neuroscientist 7:123-135, 2001.
This is a python version of the hoc code.
Parameters
----------
freq : float, default=100. (Hz)
Frequency in Hz to use in computing nseg.
d_lambda : float, default=0.1
fraction of AC length constant for minimum segment length
"""
if self.hr is None: # no hoc reader file, so no adjustments
return
for st in self.all_sections.keys():
for i, section in enumerate(self.all_sections[st]):
nseg = (
int(
(section.L / (d_lambda * self._lambda_f(freq, section)) + 0.9)
/ 2
)
* 2
+ 1
)
if nseg < 3:
nseg = 3 # ensure at least 3 segments per section...
section.nseg = nseg
def _lambda_f(self, freq, section):
"""
get lambda_f for the section (internal)
Parameters
----------
freq : float, default=100. (Hz)
Frequency in Hz to use in computing nseg.
section : Neuron section object
Returns
-------
section length normalized by the length constant at freq.
"""
self.hr.h("access %s" % section.name())
if self.hr.h.n3d() < 2:
return 1e-5 * np.sqrt(
section.diam / (4.0 * np.pi * freq * section.Ra * section.cm)
)
# above was too inaccurate with large variation in 3d diameter
# so now we use all 3-d points to get a better approximate lambda
x1 = self.hr.h.arc3d(0)
d1 = self.hr.h.diam3d(0)
lam = 0.001
for i in range(int(self.hr.h.n3d()) - 1):
x2 = self.hr.h.arc3d(i)
d2 = self.hr.h.diam3d(i)
lam = lam + ((x2 - x1) / np.sqrt(d1 + d2))
x1 = x2
d1 = d2
# length of the section in units of lambda
lam = (
lam
* np.sqrt(2.0)
* 1e-5
* np.sqrt(4.0 * np.pi * freq * section.Ra * section.cm)
)
return section.L / lam
@property
def soma(self):
"""
First (or only) section in the "soma" section group.
"""
if isinstance(self.all_sections["soma"], list):
return self.all_sections["soma"][0]
else:
return self.all_sections["soma"]
def decorate(self):
"""
decorate the cell with it's own class channel decorator
"""
self.decorated = decorator.Decorator(cell=self)
self.decorated.channelValidate(self, verify=False)
self.mechanisms = (
self.hr.mechanisms
) # copy out all of the mechanisms that were inserted
# def channel_manager(self, modelType='RM03'):
# """
# Every cell class should have a channel manager if it is set up to handle morphology.
# This function should be overridden in the class with an appropriate routine that
# builds the dictionary needed to decorate the cell. See the bushy cell class for
# an example.
#
# Parameters
# ----------
# modelType : string (default: 'RM03')
# A string that identifies what type of model the channel manager will implement.
# This may be used to define different kinds of channels, or channel densities
# and compartmental placement for different cells.
# """
# raise NotImplementedError("No channel manager exists for cells of the class: %s" %
# (self.__class__.__name__))
def connect(self, post_cell, pre_opts=None, post_opts=None, **kwds):
"""
Create a new synapse connecting this cell to a postsynaptic cell.
The synapse is automatically created using
pre_cell.make_terminal(post_cell, \**pre_opts) and
post_cell.make_psd(terminal, \**post_opts).
By default, the cells decide which sections to connect. This can be
overridden by specifying 'section' in pre_opts and/or post_opts.
Parameters
----------
post_cell : NEURON section (required)
The postsynaptic cell that will receive the connection.
pre_opts : dictionary of options for the presynaptic cell (default: None)
see the synapses class for valid options and format.
post_opts : diction of options for the postsynaptic cell (default: None)
see synapses class for valid options and format.
\**kwds : (optional)
argmuments that are passed to the synapses class.
Returns
-------
the synapse object
"""
if pre_opts is None:
pre_opts = {}
if post_opts is None:
post_opts = {}
synapse = synapses.Synapse(self, pre_opts, post_cell, post_opts, **kwds)
self.outputs.append(synapse)
post_cell.inputs.append([synapse, post_opts, kwds])
return synapse
def print_connections(self):
"""
This is mostly for debugging ...
"""
print("outputs: ", self.outputs)
print("inputs: ", self.inputs)
def make_terminal(self, post_cell, **kwds):
"""
Create a synaptic terminal release mechanism suitable for output
from this cell to post_sec
This routine is a placeholder and should be replaced in the specific
cell class with code that performs the required actions for that class.
Parameters
----------
post_cell : the target terminal cell (required)
\**kwds : parameters passed to the terminal
"""
raise NotImplementedError(
"Cannot make Terminal connecting %s => %s"
% (self.__class__.__name__, post_cell.__class__.__name__)
)
def make_psd(self, terminal, **kwds):
"""
Create a PSD suitable for synaptic input from pre_sec.
This routine is a placeholder and should be overridden in the specific
cell class with code that performs the required actions for that class.
Parameters
----------
terminal : the terminal that connects to the PSD (required)
\**kwds : parameters passed to the terminal
"""
pre_cell = terminal.cell
raise NotImplementedError(
"Cannot make PSD connecting %s => %s"
% (pre_cell.__class__.__name__, self.__class__.__name__)
)
def make_glu_psd(self, post_sec, terminal, AMPA_gmax, NMDA_gmax, **kwds):
# Get AMPAR kinetic constants from database
params = data.get(
"sgc_ampa_kinetics",
species=self.species,
post_type=self.type,
field=["Ro1", "Ro2", "Rc1", "Rc2", "PA"],
)
return synapses.GluPSD(
post_sec,
terminal,
ampa_gmax=AMPA_gmax,
nmda_gmax=NMDA_gmax,
ampa_params=dict(
Ro1=params["Ro1"],
Ro2=params["Ro2"],
Rc1=params["Rc1"],
Rc2=params["Rc2"],
PA=params["PA"],
),
**kwds
)
def make_gly_psd(self, post_sec, terminal, psdtype, **kwds):
# Get GLY kinetic constants from database
params = data.get(
"gly_kinetics",
species=self.species,
post_type=self.type,
field=["KU", "KV", "XMax"],
)
psd = synapses.GlyPSD(post_sec, terminal, psdType=psdtype, **kwds)
return psd
def make_exp2_psd(
self, post_sec, terminal, weight=0.01, loc=0.5, tau1=0.1, tau2=0.3, erev=0.0
):
return synapses.Exp2PSD(
post_sec, terminal, weight=weight, loc=loc, tau1=tau1, tau2=tau2, erev=erev
)
def print_status(self):
print("\nCell model: %s" % self.__class__.__name__)
print(self.__doc__)
print(" Model Status:")
print("-" * 24)
for s in self.status.keys():
print("{0:>12s} : {1:<12s}".format(s, repr(self.status[s])))
print("-" * 32)
def cell_initialize(self, showinfo=False, vrange=None, **kwargs):
"""
Initialize this cell to it's "rmp" under current conditions
All sections in the cell are set to the same value
"""
if self.vm0 is None:
self.vm0 = self.find_i0(showinfo=showinfo, vrange=vrange, **kwargs)
for part in self.all_sections.keys():
for sec in self.all_sections[part]:
sec.v = self.vm0
def get_mechs(self, section):
"""
return a list of the mechanisms that are present in a section
a mechanism is required to have a gbar variable.
This routine should be called at the end of every cell creation routine.
"""
u = dir(section())
mechs = []
for m in u:
if m[0:2] == "__":
continue
if m in [
"cm",
"diam",
"k_ion",
"na_ion",
"next",
"point_processes",
"sec",
"v",
"x",
]:
continue # skip non-mechanisms known to us
try:
gx = eval("section()." + m + ".gbar")
mechs.append(m)
except:
pass
self.mechs = mechs
return mechs
def print_mechs(self, section):
"""
print the mechanisms that are inserted into the specified section,
and their densities (in uS/cm^2)
"""
print("\n Installed mechanisms:")
self.get_mechs(section)
# print eval('section().nav11.gbar')
print("somaarea: {:.3e}".format(self.somaarea))
print("Mechanisms:", end="")
for s in self.mechs:
print(" {:>8s} ".format(s), end="")
print("")
for m in self.mechs:
try:
gx = eval("section()." + m + ".gbar")
erev = 0.0
if m == "leak":
erev = eval("section()." + m + ".erev")
if m in ["jsrna", "na", "nacn", "nav11", "nacncoop", "napyr", "nap"]:
erev = eval("section().ena")
if m in ["klt", "kht", "ka"]:
erev = eval("section().e%s" % m)
if m in ["kis", "kif", "kdpyr", "kcnq"]:
erev = eval("section().ek")
if m in ["hcno", "ihvcn", "hcnobo", "ihpyr", "ihpyr_adj"]:
erev = eval("section()." + m + ".eh")
print(
"{0:>12s} : {2:8.1f} nS {1:7.3e} mho/cm2 {3:>5.1f} mV".format(
m, gx, mho2ns(gx, self.somaarea), erev
)
)
except:
print("{0:>12s} : <no gbar> ".format(m))
print("-" * 32)
def print_all_mechs(self):
print(self.get_all_mechs())
def get_all_mechs(self):
"""
return a string with all the mechanisms
"""
res = "\nAll mechanisms in all sections: \n"
for part in self.all_sections.keys():
if len(self.all_sections[part]) == 0:
# res += 'Cell part: %s hs not sections' % part
continue
res += "Cell part: %s\n" % part
for sec in self.all_sections[part]:
res += " Section: %s\n" % sec.name()
res += " %s" % self.get_mechs(sec) + "\n"
for m in self.get_mechs(sec):
gx = eval("sec()." + m + ".gbar")
res += " %s: %f\n" % (m, gx)
return res
def save_all_mechs(self):
"""
get and save all of the initial mechanisms and their
maximal conductances when the cell is created.
We use this to get and check values later when the run
is actually done.
Note: some cell constructions may require that save_all_mechs
be done again after the initial "build". In this case,
setting the cell's initial_mechanisms property to None must
be done to allow a new configuration of mechanisms to be saved.
"""
if self.initial_mechanisms is not None:
raise ValueError(
"Cells: Attempting to save initial mechanisms more than once"
)
self.initial_mechanisms = {}
for part in self.all_sections.keys():
self.initial_mechanisms[part] = {}
# print('Cell part: %s' % part )
for sec in self.all_sections[part]:
# print(' Section: ', sec)
# print(' ', self.get_mechs(sec))
self.initial_mechanisms[part][sec] = {}
for m in self.get_mechs(sec):
gx = eval("sec()." + m + ".gbar")
# print(' %s: %f' % (m, gx))
self.initial_mechanisms[part][sec][m] = gx
def check_all_mechs(self):
"""
Check that all mechanisms are the same as when we initially created the cell
"""
check = {}
for part in self.all_sections.keys():
if part not in self.initial_mechanisms.keys():
raise ValueError("Cell part %s was not in the original cell")
check[part] = {}
for sec in self.all_sections[part]:
# print(' Section: ', sec)
# print(' ', self.get_mechs(sec))
if sec not in self.initial_mechanisms[part].keys():
raise ValueError("Cell section was not in the original cell: ", sec)
check[part][sec] = sec
for m in self.get_mechs(sec):
gx = eval("sec()." + m + ".gbar")
# print(' %s: %f' % (m, gx))
if m not in self.initial_mechanisms[part][sec].keys():
raise ValueError(
"Mechanism %s was not in cell part %s, section = "
% (m, part),
sec,
)
if self.initial_mechanisms[part][sec][m] != gx:
raise ValueError(
"Conductance for mechanism %s in cell part %s has changed (%f, %f), section = "
% (m, part, self.initial_mechanisms[part][sec][m], gx),
sec,
)
return True
def get_cellpars(self, dataset, species="guineapig", cell_type="II"):
raise NotImplementedError(
"get_cellpars should be reimplemented in the individual cell modules"
)
def channel_manager(self, modelName=None, modelType=None):
"""
This routine defines channel density maps and distance map patterns
for each type of compartment in the cell. The maps
are used by the ChannelDecorator class (specifically, its private
\_biophys function) to decorate the cell membrane.
These settings are only used if the decorator is called; otherwise
for point cells, the species_scaling routine defines the channel
densities.
Parameters
----------
modelType : string (default: 'None'
A string that defines the type of the model.
These are determined in the tables in the data directory, for ionchannels.py
Returns
-------
Nothing
Notes
-----
This routine defines the following variables for the class:
* conductances (gBar)
* a channelMap (dictonary of channel densities in defined anatomical compartments)
* a current injection range for IV's (used for testing)
* a distance map, which defines how each conductance in a selected compartment
changes with distance from the soma. The current implementation includes both
linear and exponential gradients,
the minimum conductance at the end of the gradient, and the space constant or
slope for the gradient.
"""
dataset = "%s_channels" % modelName
decorationmap = dataset + "_compartments"
# print('dataset: {0:s} decorationmap: {1:s}'.format(dataset, decorationmap))
cellpars = self.get_cellpars(
dataset, species=self.status["species"], modelType=modelType
)
refarea = 1e-3 * cellpars.cap / self.c_m
# ? print ('cellpars: ' )
cellpars.show()
# print(' species: ', self.status['species'])
# print('m# odelType: ', modelType)
# print('dataset: ', dataset)
table = data._db.get_table_info(dataset)
# table = data.get_table_info('mGBC_channels')
# print(dir(data.ionchannels))
# print( data.print_table('mGBC_channels'))
if len(table.keys()) == 0:
raise ValueError("data table %s lacks keys - does it exist?" % dataset)
chscale = data._db.get_table_info(decorationmap)
pars = {}
# retrive the conductances from the data set
# print ('table keys: ', table.keys())
# print('table: ', table)
# print('chscale: ', chscale)
for g in table["field"]:
x = data._db.get(
dataset, species=self.status["species"], model_type=modelType, field=g
)
if not isinstance(x, float):
continue
if "_gbar" in g:
pars[g] = x / refarea
else:
pars[g] = x
self.channelMap = OrderedDict()
for c in chscale["compartment"]:
self.channelMap[c] = {}
for g in pars.keys():
if g not in chscale["parameter"]:
# print ('Parameter %s not found in chscale parameters!' % g)
continue
scale = data._db.get(
decorationmap,
species=self.status["species"],
model_type=modelType,
compartment=c,
parameter=g,
)
if "_gbar" in g:
self.channelMap[c][g] = pars[g] * scale
else:
self.channelMap[c][g] = pars[g]
self.irange = np.linspace(-0.6, 1, 9)
self.distMap = {
"dend": {
"klt": {"gradient": "exp", "gminf": 0.0, "lambda": 50.0},
"kht": {"gradient": "exp", "gminf": 0.0, "lambda": 50.0},
"nav11": {"gradient": "exp", "gminf": 0.0, "lambda": 50.0},
}, # linear with distance, gminf (factor) is multiplied by gbar
"dendrite": {
"klt": {"gradient": "linear", "gminf": 0.0, "lambda": 100.0},
"kht": {"gradient": "linear", "gminf": 0.0, "lambda": 100.0},
"nav11": {"gradient": "linear", "gminf": 0.0, "lambda": 100.0},
}, # linear with distance, gminf (factor) is multiplied by gbar
"apic": {
"klt": {"gradient": "linear", "gminf": 0.0, "lambda": 100.0},
"kht": {"gradient": "linear", "gminf": 0.0, "lambda": 100.0},
"nav11": {"gradient": "exp", "gminf": 0.0, "lambda": 200.0},
}, # gradients are: flat, linear, exponential
}
self.check_temperature()
return
def i_currents(self, V):
"""
For the steady-state case, return the total current at voltage V
Used to find the zero current point
vrange brackets the interval
Implemented here are the basic known mechanisms. If you add or need
more mechanisms, they either need to be accomadated in this routine,
or this routine needs to be implemented (overridden) in the
specific cell class.
"""
for part in self.all_sections.keys():
for sec in self.all_sections[part]:
sec.v = V
h.celsius = self.status["temperature"]
h.t = 0.0
h.finitialize(V)
h.fcurrent()
self.ix = {}
if "na" in self.mechanisms:
# print dir(self.soma().na)
try:
self.ix["na"] = self.soma().na.gna * (V - self.soma().ena)
except:
self.ix["na"] = self.soma().nav11.gna * (V - self.soma().ena)
if "jsrna" in self.mechanisms:
self.ix["jsrna"] = self.soma().jsrna.gna * (V - self.soma().ena)
if "nav11" in self.mechanisms:
self.ix["nav11"] = self.soma().nav11.gna * (V - self.soma().ena)
if "nacn" in self.mechanisms:
self.ix["nacn"] = self.soma().nacn.gna * (V - self.soma().ena)
if "napyr" in self.mechanisms:
self.ix["napyr"] = self.soma().napyr.gna * (V - self.soma().ena)
if "nap" in self.mechanisms:
self.ix["nap"] = self.soma().nap.gna * (V - self.soma().ena)
if "nacncoop" in self.mechanisms:
self.ix["nacncoop"] = self.soma().nacncoop.gna * (V - self.soma().ena)
if "klt" in self.mechanisms:
self.ix["klt"] = self.soma().klt.gklt * (V - self.soma().ek)
if "kht" in self.mechanisms:
self.ix["kht"] = self.soma().kht.gkht * (V - self.soma().ek)
if "ka" in self.mechanisms:
self.ix["ka"] = self.soma().ka.gka * (V - self.soma().ek)
if "kdpyr" in self.mechanisms:
self.ix["kdpyr"] = self.soma().kdpyr.gk * (V - self.soma().ek)
if "kcnq" in self.mechanisms:
self.ix["kcnq"] = self.soma().kdcnq.gk * (V - self.soma().ek)
if "kis" in self.mechanisms:
self.ix["kis"] = self.soma().kis.gk * (V - self.soma().ek)
if "kif" in self.mechanisms:
self.ix["kif"] = self.soma().kif.gk * (V - self.soma().ek)
if "ihvcn" in self.mechanisms:
self.ix["ihvcn"] = self.soma().ihvcn.gh * (V - self.soma().ihvcn.eh)
if "ihpyr" in self.mechanisms:
self.ix["ihpyr"] = self.soma().ihpyr.gh * (V - self.soma().ihpyr.eh)
if "ihpyr_adj" in self.mechanisms:
self.ix["ihpyr_adj"] = self.soma().ihpyr_adj.gh * (
V - self.soma().ihpyr_adj.eh
)
if "hcno" in self.mechanisms:
raise ValueError("HCNO is not supported - use hcnobo instead")
# self.ix['hcno'] = self.soma().hcno.gh*(V - self.soma().hcno.eh)
if "hcnobo" in self.mechanisms:
self.ix["hcnobo"] = self.soma().hcnobo.gh * (V - self.soma().hcnobo.eh)
if "leak" in self.mechanisms:
self.ix["leak"] = self.soma().leak.gbar * (V - self.soma().leak.erev)
# print self.status['name'], self.status['type'], V, self.ix
isum = np.sum([self.ix[i] for i in self.ix])
# print 'conductances: ', self.ix.keys()
# print 'V, isum, values: ', V, isum, [self.ix[i] for i in self.ix]
return isum
def find_i0(self, vrange=None, showinfo=False):
"""
find the root of the system of equations in vrange.
Finds RMP fairly accurately as zero current level for current conductances.
Parameters
----------
vrange : list of 2 floats (default: [-70, -55])
The voltage range over which the root search will be performed.
showinfo : boolean (default: False)
a flag to print out which roots were found and which mechanisms were in the cell
Returns
-------
The voltage at which I = 0 in the vrange specified
"""
if vrange is None:
vrange = self.vrange
# print( vrange)
# print (self.i_currents(V=vrange[0]), self.i_currents(V=vrange[1]))
# v0 = scipy.optimize.brentq(self.i_currents, vrange[0], vrange[1], maxiter=10000)
# print( 'v0: ', v0)
try:
v0 = scipy.optimize.brentq(
self.i_currents, vrange[0], vrange[1], maxiter=10000
)
except:
print("find i0 failed:")
print(self.ix)
i0 = self.i_currents(V=vrange[0])
i1 = self.i_currents(V=vrange[1])
ivi = []
ivv = []
for v in np.arange(vrange[0], vrange[1], 0.5):
ivi.append(self.i_currents(V=v))
ivv.append(v)
print("iv: ")
for i in range(len(ivi)):
print("%6.1f %9.4f" % (ivv[i], ivi[i]))
print(
"This means the voltage range for the search might be too large\nor too far away from the target"
)
raise ValueError(
"vrange not good for %s : %f at %6.1f, %f at %6.1f, temp=%6.1f"
% (self.status["name"], i0, vrange[0], i1, vrange[1], h.celsius)
)
# check to be sure all the currents that are needed are calculated
# can't do this until i_currents has populated self.ix, so do it now...
for m in self.mechanisms:
if m not in self.ix.keys():
raise ValueError(
"Mechanism %s in cell is missing from i_currents calculation", m
)
if showinfo:
print(
"\n [soma] find_i0 Species: %s cell type: %s Temp %6.1f"
% (self.status["species"], self.status["modelType"], h.celsius)
)
print(" *** found V0 = %f" % v0)
print(" *** and cell has mechanisms: ", self.mechanisms)
return v0
def compute_rmrintau(self, auto_initialize=True, vrange=None):
"""
Run the model for 2 msec after initialization - then
compute the inverse of the sum of the conductances to get Rin at rest
compute Cm*Rin to get tau at rest
Parameters
----------
auto_initialize : boolean (default: True)
If true, forces initialization of cell in NEURON befor the computation.
Returns
-------
A dictionary containing: Rin (Mohm), tau (ms) and Vm (mV)
"""
gnames = { # R&M 03 and related:
"nacn": "gna",
"na": "gna",
"jsrna": "gna",
"nav11": "gna",
"nacncoop": "gna",
"leak": "gbar",
"klt": "gklt",
"kht": "gkht",
"ka": "gka",
"ihvcn": "gh",
"hcno": "gh",
"hcnobo": "gh",
# pyramidal cell specific:
"napyr": "gna",
"nap": "gnap",
"kdpyr": "gk",
"kif": "gkif",
"kis": "gkis",
"ihpyr": "gh",
"ihpyr_adj": "gh",
"kcnq": "gk",
# cartwheel cell specific:
"bkpkj": "gbkpkj",
"hpkj": "gh",
"kpkj": "gk",
"kpkj2": "gk",
"kpkjslow": "gk",
"kpksk": "gk",
"lkpkj": "gbar",
"naRsg": "gna",
# SGC Ih specific:
"ihsgcApical": "gh",
"ihsgcBasalMiddle": "gh",
}
if auto_initialize:
self.cell_initialize(vrange=vrange)
custom_init()
self.computeAreas()
gsum = 0.0
soma_sections = self.all_sections["soma"]
# 1e-8*np.pi*soma.diam*soma.L
somaarea = np.sum([1e-8 * np.pi * s.L * s.diam for s in soma_sections])
for sec in soma_sections:
u = self.get_mechs(sec)
for m in u:
# gx = 'section().'+m+'.'+gnames[m]
gm = "%s_%s" % (gnames[m], m)
gsum += getattr(sec(), gm)
# eval(gx)
# print('{0:>12s} : gx '.format(m))
# convert gsum from us/cm2 to nS using cell area
# print ('gsum, self.somaarea: ', gsum, self.somaarea)
gs = mho2ns(gsum, self.somaarea)
Rin = 1e3 / gs # convert to megohms
tau = Rin * self.totcap * 1e-3 # convert to msec
return {"Rin": Rin, "tau": tau, "v": self.soma(0.5).v}
def set_soma_size_from_Cm(self, cap):
"""
Use soma capacitance to set the cell size. Area of the open cylinder is same as a sphere of
the same diameter.
Compute area and save total capacitance as well
"""
self.totcap = cap
self.somaarea = self.totcap * 1e-6 / self.c_m # pf -> uF, cm = 1uf/cm^2 nominal
lstd = 1e4 * ((self.somaarea / np.pi) ** 0.5) # convert from cm to um
self.soma.diam = lstd
self.soma.L = lstd
def set_soma_size_from_Diam(self, diam):
"""
Use diameter to set the cell size. Area of the open cylinder is same as a sphere of
the same diameter.
Compute area and total capacitance as well
"""
self.somaarea = 1e-8 * 4.0 * np.pi * (diam / 2.0) ** 2 # in microns^2
self.totcap = self.c_m * self.somaarea * 1e6
# lstd = diam # 1E4 * ((self.somaarea / np.pi) ** 0.5) # convert from cm to um
self.soma.diam = diam
self.soma.L = diam
def set_soma_size_from_Section(self, soma):
self.soma.diam = soma.diam
self.soma.L = soma.L
self.somaarea = 1e-8 * np.pi * soma.diam * soma.L
self.totcap = self.c_m * self.somaarea * 1e6
def print_soma_info(self):
print("-" * 40)
print("Soma Parameters: ")
print(" Area: ", self.somaarea)
print(" Cap: ", self.totcap)
print(" L: ", self.soma.L)
print(" diam: ", self.soma.diam)
print(" cm: ", self.c_m)
print("-" * 40)
def distances(self, section=None):
self.distanceMap = {}
if section is None:
self.hr.h("access %s" % self.soma.name()) # reference point
else:
self.hr.h("access %s" % section.name())
d = self.hr.h.distance()
for sec in self.all_sections:
s = self.all_sections[sec]
if len(s) > 0:
for u in s:
self.hr.h("access %s" % u.name())
self.distanceMap[u.name()] = (
self.hr.h.distance(0.5) - d
) # should be distance from first point
def computeAreas(self):
self.areaMap = {}
for sec in self.all_sections: # keys for names of section types
s = self.all_sections[sec] # get all the sections of that type
sectype = self.get_section_type(s)
if len(s) > 0:
self.areaMap[sec] = {}
for u in s:
self.areaMap[sec][u] = np.pi * u.diam * u.L
else:
pass
# print(' No section of type %s in cell' % sec)
def add_axon(
self,
c_m=1.0,
R_a=150,
axonsf=1.0,
nodes=5,
debug=False,
dia=None,
len=None,
seg=None,
):
"""
Add an axon to the soma with an initial segment (tapered), and multiple nodes of Ranvier
The size of the axon is determined by self.axonsf, which in turn is set by the species
The somaarea is used to scale the density of ion channels in the initial segment
"""
nnodes = range(nodes)
axnode = []
internode = []
Section = h.Section
initsegment = Section(cell=self.soma)
initsegment.connect(self.soma)
for i in nnodes:
axnode.append(Section(cell=self.soma))
internode.append(Section(cell=self.soma))
axnode[0].connect(initsegment)
for i in nnodes:
internode[i].connect(axnode[i])
if i < nnodes[-1]:
axnode[i + 1].connect(internode[i])
# create an initial segment
ninitseg = 21
initsegment.nseg = ninitseg
initsegment.diam = 4.0 * axonsf
initsegment.L = 36.0 * axonsf
initsegment.cm = c_m # c_m
initsegment.Ra = R_a # R_a
initsegment.insert("nacn") # uses a standard Rothman sodium channel
initsegment.insert("kht")
initsegment.insert("klt")
initsegment.insert("ihvcn")
initsegment.insert("leak")
gnamax = nstomho(6000.0, self.somaarea)
gnamin = 0.0 * gnamax
gnastep = (gnamax - gnamin) / ninitseg # taper sodium channel density
for ip, inseg in enumerate(initsegment):
gna = gnamin + ip * gnastep
if debug:
print("Initial segment %d: gnabar = %9.6f" % (ip, gna))
inseg.nacn.gbar = gna
inseg.klt.gbar = 0.2 * nstomho(200.0, self.somaarea)
inseg.kht.gbar = nstomho(150.0, self.somaarea)
inseg.ihvcn.gbar = 0.0 * nstomho(20.0, self.somaarea)
inseg.leak.gbar = nstomho(2.0, self.somaarea)
inseg.ena = self.e_na
inseg.ek = self.e_k
inseg.leak.erev = self.e_leak
for i in nnodes:
axnode[i] = self.loadaxnodes(axnode[i], self.somaarea, eleak=self.e_leak)
internode[i] = self.loadinternodes(
internode[i], self.somaarea, eleak=self.e_leak
)
if debug:
print("<< {:s} Axon Added >>".format(self.__class__.__name__))
h.topology()
self.add_section(initsegment, "initialsegment")
self.add_section(axnode, "axonnode")
self.add_section(internode, "internode")
@staticmethod
def loadaxnodes(axnode, somaarea, nodeLength=2.5, nodeDiameter=2.0, eleak=-65):
v_potassium = -80 # potassium reversal potential
v_sodium = 50 # sodium reversal potential
Ra = 150
cm = 1.0
axnode.nseg = 1
axnode.L = nodeLength
axnode.diam = nodeDiameter
axnode.Ra = Ra
axnode.cm = cm
axnode.insert("nacn")
axnode.insert("kht")
axnode.insert("klt")
axnode.insert("leak")
axnode.insert("ihvcn")
for ax in axnode:
ax.nacn.gbar = nstomho(1000.0, somaarea)
ax.kht.gbar = nstomho(150.0, somaarea)
ax.klt.gbar = nstomho(200.0, somaarea)
ax.ihvcn.gbar = 0
ax.leak.gbar = nstomho(2.0, somaarea)
ax.ena = v_sodium
ax.ek = v_potassium
ax.leak.erev = eleak
return axnode
@staticmethod
def loadinternodes(
internode, somaarea, internodeLength=1000, internodeDiameter=10, eleak=-65
):
v_potassium = -80 # potassium reversal potential
v_sodium = 50 # sodium reversal potential
Ra = 150
cm = 0.002
internode.nseg = 20
internode.L = internodeLength
internode.diam = internodeDiameter
internode.Ra = Ra
internode.cm = cm
internode.insert("nacn")
internode.insert("kht")
internode.insert("leak")
for inno in internode:
inno.leak.gbar = nstomho(0.002, somaarea)
inno.nacn.gbar = 0 * nstomho(500.0, somaarea)
inno.kht.gbar = 0 * nstomho(150.0, somaarea)
inno.ek = v_potassium
inno.ena = v_sodium
inno.leak.erev = eleak
return internode