Source code for nest.tests.test_stdp_multiplicity

# -*- coding: utf-8 -*-
#
# test_stdp_multiplicity.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST.  If not, see <http://www.gnu.org/licenses/>.

# This script tests the parrot_neuron in NEST.

import nest
import unittest
import math
import numpy as np


[docs]@nest.check_stack class StdpSpikeMultiplicity(unittest.TestCase): """ Test correct handling of spike multiplicity in STDP. This test originated from work on issue #77. Concerning the definition of STDP for multiplicity > 1, consider the following (see also @flinz on #82): A plastic synapse is created between two parrot neurons. As reference case, we use two precise spiking parrot neurons driven by spike trains n*h - k*delta with delta < h, and 1 < k < K such that K delta < h, i.e., we get K spikes in each time step (h is resolution). If we transmit the same spike trains via plain parrot neurons, they will be handled as spikes with multiplicity K. Then, in the limit delta -> 0, the weight changes observed in the synapse between the precise parrots shall converge to the weight changes observed between the plain parrots. We test the resulting weights as follows: 1. Weights obtained using parrot_neuron must be identical independent of delta, since in this case all spikes are at the end of the step, i.e., all spikes have identical times independent of delta. 2. We choose delta values that are decrease by factors of 2. The plasticity rules depend on spike-time differences through exp(dT / tau) where dT is the time between pre- and postsynaptic spikes. We construct pre- and postsynaptic spike times so that dT = pre_post_shift + m * delta with m * delta < resolution << pre_post_shift. The time-dependence of the plasticity rule is therefore to good approximation linear in delta. We can thus test as follows: Let w_pl be the weight obtained with the plain parrot, and w_ps_j the weight obtained with the precise parrot for delta_j = delta0 / 2^j. Then, ( w_ps_{j+1} - w_pl ) / ( w_ps_j - w_pl ) ~ 0.5 for all j i.e., the difference between plain and precise weights halves each time delta is halved. """
[docs] def run_protocol(self, pre_post_shift): """ Create network and simulate for each delta value. Returns a dict with the synaptic weight at end of simulation for plain and precise parrots, one weight per delta value. All values for the plain parrot case should be identical, and the values for the precise parrot case should converge to that value for delta -> 0. All delta values must fulfill multiplicity * delta < resolution / 2 so that in the plain case off-grid spike times are rounded up to the end of the step and thus belong to the same step as the corresponding precise spikes. :param pre_post_shift: Delay between pre- and postsynaptic trains :returns: {'parrot': [<weights>], 'parrot_ps': [<weights>]} """ multiplicity = 2**3 resolution = 2.**-4 tics_per_ms = 1. / resolution * multiplicity * 4 deltas = [resolution / multiplicity / 2**m for m in range(2, 10)] delay = 1. # k spikes will be emitted at these two times pre_spike_times_base = [100., 200.] nest.set_verbosity("M_WARNING") post_weights = {'parrot': [], 'parrot_ps': []} for delta in deltas: assert multiplicity * delta < resolution / 2., "Test inconsistent." nest.ResetKernel() nest.SetKernelStatus({'tics_per_ms': tics_per_ms, 'resolution': resolution}) pre_times = sorted(t_base - k * delta for t_base in pre_spike_times_base for k in range(multiplicity)) post_times = [pre_time + pre_post_shift for pre_time in pre_times] # create spike_generators with these times pre_sg = nest.Create("spike_generator", params={"spike_times": pre_times, 'allow_offgrid_spikes': True}) post_sg = nest.Create("spike_generator", params={"spike_times": post_times, 'allow_offgrid_spikes': True}) pre_sg_ps = nest.Create("spike_generator", params={"spike_times": pre_times, 'precise_times': True}) post_sg_ps = nest.Create("spike_generator", params={"spike_times": post_times, 'precise_times': True}) # create parrot neurons and connect spike_generators pre_parrot = nest.Create("parrot_neuron") post_parrot = nest.Create("parrot_neuron") pre_parrot_ps = nest.Create("parrot_neuron_ps") post_parrot_ps = nest.Create("parrot_neuron_ps") nest.Connect(pre_sg, pre_parrot, syn_spec={"delay": delay}) nest.Connect(post_sg, post_parrot, syn_spec={"delay": delay}) nest.Connect(pre_sg_ps, pre_parrot_ps, syn_spec={"delay": delay}) nest.Connect(post_sg_ps, post_parrot_ps, syn_spec={"delay": delay}) # create spike detector --- debugging only spikes = nest.Create("spike_detector", params={'precise_times': True}) nest.Connect( pre_parrot + post_parrot + pre_parrot_ps + post_parrot_ps, spikes ) # connect both parrot neurons with a stdp synapse onto port 1 # thereby spikes transmitted through the stdp connection are # not repeated postsynaptically. nest.Connect( pre_parrot, post_parrot, syn_spec={'model': 'stdp_synapse', 'receptor_type': 1}) nest.Connect( pre_parrot_ps, post_parrot_ps, syn_spec={'model': 'stdp_synapse', 'receptor_type': 1}) # get STDP synapse and weight before protocol syn = nest.GetConnections(source=pre_parrot, synapse_model="stdp_synapse") w_pre = nest.GetStatus(syn)[0]['weight'] syn_ps = nest.GetConnections(source=pre_parrot_ps, synapse_model="stdp_synapse") w_pre_ps = nest.GetStatus(syn)[0]['weight'] sim_time = max(pre_times + post_times) + 5 * delay nest.Simulate(sim_time) # get weight post protocol w_post = nest.GetStatus(syn)[0]['weight'] w_post_ps = nest.GetStatus(syn_ps)[0]['weight'] assert w_post != w_pre, "Plain parrot weight did not change." assert w_post_ps != w_pre_ps, "Precise parrot \ weight did not change." post_weights['parrot'].append(w_post) post_weights['parrot_ps'].append(w_post_ps) return post_weights
[docs] def test_ParrotNeuronSTDPProtocolPotentiation(self): """Check weight convergence on potentiation.""" post_weights = self.run_protocol(pre_post_shift=10.0) w_plain = np.array(post_weights['parrot']) w_precise = np.array(post_weights['parrot_ps']) assert all(w_plain == w_plain[0]), 'Plain weights differ' dw = w_precise - w_plain dwrel = dw[1:] / dw[:-1] assert all(np.round(dwrel, decimals=3) == 0.5), 'Precise weights do not converge.'
[docs] def test_ParrotNeuronSTDPProtocolDepression(self): """Check weight convergence on depression.""" post_weights = self.run_protocol(pre_post_shift=-10.0) w_plain = np.array(post_weights['parrot']) w_precise = np.array(post_weights['parrot_ps']) assert all(w_plain == w_plain[0]), 'Plain weights differ' dw = w_precise - w_plain dwrel = dw[1:] / dw[:-1] assert all(np.round(dwrel, decimals=3) == 0.5), 'Precise weights do not converge.'
[docs]def suite(): # makeSuite is sort of obsolete http://bugs.python.org/issue2721 # using loadTestsFromTestCase instead. suite = unittest.TestLoader().loadTestsFromTestCase(StdpSpikeMultiplicity) return unittest.TestSuite([suite])
[docs]def run(): runner = unittest.TextTestRunner(verbosity=2) runner.run(suite())
if __name__ == "__main__": run()