In the code below, I have tried to retain as much of the original stimsphere code as possible, to illustrate proper functioning, and also added in a highly reduced representation of a complex neuron shape stimulated by electric field.
Three of NEURON's integration methods (Crank-Nicolson, backward Euler, and CVODE) give nearly identical results for the sphere cell scenario. Crank-Nicholson diverges sharply for the scenario of the complex cell.
What is going on here? I know of no mathematical property of the Crank-Nicolson method which prohibits its application for a 'squiggly shape' scenario. (And, since better minds than mine chose to include it in NEURON, I suspect no such property exists.)
The python code:
Code: Select all
from neuron import h, gui
import matplotlib.pyplot as plt
import numpy as np
h.load_file("stdrun.hoc")
h.load_file("rig.ses")
# use interp_xyz at the SECTION level
def interp_xyz(section, origin=[0,0,0]):
definition = section.nseg
nn = 0
sec = section # h.cas()
# get the pt3d data for each section
nn = int(sec.n3d()) # nn is the number of 3-D data points defining each section
xx = h.Vector(nn)
yy = h.Vector(nn)
zz = h.Vector(nn)
length = h.Vector(nn)
for ii in range(nn):
xx[ii] = sec.x3d(ii)
yy[ii] = sec.y3d(ii)
zz[ii] = sec.z3d(ii)
length[ii] = sec.arc3d(ii)
# h.Vector().interpolate() requires normalized section lengths
length.div(length[nn - 1])
# creating the destination vector
range_var = h.Vector(definition + 2)
range_var.indgen(1 / definition)
range_var.sub(1 / (2 * definition))
range_var[0] = 0
range_var[definition + 1] = 1
# likewise, the Vectors of interpolated pt3d data should be extended to include endpoints
xint = h.Vector(definition + 2)
yint = h.Vector(definition + 2)
zint = h.Vector(definition + 2)
# meaning: interpolate the (length, xyz) data onto the range_var scale
xint.interpolate(range_var, length, xx)
yint.interpolate(range_var, length, yy)
zint.interpolate(range_var, length, zz)
x = [i - origin[0] for i in xint.c(1,definition)]
y = [j - origin[1] for j in yint.c(1,definition)]
z = [k - origin[2] for k in zint.c(1,definition)]
return list(zip(x, y, z))
def makerealdend(nseg):
data = {'x': [-8.96, -10.42, -11.85, -13.67, -13.41, -14.06, -13.67,
-13.54, -13.63, -13.94, -12.34, -11.98, -12.2, -12.14,
-10.57, -9.16, -7.46, -3.98, -1.88, -0.19, 2.52, 4.12, 4.08],
'y': [-2.26, -3.05, -4.68, -6.77, -8, -8.76, -10.25, -11.23,
-12.64, -13.63, -14.19, -15.24, -17.89, -19.94, -21.28,
-21.9, -21.55, -19.79, -18.59, -18.23, -17.05, -16.1, -16.04],
'z': [-0.65, -1.1, -1.34, -1.37, -3.4, -3.72, -3.94, -4.3, -6.69,
-7.4, -8.15, -8.89, -9.6, -10.52, -12.96, -14.86, -15.57,
-15.99, -16.61, -17.56, -19.05, -20.11, -21.63],
'diam': 0.95
}
n3d = len(data['x'])
dendrite = h.Section(name="dendrite")
for n in range(n3d):
xx = data['x'][n]
yy = data['y'][n]
zz = data['z'][n]
dd = data['diam']
dendrite.pt3dadd(xx,yy,zz,dd)
for sec in h.allsec():
h.hh_li_v1.insert(sec)
sec.ek = -90
sec.ena = 60
sec.cm = 0.75
sec.Ra = 100
h.extracellular.insert(sec)
sec.nseg = nseg
return [dendrite]
def makespherecell(diam, n3dpts, nseg):
somaR = h.Section(name='somaR')
somaL = h.Section(name='somaL')
somaR.connect(somaL(0),0)
for i in range(n3dpts):
theta = h.PI*0.5*(1 - i / (n3dpts - 1))
xx = 0.5*diam*h.cos(theta)
dd = diam*h.sin(theta)
somaR.pt3dadd(xx,0,0,dd) # remove the 'i'
somaL.pt3dadd(-xx,0,0,dd)
for sec in h.allsec():
h.hh_li_v1.insert(sec)
sec.ek = -90
sec.ena = 60
sec.cm = 0.75
sec.Ra = 100
# h.xtra.insert(sec)
# h.hh.insert(sec)
h.extracellular.insert(sec)
sec.nseg = nseg
return [somaR, somaL]
def spherecalc(sec, seg, dedx=1):
xloc = (sec.diam3d(0)/2)*seg.x # distance of seg's node from 0 end
if sec.x3d(1) < 0:
xloc = -xloc # left hemisphere
rx = 1e-3 * (dedx * xloc)
return rx
def realcalc(pts, j):
rx = pts[j][1] * 1e-3
if abs(rx) < 1e-9:
rx = 1e-9 * np.sign(rx)
return rx
##########
# This now works with hh/xtra, hh_sphere, and hh_li_v1
# WHY doesn't it work in the big script?
##########
# cell = makespherecell(5, 25, 9)
cell = makerealdend(9)
amp = 1e3 * h.PI * 2.8 # near threshold for sphere cell
amp = 8e3 # near threshold (maybe?) for real dendrite
h.celsius = 37
t_sim = 20
h.tstop = t_sim
dpts = 2000
stimpts = 150
padpts = (dpts - stimpts) // 4
rempts = dpts - padpts - stimpts
wave = np.ones(stimpts)
stim = np.pad(wave, (padpts,rempts), constant_values=0)
svec = h.Vector(stim).mul(amp)
tvec = h.Vector(np.linspace(0,t_sim,dpts))
svec.play(h._ref_ex_hh_li_v1, tvec, True)
for sec in h.allsec():
pt3ds = interp_xyz(sec)
j = -1
for seg in sec:
j+=1
seg.hh_li_v1._ref_Vs = seg._ref_e_extracellular
seg.hh_li_v1.rx = spherecalc(sec, seg)
seg.hh_li_v1.rx = realcalc(pt3ds, j)
# h.dt = 1e-3
rdt = 0.025
t = h.Vector().record(h._ref_t, rdt)
v = h.Vector().record(cell[0](0.5)._ref_v, rdt)
h.secondorder = 0
h.finitialize(-65)
h.continuerun(t_sim)
print(f"the chosen solver is {h.CVode().current_method()}")
lns = plt.plot(t,v, label='backward Euler, soma')
h.secondorder = 2
h.finitialize(-65)
h.continuerun(t_sim)
print(f"the chosen solver is {h.CVode().current_method()}")
lns += plt.plot(t,v, label='Crank-Nicolson, soma')
cvode = h.CVode()
cvode.active(True)
cvode.use_daspk(True)
cvode.dae_init_dteps(1e-9)
h.finitialize(-65)
h.continuerun(t_sim)
print(f"the chosen solver is {h.CVode().current_method()}")
lns += plt.plot(t,v, label='CVODE, soma')
lns += plt.twinx().plot(tvec, svec, label='RF', color='r', linestyle=':')
lbls = [x.get_label() for x in lns]
plt.legend(lns, lbls)
plt.show()
Code: Select all
: A custom Hodgkin-Huxley-style ion channel built on the ODEs found in Li et al 2015
NEURON {
SUFFIX hh_li_v1
USEION k READ ek WRITE ik
USEION na READ ena WRITE ina
NONSPECIFIC_CURRENT il:, i
RANGE gnabar, ena, gna
RANGE gkbar, ek, gk
RANGE glbar, el, gl
RANGE rx
GLOBAL ex
POINTER Vs
}
UNITS {
(mV) = (millivolt)
(mA) = (milliamp)
(mS) = (millisiemens)
(S) = (siemens)
(nW) = (nanowatts)
(uA) = (microamp)
(uF) = (microfarad)
(um) = (micrometer)
}
PARAMETER {
gkbar = 40 (mS/cm2) <0,1e9>
gnabar = 150 (mS/cm2) <0,1e9>
glbar = 0.033 (mS/cm2) <0,1e9>
el = -70 (mV)
:ek = -90 (mV)
:ena = 60 (mV)
}
ASSIGNED {
celsius (degC)
phi
dt (ms)
v (mV)
Vs (mV)
ex (mV/um)
rx (um)
ena (mV)
ek (mV)
gna (mS/cm2)
gk (mS/cm2)
gl (mS/cm2)
ina (mA/cm2)
ik (mA/cm2)
il (mA/cm2)
}
STATE { n m h}
BREAKPOINT {
SOLVE states METHOD cnexp
: The ionic conductances w.r.t time
gna = gnabar*m^3*h
gk = gkbar*n
gl = glbar
: The ionic currents
ik = gk*(v - ek)*(1e-3)
ina = gna*(v - ena)*(1e-3)
il = gl*(v - el)*(1e-3)
: The voltage which will be pointed into _e_extracellular
Vs = ex*rx:*(1e6)
}
DERIVATIVE states {
n' = alpha_n(v, phi)*(1 - n) - beta_n(v, phi)*n
m' = alpha_m(v, phi)*(1 - m) - beta_m(v, phi)*m
h' = (1 / (1 + exp((v + 60) / 6.2 (mV))) - h)*(alpha_h(v, phi) + beta_h(v, phi))
}
INITIAL {
phi = 2.3^((celsius - 23)/10 (degC))
n = alpha_n(v, phi) / (alpha_n(v,phi) + beta_n(v,phi))
m = alpha_m(v, phi) / (alpha_m(v,phi) + beta_m(v,phi))
h = alpha_h(v, phi) / (alpha_h(v,phi) + beta_h(v,phi))
Vs = ex*rx:*(1e6)
}
FUNCTION alpha_n(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp(-(v - 30)/9 (mV))
if (fabs(x) < 1e-6) {
alpha_n = phi*0.01*x / (1 - exp(-x))
}
else {
alpha_n = phi*(0.01*(v - 30)) / x
}
UNITSON
}
FUNCTION beta_n(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp((v - 30)/9 (mV))
if (fabs(x) < 1e-6) {
beta_n = -phi*0.002*x / (1 - exp(-x))
}
else {
beta_n = -phi*(0.002*(v - 30)) / x
}
:beta_n = phi*0.125*exp(-(v + 65) / 80)
UNITSON
}
FUNCTION alpha_m(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp(-(v + 30)/8 (mV))
if (fabs(x) < 1e-6) {
alpha_m = phi*0.182*x / (1 - exp(-x))
}
else {
alpha_m = phi*(0.182*(v + 30)) / x
}
UNITSON
}
FUNCTION beta_m(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp((v + 30)/8 (mV))
if (fabs(x) < 1e-6) {
beta_m = -phi*0.124*x / (1 - exp(-x))
}
else {
beta_m = -phi*(0.124*(v + 30)) / x
}
:beta_m = phi * 4 * exp(-(v+65)/18)
UNITSON
}
FUNCTION alpha_h(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp(-(v + 45)/6 (mV))
if (fabs(x) < 1e-6) {
alpha_h = phi*0.028*x / (1 - exp(-x))
}
else {
alpha_h = phi*(0.028*(v + 45)) / x
}
:alpha_h = phi*.07 * exp(-(v+65)/20)
UNITSON
}
FUNCTION beta_h(v (mV), phi) (/ms) {
LOCAL x
UNITSOFF
x = 1 - exp((v + 70)/6 (mV))
if (fabs(x) < 1e-6) {
beta_h = -phi*0.0091*x / (1 - exp(-x))
}
else {
beta_h = -phi*(0.0091*(v + 70)) / x
}
:beta_h = phi / (exp(-(v+35)/10) + 1)
UNITSON
}