diff --git a/examples/nest/hpc_benchmark.sli b/examples/nest/hpc_benchmark.sli
index 1ccfab6ccf..ef7329916d 100644
--- a/examples/nest/hpc_benchmark.sli
+++ b/examples/nest/hpc_benchmark.sli
@@ -144,7 +144,6 @@ def
/V_reset 0.0 mV % Reset Potential (mV)
/tau_syn_ex tau_syn % time const. postsynaptic excitatory currents (ms)
/tau_syn_in tau_syn % time const. postsynaptic inhibitory currents (ms)
- /tau_minus 30.0 ms %time constant for STDP (depression)
% V can be randomly initialized see below
/V_m 5.7 mV % mean value of membrane potential
>>
@@ -169,6 +168,7 @@ def
/lambda 0.1 % STDP step size
/mu 0.4 % STDP weight dependence exponent (potentiation)
/tau_plus 15.0 % time constant for potentiation
+ /tau_minus 30.0 %time constant for depression
>>
/eta 1.685 % scaling of external stimulus
diff --git a/models/iaf_psc_alpha_hom.cpp b/models/iaf_psc_alpha_hom.cpp
new file mode 100644
index 0000000000..f9e5b03a01
--- /dev/null
+++ b/models/iaf_psc_alpha_hom.cpp
@@ -0,0 +1,408 @@
+/*
+ * iaf_psc_alpha_hom.cpp
+ *
+ * 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 .
+ *
+ */
+
+#include "iaf_psc_alpha_hom.h"
+
+// C++ includes:
+#include
+
+// Includes from libnestutil:
+#include "dict_util.h"
+#include "exceptions.h"
+#include "iaf_propagator.h"
+#include "kernel_manager.h"
+#include "nest_impl.h"
+#include "numerics.h"
+#include "ring_buffer_impl.h"
+#include "universal_data_logger_impl.h"
+
+// Includes from sli:
+#include "dictutils.h"
+
+nest::RecordablesMap< nest::iaf_psc_alpha_hom > nest::iaf_psc_alpha_hom::recordablesMap_;
+
+namespace nest
+{
+void
+register_iaf_psc_alpha_hom( const std::string& name )
+{
+ register_node_model< iaf_psc_alpha_hom >( name );
+}
+
+
+/*
+ * Override the create() method with one call to RecordablesMap::insert_()
+ * for each quantity to be recorded.
+ */
+template <>
+void
+RecordablesMap< iaf_psc_alpha_hom >::create()
+{
+ // use standard names wherever you can for consistency!
+ insert_( names::V_m, &iaf_psc_alpha_hom::get_V_m_ );
+ insert_( names::I_syn_ex, &iaf_psc_alpha_hom::get_I_syn_ex_ );
+ insert_( names::I_syn_in, &iaf_psc_alpha_hom::get_I_syn_in_ );
+}
+
+/* ----------------------------------------------------------------
+ * Default constructors defining default parameters and state
+ * ---------------------------------------------------------------- */
+
+iaf_psc_alpha_hom::Parameters_::Parameters_()
+ : Tau_( 10.0 ) // ms
+ , C_( 250.0 ) // pF
+ , TauR_( 2.0 ) // ms
+ , E_L_( -70.0 ) // mV
+ , I_e_( 0.0 ) // pA
+ , V_reset_( -70.0 - E_L_ ) // mV, rel to E_L_
+ , Theta_( -55.0 - E_L_ ) // mV, rel to E_L_
+ , LowerBound_( -std::numeric_limits< double >::infinity() )
+ , tau_ex_( 2.0 ) // ms
+ , tau_in_( 2.0 ) // ms
+{
+}
+
+iaf_psc_alpha_hom::State_::State_()
+ : y0_( 0.0 )
+ , dI_ex_( 0.0 )
+ , I_ex_( 0.0 )
+ , dI_in_( 0.0 )
+ , I_in_( 0.0 )
+ , y3_( 0.0 )
+ , r_( 0 )
+{
+}
+
+/* ----------------------------------------------------------------
+ * Parameter and state extractions and manipulation functions
+ * ---------------------------------------------------------------- */
+
+void
+iaf_psc_alpha_hom::Parameters_::get( DictionaryDatum& d ) const
+{
+ def< double >( d, names::E_L, E_L_ ); // Resting potential
+ def< double >( d, names::I_e, I_e_ );
+ def< double >( d, names::V_th, Theta_ + E_L_ ); // threshold value
+ def< double >( d, names::V_reset, V_reset_ + E_L_ );
+ def< double >( d, names::V_min, LowerBound_ + E_L_ );
+ def< double >( d, names::C_m, C_ );
+ def< double >( d, names::tau_m, Tau_ );
+ def< double >( d, names::t_ref, TauR_ );
+ def< double >( d, names::tau_syn_ex, tau_ex_ );
+ def< double >( d, names::tau_syn_in, tau_in_ );
+}
+
+double
+iaf_psc_alpha_hom::Parameters_::set( const DictionaryDatum& d, Node* node )
+{
+ // if E_L_ is changed, we need to adjust all variables defined relative to
+ // E_L_
+ const double ELold = E_L_;
+ updateValueParam< double >( d, names::E_L, E_L_, node );
+ const double delta_EL = E_L_ - ELold;
+
+ if ( updateValueParam< double >( d, names::V_reset, V_reset_, node ) )
+ {
+ V_reset_ -= E_L_;
+ }
+ else
+ {
+ V_reset_ -= delta_EL;
+ }
+
+ if ( updateValueParam< double >( d, names::V_th, Theta_, node ) )
+ {
+ Theta_ -= E_L_;
+ }
+ else
+ {
+ Theta_ -= delta_EL;
+ }
+
+ if ( updateValueParam< double >( d, names::V_min, LowerBound_, node ) )
+ {
+ LowerBound_ -= E_L_;
+ }
+ else
+ {
+ LowerBound_ -= delta_EL;
+ }
+
+ updateValueParam< double >( d, names::I_e, I_e_, node );
+ updateValueParam< double >( d, names::C_m, C_, node );
+ updateValueParam< double >( d, names::tau_m, Tau_, node );
+ updateValueParam< double >( d, names::tau_syn_ex, tau_ex_, node );
+ updateValueParam< double >( d, names::tau_syn_in, tau_in_, node );
+ updateValueParam< double >( d, names::t_ref, TauR_, node );
+
+ if ( C_ <= 0.0 )
+ {
+ throw BadProperty( "Capacitance must be > 0." );
+ }
+
+ if ( Tau_ <= 0.0 )
+ {
+ throw BadProperty( "Membrane time constant must be > 0." );
+ }
+
+ if ( tau_ex_ <= 0.0 or tau_in_ <= 0.0 )
+ {
+ throw BadProperty( "All synaptic time constants must be > 0." );
+ }
+
+ if ( TauR_ < 0.0 )
+ {
+ throw BadProperty( "The refractory time t_ref can't be negative." );
+ }
+ if ( V_reset_ >= Theta_ )
+ {
+ throw BadProperty( "Reset potential must be smaller than threshold." );
+ }
+
+ return delta_EL;
+}
+
+void
+iaf_psc_alpha_hom::State_::get( DictionaryDatum& d, const Parameters_& p ) const
+{
+ def< double >( d, names::V_m, y3_ + p.E_L_ ); // Membrane potential
+}
+
+void
+iaf_psc_alpha_hom::State_::set( const DictionaryDatum& d, const Parameters_& p, double delta_EL, Node* node )
+{
+ if ( updateValueParam< double >( d, names::V_m, y3_, node ) )
+ {
+ y3_ -= p.E_L_;
+ }
+ else
+ {
+ y3_ -= delta_EL;
+ }
+}
+
+iaf_psc_alpha_hom::Buffers_::Buffers_( iaf_psc_alpha_hom& n )
+ : logger_( n )
+{
+}
+
+iaf_psc_alpha_hom::Buffers_::Buffers_( const Buffers_&, iaf_psc_alpha_hom& n )
+ : logger_( n )
+{
+}
+
+
+/* ----------------------------------------------------------------
+ * Default and copy constructor for node
+ * ---------------------------------------------------------------- */
+
+iaf_psc_alpha_hom::iaf_psc_alpha_hom()
+ : ArchivingNodeHom()
+ , P_()
+ , S_()
+ , B_( *this )
+{
+ recordablesMap_.create();
+}
+
+iaf_psc_alpha_hom::iaf_psc_alpha_hom( const iaf_psc_alpha_hom& n )
+ : ArchivingNodeHom( n )
+ , P_( n.P_ )
+ , S_( n.S_ )
+ , B_( n.B_, *this )
+{
+}
+
+/* ----------------------------------------------------------------
+ * Node initialization functions
+ * ---------------------------------------------------------------- */
+
+void
+iaf_psc_alpha_hom::init_buffers_()
+{
+ B_.input_buffer_.clear(); // includes resize
+
+ B_.logger_.reset();
+
+ ArchivingNodeHom::clear_history();
+}
+
+void
+iaf_psc_alpha_hom::pre_run_hook()
+{
+ // ensures initialization in case mm connected after Simulate
+ B_.logger_.init();
+
+ const double h = Time::get_resolution().get_ms();
+
+ // these P are independent
+ V_.P11_ex_ = V_.P22_ex_ = std::exp( -h / P_.tau_ex_ );
+ V_.P11_in_ = V_.P22_in_ = std::exp( -h / P_.tau_in_ );
+
+ V_.P33_ = std::exp( -h / P_.Tau_ );
+
+ V_.expm1_tau_m_ = numerics::expm1( -h / P_.Tau_ );
+
+ // these depend on the above. Please do not change the order.
+ V_.P30_ = -P_.Tau_ / P_.C_ * numerics::expm1( -h / P_.Tau_ );
+ V_.P21_ex_ = h * V_.P11_ex_;
+ V_.P21_in_ = h * V_.P11_in_;
+
+ // these are determined according to a numeric stability criterion
+ std::tie( V_.P31_ex_, V_.P32_ex_ ) = IAFPropagatorAlpha( P_.tau_ex_, P_.Tau_, P_.C_ ).evaluate( h );
+ std::tie( V_.P31_in_, V_.P32_in_ ) = IAFPropagatorAlpha( P_.tau_in_, P_.Tau_, P_.C_ ).evaluate( h );
+
+ V_.EPSCInitialValue_ = 1.0 * numerics::e / P_.tau_ex_;
+ V_.IPSCInitialValue_ = 1.0 * numerics::e / P_.tau_in_;
+
+ // TauR specifies the length of the absolute refractory period as
+ // a double in ms. The grid based iaf_psc_alpha can only handle refractory
+ // periods that are integer multiples of the computation step size (h).
+ // To ensure consistency with the overall simulation scheme such conversion
+ // should be carried out via objects of class nest::Time. The conversion
+ // requires 2 steps:
+ // 1. A time object is constructed defining representation of
+ // TauR in tics. This representation is then converted to computation
+ // time steps again by a strategy defined by class nest::Time.
+ // 2. The refractory time in units of steps is read out get_steps(), a
+ // member function of class nest::Time.
+ //
+ // The definition of the refractory period of the iaf_psc_alpha is consistent
+ // the one of iaf_psc_alpha_ps.
+ //
+ // Choosing a TauR that is not an integer multiple of the computation time
+ // step h will lead to accurate (up to the resolution h) and self-consistent
+ // results. However, a neuron model capable of operating with real valued
+ // spike time may exhibit a different effective refractory time.
+
+ V_.RefractoryCounts_ = Time( Time::ms( P_.TauR_ ) ).get_steps();
+ // since t_ref_ >= 0, this can only fail in error
+ assert( V_.RefractoryCounts_ >= 0 );
+}
+
+/* ----------------------------------------------------------------
+ * Update and spike handling functions
+ */
+
+void
+iaf_psc_alpha_hom::update( Time const& origin, const long from, const long to )
+{
+ for ( long lag = from; lag < to; ++lag )
+ {
+ if ( S_.r_ == 0 )
+ {
+ // neuron not refractory
+ S_.y3_ = V_.P30_ * ( S_.y0_ + P_.I_e_ ) + V_.P31_ex_ * S_.dI_ex_ + V_.P32_ex_ * S_.I_ex_ + V_.P31_in_ * S_.dI_in_
+ + V_.P32_in_ * S_.I_in_ + V_.expm1_tau_m_ * S_.y3_ + S_.y3_;
+
+ // lower bound of membrane potential
+ S_.y3_ = ( S_.y3_ < P_.LowerBound_ ? P_.LowerBound_ : S_.y3_ );
+ }
+ else
+ {
+ // neuron is absolute refractory
+ --S_.r_;
+ }
+
+ // alpha shape EPSCs
+ S_.I_ex_ = V_.P21_ex_ * S_.dI_ex_ + V_.P22_ex_ * S_.I_ex_;
+ S_.dI_ex_ *= V_.P11_ex_;
+
+ // get read access to the correct input-buffer slot
+ const size_t input_buffer_slot = kernel().event_delivery_manager.get_modulo( lag );
+ auto& input = B_.input_buffer_.get_values_all_channels( input_buffer_slot );
+
+ // Apply spikes delivered in this step; spikes arriving at T+1 have
+ // an immediate effect on the state of the neuron
+ V_.weighted_spikes_ex_ = input[ Buffers_::SYN_EX ];
+ S_.dI_ex_ += V_.EPSCInitialValue_ * V_.weighted_spikes_ex_;
+
+ // alpha shape EPSCs
+ S_.I_in_ = V_.P21_in_ * S_.dI_in_ + V_.P22_in_ * S_.I_in_;
+ S_.dI_in_ *= V_.P11_in_;
+
+ // Apply spikes delivered in this step; spikes arriving at T+1 have
+ // an immediate effect on the state of the neuron
+ V_.weighted_spikes_in_ = input[ Buffers_::SYN_IN ];
+ S_.dI_in_ += V_.IPSCInitialValue_ * V_.weighted_spikes_in_;
+
+ // threshold crossing
+ if ( S_.y3_ >= P_.Theta_ )
+ {
+ S_.r_ = V_.RefractoryCounts_;
+ S_.y3_ = P_.V_reset_;
+ // A supra-threshold membrane potential should never be observable.
+ // The reset at the time of threshold crossing enables accurate
+ // integration independent of the computation step size, see [2,3] for
+ // details.
+
+ set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
+ SpikeEvent se;
+ kernel().event_delivery_manager.send( *this, se, lag );
+ }
+
+ // set new input current
+ S_.y0_ = input[ Buffers_::I0 ];
+
+ // reset all values in the currently processed input-buffer slot
+ B_.input_buffer_.reset_values_all_channels( input_buffer_slot );
+
+ // log state data
+ B_.logger_.record_data( origin.get_steps() + lag );
+ }
+}
+
+void
+iaf_psc_alpha_hom::handle( SpikeEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+
+ const size_t input_buffer_slot = kernel().event_delivery_manager.get_modulo(
+ e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ) );
+
+ const double s = e.get_weight() * e.get_multiplicity();
+
+ // separate buffer channels for excitatory and inhibitory inputs
+ B_.input_buffer_.add_value( input_buffer_slot, s > 0 ? Buffers_::SYN_EX : Buffers_::SYN_IN, s );
+}
+
+void
+iaf_psc_alpha_hom::handle( CurrentEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+
+ const size_t input_buffer_slot = kernel().event_delivery_manager.get_modulo(
+ e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ) );
+
+ const double I = e.get_current();
+ const double w = e.get_weight();
+
+ B_.input_buffer_.add_value( input_buffer_slot, Buffers_::I0, w * I );
+}
+
+void
+iaf_psc_alpha_hom::handle( DataLoggingRequest& e )
+{
+ B_.logger_.handle( e );
+}
+
+} // namespace
diff --git a/models/iaf_psc_alpha_hom.h b/models/iaf_psc_alpha_hom.h
new file mode 100644
index 0000000000..b2d387ee91
--- /dev/null
+++ b/models/iaf_psc_alpha_hom.h
@@ -0,0 +1,487 @@
+/*
+ * iaf_psc_alpha_hom.h
+ *
+ * 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 .
+ *
+ */
+
+#ifndef IAF_PSC_ALPHA_HOM_H
+#define IAF_PSC_ALPHA_HOM_H
+
+// Includes from nestkernel:
+#include "archiving_node_hom.h"
+#include "connection.h"
+#include "event.h"
+#include "nest_types.h"
+#include "recordables_map.h"
+#include "ring_buffer.h"
+#include "universal_data_logger.h"
+
+namespace nest
+{
+// Disable clang-formatting for documentation due to over-wide table.
+// clang-format off
+/* BeginUserDocs: neuron, integrate-and-fire, current-based
+
+Short description
++++++++++++++++++
+
+Leaky integrate-and-fire model with alpha-shaped input currents
+
+Description
++++++++++++
+
+``iaf_psc_alpha_hom`` is a leaky integrate-and-fire neuron model with
+
+* a hard threshold,
+* a fixed refractory period,
+* no adaptation mechanisms,
+ * exponential lookup tables for efficiency,
+* :math:`\alpha`-shaped synaptic input currents.
+
+Membrane potential evolution, spike emission, and refractoriness
+................................................................
+
+The membrane potential evolves according to
+
+.. math::
+
+ \frac{dV_\text{m}}{dt} = -\frac{V_{\text{m}} - E_\text{L}}{\tau_{\text{m}}} + \frac{I_{\text{syn}} + I_\text{e}}{C_{\text{m}}}
+
+where the synaptic input current :math:`I_{\text{syn}}(t)` is discussed below and :math:`I_\text{e}` is
+a constant input current set as a model parameter.
+
+A spike is emitted at time step :math:`t^*=t_{k+1}` if
+
+.. math::
+
+ V_\text{m}(t_k) < V_{th} \quad\text{and}\quad V_\text{m}(t_{k+1})\geq V_\text{th} \;.
+
+Subsequently,
+
+.. math::
+
+ V_\text{m}(t) = V_{\text{reset}} \quad\text{for}\quad t^* \leq t < t^* + t_{\text{ref}} \;,
+
+that is, the membrane potential is clamped to :math:`V_{\text{reset}}` during the refractory period.
+
+Synaptic input
+..............
+
+The synaptic input current has an excitatory and an inhibitory component
+
+.. math::
+
+ I_{\text{syn}}(t) = I_{\text{syn, ex}}(t) + I_{\text{syn, in}}(t)
+
+where
+
+.. math::
+
+ I_{\text{syn, X}}(t) = \sum_{j} w_j \sum_k i_{\text{syn, X}}(t-t_j^k-d_j) \;,
+
+where :math:`j` indexes either excitatory (:math:`\text{X} = \text{ex}`)
+or inhibitory (:math:`\text{X} = \text{in}`) presynaptic neurons,
+:math:`k` indexes the spike times of neuron :math:`j`, and :math:`d_j`
+is the delay from neuron :math:`j`.
+
+The individual post-synaptic currents (PSCs) are given by
+
+.. math::
+
+ i_{\text{syn, X}}(t) = \frac{e}{\tau_{\text{syn, X}}} t e^{-\frac{t}{\tau_{\text{syn, X}}}} \Theta(t)
+
+where :math:`\Theta(x)` is the Heaviside step function. The PSCs are normalized to unit maximum, that is,
+
+.. math::
+
+ i_{\text{syn, X}}(t= \tau_{\text{syn, X}}) = 1 \;.
+
+As a consequence, the total charge :math:`q` transferred by a single PSC depends
+on the synaptic time constant according to
+
+.. math::
+
+ q = \int_0^{\infty} i_{\text{syn, X}}(t) dt = e \tau_{\text{syn, X}} \;.
+
+By default, :math:`V_\text{m}` is not bounded from below. To limit
+hyperpolarization to biophysically plausible values, set parameter
+:math:`V_{\text{min}}` as lower bound of :math:`V_\text{m}`.
+
+.. note::
+
+ NEST uses exact integration [1]_, [2]_ to integrate subthreshold membrane
+ dynamics with maximum precision; see also [3]_.
+
+ If :math:`\tau_\text{m}\approx \tau_{\text{syn, ex}}` or
+ :math:`\tau_\text{m}\approx \tau_{\text{syn, in}}`, the model will
+ numerically behave as if :math:`\tau_\text{m} = \tau_{\text{syn, ex}}` or
+ :math:`\tau_\text{m} = \tau_{\text{syn, in}}`, respectively, to avoid
+ numerical instabilities.
+
+ For implementation details see the
+ `IAF Integration Singularity notebook <../model_details/IAF_Integration_Singularity.ipynb>`_.
+
+
+Parameters
+++++++++++
+
+The following parameters can be set in the status dictionary.
+
+=============== ================== =============================== ========================================================================
+**Parameter** **Default** **Math equivalent** **Description**
+=============== ================== =============================== ========================================================================
+``E_L`` -70 mV :math:`E_\text{L}` Resting membrane potential
+``C_m`` 250 pF :math:`C_{\text{m}}` Capacity of the membrane
+``tau_m`` 10 ms :math:`\tau_{\text{m}}` Membrane time constant
+``t_ref`` 2 ms :math:`t_{\text{ref}}` Duration of refractory period
+``V_th`` -55 mV :math:`V_{\text{th}}` Spike threshold
+``V_reset`` -70 mV :math:`V_{\text{reset}}` Reset potential of the membrane
+``tau_syn_ex`` 2 ms :math:`\tau_{\text{syn, ex}}` Rise time of the excitatory synaptic alpha function
+``tau_syn_in`` 2 ms :math:`\tau_{\text{syn, in}}` Rise time of the inhibitory synaptic alpha function
+``I_e`` 0 pA :math:`I_\text{e}` Constant input current
+``V_min`` :math:`-\infty` mV :math:`V_{\text{min}}` Absolute lower value for the membrane potential
+=============== ================== =============================== ========================================================================
+
+The following state variables evolve during simulation and are available either as neuron properties or as recordables.
+
+================== ================= ========================== =================================
+**State variable** **Initial value** **Math equivalent** **Description**
+================== ================= ========================== =================================
+``V_m`` -70 mV :math:`V_{\text{m}}` Membrane potential
+``I_syn_ex`` 0 pA :math:`I_{\text{syn, ex}}` Excitatory synaptic input current
+``I_syn_in`` 0 pA :math:`I_{\text{syn, in}}` Inhibitory synaptic input current
+================== ================= ========================== =================================
+
+
+References
+++++++++++
+
+.. [1] Rotter S, Diesmann M (1999). Exact simulation of
+ time-invariant linear systems with applications to neuronal
+ modeling. Biologial Cybernetics 81:381-402.
+ DOI: https://doi.org/10.1007/s004220050570
+.. [2] Diesmann M, Gewaltig M-O, Rotter S, & Aertsen A (2001). State
+ space analysis of synchronous spiking in cortical neural
+ networks. Neurocomputing 38-40:565-571.
+ DOI: https://doi.org/10.1016/S0925-2312(01)00409-X
+.. [3] Morrison A, Straube S, Plesser H E, Diesmann M (2006). Exact
+ subthreshold integration with continuous spike times in discrete time
+ neural network simulations. Neural Computation, in press
+ DOI: https://doi.org/10.1162/neco.2007.19.1.47
+
+Sends
++++++
+
+SpikeEvent
+
+Receives
+++++++++
+
+SpikeEvent, CurrentEvent, DataLoggingRequest
+
+See also
+++++++++
+
+iaf_psc_delta, iaf_psc_exp, iaf_cond_exp
+
+
+Examples using this model
++++++++++++++++++++++++++
+
+.. listexamples:: iaf_psc_alpha_hom
+
+EndUserDocs */
+// clang-format on
+
+void register_iaf_psc_alpha_hom( const std::string& name );
+
+class iaf_psc_alpha_hom : public ArchivingNodeHom
+{
+
+public:
+ iaf_psc_alpha_hom();
+ iaf_psc_alpha_hom( const iaf_psc_alpha_hom& );
+
+ /**
+ * Import sets of overloaded virtual functions.
+ * @see Technical Issues / Virtual Functions: Overriding, Overloading, and
+ * Hiding
+ */
+ using Node::handle;
+ using Node::handles_test_event;
+
+ size_t send_test_event( Node&, size_t, synindex, bool ) override;
+
+ void handle( SpikeEvent& ) override;
+ void handle( CurrentEvent& ) override;
+ void handle( DataLoggingRequest& ) override;
+
+ size_t handles_test_event( SpikeEvent&, size_t ) override;
+ size_t handles_test_event( CurrentEvent&, size_t ) override;
+ size_t handles_test_event( DataLoggingRequest&, size_t ) override;
+
+ void get_status( DictionaryDatum& ) const override;
+ void set_status( const DictionaryDatum& ) override;
+
+private:
+ void init_buffers_() override;
+ void pre_run_hook() override;
+
+ void update( Time const&, const long, const long ) override;
+
+ // The next two classes need to be friends to access the State_ class/member
+ friend class RecordablesMap< iaf_psc_alpha_hom >;
+ friend class UniversalDataLogger< iaf_psc_alpha_hom >;
+
+ // ----------------------------------------------------------------
+
+ struct Parameters_
+ {
+ /** Membrane time constant in ms. */
+ double Tau_;
+
+ /** Membrane capacitance in pF. */
+ double C_;
+
+ /** Refractory period in ms. */
+ double TauR_;
+
+ /** Resting potential in mV. */
+ double E_L_;
+
+ /** External current in pA */
+ double I_e_;
+
+ /** Reset value of the membrane potential */
+ double V_reset_;
+
+ /** Threshold, RELATIVE TO RESTING POTENTIAL(!).
+ I.e. the real threshold is (E_L_+Theta_). */
+ double Theta_;
+
+ /** Lower bound, RELATIVE TO RESTING POTENTIAL(!).
+ I.e. the real lower bound is (LowerBound_+E_L_). */
+ double LowerBound_;
+
+ /** Time constant of excitatory synaptic current in ms. */
+ double tau_ex_;
+
+ /** Time constant of inhibitory synaptic current in ms. */
+ double tau_in_;
+
+ Parameters_(); //!< Sets default parameter values
+
+ void get( DictionaryDatum& ) const; //!< Store current values in dictionary
+
+ /** Set values from dictionary.
+ * @returns Change in reversal potential E_L, to be passed to State_::set()
+ */
+ double set( const DictionaryDatum&, Node* node );
+ };
+
+ // ----------------------------------------------------------------
+
+ struct State_
+ {
+ double y0_; //!< Constant current
+ double dI_ex_;
+ double I_ex_;
+ double dI_in_;
+ double I_in_;
+ //! This is the membrane potential RELATIVE TO RESTING POTENTIAL.
+ double y3_;
+
+ int r_; //!< Number of refractory steps remaining
+
+ State_(); //!< Default initialization
+
+ void get( DictionaryDatum&, const Parameters_& ) const;
+
+ /** Set values from dictionary.
+ * @param dictionary to take data from
+ * @param current parameters
+ * @param Change in reversal potential E_L specified by this dict
+ */
+ void set( const DictionaryDatum&, const Parameters_&, double, Node* node );
+ };
+
+ // ----------------------------------------------------------------
+
+ struct Buffers_
+ {
+
+ Buffers_( iaf_psc_alpha_hom& );
+ Buffers_( const Buffers_&, iaf_psc_alpha_hom& );
+
+ //! Indices for access to different channels of input_buffer_
+ enum
+ {
+ SYN_IN = 0,
+ SYN_EX,
+ I0,
+ NUM_INPUT_CHANNELS
+ };
+
+ /** buffers and sums up incoming spikes/currents */
+ MultiChannelInputBuffer< NUM_INPUT_CHANNELS > input_buffer_;
+
+ //! Logger for all analog data
+ UniversalDataLogger< iaf_psc_alpha_hom > logger_;
+ };
+
+ // ----------------------------------------------------------------
+
+ struct Variables_
+ {
+
+ /** Amplitude of the synaptic current.
+ This value is chosen such that a postsynaptic potential with
+ weight one has an amplitude of 1 mV.
+ */
+ double EPSCInitialValue_;
+ double IPSCInitialValue_;
+ int RefractoryCounts_;
+
+ double P11_ex_;
+ double P21_ex_;
+ double P22_ex_;
+ double P31_ex_;
+ double P32_ex_;
+ double P11_in_;
+ double P21_in_;
+ double P22_in_;
+ double P31_in_;
+ double P32_in_;
+ double P30_;
+ double P33_;
+ double expm1_tau_m_;
+
+ double weighted_spikes_ex_;
+ double weighted_spikes_in_;
+ };
+
+ // Access functions for UniversalDataLogger -------------------------------
+
+ //! Read out the real membrane potential
+ inline double
+ get_V_m_() const
+ {
+ return S_.y3_ + P_.E_L_;
+ }
+
+ inline double
+ get_I_syn_ex_() const
+ {
+ return S_.I_ex_;
+ }
+
+ inline double
+ get_I_syn_in_() const
+ {
+ return S_.I_in_;
+ }
+
+ // Data members -----------------------------------------------------------
+
+ /**
+ * Instances of private data structures for the different types
+ * of data pertaining to the model.
+ * @note The order of definitions is important for speed.
+ * @{
+ */
+ Parameters_ P_;
+ State_ S_;
+ Variables_ V_;
+ Buffers_ B_;
+ /** @} */
+
+ //! Mapping of recordables names to access functions
+ static RecordablesMap< iaf_psc_alpha_hom > recordablesMap_;
+};
+
+inline size_t
+nest::iaf_psc_alpha_hom::send_test_event( Node& target, size_t receptor_type, synindex, bool )
+{
+ SpikeEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
+}
+
+inline size_t
+iaf_psc_alpha_hom::handles_test_event( SpikeEvent&, size_t receptor_type )
+{
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+ return 0;
+}
+
+inline size_t
+iaf_psc_alpha_hom::handles_test_event( CurrentEvent&, size_t receptor_type )
+{
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+ return 0;
+}
+
+inline size_t
+iaf_psc_alpha_hom::handles_test_event( DataLoggingRequest& dlr, size_t receptor_type )
+{
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+ return B_.logger_.connect_logging_device( dlr, recordablesMap_ );
+}
+
+inline void
+iaf_psc_alpha_hom::get_status( DictionaryDatum& d ) const
+{
+ P_.get( d );
+ S_.get( d, P_ );
+ ArchivingNodeHom::get_status( d );
+
+ ( *d )[ names::recordables ] = recordablesMap_.get_list();
+}
+
+inline void
+iaf_psc_alpha_hom::set_status( const DictionaryDatum& d )
+{
+ Parameters_ ptmp = P_; // temporary copy in case of errors
+ const double delta_EL = ptmp.set( d, this ); // throws if BadProperty
+ State_ stmp = S_; // temporary copy in case of errors
+ stmp.set( d, ptmp, delta_EL, this ); // throws if BadProperty
+
+ // We now know that (ptmp, stmp) are consistent. We do not
+ // write them back to (P_, S_) before we are also sure that
+ // the properties to be set in the parent class are internally
+ // consistent.
+ ArchivingNodeHom::set_status( d );
+
+ // if we get here, temporaries contain consistent set of properties
+ P_ = ptmp;
+ S_ = stmp;
+}
+
+} // namespace
+
+#endif /* #ifndef IAF_PSC_ALPHA_HOM_H */
diff --git a/models/stdp_pl_synapse_hom.cpp b/models/stdp_pl_synapse_hom.cpp
index 3bc6a0c247..bb00b10e78 100644
--- a/models/stdp_pl_synapse_hom.cpp
+++ b/models/stdp_pl_synapse_hom.cpp
@@ -47,7 +47,7 @@ namespace nest
STDPPLHomCommonProperties::STDPPLHomCommonProperties()
: CommonSynapseProperties()
, tau_plus_( 20.0 )
- , tau_plus_inv_( 1. / tau_plus_ )
+ , tau_minus_( 20.0 )
, lambda_( 0.1 )
, alpha_( 1.0 )
, mu_( 0.4 )
@@ -60,6 +60,7 @@ STDPPLHomCommonProperties::get_status( DictionaryDatum& d ) const
CommonSynapseProperties::get_status( d );
def< double >( d, names::tau_plus, tau_plus_ );
+ def< double >( d, names::tau_minus, tau_minus_ );
def< double >( d, names::lambda, lambda_ );
def< double >( d, names::alpha, alpha_ );
def< double >( d, names::mu, mu_ );
@@ -70,18 +71,62 @@ STDPPLHomCommonProperties::set_status( const DictionaryDatum& d, ConnectorModel&
{
CommonSynapseProperties::set_status( d, cm );
- updateValue< double >( d, names::tau_plus, tau_plus_ );
- if ( tau_plus_ > 0. )
+ if ( updateValue< double >( d, names::tau_plus, tau_plus_ ) )
{
- tau_plus_inv_ = 1. / tau_plus_;
+ if ( tau_plus_ > 0. )
+ {
+ init_exp_tau_plus();
+ }
+ else
+ {
+ throw BadProperty( "tau_plus > 0. required." );
+ }
}
- else
+
+ if ( updateValue< double >( d, names::tau_minus, tau_minus_ ) )
{
- throw BadProperty( "tau_plus > 0. required." );
+ if ( tau_minus_ > 0. )
+ {
+ init_exp_tau_minus();
+ }
+ else
+ {
+ throw BadProperty( "tau_minus > 0. required." );
+ }
}
+
updateValue< double >( d, names::lambda, lambda_ );
updateValue< double >( d, names::alpha, alpha_ );
updateValue< double >( d, names::mu, mu_ );
}
+void
+STDPPLHomCommonProperties::init_exp_tau_plus()
+{
+ const auto minus_tau_plus_inv_ = -1. / tau_plus_;
+ exp_tau_plus_.resize( EXP_LOOKUP_SIZE, 0.0 );
+ for ( unsigned long dt = 0; dt < exp_tau_plus_.size(); ++dt )
+ {
+ exp_tau_plus_[ dt ] = std::exp( Time( Time::step( dt ) ).get_ms() * minus_tau_plus_inv_ );
+ }
+}
+
+void
+STDPPLHomCommonProperties::init_exp_tau_minus()
+{
+ const auto minus_tau_minus_inv_ = -1. / tau_minus_;
+ exp_tau_minus_.resize( EXP_LOOKUP_SIZE, 0.0 );
+ for ( unsigned long dt = 0; dt < exp_tau_minus_.size(); ++dt )
+ {
+ exp_tau_minus_[ dt ] = std::exp( Time( Time::step( dt ) ).get_ms() * minus_tau_minus_inv_ );
+ }
+}
+
+void
+STDPPLHomCommonProperties::calibrate( const TimeConverter& )
+{
+ init_exp_tau_minus();
+ init_exp_tau_plus();
+}
+
} // of namespace nest
diff --git a/models/stdp_pl_synapse_hom.h b/models/stdp_pl_synapse_hom.h
index b01a79c79d..6ed1120c22 100644
--- a/models/stdp_pl_synapse_hom.h
+++ b/models/stdp_pl_synapse_hom.h
@@ -25,6 +25,7 @@
// C++ includes:
#include
+#include
// Includes from nestkernel:
#include "connection.h"
@@ -97,6 +98,7 @@ EndUserDocs */
*/
class STDPPLHomCommonProperties : public CommonSynapseProperties
{
+ constexpr static size_t EXP_LOOKUP_SIZE = 10000;
public:
/**
@@ -115,14 +117,55 @@ class STDPPLHomCommonProperties : public CommonSynapseProperties
*/
void set_status( const DictionaryDatum& d, ConnectorModel& cm );
+ void init_exp_tau_plus();
+ void init_exp_tau_minus();
+
+ double get_exp_tau_plus( const long dt_steps ) const;
+ double get_exp_tau_minus( const long dt_steps ) const;
+
+ void calibrate( const TimeConverter& );
+
// data members common to all connections
double tau_plus_;
- double tau_plus_inv_; //!< 1 / tau_plus for efficiency
+ double tau_minus_;
double lambda_;
double alpha_;
double mu_;
+
+ // look up table for the exponentials
+ // exp( -dt / tau_plus ) and exp( -dt / tau_minus )
+ std::vector< double > exp_tau_plus_;
+ std::vector< double > exp_tau_minus_;
};
+inline double
+STDPPLHomCommonProperties::get_exp_tau_plus( const long dt_steps ) const
+{
+ if ( static_cast< size_t >( dt_steps ) < exp_tau_plus_.size() )
+ {
+ return exp_tau_plus_[ dt_steps ];
+ }
+ else
+ {
+ const auto minus_tau_plus_inv_ = -1. / tau_plus_;
+ return std::exp( Time( Time::step( dt_steps ) ).get_ms() * minus_tau_plus_inv_ );
+ }
+}
+
+inline double
+STDPPLHomCommonProperties::get_exp_tau_minus( const long dt_steps ) const
+{
+ if ( static_cast< size_t >( dt_steps ) < exp_tau_minus_.size() )
+ {
+ return exp_tau_minus_[ dt_steps ];
+ }
+ else
+ {
+ const auto minus_tau_minus_inv_ = -1. / tau_minus_;
+ return std::exp( Time( Time::step( dt_steps ) ).get_ms() * minus_tau_minus_inv_ );
+ }
+}
+
/**
* Class representing an STDP connection with homogeneous parameters, i.e.
@@ -207,13 +250,13 @@ class stdp_pl_synapse_hom : public Connection< targetidentifierT >
* \param receptor_type The ID of the requested receptor type
*/
void
- check_connection( Node& s, Node& t, size_t receptor_type, const CommonPropertiesType& )
+ check_connection( Node& s, Node& t, size_t receptor_type, const CommonPropertiesType& cp )
{
ConnTestDummyNode dummy_target;
ConnectionBase::check_connection_( dummy_target, s, t, receptor_type );
- t.register_stdp_connection( t_lastspike_ - get_delay(), get_delay() );
+ t.register_stdp_connection( t_lastspike_ - get_delay_steps(), get_delay_steps(), cp.tau_minus_ );
}
void
@@ -239,7 +282,7 @@ class stdp_pl_synapse_hom : public Connection< targetidentifierT >
// data members of each connection
double weight_;
double Kplus_;
- double t_lastspike_;
+ long t_lastspike_;
};
template < typename targetidentifierT >
@@ -260,33 +303,35 @@ stdp_pl_synapse_hom< targetidentifierT >::send( Event& e, size_t t, const STDPPL
{
// synapse STDP depressing/facilitation dynamics
- const double t_spike = e.get_stamp().get_ms();
+ const long t_spike = e.get_stamp().get_steps();
// t_lastspike_ = 0 initially
Node* target = get_target( t );
- double dendritic_delay = get_delay();
+ const long dendritic_delay = get_delay_steps();
// get spike history in relevant range (t1, t2] from postsynaptic neuron
- std::deque< histentry >::iterator start;
- std::deque< histentry >::iterator finish;
+ std::deque< histentry_step >::iterator start;
+ std::deque< histentry_step >::iterator finish;
target->get_history( t_lastspike_ - dendritic_delay, t_spike - dendritic_delay, &start, &finish );
// facilitation due to postsynaptic spikes since last pre-synaptic spike
- double minus_dt;
+ size_t dt;
while ( start != finish )
{
- minus_dt = t_lastspike_ - ( start->t_ + dendritic_delay );
+ dt = ( start->t_ + dendritic_delay ) - t_lastspike_;
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 * cp.tau_plus_inv_ ), cp );
+
+ weight_ = facilitate_( weight_, Kplus_ * cp.get_exp_tau_plus( dt ), cp );
}
// depression due to new pre-synaptic spike
- weight_ = depress_( weight_, target->get_K_value( t_spike - dendritic_delay ), cp );
+
+ const auto k_val = target->get_K_value( t_spike - dendritic_delay, dt );
+ weight_ = depress_( weight_, k_val * cp.get_exp_tau_minus( dt ), cp );
e.set_receiver( *target );
e.set_weight( weight_ );
@@ -294,7 +339,7 @@ stdp_pl_synapse_hom< targetidentifierT >::send( Event& e, size_t t, const STDPPL
e.set_rport( get_rport() );
e();
- Kplus_ = Kplus_ * std::exp( ( t_lastspike_ - t_spike ) * cp.tau_plus_inv_ ) + 1.0;
+ Kplus_ = Kplus_ * cp.get_exp_tau_plus( t_spike - t_lastspike_ ) + 1.0;
t_lastspike_ = t_spike;
@@ -306,7 +351,7 @@ stdp_pl_synapse_hom< targetidentifierT >::stdp_pl_synapse_hom()
: ConnectionBase()
, weight_( 1.0 )
, Kplus_( 0.0 )
- , t_lastspike_( 0.0 )
+ , t_lastspike_( 0 )
{
}
@@ -331,7 +376,6 @@ stdp_pl_synapse_hom< targetidentifierT >::set_status( const DictionaryDatum& d,
// base class properties
ConnectionBase::set_status( d, cm );
updateValue< double >( d, names::weight, weight_ );
-
updateValue< double >( d, names::Kplus, Kplus_ );
}
diff --git a/modelsets/full b/modelsets/full
index 2d8bbf461c..ef5f368d85 100644
--- a/modelsets/full
+++ b/modelsets/full
@@ -54,6 +54,7 @@ iaf_cond_beta
iaf_cond_exp
iaf_cond_exp_sfa_rr
iaf_psc_alpha
+iaf_psc_alpha_hom
iaf_psc_alpha_multisynapse
iaf_psc_alpha_ps
iaf_psc_delta
diff --git a/nestkernel/CMakeLists.txt b/nestkernel/CMakeLists.txt
index 94aee72cc9..298283ba48 100644
--- a/nestkernel/CMakeLists.txt
+++ b/nestkernel/CMakeLists.txt
@@ -21,6 +21,7 @@ set ( nestkernel_sources
universal_data_logger_impl.h universal_data_logger.h
recordables_map.h
archiving_node.h archiving_node.cpp
+ archiving_node_hom.h archiving_node_hom.cpp
clopath_archiving_node.h clopath_archiving_node.cpp
urbanczik_archiving_node.h urbanczik_archiving_node_impl.h
eprop_archiving_node.h eprop_archiving_node_impl.h eprop_archiving_node.cpp
diff --git a/nestkernel/archiving_node_hom.cpp b/nestkernel/archiving_node_hom.cpp
new file mode 100644
index 0000000000..53ad2c96f2
--- /dev/null
+++ b/nestkernel/archiving_node_hom.cpp
@@ -0,0 +1,286 @@
+/*
+ * archiving_node_hom.cpp
+ *
+ * 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 .
+ *
+ */
+
+#include "archiving_node_hom.h"
+
+// Includes from nestkernel:
+#include "kernel_manager.h"
+
+// Includes from sli:
+#include "dictutils.h"
+
+namespace nest
+{
+
+// member functions for ArchivingNode
+
+nest::ArchivingNodeHom::ArchivingNodeHom()
+ : n_incoming_( 0 )
+ , Kminus_( 0.0 )
+ , Kminus_triplet_( 0.0 )
+ , tau_minus_( TAU_MINUS_PLACEHOLDER )
+ , tau_minus_inv_( 1. / tau_minus_ )
+ , tau_minus_triplet_( 110.0 )
+ , tau_minus_triplet_inv_( 1. / tau_minus_triplet_ )
+ , max_delay_( 0 )
+ , trace_( 0.0 )
+ , last_spike_( -1 )
+{
+}
+
+nest::ArchivingNodeHom::ArchivingNodeHom( const ArchivingNodeHom& n )
+ : StructuralPlasticityNode( n )
+ , n_incoming_( n.n_incoming_ )
+ , Kminus_( n.Kminus_ )
+ , Kminus_triplet_( n.Kminus_triplet_ )
+ , tau_minus_( TAU_MINUS_PLACEHOLDER )
+ , tau_minus_inv_( 1. / tau_minus_ )
+ , tau_minus_triplet_( n.tau_minus_triplet_ )
+ , tau_minus_triplet_inv_( n.tau_minus_triplet_inv_ )
+ , max_delay_( n.max_delay_ )
+ , trace_( n.trace_ )
+ , last_spike_( n.last_spike_ )
+{
+}
+
+void
+ArchivingNodeHom::register_stdp_connection( size_t t_first_read, size_t delay, const double tau_minus )
+{
+ if ( tau_minus_ > 1.0 && tau_minus_ != tau_minus )
+ {
+ throw IllegalConnection();
+ }
+ else
+ {
+ tau_minus_ = tau_minus;
+ tau_minus_inv_ = 1. / tau_minus_;
+ }
+
+ // Mark all entries in the deque, which we will not read in future as read by
+ // this input, so that we safely increment the incoming number of
+ // connections afterwards without leaving spikes in the history.
+ // For details see bug #218. MH 08-04-22
+ for ( std::deque< histentry_step >::iterator runner = history_.begin();
+ runner != history_.end() and ( t_first_read - runner->t_ >= 0 );
+ ++runner )
+ {
+ ( runner->access_counter_ )++;
+ }
+
+ n_incoming_++;
+
+ max_delay_ = std::max( delay, max_delay_ );
+}
+
+double
+nest::ArchivingNodeHom::get_K_value( long t, size_t& dt_steps )
+{
+ dt_steps = 0;
+ // case when the neuron has not yet spiked
+ if ( history_.empty() )
+ {
+ trace_ = 0.;
+ return trace_;
+ }
+
+ int i = history_.size() - 1;
+
+ while ( i >= 0 )
+ {
+ const auto hist = history_[ i ];
+ if ( t > static_cast< long >( hist.t_ ) )
+ {
+ trace_ = hist.Kminus_;
+ dt_steps = t - hist.t_;
+
+ return trace_;
+ }
+ --i;
+ }
+
+ // this case occurs when the trace was requested at a time precisely at or
+ // before the first spike in the history
+ trace_ = 0.;
+ return trace_;
+}
+
+void
+nest::ArchivingNodeHom::get_K_values( double t,
+ double& K_value,
+ double& nearest_neighbor_K_value,
+ double& K_triplet_value )
+{
+ // case when the neuron has not yet spiked
+ if ( history_.empty() )
+ {
+ K_triplet_value = Kminus_triplet_;
+ nearest_neighbor_K_value = Kminus_;
+ K_value = Kminus_;
+ return;
+ }
+
+ // search for the latest post spike in the history buffer that came strictly
+ // before `t`
+ int i = history_.size() - 1;
+ while ( i >= 0 )
+ {
+ if ( t - history_[ i ].t_ > kernel().connection_manager.get_stdp_eps() )
+ {
+ K_triplet_value =
+ ( history_[ i ].Kminus_triplet_ * std::exp( ( history_[ i ].t_ - t ) * tau_minus_triplet_inv_ ) );
+ K_value = ( history_[ i ].Kminus_ * std::exp( ( history_[ i ].t_ - t ) * tau_minus_inv_ ) );
+ nearest_neighbor_K_value = std::exp( ( history_[ i ].t_ - t ) * tau_minus_inv_ );
+ return;
+ }
+ --i;
+ }
+
+ // this case occurs when the trace was requested at a time precisely at or
+ // before the first spike in the history
+ K_triplet_value = 0.0;
+ nearest_neighbor_K_value = 0.0;
+ K_value = 0.0;
+}
+
+void
+nest::ArchivingNodeHom::get_history( long t1,
+ long t2,
+ std::deque< histentry_step >::iterator* start,
+ std::deque< histentry_step >::iterator* finish )
+{
+ *finish = history_.end();
+ if ( history_.empty() )
+ {
+ *start = *finish;
+ return;
+ }
+ std::deque< histentry_step >::reverse_iterator runner = history_.rbegin();
+ while ( runner != history_.rend() and static_cast< long >( runner->t_ ) > t2 )
+ {
+ ++runner;
+ }
+
+ *finish = runner.base();
+ while ( runner != history_.rend() and static_cast< long >( runner->t_ ) > t1 )
+ {
+ runner->access_counter_++;
+ ++runner;
+ }
+ *start = runner.base();
+}
+
+void
+nest::ArchivingNodeHom::set_spiketime( Time const& t_sp, double offset )
+{
+ StructuralPlasticityNode::set_spiketime( t_sp, offset );
+
+ const size_t t_sp_steps = t_sp.get_steps();
+
+ if ( n_incoming_ )
+ {
+ // prune all spikes from history which are no longer needed
+ // only remove a spike if:
+ // - its access counter indicates it has been read out by all connected
+ // STDP synapses, and
+ // - there is another, later spike, that is strictly more than
+ // (min_global_delay + max_local_delay + eps) away from the new spike (at t_sp_ms)
+ while ( history_.size() > 1 )
+ {
+ const size_t next_t_sp = history_[ 1 ].t_;
+ if ( history_.front().access_counter_ >= n_incoming_
+ and t_sp_steps - next_t_sp > max_delay_ + kernel().connection_manager.get_min_delay() )
+ {
+ history_.pop_front();
+ }
+ else
+ {
+ break;
+ }
+ }
+ // update spiking history
+ Kminus_ = Kminus_ * std::exp( Time( Time::step( last_spike_ - t_sp_steps ) ).get_ms() * tau_minus_inv_ ) + 1.0;
+ Kminus_triplet_ =
+ Kminus_triplet_ * std::exp( Time( Time::step( last_spike_ - t_sp_steps ) ).get_ms() * tau_minus_triplet_inv_ )
+ + 1.0;
+ last_spike_ = t_sp_steps;
+ history_.push_back( histentry_step( last_spike_, Kminus_, Kminus_triplet_, 0 ) );
+ }
+ else
+ {
+ last_spike_ = t_sp_steps;
+ }
+}
+
+void
+nest::ArchivingNodeHom::get_status( DictionaryDatum& d ) const
+{
+ def< double >( d, names::t_spike, get_spiketime_ms() );
+ def< double >( d, names::tau_minus, tau_minus_ );
+ def< double >( d, names::tau_minus_triplet, tau_minus_triplet_ );
+ def< double >( d, names::post_trace, trace_ );
+#ifdef DEBUG_ARCHIVER
+ def< int >( d, names::archiver_length, history_.size() );
+#endif
+
+ // add status dict items from the parent class
+ StructuralPlasticityNode::get_status( d );
+}
+
+void
+nest::ArchivingNodeHom::set_status( const DictionaryDatum& d )
+{
+ // We need to preserve values in case invalid values are set
+ double new_tau_minus_triplet = tau_minus_triplet_;
+
+ updateValue< double >( d, names::tau_minus_triplet, new_tau_minus_triplet );
+
+ if ( new_tau_minus_triplet <= 0.0 )
+ {
+ throw BadProperty( "All time constants must be strictly positive." );
+ }
+
+ StructuralPlasticityNode::set_status( d );
+
+ // do the actual update
+ tau_minus_triplet_ = new_tau_minus_triplet;
+ tau_minus_triplet_inv_ = 1. / tau_minus_triplet_;
+
+ // check, if to clear spike history and K_minus
+ bool clear = false;
+ updateValue< bool >( d, names::clear, clear );
+ if ( clear )
+ {
+ clear_history();
+ }
+}
+
+void
+nest::ArchivingNodeHom::clear_history()
+{
+ last_spike_ = -1.0;
+ Kminus_ = 0.0;
+ Kminus_triplet_ = 0.0;
+ history_.clear();
+}
+
+
+} // of namespace nest
diff --git a/nestkernel/archiving_node_hom.h b/nestkernel/archiving_node_hom.h
new file mode 100644
index 0000000000..4fae247529
--- /dev/null
+++ b/nestkernel/archiving_node_hom.h
@@ -0,0 +1,156 @@
+/*
+ * archiving_node_hom.h
+ *
+ * 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 .
+ *
+ */
+
+#ifndef ARCHIVING_NODE_HOM_H
+#define ARCHIVING_NODE_HOM_H
+
+// C++ includes:
+#include
+#include
+
+// Includes from nestkernel:
+#include "histentry.h"
+#include "nest_time.h"
+#include "nest_types.h"
+#include "node.h"
+#include "structural_plasticity_node.h"
+
+// Includes from sli:
+#include "dictdatum.h"
+
+#define DEBUG_ARCHIVER 1
+
+namespace nest
+{
+
+constexpr double TAU_MINUS_PLACEHOLDER = 1.0; // indicate that tau minus has not been set yet
+
+/**
+ * A node which archives spike history for the purposes of spike-timing
+ * dependent plasticity (STDP), exclusive to and optimized for homogenous synapses.
+ */
+class ArchivingNodeHom : public StructuralPlasticityNode
+{
+public:
+ ArchivingNodeHom();
+
+ ArchivingNodeHom( const ArchivingNodeHom& );
+
+ /**
+ * Return the Kminus (synaptic trace) value at time t (given in ms).
+ *
+ * When the trace is requested at the exact same time that the neuron emits a spike,
+ * the trace value as it was just before the spike is returned.
+ */
+ double get_K_value( long t, size_t& dt_steps ) override;
+
+ /**
+ * Write the different STDP K values at time t (in ms) to the provided locations.
+ *
+ * @param Kminus the eligibility trace for STDP
+ * @param nearest_neighbour_Kminus eligibility trace for nearest-neighbour STDP, like Kminus,
+ but increased to 1, rather than by 1, when a spike occurs
+ * @param Kminus_triplet eligibility trace for triplet STDP
+ *
+ * @throws UnexpectedEvent
+ */
+ void get_K_values( double t, double& Kminus, double& nearest_neighbor_Kminus, double& Kminus_triplet ) override;
+
+ /**
+ * Return the triplet Kminus value for the associated iterator.
+ */
+ double get_K_triplet_value( const std::deque< histentry_step >::iterator& iter );
+
+ /**
+ * Return the spike times (in steps) of spikes which occurred in the range [t1,t2].
+ */
+ void get_history( long t1,
+ long t2,
+ std::deque< histentry_step >::iterator* start,
+ std::deque< histentry_step >::iterator* finish ) override;
+
+ /**
+ * Register a new incoming STDP connection.
+ *
+ * t_first_read: The newly registered synapse will read the history entries
+ * with t > t_first_read.
+ */
+ void register_stdp_connection( size_t t_first_read, size_t delay, const double tau_minus ) override;
+
+ void get_status( DictionaryDatum& d ) const override;
+ void set_status( const DictionaryDatum& d ) override;
+
+protected:
+ /**
+ * Record spike history
+ */
+ void set_spiketime( Time const& t_sp, double offset = 0.0 );
+
+ /**
+ * Return most recent spike time in ms
+ */
+ inline double get_spiketime_ms() const;
+
+ /**
+ * Clear spike history
+ */
+ void clear_history();
+
+ /**
+ * Number of incoming connections from STDP connectors.
+ *
+ * This variable is needed to determine if every incoming connection has
+ * read the spikehistory for a given point in time
+ */
+ size_t n_incoming_;
+
+private:
+ // sum exp(-(t-ti)/tau_minus)
+ double Kminus_;
+
+ // sum exp(-(t-ti)/tau_minus_triplet)
+ double Kminus_triplet_;
+
+ double tau_minus_;
+ double tau_minus_inv_;
+
+ // time constant for triplet low pass filtering of "post" spike train
+ double tau_minus_triplet_;
+ double tau_minus_triplet_inv_;
+
+ size_t max_delay_;
+ double trace_;
+
+ long last_spike_;
+
+ // spiking history needed by stdp synapses
+ std::deque< histentry_step > history_;
+};
+
+inline double
+ArchivingNodeHom::get_spiketime_ms() const
+{
+ return Time( Time::step( last_spike_ ) ).get_ms();
+}
+
+} // of namespace
+#endif
diff --git a/nestkernel/histentry.cpp b/nestkernel/histentry.cpp
index b56bb1ce87..f4a848c1b7 100644
--- a/nestkernel/histentry.cpp
+++ b/nestkernel/histentry.cpp
@@ -30,6 +30,14 @@ nest::histentry::histentry( double t, double Kminus, double Kminus_triplet, size
{
}
+nest::histentry_step::histentry_step( size_t t, double Kminus, double Kminus_triplet, size_t access_counter )
+ : t_( t )
+ , Kminus_( Kminus )
+ , Kminus_triplet_( Kminus_triplet )
+ , access_counter_( access_counter )
+{
+}
+
nest::histentry_extended::histentry_extended( double t, double dw, size_t access_counter )
: t_( t )
, dw_( dw )
diff --git a/nestkernel/histentry.h b/nestkernel/histentry.h
index 0d5b1392bf..11c8b95a36 100644
--- a/nestkernel/histentry.h
+++ b/nestkernel/histentry.h
@@ -43,6 +43,18 @@ class histentry
size_t access_counter_; //!< access counter to enable removal of the entry, once all neurons read it
};
+class histentry_step
+{
+public:
+ histentry_step( size_t t, double Kminus, double Kminus_triplet, size_t access_counter );
+
+ size_t t_; //!< point in time when spike occurred (in steps)
+ double Kminus_; //!< value of Kminus at that time
+ double Kminus_triplet_; //!< value of triplet STDP Kminus at that time
+ size_t access_counter_; //!< access counter to enable removal of the entry, once all neurons read it
+};
+
+
// entry in the history of plasticity rules which consider additional factors
class histentry_extended
{
diff --git a/nestkernel/node.cpp b/nestkernel/node.cpp
index eb3c0f0497..3479bd1ef3 100644
--- a/nestkernel/node.cpp
+++ b/nestkernel/node.cpp
@@ -212,6 +212,12 @@ Node::send_test_event( Node&, size_t, synindex, bool )
* Default implementation of register_stdp_connection() just
* throws IllegalConnection
*/
+void
+Node::register_stdp_connection( size_t, size_t, const double )
+{
+ throw IllegalConnection( "The target node does not support homogenous STDP synapses." );
+}
+
void
Node::register_stdp_connection( double, double )
{
@@ -470,11 +476,16 @@ Node::get_LTD_value( double )
}
double
-Node::get_K_value( double )
+Node::get_K_value( long, size_t& )
{
throw UnexpectedEvent();
}
+double
+Node::get_K_value( double )
+{
+ throw UnexpectedEvent();
+}
void
Node::get_K_values( double, double&, double&, double& )
@@ -482,6 +493,12 @@ Node::get_K_values( double, double&, double&, double& )
throw UnexpectedEvent();
}
+void
+nest::Node::get_history( long, long, std::deque< histentry_step >::iterator*, std::deque< histentry_step >::iterator* )
+{
+ throw UnexpectedEvent();
+}
+
void
nest::Node::get_history( double, double, std::deque< histentry >::iterator*, std::deque< histentry >::iterator* )
{
diff --git a/nestkernel/node.h b/nestkernel/node.h
index 9fde5624fb..2d8b1467ae 100644
--- a/nestkernel/node.h
+++ b/nestkernel/node.h
@@ -479,6 +479,8 @@ class Node
* @throws IllegalConnection
*
*/
+ virtual void register_stdp_connection( size_t, size_t, const double tau_minus );
+
virtual void register_stdp_connection( double, double );
/**
@@ -761,7 +763,9 @@ class Node
* return the Kminus value at t (in ms).
* @throws UnexpectedEvent
*/
- virtual double get_K_value( double t );
+ virtual double get_K_value( long t, size_t& dt_steps );
+
+ virtual double get_K_value( double );
virtual double get_LTD_value( double t );
@@ -777,6 +781,11 @@ class Node
* return the spike history for (t1,t2].
* @throws UnexpectedEvent
*/
+ virtual void get_history( long t1,
+ long t2,
+ std::deque< histentry_step >::iterator* start,
+ std::deque< histentry_step >::iterator* finish );
+
virtual void get_history( double t1,
double t2,
std::deque< histentry >::iterator* start,
diff --git a/pynest/examples/hpc_benchmark.py b/pynest/examples/hpc_benchmark.py
index c59f16edbc..d36f16b648 100644
--- a/pynest/examples/hpc_benchmark.py
+++ b/pynest/examples/hpc_benchmark.py
@@ -160,7 +160,7 @@ def convert_synapse_weight(tau_m, tau_syn, C_m):
"NE": int(9000 * params["scale"]), # number of excitatory neurons
"NI": int(2250 * params["scale"]), # number of inhibitory neurons
"Nrec": 1000, # number of neurons to record spikes from
- "model_params": { # Set variables for iaf_psc_alpha
+ "model_params": { # Set variables for iaf_psc_alpha_hom
"E_L": 0.0, # Resting membrane potential(mV)
"C_m": 250.0, # Capacity of the membrane(pF)
"tau_m": 10.0, # Membrane time constant(ms)
@@ -171,7 +171,6 @@ def convert_synapse_weight(tau_m, tau_syn, C_m):
"tau_syn_ex": tau_syn,
# time const. postsynaptic inhibitory currents(ms)
"tau_syn_in": tau_syn,
- "tau_minus": 30.0, # time constant for STDP(depression)
# V can be randomly initialized see below
"V_m": 5.7, # mean value of membrane potential
},
@@ -193,6 +192,7 @@ def convert_synapse_weight(tau_m, tau_syn, C_m):
"lambda": 0.1, # STDP step size
"mu": 0.4, # STDP weight dependence exponent(potentiation)
"tau_plus": 15.0, # time constant for potentiation
+ "tau_minus": 30.0, # time constant for STDP(depression)
},
"eta": 1.685, # scaling of external stimulus
"filestem": params["path_name"],
@@ -224,10 +224,10 @@ def build_network(logger):
nest.overwrite_files = True
nest.message(M_INFO, "build_network", "Creating excitatory population.")
- E_neurons = nest.Create("iaf_psc_alpha", NE, params=model_params)
+ E_neurons = nest.Create("iaf_psc_alpha_hom", NE, params=model_params)
nest.message(M_INFO, "build_network", "Creating inhibitory population.")
- I_neurons = nest.Create("iaf_psc_alpha", NI, params=model_params)
+ I_neurons = nest.Create("iaf_psc_alpha_hom", NI, params=model_params)
if brunel_params["randomize_Vm"]:
nest.message(M_INFO, "build_network", "Randomizing membrane potentials.")
diff --git a/testsuite/pytests/connect_test_base.py b/testsuite/pytests/connect_test_base.py
index 12124080ba..e371ceb618 100644
--- a/testsuite/pytests/connect_test_base.py
+++ b/testsuite/pytests/connect_test_base.py
@@ -180,12 +180,6 @@ def testStdpFacetshwSynapseHom(self):
syn_params = {"synapse_model": "stdp_facetshw_synapse_hom"}
check_synapse(params, values, syn_params, self)
- def testStdpPlSynapseHom(self):
- params = ["Kplus"]
- values = [0.173]
- syn_params = {"synapse_model": "stdp_pl_synapse_hom"}
- check_synapse(params, values, syn_params, self)
-
def testStdpSynapseHom(self):
params = ["Kplus"]
values = [0.382]
@@ -229,7 +223,6 @@ def testRPortAllSynapses(self):
"static_synapse_hom_w",
"stdp_dopamine_synapse",
"stdp_facetshw_synapse_hom",
- "stdp_pl_synapse_hom",
"stdp_synapse_hom",
"stdp_synapse",
"tsodyks2_synapse",
@@ -260,7 +253,6 @@ def testWeightAllSynapses(self):
"quantal_stp_synapse",
"stdp_dopamine_synapse",
"stdp_facetshw_synapse_hom",
- "stdp_pl_synapse_hom",
"stdp_synapse_hom",
"stdp_synapse",
"tsodyks2_synapse",
@@ -284,7 +276,6 @@ def testDelayAllSynapses(self):
"static_synapse_hom_w",
"stdp_dopamine_synapse",
"stdp_facetshw_synapse_hom",
- "stdp_pl_synapse_hom",
"stdp_synapse_hom",
"stdp_synapse",
"tsodyks2_synapse",
diff --git a/testsuite/pytests/sli2py_connect/test_common_properties_setting.py b/testsuite/pytests/sli2py_connect/test_common_properties_setting.py
index 2d1f2ee687..b1bb7b8159 100644
--- a/testsuite/pytests/sli2py_connect/test_common_properties_setting.py
+++ b/testsuite/pytests/sli2py_connect/test_common_properties_setting.py
@@ -58,7 +58,7 @@ def set_default_delay_resolution():
"neuron": "iaf_psc_alpha",
},
"stdp_facetshw_synapse_hom": {"parameter": "tau_plus", "value": 10, "setup": None, "neuron": "iaf_psc_alpha"},
- "stdp_pl_synapse_hom": {"parameter": "tau_plus", "value": 10, "setup": None, "neuron": "iaf_psc_alpha"},
+ "stdp_pl_synapse_hom": {"parameter": "tau_plus", "value": 10, "setup": None, "neuron": "iaf_psc_alpha_hom"},
"stdp_synapse_hom": {"parameter": "tau_plus", "value": 10, "setup": None, "neuron": "iaf_psc_alpha"},
"static_synapse_hom_w": {"parameter": "weight", "value": 10, "setup": None, "neuron": "iaf_psc_alpha"},
"tsodyks_synapse_hom": {"parameter": "tau_psc", "value": 10, "setup": None, "neuron": "iaf_psc_alpha"},
diff --git a/testsuite/pytests/sli2py_regressions/test_issue_77.py b/testsuite/pytests/sli2py_regressions/test_issue_77.py
index 651a85a77a..2c01870a56 100644
--- a/testsuite/pytests/sli2py_regressions/test_issue_77.py
+++ b/testsuite/pytests/sli2py_regressions/test_issue_77.py
@@ -61,6 +61,7 @@
"eprop_readout_bsshslm_2020", # does not send spikes
"eprop_iaf_bsshslm_2020", # does not support stdp synapses
"eprop_iaf_adapt_bsshslm_2020", # does not support stdp synapses
+ "iaf_psc_alpha_hom", # size_t archivingnode/histentry only
]
# The following models require connections to rport 1 or other specific parameters:
diff --git a/testsuite/pytests/sli2py_synapses/test_common_props_setting.py b/testsuite/pytests/sli2py_synapses/test_common_props_setting.py
index 60fcbef9f7..52059cbe03 100644
--- a/testsuite/pytests/sli2py_synapses/test_common_props_setting.py
+++ b/testsuite/pytests/sli2py_synapses/test_common_props_setting.py
@@ -45,9 +45,7 @@ def prepare():
nest.SetDefaults("stdp_dopamine_synapse", {"volume_transmitter": vt})
-@pytest.mark.parametrize(
- "synapse", ["stdp_synapse_hom", "stdp_pl_synapse_hom", "stdp_facetshw_synapse_hom", "stdp_dopamine_synapse"]
-)
+@pytest.mark.parametrize("synapse", ["stdp_synapse_hom", "stdp_facetshw_synapse_hom", "stdp_dopamine_synapse"])
class TestSettingCommonProps:
def test_setting_common_props_on_original(self, synapse):
expected_values = {"tau_plus": 5.0, "weight": 2.0}
diff --git a/testsuite/pytests/test_changing_tic_base.py b/testsuite/pytests/test_changing_tic_base.py
index 6caa62b353..0096ae05f5 100644
--- a/testsuite/pytests/test_changing_tic_base.py
+++ b/testsuite/pytests/test_changing_tic_base.py
@@ -35,7 +35,9 @@ class TestChangingTicBase(unittest.TestCase):
# change in tic-base. However, because the value in the defaults
# is converted to ms, it will differ from the reference value.
# The model is therefore ignored.
- ignored_models = ["iaf_psc_exp_ps_lossless"]
+ # iaf_psc_alpha_hom currently has a different archiving node base class
+ # to support size_t histentries and is therefore not compatible.
+ ignored_models = ["iaf_psc_exp_ps_lossless", "iaf_psc_alpha_hom"]
def setUp(self):
nest.ResetKernel()
diff --git a/testsuite/pytests/test_labeled_synapses.py b/testsuite/pytests/test_labeled_synapses.py
index 77e4e2bfd0..656867480a 100644
--- a/testsuite/pytests/test_labeled_synapses.py
+++ b/testsuite/pytests/test_labeled_synapses.py
@@ -30,6 +30,10 @@
HAVE_GSL = nest.ll_api.sli_func("statusdict/have_gsl ::")
+# apply exceptions for synapses that are not compatible with the default histentry/archivingnode
+synapse_list = [s for s in nest.synapse_models if s.endswith("_lbl") and not s.startswith("stdp_pl_synapse_hom")]
+
+
@nest.ll_api.check_stack
@unittest.skipIf(not HAVE_GSL, "GSL is not available")
class LabeledSynapsesTestCase(unittest.TestCase):
@@ -99,7 +103,7 @@ def default_network(self, syn_model):
def test_SetLabelToSynapseOnConnect(self):
"""Set a label to a labeled synapse on connect."""
- for syn in [s for s in nest.synapse_models if s.endswith("_lbl")]:
+ for syn in synapse_list:
a, r_type = self.default_network(syn)
# see if symmetric connections are required
@@ -118,7 +122,7 @@ def test_SetLabelToSynapseOnConnect(self):
def test_SetLabelToSynapseSetStatus(self):
"""Set a label to a labeled synapse on SetStatus."""
- for syn in [s for s in nest.synapse_models if s.endswith("_lbl")]:
+ for syn in synapse_list:
a, r_type = self.default_network(syn)
# see if symmetric connections are required
@@ -139,7 +143,7 @@ def test_SetLabelToSynapseSetStatus(self):
def test_SetLabelToSynapseSetDefaults(self):
"""Set a label to a labeled synapse on SetDefaults."""
- for syn in [s for s in nest.synapse_models if s.endswith("_lbl")]:
+ for syn in synapse_list:
a, r_type = self.default_network(syn)
# see if symmetric connections are required
@@ -156,7 +160,7 @@ def test_SetLabelToSynapseSetDefaults(self):
def test_GetLabeledSynapses(self):
"""Get labeled synapses with GetConnections."""
- for syn in [s for s in nest.synapse_models if s.endswith("_lbl")]:
+ for syn in synapse_list:
a, r_type = self.default_network(syn)
# see if symmetric connections are required
@@ -183,6 +187,8 @@ def test_SetLabelToNotLabeledSynapse(self):
"""Try set a label to an 'un-label-able' synapse."""
for syn in [s for s in nest.synapse_models if not s.endswith("_lbl")]:
+ if syn.startswith("stdp_pl_synapse_hom"):
+ continue
if syn == "sic_connection":
# Skip sic_connection since it requires different pre- and post-synaptic neuron models
continue
diff --git a/testsuite/pytests/test_sp/test_disconnect.py b/testsuite/pytests/test_sp/test_disconnect.py
index 40c22e19a8..5c881ed55c 100644
--- a/testsuite/pytests/test_sp/test_disconnect.py
+++ b/testsuite/pytests/test_sp/test_disconnect.py
@@ -63,6 +63,9 @@ def setUp(self):
"urbanczik_synapse_lbl",
"urbanczik_synapse_hpc",
"sic_connection",
+ "stdp_pl_synapse_hom",
+ "stdp_pl_synapse_hom_hpc",
+ "stdp_pl_synapse_hom_lbl",
]
def test_synapse_deletion_one_to_one_no_sp(self):
diff --git a/testsuite/pytests/test_sp/test_disconnect_multiple.py b/testsuite/pytests/test_sp/test_disconnect_multiple.py
index e0529f1645..5f8810c370 100644
--- a/testsuite/pytests/test_sp/test_disconnect_multiple.py
+++ b/testsuite/pytests/test_sp/test_disconnect_multiple.py
@@ -55,6 +55,9 @@ def setUp(self):
"eprop_learning_signal_connection_bsshslm_2020_lbl",
"eprop_learning_signal_connection_bsshslm_2020_hpc",
"sic_connection",
+ "stdp_pl_synapse_hom",
+ "stdp_pl_synapse_hom_hpc",
+ "stdp_pl_synapse_hom_lbl",
]
def test_multiple_synapse_deletion_all_to_all(self):
diff --git a/testsuite/pytests/test_stdp_pl_synapse_hom.py b/testsuite/pytests/test_stdp_pl_synapse_hom.py
index 4243ccc004..56f6ac1203 100644
--- a/testsuite/pytests/test_stdp_pl_synapse_hom.py
+++ b/testsuite/pytests/test_stdp_pl_synapse_hom.py
@@ -18,12 +18,12 @@
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see .
-
+import unittest
from math import exp
import nest
import numpy as np
-import pytest
+from nest.lib.hl_api_exceptions import NESTErrors
DEBUG_PLOTS = False
@@ -38,7 +38,7 @@
@nest.ll_api.check_stack
-class TestSTDPPlSynapse:
+class TestSTDPPlSynapse(unittest.TestCase):
"""
Compare the STDP power-law synaptic plasticity model against a self-contained Python reference.
@@ -47,7 +47,6 @@ class TestSTDPPlSynapse:
"""
def init_params(self):
- self.resolution = 0.1 # [ms]
self.simulation_duration = 1e3 # [ms]
self.synapse_model = "stdp_pl_synapse_hom"
self.presynaptic_firing_rate = 100.0 # [ms^-1]
@@ -60,9 +59,15 @@ def init_params(self):
"alpha": 1.0,
"mu": 0.4,
"tau_plus": self.tau_pre,
+ "tau_minus": self.tau_post,
+ }
+ self.synapse_parameters = {
+ "synapse_model": self.synapse_model,
+ "receptor_type": 0,
+ "weight": self.init_weight,
}
- self.synapse_parameters = {"synapse_model": self.synapse_model, "receptor_type": 0, "weight": self.init_weight}
- self.neuron_parameters = {"tau_minus": self.tau_post}
+ self.neuron_parameters = {}
+ self.nest_neuron_model = "iaf_psc_alpha_hom"
# While the random sequences, fairly long, would supposedly reveal small differences in the weight change
# between NEST and ours, some low-probability events (say, coinciding spikes) can well not have occurred. To
@@ -75,6 +80,12 @@ def init_params(self):
self.hardcoded_post_times = np.array(
[2, 3, 4, 8, 9, 10, 12, 12.2, 14.1, 15.4, 22, 23, 24, 28, 29, 30, 32, 33.2, 35.1, 36.4], dtype=float
)
+
+ # scale hardcoded times to resolution; this is a temporary fix to test stdp_pl_synapse_hom's calibrate()
+ # and should be refactored along with the test setup, see #3250
+ self.hardcoded_pre_times = np.round(self.hardcoded_pre_times / self.resolution) * self.resolution
+ self.hardcoded_post_times = np.round(self.hardcoded_post_times / self.resolution) * self.resolution
+
self.hardcoded_trains_length = 5.0 + max(np.amax(self.hardcoded_pre_times), np.amax(self.hardcoded_post_times))
def do_nest_simulation_and_compare_to_reproduced_weight(self, fname_snip):
@@ -334,7 +345,6 @@ def plot_weight_evolution(
):
if not DEBUG_PLOTS: # make pylint happy if no matplotlib
return
-
# pylint: disable=E0601
fig, ax = plt.subplots(nrows=3)
@@ -369,16 +379,36 @@ def plot_weight_evolution(
plt.close(fig)
def test_stdp_synapse(self):
- self.init_params()
- for self.dendritic_delay in (1.0, 0.5, self.resolution):
- self.synapse_parameters["delay"] = self.dendritic_delay
- for self.min_delay in (1.0, 0.4, self.resolution):
- for self.max_delay in (3.0, 1.0):
- self.min_delay = min(self.min_delay, self.max_delay)
- self.max_delay = max(self.min_delay, self.max_delay)
- for self.nest_neuron_model in ("iaf_psc_exp", "iaf_cond_exp"):
+ for self.resolution in (0.1, 0.125):
+ self.init_params()
+ for self.dendritic_delay in (1.0, 0.5, self.resolution):
+ self.synapse_parameters["delay"] = self.dendritic_delay
+ for self.min_delay in (1.0, 0.4, self.resolution):
+ for self.max_delay in (3.0, 1.0):
+ self.min_delay = min(self.min_delay, self.max_delay)
+ self.max_delay = max(self.min_delay, self.max_delay)
for self.neuron_parameters["t_ref"] in (self.resolution, 0.5, 1.0, 1.1, 2.5):
fname_snip = "_[nest_neuron_mdl=" + self.nest_neuron_model + "]"
fname_snip += "_[dend_delay=" + str(self.dendritic_delay) + "]"
fname_snip += "_[t_ref=" + str(self.neuron_parameters["t_ref"]) + "]"
self.do_nest_simulation_and_compare_to_reproduced_weight(fname_snip=fname_snip)
+
+ def test_hom_node_illegal_connections(self):
+ node = nest.Create("iaf_psc_alpha")
+ node_hom = nest.Create("iaf_psc_alpha_hom")
+
+ nest.Connect(node, node)
+ nest.Connect(node_hom, node_hom)
+
+ with self.assertRaisesRegex(nest.NESTError, "IllegalConnection"):
+ nest.Connect(node_hom, node, syn_spec={"synapse_model": "stdp_pl_synapse_hom"})
+
+ with self.assertRaisesRegex(nest.NESTError, "IllegalConnection"):
+ nest.Connect(node_hom, node_hom, syn_spec={"synapse_model": "stdp_synapse_hom"})
+
+ def test_hom_node_illegal_update(self):
+ # assert tau_minus is read-only
+ node_hom = nest.Create("iaf_psc_alpha_hom")
+ with self.assertRaises(NESTErrors.DictError):
+ nest.SetStatus(node_hom, {"tau_minus": 16.0})
+ nest.GetStatus(node_hom, "tau_minus")
diff --git a/testsuite/regressiontests/ticket-686-positive-parameters.sli b/testsuite/regressiontests/ticket-686-positive-parameters.sli
index a6005cc2aa..5d067600e2 100644
--- a/testsuite/regressiontests/ticket-686-positive-parameters.sli
+++ b/testsuite/regressiontests/ticket-686-positive-parameters.sli
@@ -53,6 +53,7 @@ M_ERROR setverbosity
/correlomatrix_detector
/correlospinmatrix_detector
/iaf_tum_2000 % tau_fac == 0 is permitted
+ /iaf_psc_alpha_hom % tau_minus is set on common syn properties, read-only on neuron
/siegert_neuron ]
def
diff --git a/testsuite/regressiontests/ticket-689.sli b/testsuite/regressiontests/ticket-689.sli
index 53f45f7e63..1d3d703634 100644
--- a/testsuite/regressiontests/ticket-689.sli
+++ b/testsuite/regressiontests/ticket-689.sli
@@ -42,8 +42,8 @@ M_ERROR setverbosity
/total_num_virtual_procs 1
>> SetKernelStatus
-/iaf_psc_alpha Create /n1 Set
-/iaf_psc_alpha Create /n2 Set
+/iaf_psc_alpha_hom Create /n1 Set
+/iaf_psc_alpha_hom Create /n2 Set
n1 n2 /all_to_all /stdp_pl_synapse_hom_hpc Connect
{