I have a NET_RECEIVE block containing both a WATCH and several net_send calls. The WATCH fails to to put an event in the queue when the parameter it's WATCH-ing crosses threshold. This behavior ceases when I comment out the net_send calls. In fact, I can make the behavior come & go by changing the value of the "delay" argument to net_send.
However, I'm having the devil of a time producing a short piece of code that reliably reproduces the problem: there's something stochastic about it. For example, I reliably make the bug appear & dispear, repeatedly, by varying the delay parameter between two values, then, when I think I've gotten rid of it for good, I move on & start editing some other part of the code, and the problem reappears. (Obviously, I'm recompiling after each edit to the sourcecode.) I suspect I'm dealing with a race condition.
With a problem like this, it's not good enough to make the problem go away: I have to understand what's causing it. Otherwise, Murphy's law garantees it'll reappear mysteriously after the code's passed its unit tests and is in production.
I've written a pair of .mod and .hoc files which exercise the mechanism repeatedly, with a range of delay parameters, until the WATCH fails. This *seems* to replicate the problem reliably.
There's a range variable "watch_time" which takes value of 0 or nonzero. If it's nonzero, the mechanism looks for a threshold-crossing of the "t" variable (time). This does *not* bring out the bug. If "watch_time" is set to zero, the mechanism looks for a threshold crossing of the "v" variable (membrane potential). This *does* bring out the problem.
Here's the .mod file ("ex_watch.mod"):
Code: Select all
NEURON {
POINT_PROCESS ex_watch
RANGE out_var,watch_time,delay
}
PARAMETER {
t_transition = 50 (ms)
v_transition = 0 (mV)
}
ASSIGNED {
watch_time
out_var
delay
v
:t :Do I really need to declare t as an ASSIGNED variable? Apparently not. (Built-in NMODL global)
}
STATE {
dummy_state :Can I perhaps omit the STATE, BREAKPOINT, and DERIVATIVE blocks altogether?
}
INITIAL {
out_var = 0
dummy_state = 0
net_send(0,99)
}
BREAKPOINT {
SOLVE states METHOD cnexp :Can I perhaps omit the STATE, BREAKPOINT, and DERIVATIVE blocks altogether?
}
DERIVATIVE states {
dummy_state' = 0 :Can I perhaps omit the STATE, BREAKPOINT, and DERIVATIVE blocks altogether?
}
NET_RECEIVE (dummy_weight) {
if (watch_time == 1) {
WATCH (t > t_transition) 1
} ELSE {
WATCH (v > v_transition) 1
}
IF (flag == 1) {
out_var = out_var + 1
} ELSE {
IF (flag == 0) {
net_send(delay,-1)
} ELSE {
IF (flag == -1) {
:do nothing
} ELSE {
IF (flag == 99) {
:do nothing
} ELSE {
:This should never happen.
}
}
}
}
}
Code: Select all
//BEGIN Define simpleCell class
begintemplate simpleCell
public celldef, position
public soma
proc celldef() {
topol()
subsets()
geom()
biophys()
geom_nseg()
}
create soma
proc topol() { local i
basic_shape()
}
proc basic_shape() {
soma {pt3dclear() pt3dadd(0, 0, 0, 1) pt3dadd(15, 0, 0, 1)}
}
proc position () {
soma {
netX = $1
netY = $2
netZ = $3
}
}
objref all
proc subsets() { local i
objref all
all = new SectionList()
soma all.append()
}
proc geom() {
soma { L = 30 diam = 30 }
}
proc geom_nseg() {
soma area(.5) // make sure diam reflects 3d points
}
proc biophys() {
soma {
Ra = 80
cm = 1
insert hh
gnabar_hh = 0.12
gkbar_hh = 0.036
gl_hh = 0.0003
el_hh = -54.3
}
}
access soma
endtemplate simpleCell
//END Define simpleCell class
//Create first cell
objref cell1
cell1 = new simpleCell()
cell1.celldef()
//Create second cell
objref cell2
cell2 = new simpleCell()
cell2.celldef()
//Insert ex_watch mechanism into cell1
objref mech1
cell1.soma mech1 = new ex_watch(0.5)
//Instrument cell2 with a current-injecting electrode
objref trode
cell2.soma trode = new Ipulse2(0.5)
trode.del = 50
trode.amp = 0.5
trode.dur = 1.0
trode.num = 1
trode.per = 50
//Connect the mechanism in cell1 to cell2 via NetCon object
objref nc
cell2.soma nc = new NetCon( &v(0.5), mech1, 20, 0, 1) //threshold 20, delay 0, weight 1
//Load the standard run system
load_file("stdrun.hoc")
tstop = trode.del * 2
//mech1.watch_time = 1
mech1.watch_time = 0
proc testit() {
incr = 0.1
final = 25
for i = 1, (final/incr) {
mech1.delay = i*incr
run()
if (mech1.out_var == 0) {
print "WATCH failed at the following delay:"
print mech1.delay
break
}
}
}
////Create graphs to monitor range variables
objref g1
g1 = new Graph()
addplot(g1,0)
g1.size(0,tstop,0,5)
g1.addvar("mech1.out_var")
//Create graphs to monitor range variables
objref g2
g2 = new Graph()
addplot(g2,0)
g2.size(0,tstop,-70,50)
g2.addvar("cell2.soma.v(0.5)")
Code: Select all
[58] ./i686/special
NEURON -- Release 6.0.4 (1819) 2007-07-20
Duke, Yale, and the BlueBrain Project -- Copyright 1984-2007
See http://www.neuron.yale.edu/credits.html
loading membrane mechanisms from /Users/sec/neuron/current/i686/.libs/libnrnmech.so
Additional mechanisms from files
ipulse2.mod ex_watch.mod
oc>load_file("ex_ex_watch.hoc")
0
0
1
1
1
1
1
1
oc>mech1.watch_time=0
oc>testit()
WATCH failed at the following delay:
0.1
oc>mech1.watch_time=1
oc>testit()
oc>
When all is said & done, despite their frustrations, NEURON & NMODL are the greatest thing since sliced bread.