I'm a bit confused with NEST's STDP models. I'm not sure if the issues that I have found are design choices or unintended bugs.
To debug stdp models, I added print statements in the stdp_synapse.h file:
double minus_dt; while ( start != finish ) { minus_dt = t_lastspike_ - ( start->t_ + dendritic_delay ); std::cout<<"minus_dt: "<<minus_dt<<std::endl; std::cout<<"postspike time: "<<start->t_<<std::endl; ++start;
// get_history() should make sure that // start->t_ > t_lastspike - dendritic_delay, i.e. minus_dt < 0 assert( minus_dt < -1.0 * kernel().connection_manager.get_stdp_eps() ); weight_ = facilitate_( weight_, Kplus_ * std::exp( minus_dt / tau_plus_ ) ); }
I created two parrot neurons and fed spike times to the first neuron. I set the syn_delay to 0.3 ms. I found two issues:
1. If the spike train consists of only one spike, no STDP updates are performed. I would expect one positive update since the postsynaptic spike arrives 0.3 ms after the presynaptic spike.
2. minus_dt=-0.6 instead of -0.3. The spike separation is 0.3 ms, however for the STDP calculation, one uses 0.6 ms which confuses me. Indeed, simulating with presynaptic spike train [10., 20.] leads to weight change 1.0->35.5443 (lamba=1, mu_plus=mu_minus=0, otherwise defaults). Analytically, the same can be achieved by calculating
(1/100 + np.exp(-0.6/20) - np.exp(-9.4/20))*100 = np.float64(35.54432652658074)
This confirms that indeed minus_dt=-0.6 ms and that the first LTP STDP interaction is neglected.
Are these known issues or new findings? I tested with NEST 3.3 and 3.8 and both had the same issues. I do not understand the point of having dendritic_delay in minus_dt = t_lastspike_ - ( start->t_ + dendritic_delay ), as it is already included in the start->t_ (?).
Here is the python file that I used for the testing:
import matplotlib.pyplot as plt import nest
dt = 0.1 nest.resolution = dt
syn_delay = 0.3 syn_model = "stdp_synapse" syn_specs = {"synapse_model": syn_model, "weight": 1.0, "delay": syn_delay, "lambda": 1., "mu_plus": 0., "mu_minus": 0. }
# neurons pre_neuron = nest.Create("parrot_neuron") post_neuron = nest.Create("parrot_neuron")
# test also with "spike_times": [10.], this results to no STDP updates nest_spikes_pre = nest.Create("spike_generator", params={"spike_times": [10., 20.]}) nest.Connect(nest_spikes_pre, pre_neuron)
sr_post = nest.Create("spike_recorder") nest.Connect(post_neuron, sr_post)
sr_pre = nest.Create("spike_recorder") nest.Connect(pre_neuron, sr_pre)
nest.Connect(pre_neuron, post_neuron, syn_spec=syn_specs)
weight_before_sim = nest.GetConnections(target=post_neuron, synapse_model=syn_model).weight
nest.Simulate(100)
print("Weight before: ", weight_before_sim) print("Weight after: ", nest.GetConnections(target=post_neuron, synapse_model=syn_model).weight)
fig, axs = plt.subplots(1, 2) axs[1].title.set_text("Post neuron") axs[0].title.set_text("Pre neuron")
events = sr_post.get("events") senders = events["senders"] ts = events["times"] axs[1].plot(ts, senders, "r.") events = sr_pre.get("events") senders = events["senders"] ts = events["times"] axs[0].plot(ts, senders, "r.")
plt.show()
Dear Iiro,
Thanks for your report! Please find below few pointers about the results you are seeing.
The "synaptic delay" in NEST is interpreted as a dendritic delay, that is, the backpropagation delay from a somatic spike into the dendrite (where it finally reaches the synapse on the postsynaptic side). Hopefully this can explain the timing question you are seeing. For more details on this algorithm, please see:
Morrison, A., Aertsen, A., & Diesmann, M. (2007). Spike-timing-dependent plasticity in balanced random networks. Neural Computation, 19(6), 1437-1467.
As for the missing weight updates that you are seeing, this is caused by an optimisation in the way NEST handles spikes. For efficiency reasons, postsynaptic spikes are not handeled immedately as they arrive at the synapse, but are buffered in the history buffer (the one that's inside the ArchivingNode, as you have already found). Only when the next presynaptic spike is received by the synapse, is the synaptic state (in particular, its weight) actually updated. For more details on this approach, please see:
Morrison, A., Diesmann, M., & Gerstner, W. (2008). Phenomenological models of synaptic plasticity based on spike timing. Biological Cybernetics, 98(6), 459-478.
Please let me know if this already answers your questions, otherwise I will be happy to go into more detail in a follow-up email.
Cheers, kind regards, Charl
On Wed, Mar 5, 2025, at 11:57, iiro.ahokainen@tuni.fi wrote:
I'm a bit confused with NEST's STDP models. I'm not sure if the issues that I have found are design choices or unintended bugs.
To debug stdp models, I added print statements in the stdp_synapse.h file:
double minus_dt; while ( start != finish ) { minus_dt = t_lastspike_ - ( start->t_ + dendritic_delay ); std::cout<<"minus_dt: "<<minus_dt<<std::endl; std::cout<<"postspike time: "<<start->t_<<std::endl; ++start;
// get_history() should make sure that // start->t_ > t_lastspike - dendritic_delay, i.e. minus_dt < 0 assert( minus_dt < -1.0 * kernel().connection_manager.get_stdp_eps() ); weight_ = facilitate_( weight_, Kplus_ * std::exp( minus_dt / tau_plus_ ) ); }
I created two parrot neurons and fed spike times to the first neuron. I set the syn_delay to 0.3 ms. I found two issues:
If the spike train consists of only one spike, no STDP updates
are performed. I would expect one positive update since the postsynaptic spike arrives 0.3 ms after the presynaptic spike.
minus_dt=-0.6 instead of -0.3. The spike separation is 0.3 ms,
however for the STDP calculation, one uses 0.6 ms which confuses me. Indeed, simulating with presynaptic spike train [10., 20.] leads to weight change 1.0->35.5443 (lamba=1, mu_plus=mu_minus=0, otherwise defaults). Analytically, the same can be achieved by calculating
(1/100 + np.exp(-0.6/20) - np.exp(-9.4/20))*100 = np.float64(35.54432652658074)
This confirms that indeed minus_dt=-0.6 ms and that the first LTP STDP interaction is neglected.
Are these known issues or new findings? I tested with NEST 3.3 and 3.8 and both had the same issues. I do not understand the point of having dendritic_delay in minus_dt = t_lastspike_ - ( start->t_ + dendritic_delay ), as it is already included in the start->t_ (?). _______________________________________________ NEST Users mailing list -- users@nest-simulator.org To unsubscribe send an email to users-leave@nest-simulator.org