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 {