/*
Stimulus protocol as used by Sjöström et al. (2001)
Demonstrating the synaptic plasticity rule by Ebner et al. (2019)
https://doi.org/10.1016/j.celrep.2019.11.068
Made to work with the Hay et al. (2011) L5b pyramidal cell model
*/
load_file("nrngui.hoc")
// ## Object references
objref freqs, loc_prox, loc_dist
objref trec, vrec_prox, vrec_dist
objref pre_post, post_pre
objref syn, stim, nc, ic
// ## Parameters
REPS = 5 // Number of pre- and postsynaptic spikes
DT = 0.025 // ms, integration step.
AMP = 2.7 // nA, amplitude of current injection to trigger postsynaptic spikes
DUR = 5.0 // ms, duration of the current injection
WARM_UP = 2300 // ms, silence phase before stimulation
COOL_DOWN = 100 // ms, silence phase after stimulation
WEIGHT = 0.0035 // µS, conductance of (single) synaptic potentials
LOCATION = 1 // 1 ... proximal (90 µm) / 2 ... distal (669 µm)
freqs = new Vector() // stimulation frequencies (Hz)
// freqs.append(0.1, 10, 20, 40, 50) // use this to include 0.1 Hz condition. This will take some time.
freqs.append(10, 20, 40, 50)
// ## Create cell
load_file("import3d.hoc")
objref L5PC
strdef morphology_file
morphology_file = "morphologies/cell1.asc"
load_file("models/L5PCbiophys4.hoc")
load_file("models/L5PCtemplate.hoc")
L5PC = new L5PCtemplate(morphology_file)
// ## Create synapse
L5PC.apic[1] loc_prox = new SectionRef()
L5PC.apic[50] loc_dist = new SectionRef()
if(LOCATION == 1) {loc_prox.sec syn = new Syn4P(0.5)
} else {loc_dist.sec syn = new Syn4P(0.5)}
syn.tau_a = 0.2 // time constant of EPSP rise
syn.tau_b = 2 // time constant of EPSP decay
syn.e = 0 // reversal potential
syn.w_pre_init = 0.5 // pre factor initial value
syn.w_post_init = 2.0 // post factor initial value
syn.s_ampa = 0.5 // contribution of AMPAR currents
syn.s_nmda = 0.5 // contribution of NMDAR currents
syn.tau_G_a = 2 // time constant of presynaptic event G (rise)
syn.tau_G_b = 50 // time constant of presynaptic event G (decay)
syn.m_G = 10 // slope of the saturation function for G
syn.A_LTD_pre = 3e-3 // amplitude of pre-LTD
syn.A_LTP_pre = 33e-4 // amplitude of pre-LTP
syn.A_LTD_post = 36e-5 // amplitude of post-LTD
syn.A_LTP_post = 2e-1 // amplitude of post-LTP
syn.tau_u_T = 10 // time constant for filtering u to calculate T
syn.theta_u_T = -60 // voltage threshold applied to u to calculate T
syn.m_T = 1.7 // slope of the saturation function for T
syn.theta_u_N = -30 // voltage threshold applied to u to calculate N
syn.tau_Z_a = 1 // time constant of presynaptic event Z (rise)
syn.tau_Z_b = 15 // time constant of presynaptic event Z (decay)
syn.m_Z = 6 // slope of the saturation function for Z
syn.tau_N_alpha = 7.5 // time constant for calculating N-alpha
syn.tau_N_beta = 30 // time constant for calculating N-beta
syn.m_N_alpha = 2 // slope of the saturation function for N_alpha
syn.m_N_beta = 10 // slope of the saturation function for N_beta
syn.theta_N_X = 0.2 // threshold for N to calculate X
syn.theta_u_C = -68 // voltage threshold applied to u to calculate C
syn.theta_C_minus = 15 // threshold applied to C for post-LTD (P activation)
syn.theta_C_plus = 35 // threshold applied to C for post-LTP (K-alpha activation)
syn.tau_K_alpha = 15 // time constant for filtering K_alpha to calculate K_alpha_bar
syn.tau_K_gamma = 20 // time constant for filtering K_beta to calculate K_gamma
syn.m_K_alpha = 1.5 // slope of the saturation function for K_alpha
syn.m_K_beta = 1.7 // slope of the saturation function for K_beta
syn.s_K_beta = 100 // scaling factor for calculation of K_beta
// ## Custom init procedure
proc init(){ local i
finitialize(-70)
dt = 10
i = 0
for i = 1,224 fadvance() // Run warm-up phase using big steps
print t
dt = DT
}
// ## Current trace vector
obfunc current_trace() { local freq, delta_t, total_t, start_t, end_t, counter localobj trace
freq = $1
delta_t = $2
total_t = $3
trace = new Vector(total_t/DT, 0)
for counter = 0, REPS - 1 {
start_t = (0 + delta_t + counter * (1000.0 / freq) + WARM_UP)
end_t = (start_t + DUR) // this is the actual duration of the current injection
trace.fill(AMP, start_t/DT, end_t/DT)
}
return trace
}
// ## Stimulation protocol; goes through all frequencies at a particular timing
obfunc stimulation() { local delta_t, counter localobj delta_w, ic_trace
delta_t = $1
delta_w = new Vector()
// Create objects
stim = new NetStim()
stim.number = REPS
stim.start = WARM_UP
stim.noise = 0
nc = new NetCon(stim, syn, 0, 0, WEIGHT)
L5PC.soma ic = new IClamp(0.5)
ic.del = 0
ic.dur = 1e9 // not the actual duration
for counter = 0, freqs.size() - 1 {
print "\n*Loop ", counter + 1, "of ", freqs.size, "// f = ", freqs.x[counter], "Hz"
stim.interval = 1000.0 / freqs.x[counter] // calculate interval
total_time = WARM_UP + REPS * (1000.0 / freqs.x[counter]) + COOL_DOWN
tstop = total_time
ic_trace = new Vector()
ic_trace = current_trace(freqs.x[counter], delta_t, total_time)
ic_trace.play(&ic.amp, DT) // play the trace vector into the IClamp protocol
// Recording
trec = new Vector()
trec.record(&t)
vrec_prox = new Vector()
vrec_prox.record(&loc_prox.sec.v(0.5)) // voltage at proximal location
vrec_dist = new Vector()
vrec_dist.record(&loc_dist.sec.v(0.5)) // voltage at distal location
init()
run()
w_init = syn.w_pre_init * syn.w_post_init
delta_w_pre = syn.w_pre - syn.w_pre_init // Difference of present and initial values
delta_w_post = syn.w_post - syn.w_post_init
if(freqs.x[counter] == 0.1) {
w_final_pre = syn.w_pre_init + delta_w_pre * 10 // 10 repetitions at 0.1 Hz
w_final_post = syn.w_post_init + delta_w_post * 10
} else {
w_final_pre = syn.w_pre_init + delta_w_pre * 15 // 15 repetitions at 10, 20, 40 and 50 Hz
w_final_post = syn.w_post_init + delta_w_post * 15
}
if (w_final_pre > 1.0) { w_final_pre = 1.0 }
if (w_final_pre < 0.0) { w_final_pre = 0.0 }
if (w_final_post > 5.0) { w_final_post = 5.0 }
if (w_final_post < 0.0) { w_final_post = 0.0 }
w_final = w_final_pre * w_final_post
outcome = (w_final - w_init) / w_init * 100
print "dw(pre) = ", delta_w_pre, "--> ", (w_final_pre - syn.w_pre_init) / syn.w_pre_init * 100, "%"
print "dw(post) = ", delta_w_post, "--> ", (w_final_post - syn.w_post_init) / syn.w_post_init * 100, "%"
print "w(start) = ", w_init
print "w(end) = ", w_final
print "normalized weight change at f = ", freqs.x[counter], " --> ", outcome, "%"
delta_w.append(outcome)
}
return delta_w
}
// ## Main
pre_post = new Vector()
post_pre = new Vector()
print "\n*** Starting Pre-Post ***"
pre_post = stimulation(10)
print "\n*** Starting Post-Pre ***"
post_pre = stimulation(-10)
// ## Visualization
objref voltplot
voltplot = new Graph(0) // proximal & distal voltage over time (final run only)
voltplot.size(0, 10625, -85, 10)
voltplot.view(-0.3, -85, 10625, 105, 65, 105, 300, 200)
vrec_prox.line(voltplot, 1, 2)
vrec_dist.line(voltplot, 3, 2)
objref compplot, compvec1, compvec2
compvec1 = new Vector()
compvec1.append(14, 29, 53, 56) // Experimental data (Sjöström et al., 2001) pre-post
compvec2 = new Vector()
compvec2.append(-41, -35, 56, 75) // Experimental data (Sjöström et al., 2001) post-pre
compplot = new Graph(0)
compplot.size(0, 3, -50, 80)
compplot.view(-0.3, -50, 3.3, 130, 65, 105, 300, 200)
compvec1.line(compplot, 9, 2)
pre_post.line(compplot, 2, 2)
compvec2.line(compplot, 9, 2)
post_pre.line(compplot, 2, 2)