Michael Hines pointed out the source of the problem: calling a random number generator in a BREAKPOINT block. The BREAKPOINT block is executed twice on each time step, with slightly different values of v, in order to calculate the derivative di/dv (conductance of the point process). Calling normrand twice causes large random changes in the numerator of the di/dv calculation, which means the current source no longer has a conductance of 0 but instead has a (potentially very large) fluctuating conductance.
The solution is to use a BEFORE BREAKPOINT block to calcuate the value that is to be assigned to i. But the BREAKPOINT block must still exist, to make sure that the value of i will actually be updated on each call to fadvance().
So here's the revised mod file, which should work properly (at least it does in my tests):
Code: Select all
NEURON {
POINT_PROCESS IClampNoise
RANGE i,del,dur,f0,f1,r,torn,std,bias
ELECTRODE_CURRENT i
}
UNITS {
(nA) = (nanoamp)
}
PARAMETER {
del=50 (ms)
dur=200 (ms)
torn=500 (ms)
std=0.2 (nA)
f0=0.2 (nA)
f1=0.8 (nA)
r =60
bias = 0 (nA)
}
ASSIGNED {
ival (nA)
i (nA)
amp (nA)
noise (nA)
on (1)
}
INITIAL {
i = 0
on = 0
net_send(del, 1)
}
PROCEDURE seed(x) {
set_seed(x)
}
BEFORE BREAKPOINT {
if (on) {
noise = normrand(0,std*1(/nA))*1(nA)
amp = f0 + 0.5*(f1-f0)*(tanh((t-torn)/(r/3)/(1(ms))-3)+1)
ival = amp + noise + bias
} else {
ival = 0
}
}
BREAKPOINT {
i = ival
}
NET_RECEIVE (w) {
if (flag == 1) {
if (on == 0) {
: turn it on
on = 1
: prepare to turn it off
net_send(dur, 1)
} else {
: turn it off
on = 0
}
}
}
Comments about the new implementation.
Instead of using the long-deprecated at_time(), this implementation employs a simple state machine (which uses NEURON's event delivery system) to control turning the current on and off. The value of "on" denotes whether the current source is or isn't delivering a current. In a simulation, fadvance() is called once per time step. At each call to fadvance(), if "on" is 1, i is assigned a value that depends on a single new evaluation of normrand; otherwise, is assigned a value of 0. The actual calculation of the new value of i is done in the BEFORE BREAKPOINT block (which is called once per fadvance()), and the value is stored in the intermediate variable ival. The actual assignment of the new value to i is done in the BREAKPOINT block (which is called twice per fadvance()).
The INITIAL block sets i to 0, and also sets up the state machine by assgning 0 to "on" and launching a self-event that will return after a delay of "del" ms (i.e. at time t = current time + del = del). The self-event is associated with a "flag" value of 1, so that the NET_RECEIVE block can test any events it receives and verify that the event is a self-event.
When this self-event arrives, the first part of the conditional statement inside the NET_RECEIVE block sets "on" to 1 and launches a second self-event that will return after a delay of "dur" ms. Arrival of this second self-event causes execution of the second part of the conditional, which swithces "on" back to 0, terminating the injected current.
A final note: this implementation contains PROCEDURE seed(), which should be called from hoc during initialization (e.g. by an FInitializeHandler) to specify the seed used by the random number generator. Otherwise successive runs will produce different results even though no parameters have been changed.