From 6149e4e29138a2accfebcf0b233087e7e618304f Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 13 Sep 2023 10:59:13 +0200
Subject: [PATCH 001/184] add nest names
---
nestkernel/nest_names.h | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index cc2aa1d516..f071f1d8f7 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -121,6 +121,7 @@ extern const Name clear;
extern const Name comp_idx;
extern const Name comparator;
extern const Name compartments;
+extern const Name conc_Mg2;
extern const Name configbit_0;
extern const Name configbit_1;
extern const Name connection_count;
@@ -202,11 +203,13 @@ extern const Name filenames;
extern const Name frequency;
extern const Name frozen;
+extern const Name GABA;
extern const Name GABA_A;
extern const Name GABA_B;
extern const Name g;
extern const Name g_AMPA;
extern const Name g_C;
+extern const Name g_GABA;
extern const Name g_GABA_A;
extern const Name g_GABA_B;
extern const Name g_K;
@@ -324,6 +327,7 @@ extern const Name music_channel;
extern const Name N;
extern const Name NMDA;
+extern const Name NMDA_sum;
extern const Name N_channels;
extern const Name N_NaP;
extern const Name N_T;
@@ -484,6 +488,8 @@ extern const Name targets;
extern const Name tau;
extern const Name tau_1;
extern const Name tau_2;
+extern const Name tau_AMPA;
+extern const Name tau_GABA;
extern const Name tau_Ca;
extern const Name tau_D_KNa;
extern const Name tau_Delta;
From 297dcabd58aa8cb43904cc6bb7bd63f3bbc6477a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 13 Sep 2023 13:57:01 +0200
Subject: [PATCH 002/184] Files from Stine, fixed 0->nullptr and port->size_t
---
models/iaf_wang_2002.cpp | 541 +++++++++++++++++++++++++++++++++++++++
models/iaf_wang_2002.h | 534 ++++++++++++++++++++++++++++++++++++++
2 files changed, 1075 insertions(+)
create mode 100644 models/iaf_wang_2002.cpp
create mode 100644 models/iaf_wang_2002.h
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
new file mode 100644
index 0000000000..bbc17271df
--- /dev/null
+++ b/models/iaf_wang_2002.cpp
@@ -0,0 +1,541 @@
+/*
+ * iaf_wang_2002.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_wang_2002.h"
+
+#ifdef HAVE_GSL
+
+// Includes from libnestutil:
+#include "dictdatum.h"
+#include "dict_util.h"
+#include "numerics.h"
+
+// Includes from nestkernel:
+#include "exceptions.h"
+#include "kernel_manager.h"
+#include "universal_data_logger_impl.h"
+
+// Includes from sli:
+#include "dict.h"
+#include "dictutils.h"
+#include "doubledatum.h"
+#include "integerdatum.h"
+#include "lockptrdatum.h"
+
+/* ---------------------------------------------------------------------------
+ * Recordables map
+ * --------------------------------------------------------------------------- */
+nest::RecordablesMap< nest::iaf_wang_2002 > nest::iaf_wang_2002::recordablesMap_;
+
+namespace nest
+{
+/*
+ * Override the create() method with one call to RecordablesMap::insert_()
+ * for each quantity to be recorded.
+ */
+template <>
+void
+RecordablesMap< iaf_wang_2002 >::create()
+{
+ // add state variables to recordables map
+ insert_( names::V_m, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::V_m > );
+ insert_( names::g_AMPA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::G_AMPA > );
+ insert_( names::g_GABA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::G_GABA > );
+ insert_( names::NMDA_sum, &iaf_wang_2002::get_NMDA_sum_ );
+}
+}
+/* ---------------------------------------------------------------------------
+ * Default constructors defining default parameters and state
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002::Parameters_::Parameters_()
+ : E_L( -70.0 ) // mV
+ , E_ex( 0.0 ) // mV
+ , E_in( -70.0 ) // mV
+ , V_th( -55.0 ) // mV
+ , V_reset( -60.0 ) // mV
+ , C_m( 500.0 ) // pF
+ , g_L( 25.0 ) // nS
+ , t_ref( 2.0 ) // ms
+ , tau_AMPA( 2.0 ) // ms
+ , tau_GABA( 5.0 ) // ms
+ , tau_rise_NMDA( 2.0 ) // ms
+ , tau_decay_NMDA( 100 ) // ms
+ , alpha( 0.5 ) // 1 / ms
+ , conc_Mg2( 1 ) // mM
+ , gsl_error_tol( 1e-3 )
+{
+}
+
+nest::iaf_wang_2002::State_::State_( const Parameters_& p )
+ : r_( 0 )
+ , sum_S_post_( 0 )
+{
+ ode_state_[ V_m ] = p.E_L; // initialize to reversal potential
+ ode_state_[ G_AMPA ] = 0.0;
+ ode_state_[ G_GABA ] = 0.0;
+ ode_state_[ S_pre ] = 0.0;
+ ode_state_[ X_pre ] = 0.0;
+
+ dy_[ V_m ] = 0.0;
+ dy_[ G_AMPA ] = 0.0;
+ dy_[ G_GABA ] = 0.0;
+ dy_[ S_pre ] = 0.0;
+ dy_[ X_pre ] = 0.0;
+}
+
+nest::iaf_wang_2002::State_::State_( const State_& s )
+ : r_( s.r_ )
+ , sum_S_post_( s.sum_S_post_ )
+{
+ ode_state_[ V_m ] = s.ode_state_[ V_m ];
+ ode_state_[ G_AMPA ] = s.ode_state_[ G_AMPA ];
+ ode_state_[ G_GABA ] = s.ode_state_[ G_GABA ];
+ ode_state_[ S_pre ] = s.ode_state_[ S_pre ];
+ ode_state_[ X_pre ] = s.ode_state_[ X_pre ];
+
+ dy_[ V_m ] = s.dy_[ V_m ];
+ dy_[ G_AMPA ] = s.dy_[ G_AMPA ];
+ dy_[ G_GABA ] = s.dy_[ G_GABA ];
+ dy_[ S_pre ] = s.dy_[ S_pre ];
+ dy_[ X_pre ] = s.dy_[ X_pre ];
+}
+
+nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
+ : logger_( n )
+ , spikes_()
+ , NMDA_cond_()
+ , s_( nullptr )
+ , c_( nullptr )
+ , e_( nullptr )
+ , step_( Time::get_resolution().get_ms() )
+ , integration_step_( step_ )
+{
+ // Initialization of the remaining members is deferred to init_buffers_().
+}
+
+nest::iaf_wang_2002::Buffers_::Buffers_( const Buffers_&, iaf_wang_2002& n )
+ : logger_( n )
+ , spikes_()
+ , NMDA_cond_()
+ , s_( nullptr )
+ , c_( nullptr )
+ , e_( nullptr )
+ , step_( Time::get_resolution().get_ms() )
+ , integration_step_( step_ )
+{
+ // Initialization of the remaining members is deferred to init_buffers_().
+}
+
+/* ---------------------------------------------------------------------------
+ * Parameter and state extractions and manipulation functions
+ * --------------------------------------------------------------------------- */
+
+void
+nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
+{
+ def< double >( d, names::E_L, E_L );
+ def< double >( d, names::E_ex, E_ex );
+ def< double >( d, names::E_in, E_in );
+ def< double >( d, names::V_th, V_th );
+ def< double >( d, names::V_reset, V_reset );
+ def< double >( d, names::C_m, C_m );
+ def< double >( d, names::g_L, g_L );
+ def< double >( d, names::t_ref, t_ref );
+ def< double >( d, names::tau_AMPA, tau_AMPA );
+ def< double >( d, names::tau_GABA, tau_GABA );
+ def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
+ def< double >( d, names::tau_decay_NMDA, tau_decay_NMDA );
+ def< double >( d, names::alpha, alpha );
+ def< double >( d, names::conc_Mg2, conc_Mg2 );
+ def< double >( d, names::gsl_error_tol, gsl_error_tol );
+}
+
+void
+nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
+{
+ // allow setting the membrane potential
+ updateValueParam< double >( d, names::V_th, V_th, node );
+ updateValueParam< double >( d, names::V_reset, V_reset, node );
+ updateValueParam< double >( d, names::t_ref, t_ref, node );
+ updateValueParam< double >( d, names::E_L, E_L, node );
+
+ updateValueParam< double >( d, names::E_ex, E_ex, node );
+ updateValueParam< double >( d, names::E_in, E_in, node );
+
+ updateValueParam< double >( d, names::C_m, C_m, node );
+ updateValueParam< double >( d, names::g_L, g_L, node );
+
+ updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
+ updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
+ updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
+ updateValueParam< double >( d, names::tau_decay_NMDA, tau_decay_NMDA, node );
+
+ updateValueParam< double >( d, names::alpha, alpha, node );
+ updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
+
+ updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
+
+ if ( V_reset >= V_th )
+ {
+ throw BadProperty( "Reset potential must be smaller than threshold." );
+ }
+ if ( C_m <= 0 )
+ {
+ throw BadProperty( "Capacitance must be strictly positive." );
+ }
+ if ( t_ref < 0 )
+ {
+ throw BadProperty( "Refractory time cannot be negative." );
+ }
+ if ( tau_AMPA <= 0 or tau_GABA <= 0 or tau_rise_NMDA <= 0 or tau_decay_NMDA <= 0 )
+ {
+ throw BadProperty( "All time constants must be strictly positive." );
+ }
+ if ( alpha <= 0 )
+ {
+ throw BadProperty( "alpha > 0 required." );
+ }
+ if ( conc_Mg2 <= 0 )
+ {
+ throw BadProperty( "Mg2 concentration must be strictly positive." );
+ }
+ if ( gsl_error_tol <= 0.0 )
+ {
+ throw BadProperty( "The gsl_error_tol must be strictly positive." );
+ }
+}
+
+void
+nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
+{
+ def< double >( d, names::V_m, ode_state_[ V_m ] ); // Membrane potential
+ def< double >( d, names::g_AMPA, ode_state_[ G_AMPA ] );
+ def< double >( d, names::g_GABA, ode_state_[ G_GABA ] );
+
+ // total NMDA sum
+ double NMDA_sum = get_NMDA_sum();
+ def< double >( d, names::NMDA_sum, NMDA_sum );
+}
+
+void
+nest::iaf_wang_2002::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
+{
+ updateValueParam< double >( d, names::V_m, ode_state_[ V_m ], node );
+ updateValueParam< double >( d, names::g_AMPA, ode_state_[ G_AMPA ], node );
+ updateValueParam< double >( d, names::g_GABA, ode_state_[ G_GABA ], node );
+}
+
+/* ---------------------------------------------------------------------------
+ * Default constructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002::iaf_wang_2002()
+ : ArchivingNode()
+ , P_()
+ , S_( P_ )
+ , B_( *this )
+{
+ recordablesMap_.create();
+
+ calibrate();
+}
+
+/* ---------------------------------------------------------------------------
+ * Copy constructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002::iaf_wang_2002( const iaf_wang_2002& n_ )
+ : ArchivingNode( n_ )
+ , P_( n_.P_ )
+ , S_( n_.S_ )
+ , B_( n_.B_, *this )
+{
+}
+
+/* ---------------------------------------------------------------------------
+ * Destructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002::~iaf_wang_2002()
+{
+ // GSL structs may not have been allocated, so we need to protect destruction
+
+ if ( B_.s_ )
+ {
+ gsl_odeiv_step_free( B_.s_ );
+ }
+
+ if ( B_.c_ )
+ {
+ gsl_odeiv_control_free( B_.c_ );
+ }
+
+ if ( B_.e_ )
+ {
+ gsl_odeiv_evolve_free( B_.e_ );
+ }
+}
+
+/* ---------------------------------------------------------------------------
+ * Node initialization functions
+ * --------------------------------------------------------------------------- */
+
+void
+nest::iaf_wang_2002::init_state_()
+{
+}
+
+void
+nest::iaf_wang_2002::init_buffers_()
+{
+ B_.spikes_.resize( 2 );
+
+ for ( auto& sb : B_.spikes_ )
+ {
+ sb.clear(); // includes resize
+ }
+
+ B_.NMDA_cond_.clear();
+ B_.currents_.clear(); // includes resize
+
+ B_.logger_.reset(); // includes resize
+ ArchivingNode::clear_history();
+
+ if ( B_.s_ == nullptr )
+ {
+ B_.s_ = gsl_odeiv_step_alloc( gsl_odeiv_step_rkf45, State_::STATE_VEC_SIZE );
+ }
+ else
+ {
+ gsl_odeiv_step_reset( B_.s_ );
+ }
+
+ if ( B_.c_ == nullptr )
+ {
+ B_.c_ = gsl_odeiv_control_y_new( P_.gsl_error_tol, 0.0 );
+ }
+ else
+ {
+ gsl_odeiv_control_init( B_.c_, P_.gsl_error_tol, 0.0, 1.0, 0.0 );
+ }
+
+ if ( B_.e_ == nullptr )
+ {
+ B_.e_ = gsl_odeiv_evolve_alloc( State_::STATE_VEC_SIZE );
+ }
+ else
+ {
+ gsl_odeiv_evolve_reset( B_.e_ );
+ }
+
+ B_.sys_.function = iaf_wang_2002_dynamics;
+ B_.sys_.jacobian = nullptr;
+ B_.sys_.dimension = State_::STATE_VEC_SIZE;
+ B_.sys_.params = reinterpret_cast< void* >( this );
+ B_.step_ = Time::get_resolution().get_ms();
+ B_.integration_step_ = Time::get_resolution().get_ms();
+
+ B_.I_stim_ = 0.0;
+}
+
+void
+nest::iaf_wang_2002::calibrate()
+{
+ B_.logger_.init();
+
+ // internals V_
+ V_.RefractoryCounts = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
+}
+
+/* ---------------------------------------------------------------------------
+ * Update and spike handling functions
+ * --------------------------------------------------------------------------- */
+
+extern "C" inline int
+nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void* pnode )
+{
+ // a shorthand
+ typedef nest::iaf_wang_2002::State_ State_;
+
+ // get access to node so we can almost work as in a member function
+ assert( pnode );
+ const nest::iaf_wang_2002& node = *( reinterpret_cast< nest::iaf_wang_2002* >( pnode ) );
+
+ // ode_state[] here is---and must be---the state vector supplied by the integrator,
+ // not the state vector in the node, node.S_.ode_state[].
+
+ const double I_AMPA = ( ode_state[ State_::V_m ] - node.P_.E_ex ) * ode_state[ State_::G_AMPA ];
+
+ const double I_rec_GABA = ( ode_state[ State_::V_m ] - node.P_.E_in ) * ode_state[ State_::G_GABA ];
+
+ const double I_rec_NMDA = ( ode_state[ State_::V_m ] - node.P_.E_ex )
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ State_::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
+
+ const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
+
+ f[ State_::V_m ] = ( -node.P_.g_L * ( ode_state[ State_::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
+
+ f[ State_::G_AMPA ] = -ode_state[ State_::G_AMPA ] / node.P_.tau_AMPA;
+ f[ State_::G_GABA ] = -ode_state[ State_::G_GABA ] / node.P_.tau_GABA;
+
+ f[ State_::S_pre ] =
+ -ode_state[ State_::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ State_::X_pre ] * ( 1 - ode_state[ State_::S_pre ] );
+ f[ State_::X_pre ] = -ode_state[ State_::X_pre ] / node.P_.tau_rise_NMDA;
+
+ return GSL_SUCCESS;
+}
+
+void
+nest::iaf_wang_2002::update( Time const& origin, const long from, const long to )
+{
+ std::vector< double > s_vals( kernel().connection_manager.get_min_delay(), 0.0 );
+
+ for ( long lag = from; lag < to; ++lag )
+ {
+ /*double t = 0.0;
+
+ // numerical integration with adaptive step size control:
+ // ------------------------------------------------------
+ // gsl_odeiv_evolve_apply performs only a single numerical
+ // integration step, starting from t and bounded by step;
+ // the while-loop ensures integration over the whole simulation
+ // step (0, step] if more than one integration step is needed due
+ // to a small integration step size;
+ // note that (t+IntegrationStep > step) leads to integration over
+ // (t, step] and afterwards setting t to step, but it does not
+ // enforce setting IntegrationStep to step-t; this is of advantage
+ // for a consistent and efficient integration across subsequent
+ // simulation intervals
+
+ while ( t < B_.step_ )
+ {
+ const int status = gsl_odeiv_evolve_apply( B_.e_,
+ B_.c_,
+ B_.s_,
+ &B_.sys_, // system of ODE
+ &t, // from t
+ B_.step_, // to t <= step
+ &B_.integration_step_, // integration step size
+ S_.ode_state_ ); // neuronal state
+
+ if ( status != GSL_SUCCESS )
+ {
+ throw GSLSolverFailure( get_name(), status );
+ }
+ }*/
+
+ iaf_wang_2002_dynamics( 0, S_.ode_state_, S_.dy_, reinterpret_cast< void* >( this ) );
+ for ( auto i = 0; i < State_::STATE_VEC_SIZE; ++i )
+ {
+ S_.ode_state_[ i ] += B_.step_ * S_.dy_[ i ];
+ }
+
+ // add incoming spikes
+ S_.ode_state_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.ode_state_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+ S_.sum_S_post_ = B_.NMDA_cond_.get_value( lag );
+ B_.NMDA_cond_.set_value( lag, 0.0 );
+
+ // absolute refractory period
+ if ( S_.r_ )
+ {
+ // neuron is absolute refractory
+ --S_.r_;
+ S_.ode_state_[ State_::V_m ] = P_.V_reset; // clamp potential
+ }
+ else if ( S_.ode_state_[ State_::V_m ] >= P_.V_th )
+ {
+ // neuron is not absolute refractory
+ S_.r_ = V_.RefractoryCounts;
+ S_.ode_state_[ State_::V_m ] = P_.V_reset;
+
+ S_.ode_state_[ State_::X_pre ] += 1;
+
+ // log spike with ArchivingNode
+ set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
+
+ SpikeEvent se;
+ kernel().event_delivery_manager.send( *this, se, lag );
+ }
+
+ // send NMDA update
+ s_vals[ lag ] = S_.ode_state_[ State_::S_pre ];
+
+ // set new input current
+ B_.I_stim_ = B_.currents_.get_value( lag );
+
+ // voltage logging
+ B_.logger_.record_data( origin.get_steps() + lag );
+ }
+
+ DelayedRateConnectionEvent drce;
+ drce.set_coeffarray( s_vals );
+ kernel().event_delivery_manager.send_secondary( *this, drce );
+}
+
+// Do not move this function as inline to h-file. It depends on
+// universal_data_logger_impl.h being included here.
+void
+nest::iaf_wang_2002::handle( DataLoggingRequest& e )
+{
+ B_.logger_.handle( e );
+}
+
+void
+nest::iaf_wang_2002::handle( SpikeEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+ assert( e.get_rport() < NMDA );
+
+ const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
+
+ const auto rport = e.get_rport();
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+}
+
+void
+nest::iaf_wang_2002::handle( DelayedRateConnectionEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+ assert( e.get_rport() == NMDA );
+
+ const double weight = e.get_weight();
+ long delay = e.get_delay_steps();
+
+ for ( auto it = e.begin(); it != e.end(); ++delay )
+ {
+ B_.NMDA_cond_.add_value( delay, weight * e.get_coeffvalue( it ) );
+ }
+
+}
+
+void
+nest::iaf_wang_2002::handle( CurrentEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+
+ B_.currents_.add_value(
+ e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ), e.get_weight() * e.get_current() );
+}
+
+#endif // HAVE_GSL
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
new file mode 100644
index 0000000000..1965eaad48
--- /dev/null
+++ b/models/iaf_wang_2002.h
@@ -0,0 +1,534 @@
+/*
+ * iaf_wang_2002.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_WANG_2002
+#define IAF_WANG_2002
+
+// Generated includes:
+#include "config.h"
+
+#ifdef HAVE_GSL
+
+// C includes:
+#include
+#include
+#include
+
+// Includes from nestkernel:
+#include "archiving_node.h"
+#include "connection.h"
+#include "event.h"
+#include "nest_types.h"
+#include "ring_buffer.h"
+#include "universal_data_logger.h"
+
+namespace nest
+{
+/**
+ * Function computing right-hand side of ODE for GSL solver.
+ * @note Must be declared here so we can befriend it in class.
+ * @note Must have C-linkage for passing to GSL. Internally, it is
+ * a first-class C++ function, but cannot be a member function
+ * because of the C-linkage.
+ * @note No point in declaring it inline, since it is called
+ * through a function pointer.
+ * @param void* Pointer to model neuron instance.
+ */
+extern "C" inline int iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode );
+
+
+/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
+
+Short description
++++++++++++++++++
+
+Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
+
+Description
++++++++++++
+
+This model implements a version of the neuron model described in [1]_.
+
+It contains AMPA, GABA and NMDA synapses, where the number of NMDA ports are dependent
+on the number of presynaptic connections.
+
+The AMPA and GABA synapses are given as alpha functions, while the NMDA synapse is modeled
+with a non-linear function described by
+
+.. math::
+ \frac{ dg_j^{NMDA}(t) }{ dt } = - \frac{ g_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
+ \frac{ dx_j(t) }{ dt } =- \frac{ x_j(t) }{ \tau_{NMDA,rise} } + \sum_k \delta(t - t_j^k).
+
+The synaptic current of NMDA is given by
+
+.. math::
+ I_{NMDA}(t) = \frac{ V(t) - E_{ex} }{ 1 + [Mg^{2+}]exp(-0.062V(t))/3.57 }\sum_{j=1}w_jg_j^{NMDA},
+
+where `w_j` is the weight of connection with presynaptic neuron `j`.
+
+
+Parameters
+++++++++++
+
+The following parameters can be set in the status dictionary.
+
+
+=============== ======= ===========================================================
+ E_L mV Resting potential
+ E_ex mV Excitatory reversal potential
+ E_in mV Inhibitory reversal potential
+ V_th mV Threshold potential
+ V_reset mV Reset potential
+ C_m pF Membrane capacitance
+ g_L nS Leak conductance
+ t_ref ms Refractory period
+ tau_AMPA ms Synaptic time constant for AMPA synapse
+ tau_GABA ms Synaptic time constant for GABA synapse
+ tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
+ tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
+ alpha 1/ms Scaling factor for NMDA synapse
+ conc_Mg2 mM Extracellular magnesium concentration
+ gsl_error_tol - GSL error tolerance
+=============== ======= ===========================================================
+
+
+Recordables
++++++++++++
+
+The following values can be recorded.
+
+=========== ===========================================================
+ V_m Membrane potential
+ g_AMPA AMPA gate
+ g_GABA GABA gate
+ NMDA_sum sum of NMDA over all presynaptic neurons j
+=========== ===========================================================
+
+.. note::
+ It is possible to set values for `V_m`, `g_AMPA` and `g_GABA` when creating the model, while the
+ different `g_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
+
+.. note::
+ The variable `g_AMPA` and `g_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
+ in [1]_. `g_{recAMPA, extAMPA, GABA, NMBA}` from [1]_ is built into the weights in this NEST model, so setting the
+ variables is thus done by changing the weights.
+
+Sends
++++++
+
+SpikeEvent
+
+Receives
+++++++++
+
+SpikeEvent, CurrentEvent, DataLoggingRequest
+
+References
+++++++++++
+
+.. [1] Wang, X. J. (2002). Probabilistic decision making by slow reverberation in
+ cortical circuits. Neuron, 36(5), 955-968.
+ DOI: https://doi.org/10.1016/S0896-6273(02)01092-9
+
+See also
+++++++++
+
+iaf_cond_alpha, ht_neuron
+
+EndUserDocs */
+
+
+/**
+ * Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
+ */
+class iaf_wang_2002 : public ArchivingNode
+{
+public:
+ /**
+ * The constructor is only used to create the model prototype in the model manager.
+ */
+ iaf_wang_2002();
+
+ /**
+ * The copy constructor is used to create model copies and instances of the model.
+ * @note The copy constructor needs to initialize the parameters and part of the state.
+ * Initialization of rest of state, buffers and internal variables is deferred to
+ * @c init_state_(), @c init_buffers_() and @c calibrate().
+ */
+ iaf_wang_2002( const iaf_wang_2002& );
+
+ ~iaf_wang_2002() override;
+
+ /**
+ * Import all overloaded virtual functions that we
+ * override in this class. For background information,
+ * see http://www.gotw.ca/gotw/005.htm.
+ */
+
+ using Node::handles_test_event;
+ using Node::sends_secondary_event;
+ using Node::handle;
+
+ //! Used to validate that we can send SpikeEvent to desired target:port.
+ size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
+
+ void sends_secondary_event( DelayedRateConnectionEvent& ) override;
+
+ /* -------------------------------------------------------------------------
+ * Functions handling incoming events.
+ * We tell NEST that we can handle incoming events of various types by
+ * defining handle() for the given event.
+ * ------------------------------------------------------------------------- */
+
+ void handle( SpikeEvent& ) override; //!< accept spikes
+ void handle( DelayedRateConnectionEvent& ) override; //!< accept spikes
+ void handle( CurrentEvent& e ) override; //!< accept current
+ void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
+
+ size_t handles_test_event( SpikeEvent&, size_t ) override;
+ size_t handles_test_event( DelayedRateConnectionEvent&,size_t ) override;
+ size_t handles_test_event( CurrentEvent&, size_t ) override;
+ size_t handles_test_event( DataLoggingRequest&, size_t ) override;
+
+ /* -------------------------------------------------------------------------
+ * Functions for getting/setting parameters and state values.
+ * ------------------------------------------------------------------------- */
+
+ void get_status( DictionaryDatum& ) const override;
+ void set_status( const DictionaryDatum& ) override;
+
+private:
+ /**
+ * Synapse types to connect to
+ */
+ enum SynapseTypes
+ {
+ INF_SPIKE_RECEPTOR = 0,
+ AMPA,
+ GABA,
+ NMDA,
+ SUP_SPIKE_RECEPTOR
+ };
+
+ void init_state_() override;
+ void init_buffers_() override;
+ void calibrate();
+ 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_wang_2002 >;
+ friend class UniversalDataLogger< iaf_wang_2002 >;
+
+ // Parameters class --------------------------------------------------------------
+
+ /**
+ * Parameters of the neuron.
+ *
+ * These are the parameters that can be set by the user through @c `node.set()`.
+ * They are initialized from the model prototype when the node is created.
+ * Parameters do not change during calls to @c update().
+ */
+ struct Parameters_
+ {
+ double E_L; //!< Resting Potential in mV
+ double E_ex; //!< Excitatory reversal Potential in mV
+ double E_in; //!< Inhibitory reversal Potential in mV
+ double V_th; //!< Threshold Potential in mV
+ double V_reset; //!< Reset Potential in mV
+ double C_m; //!< Membrane Capacitance in pF
+ double g_L; //!< Leak Conductance in nS
+ double t_ref; //!< Refractory period in ms
+ double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
+ double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
+ double tau_rise_NMDA; //!< Synaptic Rise Time Constant NMDA Synapse in ms
+ double tau_decay_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
+ double alpha; //!< Scaling factor for NMDA synapse in 1/ms
+ double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
+
+ double gsl_error_tol; //!< GSL Error Tolerance
+
+ //! Initialize parameters to their default values.
+ Parameters_();
+
+ void get( DictionaryDatum& ) const; //!< Store current values in dictionary
+ void set( const DictionaryDatum&, Node* node ); //!< Set values from dictionary
+ };
+
+
+ // State variables class --------------------------------------------
+
+ /**
+ * State variables of the model.
+ *
+ * State variables consist of the state vector for the subthreshold
+ * dynamics and the refractory count. The state vector must be a
+ * C-style array to be compatible with GSL ODE solvers.
+ *
+ * @note Copy constructor is required because of the C-style array.
+ */
+ struct State_
+ {
+ //! Symbolic indices to the elements of the state vector y
+ enum StateVecElems
+ {
+ V_m = 0,
+ G_AMPA,
+ G_GABA,
+ S_pre,
+ X_pre,
+ STATE_VEC_SIZE
+ };
+
+ double ode_state_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
+ double dy_[ STATE_VEC_SIZE ];
+ int r_; //!< number of refractory steps remaining
+ double sum_S_post_;
+
+ State_( const Parameters_& ); //!< Default initialization
+ State_( const State_& );
+
+ void get( DictionaryDatum& ) const;
+ void set( const DictionaryDatum&, const Parameters_&, Node* );
+
+ //! Get the sum of NMDA over all presynaptic neurons
+ double
+ get_NMDA_sum() const
+ {
+ /*double NMDA_sum = 0.0;
+ for ( size_t i = G_NMDA_base; i < state_vec_size; i += 2 )
+ {
+ NMDA_sum += ode_state_[ i + 1 ];
+ }
+ return NMDA_sum;*/
+ return -1;
+ }
+ };
+
+ // Variables class -------------------------------------------------------
+
+ /**
+ * Internal variables of the model.
+ * Variables are re-initialized upon each call to Simulate.
+ */
+ struct Variables_
+ {
+ //! refractory time in steps
+ long RefractoryCounts;
+ };
+
+ // Buffers class --------------------------------------------------------
+
+ /**
+ * Buffers of the model.
+ * Buffers are on par with state variables in terms of persistence,
+ * i.e., initialized only upon first Simulate call after ResetKernel,
+ * but its implementation details hidden from the user.
+ */
+ struct Buffers_
+ {
+ Buffers_( iaf_wang_2002& );
+ Buffers_( const Buffers_&, iaf_wang_2002& );
+
+ //! Logger for all analog data
+ UniversalDataLogger< iaf_wang_2002 > logger_;
+
+ // -----------------------------------------------------------------------
+ // Buffers and sums of incoming spikes and currents per timestep
+ // -----------------------------------------------------------------------
+ std::vector< RingBuffer > spikes_;
+ RingBuffer NMDA_cond_;
+ RingBuffer currents_;
+
+ // -----------------------------------------------------------------------
+ // GSL ODE solver data structures
+ // -----------------------------------------------------------------------
+
+ gsl_odeiv_step* s_; //!< stepping function
+ gsl_odeiv_control* c_; //!< adaptive stepsize control function
+ gsl_odeiv_evolve* e_; //!< evolution function
+ gsl_odeiv_system sys_; //!< struct describing system
+
+ /**
+ * integration_step_ should be reset with the neuron on ResetNetwork,
+ * but remain unchanged during calibration. Since it is initialized with
+ * step_, and the resolution cannot change after nodes have been created,
+ * it is safe to place both here.
+ */
+ double step_; //!< step size in ms
+ double integration_step_; //!< current integration time step, updated by GSL
+
+ /**
+ * Input current injected by CurrentEvent.
+ * This variable is used to transport the current applied into the
+ * _dynamics function computing the derivative of the state vector.
+ * It must be a part of Buffers_, since it is initialized once before
+ * the first simulation, but not modified before later Simulate calls.
+ */
+ double I_stim_;
+ };
+
+ // Access functions for UniversalDataLogger -------------------------------
+
+ //! Read out state vector elements, used by UniversalDataLogger
+ template < State_::StateVecElems elem >
+ double
+ get_ode_state_elem_() const
+ {
+ return S_.ode_state_[ elem ];
+ }
+
+ //! Get the sum of NMDA from state, used by UniversalDataLogger
+ double
+ get_NMDA_sum_() const
+ {
+ return S_.get_NMDA_sum();
+ }
+
+ // Data members -----------------------------------------------------------
+
+ // keep the order of these lines, seems to give best performance
+ Parameters_ P_; //!< Free parameters.
+ State_ S_; //!< Dynamic state.
+ Variables_ V_; //!< Internal Variables
+ Buffers_ B_; //!< Buffers.
+
+ //! Mapping of recordables names to access functions
+ static RecordablesMap< iaf_wang_2002 > recordablesMap_;
+ friend int iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode );
+
+}; /* neuron iaf_wang_2002 */
+
+inline size_t
+iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bool )
+{
+ if ( receptor_type != NMDA )
+ {
+ SpikeEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
+ }
+ else
+ {
+ DelayedRateConnectionEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
+ }
+}
+
+inline size_t
+iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
+{
+ if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) or receptor_type == NMDA )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ return 0;
+ }
+ else
+ {
+ return receptor_type;
+ }
+}
+
+inline size_t
+iaf_wang_2002::handles_test_event( DelayedRateConnectionEvent&, size_t receptor_type )
+{
+ if ( receptor_type != NMDA )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ return 0;
+ }
+ else
+ {
+ return receptor_type;
+ }
+}
+
+inline size_t
+iaf_wang_2002::handles_test_event( CurrentEvent&, size_t receptor_type )
+{
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+ return 0;
+}
+
+inline size_t
+iaf_wang_2002::handles_test_event( DataLoggingRequest& dlr, size_t receptor_type )
+{
+ /*
+ * You should usually not change the code in this function.
+ * It confirms to the connection management system that we are able
+ * to handle @c DataLoggingRequest on port 0.
+ * The function also tells the built-in UniversalDataLogger that this node
+ * is recorded from and that it thus needs to collect data during simulation.
+ */
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+
+ return B_.logger_.connect_logging_device( dlr, recordablesMap_ );
+}
+
+inline void
+iaf_wang_2002::get_status( DictionaryDatum& d ) const
+{
+ P_.get( d );
+ S_.get( d );
+ ArchivingNode::get_status( d );
+
+ DictionaryDatum receptor_type = new Dictionary();
+
+ ( *receptor_type )[ names::AMPA ] = AMPA;
+ ( *receptor_type )[ names::GABA ] = GABA;
+ ( *receptor_type )[ names::NMDA ] = NMDA;
+
+ ( *d )[ names::receptor_types ] = receptor_type;
+
+ ( *d )[ names::recordables ] = recordablesMap_.get_list();
+}
+
+inline void
+iaf_wang_2002::set_status( const DictionaryDatum& d )
+{
+ Parameters_ ptmp = P_; // temporary copy in case of errors
+ ptmp.set( d, this ); // throws if BadProperty
+ State_ stmp = S_; // temporary copy in case of errors
+ stmp.set( d, ptmp, 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.
+ */
+ ArchivingNode::set_status( d );
+
+ // if we get here, temporaries contain consistent set of properties
+ P_ = ptmp;
+ S_ = stmp;
+};
+} // namespace
+
+#endif // HAVE_GSL
+#endif // IAF_WANG_2002
From 4ccecdf7ccb4fe41b963b5f7acf6f68008701cae Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 14 Sep 2023 09:16:20 +0200
Subject: [PATCH 003/184] change variable name
---
models/iaf_wang_2002.cpp | 22 +++++++++++-----------
modelsets/full | 1 +
2 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index bbc17271df..b02232344b 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -375,7 +375,7 @@ extern "C" inline int
nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void* pnode )
{
// a shorthand
- typedef nest::iaf_wang_2002::State_ State_;
+ typedef nest::iaf_wang_2002::State_ S;
// get access to node so we can almost work as in a member function
assert( pnode );
@@ -384,23 +384,23 @@ nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void
// ode_state[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.ode_state[].
- const double I_AMPA = ( ode_state[ State_::V_m ] - node.P_.E_ex ) * ode_state[ State_::G_AMPA ];
+ const double I_AMPA = ( ode_state[ S::V_m ] - node.P_.E_ex ) * ode_state[ S::G_AMPA ];
- const double I_rec_GABA = ( ode_state[ State_::V_m ] - node.P_.E_in ) * ode_state[ State_::G_GABA ];
+ const double I_rec_GABA = ( ode_state[ S::V_m ] - node.P_.E_in ) * ode_state[ S::G_GABA ];
- const double I_rec_NMDA = ( ode_state[ State_::V_m ] - node.P_.E_ex )
- / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ State_::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
+ const double I_rec_NMDA = ( ode_state[ S::V_m ] - node.P_.E_ex )
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
- f[ State_::V_m ] = ( -node.P_.g_L * ( ode_state[ State_::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
+ f[ S::V_m ] = ( -node.P_.g_L * ( ode_state[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
- f[ State_::G_AMPA ] = -ode_state[ State_::G_AMPA ] / node.P_.tau_AMPA;
- f[ State_::G_GABA ] = -ode_state[ State_::G_GABA ] / node.P_.tau_GABA;
+ f[ S::G_AMPA ] = -ode_state[ S::G_AMPA ] / node.P_.tau_AMPA;
+ f[ S::G_GABA ] = -ode_state[ S::G_GABA ] / node.P_.tau_GABA;
- f[ State_::S_pre ] =
- -ode_state[ State_::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ State_::X_pre ] * ( 1 - ode_state[ State_::S_pre ] );
- f[ State_::X_pre ] = -ode_state[ State_::X_pre ] / node.P_.tau_rise_NMDA;
+ f[ S::S_pre ] =
+ -ode_state[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ S::X_pre ] * ( 1 - ode_state[ S::S_pre ] );
+ f[ S::X_pre ] = -ode_state[ S::X_pre ] / node.P_.tau_rise_NMDA;
return GSL_SUCCESS;
}
diff --git a/modelsets/full b/modelsets/full
index 4f0e835f41..9c8b617b59 100644
--- a/modelsets/full
+++ b/modelsets/full
@@ -56,6 +56,7 @@ iaf_psc_exp_htum
iaf_psc_exp_multisynapse
iaf_psc_exp_ps
iaf_psc_exp_ps_lossless
+iaf_wang_2002
izhikevich
jonke_synapse
lin_rate
From 8d3d96c610d77bc967867be469966899d303e91c Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 14 Sep 2023 09:31:09 +0200
Subject: [PATCH 004/184] add nest names
---
nestkernel/nest_names.cpp | 3 +++
1 file changed, 3 insertions(+)
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 7559f5279b..e3fb1354a9 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -177,6 +177,7 @@ const Name filenames( "filenames" );
const Name frequency( "frequency" );
const Name frozen( "frozen" );
+const Name GABA( "GABA" );
const Name GABA_A( "GABA_A" );
const Name GABA_B( "GABA_B" );
const Name g( "g" );
@@ -459,9 +460,11 @@ const Name targets( "targets" );
const Name tau( "tau" );
const Name tau_1( "tau_1" );
const Name tau_2( "tau_2" );
+const Name tau_AMPA( "tau_AMPA" );
const Name tau_Ca( "tau_Ca" );
const Name tau_D_KNa( "tau_D_KNa" );
const Name tau_Delta( "tau_Delta" );
+const Name tau_GABA( "tau_GABA" );
const Name tau_Mg_fast_NMDA( "tau_Mg_fast_NMDA" );
const Name tau_Mg_slow_NMDA( "tau_Mg_slow_NMDA" );
const Name tau_P( "tau_P" );
From b17358cdc31c8ed481d2148ceaad06c38f4321b0 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 14 Sep 2023 11:10:47 +0200
Subject: [PATCH 005/184] compiles!
---
models/iaf_wang_2002.cpp | 122 +++++++++++++++++++++-----------------
models/iaf_wang_2002.h | 90 +++++++++++++++-------------
nestkernel/nest_names.cpp | 3 +
3 files changed, 119 insertions(+), 96 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index b02232344b..b3711b8a74 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -63,6 +63,43 @@ RecordablesMap< iaf_wang_2002 >::create()
insert_( names::NMDA_sum, &iaf_wang_2002::get_NMDA_sum_ );
}
}
+
+extern "C" inline int
+nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void* pnode )
+{
+ // a shorthand
+ typedef nest::iaf_wang_2002::State_ S;
+
+ // get access to node so we can almost work as in a member function
+ assert( pnode );
+ const nest::iaf_wang_2002& node = *( reinterpret_cast< nest::iaf_wang_2002* >( pnode ) );
+
+ // ode_state[] here is---and must be---the state vector supplied by the integrator,
+ // not the state vector in the node, node.S_.ode_state[].
+
+ const double I_AMPA = ( ode_state[ S::V_m ] - node.P_.E_ex ) * ode_state[ S::G_AMPA ];
+
+ const double I_rec_GABA = ( ode_state[ S::V_m ] - node.P_.E_in ) * ode_state[ S::G_GABA ];
+
+ const double I_rec_NMDA = ( ode_state[ S::V_m ] - node.P_.E_ex )
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
+
+ const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
+
+ f[ S::V_m ] = ( -node.P_.g_L * ( ode_state[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
+
+ f[ S::G_AMPA ] = -ode_state[ S::G_AMPA ] / node.P_.tau_AMPA;
+ f[ S::G_GABA ] = -ode_state[ S::G_GABA ] / node.P_.tau_GABA;
+
+ f[ S::S_pre ] =
+ -ode_state[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ S::X_pre ] * ( 1 - ode_state[ S::S_pre ] );
+ f[ S::X_pre ] = -ode_state[ S::X_pre ] / node.P_.tau_rise_NMDA;
+
+ return GSL_SUCCESS;
+}
+
+
+
/* ---------------------------------------------------------------------------
* Default constructors defining default parameters and state
* --------------------------------------------------------------------------- */
@@ -358,53 +395,30 @@ nest::iaf_wang_2002::init_buffers_()
B_.I_stim_ = 0.0;
}
+void
+nest::iaf_wang_2002::pre_run_hook()
+{
+ // ensures initialization in case mm connected after Simulate
+ B_.logger_.init();
+
+ V_.RefractoryCounts_ = Time( Time::ms( P_.t_ref ) ).get_steps();
+ // since t_ref_ >= 0, this can only fail in error
+ assert( V_.RefractoryCounts_ >= 0 );
+}
+
void
nest::iaf_wang_2002::calibrate()
{
B_.logger_.init();
// internals V_
- V_.RefractoryCounts = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
+ V_.RefractoryCounts_ = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
}
/* ---------------------------------------------------------------------------
* Update and spike handling functions
* --------------------------------------------------------------------------- */
-extern "C" inline int
-nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void* pnode )
-{
- // a shorthand
- typedef nest::iaf_wang_2002::State_ S;
-
- // get access to node so we can almost work as in a member function
- assert( pnode );
- const nest::iaf_wang_2002& node = *( reinterpret_cast< nest::iaf_wang_2002* >( pnode ) );
-
- // ode_state[] here is---and must be---the state vector supplied by the integrator,
- // not the state vector in the node, node.S_.ode_state[].
-
- const double I_AMPA = ( ode_state[ S::V_m ] - node.P_.E_ex ) * ode_state[ S::G_AMPA ];
-
- const double I_rec_GABA = ( ode_state[ S::V_m ] - node.P_.E_in ) * ode_state[ S::G_GABA ];
-
- const double I_rec_NMDA = ( ode_state[ S::V_m ] - node.P_.E_ex )
- / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
-
- const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
-
- f[ S::V_m ] = ( -node.P_.g_L * ( ode_state[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
-
- f[ S::G_AMPA ] = -ode_state[ S::G_AMPA ] / node.P_.tau_AMPA;
- f[ S::G_GABA ] = -ode_state[ S::G_GABA ] / node.P_.tau_GABA;
-
- f[ S::S_pre ] =
- -ode_state[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ S::X_pre ] * ( 1 - ode_state[ S::S_pre ] );
- f[ S::X_pre ] = -ode_state[ S::X_pre ] / node.P_.tau_rise_NMDA;
-
- return GSL_SUCCESS;
-}
-
void
nest::iaf_wang_2002::update( Time const& origin, const long from, const long to )
{
@@ -466,7 +480,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
else if ( S_.ode_state_[ State_::V_m ] >= P_.V_th )
{
// neuron is not absolute refractory
- S_.r_ = V_.RefractoryCounts;
+ S_.r_ = V_.RefractoryCounts_;
S_.ode_state_[ State_::V_m ] = P_.V_reset;
S_.ode_state_[ State_::X_pre ] += 1;
@@ -488,9 +502,9 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
B_.logger_.record_data( origin.get_steps() + lag );
}
- DelayedRateConnectionEvent drce;
- drce.set_coeffarray( s_vals );
- kernel().event_delivery_manager.send_secondary( *this, drce );
+// DelayedRateConnectionEvent drce;
+// drce.set_coeffarray( s_vals );
+// kernel().event_delivery_manager.send_secondary( *this, drce );
}
// Do not move this function as inline to h-file. It depends on
@@ -513,21 +527,21 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
-void
-nest::iaf_wang_2002::handle( DelayedRateConnectionEvent& e )
-{
- assert( e.get_delay_steps() > 0 );
- assert( e.get_rport() == NMDA );
-
- const double weight = e.get_weight();
- long delay = e.get_delay_steps();
-
- for ( auto it = e.begin(); it != e.end(); ++delay )
- {
- B_.NMDA_cond_.add_value( delay, weight * e.get_coeffvalue( it ) );
- }
-
-}
+// void
+// nest::iaf_wang_2002::handle( DelayedRateConnectionEvent& e )
+// {
+// assert( e.get_delay_steps() > 0 );
+// assert( e.get_rport() == NMDA );
+//
+// const double weight = e.get_weight();
+// long delay = e.get_delay_steps();
+//
+// for ( auto it = e.begin(); it != e.end(); ++delay )
+// {
+// B_.NMDA_cond_.add_value( delay, weight * e.get_coeffvalue( it ) );
+// }
+//
+// }
void
nest::iaf_wang_2002::handle( CurrentEvent& e )
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 1965eaad48..e57e5aefab 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef IAF_WANG_2002
-#define IAF_WANG_2002
+#ifndef IAF_WANG_2002_H
+#define IAF_WANG_2002_H
// Generated includes:
#include "config.h"
@@ -53,7 +53,7 @@ namespace nest
* through a function pointer.
* @param void* Pointer to model neuron instance.
*/
-extern "C" inline int iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode );
+extern "C" inline int iaf_wang_2002_dynamics( double, const double*, double*, void* );
/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
@@ -191,7 +191,7 @@ class iaf_wang_2002 : public ArchivingNode
//! Used to validate that we can send SpikeEvent to desired target:port.
size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
- void sends_secondary_event( DelayedRateConnectionEvent& ) override;
+// void sends_secondary_event( DelayedRateConnectionEvent& ) override;
/* -------------------------------------------------------------------------
* Functions handling incoming events.
@@ -200,12 +200,12 @@ class iaf_wang_2002 : public ArchivingNode
* ------------------------------------------------------------------------- */
void handle( SpikeEvent& ) override; //!< accept spikes
- void handle( DelayedRateConnectionEvent& ) override; //!< accept spikes
+// void handle( DelayedRateConnectionEvent& ) override; //!< accept spikes
void handle( CurrentEvent& e ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
size_t handles_test_event( SpikeEvent&, size_t ) override;
- size_t handles_test_event( DelayedRateConnectionEvent&,size_t ) override;
+// size_t handles_test_event( DelayedRateConnectionEvent&,size_t ) override;
size_t handles_test_event( CurrentEvent&, size_t ) override;
size_t handles_test_event( DataLoggingRequest&, size_t ) override;
@@ -230,6 +230,7 @@ class iaf_wang_2002 : public ArchivingNode
};
void init_state_() override;
+ void pre_run_hook() override;
void init_buffers_() override;
void calibrate();
void update( Time const&, const long, const long ) override;
@@ -274,6 +275,7 @@ class iaf_wang_2002 : public ArchivingNode
};
+public:
// State variables class --------------------------------------------
/**
@@ -323,19 +325,9 @@ class iaf_wang_2002 : public ArchivingNode
}
};
- // Variables class -------------------------------------------------------
-
- /**
- * Internal variables of the model.
- * Variables are re-initialized upon each call to Simulate.
- */
- struct Variables_
- {
- //! refractory time in steps
- long RefractoryCounts;
- };
- // Buffers class --------------------------------------------------------
+private:
+ // Buffers class --------------------------------------------------------
/**
* Buffers of the model.
@@ -386,6 +378,20 @@ class iaf_wang_2002 : public ArchivingNode
double I_stim_;
};
+// Variables class -------------------------------------------------------
+
+ /**
+ * Internal variables of the model.
+ * Variables are re-initialized upon each call to Simulate.
+ */
+ struct Variables_
+ {
+ //! refractory time in steps
+ long RefractoryCounts_;
+ };
+
+
+
// Access functions for UniversalDataLogger -------------------------------
//! Read out state vector elements, used by UniversalDataLogger
@@ -413,24 +419,24 @@ class iaf_wang_2002 : public ArchivingNode
//! Mapping of recordables names to access functions
static RecordablesMap< iaf_wang_2002 > recordablesMap_;
- friend int iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode );
+ friend int iaf_wang_2002_dynamics( double, const double*, double*, void* );
}; /* neuron iaf_wang_2002 */
inline size_t
iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bool )
{
- if ( receptor_type != NMDA )
+// if ( receptor_type != NMDA )
+// {
+ SpikeEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
+// }
+// else
{
- SpikeEvent e;
- e.set_sender( *this );
- return target.handles_test_event( e, receptor_type );
- }
- else
- {
- DelayedRateConnectionEvent e;
- e.set_sender( *this );
- return target.handles_test_event( e, receptor_type );
+// DelayedRateConnectionEvent e;
+// e.set_sender( *this );
+// return target.handles_test_event( e, receptor_type );
}
}
@@ -448,19 +454,19 @@ iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
}
}
-inline size_t
-iaf_wang_2002::handles_test_event( DelayedRateConnectionEvent&, size_t receptor_type )
-{
- if ( receptor_type != NMDA )
- {
- throw UnknownReceptorType( receptor_type, get_name() );
- return 0;
- }
- else
- {
- return receptor_type;
- }
-}
+// inline size_t
+// iaf_wang_2002::handles_test_event( DelayedRateConnectionEvent&, size_t receptor_type )
+// {
+// if ( receptor_type != NMDA )
+// {
+// throw UnknownReceptorType( receptor_type, get_name() );
+// return 0;
+// }
+// else
+// {
+// return receptor_type;
+// }
+// }
inline size_t
iaf_wang_2002::handles_test_event( CurrentEvent&, size_t receptor_type )
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index e3fb1354a9..18405a3f07 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -105,6 +105,7 @@ const Name count_covariance( "count_covariance" );
const Name count_histogram( "count_histogram" );
const Name covariance( "covariance" );
const Name compartments( "compartments" );
+const Name conc_Mg2( "conc_Mg2" );
const Name comp_idx( "comp_idx" );
const Name Delta_T( "Delta_T" );
@@ -182,6 +183,7 @@ const Name GABA_A( "GABA_A" );
const Name GABA_B( "GABA_B" );
const Name g( "g" );
const Name g_AMPA( "g_AMPA" );
+const Name g_GABA( "g_GABA" );
const Name g_GABA_A( "g_GABA_A" );
const Name g_GABA_B( "g_GABA_B" );
const Name g_K( "g_K" );
@@ -300,6 +302,7 @@ const Name music_channel( "music_channel" );
const Name N( "N" );
const Name NMDA( "NMDA" );
+const Name NMDA_sum( "NMDA_sum" );
const Name N_channels( "N_channels" );
const Name N_NaP( "N_NaP" );
const Name N_T( "N_T" );
From 41a81364bd5ff4c9bf6690253a594601c61baf50 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Sat, 23 Sep 2023 16:14:22 +0200
Subject: [PATCH 006/184] cleaning up code, wip
---
models/iaf_wang_2002.cpp | 69 +++++++++++++----------------------
models/iaf_wang_2002.h | 79 +++++++++++++++++-----------------------
2 files changed, 60 insertions(+), 88 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index b3711b8a74..666d2e5782 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -127,34 +127,22 @@ nest::iaf_wang_2002::State_::State_( const Parameters_& p )
: r_( 0 )
, sum_S_post_( 0 )
{
- ode_state_[ V_m ] = p.E_L; // initialize to reversal potential
- ode_state_[ G_AMPA ] = 0.0;
- ode_state_[ G_GABA ] = 0.0;
- ode_state_[ S_pre ] = 0.0;
- ode_state_[ X_pre ] = 0.0;
-
- dy_[ V_m ] = 0.0;
- dy_[ G_AMPA ] = 0.0;
- dy_[ G_GABA ] = 0.0;
- dy_[ S_pre ] = 0.0;
- dy_[ X_pre ] = 0.0;
+ y_[ V_m ] = p.E_L; // initialize to reversal potential
+ y_[ G_AMPA ] = 0.0;
+ y_[ G_GABA ] = 0.0;
+ y_[ S_pre ] = 0.0;
+ y_[ X_pre ] = 0.0;
}
nest::iaf_wang_2002::State_::State_( const State_& s )
: r_( s.r_ )
, sum_S_post_( s.sum_S_post_ )
{
- ode_state_[ V_m ] = s.ode_state_[ V_m ];
- ode_state_[ G_AMPA ] = s.ode_state_[ G_AMPA ];
- ode_state_[ G_GABA ] = s.ode_state_[ G_GABA ];
- ode_state_[ S_pre ] = s.ode_state_[ S_pre ];
- ode_state_[ X_pre ] = s.ode_state_[ X_pre ];
-
- dy_[ V_m ] = s.dy_[ V_m ];
- dy_[ G_AMPA ] = s.dy_[ G_AMPA ];
- dy_[ G_GABA ] = s.dy_[ G_GABA ];
- dy_[ S_pre ] = s.dy_[ S_pre ];
- dy_[ X_pre ] = s.dy_[ X_pre ];
+ y_[ V_m ] = s.y_[ V_m ];
+ y_[ G_AMPA ] = s.y_[ G_AMPA ];
+ y_[ G_GABA ] = s.y_[ G_GABA ];
+ y_[ S_pre ] = s.y_[ S_pre ];
+ y_[ X_pre ] = s.y_[ X_pre ];
}
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
@@ -265,9 +253,9 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
void
nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
{
- def< double >( d, names::V_m, ode_state_[ V_m ] ); // Membrane potential
- def< double >( d, names::g_AMPA, ode_state_[ G_AMPA ] );
- def< double >( d, names::g_GABA, ode_state_[ G_GABA ] );
+ def< double >( d, names::V_m, y_[ V_m ] ); // Membrane potential
+ def< double >( d, names::g_AMPA, y_[ G_AMPA ] );
+ def< double >( d, names::g_GABA, y_[ G_GABA ] );
// total NMDA sum
double NMDA_sum = get_NMDA_sum();
@@ -277,9 +265,9 @@ nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
void
nest::iaf_wang_2002::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
{
- updateValueParam< double >( d, names::V_m, ode_state_[ V_m ], node );
- updateValueParam< double >( d, names::g_AMPA, ode_state_[ G_AMPA ], node );
- updateValueParam< double >( d, names::g_GABA, ode_state_[ G_GABA ], node );
+ updateValueParam< double >( d, names::V_m, y_[ V_m ], node );
+ updateValueParam< double >( d, names::g_AMPA, y_[ G_AMPA ], node );
+ updateValueParam< double >( d, names::g_GABA, y_[ G_GABA ], node );
}
/* ---------------------------------------------------------------------------
@@ -426,7 +414,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
for ( long lag = from; lag < to; ++lag )
{
- /*double t = 0.0;
+ double t = 0.0;
// numerical integration with adaptive step size control:
// ------------------------------------------------------
@@ -450,23 +438,18 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
&t, // from t
B_.step_, // to t <= step
&B_.integration_step_, // integration step size
- S_.ode_state_ ); // neuronal state
+ S_.y_ ); // neuronal state
if ( status != GSL_SUCCESS )
{
throw GSLSolverFailure( get_name(), status );
}
- }*/
-
- iaf_wang_2002_dynamics( 0, S_.ode_state_, S_.dy_, reinterpret_cast< void* >( this ) );
- for ( auto i = 0; i < State_::STATE_VEC_SIZE; ++i )
- {
- S_.ode_state_[ i ] += B_.step_ * S_.dy_[ i ];
}
+
// add incoming spikes
- S_.ode_state_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
- S_.ode_state_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+ S_.y_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.y_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
S_.sum_S_post_ = B_.NMDA_cond_.get_value( lag );
B_.NMDA_cond_.set_value( lag, 0.0 );
@@ -475,15 +458,15 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
{
// neuron is absolute refractory
--S_.r_;
- S_.ode_state_[ State_::V_m ] = P_.V_reset; // clamp potential
+ S_.y_[ State_::V_m ] = P_.V_reset; // clamp potential
}
- else if ( S_.ode_state_[ State_::V_m ] >= P_.V_th )
+ else if ( S_.y_[ State_::V_m ] >= P_.V_th )
{
// neuron is not absolute refractory
S_.r_ = V_.RefractoryCounts_;
- S_.ode_state_[ State_::V_m ] = P_.V_reset;
+ S_.y_[ State_::V_m ] = P_.V_reset;
- S_.ode_state_[ State_::X_pre ] += 1;
+ S_.y_[ State_::X_pre ] += 1;
// log spike with ArchivingNode
set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
@@ -493,7 +476,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
// send NMDA update
- s_vals[ lag ] = S_.ode_state_[ State_::S_pre ];
+ s_vals[ lag ] = S_.y_[ State_::S_pre ];
// set new input current
B_.I_stim_ = B_.currents_.get_value( lag );
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index e57e5aefab..5b341c8b68 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -110,7 +110,6 @@ The following parameters can be set in the status dictionary.
gsl_error_tol - GSL error tolerance
=============== ======= ===========================================================
-
Recordables
+++++++++++
@@ -163,19 +162,8 @@ EndUserDocs */
class iaf_wang_2002 : public ArchivingNode
{
public:
- /**
- * The constructor is only used to create the model prototype in the model manager.
- */
iaf_wang_2002();
-
- /**
- * The copy constructor is used to create model copies and instances of the model.
- * @note The copy constructor needs to initialize the parameters and part of the state.
- * Initialization of rest of state, buffers and internal variables is deferred to
- * @c init_state_(), @c init_buffers_() and @c calibrate().
- */
iaf_wang_2002( const iaf_wang_2002& );
-
~iaf_wang_2002() override;
/**
@@ -185,23 +173,17 @@ class iaf_wang_2002 : public ArchivingNode
*/
using Node::handles_test_event;
- using Node::sends_secondary_event;
+// using Node::sends_secondary_event;
using Node::handle;
//! Used to validate that we can send SpikeEvent to desired target:port.
- size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
+ size_t send_test_event( Node&, size_t, synindex, bool ) override;
// void sends_secondary_event( DelayedRateConnectionEvent& ) override;
- /* -------------------------------------------------------------------------
- * Functions handling incoming events.
- * We tell NEST that we can handle incoming events of various types by
- * defining handle() for the given event.
- * ------------------------------------------------------------------------- */
-
void handle( SpikeEvent& ) override; //!< accept spikes
// void handle( DelayedRateConnectionEvent& ) override; //!< accept spikes
- void handle( CurrentEvent& e ) override; //!< accept current
+ void handle( CurrentEvent& ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
size_t handles_test_event( SpikeEvent&, size_t ) override;
@@ -223,6 +205,7 @@ class iaf_wang_2002 : public ArchivingNode
enum SynapseTypes
{
INF_SPIKE_RECEPTOR = 0,
+ AMPA_EXT,
AMPA,
GABA,
NMDA,
@@ -235,19 +218,17 @@ class iaf_wang_2002 : public ArchivingNode
void calibrate();
void update( Time const&, const long, const long ) override;
+
+ // make dynamics function quasi-member
+ friend int iaf_wang_2002_dynamics( double, const double*, double*, void* );
+
// The next two classes need to be friends to access the State_ class/member
friend class RecordablesMap< iaf_wang_2002 >;
friend class UniversalDataLogger< iaf_wang_2002 >;
- // Parameters class --------------------------------------------------------------
-
- /**
- * Parameters of the neuron.
- *
- * These are the parameters that can be set by the user through @c `node.set()`.
- * They are initialized from the model prototype when the node is created.
- * Parameters do not change during calls to @c update().
- */
+private:
+ //
+ // Model parameters
struct Parameters_
{
double E_L; //!< Resting Potential in mV
@@ -300,8 +281,7 @@ class iaf_wang_2002 : public ArchivingNode
STATE_VEC_SIZE
};
- double ode_state_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
- double dy_[ STATE_VEC_SIZE ];
+ double y_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
int r_; //!< number of refractory steps remaining
double sum_S_post_;
@@ -399,7 +379,7 @@ class iaf_wang_2002 : public ArchivingNode
double
get_ode_state_elem_() const
{
- return S_.ode_state_[ elem ];
+ return S_.y_[ elem ];
}
//! Get the sum of NMDA from state, used by UniversalDataLogger
@@ -419,32 +399,41 @@ class iaf_wang_2002 : public ArchivingNode
//! Mapping of recordables names to access functions
static RecordablesMap< iaf_wang_2002 > recordablesMap_;
- friend int iaf_wang_2002_dynamics( double, const double*, double*, void* );
}; /* neuron iaf_wang_2002 */
inline size_t
iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bool )
{
-// if ( receptor_type != NMDA )
-// {
- SpikeEvent e;
- e.set_sender( *this );
- return target.handles_test_event( e, receptor_type );
-// }
-// else
+ std::cout << "RECEPTOR TYPE " << receptor_type << std::endl;
+ if ( receptor_type != NMDA )
+ {
+ SpikeEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
+ }
+ else
{
-// DelayedRateConnectionEvent e;
-// e.set_sender( *this );
-// return target.handles_test_event( e, receptor_type );
+ DelayedRateConnectionEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
}
}
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) or receptor_type == NMDA )
+ std::cout << "====================" << std::endl;
+ std::cout << "RECEPTOR TYPE " << receptor_type << std::endl;
+ std::cout << "INF RECEPTOR " << INF_SPIKE_RECEPTOR << std::endl;
+ std::cout << "SUP RECEPTOR " << SUP_SPIKE_RECEPTOR << std::endl;
+ std::cout << "====================" << std::endl;
+ if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) ) //or receptor_type == NMDA )
{
+ std::cout << "====================" << std::endl;
+ std::cout << "THROWING ERROR" << receptor_type << std::endl;
+ std::cout << "====================" << std::endl;
+
throw UnknownReceptorType( receptor_type, get_name() );
return 0;
}
From f20c745ea0fd1acb31b3d50001f97b461c368f1f Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 3 Oct 2023 11:38:01 +0200
Subject: [PATCH 007/184] some changes, not sure what
---
models/iaf_wang_2002.cpp | 60 +++++++++++++++++-----------------------
models/iaf_wang_2002.h | 11 ++------
2 files changed, 27 insertions(+), 44 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 666d2e5782..b024b31040 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -65,7 +65,7 @@ RecordablesMap< iaf_wang_2002 >::create()
}
extern "C" inline int
-nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void* pnode )
+nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode )
{
// a shorthand
typedef nest::iaf_wang_2002::State_ S;
@@ -74,26 +74,26 @@ nest::iaf_wang_2002_dynamics( double, const double ode_state[], double f[], void
assert( pnode );
const nest::iaf_wang_2002& node = *( reinterpret_cast< nest::iaf_wang_2002* >( pnode ) );
- // ode_state[] here is---and must be---the state vector supplied by the integrator,
- // not the state vector in the node, node.S_.ode_state[].
+ // y[] here is---and must be---the state vector supplied by the integrator,
+ // not the state vector in the node, node.S_.y[].
- const double I_AMPA = ( ode_state[ S::V_m ] - node.P_.E_ex ) * ode_state[ S::G_AMPA ];
+ const double I_AMPA = ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::G_AMPA ];
- const double I_rec_GABA = ( ode_state[ S::V_m ] - node.P_.E_in ) * ode_state[ S::G_GABA ];
+ const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::G_GABA ];
- const double I_rec_NMDA = ( ode_state[ S::V_m ] - node.P_.E_ex )
- / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
+ const double I_rec_NMDA = ( y[ S::V_m ] - node.P_.E_ex )
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
- f[ S::V_m ] = ( -node.P_.g_L * ( ode_state[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
+ f[ S::V_m ] = ( -node.P_.g_L * ( y[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
- f[ S::G_AMPA ] = -ode_state[ S::G_AMPA ] / node.P_.tau_AMPA;
- f[ S::G_GABA ] = -ode_state[ S::G_GABA ] / node.P_.tau_GABA;
+ f[ S::G_AMPA ] = -y[ S::G_AMPA ] / node.P_.tau_AMPA;
+ f[ S::G_GABA ] = -y[ S::G_GABA ] / node.P_.tau_GABA;
f[ S::S_pre ] =
- -ode_state[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ S::X_pre ] * ( 1 - ode_state[ S::S_pre ] );
- f[ S::X_pre ] = -ode_state[ S::X_pre ] / node.P_.tau_rise_NMDA;
+ -y[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * y[ S::X_pre ] * ( 1 - y[ S::S_pre ] );
+ f[ S::X_pre ] = -y[ S::X_pre ] / node.P_.tau_rise_NMDA;
return GSL_SUCCESS;
}
@@ -333,7 +333,7 @@ nest::iaf_wang_2002::init_state_()
void
nest::iaf_wang_2002::init_buffers_()
{
- B_.spikes_.resize( 2 );
+ B_.spikes_.resize( 3 );
for ( auto& sb : B_.spikes_ )
{
@@ -446,14 +446,23 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
}
+
- // add incoming spikes
+// std::cout << "B_.spikes_[ AMPA - 1 ].get_value( lag ): " << B_.spikes_[ AMPA - 1 ].get_value( lag ) << std::endl;
+// std::cout << "B_.spikes_[ GABA - 1 ].get_value( lag ): " << B_.spikes_[ GABA - 1 ].get_value( lag ) << std::endl;
+// std::cout << "S_.y_[ State_::G_AMPA ]: " << S_.y_[ State_::G_AMPA ] << std::endl;
+// std::cout << "S_.y_[ State_::G_GABA ]: " << S_.y_[ State_::G_GABA ] << std::endl;
+
+ S_.y_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.y_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+
+ // add incoming spikes
S_.y_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
S_.y_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
S_.sum_S_post_ = B_.NMDA_cond_.get_value( lag );
B_.NMDA_cond_.set_value( lag, 0.0 );
- // absolute refractory period
+ // absolute refractory period
if ( S_.r_ )
{
// neuron is absolute refractory
@@ -483,11 +492,8 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// voltage logging
B_.logger_.record_data( origin.get_steps() + lag );
+ std::cout << "Inside update" << std::endl;
}
-
-// DelayedRateConnectionEvent drce;
-// drce.set_coeffarray( s_vals );
-// kernel().event_delivery_manager.send_secondary( *this, drce );
}
// Do not move this function as inline to h-file. It depends on
@@ -510,22 +516,6 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
-// void
-// nest::iaf_wang_2002::handle( DelayedRateConnectionEvent& e )
-// {
-// assert( e.get_delay_steps() > 0 );
-// assert( e.get_rport() == NMDA );
-//
-// const double weight = e.get_weight();
-// long delay = e.get_delay_steps();
-//
-// for ( auto it = e.begin(); it != e.end(); ++delay )
-// {
-// B_.NMDA_cond_.add_value( delay, weight * e.get_coeffvalue( it ) );
-// }
-//
-// }
-
void
nest::iaf_wang_2002::handle( CurrentEvent& e )
{
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 5b341c8b68..9f2239cb3e 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -239,6 +239,7 @@ class iaf_wang_2002 : public ArchivingNode
double C_m; //!< Membrane Capacitance in pF
double g_L; //!< Leak Conductance in nS
double t_ref; //!< Refractory period in ms
+ double tau_AMPA_ext; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
double tau_rise_NMDA; //!< Synaptic Rise Time Constant NMDA Synapse in ms
@@ -274,6 +275,7 @@ class iaf_wang_2002 : public ArchivingNode
enum StateVecElems
{
V_m = 0,
+ G_AMPA_ext,
G_AMPA,
G_GABA,
S_pre,
@@ -423,17 +425,8 @@ iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bo
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- std::cout << "====================" << std::endl;
- std::cout << "RECEPTOR TYPE " << receptor_type << std::endl;
- std::cout << "INF RECEPTOR " << INF_SPIKE_RECEPTOR << std::endl;
- std::cout << "SUP RECEPTOR " << SUP_SPIKE_RECEPTOR << std::endl;
- std::cout << "====================" << std::endl;
if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) ) //or receptor_type == NMDA )
{
- std::cout << "====================" << std::endl;
- std::cout << "THROWING ERROR" << receptor_type << std::endl;
- std::cout << "====================" << std::endl;
-
throw UnknownReceptorType( receptor_type, get_name() );
return 0;
}
From 4731cde43bd4a6ccb9ace6a9c8cbf5d3399dfe75 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 6 Oct 2023 15:52:08 +0200
Subject: [PATCH 008/184] new dynamics in place, not tested yet
---
models/iaf_wang_2002.cpp | 115 +++++++++++++++++---------------------
models/iaf_wang_2002.h | 79 ++++++++++----------------
nestkernel/nest_names.cpp | 3 +
nestkernel/nest_names.h | 3 +
4 files changed, 85 insertions(+), 115 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index b024b31040..31d2896afe 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -58,9 +58,9 @@ RecordablesMap< iaf_wang_2002 >::create()
{
// add state variables to recordables map
insert_( names::V_m, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::V_m > );
- insert_( names::g_AMPA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::G_AMPA > );
- insert_( names::g_GABA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::G_GABA > );
- insert_( names::NMDA_sum, &iaf_wang_2002::get_NMDA_sum_ );
+ insert_( names::s_AMPA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_AMPA > );
+ insert_( names::s_GABA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_GABA > );
+ insert_( names::s_NMDA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_NMDA > );
}
}
@@ -77,23 +77,20 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
// y[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.y[].
- const double I_AMPA = ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::G_AMPA ];
+ const double I_AMPA = ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ];
- const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::G_GABA ];
+ const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
const double I_rec_NMDA = ( y[ S::V_m ] - node.P_.E_ex )
- / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * node.S_.sum_S_post_;
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * y[ S::s_NMDA ];
const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
f[ S::V_m ] = ( -node.P_.g_L * ( y[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
- f[ S::G_AMPA ] = -y[ S::G_AMPA ] / node.P_.tau_AMPA;
- f[ S::G_GABA ] = -y[ S::G_GABA ] / node.P_.tau_GABA;
-
- f[ S::S_pre ] =
- -y[ S::S_pre ] / node.P_.tau_decay_NMDA + node.P_.alpha * y[ S::X_pre ] * ( 1 - y[ S::S_pre ] );
- f[ S::X_pre ] = -y[ S::X_pre ] / node.P_.tau_rise_NMDA;
+ f[ S::s_AMPA ] = -y[ S::s_AMPA ] / node.P_.tau_AMPA;
+ f[ S::s_NMDA ] = -y[ S::s_AMPA ] / node.P_.tau_decay_NMDA;
+ f[ S::s_GABA ] = -y[ S::s_GABA ] / node.P_.tau_GABA;
return GSL_SUCCESS;
}
@@ -115,7 +112,6 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
- , tau_rise_NMDA( 2.0 ) // ms
, tau_decay_NMDA( 100 ) // ms
, alpha( 0.5 ) // 1 / ms
, conc_Mg2( 1 ) // mM
@@ -125,30 +121,27 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
nest::iaf_wang_2002::State_::State_( const Parameters_& p )
: r_( 0 )
- , sum_S_post_( 0 )
{
y_[ V_m ] = p.E_L; // initialize to reversal potential
- y_[ G_AMPA ] = 0.0;
- y_[ G_GABA ] = 0.0;
- y_[ S_pre ] = 0.0;
- y_[ X_pre ] = 0.0;
+ y_[ s_AMPA ] = 0.0;
+ y_[ s_GABA ] = 0.0;
+ y_[ s_NMDA ] = 0.0;
}
nest::iaf_wang_2002::State_::State_( const State_& s )
: r_( s.r_ )
- , sum_S_post_( s.sum_S_post_ )
{
y_[ V_m ] = s.y_[ V_m ];
- y_[ G_AMPA ] = s.y_[ G_AMPA ];
- y_[ G_GABA ] = s.y_[ G_GABA ];
- y_[ S_pre ] = s.y_[ S_pre ];
- y_[ X_pre ] = s.y_[ X_pre ];
+ y_[ s_AMPA ] = s.y_[ s_AMPA ];
+ y_[ s_GABA ] = s.y_[ s_GABA ];
+ y_[ s_NMDA ] = s.y_[ s_NMDA ];
}
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
: logger_( n )
- , spikes_()
- , NMDA_cond_()
+ , spike_AMPA()
+ , spike_GABA()
+ , spike_NMDA()
, s_( nullptr )
, c_( nullptr )
, e_( nullptr )
@@ -160,13 +153,9 @@ nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
nest::iaf_wang_2002::Buffers_::Buffers_( const Buffers_&, iaf_wang_2002& n )
: logger_( n )
- , spikes_()
- , NMDA_cond_()
, s_( nullptr )
, c_( nullptr )
, e_( nullptr )
- , step_( Time::get_resolution().get_ms() )
- , integration_step_( step_ )
{
// Initialization of the remaining members is deferred to init_buffers_().
}
@@ -254,8 +243,9 @@ void
nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
{
def< double >( d, names::V_m, y_[ V_m ] ); // Membrane potential
- def< double >( d, names::g_AMPA, y_[ G_AMPA ] );
- def< double >( d, names::g_GABA, y_[ G_GABA ] );
+ def< double >( d, names::s_AMPA, y_[ s_AMPA ] );
+ def< double >( d, names::s_GABA, y_[ s_GABA ] );
+ def< double >( d, names::s_NMDA, y_[ s_NMDA] );
// total NMDA sum
double NMDA_sum = get_NMDA_sum();
@@ -266,8 +256,9 @@ void
nest::iaf_wang_2002::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
{
updateValueParam< double >( d, names::V_m, y_[ V_m ], node );
- updateValueParam< double >( d, names::g_AMPA, y_[ G_AMPA ], node );
- updateValueParam< double >( d, names::g_GABA, y_[ G_GABA ], node );
+ updateValueParam< double >( d, names::s_AMPA, y_[ s_AMPA ], node );
+ updateValueParam< double >( d, names::s_GABA, y_[ s_GABA ], node );
+ updateValueParam< double >( d, names::s_NMDA, y_[ s_NMDA ], node );
}
/* ---------------------------------------------------------------------------
@@ -333,14 +324,9 @@ nest::iaf_wang_2002::init_state_()
void
nest::iaf_wang_2002::init_buffers_()
{
- B_.spikes_.resize( 3 );
-
- for ( auto& sb : B_.spikes_ )
- {
- sb.clear(); // includes resize
- }
-
- B_.NMDA_cond_.clear();
+ B_.spike_AMPA.clear();
+ B_.spike_GABA.clear();
+ B_.spike_NMDA.clear();
B_.currents_.clear(); // includes resize
B_.logger_.reset(); // includes resize
@@ -446,23 +432,12 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
}
-
-
-// std::cout << "B_.spikes_[ AMPA - 1 ].get_value( lag ): " << B_.spikes_[ AMPA - 1 ].get_value( lag ) << std::endl;
-// std::cout << "B_.spikes_[ GABA - 1 ].get_value( lag ): " << B_.spikes_[ GABA - 1 ].get_value( lag ) << std::endl;
-// std::cout << "S_.y_[ State_::G_AMPA ]: " << S_.y_[ State_::G_AMPA ] << std::endl;
-// std::cout << "S_.y_[ State_::G_GABA ]: " << S_.y_[ State_::G_GABA ] << std::endl;
-
- S_.y_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
- S_.y_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
- // add incoming spikes
- S_.y_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
- S_.y_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
- S_.sum_S_post_ = B_.NMDA_cond_.get_value( lag );
- B_.NMDA_cond_.set_value( lag, 0.0 );
+ // add incoming spikes
+ S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
+ S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
+ S_.y_[ State_::s_NMDA ] += B_.spike_NMDA.get_value( lag );
- // absolute refractory period
if ( S_.r_ )
{
// neuron is absolute refractory
@@ -475,18 +450,22 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.r_ = V_.RefractoryCounts_;
S_.y_[ State_::V_m ] = P_.V_reset;
- S_.y_[ State_::X_pre ] += 1;
+ // get previous spike time
+ double t_lastspike = get_spiketime_ms();
+
// log spike with ArchivingNode
set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
+ double t_spike = get_spiketime_ms();
+
+ // compute current value of s_NMDA and add NMDA update to spike offset
+ S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
SpikeEvent se;
+ se.set_offset( P_.alpha * ( 1 - S_.s_NMDA_pre ));
kernel().event_delivery_manager.send( *this, se, lag );
}
- // send NMDA update
- s_vals[ lag ] = S_.y_[ State_::S_pre ];
-
// set new input current
B_.I_stim_ = B_.currents_.get_value( lag );
@@ -508,12 +487,20 @@ void
nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
- assert( e.get_rport() < NMDA );
- const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
+ if ( e.get_weight() > 0.0 )
+ {
+ B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ e.get_weight() * e.get_multiplicity() );
- const auto rport = e.get_rport();
- B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ e.get_weight() * e.get_multiplicity() * e.get_offset() );
+ }
+ else
+ {
+ B_.spike_GABA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ -e.get_weight() * e.get_multiplicity() );
+ }
}
void
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 9f2239cb3e..5d3693515b 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -100,7 +100,13 @@ The following parameters can be set in the status dictionary.
V_reset mV Reset potential
C_m pF Membrane capacitance
g_L nS Leak conductance
+ g_AMPA_ext nS Peak external AMPA conductance
+ g_AMPA nS Peak recurrent AMPA conductance
+ g_NMDA nS Peak recurrent NMDA conductance
+ g_GABA nS Peak recurrent GABA conductance
+ g_L nS Leak conductance
t_ref ms Refractory period
+ tau_AMPA_ext ms Synaptic time constant for external AMPA synapse
tau_AMPA ms Synaptic time constant for AMPA synapse
tau_GABA ms Synaptic time constant for GABA synapse
tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
@@ -117,8 +123,9 @@ The following values can be recorded.
=========== ===========================================================
V_m Membrane potential
- g_AMPA AMPA gate
- g_GABA GABA gate
+ s_AMPA AMPA gate
+ s_AMPA_ext external AMPA gate
+ s_GABA GABA gate
NMDA_sum sum of NMDA over all presynaptic neurons j
=========== ===========================================================
@@ -205,10 +212,8 @@ class iaf_wang_2002 : public ArchivingNode
enum SynapseTypes
{
INF_SPIKE_RECEPTOR = 0,
- AMPA_EXT,
- AMPA,
+ AMPA_NMDA,
GABA,
- NMDA,
SUP_SPIKE_RECEPTOR
};
@@ -218,7 +223,6 @@ class iaf_wang_2002 : public ArchivingNode
void calibrate();
void update( Time const&, const long, const long ) override;
-
// make dynamics function quasi-member
friend int iaf_wang_2002_dynamics( double, const double*, double*, void* );
@@ -238,8 +242,12 @@ class iaf_wang_2002 : public ArchivingNode
double V_reset; //!< Reset Potential in mV
double C_m; //!< Membrane Capacitance in pF
double g_L; //!< Leak Conductance in nS
+ double g_GABA; //!< Peak conductance GABA
+ double g_NMDA; //!< Peak conductance NMDA
+ double g_AMPA; //!< Peak conductance AMPA
+ double g_AMPA_ext; //!< Peak conductance external AMPA
double t_ref; //!< Refractory period in ms
- double tau_AMPA_ext; //!< Synaptic Time Constant AMPA Synapse in ms
+ double tau_AMPA_ext; //!< Synaptic Time Constant external AMPA Synapse in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
double tau_rise_NMDA; //!< Synaptic Rise Time Constant NMDA Synapse in ms
@@ -275,21 +283,20 @@ class iaf_wang_2002 : public ArchivingNode
enum StateVecElems
{
V_m = 0,
- G_AMPA_ext,
- G_AMPA,
- G_GABA,
- S_pre,
- X_pre,
+ s_AMPA,
+ s_NMDA,
+ s_GABA,
STATE_VEC_SIZE
};
double y_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
int r_; //!< number of refractory steps remaining
- double sum_S_post_;
State_( const Parameters_& ); //!< Default initialization
State_( const State_& );
+ double s_NMDA_pre;
+
void get( DictionaryDatum& ) const;
void set( const DictionaryDatum&, const Parameters_&, Node* );
@@ -328,8 +335,9 @@ class iaf_wang_2002 : public ArchivingNode
// -----------------------------------------------------------------------
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
- std::vector< RingBuffer > spikes_;
- RingBuffer NMDA_cond_;
+ RingBuffer spike_AMPA;
+ RingBuffer spike_GABA;
+ RingBuffer spike_NMDA;
RingBuffer currents_;
// -----------------------------------------------------------------------
@@ -408,48 +416,21 @@ inline size_t
iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bool )
{
std::cout << "RECEPTOR TYPE " << receptor_type << std::endl;
- if ( receptor_type != NMDA )
- {
- SpikeEvent e;
- e.set_sender( *this );
- return target.handles_test_event( e, receptor_type );
- }
- else
- {
- DelayedRateConnectionEvent e;
- e.set_sender( *this );
- return target.handles_test_event( e, receptor_type );
- }
+ SpikeEvent e;
+ e.set_sender( *this );
+ return target.handles_test_event( e, receptor_type );
}
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) ) //or receptor_type == NMDA )
+ if ( receptor_type != 0 )
{
throw UnknownReceptorType( receptor_type, get_name() );
- return 0;
- }
- else
- {
- return receptor_type;
}
+ return 0;
}
-// inline size_t
-// iaf_wang_2002::handles_test_event( DelayedRateConnectionEvent&, size_t receptor_type )
-// {
-// if ( receptor_type != NMDA )
-// {
-// throw UnknownReceptorType( receptor_type, get_name() );
-// return 0;
-// }
-// else
-// {
-// return receptor_type;
-// }
-// }
-
inline size_t
iaf_wang_2002::handles_test_event( CurrentEvent&, size_t receptor_type )
{
@@ -487,10 +468,6 @@ iaf_wang_2002::get_status( DictionaryDatum& d ) const
DictionaryDatum receptor_type = new Dictionary();
- ( *receptor_type )[ names::AMPA ] = AMPA;
- ( *receptor_type )[ names::GABA ] = GABA;
- ( *receptor_type )[ names::NMDA ] = NMDA;
-
( *d )[ names::receptor_types ] = receptor_type;
( *d )[ names::recordables ] = recordablesMap_.get_list();
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 18405a3f07..c22de3f4ec 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -404,6 +404,9 @@ const Name receptor_idx( "receptor_idx" );
const Name S( "S" );
const Name S_act_NMDA( "S_act_NMDA" );
+const Name s_NMDA( "S_NMDA" );
+const Name s_AMPA( "S_AMPA" );
+const Name s_GABA( "S_GABA" );
const Name sdev( "sdev" );
const Name send_buffer_size_secondary_events( "send_buffer_size_secondary_events" );
const Name senders( "senders" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index f071f1d8f7..b210c14ac5 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -429,6 +429,9 @@ extern const Name rule;
extern const Name S;
extern const Name S_act_NMDA;
+extern const Name s_GABA;
+extern const Name s_AMPA;
+extern const Name s_NMDA;
extern const Name sdev;
extern const Name senders;
extern const Name send_buffer_size_secondary_events;
From c5347b2d6213814c3a0ab323adca071cea75278d Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 18 Oct 2023 14:54:11 +0200
Subject: [PATCH 009/184] solution verified analytically
---
pynest/examples/wang_neuron.py | 100 +++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
create mode 100644 pynest/examples/wang_neuron.py
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
new file mode 100644
index 0000000000..3de41e62cd
--- /dev/null
+++ b/pynest/examples/wang_neuron.py
@@ -0,0 +1,100 @@
+import nest
+import matplotlib.pyplot as plt
+import numpy as np
+
+nest.rng_seed = 12345
+
+w_ex = 40.
+w_in = -15.
+alpha = 0.5
+tau_AMPA = 2.0
+tau_GABA = 5.0
+tau_NMDA = 100.0
+nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_rise_NMDA": tau_NMDA})
+
+pg = nest.Create("poisson_generator", {"rate": 50.})
+sr = nest.Create("spike_recorder")
+nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_rise_NMDA": tau_NMDA,
+ "t_ref": 0.})
+
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_in}
+
+conn_spec = {"rule": "all_to_all"}
+
+def s_soln(w, t, tau):
+ isyn = np.zeros_like(t)
+ useinds = t >= 0.
+ isyn[useinds] = w * np.exp(-t[useinds] / tau)
+ return isyn
+
+def spiketrain_response(t, tau, spiketrain, w):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ response += s_soln(w, t_, tau)
+ return response
+
+def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ w_ = w * alpha * (1 - response[zero_arg])
+ w_ = min(w_, 1 - response[zero_arg])
+ response += s_soln(w_, t_, tau)
+ return response
+
+nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, sr)
+nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(mm1, nrn1)
+
+nest.Connect(mm2, nrn2)
+
+nest.Simulate(1000.)
+
+# get spike times from membrane potential
+# cannot use spike_recorder because we abuse exact spike timing
+V_m = mm1.get("events", "V_m")
+times = mm1.get("events", "times")
+diff = np.ediff1d(V_m, to_begin=0.)
+spikes = sr.get("events", "times")
+spikes = times[diff < -3]
+
+# compute analytical solutions
+times = mm1.get("events", "times")
+ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+
+fig, ax = plt.subplots(4,2)
+ax[0,0].plot(mm1.events["V_m"])
+ax[0,1].plot(mm2.events["V_m"])
+
+ax[1,0].plot(mm1.events["s_AMPA"])
+ax[1,1].plot(mm2.events["s_AMPA"])
+ax[1,1].plot(ampa_soln, '--')
+
+ax[2,0].plot(mm1.events["s_GABA"])
+ax[2,1].plot(mm2.events["s_GABA"])
+ax[2,1].plot(gaba_soln, '--')
+
+ax[3,0].plot(mm1.events["s_NMDA"])
+ax[3,1].plot(mm2.events["s_NMDA"])
+ax[3,1].plot(nmda_soln, '--')
+
+plt.show()
+
From abc08d4eb67e1d89821f0c3d1f5be7a5222c73f4 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 25 Oct 2023 17:37:35 +0200
Subject: [PATCH 010/184] fix receptor port for external input, WIP reproduce
wang2002
---
models/iaf_wang_2002.cpp | 21 ++++++---
models/iaf_wang_2002.h | 21 +++++++--
pynest/examples/wang_decision_making.py | 58 +++++++++++++++++++++++++
pynest/examples/wang_neuron.py | 7 +--
4 files changed, 93 insertions(+), 14 deletions(-)
create mode 100644 pynest/examples/wang_decision_making.py
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 31d2896afe..c9a6d90e7f 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -89,7 +89,7 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
f[ S::V_m ] = ( -node.P_.g_L * ( y[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
f[ S::s_AMPA ] = -y[ S::s_AMPA ] / node.P_.tau_AMPA;
- f[ S::s_NMDA ] = -y[ S::s_AMPA ] / node.P_.tau_decay_NMDA;
+ f[ S::s_NMDA ] = -y[ S::s_NMDA ] / node.P_.tau_decay_NMDA;
f[ S::s_GABA ] = -y[ S::s_GABA ] / node.P_.tau_GABA;
return GSL_SUCCESS;
@@ -437,7 +437,10 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
S_.y_[ State_::s_NMDA ] += B_.spike_NMDA.get_value( lag );
-
+ if ( S_.y_[ State_::s_NMDA ] > 1 )
+ {
+ S_.y_[ State_::s_NMDA ] = 1;
+ }
if ( S_.r_ )
{
// neuron is absolute refractory
@@ -453,7 +456,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// get previous spike time
double t_lastspike = get_spiketime_ms();
-
// log spike with ArchivingNode
set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
@@ -461,8 +463,11 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
+ double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
+ S_.s_NMDA_pre += s_NMDA_delta;
+
SpikeEvent se;
- se.set_offset( P_.alpha * ( 1 - S_.s_NMDA_pre ));
+ se.set_offset( s_NMDA_delta );
kernel().event_delivery_manager.send( *this, se, lag );
}
@@ -471,7 +476,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// voltage logging
B_.logger_.record_data( origin.get_steps() + lag );
- std::cout << "Inside update" << std::endl;
}
}
@@ -487,14 +491,17 @@ void
nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
+ std::cout << "rport: " << e.get_rport() << std::endl;
if ( e.get_weight() > 0.0 )
{
B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() );
-
- B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+
+ if ( e.get_rport() == 0 ) {
+ B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() * e.get_offset() );
+ }
}
else
{
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 5d3693515b..cf092affaa 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -205,6 +205,12 @@ class iaf_wang_2002 : public ArchivingNode
void get_status( DictionaryDatum& ) const override;
void set_status( const DictionaryDatum& ) override;
+ bool
+ is_off_grid() const override
+ {
+ return true;
+ }
+
private:
/**
* Synapse types to connect to
@@ -295,7 +301,7 @@ class iaf_wang_2002 : public ArchivingNode
State_( const Parameters_& ); //!< Default initialization
State_( const State_& );
- double s_NMDA_pre;
+ double s_NMDA_pre = 0;
void get( DictionaryDatum& ) const;
void set( const DictionaryDatum&, const Parameters_&, Node* );
@@ -424,11 +430,18 @@ iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bo
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( receptor_type != 0 )
+ if ( receptor_type == 0 )
+ {
+ return 0;
+ }
+ else if ( receptor_type == 1 )
+ {
+ return 1;
+ }
+ else
{
throw UnknownReceptorType( receptor_type, get_name() );
}
- return 0;
}
inline size_t
@@ -468,7 +481,7 @@ iaf_wang_2002::get_status( DictionaryDatum& d ) const
DictionaryDatum receptor_type = new Dictionary();
- ( *d )[ names::receptor_types ] = receptor_type;
+// ( *d )[ names::receptor_types ] = receptor_type;
( *d )[ names::recordables ] = recordablesMap_.get_list();
}
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
new file mode 100644
index 0000000000..a57e84520d
--- /dev/null
+++ b/pynest/examples/wang_decision_making.py
@@ -0,0 +1,58 @@
+import nest
+import matplotlib.pyplot as plt
+import numpy as np
+
+np.random.seed(123)
+rng = np.random.default_rng()
+
+epop_params = {"g_AMPA_ext": 2.1,
+ "g_AMPA_rec": 0.05,
+ "g_NMDA": 0.165,
+ "g_GABA": 1.3,
+ "tau_GABA": 5.0,
+ "tau_AMPA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "alpha": 0.5,
+ "conc_Mg2": 1.0,
+ "g_L": 25. # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "C_m": 500.0, # membrane capacitance
+ "t_ref": 2.0 # refreactory period
+ }
+
+
+ipop_params = {"g_AMPA_ext": 1.62,
+ "g_AMPA_rec": 0.04,
+ "g_NMDA": 0.13,
+ "g_GABA": 1.0,
+ "tau_GABA": 5.0,
+ "tau_AMPA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "alpha": 0.5,
+ "conc_Mg2": 1.0,
+ "g_L": 20. # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "C_m": 200.0, # membrane capacitance
+ "t_ref": 1.0 # refreactory period
+ }
+
+NE = 1600
+NI = 400
+
+
+
+epop1 = nest.Create("iaf_wang_2002", int(0.5 * NE))
+epop2 = nest.Create("iaf_wang_2002", int(0.5 * NE))
+ipop = nest.Create("iaf_wang_2002", NE)
+
+
+
+
+
+
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index 3de41e62cd..562a73642d 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -12,20 +12,21 @@
tau_NMDA = 100.0
nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
"tau_GABA": tau_GABA,
- "tau_rise_NMDA": tau_NMDA})
+ "tau_decay_NMDA": tau_NMDA})
pg = nest.Create("poisson_generator", {"rate": 50.})
sr = nest.Create("spike_recorder")
nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
"tau_GABA": tau_GABA,
- "tau_rise_NMDA": tau_NMDA,
+ "tau_decay_NMDA": tau_NMDA,
"t_ref": 0.})
mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex}
+ "weight": w_ex,
+ "receptor_type": 0}
in_syn_spec = {"synapse_model": "static_synapse",
"weight": w_in}
From fc17cf5206c4197873a3578925a0656064f67873 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 25 Oct 2023 17:38:33 +0200
Subject: [PATCH 011/184] lower case s for gating variable
---
nestkernel/nest_names.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index c22de3f4ec..83d98fb6d5 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -404,9 +404,9 @@ const Name receptor_idx( "receptor_idx" );
const Name S( "S" );
const Name S_act_NMDA( "S_act_NMDA" );
-const Name s_NMDA( "S_NMDA" );
-const Name s_AMPA( "S_AMPA" );
-const Name s_GABA( "S_GABA" );
+const Name s_NMDA( "s_NMDA" );
+const Name s_AMPA( "s_AMPA" );
+const Name s_GABA( "s_GABA" );
const Name sdev( "sdev" );
const Name send_buffer_size_secondary_events( "send_buffer_size_secondary_events" );
const Name senders( "senders" );
From 9ac001261bf4faa66b0b24f478abebe841e5b70a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 30 Oct 2023 11:45:30 +0100
Subject: [PATCH 012/184] test works
---
testsuite/pytests/test_iaf_wang_2002.py | 125 ++++++++++++++++++++++++
1 file changed, 125 insertions(+)
create mode 100644 testsuite/pytests/test_iaf_wang_2002.py
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
new file mode 100644
index 0000000000..dd413a226f
--- /dev/null
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+#
+# test_iaf_wang_2002.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST. If not, see .
+
+"""
+Tests synaptic dynamics of iaf_wang_2002. Since the neuron is conductance based,
+it is impossible to analytically confirm the membrane potential, but all the
+synaptic currents can be computed analytically (for the simplified implementation
+we use). The integration of the membrane potential is not tested here.
+"""
+
+
+
+import nest
+import numpy as np
+import numpy.testing as nptest
+import pytest
+
+def s_soln(w, t, tau):
+ """
+ Solution for GABA/AMPA receptors
+ """
+ isyn = np.zeros_like(t)
+ useinds = t >= 0.
+ isyn[useinds] = w * np.exp(-t[useinds] / tau)
+ return isyn
+
+def spiketrain_response(t, tau, spiketrain, w):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ response += s_soln(w, t_, tau)
+ return response
+
+def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
+ """
+ Solution for NMDA receptors
+ """
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ w_ = w * alpha * (1 - response[zero_arg])
+ w_ = min(w_, 1 - response[zero_arg])
+ response += s_soln(w_, t_, tau)
+ return response
+
+def test_wang():
+ w_ex = 40.
+ w_in = -15.
+ alpha = 0.5
+ tau_AMPA = 2.0
+ tau_GABA = 5.0
+ tau_NMDA = 100.0
+
+ # Create 2 neurons, so that the Wang dynamics are present
+ nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_NMDA})
+
+ pg = nest.Create("poisson_generator", {"rate": 50.})
+ sr = nest.Create("spike_recorder")
+ nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_NMDA,
+ "t_ref": 0.})
+
+ mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+ mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+
+ ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 0}
+
+ in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_in}
+
+ conn_spec = {"rule": "all_to_all"}
+
+
+ nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+ nest.Connect(nrn1, sr)
+ nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+ nest.Connect(mm1, nrn1)
+
+ nest.Connect(mm2, nrn2)
+
+ nest.Simulate(1000.)
+
+ # get spike times from membrane potential
+ # cannot use spike_recorder because we abuse exact spike timing
+ V_m = mm1.get("events", "V_m")
+ times = mm1.get("events", "times")
+ diff = np.ediff1d(V_m, to_begin=0.)
+ spikes = sr.get("events", "times")
+ spikes = times[diff < -3]
+
+ # compute analytical solutions
+ times = mm1.get("events", "times")
+ ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+ nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+ gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+
+ nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
+ nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
+ nptest.assert_array_almost_equal(nmda_soln, mm2.events["s_NMDA"])
From ca4a7ec419b53519a4d9f20656ac20e785c4738a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 6 Nov 2023 11:38:53 +0100
Subject: [PATCH 013/184] remove unneccessary lines
---
models/iaf_wang_2002.cpp | 1 -
models/iaf_wang_2002.h | 24 ++----------------------
testsuite/pytests/test_iaf_wang_2002.py | 2 --
3 files changed, 2 insertions(+), 25 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index c9a6d90e7f..ffde39d7c6 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -491,7 +491,6 @@ void
nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
- std::cout << "rport: " << e.get_rport() << std::endl;
if ( e.get_weight() > 0.0 )
{
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index cf092affaa..4bbbe53ef5 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -55,9 +55,10 @@ namespace nest
*/
extern "C" inline int iaf_wang_2002_dynamics( double, const double*, double*, void* );
-
/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
+DOCUMENTATION WILL BE UPDATED
+
Short description
+++++++++++++++++
@@ -91,7 +92,6 @@ Parameters
The following parameters can be set in the status dictionary.
-
=============== ======= ===========================================================
E_L mV Resting potential
E_ex mV Excitatory reversal potential
@@ -306,18 +306,6 @@ class iaf_wang_2002 : public ArchivingNode
void get( DictionaryDatum& ) const;
void set( const DictionaryDatum&, const Parameters_&, Node* );
- //! Get the sum of NMDA over all presynaptic neurons
- double
- get_NMDA_sum() const
- {
- /*double NMDA_sum = 0.0;
- for ( size_t i = G_NMDA_base; i < state_vec_size; i += 2 )
- {
- NMDA_sum += ode_state_[ i + 1 ];
- }
- return NMDA_sum;*/
- return -1;
- }
};
@@ -398,13 +386,6 @@ class iaf_wang_2002 : public ArchivingNode
return S_.y_[ elem ];
}
- //! Get the sum of NMDA from state, used by UniversalDataLogger
- double
- get_NMDA_sum_() const
- {
- return S_.get_NMDA_sum();
- }
-
// Data members -----------------------------------------------------------
// keep the order of these lines, seems to give best performance
@@ -421,7 +402,6 @@ class iaf_wang_2002 : public ArchivingNode
inline size_t
iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bool )
{
- std::cout << "RECEPTOR TYPE " << receptor_type << std::endl;
SpikeEvent e;
e.set_sender( *this );
return target.handles_test_event( e, receptor_type );
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index dd413a226f..4b8ff93cfd 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -27,7 +27,6 @@
"""
-
import nest
import numpy as np
import numpy.testing as nptest
@@ -94,7 +93,6 @@ def test_wang():
"weight": w_in}
conn_spec = {"rule": "all_to_all"}
-
nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
nest.Connect(nrn1, sr)
From d9653dafa2dc08d4e19fed9d6ad9fe6f7387003f Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 6 Nov 2023 16:28:09 +0100
Subject: [PATCH 014/184] compilation bug wip
---
models/iaf_wang_2002.cpp | 36 ++++++++------
models/iaf_wang_2002.h | 8 +---
modelsets/full | 2 +-
nestkernel/nest_names.cpp | 1 +
nestkernel/nest_names.h | 1 +
pynest/examples/wang_decision_making.py | 64 +++++++++++++++++++++++--
6 files changed, 84 insertions(+), 28 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index ffde39d7c6..e8f82ae45e 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -48,6 +48,11 @@ nest::RecordablesMap< nest::iaf_wang_2002 > nest::iaf_wang_2002::recordablesMap_
namespace nest
{
+void
+register_iaf_wang_2002( const std::string& name )
+{
+ register_node_model< iaf_wang_2002 >( name );
+}
/*
* Override the create() method with one call to RecordablesMap::insert_()
* for each quantity to be recorded.
@@ -109,6 +114,10 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, V_reset( -60.0 ) // mV
, C_m( 500.0 ) // pF
, g_L( 25.0 ) // nS
+ , g_GABA ( 1.3 ) //
+ , g_NMDA ( 0.165 ) //
+ , g_AMPA ( 0.05 ) //
+ , g_AMPA_ext ( 1.3 ) //
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
@@ -174,10 +183,13 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::V_reset, V_reset );
def< double >( d, names::C_m, C_m );
def< double >( d, names::g_L, g_L );
+ def< double >( d, names::g_GABA, g_GABA );
+ def< double >( d, names::g_NMDA, g_NMDA );
+ def< double >( d, names::g_AMPA, g_AMPA );
+ def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
def< double >( d, names::t_ref, t_ref );
def< double >( d, names::tau_AMPA, tau_AMPA );
def< double >( d, names::tau_GABA, tau_GABA );
- def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
def< double >( d, names::tau_decay_NMDA, tau_decay_NMDA );
def< double >( d, names::alpha, alpha );
def< double >( d, names::conc_Mg2, conc_Mg2 );
@@ -188,25 +200,23 @@ void
nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
{
// allow setting the membrane potential
- updateValueParam< double >( d, names::V_th, V_th, node );
- updateValueParam< double >( d, names::V_reset, V_reset, node );
- updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::E_L, E_L, node );
-
updateValueParam< double >( d, names::E_ex, E_ex, node );
updateValueParam< double >( d, names::E_in, E_in, node );
-
+ updateValueParam< double >( d, names::V_th, V_th, node );
+ updateValueParam< double >( d, names::V_reset, V_reset, node );
updateValueParam< double >( d, names::C_m, C_m, node );
updateValueParam< double >( d, names::g_L, g_L, node );
-
+ updateValueParam< double >( d, names::g_GABA, g_GABA, node );
+ updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
+ updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
+ updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
+ updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
- updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
updateValueParam< double >( d, names::tau_decay_NMDA, tau_decay_NMDA, node );
-
updateValueParam< double >( d, names::alpha, alpha, node );
updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
-
updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
if ( V_reset >= V_th )
@@ -221,7 +231,7 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
{
throw BadProperty( "Refractory time cannot be negative." );
}
- if ( tau_AMPA <= 0 or tau_GABA <= 0 or tau_rise_NMDA <= 0 or tau_decay_NMDA <= 0 )
+ if ( tau_AMPA <= 0 or tau_GABA <= 0 or tau_decay_NMDA <= 0 )
{
throw BadProperty( "All time constants must be strictly positive." );
}
@@ -246,10 +256,6 @@ nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
def< double >( d, names::s_AMPA, y_[ s_AMPA ] );
def< double >( d, names::s_GABA, y_[ s_GABA ] );
def< double >( d, names::s_NMDA, y_[ s_NMDA] );
-
- // total NMDA sum
- double NMDA_sum = get_NMDA_sum();
- def< double >( d, names::NMDA_sum, NMDA_sum );
}
void
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 4bbbe53ef5..970ec91493 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -106,10 +106,8 @@ The following parameters can be set in the status dictionary.
g_GABA nS Peak recurrent GABA conductance
g_L nS Leak conductance
t_ref ms Refractory period
- tau_AMPA_ext ms Synaptic time constant for external AMPA synapse
tau_AMPA ms Synaptic time constant for AMPA synapse
tau_GABA ms Synaptic time constant for GABA synapse
- tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
alpha 1/ms Scaling factor for NMDA synapse
conc_Mg2 mM Extracellular magnesium concentration
@@ -162,10 +160,8 @@ iaf_cond_alpha, ht_neuron
EndUserDocs */
+void register_iaf_wang_2002( const std::string& name );
-/**
- * Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
- */
class iaf_wang_2002 : public ArchivingNode
{
public:
@@ -253,10 +249,8 @@ class iaf_wang_2002 : public ArchivingNode
double g_AMPA; //!< Peak conductance AMPA
double g_AMPA_ext; //!< Peak conductance external AMPA
double t_ref; //!< Refractory period in ms
- double tau_AMPA_ext; //!< Synaptic Time Constant external AMPA Synapse in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
- double tau_rise_NMDA; //!< Synaptic Rise Time Constant NMDA Synapse in ms
double tau_decay_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
double alpha; //!< Scaling factor for NMDA synapse in 1/ms
double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
diff --git a/modelsets/full b/modelsets/full
index 4cc4e407cc..569ac35de2 100644
--- a/modelsets/full
+++ b/modelsets/full
@@ -59,8 +59,8 @@ iaf_psc_exp_htum
iaf_psc_exp_multisynapse
iaf_psc_exp_ps
iaf_psc_exp_ps_lossless
-iaf_wang_2002
iaf_tum_2000
+iaf_wang_2002
izhikevich
jonke_synapse
lin_rate
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 4d09bc4d88..adfb514a96 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -184,6 +184,7 @@ const Name GABA_A( "GABA_A" );
const Name GABA_B( "GABA_B" );
const Name g( "g" );
const Name g_AMPA( "g_AMPA" );
+const Name g_AMPA_ext( "g_AMPA_ext" );
const Name g_GABA( "g_GABA" );
const Name g_ahp( "g_ahp" );
const Name g_C( "g_C" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index 765b21c509..3389a48db9 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -210,6 +210,7 @@ extern const Name GABA_A;
extern const Name GABA_B;
extern const Name g;
extern const Name g_AMPA;
+extern const Name g_AMPA_ext;
extern const Name g_ahp;
extern const Name g_C;
extern const Name g_GABA;
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index a57e84520d..4c0a4db2ff 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -5,6 +5,7 @@
np.random.seed(123)
rng = np.random.default_rng()
+# Parameters from paper
epop_params = {"g_AMPA_ext": 2.1,
"g_AMPA_rec": 0.05,
"g_NMDA": 0.165,
@@ -14,7 +15,7 @@
"tau_decay_NMDA": 100.0,
"alpha": 0.5,
"conc_Mg2": 1.0,
- "g_L": 25. # leak conductance
+ "g_L": 25., # leak conductance
"E_L": -70.0, # leak reversal potential
"E_ex": 0.0, # excitatory reversal potential
"E_in": -70.0, # inhibitory reversal potential
@@ -33,7 +34,7 @@
"tau_decay_NMDA": 100.0,
"alpha": 0.5,
"conc_Mg2": 1.0,
- "g_L": 20. # leak conductance
+ "g_L": 20., # leak conductance
"E_L": -70.0, # leak reversal potential
"E_ex": 0.0, # excitatory reversal potential
"E_in": -70.0, # inhibitory reversal potential
@@ -42,14 +43,67 @@
"t_ref": 1.0 # refreactory period
}
+simtime = 4000.
+signal_start = 1000.
+signal_duration = 2000.
+signal_update_interval = 50.
+f = 0.15 # proportion of neurons receiving signal inputs
+w_plus = 1.7
+w_minus = 1 - f * (w_plus - 1) / (1 - f)
+
+
NE = 1600
NI = 400
+selective_pop1 = nest.Create("iaf_wang_2002", int(0.15 * NE), params=epop_params)
+selective_pop2 = nest.Create("iaf_wang_2002", int(0.15 * NE), params=epop_params)
+nonselective_pop = nest.Create("iaf_wang_2002", int(0.7 * NE), params=epop_params)
+inhibitory_pop = nest.Create("iaf_wang_2002", NI, params=ipop_params)
+
+mu_0 = 40.
+rho_a = mu_0 / 100
+rho_b = rho_a
+c = 20.
+sigma = 4.
+mu_a = mu_0 + rho_a * c
+mu_b = mu_0 - rho_b * c
+
+num_updates = int(signal_duration / signal_update_interval)
+update_times = np.arange(0, signal_duration, signal_update_interval)
+update_times[0] = 0.1
+rates_a = np.random.normal(mu_a, sigma, size=num_updates)
+rates_b = np.random.normal(mu_a, sigma, size=num_updates)
+
+poisson_a = nest.Create("inhomogeneous_poisson_generator",
+ params={"origin": signal_start-0.1,
+ "start": 0.,
+ "stop": signal_duration,
+ "rate_times": update_times,
+ "rate_values": rates_a})
+
+poisson_b = nest.Create("inhomogeneous_poisson_generator",
+ params={"origin": signal_start-0.1,
+ "start": 0.,
+ "stop": signal_duration,
+ "rate_times": update_times,
+ "rate_values": rates_b})
+
+
+poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
+
+
+syn_spec_selective = {"model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
+syn_spec_nonselective = {"model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
+syn_spec_inhibitory = {"model": "static_synapse", "weight":-1., "delay":0.1, 'receptor_type': 0}
+syn_spec_bg = {"model": "static_synapse", "weight":1., "delay":0.1, 'receptor_type': 1}
+
+nest.Connect(nonselective_pop,
+ selective_pop1 + selective_pop2 + nonselective_pop,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_nonselective)
+
-epop1 = nest.Create("iaf_wang_2002", int(0.5 * NE))
-epop2 = nest.Create("iaf_wang_2002", int(0.5 * NE))
-ipop = nest.Create("iaf_wang_2002", NE)
From 5293f736b36385918958c2b85e426a564048975a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 10 Nov 2023 17:06:45 +0100
Subject: [PATCH 015/184] fix register_model, wip reproduce paper
---
models/iaf_wang_2002.cpp | 1 +
pynest/examples/wang_decision_making.py | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index e8f82ae45e..049bdd486b 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -32,6 +32,7 @@
// Includes from nestkernel:
#include "exceptions.h"
#include "kernel_manager.h"
+#include "nest_impl.h"
#include "universal_data_logger_impl.h"
// Includes from sli:
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 4c0a4db2ff..554e577e12 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -102,6 +102,22 @@
conn_spec="all_to_all",
syn_spec=syn_spec_nonselective)
+nest.Connect(selective_pop1,
+ selective_pop2,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_selective)
+
+nest.Connect(selective_pop2,
+ selective_pop1,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_selective)
+
+nest.Connect(inhibitory_pop,
+ selective_pop1 + selective_pop2 + nonselective_pop,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_inhibitory)
+
+nest.Simulate(4000.)
From 7cea3b2d9afeafd4d4dae33b9f0535e6d2fa964e Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 14 Nov 2023 21:26:49 +0100
Subject: [PATCH 016/184] add separate synaptic channel for external AMPA
---
models/iaf_wang_2002.cpp | 33 +++++++++++++++++++++++----------
models/iaf_wang_2002.h | 2 ++
nestkernel/nest_names.cpp | 1 +
nestkernel/nest_names.h | 1 +
4 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 049bdd486b..603d5c3838 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -65,6 +65,7 @@ RecordablesMap< iaf_wang_2002 >::create()
// add state variables to recordables map
insert_( names::V_m, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::V_m > );
insert_( names::s_AMPA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_AMPA > );
+ insert_( names::s_AMPA_ext, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_AMPA_ext > );
insert_( names::s_GABA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_GABA > );
insert_( names::s_NMDA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_NMDA > );
}
@@ -83,18 +84,20 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
// y[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.y[].
- const double I_AMPA = ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ];
+ const double I_AMPA = node.P_.g_AMPA * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ]
+ + node.P_.g_AMPA_ext * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA_ext ];
- const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
+ const double I_rec_GABA = node.P_.g_GABA * ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
- const double I_rec_NMDA = ( y[ S::V_m ] - node.P_.E_ex )
+ const double I_rec_NMDA = node.P_.g_NMDA * ( y[ S::V_m ] - node.P_.E_ex )
/ ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * y[ S::s_NMDA ];
- const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
+ const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA + node.B_.I_stim_;
f[ S::V_m ] = ( -node.P_.g_L * ( y[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
f[ S::s_AMPA ] = -y[ S::s_AMPA ] / node.P_.tau_AMPA;
+ f[ S::s_AMPA_ext ] = -y[ S::s_AMPA_ext ] / node.P_.tau_AMPA;
f[ S::s_NMDA ] = -y[ S::s_NMDA ] / node.P_.tau_decay_NMDA;
f[ S::s_GABA ] = -y[ S::s_GABA ] / node.P_.tau_GABA;
@@ -134,6 +137,7 @@ nest::iaf_wang_2002::State_::State_( const Parameters_& p )
{
y_[ V_m ] = p.E_L; // initialize to reversal potential
y_[ s_AMPA ] = 0.0;
+ y_[ s_AMPA_ext ] = 0.0;
y_[ s_GABA ] = 0.0;
y_[ s_NMDA ] = 0.0;
}
@@ -143,6 +147,7 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
{
y_[ V_m ] = s.y_[ V_m ];
y_[ s_AMPA ] = s.y_[ s_AMPA ];
+ y_[ s_AMPA_ext ] = s.y_[ s_AMPA_ext ];
y_[ s_GABA ] = s.y_[ s_GABA ];
y_[ s_NMDA ] = s.y_[ s_NMDA ];
}
@@ -150,6 +155,7 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
: logger_( n )
, spike_AMPA()
+ , spike_AMPA_ext()
, spike_GABA()
, spike_NMDA()
, s_( nullptr )
@@ -255,6 +261,7 @@ nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
{
def< double >( d, names::V_m, y_[ V_m ] ); // Membrane potential
def< double >( d, names::s_AMPA, y_[ s_AMPA ] );
+ def< double >( d, names::s_AMPA_ext, y_[ s_AMPA_ext ] );
def< double >( d, names::s_GABA, y_[ s_GABA ] );
def< double >( d, names::s_NMDA, y_[ s_NMDA] );
}
@@ -264,6 +271,7 @@ nest::iaf_wang_2002::State_::set( const DictionaryDatum& d, const Parameters_&,
{
updateValueParam< double >( d, names::V_m, y_[ V_m ], node );
updateValueParam< double >( d, names::s_AMPA, y_[ s_AMPA ], node );
+ updateValueParam< double >( d, names::s_AMPA_ext, y_[ s_AMPA_ext ], node );
updateValueParam< double >( d, names::s_GABA, y_[ s_GABA ], node );
updateValueParam< double >( d, names::s_NMDA, y_[ s_NMDA ], node );
}
@@ -332,6 +340,7 @@ void
nest::iaf_wang_2002::init_buffers_()
{
B_.spike_AMPA.clear();
+ B_.spike_AMPA_ext.clear();
B_.spike_GABA.clear();
B_.spike_NMDA.clear();
B_.currents_.clear(); // includes resize
@@ -442,6 +451,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// add incoming spikes
S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
+ S_.y_[ State_::s_AMPA_ext ] += B_.spike_AMPA_ext.get_value( lag );
S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
S_.y_[ State_::s_NMDA ] += B_.spike_NMDA.get_value( lag );
if ( S_.y_[ State_::s_NMDA ] > 1 )
@@ -501,12 +511,15 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
if ( e.get_weight() > 0.0 )
{
- B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- e.get_weight() * e.get_multiplicity() );
-
- if ( e.get_rport() == 0 ) {
- B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- e.get_weight() * e.get_multiplicity() * e.get_offset() );
+ if ( e.get_rport() == 0 ) { // recurrent spike
+ B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ e.get_weight() * e.get_multiplicity() );
+ B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ e.get_weight() * e.get_multiplicity() * e.get_offset() );
+ }
+ else if ( e.get_rport() == 1 ) { // external spike
+ B_.spike_AMPA_ext.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ e.get_weight() * e.get_multiplicity() );
}
}
else
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 970ec91493..e24d6f26ec 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -284,6 +284,7 @@ class iaf_wang_2002 : public ArchivingNode
{
V_m = 0,
s_AMPA,
+ s_AMPA_ext,
s_NMDA,
s_GABA,
STATE_VEC_SIZE
@@ -324,6 +325,7 @@ class iaf_wang_2002 : public ArchivingNode
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
RingBuffer spike_AMPA;
+ RingBuffer spike_AMPA_ext;
RingBuffer spike_GABA;
RingBuffer spike_NMDA;
RingBuffer currents_;
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index adfb514a96..9abbe825ca 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -420,6 +420,7 @@ const Name S( "S" );
const Name S_act_NMDA( "S_act_NMDA" );
const Name s_NMDA( "s_NMDA" );
const Name s_AMPA( "s_AMPA" );
+const Name s_AMPA_ext( "s_AMPA_ext" );
const Name s_GABA( "s_GABA" );
const Name SIC_scale( "SIC_scale" );
const Name SIC_th( "SIC_th" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index 3389a48db9..4c89dae4f1 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -448,6 +448,7 @@ extern const Name S;
extern const Name S_act_NMDA;
extern const Name s_GABA;
extern const Name s_AMPA;
+extern const Name s_AMPA_ext;
extern const Name s_NMDA;
extern const Name SIC_scale;
extern const Name SIC_th;
From d9a467045c753bba82dc9e3fd41c07d709ce8a79 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 14 Nov 2023 21:27:17 +0100
Subject: [PATCH 017/184] adjust rate
---
pynest/examples/wang_neuron.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index 562a73642d..661bb155a1 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -4,7 +4,7 @@
nest.rng_seed = 12345
-w_ex = 40.
+w_ex = 400.
w_in = -15.
alpha = 0.5
tau_AMPA = 2.0
@@ -21,8 +21,8 @@
"tau_decay_NMDA": tau_NMDA,
"t_ref": 0.})
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_AMPA_ext", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_AMPA_ext", "s_NMDA", "s_GABA"], "interval": 0.1})
ex_syn_spec = {"synapse_model": "static_synapse",
"weight": w_ex,
From 691a5e7efc91dee71d1a26e4284f216fbbd961fc Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 14 Nov 2023 21:27:32 +0100
Subject: [PATCH 018/184] work on replication of (Wang, 2002), still not
working
---
pynest/examples/wang_decision_making.py | 56 +++++++++++++++++++++----
1 file changed, 49 insertions(+), 7 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 554e577e12..b9d910a2a5 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -7,7 +7,7 @@
# Parameters from paper
epop_params = {"g_AMPA_ext": 2.1,
- "g_AMPA_rec": 0.05,
+ "g_AMPA": 0.05,
"g_NMDA": 0.165,
"g_GABA": 1.3,
"tau_GABA": 5.0,
@@ -20,13 +20,14 @@
"E_ex": 0.0, # excitatory reversal potential
"E_in": -70.0, # inhibitory reversal potential
"V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
"C_m": 500.0, # membrane capacitance
"t_ref": 2.0 # refreactory period
}
ipop_params = {"g_AMPA_ext": 1.62,
- "g_AMPA_rec": 0.04,
+ "g_AMPA": 0.04,
"g_NMDA": 0.13,
"g_GABA": 1.0,
"tau_GABA": 5.0,
@@ -39,6 +40,7 @@
"E_ex": 0.0, # excitatory reversal potential
"E_in": -70.0, # inhibitory reversal potential
"V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
"C_m": 200.0, # membrane capacitance
"t_ref": 1.0 # refreactory period
}
@@ -91,11 +93,25 @@
poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
+syn_spec_selective = {"synapse_model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
+syn_spec_nonselective = {"synapse_model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
+syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1., "delay":0.1, 'receptor_type': 0}
+syn_spec_ext = {"synapse_model": "static_synapse", "weight":1., "delay":0.1, 'receptor_type': 1}
-syn_spec_selective = {"model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
-syn_spec_nonselective = {"model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
-syn_spec_inhibitory = {"model": "static_synapse", "weight":-1., "delay":0.1, 'receptor_type': 0}
-syn_spec_bg = {"model": "static_synapse", "weight":1., "delay":0.1, 'receptor_type': 1}
+sr_nonselective = nest.Create("spike_recorder")
+sr_selective1 = nest.Create("spike_recorder")
+sr_selective2 = nest.Create("spike_recorder")
+sr_inhibitory = nest.Create("spike_recorder")
+
+mm_selective1 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_AMPA_ext", "s_GABA"]})
+mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+
+
+
+nest.Connect(poisson_0,
+ nonselective_pop + selective_pop1 + selective_pop2 + inhibitory_pop,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_ext)
nest.Connect(nonselective_pop,
selective_pop1 + selective_pop2 + nonselective_pop,
@@ -117,12 +133,38 @@
conn_spec="all_to_all",
syn_spec=syn_spec_inhibitory)
-nest.Simulate(4000.)
+nest.Connect(poisson_a,
+ selective_pop1,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_ext)
+nest.Connect(poisson_b,
+ selective_pop2,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_ext)
+nest.Connect(nonselective_pop, sr_nonselective)
+nest.Connect(selective_pop1, sr_selective1)
+nest.Connect(selective_pop2, sr_selective2)
+nest.Connect(inhibitory_pop, sr_inhibitory)
+nest.Connect(mm_selective1, selective_pop1)
+nest.Connect(mm_inhibitory, inhibitory_pop)
+nest.Simulate(4000.)
+spikes_nonselective = sr_nonselective.get("events", "times")
+spikes_selective1 = sr_selective1.get("events", "times")
+spikes_selective2 = sr_selective2.get("events", "times")
+spikes_inhibitory = sr_inhibitory.get("events", "times")
+
+senders = mm_selective1.get("events", "senders")
+inds = senders == 1
+vm = mm_selective1.get("events", "V_m")[inds]
+s_AMPA = mm_selective1.get("events", "s_AMPA")[inds]
+s_AMPA_ext = mm_selective1.get("events", "s_AMPA_ext")[inds]
+s_GABA = mm_selective1.get("events", "s_GABA")[inds]
+s_NMDA = mm_selective1.get("events", "s_NMDA")[inds]
From 8614377ea1acc5839772a964ba889948691ebcd1 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 15 Nov 2023 13:04:30 +0100
Subject: [PATCH 019/184] script works, but something wrong with dynamics
---
pynest/examples/wang_decision_making.py | 39 ++++++++++++++++---------
1 file changed, 25 insertions(+), 14 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index b9d910a2a5..b1b1d69969 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -53,7 +53,6 @@
w_plus = 1.7
w_minus = 1 - f * (w_plus - 1) / (1 - f)
-
NE = 1600
NI = 400
@@ -65,7 +64,7 @@
mu_0 = 40.
rho_a = mu_0 / 100
rho_b = rho_a
-c = 20.
+c = 80.
sigma = 4.
mu_a = mu_0 + rho_a * c
mu_b = mu_0 - rho_b * c
@@ -74,7 +73,7 @@
update_times = np.arange(0, signal_duration, signal_update_interval)
update_times[0] = 0.1
rates_a = np.random.normal(mu_a, sigma, size=num_updates)
-rates_b = np.random.normal(mu_a, sigma, size=num_updates)
+rates_b = np.random.normal(mu_b, sigma, size=num_updates)
poisson_a = nest.Create("inhomogeneous_poisson_generator",
params={"origin": signal_start-0.1,
@@ -90,12 +89,12 @@
"rate_times": update_times,
"rate_values": rates_b})
-
poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
-syn_spec_selective = {"synapse_model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
-syn_spec_nonselective = {"synapse_model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
-syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1., "delay":0.1, 'receptor_type': 0}
+syn_spec_pot = {"synapse_model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
+syn_spec_default = {"synapse_model": "static_synapse", "weight":1.0, "delay":0.1, 'receptor_type': 0}
+syn_spec_dep = {"synapse_model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
+syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1.0, "delay":0.1, 'receptor_type': 0}
syn_spec_ext = {"synapse_model": "static_synapse", "weight":1., "delay":0.1, 'receptor_type': 1}
sr_nonselective = nest.Create("spike_recorder")
@@ -107,29 +106,38 @@
mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-
nest.Connect(poisson_0,
nonselective_pop + selective_pop1 + selective_pop2 + inhibitory_pop,
conn_spec="all_to_all",
syn_spec=syn_spec_ext)
nest.Connect(nonselective_pop,
- selective_pop1 + selective_pop2 + nonselective_pop,
+ selective_pop1 + selective_pop2,
conn_spec="all_to_all",
- syn_spec=syn_spec_nonselective)
+ syn_spec=syn_spec_dep)
+
+nest.Connect(nonselective_pop,
+ nonselective_pop + inhibitory_pop,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_default)
nest.Connect(selective_pop1,
selective_pop2,
conn_spec="all_to_all",
- syn_spec=syn_spec_selective)
+ syn_spec=syn_spec_dep)
nest.Connect(selective_pop2,
selective_pop1,
conn_spec="all_to_all",
- syn_spec=syn_spec_selective)
+ syn_spec=syn_spec_dep)
+
+nest.Connect(selective_pop1 + selective_pop2,
+ nonselective_pop + inhibitory_pop,
+ conn_spec="all_to_all",
+ syn_spec=syn_spec_default)
nest.Connect(inhibitory_pop,
- selective_pop1 + selective_pop2 + nonselective_pop,
+ selective_pop1 + selective_pop2 + nonselective_pop + inhibitory_pop,
conn_spec="all_to_all",
syn_spec=syn_spec_inhibitory)
@@ -143,7 +151,6 @@
conn_spec="all_to_all",
syn_spec=syn_spec_ext)
-
nest.Connect(nonselective_pop, sr_nonselective)
nest.Connect(selective_pop1, sr_selective1)
nest.Connect(selective_pop2, sr_selective2)
@@ -160,6 +167,10 @@
spikes_selective2 = sr_selective2.get("events", "times")
spikes_inhibitory = sr_inhibitory.get("events", "times")
+
+
+
+
senders = mm_selective1.get("events", "senders")
inds = senders == 1
vm = mm_selective1.get("events", "V_m")[inds]
From 2138a99584282381660cdb37c0fe5c47d53de6fb Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 17 Nov 2023 15:56:54 +0100
Subject: [PATCH 020/184] bughunting, s_NMDA explodes
---
models/iaf_wang_2002.cpp | 54 ++++++++++++++++++++--------------------
models/iaf_wang_2002.h | 48 ++++++++---------------------------
2 files changed, 38 insertions(+), 64 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 603d5c3838..a99ff4d33d 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -42,6 +42,9 @@
#include "integerdatum.h"
#include "lockptrdatum.h"
+// Includes from standard library
+#include
+
/* ---------------------------------------------------------------------------
* Recordables map
* --------------------------------------------------------------------------- */
@@ -65,7 +68,6 @@ RecordablesMap< iaf_wang_2002 >::create()
// add state variables to recordables map
insert_( names::V_m, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::V_m > );
insert_( names::s_AMPA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_AMPA > );
- insert_( names::s_AMPA_ext, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_AMPA_ext > );
insert_( names::s_GABA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_GABA > );
insert_( names::s_NMDA, &iaf_wang_2002::get_ode_state_elem_< iaf_wang_2002::State_::s_NMDA > );
}
@@ -84,8 +86,7 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
// y[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.y[].
- const double I_AMPA = node.P_.g_AMPA * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ]
- + node.P_.g_AMPA_ext * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA_ext ];
+ const double I_AMPA = node.P_.g_AMPA * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ];
const double I_rec_GABA = node.P_.g_GABA * ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
@@ -97,7 +98,6 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
f[ S::V_m ] = ( -node.P_.g_L * ( y[ S::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
f[ S::s_AMPA ] = -y[ S::s_AMPA ] / node.P_.tau_AMPA;
- f[ S::s_AMPA_ext ] = -y[ S::s_AMPA_ext ] / node.P_.tau_AMPA;
f[ S::s_NMDA ] = -y[ S::s_NMDA ] / node.P_.tau_decay_NMDA;
f[ S::s_GABA ] = -y[ S::s_GABA ] / node.P_.tau_GABA;
@@ -105,7 +105,6 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
}
-
/* ---------------------------------------------------------------------------
* Default constructors defining default parameters and state
* --------------------------------------------------------------------------- */
@@ -121,7 +120,6 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, g_GABA ( 1.3 ) //
, g_NMDA ( 0.165 ) //
, g_AMPA ( 0.05 ) //
- , g_AMPA_ext ( 1.3 ) //
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
@@ -137,7 +135,6 @@ nest::iaf_wang_2002::State_::State_( const Parameters_& p )
{
y_[ V_m ] = p.E_L; // initialize to reversal potential
y_[ s_AMPA ] = 0.0;
- y_[ s_AMPA_ext ] = 0.0;
y_[ s_GABA ] = 0.0;
y_[ s_NMDA ] = 0.0;
}
@@ -147,7 +144,6 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
{
y_[ V_m ] = s.y_[ V_m ];
y_[ s_AMPA ] = s.y_[ s_AMPA ];
- y_[ s_AMPA_ext ] = s.y_[ s_AMPA_ext ];
y_[ s_GABA ] = s.y_[ s_GABA ];
y_[ s_NMDA ] = s.y_[ s_NMDA ];
}
@@ -155,7 +151,6 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
: logger_( n )
, spike_AMPA()
- , spike_AMPA_ext()
, spike_GABA()
, spike_NMDA()
, s_( nullptr )
@@ -193,7 +188,6 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::g_GABA, g_GABA );
def< double >( d, names::g_NMDA, g_NMDA );
def< double >( d, names::g_AMPA, g_AMPA );
- def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
def< double >( d, names::t_ref, t_ref );
def< double >( d, names::tau_AMPA, tau_AMPA );
def< double >( d, names::tau_GABA, tau_GABA );
@@ -217,7 +211,6 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::g_GABA, g_GABA, node );
updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
- updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
@@ -261,7 +254,6 @@ nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
{
def< double >( d, names::V_m, y_[ V_m ] ); // Membrane potential
def< double >( d, names::s_AMPA, y_[ s_AMPA ] );
- def< double >( d, names::s_AMPA_ext, y_[ s_AMPA_ext ] );
def< double >( d, names::s_GABA, y_[ s_GABA ] );
def< double >( d, names::s_NMDA, y_[ s_NMDA] );
}
@@ -271,7 +263,6 @@ nest::iaf_wang_2002::State_::set( const DictionaryDatum& d, const Parameters_&,
{
updateValueParam< double >( d, names::V_m, y_[ V_m ], node );
updateValueParam< double >( d, names::s_AMPA, y_[ s_AMPA ], node );
- updateValueParam< double >( d, names::s_AMPA_ext, y_[ s_AMPA_ext ], node );
updateValueParam< double >( d, names::s_GABA, y_[ s_GABA ], node );
updateValueParam< double >( d, names::s_NMDA, y_[ s_NMDA ], node );
}
@@ -340,7 +331,6 @@ void
nest::iaf_wang_2002::init_buffers_()
{
B_.spike_AMPA.clear();
- B_.spike_AMPA_ext.clear();
B_.spike_GABA.clear();
B_.spike_NMDA.clear();
B_.currents_.clear(); // includes resize
@@ -414,10 +404,12 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
{
std::vector< double > s_vals( kernel().connection_manager.get_min_delay(), 0.0 );
+ std::cout << "INSIDE UPDATE" << std::endl;
for ( long lag = from; lag < to; ++lag )
{
double t = 0.0;
-
+
+ std::cout << "INSIDE INNER UPDATE" << std::endl;
// numerical integration with adaptive step size control:
// ------------------------------------------------------
// gsl_odeiv_evolve_apply performs only a single numerical
@@ -433,6 +425,11 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
while ( t < B_.step_ )
{
+ std::cout << "INSIDE GSL LOOP" << std::endl;
+ std::cout << "V_m: " << S_.y_[ 0 ] << std::endl;
+ std::cout << "s_AMPA: " << S_.y_[ State_::s_AMPA ] << std::endl;
+ std::cout << "s_NMDA: " << S_.y_[ State_::s_NMDA ] << std::endl;
+ std::cout << "s_GABA: " << S_.y_[ State_::s_GABA ] << std::endl;
const int status = gsl_odeiv_evolve_apply( B_.e_,
B_.c_,
B_.s_,
@@ -448,24 +445,22 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
}
-
+ std::cout << "GSL STEP RUN" << std::endl;
// add incoming spikes
S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
- S_.y_[ State_::s_AMPA_ext ] += B_.spike_AMPA_ext.get_value( lag );
S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
S_.y_[ State_::s_NMDA ] += B_.spike_NMDA.get_value( lag );
- if ( S_.y_[ State_::s_NMDA ] > 1 )
- {
- S_.y_[ State_::s_NMDA ] = 1;
- }
+ S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
if ( S_.r_ )
{
+ std::cout << "REFRACTORY" << std::endl;
// neuron is absolute refractory
--S_.r_;
S_.y_[ State_::V_m ] = P_.V_reset; // clamp potential
}
else if ( S_.y_[ State_::V_m ] >= P_.V_th )
{
+ std::cout << "SPIKING" << std::endl;
// neuron is not absolute refractory
S_.r_ = V_.RefractoryCounts_;
S_.y_[ State_::V_m ] = P_.V_reset;
@@ -482,17 +477,23 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
S_.s_NMDA_pre += s_NMDA_delta;
-
+
+ std::cout << "CREATING SPIKEEVENT" << std::endl;
SpikeEvent se;
se.set_offset( s_NMDA_delta );
kernel().event_delivery_manager.send( *this, se, lag );
+ std::cout << "FINISHED SPIKEEVENT" << std::endl;
}
+
// set new input current
B_.I_stim_ = B_.currents_.get_value( lag );
+ std::cout << "LOGGING DATA" << std::endl;
// voltage logging
B_.logger_.record_data( origin.get_steps() + lag );
+ std::cout << "FINISHED UPDATE" << std::endl;
+ std::cout << "" << std::endl;
}
}
@@ -508,25 +509,24 @@ void
nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
+ std::cout << "INSIDE HANDLE SPIKEEVENT" << std::endl;
if ( e.get_weight() > 0.0 )
{
- if ( e.get_rport() == 0 ) { // recurrent spike
B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() );
+
+ if ( e.get_offset() != 0.0 ) {
B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() * e.get_offset() );
}
- else if ( e.get_rport() == 1 ) { // external spike
- B_.spike_AMPA_ext.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- e.get_weight() * e.get_multiplicity() );
- }
}
else
{
B_.spike_GABA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
-e.get_weight() * e.get_multiplicity() );
}
+ std::cout << "EXITING HANDLE SPIKEEVENT" << std::endl;
}
void
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index e24d6f26ec..a02626c9c1 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -67,16 +67,18 @@ Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
Description
+++++++++++
-This model implements a version of the neuron model described in [1]_.
+This model implements a simplified version of the neuron model described in [1]_.
-It contains AMPA, GABA and NMDA synapses, where the number of NMDA ports are dependent
-on the number of presynaptic connections.
+It contains AMPA, GABA and NMDA synapses
-The AMPA and GABA synapses are given as alpha functions, while the NMDA synapse is modeled
-with a non-linear function described by
+The equations for the synaptic currents from AMPA and GABA receptors are given by
+the following equations
.. math::
- \frac{ dg_j^{NMDA}(t) }{ dt } = - \frac{ g_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
+ I_{\mathrm{AMPA}} = g_{\mathrm{AMPA}}(V(t) - E_{\mathrm{ex}} \sum_{j=1}^{C_E} w_j s_j^{\mathrm{AMPA}}
+
+.. math::
+ \frac{ ds_j^{NMDA}(t) }{ dt } = - \frac{ s_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
\frac{ dx_j(t) }{ dt } =- \frac{ x_j(t) }{ \tau_{NMDA,rise} } + \sum_k \delta(t - t_j^k).
The synaptic current of NMDA is given by
@@ -100,7 +102,6 @@ The following parameters can be set in the status dictionary.
V_reset mV Reset potential
C_m pF Membrane capacitance
g_L nS Leak conductance
- g_AMPA_ext nS Peak external AMPA conductance
g_AMPA nS Peak recurrent AMPA conductance
g_NMDA nS Peak recurrent NMDA conductance
g_GABA nS Peak recurrent GABA conductance
@@ -122,7 +123,6 @@ The following values can be recorded.
=========== ===========================================================
V_m Membrane potential
s_AMPA AMPA gate
- s_AMPA_ext external AMPA gate
s_GABA GABA gate
NMDA_sum sum of NMDA over all presynaptic neurons j
=========== ===========================================================
@@ -185,7 +185,6 @@ class iaf_wang_2002 : public ArchivingNode
// void sends_secondary_event( DelayedRateConnectionEvent& ) override;
void handle( SpikeEvent& ) override; //!< accept spikes
-// void handle( DelayedRateConnectionEvent& ) override; //!< accept spikes
void handle( CurrentEvent& ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
@@ -208,17 +207,6 @@ class iaf_wang_2002 : public ArchivingNode
}
private:
- /**
- * Synapse types to connect to
- */
- enum SynapseTypes
- {
- INF_SPIKE_RECEPTOR = 0,
- AMPA_NMDA,
- GABA,
- SUP_SPIKE_RECEPTOR
- };
-
void init_state_() override;
void pre_run_hook() override;
void init_buffers_() override;
@@ -247,7 +235,6 @@ class iaf_wang_2002 : public ArchivingNode
double g_GABA; //!< Peak conductance GABA
double g_NMDA; //!< Peak conductance NMDA
double g_AMPA; //!< Peak conductance AMPA
- double g_AMPA_ext; //!< Peak conductance external AMPA
double t_ref; //!< Refractory period in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
@@ -284,19 +271,18 @@ class iaf_wang_2002 : public ArchivingNode
{
V_m = 0,
s_AMPA,
- s_AMPA_ext,
s_NMDA,
s_GABA,
STATE_VEC_SIZE
};
double y_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
+ double s_NMDA_pre; // ADD INITIALIZATION
int r_; //!< number of refractory steps remaining
State_( const Parameters_& ); //!< Default initialization
State_( const State_& );
- double s_NMDA_pre = 0;
void get( DictionaryDatum& ) const;
void set( const DictionaryDatum&, const Parameters_&, Node* );
@@ -325,7 +311,6 @@ class iaf_wang_2002 : public ArchivingNode
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
RingBuffer spike_AMPA;
- RingBuffer spike_AMPA_ext;
RingBuffer spike_GABA;
RingBuffer spike_NMDA;
RingBuffer currents_;
@@ -406,18 +391,11 @@ iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bo
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( receptor_type == 0 )
- {
- return 0;
- }
- else if ( receptor_type == 1 )
- {
- return 1;
- }
- else
+ if ( receptor_type != 0 )
{
throw UnknownReceptorType( receptor_type, get_name() );
}
+ return 0;
}
inline size_t
@@ -455,10 +433,6 @@ iaf_wang_2002::get_status( DictionaryDatum& d ) const
S_.get( d );
ArchivingNode::get_status( d );
- DictionaryDatum receptor_type = new Dictionary();
-
-// ( *d )[ names::receptor_types ] = receptor_type;
-
( *d )[ names::recordables ] = recordablesMap_.get_list();
}
From ffb55c471212c2c8bfc5cc0e53526939a4a20846 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 17 Nov 2023 17:24:23 +0100
Subject: [PATCH 021/184] fixed bug, uninitialized variable
---
models/iaf_wang_2002.cpp | 21 +++------------------
models/iaf_wang_2002.h | 4 +++-
2 files changed, 6 insertions(+), 19 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index a99ff4d33d..59e14bc15f 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -137,6 +137,7 @@ nest::iaf_wang_2002::State_::State_( const Parameters_& p )
y_[ s_AMPA ] = 0.0;
y_[ s_GABA ] = 0.0;
y_[ s_NMDA ] = 0.0;
+ s_NMDA_pre = 0.0;
}
nest::iaf_wang_2002::State_::State_( const State_& s )
@@ -146,6 +147,7 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
y_[ s_AMPA ] = s.y_[ s_AMPA ];
y_[ s_GABA ] = s.y_[ s_GABA ];
y_[ s_NMDA ] = s.y_[ s_NMDA ];
+ s_NMDA_pre = s.s_NMDA_pre;
}
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
@@ -404,12 +406,10 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
{
std::vector< double > s_vals( kernel().connection_manager.get_min_delay(), 0.0 );
- std::cout << "INSIDE UPDATE" << std::endl;
for ( long lag = from; lag < to; ++lag )
{
double t = 0.0;
- std::cout << "INSIDE INNER UPDATE" << std::endl;
// numerical integration with adaptive step size control:
// ------------------------------------------------------
// gsl_odeiv_evolve_apply performs only a single numerical
@@ -425,11 +425,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
while ( t < B_.step_ )
{
- std::cout << "INSIDE GSL LOOP" << std::endl;
- std::cout << "V_m: " << S_.y_[ 0 ] << std::endl;
- std::cout << "s_AMPA: " << S_.y_[ State_::s_AMPA ] << std::endl;
- std::cout << "s_NMDA: " << S_.y_[ State_::s_NMDA ] << std::endl;
- std::cout << "s_GABA: " << S_.y_[ State_::s_GABA ] << std::endl;
const int status = gsl_odeiv_evolve_apply( B_.e_,
B_.c_,
B_.s_,
@@ -445,7 +440,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
}
- std::cout << "GSL STEP RUN" << std::endl;
// add incoming spikes
S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
@@ -453,14 +447,12 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
if ( S_.r_ )
{
- std::cout << "REFRACTORY" << std::endl;
// neuron is absolute refractory
--S_.r_;
S_.y_[ State_::V_m ] = P_.V_reset; // clamp potential
}
else if ( S_.y_[ State_::V_m ] >= P_.V_th )
{
- std::cout << "SPIKING" << std::endl;
// neuron is not absolute refractory
S_.r_ = V_.RefractoryCounts_;
S_.y_[ State_::V_m ] = P_.V_reset;
@@ -476,24 +468,19 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
- S_.s_NMDA_pre += s_NMDA_delta;
+ S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
- std::cout << "CREATING SPIKEEVENT" << std::endl;
SpikeEvent se;
se.set_offset( s_NMDA_delta );
kernel().event_delivery_manager.send( *this, se, lag );
- std::cout << "FINISHED SPIKEEVENT" << std::endl;
}
// set new input current
B_.I_stim_ = B_.currents_.get_value( lag );
- std::cout << "LOGGING DATA" << std::endl;
// voltage logging
B_.logger_.record_data( origin.get_steps() + lag );
- std::cout << "FINISHED UPDATE" << std::endl;
- std::cout << "" << std::endl;
}
}
@@ -509,7 +496,6 @@ void
nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
- std::cout << "INSIDE HANDLE SPIKEEVENT" << std::endl;
if ( e.get_weight() > 0.0 )
{
@@ -526,7 +512,6 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
B_.spike_GABA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
-e.get_weight() * e.get_multiplicity() );
}
- std::cout << "EXITING HANDLE SPIKEEVENT" << std::endl;
}
void
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index a02626c9c1..bcca315b15 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -76,6 +76,7 @@ the following equations
.. math::
I_{\mathrm{AMPA}} = g_{\mathrm{AMPA}}(V(t) - E_{\mathrm{ex}} \sum_{j=1}^{C_E} w_j s_j^{\mathrm{AMPA}}
+ \frac{d}{dt}s^{\mathrm{AMPA}}_j = -\frac{s_j}{\tau_{\mathrm{AMPA}}}
.. math::
\frac{ ds_j^{NMDA}(t) }{ dt } = - \frac{ s_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
@@ -277,7 +278,8 @@ class iaf_wang_2002 : public ArchivingNode
};
double y_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
- double s_NMDA_pre; // ADD INITIALIZATION
+ double s_NMDA_pre; // for determining (unweighted) alpha * (1 - s_NMDA) term on
+ // pre-synaptic side
int r_; //!< number of refractory steps remaining
State_( const Parameters_& ); //!< Default initialization
From 422c1c979257577f425b228f1e3278a70c8cb13a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 17 Nov 2023 18:10:48 +0100
Subject: [PATCH 022/184] reintroduce receptor type
---
models/iaf_wang_2002.cpp | 14 +++++++++++++-
models/iaf_wang_2002.h | 12 ++++++++++--
2 files changed, 23 insertions(+), 3 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 59e14bc15f..1438ceb06b 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -120,6 +120,7 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, g_GABA ( 1.3 ) //
, g_NMDA ( 0.165 ) //
, g_AMPA ( 0.05 ) //
+ , g_AMPA_ext ( 0.05 ) //
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
@@ -190,6 +191,7 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::g_GABA, g_GABA );
def< double >( d, names::g_NMDA, g_NMDA );
def< double >( d, names::g_AMPA, g_AMPA );
+ def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
def< double >( d, names::t_ref, t_ref );
def< double >( d, names::tau_AMPA, tau_AMPA );
def< double >( d, names::tau_GABA, tau_GABA );
@@ -213,6 +215,7 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::g_GABA, g_GABA, node );
updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
+ updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
@@ -499,9 +502,18 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
if ( e.get_weight() > 0.0 )
{
+ if ( e.get_rport() == 0 ) {
B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() );
-
+ }
+ // if from external population, ignore weight
+ // when computing the actual synaptic current, this contribution will be multiplied by
+ // g_AMPA. therefore we multiply by g_AMPA_ext / g_AMPA here, and the g_AMPA denominator
+ // will be cancelled
+ else if ( e.get_rport() == 1 ) {
+ B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ P_.g_AMPA_ext / P_.g_AMPA * e.get_multiplicity() );
+ }
if ( e.get_offset() != 0.0 ) {
B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() * e.get_offset() );
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index bcca315b15..ec61029256 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -236,6 +236,7 @@ class iaf_wang_2002 : public ArchivingNode
double g_GABA; //!< Peak conductance GABA
double g_NMDA; //!< Peak conductance NMDA
double g_AMPA; //!< Peak conductance AMPA
+ double g_AMPA_ext; //!< Peak conductance AMPA
double t_ref; //!< Refractory period in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
@@ -393,11 +394,18 @@ iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bo
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( receptor_type != 0 )
+ if ( receptor_type == 0 )
+ {
+ return 0;
+ }
+ else if ( receptor_type == 1 )
+ {
+ return 1;
+ }
+ else
{
throw UnknownReceptorType( receptor_type, get_name() );
}
- return 0;
}
inline size_t
From 7e6ec793bb6836211ac5927c6e752247b5732657 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 17 Nov 2023 18:18:48 +0100
Subject: [PATCH 023/184] various fixes from review
---
models/iaf_wang_2002.cpp | 28 ++++++++++++++--------------
models/iaf_wang_2002.h | 7 +++----
2 files changed, 17 insertions(+), 18 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 1438ceb06b..e79ffd9285 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -153,9 +153,9 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
: logger_( n )
- , spike_AMPA()
- , spike_GABA()
- , spike_NMDA()
+ , spike_AMPA_()
+ , spike_GABA_()
+ , spike_NMDA_()
, s_( nullptr )
, c_( nullptr )
, e_( nullptr )
@@ -335,9 +335,9 @@ nest::iaf_wang_2002::init_state_()
void
nest::iaf_wang_2002::init_buffers_()
{
- B_.spike_AMPA.clear();
- B_.spike_GABA.clear();
- B_.spike_NMDA.clear();
+ B_.spike_AMPA_.clear();
+ B_.spike_GABA_.clear();
+ B_.spike_NMDA_.clear();
B_.currents_.clear(); // includes resize
B_.logger_.reset(); // includes resize
@@ -444,9 +444,9 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
// add incoming spikes
- S_.y_[ State_::s_AMPA ] += B_.spike_AMPA.get_value( lag );
- S_.y_[ State_::s_GABA ] += B_.spike_GABA.get_value( lag );
- S_.y_[ State_::s_NMDA ] += B_.spike_NMDA.get_value( lag );
+ S_.y_[ State_::s_AMPA ] += B_.spike_AMPA_.get_value( lag );
+ S_.y_[ State_::s_GABA ] += B_.spike_GABA_.get_value( lag );
+ S_.y_[ State_::s_NMDA ] += B_.spike_NMDA_.get_value( lag );
S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
if ( S_.r_ )
{
@@ -470,7 +470,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
- double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
+ const double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
SpikeEvent se;
@@ -503,7 +503,7 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
if ( e.get_weight() > 0.0 )
{
if ( e.get_rport() == 0 ) {
- B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ B_.spike_AMPA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() );
}
// if from external population, ignore weight
@@ -511,17 +511,17 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
// g_AMPA. therefore we multiply by g_AMPA_ext / g_AMPA here, and the g_AMPA denominator
// will be cancelled
else if ( e.get_rport() == 1 ) {
- B_.spike_AMPA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ B_.spike_AMPA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
P_.g_AMPA_ext / P_.g_AMPA * e.get_multiplicity() );
}
if ( e.get_offset() != 0.0 ) {
- B_.spike_NMDA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ B_.spike_NMDA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
e.get_weight() * e.get_multiplicity() * e.get_offset() );
}
}
else
{
- B_.spike_GABA.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
+ B_.spike_GABA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
-e.get_weight() * e.get_multiplicity() );
}
}
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index ec61029256..388c408992 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -177,7 +177,6 @@ class iaf_wang_2002 : public ArchivingNode
*/
using Node::handles_test_event;
-// using Node::sends_secondary_event;
using Node::handle;
//! Used to validate that we can send SpikeEvent to desired target:port.
@@ -313,9 +312,9 @@ class iaf_wang_2002 : public ArchivingNode
// -----------------------------------------------------------------------
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
- RingBuffer spike_AMPA;
- RingBuffer spike_GABA;
- RingBuffer spike_NMDA;
+ RingBuffer spike_AMPA_;
+ RingBuffer spike_GABA_;
+ RingBuffer spike_NMDA_;
RingBuffer currents_;
// -----------------------------------------------------------------------
From 917187383efdd3a9b1a5bc97dd21ad53247a66fb Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 17 Nov 2023 18:20:17 +0100
Subject: [PATCH 024/184] update examples so that they run with receptor types
again
---
pynest/examples/wang_decision_making.py | 65 ++++++++++++++++---------
pynest/examples/wang_neuron.py | 8 +--
2 files changed, 47 insertions(+), 26 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index b1b1d69969..790f5c99ef 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -1,13 +1,18 @@
import nest
import matplotlib.pyplot as plt
+from matplotlib.gridspec import GridSpec
import numpy as np
np.random.seed(123)
rng = np.random.default_rng()
+dt = 0.1
+nest.set(resolution=dt, print_time=True)
+
+
# Parameters from paper
-epop_params = {"g_AMPA_ext": 2.1,
- "g_AMPA": 0.05,
+epop_params = {"g_AMPA": 0.05,
+ "g_AMPA_ext": 2.1,
"g_NMDA": 0.165,
"g_GABA": 1.3,
"tau_GABA": 5.0,
@@ -26,8 +31,8 @@
}
-ipop_params = {"g_AMPA_ext": 1.62,
- "g_AMPA": 0.04,
+ipop_params = {"g_AMPA": 0.04,
+ "g_AMPA_ext": 1.62,
"g_NMDA": 0.13,
"g_GABA": 1.0,
"tau_GABA": 5.0,
@@ -52,6 +57,8 @@
f = 0.15 # proportion of neurons receiving signal inputs
w_plus = 1.7
w_minus = 1 - f * (w_plus - 1) / (1 - f)
+delay = 0.1
+
NE = 1600
NI = 400
@@ -64,7 +71,7 @@
mu_0 = 40.
rho_a = mu_0 / 100
rho_b = rho_a
-c = 80.
+c = 0.
sigma = 4.
mu_a = mu_0 + rho_a * c
mu_b = mu_0 - rho_b * c
@@ -91,21 +98,22 @@
poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
-syn_spec_pot = {"synapse_model": "static_synapse", "weight":w_plus, "delay":0.1, 'receptor_type': 0}
-syn_spec_default = {"synapse_model": "static_synapse", "weight":1.0, "delay":0.1, 'receptor_type': 0}
-syn_spec_dep = {"synapse_model": "static_synapse", "weight":w_minus, "delay":0.1, 'receptor_type': 0}
-syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1.0, "delay":0.1, 'receptor_type': 0}
-syn_spec_ext = {"synapse_model": "static_synapse", "weight":1., "delay":0.1, 'receptor_type': 1}
+syn_spec_pot = {"synapse_model": "static_synapse", "weight":w_plus, "delay":delay, "receptor_type": 0}
+syn_spec_default = {"synapse_model": "static_synapse", "weight":1.0, "delay":delay, "receptor_type": 0}
+syn_spec_dep = {"synapse_model": "static_synapse", "weight":w_minus, "delay":delay, "receptor_type": 0}
+syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1.0, "delay":delay, "receptor_type": 0}
+syn_spec_ext = {"synapse_model": "static_synapse", "weight":1., "delay":0.1, "receptor_type": 1}
sr_nonselective = nest.Create("spike_recorder")
sr_selective1 = nest.Create("spike_recorder")
sr_selective2 = nest.Create("spike_recorder")
sr_inhibitory = nest.Create("spike_recorder")
-mm_selective1 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_AMPA_ext", "s_GABA"]})
+mm_selective1 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+mm_selective2 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+mm_nonselective = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-
nest.Connect(poisson_0,
nonselective_pop + selective_pop1 + selective_pop2 + inhibitory_pop,
conn_spec="all_to_all",
@@ -156,9 +164,10 @@
nest.Connect(selective_pop2, sr_selective2)
nest.Connect(inhibitory_pop, sr_inhibitory)
-
-nest.Connect(mm_selective1, selective_pop1)
-nest.Connect(mm_inhibitory, inhibitory_pop)
+nest.Connect(mm_selective1, selective_pop1[0])
+nest.Connect(mm_selective2, selective_pop2[0])
+nest.Connect(mm_nonselective, nonselective_pop[0])
+nest.Connect(mm_inhibitory, inhibitory_pop[0])
nest.Simulate(4000.)
@@ -167,15 +176,27 @@
spikes_selective2 = sr_selective2.get("events", "times")
spikes_inhibitory = sr_inhibitory.get("events", "times")
+vm_selective1 = mm_selective1.get("events", "V_m")
+s_AMPA_selective1 = mm_selective1.get("events", "s_AMPA")
+s_GABA_selective1 = mm_selective1.get("events", "s_GABA")
+s_NMDA_selective1 = mm_selective1.get("events", "s_NMDA")
+
+vm_selective2 = mm_selective2.get("events", "V_m")
+s_AMPA_selective2 = mm_selective2.get("events", "s_AMPA")
+s_GABA_selective2 = mm_selective2.get("events", "s_GABA")
+s_NMDA_selective2 = mm_selective2.get("events", "s_NMDA")
+
+vm_inhibitory = mm_inhibitory.get("events", "V_m")
+s_AMPA_inhibitory = mm_inhibitory.get("events", "s_AMPA")
+s_GABA_inhibitory = mm_inhibitory.get("events", "s_GABA")
+s_NMDA_inhibitory = mm_inhibitory.get("events", "s_NMDA")
+gs = GridSpec(5,5)
+bins = np.arange(0, 4001, 5) - 0.001
+plt.hist(spikes_selective1, bins=bins, histtype="step")
+plt.hist(spikes_selective2, bins=bins, histtype="step")
+plt.show()
-senders = mm_selective1.get("events", "senders")
-inds = senders == 1
-vm = mm_selective1.get("events", "V_m")[inds]
-s_AMPA = mm_selective1.get("events", "s_AMPA")[inds]
-s_AMPA_ext = mm_selective1.get("events", "s_AMPA_ext")[inds]
-s_GABA = mm_selective1.get("events", "s_GABA")[inds]
-s_NMDA = mm_selective1.get("events", "s_NMDA")[inds]
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index 661bb155a1..ec4e3332fc 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -21,12 +21,12 @@
"tau_decay_NMDA": tau_NMDA,
"t_ref": 0.})
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_AMPA_ext", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_AMPA_ext", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 0}
+ "weight": w_ex}
+
in_syn_spec = {"synapse_model": "static_synapse",
"weight": w_in}
From 6a04fb1a95c2cdff4fbde48160b49cc260986431 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 22 Nov 2023 13:15:42 +0100
Subject: [PATCH 025/184] fix spike recorders
---
testsuite/pytests/test_iaf_wang_2002.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index 4b8ff93cfd..18b355b9d0 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -76,7 +76,7 @@ def test_wang():
"tau_decay_NMDA": tau_NMDA})
pg = nest.Create("poisson_generator", {"rate": 50.})
- sr = nest.Create("spike_recorder")
+ sr = nest.Create("spike_recorder", {"time_in_steps": True})
nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
"tau_GABA": tau_GABA,
"tau_decay_NMDA": tau_NMDA,
From 945975794b1d807c08e5d3817e9267282dc287c1 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Sun, 26 Nov 2023 19:22:43 +0100
Subject: [PATCH 026/184] exact model from Stine, wip
---
models/iaf_wang_2002_exact.cpp | 583 +++++++++++++++++++++++++++++++++
models/iaf_wang_2002_exact.h | 521 +++++++++++++++++++++++++++++
modelsets/full | 1 +
nestkernel/nest_names.cpp | 1 +
4 files changed, 1106 insertions(+)
create mode 100644 models/iaf_wang_2002_exact.cpp
create mode 100644 models/iaf_wang_2002_exact.h
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
new file mode 100644
index 0000000000..16fe341519
--- /dev/null
+++ b/models/iaf_wang_2002_exact.cpp
@@ -0,0 +1,583 @@
+/*
+ * iaf_wang_2002_exact.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_wang_2002_exact.h"
+
+#ifdef HAVE_GSL
+
+// Includes from libnestutil:
+#include "dictdatum.h"
+#include "dict_util.h"
+#include "numerics.h"
+
+// Includes from nestkernel:
+#include "exceptions.h"
+#include "kernel_manager.h"
+#include "nest_impl.h"
+#include "universal_data_logger_impl.h"
+
+// Includes from sli:
+#include "dict.h"
+#include "dictutils.h"
+#include "doubledatum.h"
+#include "integerdatum.h"
+#include "lockptrdatum.h"
+
+/* ---------------------------------------------------------------------------
+ * Recordables map
+ * --------------------------------------------------------------------------- */
+nest::RecordablesMap< nest::iaf_wang_2002_exact > nest::iaf_wang_2002_exact::recordablesMap_;
+
+namespace nest
+{
+void
+register_iaf_wang_2002_exact( const std::string& name )
+{
+ register_node_model< iaf_wang_2002_exact >( name );
+}
+/*
+ * Override the create() method with one call to RecordablesMap::insert_()
+ * for each quantity to be recorded.
+ */
+template <>
+void
+RecordablesMap< iaf_wang_2002_exact >::create()
+{
+ // add state variables to recordables map
+ insert_( names::V_m, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::V_m > );
+ insert_( names::g_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_AMPA > );
+ insert_( names::g_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_GABA > );
+ insert_( names::NMDA_sum, &iaf_wang_2002_exact::get_NMDA_sum_ );
+}
+}
+/* ---------------------------------------------------------------------------
+ * Default constructors defining default parameters and state
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002_exact::Parameters_::Parameters_()
+ : E_L( -70.0 ) // mV
+ , E_ex( 0.0 ) // mV
+ , E_in( -70.0 ) // mV
+ , V_th( -55.0 ) // mV
+ , V_reset( -60.0 ) // mV
+ , C_m( 500.0 ) // pF
+ , g_L( 25.0 ) // nS
+ , t_ref( 2.0 ) // ms
+ , tau_AMPA( 2.0 ) // ms
+ , tau_GABA( 5.0 ) // ms
+ , tau_rise_NMDA( 2.0 ) // ms
+ , tau_decay_NMDA( 100 ) // ms
+ , alpha( 0.5 ) // 1 / ms
+ , conc_Mg2( 1 ) // mM
+ , gsl_error_tol( 1e-3 )
+{
+}
+
+nest::iaf_wang_2002_exact::State_::State_( const Parameters_& p )
+ : state_vec_size( 0 )
+ , ode_state_( nullptr )
+ , num_ports_( 2 )
+ , r_( 0 )
+{
+ ode_state_ = new double[ G_NMDA_base ];
+ assert( ode_state_ );
+
+ ode_state_[ V_m ] = p.E_L; // initialize to reversal potential
+ ode_state_[ G_AMPA ] = 0.0;
+ ode_state_[ G_GABA ] = 0.0;
+
+ state_vec_size = G_NMDA_base;
+}
+
+nest::iaf_wang_2002_exact::State_::State_( const State_& s )
+ : state_vec_size( s.state_vec_size )
+ , ode_state_( nullptr )
+ , num_ports_( s.num_ports_ )
+ , r_( s.r_ )
+{
+ assert( s.num_ports_ == 2 );
+ assert( state_vec_size == G_NMDA_base );
+
+ ode_state_ = new double[ G_NMDA_base ];
+ assert( ode_state_ );
+
+ ode_state_[ V_m ] = s.ode_state_[ V_m ];
+ ode_state_[ G_AMPA ] = s.ode_state_[ G_AMPA ];
+ ode_state_[ G_GABA ] = s.ode_state_[ G_GABA ];
+}
+
+nest::iaf_wang_2002_exact::Buffers_::Buffers_( iaf_wang_2002_exact& n )
+ : logger_( n )
+ , spikes_()
+ , weights_()
+ , s_( nullptr )
+ , c_( nullptr )
+ , e_( nullptr )
+ , step_( Time::get_resolution().get_ms() )
+ , integration_step_( step_ )
+{
+ // Initialization of the remaining members is deferred to init_buffers_().
+}
+
+nest::iaf_wang_2002_exact::Buffers_::Buffers_( const Buffers_&, iaf_wang_2002_exact& n )
+ : logger_( n )
+ , spikes_()
+ , weights_()
+ , s_( nullptr )
+ , c_( nullptr )
+ , e_( nullptr )
+ , step_( Time::get_resolution().get_ms() )
+ , integration_step_( step_ )
+{
+ // Initialization of the remaining members is deferred to init_buffers_().
+}
+
+/* ---------------------------------------------------------------------------
+ * Parameter and state extractions and manipulation functions
+ * --------------------------------------------------------------------------- */
+
+void
+nest::iaf_wang_2002_exact::Parameters_::get( DictionaryDatum& d ) const
+{
+ def< double >( d, names::E_L, E_L );
+ def< double >( d, names::E_ex, E_ex );
+ def< double >( d, names::E_in, E_in );
+ def< double >( d, names::V_th, V_th );
+ def< double >( d, names::V_reset, V_reset );
+ def< double >( d, names::C_m, C_m );
+ def< double >( d, names::g_L, g_L );
+ def< double >( d, names::t_ref, t_ref );
+ def< double >( d, names::tau_AMPA, tau_AMPA );
+ def< double >( d, names::tau_GABA, tau_GABA );
+ def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
+ def< double >( d, names::tau_decay_NMDA, tau_decay_NMDA );
+ def< double >( d, names::alpha, alpha );
+ def< double >( d, names::conc_Mg2, conc_Mg2 );
+ def< double >( d, names::gsl_error_tol, gsl_error_tol );
+}
+
+void
+nest::iaf_wang_2002_exact::Parameters_::set( const DictionaryDatum& d, Node* node )
+{
+ // allow setting the membrane potential
+ updateValueParam< double >( d, names::V_th, V_th, node );
+ updateValueParam< double >( d, names::V_reset, V_reset, node );
+ updateValueParam< double >( d, names::t_ref, t_ref, node );
+ updateValueParam< double >( d, names::E_L, E_L, node );
+
+ updateValueParam< double >( d, names::E_ex, E_ex, node );
+ updateValueParam< double >( d, names::E_in, E_in, node );
+
+ updateValueParam< double >( d, names::C_m, C_m, node );
+ updateValueParam< double >( d, names::g_L, g_L, node );
+
+ updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
+ updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
+ updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
+ updateValueParam< double >( d, names::tau_decay_NMDA, tau_decay_NMDA, node );
+
+ updateValueParam< double >( d, names::alpha, alpha, node );
+ updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
+
+ updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
+
+ if ( V_reset >= V_th )
+ {
+ throw BadProperty( "Reset potential must be smaller than threshold." );
+ }
+ if ( C_m <= 0 )
+ {
+ throw BadProperty( "Capacitance must be strictly positive." );
+ }
+ if ( t_ref < 0 )
+ {
+ throw BadProperty( "Refractory time cannot be negative." );
+ }
+ if ( tau_AMPA <= 0 or tau_GABA <= 0 or tau_rise_NMDA <= 0 or tau_decay_NMDA <= 0 )
+ {
+ throw BadProperty( "All time constants must be strictly positive." );
+ }
+ if ( alpha <= 0 )
+ {
+ throw BadProperty( "alpha > 0 required." );
+ }
+ if ( conc_Mg2 <= 0 )
+ {
+ throw BadProperty( "Mg2 concentration must be strictly positive." );
+ }
+ if ( gsl_error_tol <= 0.0 )
+ {
+ throw BadProperty( "The gsl_error_tol must be strictly positive." );
+ }
+}
+
+void
+nest::iaf_wang_2002_exact::State_::get( DictionaryDatum& d ) const
+{
+ def< double >( d, names::V_m, ode_state_[ V_m ] ); // Membrane potential
+ def< double >( d, names::g_AMPA, ode_state_[ G_AMPA ] );
+ def< double >( d, names::g_GABA, ode_state_[ G_GABA ] );
+
+ // total NMDA sum
+ double NMDA_sum = get_NMDA_sum();
+ def< double >( d, names::NMDA_sum, NMDA_sum );
+}
+
+void
+nest::iaf_wang_2002_exact::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
+{
+ updateValueParam< double >( d, names::V_m, ode_state_[ V_m ], node );
+ updateValueParam< double >( d, names::g_AMPA, ode_state_[ G_AMPA ], node );
+ updateValueParam< double >( d, names::g_GABA, ode_state_[ G_GABA ], node );
+}
+
+/* ---------------------------------------------------------------------------
+ * Default constructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002_exact::iaf_wang_2002_exact()
+ : ArchivingNode()
+ , P_()
+ , S_( P_ )
+ , B_( *this )
+{
+ recordablesMap_.create();
+
+ calibrate();
+}
+
+/* ---------------------------------------------------------------------------
+ * Copy constructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002_exact::iaf_wang_2002_exact( const iaf_wang_2002_exact& n_ )
+ : ArchivingNode( n_ )
+ , P_( n_.P_ )
+ , S_( n_.S_ )
+ , B_( n_.B_, *this )
+{
+}
+
+/* ---------------------------------------------------------------------------
+ * Destructor for node
+ * --------------------------------------------------------------------------- */
+
+nest::iaf_wang_2002_exact::~iaf_wang_2002_exact()
+{
+ // GSL structs may not have been allocated, so we need to protect destruction
+
+ if ( B_.s_ )
+ {
+ gsl_odeiv_step_free( B_.s_ );
+ }
+
+ if ( B_.c_ )
+ {
+ gsl_odeiv_control_free( B_.c_ );
+ }
+
+ if ( B_.e_ )
+ {
+ gsl_odeiv_evolve_free( B_.e_ );
+ }
+
+ if ( S_.ode_state_ )
+ {
+ delete[] S_.ode_state_;
+ }
+}
+
+/* ---------------------------------------------------------------------------
+ * Node initialization functions
+ * --------------------------------------------------------------------------- */
+
+void
+nest::iaf_wang_2002_exact::init_state_()
+{
+ assert( S_.state_vec_size == State_::G_NMDA_base );
+
+ double* old_state = S_.ode_state_;
+ S_.state_vec_size = State_::G_NMDA_base + 2 * ( S_.num_ports_ - 2 );
+ S_.ode_state_ = new double[ S_.state_vec_size ];
+
+ assert( S_.ode_state_ );
+
+ S_.ode_state_[ State_::V_m ] = old_state[ State_::V_m ];
+ S_.ode_state_[ State_::G_AMPA ] = old_state[ State_::G_AMPA ];
+ S_.ode_state_[ State_::G_GABA ] = old_state[ State_::G_GABA ];
+
+ for ( size_t i = State_::G_NMDA_base; i < S_.state_vec_size; ++i )
+ {
+ S_.ode_state_[ i ] = 0.0;
+ }
+
+ delete[] old_state;
+}
+
+void
+nest::iaf_wang_2002_exact::init_buffers_()
+{
+ B_.spikes_.resize( S_.num_ports_ );
+
+ for ( auto& sb : B_.spikes_ )
+ {
+ sb.clear(); // includes resize
+ }
+
+ B_.currents_.clear(); // includes resize
+
+ B_.weights_.resize( S_.num_ports_ - NMDA + 1, 0.0 );
+
+ B_.logger_.reset(); // includes resize
+ ArchivingNode::clear_history();
+
+ if ( B_.s_ == nullptr )
+ {
+ B_.s_ = gsl_odeiv_step_alloc( gsl_odeiv_step_rkf45, S_.state_vec_size );
+ }
+ else
+ {
+ gsl_odeiv_step_reset( B_.s_ );
+ }
+
+ if ( B_.c_ == nullptr )
+ {
+ B_.c_ = gsl_odeiv_control_y_new( P_.gsl_error_tol, 0.0 );
+ }
+ else
+ {
+ gsl_odeiv_control_init( B_.c_, P_.gsl_error_tol, 0.0, 1.0, 0.0 );
+ }
+
+ if ( B_.e_ == nullptr )
+ {
+ B_.e_ = gsl_odeiv_evolve_alloc( S_.state_vec_size );
+ }
+ else
+ {
+ gsl_odeiv_evolve_reset( B_.e_ );
+ }
+
+ B_.sys_.function = iaf_wang_2002_exact_dynamics;
+ B_.sys_.jacobian = nullptr;
+ B_.sys_.dimension = S_.state_vec_size;
+ B_.sys_.params = reinterpret_cast< void* >( this );
+ B_.step_ = Time::get_resolution().get_ms();
+ B_.integration_step_ = Time::get_resolution().get_ms();
+
+ B_.I_stim_ = 0.0;
+}
+
+
+void
+nest::iaf_wang_2002_exact::pre_run_hook()
+{
+ // ensures initialization in case mm connected after Simulate
+ B_.logger_.init();
+
+ V_.RefractoryCounts = Time( Time::ms( P_.t_ref ) ).get_steps();
+ // since t_ref_ >= 0, this can only fail in error
+ assert( V_.RefractoryCounts >= 0 );
+}
+
+void
+nest::iaf_wang_2002_exact::calibrate()
+{
+ B_.logger_.init();
+
+ // internals V_
+ V_.RefractoryCounts = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
+}
+
+/* ---------------------------------------------------------------------------
+ * Update and spike handling functions
+ * --------------------------------------------------------------------------- */
+
+extern "C" inline int
+nest::iaf_wang_2002_exact_dynamics( double, const double ode_state[], double f[], void* pnode )
+{
+ // a shorthand
+ typedef nest::iaf_wang_2002_exact::State_ State_;
+
+ // get access to node so we can almost work as in a member function
+ assert( pnode );
+ const nest::iaf_wang_2002_exact& node = *( reinterpret_cast< nest::iaf_wang_2002_exact* >( pnode ) );
+
+ // ode_state[] here is---and must be---the state vector supplied by the integrator,
+ // not the state vector in the node, node.S_.ode_state[].
+
+ const double I_AMPA = ( ode_state[ State_::V_m ] - node.P_.E_ex ) * ode_state[ State_::G_AMPA ];
+
+ const double I_rec_GABA = ( ode_state[ State_::V_m ] - node.P_.E_in ) * ode_state[ State_::G_GABA ];
+
+ // The sum of NMDA_G
+ double total_NMDA = 0;
+ for ( size_t i = State_::G_NMDA_base + 1, w_idx = 0; i < node.S_.state_vec_size; i += 2, ++w_idx )
+ {
+ total_NMDA += ode_state[ i ] * node.B_.weights_.at( w_idx );
+ }
+
+ const double I_rec_NMDA = ( ode_state[ State_::V_m ] - node.P_.E_ex )
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * ode_state[ State_::V_m ] ) / 3.57 ) * total_NMDA;
+
+ const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA - node.B_.I_stim_;
+
+ f[ State_::V_m ] = ( -node.P_.g_L * ( ode_state[ State_::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
+
+ f[ State_::G_AMPA ] = -ode_state[ State_::G_AMPA ] / node.P_.tau_AMPA;
+ f[ State_::G_GABA ] = -ode_state[ State_::G_GABA ] / node.P_.tau_GABA;
+
+ for ( size_t i = State_::G_NMDA_base; i < node.S_.state_vec_size; i += 2 )
+ {
+ f[ i + 1 ] =
+ -ode_state[ i + 1 ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ i ] * ( 1 - ode_state[ i + 1 ] );
+ f[ i ] = -ode_state[ i ] / node.P_.tau_rise_NMDA;
+ }
+
+ return GSL_SUCCESS;
+}
+
+void
+nest::iaf_wang_2002_exact::update( Time const& origin, const long from, const long to )
+{
+ for ( long lag = from; lag < to; ++lag )
+ {
+ double t = 0.0;
+
+ // numerical integration with adaptive step size control:
+ // ------------------------------------------------------
+ // gsl_odeiv_evolve_apply performs only a single numerical
+ // integration step, starting from t and bounded by step;
+ // the while-loop ensures integration over the whole simulation
+ // step (0, step] if more than one integration step is needed due
+ // to a small integration step size;
+ // note that (t+IntegrationStep > step) leads to integration over
+ // (t, step] and afterwards setting t to step, but it does not
+ // enforce setting IntegrationStep to step-t; this is of advantage
+ // for a consistent and efficient integration across subsequent
+ // simulation intervals
+ while ( t < B_.step_ )
+ {
+ const int status = gsl_odeiv_evolve_apply( B_.e_,
+ B_.c_,
+ B_.s_,
+ &B_.sys_, // system of ODE
+ &t, // from t
+ B_.step_, // to t <= step
+ &B_.integration_step_, // integration step size
+ S_.ode_state_ ); // neuronal state
+
+ if ( status != GSL_SUCCESS )
+ {
+ throw GSLSolverFailure( get_name(), status );
+ }
+ }
+
+ // add incoming spikes
+ S_.ode_state_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.ode_state_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+
+ for ( size_t i = NMDA - 1; i < B_.spikes_.size(); ++i )
+ {
+ const size_t si = i - ( NMDA - 1 );
+
+ assert( si >= 0 );
+ assert( State_::G_NMDA_base + si * 2 < S_.state_vec_size );
+
+ S_.ode_state_[ State_::G_NMDA_base + si * 2 ] += B_.spikes_.at( i ).get_value( lag );
+ }
+
+ // absolute refractory period
+ if ( S_.r_ )
+ {
+ // neuron is absolute refractory
+ --S_.r_;
+ S_.ode_state_[ State_::V_m ] = P_.V_reset; // clamp potential
+ }
+ else if ( S_.ode_state_[ State_::V_m ] >= P_.V_th )
+ {
+ // neuron is not absolute refractory
+ S_.r_ = V_.RefractoryCounts;
+ S_.ode_state_[ State_::V_m ] = P_.V_reset;
+
+ // log spike with ArchivingNode
+ set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
+
+ SpikeEvent se;
+ kernel().event_delivery_manager.send( *this, se, lag );
+ }
+
+ // set new input current
+ B_.I_stim_ = B_.currents_.get_value( lag );
+
+ // voltage logging
+ B_.logger_.record_data( origin.get_steps() + lag );
+ }
+}
+
+// Do not move this function as inline to h-file. It depends on
+// universal_data_logger_impl.h being included here.
+void
+nest::iaf_wang_2002_exact::handle( DataLoggingRequest& e )
+{
+ B_.logger_.handle( e );
+}
+
+void
+nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+ assert( e.get_rport() <= static_cast< int >( B_.spikes_.size() ) );
+
+ const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
+
+ const auto rport = e.get_rport();
+ if ( rport < NMDA )
+ {
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ }
+ else
+ {
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
+
+ const size_t w_idx = rport - NMDA;
+ if ( B_.weights_[ w_idx ] == 0.0 )
+ {
+ B_.weights_[ w_idx ] = e.get_weight();
+ }
+ else if ( B_.weights_[ w_idx ] != e.get_weight() )
+ {
+ throw KernelException( "iaf_wang_2002_exact requires constant weights." );
+ }
+ }
+}
+
+void
+nest::iaf_wang_2002_exact::handle( CurrentEvent& e )
+{
+ assert( e.get_delay_steps() > 0 );
+
+ B_.currents_.add_value(
+ e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ), e.get_weight() * e.get_current() );
+}
+
+#endif // HAVE_GSL
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
new file mode 100644
index 0000000000..ef0fb3e414
--- /dev/null
+++ b/models/iaf_wang_2002_exact.h
@@ -0,0 +1,521 @@
+/*
+ * iaf_wang_2002_exact.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_WANG_2002_EXACT
+#define IAF_WANG_2002_EXACT
+
+// Generated includes:
+#include "config.h"
+
+#ifdef HAVE_GSL
+
+// C includes:
+#include
+#include
+#include
+
+// Includes from nestkernel:
+#include "archiving_node.h"
+#include "connection.h"
+#include "event.h"
+#include "nest_types.h"
+#include "ring_buffer.h"
+#include "universal_data_logger.h"
+
+namespace nest
+{
+/**
+ * Function computing right-hand side of ODE for GSL solver.
+ * @note Must be declared here so we can befriend it in class.
+ * @note Must have C-linkage for passing to GSL. Internally, it is
+ * a first-class C++ function, but cannot be a member function
+ * because of the C-linkage.
+ * @note No point in declaring it inline, since it is called
+ * through a function pointer.
+ * @param void* Pointer to model neuron instance.
+**/
+extern "C" inline int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
+
+
+/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
+
+Short description
++++++++++++++++++
+
+Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
+
+Description
++++++++++++
+
+This model implements a version of the neuron model described in [1]_.
+
+It contains AMPA, GABA and NMDA synapses, where the number of NMDA ports are dependent
+on the number of presynaptic connections.
+
+The AMPA and GABA synapses are given as alpha functions, while the NMDA synapse is modeled
+with a non-linear function described by
+
+.. math::
+ \frac{ dg_j^{NMDA}(t) }{ dt } = - \frac{ g_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
+ \frac{ dx_j(t) }{ dt } =- \frac{ x_j(t) }{ \tau_{NMDA,rise} } + \sum_k \delta(t - t_j^k).
+
+The synaptic current of NMDA is given by
+
+.. math::
+ I_{NMDA}(t) = \frac{ V(t) - E_{ex} }{ 1 + [Mg^{2+}]exp(-0.062V(t))/3.57 }\sum_{j=1}w_jg_j^{NMDA},
+
+where `w_j` is the weight of connection with presynaptic neuron `j`.
+
+
+Parameters
+++++++++++
+
+The following parameters can be set in the status dictionary.
+
+
+=============== ======= ===========================================================
+ E_L mV Resting potential
+ E_ex mV Excitatory reversal potential
+ E_in mV Inhibitory reversal potential
+ V_th mV Threshold potential
+ V_reset mV Reset potential
+ C_m pF Membrane capacitance
+ g_L nS Leak conductance
+ t_ref ms Refractory period
+ tau_AMPA ms Synaptic time constant for AMPA synapse
+ tau_GABA ms Synaptic time constant for GABA synapse
+ tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
+ tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
+ alpha 1/ms Scaling factor for NMDA synapse
+ conc_Mg2 mM Extracellular magnesium concentration
+ gsl_error_tol - GSL error tolerance
+=============== ======= ===========================================================
+
+
+Recordables
++++++++++++
+
+The following values can be recorded.
+
+=========== ===========================================================
+ V_m Membrane potential
+ g_AMPA AMPA gate
+ g_GABA GABA gate
+ NMDA_sum sum of NMDA over all presynaptic neurons j
+=========== ===========================================================
+
+.. note::
+ It is possible to set values for `V_m`, `g_AMPA` and `g_GABA` when creating the model, while the
+ different `g_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
+
+.. note::
+ The variable `g_AMPA` and `g_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
+ in [1]_. `g_{recAMPA, extAMPA, GABA, NMBA}` from [1]_ is built into the weights in this NEST model, so setting the
+ variables is thus done by changing the weights.
+
+Sends
++++++
+
+SpikeEvent
+
+Receives
+++++++++
+
+SpikeEvent, CurrentEvent, DataLoggingRequest
+
+References
+++++++++++
+
+.. [1] Wang, X. J. (2002). Probabilistic decision making by slow reverberation in
+ cortical circuits. Neuron, 36(5), 955-968.
+ DOI: https://doi.org/10.1016/S0896-6273(02)01092-9
+
+See also
+++++++++
+
+iaf_cond_alpha, ht_neuron
+
+EndUserDocs */
+
+void register_iaf_wang_2002_exact( const std::string& name );
+
+class iaf_wang_2002_exact : public ArchivingNode
+{
+public:
+ /**
+ * The constructor is only used to create the model prototype in the model manager.
+ **/
+ iaf_wang_2002_exact();
+
+ /**
+ * The copy constructor is used to create model copies and instances of the model.
+ * @note The copy constructor needs to initialize the parameters and part of the state.
+ * Initialization of rest of state, buffers and internal variables is deferred to
+ * @c init_state_(), @c init_buffers_() and @c calibrate().
+ **/
+ iaf_wang_2002_exact( const iaf_wang_2002_exact& );
+
+ /**
+ * Destructor.
+ **/
+ ~iaf_wang_2002_exact() override;
+
+ /*
+ * Import all overloaded virtual functions that we
+ * override in this class. For background information,
+ * see http://www.gotw.ca/gotw/005.htm.
+ */
+
+ using Node::handles_test_event;
+ using Node::handle;
+
+ /**
+ * Used to validate that we can send SpikeEvent to desired target:port.
+ **/
+ size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
+
+ /* -------------------------------------------------------------------------
+ * Functions handling incoming events.
+ * We tell NEST that we can handle incoming events of various types by
+ * defining handle() for the given event.
+ * ------------------------------------------------------------------------- */
+
+ void handle( SpikeEvent& ) override; //!< accept spikes
+ void handle( CurrentEvent& e ) override; //!< accept current
+ void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
+
+ 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;
+
+ /* -------------------------------------------------------------------------
+ * Functions for getting/setting parameters and state values.
+ * ------------------------------------------------------------------------- */
+
+ void get_status( DictionaryDatum& ) const override;
+ void set_status( const DictionaryDatum& ) override;
+
+private:
+ /**
+ * Synapse types to connect to
+ **/
+ enum SynapseTypes
+ {
+ INF_SPIKE_RECEPTOR = 0,
+ AMPA,
+ GABA,
+ NMDA,
+ SUP_SPIKE_RECEPTOR
+ };
+
+ void init_state_() override;
+ void pre_run_hook() override;
+ void init_buffers_() override;
+ void calibrate();
+ 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_wang_2002_exact >;
+ friend class UniversalDataLogger< iaf_wang_2002_exact >;
+
+ // Parameters class --------------------------------------------------------------
+
+ /**
+ * Parameters of the neuron.
+ *
+ * These are the parameters that can be set by the user through @c `node.set()`.
+ * They are initialized from the model prototype when the node is created.
+ * Parameters do not change during calls to @c update().
+ **/
+ struct Parameters_
+ {
+ double E_L; //!< Resting Potential in mV
+ double E_ex; //!< Excitatory reversal Potential in mV
+ double E_in; //!< Inhibitory reversal Potential in mV
+ double V_th; //!< Threshold Potential in mV
+ double V_reset; //!< Reset Potential in mV
+ double C_m; //!< Membrane Capacitance in pF
+ double g_L; //!< Leak Conductance in nS
+ double t_ref; //!< Refractory period in ms
+ double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
+ double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
+ double tau_rise_NMDA; //!< Synaptic Rise Time Constant NMDA Synapse in ms
+ double tau_decay_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
+ double alpha; //!< Scaling factor for NMDA synapse in 1/ms
+ double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
+
+ double gsl_error_tol; //!< GSL Error Tolerance
+
+ /**
+ * Initialize parameters to their default values.
+ **/
+ Parameters_();
+
+ void get( DictionaryDatum& ) const; //!< Store current values in dictionary
+ void set( const DictionaryDatum&, Node* node ); //!< Set values from dictionary
+ };
+
+
+ // State variables class --------------------------------------------
+
+ /**
+ * State variables of the model.
+ *
+ * State variables consist of the state vector for the subthreshold
+ * dynamics and the refractory count. The state vector must be a
+ * C-style array to be compatible with GSL ODE solvers.
+ *
+ * @note Copy constructor is required because of the C-style array.
+ */
+ struct State_
+ {
+ //! Symbolic indices to the elements of the state vector y
+ enum StateVecElems
+ {
+ V_m = 0,
+ G_AMPA,
+ G_GABA,
+ G_NMDA_base, // (x_NMDA_1, G_NMDA_1), (x_NMDA_2, G_NMDA_2), (x_NMDA_3, G_NMDA_3), ..., (x_NMDA_j, G_NMDA_j)
+ };
+
+ size_t state_vec_size;
+
+ double* ode_state_; //!< state vector, must be C-array for GSL solver
+ long num_ports_; //!< Number of ports
+ int r_; //!< number of refractory steps remaining
+
+ State_( const Parameters_& ); //!< Default initialization
+ State_( const State_& );
+
+ void get( DictionaryDatum& ) const;
+ void set( const DictionaryDatum&, const Parameters_&, Node* );
+
+ //! Get the sum of NMDA over all presynaptic neurons
+ double
+ get_NMDA_sum() const
+ {
+ double NMDA_sum = 0.0;
+ for ( size_t i = G_NMDA_base; i < state_vec_size; i += 2 )
+ {
+ NMDA_sum += ode_state_[ i + 1 ];
+ }
+ return NMDA_sum;
+ }
+ };
+
+ // Variables class -------------------------------------------------------
+
+ /**
+ * Internal variables of the model.
+ * Variables are re-initialized upon each call to Simulate.
+ */
+ struct Variables_
+ {
+ //! refractory time in steps
+ long RefractoryCounts;
+ };
+
+ // Buffers class --------------------------------------------------------
+
+ /**
+ * Buffers of the model.
+ * Buffers are on par with state variables in terms of persistence,
+ * i.e., initialized only upon first Simulate call after ResetKernel,
+ * but its implementation details hidden from the user.
+ */
+ struct Buffers_
+ {
+ Buffers_( iaf_wang_2002_exact& );
+ Buffers_( const Buffers_&, iaf_wang_2002_exact& );
+
+ /**
+ * Logger for all analog data
+ **/
+ UniversalDataLogger< iaf_wang_2002_exact > logger_;
+
+ // -----------------------------------------------------------------------
+ // Buffers and sums of incoming spikes and currents per timestep
+ // -----------------------------------------------------------------------
+ std::vector< RingBuffer > spikes_;
+ RingBuffer currents_;
+
+
+ /**
+ * Vector for weights
+ */
+ std::vector< double > weights_;
+
+ // -----------------------------------------------------------------------
+ // GSL ODE solver data structures
+ // -----------------------------------------------------------------------
+
+ gsl_odeiv_step* s_; //!< stepping function
+ gsl_odeiv_control* c_; //!< adaptive stepsize control function
+ gsl_odeiv_evolve* e_; //!< evolution function
+ gsl_odeiv_system sys_; //!< struct describing system
+
+ /*
+ * integration_step_ should be reset with the neuron on ResetNetwork,
+ * but remain unchanged during calibration. Since it is initialized with
+ * step_, and the resolution cannot change after nodes have been created,
+ * it is safe to place both here.
+ */
+ double step_; //!< step size in ms
+ double integration_step_; //!< current integration time step, updated by GSL
+
+ /**
+ * Input current injected by CurrentEvent.
+ * This variable is used to transport the current applied into the
+ * _dynamics function computing the derivative of the state vector.
+ * It must be a part of Buffers_, since it is initialized once before
+ * the first simulation, but not modified before later Simulate calls.
+ */
+ double I_stim_;
+ };
+
+ // Access functions for UniversalDataLogger -------------------------------
+
+ //! Read out state vector elements, used by UniversalDataLogger
+ template < State_::StateVecElems elem >
+ double
+ get_ode_state_elem_() const
+ {
+ return S_.ode_state_[ elem ];
+ }
+
+ //! Get the sum of NMDA from state, used by UniversalDataLogger
+ double
+ get_NMDA_sum_() const
+ {
+ return S_.get_NMDA_sum();
+ }
+
+ // Data members -----------------------------------------------------------
+
+ // keep the order of these lines, seems to give best performance
+ Parameters_ P_; //!< Free parameters.
+ State_ S_; //!< Dynamic state.
+ Variables_ V_; //!< Internal Variables
+ Buffers_ B_; //!< Buffers.
+
+ //! Mapping of recordables names to access functions
+ static RecordablesMap< iaf_wang_2002_exact > recordablesMap_;
+ friend int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
+
+}; /* neuron iaf_wang_2002_exact */
+
+inline size_t
+iaf_wang_2002_exact::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_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
+{
+ if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ return 0;
+ }
+ else
+ {
+ if ( receptor_type == NMDA )
+ {
+ ++S_.num_ports_;
+ }
+ return receptor_type;
+ }
+}
+
+inline size_t
+iaf_wang_2002_exact::handles_test_event( CurrentEvent&, size_t receptor_type )
+{
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+ return 0;
+}
+
+inline size_t
+iaf_wang_2002_exact::handles_test_event( DataLoggingRequest& dlr, size_t receptor_type )
+{
+ /*
+ * You should usually not change the code in this function.
+ * It confirms to the connection management system that we are able
+ * to handle @c DataLoggingRequest on port 0.
+ * The function also tells the built-in UniversalDataLogger that this node
+ * is recorded from and that it thus needs to collect data during simulation.
+ */
+ if ( receptor_type != 0 )
+ {
+ throw UnknownReceptorType( receptor_type, get_name() );
+ }
+
+ return B_.logger_.connect_logging_device( dlr, recordablesMap_ );
+}
+
+inline void
+iaf_wang_2002_exact::get_status( DictionaryDatum& d ) const
+{
+ P_.get( d );
+ S_.get( d );
+ ArchivingNode::get_status( d );
+
+ DictionaryDatum receptor_type = new Dictionary();
+
+ ( *receptor_type )[ names::AMPA ] = AMPA;
+ ( *receptor_type )[ names::GABA ] = GABA;
+ ( *receptor_type )[ names::NMDA ] = NMDA;
+
+ ( *d )[ names::receptor_types ] = receptor_type;
+
+ ( *d )[ names::recordables ] = recordablesMap_.get_list();
+}
+
+inline void
+iaf_wang_2002_exact::set_status( const DictionaryDatum& d )
+{
+ Parameters_ ptmp = P_; // temporary copy in case of errors
+ ptmp.set( d, this ); // throws if BadProperty
+ State_ stmp = S_; // temporary copy in case of errors
+ stmp.set( d, ptmp, 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.
+ */
+ ArchivingNode::set_status( d );
+
+ // if we get here, temporaries contain consistent set of properties
+ P_ = ptmp;
+ S_ = stmp;
+};
+} // namespace
+
+#endif // HAVE_GSL
+#endif // IAF_WANG_2002
diff --git a/modelsets/full b/modelsets/full
index 569ac35de2..f3abb17749 100644
--- a/modelsets/full
+++ b/modelsets/full
@@ -61,6 +61,7 @@ iaf_psc_exp_ps
iaf_psc_exp_ps_lossless
iaf_tum_2000
iaf_wang_2002
+iaf_wang_2002_exact
izhikevich
jonke_synapse
lin_rate
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 2b03528f9e..479ee3d798 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -313,6 +313,7 @@ const Name music_channel( "music_channel" );
const Name N( "N" );
const Name NMDA( "NMDA" );
+const Name NMDA_sum( "NMDA_sum" );
const Name N_channels( "N_channels" );
const Name N_NaP( "N_NaP" );
const Name N_T( "N_T" );
From 37d1133e893668586467364337584f03d87616ea Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 27 Nov 2023 13:47:19 +0100
Subject: [PATCH 027/184] add test for exact wang
---
testsuite/pytests/test_iaf_wang_2002_exact.py | 79 +++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 testsuite/pytests/test_iaf_wang_2002_exact.py
diff --git a/testsuite/pytests/test_iaf_wang_2002_exact.py b/testsuite/pytests/test_iaf_wang_2002_exact.py
new file mode 100644
index 0000000000..5632218cd0
--- /dev/null
+++ b/testsuite/pytests/test_iaf_wang_2002_exact.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+#
+# test_iaf_wang_2002.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST. If not, see .
+
+import unittest
+import nest
+import numpy as np
+
+
+class IafWang2002TestCase(unittest.TestCase):
+ """Tests for iaf_wang_2002"""
+
+ def setup(self):
+ nest.ResetKernel()
+
+ def test_multiple_NMDA_ports(self):
+ """
+ Check that setting multiple NMDA receptors works
+ """
+ # Create the new model, noise and detectors
+ neuron = nest.Create('iaf_wang_2002')
+ poiss = nest.Create('poisson_generator')
+ poiss.rate = 6400.
+
+ voltmeter = nest.Create('voltmeter')
+ voltmeter.set(record_from=['V_m', 'g_AMPA', 'g_GABA', 'NMDA_sum'])
+
+ # Connect to NMDA receptor several times to check that we create new ports every time.
+ receptors = neuron.get('receptor_types')
+
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['AMPA']})
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['GABA']})
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
+ nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
+
+ nest.Connect(voltmeter, neuron)
+
+ # Check if NMDA sum is 0 before simulating
+ self.assertEqual(neuron.NMDA_sum, 0.)
+
+ # Simulate
+ nest.Simulate(1000.)
+
+ # Check sum NMDA after simulating
+ self.assertTrue(neuron.NMDA_sum > 0.)
+
+ # Check g_AMPA after simulating
+ self.assertTrue(voltmeter.get('events', 'g_AMPA').any() > 0.)
+
+def suite():
+ suite = unittest.makeSuite(IafWang2002TestCase, 'test')
+ return suite
+
+def run():
+ runner = unittest.TextTestRunner(verbosity=2)
+ runner.run(suite())
+
+
+if __name__ == "__main__":
+ run()
From 5b1715b2651c8cc402bff0b461b19aa601da9328 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 30 Nov 2023 16:28:56 +0100
Subject: [PATCH 028/184] exact version, rports work, WIP
---
models/iaf_wang_2002_exact.cpp | 2 +
models/iaf_wang_2002_exact.h | 78 +++++++++++++---------------------
2 files changed, 32 insertions(+), 48 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 16fe341519..d53e101ae3 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -550,6 +550,8 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
+ std::cout << "B_.spikes_.size() = " << B_.spikes_.size() << std::endl;
+ std::cout << "rport: " << e.get_rport() << std::endl;
const auto rport = e.get_rport();
if ( rport < NMDA )
{
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index ef0fb3e414..b9361a6ad8 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -161,22 +161,8 @@ void register_iaf_wang_2002_exact( const std::string& name );
class iaf_wang_2002_exact : public ArchivingNode
{
public:
- /**
- * The constructor is only used to create the model prototype in the model manager.
- **/
iaf_wang_2002_exact();
-
- /**
- * The copy constructor is used to create model copies and instances of the model.
- * @note The copy constructor needs to initialize the parameters and part of the state.
- * Initialization of rest of state, buffers and internal variables is deferred to
- * @c init_state_(), @c init_buffers_() and @c calibrate().
- **/
iaf_wang_2002_exact( const iaf_wang_2002_exact& );
-
- /**
- * Destructor.
- **/
~iaf_wang_2002_exact() override;
/*
@@ -193,12 +179,6 @@ class iaf_wang_2002_exact : public ArchivingNode
**/
size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
- /* -------------------------------------------------------------------------
- * Functions handling incoming events.
- * We tell NEST that we can handle incoming events of various types by
- * defining handle() for the given event.
- * ------------------------------------------------------------------------- */
-
void handle( SpikeEvent& ) override; //!< accept spikes
void handle( CurrentEvent& e ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
@@ -215,6 +195,12 @@ class iaf_wang_2002_exact : public ArchivingNode
void set_status( const DictionaryDatum& ) override;
private:
+ void init_state_() override;
+ void pre_run_hook() override;
+ void init_buffers_() override;
+ void calibrate();
+ void update( Time const&, const long, const long ) override;
+
/**
* Synapse types to connect to
**/
@@ -227,25 +213,15 @@ class iaf_wang_2002_exact : public ArchivingNode
SUP_SPIKE_RECEPTOR
};
- void init_state_() override;
- void pre_run_hook() override;
- void init_buffers_() override;
- void calibrate();
- void update( Time const&, const long, const long ) override;
+
+
+ // make dynamics function quasi-member
+ friend int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
// The next two classes need to be friends to access the State_ class/member
friend class RecordablesMap< iaf_wang_2002_exact >;
friend class UniversalDataLogger< iaf_wang_2002_exact >;
- // Parameters class --------------------------------------------------------------
-
- /**
- * Parameters of the neuron.
- *
- * These are the parameters that can be set by the user through @c `node.set()`.
- * They are initialized from the model prototype when the node is created.
- * Parameters do not change during calls to @c update().
- **/
struct Parameters_
{
double E_L; //!< Resting Potential in mV
@@ -274,7 +250,7 @@ class iaf_wang_2002_exact : public ArchivingNode
void set( const DictionaryDatum&, Node* node ); //!< Set values from dictionary
};
-
+public:
// State variables class --------------------------------------------
/**
@@ -322,18 +298,7 @@ class iaf_wang_2002_exact : public ArchivingNode
}
};
- // Variables class -------------------------------------------------------
-
- /**
- * Internal variables of the model.
- * Variables are re-initialized upon each call to Simulate.
- */
- struct Variables_
- {
- //! refractory time in steps
- long RefractoryCounts;
- };
-
+private:
// Buffers class --------------------------------------------------------
/**
@@ -392,6 +357,19 @@ class iaf_wang_2002_exact : public ArchivingNode
double I_stim_;
};
+
+ // Variables class -------------------------------------------------------
+
+ /**
+ * Internal variables of the model.
+ * Variables are re-initialized upon each call to Simulate.
+ */
+ struct Variables_
+ {
+ //! refractory time in steps
+ long RefractoryCounts;
+ };
+
// Access functions for UniversalDataLogger -------------------------------
//! Read out state vector elements, used by UniversalDataLogger
@@ -419,7 +397,6 @@ class iaf_wang_2002_exact : public ArchivingNode
//! Mapping of recordables names to access functions
static RecordablesMap< iaf_wang_2002_exact > recordablesMap_;
- friend int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
}; /* neuron iaf_wang_2002_exact */
@@ -443,9 +420,14 @@ iaf_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
{
if ( receptor_type == NMDA )
{
+ // give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
++S_.num_ports_;
+ return S_.num_ports_ - 1;
}
+ else
+ {
return receptor_type;
+ }
}
}
From 8c651ff810a50059edc76928a90a443525f45c71 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 30 Nov 2023 16:29:50 +0100
Subject: [PATCH 029/184] formatting
---
models/iaf_wang_2002.h | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 388c408992..dd8f8c9019 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -182,14 +182,11 @@ class iaf_wang_2002 : public ArchivingNode
//! Used to validate that we can send SpikeEvent to desired target:port.
size_t send_test_event( Node&, size_t, synindex, bool ) override;
-// void sends_secondary_event( DelayedRateConnectionEvent& ) override;
-
void handle( SpikeEvent& ) override; //!< accept spikes
void handle( CurrentEvent& ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
size_t handles_test_event( SpikeEvent&, size_t ) override;
-// size_t handles_test_event( DelayedRateConnectionEvent&,size_t ) override;
size_t handles_test_event( CurrentEvent&, size_t ) override;
size_t handles_test_event( DataLoggingRequest&, size_t ) override;
@@ -220,9 +217,6 @@ class iaf_wang_2002 : public ArchivingNode
friend class RecordablesMap< iaf_wang_2002 >;
friend class UniversalDataLogger< iaf_wang_2002 >;
-private:
- //
- // Model parameters
struct Parameters_
{
double E_L; //!< Resting Potential in mV
@@ -285,15 +279,13 @@ class iaf_wang_2002 : public ArchivingNode
State_( const Parameters_& ); //!< Default initialization
State_( const State_& );
-
void get( DictionaryDatum& ) const;
void set( const DictionaryDatum&, const Parameters_&, Node* );
-
};
private:
- // Buffers class --------------------------------------------------------
+ // Buffers class --------------------------------------------------------
/**
* Buffers of the model.
@@ -357,8 +349,6 @@ class iaf_wang_2002 : public ArchivingNode
long RefractoryCounts_;
};
-
-
// Access functions for UniversalDataLogger -------------------------------
//! Read out state vector elements, used by UniversalDataLogger
From 8e76c45465508f9c727b00ddeec9b34875259aa1 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 12 Dec 2023 11:28:18 +0100
Subject: [PATCH 030/184] exact model seems to work
---
models/iaf_wang_2002_exact.cpp | 19 +++--
models/iaf_wang_2002_exact.h | 12 +--
pynest/examples/wang_neuron_exact.py | 107 +++++++++++++++++++++++++++
3 files changed, 124 insertions(+), 14 deletions(-)
create mode 100644 pynest/examples/wang_neuron_exact.py
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index d53e101ae3..1151fcf418 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -64,8 +64,8 @@ RecordablesMap< iaf_wang_2002_exact >::create()
{
// add state variables to recordables map
insert_( names::V_m, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::V_m > );
- insert_( names::g_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_AMPA > );
- insert_( names::g_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_GABA > );
+ insert_( names::s_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_AMPA > );
+ insert_( names::s_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_GABA > );
insert_( names::NMDA_sum, &iaf_wang_2002_exact::get_NMDA_sum_ );
}
}
@@ -234,8 +234,8 @@ void
nest::iaf_wang_2002_exact::State_::get( DictionaryDatum& d ) const
{
def< double >( d, names::V_m, ode_state_[ V_m ] ); // Membrane potential
- def< double >( d, names::g_AMPA, ode_state_[ G_AMPA ] );
- def< double >( d, names::g_GABA, ode_state_[ G_GABA ] );
+ def< double >( d, names::s_AMPA, ode_state_[ G_AMPA ] );
+ def< double >( d, names::s_GABA, ode_state_[ G_GABA ] );
// total NMDA sum
double NMDA_sum = get_NMDA_sum();
@@ -246,8 +246,8 @@ void
nest::iaf_wang_2002_exact::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
{
updateValueParam< double >( d, names::V_m, ode_state_[ V_m ], node );
- updateValueParam< double >( d, names::g_AMPA, ode_state_[ G_AMPA ], node );
- updateValueParam< double >( d, names::g_GABA, ode_state_[ G_GABA ], node );
+ updateValueParam< double >( d, names::s_AMPA, ode_state_[ G_AMPA ], node );
+ updateValueParam< double >( d, names::s_GABA, ode_state_[ G_GABA ], node );
}
/* ---------------------------------------------------------------------------
@@ -550,15 +550,17 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
- std::cout << "B_.spikes_.size() = " << B_.spikes_.size() << std::endl;
- std::cout << "rport: " << e.get_rport() << std::endl;
const auto rport = e.get_rport();
if ( rport < NMDA )
{
+// std::cout << "Received non-NMDA spike: " << std::endl;
+// std::cout << "rport: " << e.get_rport() << std::endl;
B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
else
{
+ std::cout << "Received NMDA spike: " << std::endl;
+ std::cout << "rport: " << e.get_rport() << std::endl;
B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
const size_t w_idx = rport - NMDA;
@@ -568,6 +570,7 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
}
else if ( B_.weights_[ w_idx ] != e.get_weight() )
{
+ // Why??
throw KernelException( "iaf_wang_2002_exact requires constant weights." );
}
}
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index b9361a6ad8..1a16a54168 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -118,17 +118,17 @@ The following values can be recorded.
=========== ===========================================================
V_m Membrane potential
- g_AMPA AMPA gate
- g_GABA GABA gate
+ s_AMPA AMPA gate
+ s_GABA GABA gate
NMDA_sum sum of NMDA over all presynaptic neurons j
=========== ===========================================================
.. note::
- It is possible to set values for `V_m`, `g_AMPA` and `g_GABA` when creating the model, while the
- different `g_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
+ It is possible to set values for `V_m`, `s_AMPA` and `s_GABA` when creating the model, while the
+ different `s_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
.. note::
- The variable `g_AMPA` and `g_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
+ The variable `s_AMPA` and `s_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
in [1]_. `g_{recAMPA, extAMPA, GABA, NMBA}` from [1]_ is built into the weights in this NEST model, so setting the
variables is thus done by changing the weights.
@@ -422,7 +422,7 @@ iaf_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
{
// give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
++S_.num_ports_;
- return S_.num_ports_ - 1;
+ return S_.num_ports_;
}
else
{
diff --git a/pynest/examples/wang_neuron_exact.py b/pynest/examples/wang_neuron_exact.py
new file mode 100644
index 0000000000..41382bcf1a
--- /dev/null
+++ b/pynest/examples/wang_neuron_exact.py
@@ -0,0 +1,107 @@
+import nest
+import matplotlib.pyplot as plt
+import numpy as np
+
+nest.rng_seed = 12345
+
+nest.ResetKernel()
+w_ex = 40.
+w_in = -15.
+alpha = 0.5
+tau_AMPA = 2.0
+tau_GABA = 5.0
+tau_NMDA = 100.0
+nrn1 = nest.Create("iaf_wang_2002_exact", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_NMDA})
+
+pg = nest.Create("poisson_generator", {"rate": 50.})
+sr = nest.Create("spike_recorder")
+nrn2 = nest.Create("iaf_wang_2002_exact", {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_NMDA,
+ "t_ref": 0.})
+
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 1}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 3}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_in,
+ "receptor_type": 2}
+
+conn_spec = {"rule": "all_to_all"}
+
+def s_soln(w, t, tau):
+ isyn = np.zeros_like(t)
+ useinds = t >= 0.
+ isyn[useinds] = w * np.exp(-t[useinds] / tau)
+ return isyn
+
+def spiketrain_response(t, tau, spiketrain, w):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ response += s_soln(w, t_, tau)
+ return response
+
+def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ w_ = w * alpha * (1 - response[zero_arg])
+ w_ = min(w_, 1 - response[zero_arg])
+ response += s_soln(w_, t_, tau)
+ return response
+
+nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, sr)
+nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(mm1, nrn1)
+
+nest.Connect(mm2, nrn2)
+
+nest.Simulate(1000.)
+
+# get spike times from membrane potential
+# cannot use spike_recorder because we abuse exact spike timing
+V_m = mm1.get("events", "V_m")
+times = mm1.get("events", "times")
+diff = np.ediff1d(V_m, to_begin=0.)
+spikes = sr.get("events", "times")
+spikes = times[diff < -3]
+
+# compute analytical solutimes = mm1.get("events", "times")
+ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+gaba_soln = spiketrain_response(times, tau_GABA, spikes, w_in)
+
+fig, ax = plt.subplots(4,2)
+ax[0,0].plot(mm1.events["V_m"])
+ax[0,1].plot(mm2.events["V_m"])
+
+ax[1,0].plot(mm1.events["s_AMPA"])
+ax[1,1].plot(mm2.events["s_AMPA"])
+ax[1,1].plot(ampa_soln, '--')
+
+ax[2,0].plot(mm1.events["s_GABA"])
+ax[2,1].plot(mm2.events["s_GABA"])
+ax[2,1].plot(gaba_soln, '--')
+
+ax[3,0].plot(mm1.events["NMDA_sum"])
+ax[3,1].plot(mm2.events["NMDA_sum"])
+ax[3,1].plot(nmda_soln, '--')
+
+plt.show()
+
From 4b3f7a3721d69eee42e757fa3364eef9fc011f74 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 19 Dec 2023 18:07:11 +0100
Subject: [PATCH 031/184] compare to brian, off by constant factor
---
pynest/examples/wang_compare_brian.py | 221 ++++++++++++++++++++++++++
1 file changed, 221 insertions(+)
create mode 100644 pynest/examples/wang_compare_brian.py
diff --git a/pynest/examples/wang_compare_brian.py b/pynest/examples/wang_compare_brian.py
new file mode 100644
index 0000000000..b238b232ca
--- /dev/null
+++ b/pynest/examples/wang_compare_brian.py
@@ -0,0 +1,221 @@
+import brian2 as b2
+import nest
+import numpy as np
+import time
+import statistics
+import matplotlib.pyplot as plt
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Parameters
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+V_th = -55 * b2.mV
+V_reset = -70 * b2.mV
+t_ref = 2 * b2.ms
+
+# parameters for the equation of the neuron
+# (Inhibitory and excitatory neurons have different parameters)
+g_L = 25. * b2.nS
+C_m = 0.5 * b2.nF
+
+g_AMPA_rec = 1.0 * b2.nS
+g_AMPA_ext = 100.0 *b2.nS
+g_GABA = 1.0 * b2.nS
+g_NMDA = 1.0 * b2.nS
+
+# reversal potentials
+E_L = V_reset
+E_ex= 0. * b2.mV
+E_in = -70. * b2.mV
+
+# time constant of the receptors
+tau_AMPA= 2 * b2.ms
+tau_GABA= 5 * b2.ms
+tau_NMDA_rise = 2. * b2.ms
+tau_NMDA_decay = 100. * b2.ms
+#parameters we need for nmda receptors
+alpha = 0.5 / b2.ms
+Mg2 = 1.
+
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Brian
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+## Equations
+eqsE="""
+
+ dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
+ I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
+
+ I_AMPA_ext= g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
+ ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
+ #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
+
+
+ I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
+ s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
+ #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
+ #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
+
+ I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
+ s_GABA_tot :1
+
+
+ I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
+ s_NMDA_tot : 1
+
+ """
+
+eqs_ampa="""
+ s_AMPA_tot_post= w_AMPA * s_AMPA : 1 (summed)
+ ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
+ w_AMPA: 1
+ """
+
+eqs_gaba="""
+ s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
+ ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
+ w_GABA: 1
+ """
+
+eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
+ ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
+ dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
+ w_NMDA : 1
+ """
+
+nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="euler")
+nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="euler")
+
+nrn1[0].v[0]=V_reset
+nrn2[0].v[0]=V_reset
+
+times = np.array([10, 20, 40, 80, 90]) * b2.ms
+indices = np.arange(len(times))
+spikeGen = b2.SpikeGeneratorGroup(len(times), indices, times)
+
+ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1")
+ext_conn1.connect()
+
+conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="euler")
+conn2.connect()
+conn2.w_AMPA = 2.0
+
+conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="euler")
+conn3.connect()
+conn3.w_NMDA = 1.0
+
+conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="euler")
+conn4.connect()
+conn4.w_GABA = 2.0
+
+
+vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
+ampaMonitor1 = b2.StateMonitor(nrn1, "I_AMPA_rec",record=True)
+gabaMonitor1 = b2.StateMonitor(nrn1, "I_GABA",record=True)
+nmdaMonitor1 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
+
+vMonitor2 = b2.StateMonitor(nrn2, "v", record=True)
+ampaMonitor2 = b2.StateMonitor(nrn2, "I_AMPA_rec", record=True)
+gabaMonitor2 = b2.StateMonitor(nrn2, "I_GABA", record=True)
+nmdaMonitor2 = b2.StateMonitor(nrn2, "I_NMDA", record=True)
+
+t_sim = 300
+b2.run(t_sim * b2.ms)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# NEST
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+nest.rng_seed = 12345
+
+nest.ResetKernel()
+
+neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
+ "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
+ "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
+ "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
+ "conc_Mg2": np.asarray(Mg2), # dimensionless
+ "E_ex": np.asarray(E_ex) * 1e3, # units mV
+ "E_in": np.asarray(E_in) * 1e3, # units mV
+ "E_L": np.asarray(E_L) * 1e3, # units mV
+ "V_th": np.asarray(V_th) * 1e3, # units mV
+ "C_m": np.asarray(C_m) * 1e12, # units pF
+ "g_L": np.asarray(g_L) * 1e9, # units nS
+ "V_reset": np.asarray(V_reset) * 1e3, # units nS
+ "t_ref": np.asarray(t_ref) * 1e3} # units ms
+
+
+nrn1 = nest.Create("iaf_wang_2002_exact", neuron_params)
+nrn2 = nest.Create("iaf_wang_2002_exact", neuron_params)
+
+times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
+sg = nest.Create("spike_generator", {"spike_times": times})
+sr = nest.Create("spike_recorder")
+
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_rec) * 1e9, # units nS
+ "receptor_type": 1}
+
+ex_syn_spec_ext = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
+ "receptor_type": 1}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_NMDA) * 1e9, # units nS
+ "receptor_type": 3}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_GABA) * 1e9, # units nS
+ "receptor_type": 2}
+
+conn_spec = {"rule": "all_to_all"}
+
+nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
+nest.Connect(nrn1, sr)
+nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(mm1, nrn1)
+nest.Connect(mm2, nrn2)
+
+nest.Simulate(300.)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Plotting
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+fig, ax = plt.subplots(4, 2)
+fig.set_size_inches([12,10])
+ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
+ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), label="nest")
+ax[0,0].set_xlabel("time (ms)")
+ax[0,0].set_ylabel("membrane potential V (mV)")
+ax[0,0].legend()
+
+ax[1,0].plot(ampaMonitor1.t / b2.ms, ampaMonitor1.I_AMPA_rec[0]/)
+ax[2,0].plot(gabaMonitor1.t / b2.ms, gabaMonitor1.I_GABA[0])
+ax[3,0].plot(nmdaMonitor1.t / b2.ms, nmdaMonitor1.s_NMDA_tot[0])
+
+ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
+ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"))
+ax[0,1].set_xlabel("time (ms)")
+ax[0,1].set_ylabel("membrane potential V (mV)")
+ax[0,1].legend()
+
+ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.I_AMPA_rec[0])
+ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.I_GABA[0])
+ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.I_NMDA[0])
+
+plt.show()
+
From 3253ff9247fed3877a07089671cce0704996cdd7 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 21 Dec 2023 13:04:44 +0100
Subject: [PATCH 032/184] Getting exactly same results as Brian
---
models/iaf_wang_2002_exact.cpp | 62 +++++++++----------
models/iaf_wang_2002_exact.h | 8 +--
pynest/examples/wang_compare_brian.py | 89 +++++++++++++++++----------
3 files changed, 91 insertions(+), 68 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 1151fcf418..6a8e0f676c 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -64,8 +64,8 @@ RecordablesMap< iaf_wang_2002_exact >::create()
{
// add state variables to recordables map
insert_( names::V_m, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::V_m > );
- insert_( names::s_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_AMPA > );
- insert_( names::s_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::G_GABA > );
+ insert_( names::s_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::s_AMPA > );
+ insert_( names::s_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::s_GABA > );
insert_( names::NMDA_sum, &iaf_wang_2002_exact::get_NMDA_sum_ );
}
}
@@ -98,14 +98,14 @@ nest::iaf_wang_2002_exact::State_::State_( const Parameters_& p )
, num_ports_( 2 )
, r_( 0 )
{
- ode_state_ = new double[ G_NMDA_base ];
+ ode_state_ = new double[ s_NMDA_base ];
assert( ode_state_ );
ode_state_[ V_m ] = p.E_L; // initialize to reversal potential
- ode_state_[ G_AMPA ] = 0.0;
- ode_state_[ G_GABA ] = 0.0;
+ ode_state_[ s_AMPA ] = 0.0;
+ ode_state_[ s_GABA ] = 0.0;
- state_vec_size = G_NMDA_base;
+ state_vec_size = s_NMDA_base;
}
nest::iaf_wang_2002_exact::State_::State_( const State_& s )
@@ -115,14 +115,14 @@ nest::iaf_wang_2002_exact::State_::State_( const State_& s )
, r_( s.r_ )
{
assert( s.num_ports_ == 2 );
- assert( state_vec_size == G_NMDA_base );
+ assert( state_vec_size == s_NMDA_base );
- ode_state_ = new double[ G_NMDA_base ];
+ ode_state_ = new double[ s_NMDA_base ];
assert( ode_state_ );
ode_state_[ V_m ] = s.ode_state_[ V_m ];
- ode_state_[ G_AMPA ] = s.ode_state_[ G_AMPA ];
- ode_state_[ G_GABA ] = s.ode_state_[ G_GABA ];
+ ode_state_[ s_AMPA ] = s.ode_state_[ s_AMPA ];
+ ode_state_[ s_GABA ] = s.ode_state_[ s_GABA ];
}
nest::iaf_wang_2002_exact::Buffers_::Buffers_( iaf_wang_2002_exact& n )
@@ -234,8 +234,8 @@ void
nest::iaf_wang_2002_exact::State_::get( DictionaryDatum& d ) const
{
def< double >( d, names::V_m, ode_state_[ V_m ] ); // Membrane potential
- def< double >( d, names::s_AMPA, ode_state_[ G_AMPA ] );
- def< double >( d, names::s_GABA, ode_state_[ G_GABA ] );
+ def< double >( d, names::s_AMPA, ode_state_[ s_AMPA ] );
+ def< double >( d, names::s_GABA, ode_state_[ s_GABA ] );
// total NMDA sum
double NMDA_sum = get_NMDA_sum();
@@ -246,8 +246,8 @@ void
nest::iaf_wang_2002_exact::State_::set( const DictionaryDatum& d, const Parameters_&, Node* node )
{
updateValueParam< double >( d, names::V_m, ode_state_[ V_m ], node );
- updateValueParam< double >( d, names::s_AMPA, ode_state_[ G_AMPA ], node );
- updateValueParam< double >( d, names::s_GABA, ode_state_[ G_GABA ], node );
+ updateValueParam< double >( d, names::s_AMPA, ode_state_[ s_AMPA ], node );
+ updateValueParam< double >( d, names::s_GABA, ode_state_[ s_GABA ], node );
}
/* ---------------------------------------------------------------------------
@@ -313,19 +313,19 @@ nest::iaf_wang_2002_exact::~iaf_wang_2002_exact()
void
nest::iaf_wang_2002_exact::init_state_()
{
- assert( S_.state_vec_size == State_::G_NMDA_base );
+ assert( S_.state_vec_size == State_::s_NMDA_base );
double* old_state = S_.ode_state_;
- S_.state_vec_size = State_::G_NMDA_base + 2 * ( S_.num_ports_ - 2 );
+ S_.state_vec_size = State_::s_NMDA_base + 2 * ( S_.num_ports_ - 2 );
S_.ode_state_ = new double[ S_.state_vec_size ];
assert( S_.ode_state_ );
S_.ode_state_[ State_::V_m ] = old_state[ State_::V_m ];
- S_.ode_state_[ State_::G_AMPA ] = old_state[ State_::G_AMPA ];
- S_.ode_state_[ State_::G_GABA ] = old_state[ State_::G_GABA ];
+ S_.ode_state_[ State_::s_AMPA ] = old_state[ State_::s_AMPA ];
+ S_.ode_state_[ State_::s_GABA ] = old_state[ State_::s_GABA ];
- for ( size_t i = State_::G_NMDA_base; i < S_.state_vec_size; ++i )
+ for ( size_t i = State_::s_NMDA_base; i < S_.state_vec_size; ++i )
{
S_.ode_state_[ i ] = 0.0;
}
@@ -425,13 +425,13 @@ nest::iaf_wang_2002_exact_dynamics( double, const double ode_state[], double f[]
// ode_state[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.ode_state[].
- const double I_AMPA = ( ode_state[ State_::V_m ] - node.P_.E_ex ) * ode_state[ State_::G_AMPA ];
+ const double I_AMPA = ( ode_state[ State_::V_m ] - node.P_.E_ex ) * ode_state[ State_::s_AMPA ];
- const double I_rec_GABA = ( ode_state[ State_::V_m ] - node.P_.E_in ) * ode_state[ State_::G_GABA ];
+ const double I_rec_GABA = ( ode_state[ State_::V_m ] - node.P_.E_in ) * ode_state[ State_::s_GABA ];
- // The sum of NMDA_G
+ // The sum of s_NMDA
double total_NMDA = 0;
- for ( size_t i = State_::G_NMDA_base + 1, w_idx = 0; i < node.S_.state_vec_size; i += 2, ++w_idx )
+ for ( size_t i = State_::s_NMDA_base + 1, w_idx = 0; i < node.S_.state_vec_size; i += 2, ++w_idx )
{
total_NMDA += ode_state[ i ] * node.B_.weights_.at( w_idx );
}
@@ -443,10 +443,10 @@ nest::iaf_wang_2002_exact_dynamics( double, const double ode_state[], double f[]
f[ State_::V_m ] = ( -node.P_.g_L * ( ode_state[ State_::V_m ] - node.P_.E_L ) - I_syn ) / node.P_.C_m;
- f[ State_::G_AMPA ] = -ode_state[ State_::G_AMPA ] / node.P_.tau_AMPA;
- f[ State_::G_GABA ] = -ode_state[ State_::G_GABA ] / node.P_.tau_GABA;
+ f[ State_::s_AMPA ] = -ode_state[ State_::s_AMPA ] / node.P_.tau_AMPA;
+ f[ State_::s_GABA ] = -ode_state[ State_::s_GABA ] / node.P_.tau_GABA;
- for ( size_t i = State_::G_NMDA_base; i < node.S_.state_vec_size; i += 2 )
+ for ( size_t i = State_::s_NMDA_base; i < node.S_.state_vec_size; i += 2 )
{
f[ i + 1 ] =
-ode_state[ i + 1 ] / node.P_.tau_decay_NMDA + node.P_.alpha * ode_state[ i ] * ( 1 - ode_state[ i + 1 ] );
@@ -493,17 +493,17 @@ nest::iaf_wang_2002_exact::update( Time const& origin, const long from, const lo
}
// add incoming spikes
- S_.ode_state_[ State_::G_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
- S_.ode_state_[ State_::G_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+ S_.ode_state_[ State_::s_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.ode_state_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
for ( size_t i = NMDA - 1; i < B_.spikes_.size(); ++i )
{
const size_t si = i - ( NMDA - 1 );
assert( si >= 0 );
- assert( State_::G_NMDA_base + si * 2 < S_.state_vec_size );
+ assert( State_::s_NMDA_base + si * 2 < S_.state_vec_size );
- S_.ode_state_[ State_::G_NMDA_base + si * 2 ] += B_.spikes_.at( i ).get_value( lag );
+ S_.ode_state_[ State_::s_NMDA_base + si * 2 ] += B_.spikes_.at( i ).get_value( lag );
}
// absolute refractory period
@@ -559,8 +559,6 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
}
else
{
- std::cout << "Received NMDA spike: " << std::endl;
- std::cout << "rport: " << e.get_rport() << std::endl;
B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
const size_t w_idx = rport - NMDA;
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 1a16a54168..74dee84432 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -268,9 +268,9 @@ class iaf_wang_2002_exact : public ArchivingNode
enum StateVecElems
{
V_m = 0,
- G_AMPA,
- G_GABA,
- G_NMDA_base, // (x_NMDA_1, G_NMDA_1), (x_NMDA_2, G_NMDA_2), (x_NMDA_3, G_NMDA_3), ..., (x_NMDA_j, G_NMDA_j)
+ s_AMPA,
+ s_GABA,
+ s_NMDA_base, // (x_NMDA_1, G_NMDA_1), (x_NMDA_2, G_NMDA_2), (x_NMDA_3, G_NMDA_3), ..., (x_NMDA_j, G_NMDA_j)
};
size_t state_vec_size;
@@ -290,7 +290,7 @@ class iaf_wang_2002_exact : public ArchivingNode
get_NMDA_sum() const
{
double NMDA_sum = 0.0;
- for ( size_t i = G_NMDA_base; i < state_vec_size; i += 2 )
+ for ( size_t i = s_NMDA_base; i < state_vec_size; i += 2 )
{
NMDA_sum += ode_state_[ i + 1 ];
}
diff --git a/pynest/examples/wang_compare_brian.py b/pynest/examples/wang_compare_brian.py
index b238b232ca..67c77d9dd2 100644
--- a/pynest/examples/wang_compare_brian.py
+++ b/pynest/examples/wang_compare_brian.py
@@ -1,3 +1,12 @@
+"""
+Check that NEST implementation gives same results as a reference implementation from Brian.
+Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
+a separate parameter. This is the only difference in parameterization between the two models.
+Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
+so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
+"""
+
+
import brian2 as b2
import nest
import numpy as np
@@ -35,15 +44,20 @@
tau_GABA= 5 * b2.ms
tau_NMDA_rise = 2. * b2.ms
tau_NMDA_decay = 100. * b2.ms
-#parameters we need for nmda receptors
+
+# additional NMDA parameters
alpha = 0.5 / b2.ms
Mg2 = 1.
+# synaptic weights
+weight_AMPA = 1.
+weight_GABA = 3.
+weight_NMDA = 2.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Brian
+# Brian simulation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
## Equations
@@ -51,12 +65,12 @@
dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
-
+
I_AMPA_ext= g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
#Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
-
+
+
I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
#Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
@@ -64,11 +78,11 @@
I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
s_GABA_tot :1
-
-
+
+
I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
s_NMDA_tot : 1
-
+
"""
eqs_ampa="""
@@ -104,33 +118,31 @@
conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="euler")
conn2.connect()
-conn2.w_AMPA = 2.0
+conn2.w_AMPA = weight_AMPA
conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="euler")
conn3.connect()
-conn3.w_NMDA = 1.0
+conn3.w_NMDA = weight_NMDA
conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="euler")
conn4.connect()
-conn4.w_GABA = 2.0
-
+conn4.w_GABA = weight_GABA
vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
-ampaMonitor1 = b2.StateMonitor(nrn1, "I_AMPA_rec",record=True)
-gabaMonitor1 = b2.StateMonitor(nrn1, "I_GABA",record=True)
+ampaMonitor1 = b2.StateMonitor(nrn1, "s_AMPA_ext",record=True)
+gabaMonitor1 = b2.StateMonitor(nrn1, "s_GABA_tot",record=True)
nmdaMonitor1 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
vMonitor2 = b2.StateMonitor(nrn2, "v", record=True)
-ampaMonitor2 = b2.StateMonitor(nrn2, "I_AMPA_rec", record=True)
-gabaMonitor2 = b2.StateMonitor(nrn2, "I_GABA", record=True)
-nmdaMonitor2 = b2.StateMonitor(nrn2, "I_NMDA", record=True)
+ampaMonitor2 = b2.StateMonitor(nrn2, "s_AMPA_tot", record=True)
+gabaMonitor2 = b2.StateMonitor(nrn2, "s_GABA_tot", record=True)
+nmdaMonitor2 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
t_sim = 300
b2.run(t_sim * b2.ms)
-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# NEST
+# NEST simulation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
nest.rng_seed = 12345
@@ -149,6 +161,7 @@
"C_m": np.asarray(C_m) * 1e12, # units pF
"g_L": np.asarray(g_L) * 1e9, # units nS
"V_reset": np.asarray(V_reset) * 1e3, # units nS
+ "alpha": np.asarray(alpha * b2.ms), # units nS
"t_ref": np.asarray(t_ref) * 1e3} # units ms
@@ -163,7 +176,7 @@
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_rec) * 1e9, # units nS
+ "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
"receptor_type": 1}
ex_syn_spec_ext = {"synapse_model": "static_synapse",
@@ -171,11 +184,11 @@
"receptor_type": 1}
nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_NMDA) * 1e9, # units nS
+ "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
"receptor_type": 3}
in_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_GABA) * 1e9, # units nS
+ "weight": np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
"receptor_type": 2}
conn_spec = {"rule": "all_to_all"}
@@ -190,32 +203,44 @@
nest.Simulate(300.)
-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Plotting
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
fig, ax = plt.subplots(4, 2)
fig.set_size_inches([12,10])
+fig.subplots_adjust(hspace=0.5)
ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), label="nest")
ax[0,0].set_xlabel("time (ms)")
ax[0,0].set_ylabel("membrane potential V (mV)")
ax[0,0].legend()
-
-ax[1,0].plot(ampaMonitor1.t / b2.ms, ampaMonitor1.I_AMPA_rec[0]/)
-ax[2,0].plot(gabaMonitor1.t / b2.ms, gabaMonitor1.I_GABA[0])
-ax[3,0].plot(nmdaMonitor1.t / b2.ms, nmdaMonitor1.s_NMDA_tot[0])
+ax[0,0].set_title("Presynaptic neuron")
ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"))
ax[0,1].set_xlabel("time (ms)")
ax[0,1].set_ylabel("membrane potential V (mV)")
-ax[0,1].legend()
-
-ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.I_AMPA_rec[0])
-ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.I_GABA[0])
-ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.I_NMDA[0])
+ax[0,1].set_title("Postsynaptic neuron")
+
+ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0])
+ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"))
+ax[1,1].set_xlabel("time (ms)")
+ax[1,1].set_ylabel("s_AMPA")
+
+ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0])
+ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"))
+ax[2,1].set_xlabel("time (ms)")
+ax[2,1].set_ylabel("s_GABA")
+
+ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0])
+ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "NMDA_sum"))
+ax[3,1].set_xlabel("time (ms)")
+ax[3,1].set_ylabel("s_NMDA")
+
+ax[1,0].axis("off")
+ax[2,0].axis("off")
+ax[3,0].axis("off")
plt.show()
From 12ac8dfad86ade11b38a6d3948211c4b64fa5cc3 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 3 Jan 2024 17:55:01 +0100
Subject: [PATCH 033/184] reproduces brian exactly
---
models/iaf_wang_2002.cpp | 83 +++++++++-----------
models/iaf_wang_2002.h | 39 ++++++----
pynest/examples/wang_compare_brian.py | 73 ++++++++++-------
pynest/examples/wang_neuron.py | 108 +++++++++++++++++++-------
pynest/examples/wang_neuron_exact.py | 72 ++++++++++++-----
5 files changed, 243 insertions(+), 132 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index e79ffd9285..c098b1931f 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -86,11 +86,11 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
// y[] here is---and must be---the state vector supplied by the integrator,
// not the state vector in the node, node.S_.y[].
- const double I_AMPA = node.P_.g_AMPA * ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ];
+ const double I_AMPA = ( y[ S::V_m ] - node.P_.E_ex ) * y[ S::s_AMPA ];
- const double I_rec_GABA = node.P_.g_GABA * ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
+ const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
- const double I_rec_NMDA = node.P_.g_NMDA * ( y[ S::V_m ] - node.P_.E_ex )
+ const double I_rec_NMDA = ( y[ S::V_m ] - node.P_.E_ex )
/ ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * y[ S::s_NMDA ];
const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA + node.B_.I_stim_;
@@ -117,10 +117,10 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, V_reset( -60.0 ) // mV
, C_m( 500.0 ) // pF
, g_L( 25.0 ) // nS
- , g_GABA ( 1.3 ) //
- , g_NMDA ( 0.165 ) //
- , g_AMPA ( 0.05 ) //
- , g_AMPA_ext ( 0.05 ) //
+// , g_GABA ( 1.3 ) //
+// , g_NMDA ( 0.165 ) //
+// , g_AMPA ( 0.05 ) //
+// , g_AMPA_ext ( 0.05 ) //
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
@@ -153,9 +153,7 @@ nest::iaf_wang_2002::State_::State_( const State_& s )
nest::iaf_wang_2002::Buffers_::Buffers_( iaf_wang_2002& n )
: logger_( n )
- , spike_AMPA_()
- , spike_GABA_()
- , spike_NMDA_()
+ , spikes_()
, s_( nullptr )
, c_( nullptr )
, e_( nullptr )
@@ -188,10 +186,10 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::V_reset, V_reset );
def< double >( d, names::C_m, C_m );
def< double >( d, names::g_L, g_L );
- def< double >( d, names::g_GABA, g_GABA );
- def< double >( d, names::g_NMDA, g_NMDA );
- def< double >( d, names::g_AMPA, g_AMPA );
- def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
+// def< double >( d, names::g_GABA, g_GABA );
+// def< double >( d, names::g_NMDA, g_NMDA );
+// def< double >( d, names::g_AMPA, g_AMPA );
+// def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
def< double >( d, names::t_ref, t_ref );
def< double >( d, names::tau_AMPA, tau_AMPA );
def< double >( d, names::tau_GABA, tau_GABA );
@@ -212,10 +210,10 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::V_reset, V_reset, node );
updateValueParam< double >( d, names::C_m, C_m, node );
updateValueParam< double >( d, names::g_L, g_L, node );
- updateValueParam< double >( d, names::g_GABA, g_GABA, node );
- updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
- updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
- updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
+// updateValueParam< double >( d, names::g_GABA, g_GABA, node );
+// updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
+// updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
+// updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
@@ -335,9 +333,13 @@ nest::iaf_wang_2002::init_state_()
void
nest::iaf_wang_2002::init_buffers_()
{
- B_.spike_AMPA_.clear();
- B_.spike_GABA_.clear();
- B_.spike_NMDA_.clear();
+// std::cout << "Inside init buffers" << std::endl;
+ B_.spikes_.resize( 3 );
+ for ( auto& sb : B_.spikes_ )
+ {
+ sb.clear(); // includes resize
+ }
+
B_.currents_.clear(); // includes resize
B_.logger_.reset(); // includes resize
@@ -408,7 +410,7 @@ void
nest::iaf_wang_2002::update( Time const& origin, const long from, const long to )
{
std::vector< double > s_vals( kernel().connection_manager.get_min_delay(), 0.0 );
-
+// std::cout << "Inside update" << std::endl;
for ( long lag = from; lag < to; ++lag )
{
double t = 0.0;
@@ -425,7 +427,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// enforce setting IntegrationStep to step-t; this is of advantage
// for a consistent and efficient integration across subsequent
// simulation intervals
-
while ( t < B_.step_ )
{
const int status = gsl_odeiv_evolve_apply( B_.e_,
@@ -443,11 +444,14 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
}
}
+
// add incoming spikes
- S_.y_[ State_::s_AMPA ] += B_.spike_AMPA_.get_value( lag );
- S_.y_[ State_::s_GABA ] += B_.spike_GABA_.get_value( lag );
- S_.y_[ State_::s_NMDA ] += B_.spike_NMDA_.get_value( lag );
+ S_.y_[ State_::s_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
+ S_.y_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
+
+ S_.y_[ State_::s_NMDA ] += B_.spikes_[ NMDA - 1 ].get_value( lag );
S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
+
if ( S_.r_ )
{
// neuron is absolute refractory
@@ -478,7 +482,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
kernel().event_delivery_manager.send( *this, se, lag );
}
-
// set new input current
B_.I_stim_ = B_.currents_.get_value( lag );
@@ -500,29 +503,17 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
{
assert( e.get_delay_steps() > 0 );
- if ( e.get_weight() > 0.0 )
+ const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
+
+ const auto rport = e.get_rport();
+
+ if ( rport < NMDA )
{
- if ( e.get_rport() == 0 ) {
- B_.spike_AMPA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- e.get_weight() * e.get_multiplicity() );
- }
- // if from external population, ignore weight
- // when computing the actual synaptic current, this contribution will be multiplied by
- // g_AMPA. therefore we multiply by g_AMPA_ext / g_AMPA here, and the g_AMPA denominator
- // will be cancelled
- else if ( e.get_rport() == 1 ) {
- B_.spike_AMPA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- P_.g_AMPA_ext / P_.g_AMPA * e.get_multiplicity() );
- }
- if ( e.get_offset() != 0.0 ) {
- B_.spike_NMDA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- e.get_weight() * e.get_multiplicity() * e.get_offset() );
- }
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
else
{
- B_.spike_GABA_.add_value( e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() ),
- -e.get_weight() * e.get_multiplicity() );
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() * e.get_offset() );
}
}
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index dd8f8c9019..b6ca27c6e0 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -210,6 +210,19 @@ class iaf_wang_2002 : public ArchivingNode
void calibrate();
void update( Time const&, const long, const long ) override;
+ /**
+ * Synapse types to connect to
+ **/
+ enum SynapseTypes
+ {
+ INF_SPIKE_RECEPTOR = 0,
+ AMPA,
+ GABA,
+ NMDA,
+ SUP_SPIKE_RECEPTOR
+ };
+
+
// make dynamics function quasi-member
friend int iaf_wang_2002_dynamics( double, const double*, double*, void* );
@@ -226,10 +239,10 @@ class iaf_wang_2002 : public ArchivingNode
double V_reset; //!< Reset Potential in mV
double C_m; //!< Membrane Capacitance in pF
double g_L; //!< Leak Conductance in nS
- double g_GABA; //!< Peak conductance GABA
- double g_NMDA; //!< Peak conductance NMDA
- double g_AMPA; //!< Peak conductance AMPA
- double g_AMPA_ext; //!< Peak conductance AMPA
+// double g_GABA; //!< Peak conductance GABA
+// double g_NMDA; //!< Peak conductance NMDA
+// double g_AMPA; //!< Peak conductance AMPA
+// double g_AMPA_ext; //!< Peak conductance AMPA
double t_ref; //!< Refractory period in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
@@ -266,8 +279,8 @@ class iaf_wang_2002 : public ArchivingNode
{
V_m = 0,
s_AMPA,
- s_NMDA,
s_GABA,
+ s_NMDA,
STATE_VEC_SIZE
};
@@ -304,9 +317,10 @@ class iaf_wang_2002 : public ArchivingNode
// -----------------------------------------------------------------------
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
- RingBuffer spike_AMPA_;
- RingBuffer spike_GABA_;
- RingBuffer spike_NMDA_;
+ std::vector< RingBuffer > spikes_;
+// RingBuffer spike_AMPA_;
+// RingBuffer spike_GABA_;
+// RingBuffer spike_NMDA_;
RingBuffer currents_;
// -----------------------------------------------------------------------
@@ -383,17 +397,14 @@ iaf_wang_2002::send_test_event( Node& target, size_t receptor_type, synindex, bo
inline size_t
iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
{
- if ( receptor_type == 0 )
+ if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) )
{
+ throw UnknownReceptorType( receptor_type, get_name() );
return 0;
}
- else if ( receptor_type == 1 )
- {
- return 1;
- }
else
{
- throw UnknownReceptorType( receptor_type, get_name() );
+ return receptor_type;
}
}
diff --git a/pynest/examples/wang_compare_brian.py b/pynest/examples/wang_compare_brian.py
index 67c77d9dd2..63676ad67c 100644
--- a/pynest/examples/wang_compare_brian.py
+++ b/pynest/examples/wang_compare_brian.py
@@ -55,7 +55,6 @@
weight_NMDA = 2.
-
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Brian simulation
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@@ -66,11 +65,10 @@
dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
- I_AMPA_ext= g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
+ I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
#Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
#Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
@@ -79,14 +77,13 @@
I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
s_GABA_tot :1
-
I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
s_NMDA_tot : 1
"""
eqs_ampa="""
- s_AMPA_tot_post= w_AMPA * s_AMPA : 1 (summed)
+ s_AMPA_tot_post = w_AMPA * s_AMPA : 1 (summed)
ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
w_AMPA: 1
"""
@@ -103,11 +100,13 @@
w_NMDA : 1
"""
-nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="euler")
-nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="euler")
+b2.defaultclock.dt = 0.001 * b2.ms
-nrn1[0].v[0]=V_reset
-nrn2[0].v[0]=V_reset
+nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
+nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
+
+nrn1[0].v[0] = V_reset
+nrn2[0].v[0] = V_reset
times = np.array([10, 20, 40, 80, 90]) * b2.ms
indices = np.arange(len(times))
@@ -115,18 +114,22 @@
ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1")
ext_conn1.connect()
+ext_conn1.delay = 1.0 * b2.ms
-conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="euler")
+conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="rk4")
conn2.connect()
conn2.w_AMPA = weight_AMPA
+conn2.delay = 1.0 * b2.ms
-conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="euler")
+conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="rk4")
conn3.connect()
conn3.w_NMDA = weight_NMDA
+conn3.delay = 1.0 * b2.ms
-conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="euler")
+conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="rk4")
conn4.connect()
conn4.w_GABA = weight_GABA
+conn4.delay = 1.0 * b2.ms
vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
ampaMonitor1 = b2.StateMonitor(nrn1, "s_AMPA_ext",record=True)
@@ -148,6 +151,7 @@
nest.rng_seed = 12345
nest.ResetKernel()
+nest.resolution = b2.defaultclock.dt / b2.ms
neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
"tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
@@ -162,7 +166,8 @@
"g_L": np.asarray(g_L) * 1e9, # units nS
"V_reset": np.asarray(V_reset) * 1e3, # units nS
"alpha": np.asarray(alpha * b2.ms), # units nS
- "t_ref": np.asarray(t_ref) * 1e3} # units ms
+ # DIFFERENCE: subtract 0.1 ms from t_ref
+ "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
nrn1 = nest.Create("iaf_wang_2002_exact", neuron_params)
@@ -172,24 +177,35 @@
sg = nest.Create("spike_generator", {"spike_times": times})
sr = nest.Create("spike_recorder")
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"],
+ "interval": b2.defaultclock.dt / b2.ms}
+)
+
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"],
+ "interval": b2.defaultclock.dt / b2.ms}
+)
+# DIFFERENCE: add 0.1ms to delay
+nest_delay = 1. + b2.defaultclock.dt / b2.ms
ex_syn_spec = {"synapse_model": "static_synapse",
"weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
- "receptor_type": 1}
+ "receptor_type": 1,
+ "delay": nest_delay}
ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
- "receptor_type": 1}
+ "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
nmda_syn_spec = {"synapse_model": "static_synapse",
"weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
- "receptor_type": 3}
+ "receptor_type": 3,
+ "delay": nest_delay}
in_syn_spec = {"synapse_model": "static_synapse",
"weight": np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
- "receptor_type": 2}
+ "receptor_type": 2,
+ "delay": nest_delay}
conn_spec = {"rule": "all_to_all"}
@@ -211,34 +227,39 @@
fig.set_size_inches([12,10])
fig.subplots_adjust(hspace=0.5)
ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
-ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), label="nest")
+ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), "--", label="nest")
ax[0,0].set_xlabel("time (ms)")
ax[0,0].set_ylabel("membrane potential V (mV)")
ax[0,0].legend()
ax[0,0].set_title("Presynaptic neuron")
ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
-ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"))
+ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--")
ax[0,1].set_xlabel("time (ms)")
ax[0,1].set_ylabel("membrane potential V (mV)")
ax[0,1].set_title("Postsynaptic neuron")
+# multiply by g_AMPA_ext since it is baked into s_AMPA in NEST
+ax[1,0].plot(ampaMonitor1.t/b2.ms, ampaMonitor1.s_AMPA_ext[0] * g_AMPA_ext / b2.nS)
+ax[1,0].plot(mm1.get("events", "times"), mm1.get("events", "s_AMPA"), "--")
+ax[1,0].set_xlabel("time (ms)")
+ax[1,0].set_ylabel("s_AMPA")
+
ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0])
-ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"))
+ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--")
ax[1,1].set_xlabel("time (ms)")
ax[1,1].set_ylabel("s_AMPA")
ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0])
-ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"))
+ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--")
ax[2,1].set_xlabel("time (ms)")
ax[2,1].set_ylabel("s_GABA")
ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0])
-ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "NMDA_sum"))
+ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "NMDA_sum"), "--")
ax[3,1].set_xlabel("time (ms)")
ax[3,1].set_ylabel("s_NMDA")
-ax[1,0].axis("off")
ax[2,0].axis("off")
ax[3,0].axis("off")
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index ec4e3332fc..f2968cb907 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -2,34 +2,57 @@
import matplotlib.pyplot as plt
import numpy as np
+nest.ResetKernel()
nest.rng_seed = 12345
-w_ex = 400.
+w_ext = 40.
+w_ex = 5.
w_in = -15.
-alpha = 0.5
-tau_AMPA = 2.0
-tau_GABA = 5.0
-tau_NMDA = 100.0
-nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA})
+params_exact = {"tau_AMPA": 2.0,
+ "tau_GABA": 5.0,
+ "tau_rise_NMDA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "conc_Mg2": 1.0,
+ "E_ex": 0.0,
+ "E_in": -70.0,
+ "E_L": -70.0,
+ "V_th": -55.0,
+ "C_m": 500.0,
+ "g_L": 25.0,
+ "V_reset": -70.0,
+ "alpha": 0.5,
+ "t_ref": 2.0}
+
+params_approx = params_exact.copy()
+del params_approx["tau_rise_NMDA"]
+
+nrn1 = nest.Create("iaf_wang_2002", params_approx)
pg = nest.Create("poisson_generator", {"rate": 50.})
sr = nest.Create("spike_recorder")
-nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA,
- "t_ref": 0.})
+nrn2 = nest.Create("iaf_wang_2002", params_approx)
+
+nrn3 = nest.Create("iaf_wang_2002_exact", params_exact)
mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+
+ampa_ext_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ext,
+ "receptor_type": 1}
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex}
+ampa_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 1}
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 3}
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_in}
+gaba_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_in,
+ "receptor_type": 2}
conn_spec = {"rule": "all_to_all"}
@@ -57,13 +80,18 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
response += s_soln(w_, t_, tau)
return response
-nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(pg, nrn1, syn_spec=ampa_ext_syn_spec, conn_spec=conn_spec)
nest.Connect(nrn1, sr)
-nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
nest.Connect(mm1, nrn1)
nest.Connect(mm2, nrn2)
+nest.Connect(mm3, nrn3)
nest.Simulate(1000.)
@@ -77,25 +105,49 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
# compute analytical solutions
times = mm1.get("events", "times")
-ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
-nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
-gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+# ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+# nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+# gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
fig, ax = plt.subplots(4,2)
+fig.set_size_inches([12,10])
+fig.subplots_adjust(hspace=0.5)
+
ax[0,0].plot(mm1.events["V_m"])
+ax[0,0].set_xlabel("time (ms)")
+ax[0,0].set_ylabel("membrane potential V (mV)")
+ax[0,0].legend()
+ax[0,0].set_title("Presynaptic neuron")
+
+
ax[0,1].plot(mm2.events["V_m"])
+ax[0,1].plot(mm3.events["V_m"])
+ax[0,1].set_xlabel("time (ms)")
+ax[0,1].set_ylabel("membrane potential V (mV)")
+ax[0,1].set_title("Postsynaptic neuron")
+
-ax[1,0].plot(mm1.events["s_AMPA"])
ax[1,1].plot(mm2.events["s_AMPA"])
-ax[1,1].plot(ampa_soln, '--')
+ax[1,1].plot(mm3.events["s_AMPA"], "--")
+ax[1,1].set_xlabel("time (ms)")
+ax[1,1].set_ylabel("s_AMPA")
+
-ax[2,0].plot(mm1.events["s_GABA"])
ax[2,1].plot(mm2.events["s_GABA"])
-ax[2,1].plot(gaba_soln, '--')
+ax[2,1].plot(mm3.events["s_GABA"], "--")
+ax[2,1].set_xlabel("time (ms)")
+ax[2,1].set_ylabel("s_GABA")
+
-ax[3,0].plot(mm1.events["s_NMDA"])
ax[3,1].plot(mm2.events["s_NMDA"])
-ax[3,1].plot(nmda_soln, '--')
+ax[3,1].plot(mm3.events["NMDA_sum"], "--")
+ax[3,1].set_xlabel("time (ms)")
+ax[3,1].set_ylabel("s_NMDA")
+
+ax[1,0].axis("off")
+ax[2,0].axis("off")
+ax[3,0].axis("off")
+
plt.show()
diff --git a/pynest/examples/wang_neuron_exact.py b/pynest/examples/wang_neuron_exact.py
index 41382bcf1a..307506618f 100644
--- a/pynest/examples/wang_neuron_exact.py
+++ b/pynest/examples/wang_neuron_exact.py
@@ -5,36 +5,72 @@
nest.rng_seed = 12345
nest.ResetKernel()
-w_ex = 40.
-w_in = -15.
+
alpha = 0.5
tau_AMPA = 2.0
tau_GABA = 5.0
+tau_rise_NMDA = 2.0
tau_NMDA = 100.0
-nrn1 = nest.Create("iaf_wang_2002_exact", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA})
-pg = nest.Create("poisson_generator", {"rate": 50.})
+Mg2 = 1.0
+
+t_ref = 2.0
+
+# reversal potentials
+E_ex = 0.
+E_in = -70.0
+E_L = -70.0
+
+V_th = -55.0
+V_reset = -70.
+C_m = 500.0
+g_L = 25.0
+
+
+# set through synaptic weights
+g_AMPA_rec = 1.0
+g_AMPA_ext = 100.0
+g_GABA = 1.0
+g_NMDA = 1.0
+
+neuron_params = {"tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_rise_NMDA": tau_rise_NMDA,
+ "tau_decay_NMDA": tau_NMDA,
+ "conc_Mg2": Mg2,
+ "E_ex": E_ex,
+ "E_in": E_in,
+ "E_L": E_L,
+ "V_th": V_th,
+ "C_m": C_m,
+ "g_L": g_L,
+ "t_ref": t_ref}
+
+
+nrn1 = nest.Create("iaf_wang_2002_exact", neuron_params)
+nrn2 = nest.Create("iaf_wang_2002_exact", neuron_params)
+
+times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
+sg = nest.Create("spike_generator", {"spike_times": times})
sr = nest.Create("spike_recorder")
-nrn2 = nest.Create("iaf_wang_2002_exact", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA,
- "t_ref": 0.})
mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
+ "weight": g_AMPA_rec,
+ "receptor_type": 1}
+
+ex_syn_spec_ext = {"synapse_model": "static_synapse",
+ "weight": g_AMPA_ext,
"receptor_type": 1}
nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
+ "weight": g_NMDA,
"receptor_type": 3}
in_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_in,
+ "weight": g_GABA,
"receptor_type": 2}
conn_spec = {"rule": "all_to_all"}
@@ -63,7 +99,7 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
response += s_soln(w_, t_, tau)
return response
-nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
nest.Connect(nrn1, sr)
nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
@@ -72,7 +108,7 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
nest.Connect(mm2, nrn2)
-nest.Simulate(1000.)
+nest.Simulate(300.)
# get spike times from membrane potential
# cannot use spike_recorder because we abuse exact spike timing
@@ -83,9 +119,9 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
spikes = times[diff < -3]
# compute analytical solutimes = mm1.get("events", "times")
-ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
-nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
-gaba_soln = spiketrain_response(times, tau_GABA, spikes, w_in)
+ampa_soln = spiketrain_response(times, tau_AMPA, spikes, g_AMPA_rec)
+nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, g_NMDA, alpha)
+gaba_soln = spiketrain_response(times, tau_GABA, spikes, g_GABA)
fig, ax = plt.subplots(4,2)
ax[0,0].plot(mm1.events["V_m"])
From 0f6356c378d820b392a9e9efa35311a35d23eb59 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 12 Jan 2024 18:33:03 +0100
Subject: [PATCH 034/184] Check NMDA approximation numerically
---
.../examples/wong_wang_nmda_approximation.py | 165 ++++++++++++++++++
1 file changed, 165 insertions(+)
create mode 100644 pynest/examples/wong_wang_nmda_approximation.py
diff --git a/pynest/examples/wong_wang_nmda_approximation.py b/pynest/examples/wong_wang_nmda_approximation.py
new file mode 100644
index 0000000000..5ec7366322
--- /dev/null
+++ b/pynest/examples/wong_wang_nmda_approximation.py
@@ -0,0 +1,165 @@
+import nest
+import matplotlib.pyplot as plt
+import numpy as np
+
+nest.ResetKernel()
+nest.rng_seed = 12345
+
+w_ext = 40.
+w_ex = 1.
+w_in = -15.
+
+params_exact = {"tau_AMPA": 2.0,
+ "tau_GABA": 5.0,
+ "tau_rise_NMDA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "conc_Mg2": 1.0,
+ "E_ex": 0.0,
+ "E_in": -70.0,
+ "E_L": -70.0,
+ "V_th": -55.0,
+ "C_m": 500.0,
+ "g_L": 25.0,
+ "V_reset": -70.0,
+ "alpha": 0.5,
+ "t_ref": 2.0}
+
+params_approx = params_exact.copy()
+
+nrn1 = nest.Create("iaf_wang_2002", params_approx)
+pg = nest.Create("inhomogeneous_poisson_generator", {"rate_values": [400., 0.],
+ "rate_times": [0.1, 10.]})
+sr = nest.Create("spike_recorder")
+nrn2 = nest.Create("iaf_wang_2002", params_approx)
+
+nrn3 = nest.Create("iaf_wang_2002_exact", params_exact)
+
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
+mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+
+ampa_ext_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ext,
+ "receptor_type": 1}
+
+ampa_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 1}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_ex,
+ "receptor_type": 3}
+
+gaba_syn_spec = {"synapse_model": "static_synapse",
+ "weight": w_in,
+ "receptor_type": 2}
+
+conn_spec = {"rule": "all_to_all"}
+
+def s_soln(w, t, tau):
+ isyn = np.zeros_like(t)
+ useinds = t >= 0.
+ isyn[useinds] = w * np.exp(-t[useinds] / tau)
+ return isyn
+
+def spiketrain_response(t, tau, spiketrain, w):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ response += s_soln(w, t_, tau)
+ return response
+
+def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1. - sp
+ zero_arg = t_ == 0.
+ w_ = w * alpha * (1 - response[zero_arg])
+ w_ = min(w_, 1 - response[zero_arg])
+ response += s_soln(w_, t_, tau)
+ return response
+
+nest.Connect(pg, nrn1, syn_spec=ampa_ext_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, sr)
+nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(mm1, nrn1)
+
+nest.Connect(mm2, nrn2)
+nest.Connect(mm3, nrn3)
+
+nest.Simulate(1000.)
+
+# get spike times from membrane potential
+# cannot use spike_recorder because we abuse exact spike timing
+V_m = mm1.get("events", "V_m")
+times = mm1.get("events", "times")
+diff = np.ediff1d(V_m, to_begin=0.)
+spikes = sr.get("events", "times")
+spikes = times[diff < -3]
+
+# compute analytical solutions
+times = mm1.get("events", "times")
+# ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+# nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+# gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+
+from scipy.integrate import cumtrapz
+def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
+ a = (1 / tau_decay - 1 / tau_rise)
+
+ # argument of exp
+ arg = t * a + alpha * x0 * tau_rise * (1 - np.exp(- t / tau_rise))
+ return np.exp(arg)
+
+def nmda_fn(t, s0, x0, tau_decay, tau_rise, alpha):
+ f1 = np.exp(- t / tau_decay - alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
+
+ tvec = np.linspace(0, t, 1001)
+ integrand = nmda_integrand(tvec, x0, tau_decay, tau_rise, alpha)
+# f2 = alpha * x0 * np.trapz(integrand, x = tvec) + s0
+ f2 = np.trapz(integrand, x = tvec)
+ return f1, f2 # f1 * f2
+
+def nmda_fn_approx(t, x0, tau_decay, tau_rise, alpha):
+ f1 = np.exp(-t / tau_decay - alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
+ f2 = (np.exp((1 - np.exp(-t / tau_rise)) * alpha * tau_rise) - 1) / alpha
+
+ return f1, f2
+
+
+t = np.arange(0.1, 1000, 0.1)
+s0 = 0.1
+f1s, f2s = [], []
+for t_ in t:
+ f1, f2 = nmda_fn(t_, 0., 1., 100., 2., 0.5)
+ f1s.append(f1)
+ f2s.append(f2)
+
+s_nmda = np.array([f1s[i] * (f2s[i] * 0.5 + s0) for i, _ in enumerate(f1s)])
+
+f1_approx, f2_approx = nmda_fn_approx(t, 1, 100, 2, 0.5)
+s_nmda_approx = f1_approx * (f2_approx * 0.5 + s0)
+
+def nmda_approx_exp(t, tau_rise, alpha, tau_decay):
+ f1 = np.exp(-alpha * tau_rise * (1 - np.exp(-t / tau_rise)))
+ f2 = -(1 - np.exp(alpha * tau_rise * (1 - np.exp(-t / tau_rise))))
+ return f1, f2
+
+f1_exp, f2_exp = nmda_approx_exp(t, 2.0, 0.5, 100)
+
+i = 50
+s_nmda_exp = (f1_exp[i] * f2_exp[i] + f1_exp[i] * s0) * np.exp(-t / 100)
+
+plt.plot(t, s_nmda, color="C0")
+plt.plot(t, s_nmda_approx, color="C1")
+plt.plot(t, s_nmda_exp, "--", color="C2")
+#plt.plot(t, f1_exp * f2_exp, "--", color="C2")
+
+plt.show()
+
From a721ef09202bdcde422d07ea1fd75b73fa931c6a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 13:18:16 +0100
Subject: [PATCH 035/184] clean up
---
models/iaf_wang_2002.cpp | 21 +++++----------------
models/iaf_wang_2002.h | 9 ++-------
2 files changed, 7 insertions(+), 23 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index c098b1931f..c0a3240c5f 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -117,14 +117,11 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, V_reset( -60.0 ) // mV
, C_m( 500.0 ) // pF
, g_L( 25.0 ) // nS
-// , g_GABA ( 1.3 ) //
-// , g_NMDA ( 0.165 ) //
-// , g_AMPA ( 0.05 ) //
-// , g_AMPA_ext ( 0.05 ) //
, t_ref( 2.0 ) // ms
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
, tau_decay_NMDA( 100 ) // ms
+ , tau_rise_NMDA( 2 ) // ms
, alpha( 0.5 ) // 1 / ms
, conc_Mg2( 1 ) // mM
, gsl_error_tol( 1e-3 )
@@ -186,14 +183,11 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::V_reset, V_reset );
def< double >( d, names::C_m, C_m );
def< double >( d, names::g_L, g_L );
-// def< double >( d, names::g_GABA, g_GABA );
-// def< double >( d, names::g_NMDA, g_NMDA );
-// def< double >( d, names::g_AMPA, g_AMPA );
-// def< double >( d, names::g_AMPA_ext, g_AMPA_ext );
def< double >( d, names::t_ref, t_ref );
def< double >( d, names::tau_AMPA, tau_AMPA );
def< double >( d, names::tau_GABA, tau_GABA );
def< double >( d, names::tau_decay_NMDA, tau_decay_NMDA );
+ def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
def< double >( d, names::alpha, alpha );
def< double >( d, names::conc_Mg2, conc_Mg2 );
def< double >( d, names::gsl_error_tol, gsl_error_tol );
@@ -210,14 +204,11 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::V_reset, V_reset, node );
updateValueParam< double >( d, names::C_m, C_m, node );
updateValueParam< double >( d, names::g_L, g_L, node );
-// updateValueParam< double >( d, names::g_GABA, g_GABA, node );
-// updateValueParam< double >( d, names::g_NMDA, g_NMDA, node );
-// updateValueParam< double >( d, names::g_AMPA, g_AMPA, node );
-// updateValueParam< double >( d, names::g_AMPA_ext, g_AMPA_ext, node );
updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
updateValueParam< double >( d, names::tau_decay_NMDA, tau_decay_NMDA, node );
+ updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
updateValueParam< double >( d, names::alpha, alpha, node );
updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
@@ -333,7 +324,6 @@ nest::iaf_wang_2002::init_state_()
void
nest::iaf_wang_2002::init_buffers_()
{
-// std::cout << "Inside init buffers" << std::endl;
B_.spikes_.resize( 3 );
for ( auto& sb : B_.spikes_ )
{
@@ -410,7 +400,6 @@ void
nest::iaf_wang_2002::update( Time const& origin, const long from, const long to )
{
std::vector< double > s_vals( kernel().connection_manager.get_min_delay(), 0.0 );
-// std::cout << "Inside update" << std::endl;
for ( long lag = from; lag < to; ++lag )
{
double t = 0.0;
@@ -474,9 +463,9 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
- const double s_NMDA_delta = P_.alpha * (1 - S_.s_NMDA_pre);
+ const double s_NMDA_delta = pow(P_.tau_rise_NMDA, 2) * P_.alpha * (1 - S_.s_NMDA_pre);
S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
-
+
SpikeEvent se;
se.set_offset( s_NMDA_delta );
kernel().event_delivery_manager.send( *this, se, lag );
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index b6ca27c6e0..9fbbf00fc4 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -111,6 +111,7 @@ The following parameters can be set in the status dictionary.
tau_AMPA ms Synaptic time constant for AMPA synapse
tau_GABA ms Synaptic time constant for GABA synapse
tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
+ tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
alpha 1/ms Scaling factor for NMDA synapse
conc_Mg2 mM Extracellular magnesium concentration
gsl_error_tol - GSL error tolerance
@@ -239,14 +240,11 @@ class iaf_wang_2002 : public ArchivingNode
double V_reset; //!< Reset Potential in mV
double C_m; //!< Membrane Capacitance in pF
double g_L; //!< Leak Conductance in nS
-// double g_GABA; //!< Peak conductance GABA
-// double g_NMDA; //!< Peak conductance NMDA
-// double g_AMPA; //!< Peak conductance AMPA
-// double g_AMPA_ext; //!< Peak conductance AMPA
double t_ref; //!< Refractory period in ms
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
double tau_decay_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
+ double tau_rise_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
double alpha; //!< Scaling factor for NMDA synapse in 1/ms
double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
@@ -318,9 +316,6 @@ class iaf_wang_2002 : public ArchivingNode
// Buffers and sums of incoming spikes and currents per timestep
// -----------------------------------------------------------------------
std::vector< RingBuffer > spikes_;
-// RingBuffer spike_AMPA_;
-// RingBuffer spike_GABA_;
-// RingBuffer spike_NMDA_;
RingBuffer currents_;
// -----------------------------------------------------------------------
From c4d03cf834d6879f29b4b3d5bd55ae9b6a465714 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 14:49:50 +0100
Subject: [PATCH 036/184] implement new S_NMDA jump
---
models/iaf_wang_2002.cpp | 17 ++++++++++++++++-
models/iaf_wang_2002.h | 6 +++++-
2 files changed, 21 insertions(+), 2 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index c0a3240c5f..ba541fe205 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -124,6 +124,7 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, tau_rise_NMDA( 2 ) // ms
, alpha( 0.5 ) // 1 / ms
, conc_Mg2( 1 ) // mM
+ , approx_t_exact ( 4)
, gsl_error_tol( 1e-3 )
{
}
@@ -190,6 +191,7 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
def< double >( d, names::alpha, alpha );
def< double >( d, names::conc_Mg2, conc_Mg2 );
+ def< double >( d, names::approx_t_exact, approx_t_exact );
def< double >( d, names::gsl_error_tol, gsl_error_tol );
}
@@ -211,6 +213,7 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
updateValueParam< double >( d, names::alpha, alpha, node );
updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
+ updateValueParam< double >( d, names::approx_t_exact, approx_t_exact, node );
updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
if ( V_reset >= V_th )
@@ -237,6 +240,10 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
{
throw BadProperty( "Mg2 concentration must be strictly positive." );
}
+ if ( approx_t_exact <= 0.0 )
+ {
+ throw BadProperty( "approx_t_exact must be strictly positive." );
+ }
if ( gsl_error_tol <= 0.0 )
{
throw BadProperty( "The gsl_error_tol must be strictly positive." );
@@ -381,6 +388,14 @@ nest::iaf_wang_2002::pre_run_hook()
V_.RefractoryCounts_ = Time( Time::ms( P_.t_ref ) ).get_steps();
// since t_ref_ >= 0, this can only fail in error
assert( V_.RefractoryCounts_ >= 0 );
+
+ // compute S_NMDA jump height variables
+ const double f1 = exp(-P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA)));
+
+ const double f2 = -(1 - exp(P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA))));
+ V_.S_jump_0 = f1 * f2;
+ V_.S_jump_1 = f1;
+
}
void
@@ -463,7 +478,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
- const double s_NMDA_delta = pow(P_.tau_rise_NMDA, 2) * P_.alpha * (1 - S_.s_NMDA_pre);
+ const double s_NMDA_delta = V_.S_jump_0 + V_.S_jump_1 * S_.s_NMDA_pre - S_.s_NMDA_pre;
S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
SpikeEvent se;
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 9fbbf00fc4..086789adaa 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -244,10 +244,12 @@ class iaf_wang_2002 : public ArchivingNode
double tau_AMPA; //!< Synaptic Time Constant AMPA Synapse in ms
double tau_GABA; //!< Synaptic Time Constant GABA Synapse in ms
double tau_decay_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
- double tau_rise_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
+ double tau_rise_NMDA; //!< Synaptic Decay Time Constant NMDA Synapse in ms
double alpha; //!< Scaling factor for NMDA synapse in 1/ms
double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
+ // TODO: find better name for this variable
+ double approx_t_exact; // Time at which the S_NMDA approximation is exact
double gsl_error_tol; //!< GSL Error Tolerance
//! Initialize parameters to their default values.
@@ -356,6 +358,8 @@ class iaf_wang_2002 : public ArchivingNode
{
//! refractory time in steps
long RefractoryCounts_;
+ double S_jump_0; // zeroth order term of jump
+ double S_jump_1; // first order term of jump
};
// Access functions for UniversalDataLogger -------------------------------
From f9ed575d657896231ee5f56860af2bd540247c03 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 14:54:47 +0100
Subject: [PATCH 037/184] add comment
---
models/iaf_wang_2002_exact.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 6a8e0f676c..c6ac882278 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -497,7 +497,10 @@ nest::iaf_wang_2002_exact::update( Time const& origin, const long from, const lo
S_.ode_state_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
for ( size_t i = NMDA - 1; i < B_.spikes_.size(); ++i )
+ // i starts at 2, runs through all NMDA spikes
{
+
+ // index which starts at 0
const size_t si = i - ( NMDA - 1 );
assert( si >= 0 );
@@ -559,6 +562,8 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
}
else
{
+ // spikes_ has 2 + N elements, where N is number of NMDA synapses
+ // rport starts at 1, so subtract one to get correct index
B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
const size_t w_idx = rport - NMDA;
From b9f2979bae319abda4589057d9d79b79b36971ab Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 14:56:14 +0100
Subject: [PATCH 038/184] add approx t variable
---
nestkernel/nest_names.cpp | 1 +
nestkernel/nest_names.h | 1 +
2 files changed, 2 insertions(+)
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 479ee3d798..70cbd1dece 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -65,6 +65,7 @@ const Name amplitude( "amplitude" );
const Name amplitude_times( "amplitude_times" );
const Name amplitude_values( "amplitude_values" );
const Name anchor( "anchor" );
+const Name approx_t_exact( "approx_t_exact" );
const Name archiver_length( "archiver_length" );
const Name asc_amps( "asc_amps" );
const Name asc_decay( "asc_decay" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index 9d55639217..5464384361 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -91,6 +91,7 @@ extern const Name amplitude;
extern const Name amplitude_times;
extern const Name amplitude_values;
extern const Name anchor;
+extern const Name approx_t_exact;
extern const Name archiver_length;
extern const Name asc_amps;
extern const Name asc_decay;
From 72085ab125727a3872c63c159769cc28faac9885 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 15:39:36 +0100
Subject: [PATCH 039/184] remove print
---
models/iaf_wang_2002_exact.cpp | 2 --
1 file changed, 2 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index c6ac882278..3e82885e3f 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -556,8 +556,6 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
const auto rport = e.get_rport();
if ( rport < NMDA )
{
-// std::cout << "Received non-NMDA spike: " << std::endl;
-// std::cout << "rport: " << e.get_rport() << std::endl;
B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
else
From 2465108f75bcd0d5293e84137dc34f53cc7734b5 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 15:48:20 +0100
Subject: [PATCH 040/184] remove max(S_NMDA), no need with approx. model
---
models/iaf_wang_2002.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index ba541fe205..9ba5da6bbd 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -454,7 +454,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
S_.y_[ State_::s_NMDA ] += B_.spikes_[ NMDA - 1 ].get_value( lag );
- S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
+// S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
if ( S_.r_ )
{
From 906b4436470f6d910c5d74aa44a1437a24b7be58 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 15:48:59 +0100
Subject: [PATCH 041/184] remove min(S_NMDA, 1), no need with approximation
---
models/iaf_wang_2002.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 9ba5da6bbd..189e5333ea 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -454,7 +454,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
S_.y_[ State_::s_NMDA ] += B_.spikes_[ NMDA - 1 ].get_value( lag );
-// S_.y_[ State_::s_NMDA ] = std::min( S_.y_[ State_::s_NMDA ], 1.0 );
if ( S_.r_ )
{
From 3bc2ffd7aedda0ab624bd71c4c922552f8aa2c29 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 16:00:22 +0100
Subject: [PATCH 042/184] correct inhibitory weight sign
---
models/iaf_wang_2002.cpp | 9 ++++++++-
models/iaf_wang_2002_exact.cpp | 9 ++++++++-
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 189e5333ea..60b7a04c7b 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -512,7 +512,14 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
if ( rport < NMDA )
{
- B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ if ( e.get_weight() > 0 )
+ {
+ B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ }
+ else
+ {
+ B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
+ }
}
else
{
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 3e82885e3f..18fe3dacc8 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -556,7 +556,14 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
const auto rport = e.get_rport();
if ( rport < NMDA )
{
- B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ if ( e.get_weight() > 0 )
+ {
+ B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+ }
+ else
+ {
+ B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
+ }
}
else
{
From 04139d4e55283849392b22495961cfcd5cc49c52 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 19 Jan 2024 19:56:14 +0100
Subject: [PATCH 043/184] reproduces dynamics frfom wang 2002 paper
---
pynest/examples/wang_decision_making.py | 220 ++++++++++++++++--------
1 file changed, 152 insertions(+), 68 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 790f5c99ef..801b5d9c3a 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -3,21 +3,28 @@
from matplotlib.gridspec import GridSpec
import numpy as np
-np.random.seed(123)
+np.random.seed(1234)
rng = np.random.default_rng()
dt = 0.1
nest.set(resolution=dt, print_time=True)
+g_AMPA_ex = 0.05
+g_AMPA_ext_ex = 2.1
+g_NMDA_ex = 0.165
+g_GABA_ex = 1.3
+
+g_AMPA_in = 0.04
+g_AMPA_ext_in = 1.62
+g_NMDA_in = 0.13
+g_GABA_in = 1.0
+
# Parameters from paper
-epop_params = {"g_AMPA": 0.05,
- "g_AMPA_ext": 2.1,
- "g_NMDA": 0.165,
- "g_GABA": 1.3,
- "tau_GABA": 5.0,
+epop_params = {"tau_GABA": 5.0,
"tau_AMPA": 2.0,
"tau_decay_NMDA": 100.0,
+ "tau_rise_NMDA": 2.0,
"alpha": 0.5,
"conc_Mg2": 1.0,
"g_L": 25., # leak conductance
@@ -31,13 +38,10 @@
}
-ipop_params = {"g_AMPA": 0.04,
- "g_AMPA_ext": 1.62,
- "g_NMDA": 0.13,
- "g_GABA": 1.0,
- "tau_GABA": 5.0,
+ipop_params = {"tau_GABA": 5.0,
"tau_AMPA": 2.0,
"tau_decay_NMDA": 100.0,
+ "tau_rise_NMDA": 2.0,
"alpha": 0.5,
"conc_Mg2": 1.0,
"g_L": 20., # leak conductance
@@ -57,8 +61,7 @@
f = 0.15 # proportion of neurons receiving signal inputs
w_plus = 1.7
w_minus = 1 - f * (w_plus - 1) / (1 - f)
-delay = 0.1
-
+delay = 0.5
NE = 1600
NI = 400
@@ -98,11 +101,22 @@
poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
-syn_spec_pot = {"synapse_model": "static_synapse", "weight":w_plus, "delay":delay, "receptor_type": 0}
-syn_spec_default = {"synapse_model": "static_synapse", "weight":1.0, "delay":delay, "receptor_type": 0}
-syn_spec_dep = {"synapse_model": "static_synapse", "weight":w_minus, "delay":delay, "receptor_type": 0}
-syn_spec_inhibitory = {"synapse_model": "static_synapse", "weight":-1.0, "delay":delay, "receptor_type": 0}
-syn_spec_ext = {"synapse_model": "static_synapse", "weight":1., "delay":0.1, "receptor_type": 1}
+syn_spec_pot_AMPA = {"synapse_model": "static_synapse", "weight":w_plus * g_AMPA_ex, "delay":delay, "receptor_type": 1}
+syn_spec_pot_NMDA = {"synapse_model": "static_synapse", "weight":w_plus * g_NMDA_ex, "delay":delay, "receptor_type": 3}
+
+syn_spec_dep_AMPA = {"synapse_model": "static_synapse", "weight":w_minus * g_AMPA_ex, "delay":delay, "receptor_type": 1}
+syn_spec_dep_NMDA = {"synapse_model": "static_synapse", "weight":w_minus * g_NMDA_ex, "delay":delay, "receptor_type": 3}
+
+ie_syn_spec = {"synapse_model": "static_synapse", "weight": -1.0 * g_GABA_ex, "delay":delay, "receptor_type": 2}
+ii_syn_spec = {"synapse_model": "static_synapse", "weight": -1.0 * g_GABA_in, "delay":delay, "receptor_type": 2}
+
+ei_syn_spec_AMPA = {"synapse_model": "static_synapse", "weight": 1.0 * g_AMPA_in, "delay":delay, "receptor_type": 1}
+ei_syn_spec_NMDA = {"synapse_model": "static_synapse", "weight": 1.0 * g_NMDA_in, "delay":delay, "receptor_type": 3}
+ee_syn_spec_AMPA = {"synapse_model": "static_synapse", "weight": 1.0 * g_AMPA_ex, "delay":delay, "receptor_type": 1}
+ee_syn_spec_NMDA = {"synapse_model": "static_synapse", "weight": 1.0 * g_NMDA_ex, "delay":delay, "receptor_type": 3}
+
+exte_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_ex, "delay":0.1, "receptor_type": 1}
+exti_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_in, "delay":0.1, "receptor_type": 1}
sr_nonselective = nest.Create("spike_recorder")
sr_selective1 = nest.Create("spike_recorder")
@@ -114,68 +128,76 @@
mm_nonselective = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-nest.Connect(poisson_0,
- nonselective_pop + selective_pop1 + selective_pop2 + inhibitory_pop,
- conn_spec="all_to_all",
- syn_spec=syn_spec_ext)
-
-nest.Connect(nonselective_pop,
- selective_pop1 + selective_pop2,
- conn_spec="all_to_all",
- syn_spec=syn_spec_dep)
-
-nest.Connect(nonselective_pop,
- nonselective_pop + inhibitory_pop,
- conn_spec="all_to_all",
- syn_spec=syn_spec_default)
-
-nest.Connect(selective_pop1,
- selective_pop2,
- conn_spec="all_to_all",
- syn_spec=syn_spec_dep)
-
-nest.Connect(selective_pop2,
- selective_pop1,
- conn_spec="all_to_all",
- syn_spec=syn_spec_dep)
-
-nest.Connect(selective_pop1 + selective_pop2,
- nonselective_pop + inhibitory_pop,
- conn_spec="all_to_all",
- syn_spec=syn_spec_default)
-
-nest.Connect(inhibitory_pop,
- selective_pop1 + selective_pop2 + nonselective_pop + inhibitory_pop,
- conn_spec="all_to_all",
- syn_spec=syn_spec_inhibitory)
-
-nest.Connect(poisson_a,
- selective_pop1,
- conn_spec="all_to_all",
- syn_spec=syn_spec_ext)
-
-nest.Connect(poisson_b,
- selective_pop2,
- conn_spec="all_to_all",
- syn_spec=syn_spec_ext)
+
+
+# # # Create connections
+
+# from external
+nest.Connect(poisson_0, nonselective_pop + selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=exte_syn_spec)
+nest.Connect(poisson_0, inhibitory_pop, conn_spec="all_to_all", syn_spec=exti_syn_spec)
+
+nest.Connect(poisson_a, selective_pop1, conn_spec="all_to_all", syn_spec=exte_syn_spec)
+nest.Connect(poisson_b, selective_pop2, conn_spec="all_to_all", syn_spec=exte_syn_spec)
+
+# from nonselective pop
+nest.Connect(nonselective_pop, selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_dep_AMPA)
+nest.Connect(nonselective_pop, selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_dep_NMDA)
+
+nest.Connect(nonselective_pop, nonselective_pop, conn_spec="all_to_all", syn_spec=ee_syn_spec_AMPA)
+nest.Connect(nonselective_pop, nonselective_pop, conn_spec="all_to_all", syn_spec=ee_syn_spec_NMDA)
+
+nest.Connect(nonselective_pop, inhibitory_pop, conn_spec="all_to_all", syn_spec=ei_syn_spec_AMPA)
+nest.Connect(nonselective_pop, inhibitory_pop, conn_spec="all_to_all", syn_spec=ei_syn_spec_NMDA)
nest.Connect(nonselective_pop, sr_nonselective)
+
+# from selective pops
+nest.Connect(selective_pop1, selective_pop1, conn_spec="all_to_all", syn_spec=syn_spec_pot_AMPA)
+nest.Connect(selective_pop1, selective_pop1, conn_spec="all_to_all", syn_spec=syn_spec_pot_NMDA)
+
+nest.Connect(selective_pop2, selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_pot_AMPA)
+nest.Connect(selective_pop2, selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_pot_NMDA)
+
+nest.Connect(selective_pop1, selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_dep_AMPA)
+nest.Connect(selective_pop1, selective_pop2, conn_spec="all_to_all", syn_spec=syn_spec_dep_NMDA)
+
+nest.Connect(selective_pop2, selective_pop1, conn_spec="all_to_all", syn_spec=syn_spec_dep_AMPA)
+nest.Connect(selective_pop2, selective_pop1, conn_spec="all_to_all", syn_spec=syn_spec_dep_NMDA)
+
+nest.Connect(selective_pop1 + selective_pop2, nonselective_pop, conn_spec="all_to_all", syn_spec=ee_syn_spec_AMPA)
+nest.Connect(selective_pop1 + selective_pop2, nonselective_pop, conn_spec="all_to_all", syn_spec=ee_syn_spec_NMDA)
+
+nest.Connect(selective_pop1 + selective_pop2, inhibitory_pop, conn_spec="all_to_all", syn_spec=ei_syn_spec_AMPA)
+nest.Connect(selective_pop1 + selective_pop2, inhibitory_pop, conn_spec="all_to_all", syn_spec=ei_syn_spec_NMDA)
+
nest.Connect(selective_pop1, sr_selective1)
nest.Connect(selective_pop2, sr_selective2)
+
+# from inhibitory pop
+nest.Connect(inhibitory_pop, selective_pop1 + selective_pop2 + nonselective_pop, conn_spec="all_to_all", syn_spec=ie_syn_spec)
+nest.Connect(inhibitory_pop, inhibitory_pop, conn_spec="all_to_all", syn_spec=ii_syn_spec)
+
nest.Connect(inhibitory_pop, sr_inhibitory)
+
+# multimeters
nest.Connect(mm_selective1, selective_pop1[0])
nest.Connect(mm_selective2, selective_pop2[0])
nest.Connect(mm_nonselective, nonselective_pop[0])
nest.Connect(mm_inhibitory, inhibitory_pop[0])
-nest.Simulate(4000.)
+nest.Simulate(5000.)
spikes_nonselective = sr_nonselective.get("events", "times")
spikes_selective1 = sr_selective1.get("events", "times")
spikes_selective2 = sr_selective2.get("events", "times")
spikes_inhibitory = sr_inhibitory.get("events", "times")
+vm_nonselective = mm_nonselective.get("events", "V_m")
+s_AMPA_nonselective = mm_nonselective.get("events", "s_AMPA")
+s_GABA_nonselective = mm_nonselective.get("events", "s_GABA")
+s_NMDA_nonselective = mm_nonselective.get("events", "s_NMDA")
+
vm_selective1 = mm_selective1.get("events", "V_m")
s_AMPA_selective1 = mm_selective1.get("events", "s_AMPA")
s_GABA_selective1 = mm_selective1.get("events", "s_GABA")
@@ -191,12 +213,74 @@
s_GABA_inhibitory = mm_inhibitory.get("events", "s_GABA")
s_NMDA_inhibitory = mm_inhibitory.get("events", "s_NMDA")
-gs = GridSpec(5,5)
-bins = np.arange(0, 4001, 5) - 0.001
-plt.hist(spikes_selective1, bins=bins, histtype="step")
-plt.hist(spikes_selective2, bins=bins, histtype="step")
+res = 1.0
+bins = np.arange(0, 4001, res) - 0.001
+
+fig, ax = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True)
+fig.tight_layout()
+d = NE * f * (res / 1000)
+hist1, _ = np.histogram(spikes_selective1, bins=bins)
+hist2, _ = np.histogram(spikes_selective2, bins=bins)
+
+ax[0,0].plot(hist1 / d)
+ax[0,0].set_title("Selective pop A")
+ax[0,1].plot(hist2 / d)
+ax[0,1].set_title("Selective pop B")
+
+d = NE * (1 - 2*f) * res / 1000
+hist, _ = np.histogram(spikes_nonselective, bins=bins)
+ax[1,0].plot(hist / d)
+ax[1,0].set_title("Nonselective pop")
+
+d = NI * res / 1000
+hist, _ = np.histogram(spikes_inhibitory, bins=bins)
+ax[1,1].plot(hist / d)
+ax[1,1].set_title("Inhibitory pop")
+
+
+
+fig, ax = plt.subplots(ncols=4, nrows=4, sharex=True, sharey="row")
+fig.tight_layout()
-plt.show()
+ax[0,0].plot(s_AMPA_selective1)
+ax[0,1].plot(s_AMPA_selective2)
+ax[0,2].plot(s_AMPA_nonselective)
+ax[0,3].plot(s_AMPA_inhibitory)
+
+ax[1,0].plot(s_NMDA_selective1)
+ax[1,1].plot(s_NMDA_selective2)
+ax[1,2].plot(s_NMDA_nonselective)
+ax[1,3].plot(s_NMDA_inhibitory)
+
+ax[2,0].plot(s_GABA_selective1)
+ax[2,1].plot(s_GABA_selective2)
+ax[2,2].plot(s_GABA_nonselective)
+ax[2,3].plot(s_GABA_inhibitory)
+
+ax[3,0].plot(vm_selective1)
+ax[3,1].plot(vm_selective2)
+ax[3,2].plot(vm_nonselective)
+ax[3,3].plot(vm_inhibitory)
+
+
+ax[0,0].set_ylabel("S_AMPA")
+ax[1,0].set_ylabel("S_NMDA")
+ax[2,0].set_ylabel("S_GABA")
+ax[3,0].set_ylabel("V_m")
+
+ax[0,0].set_title("Selective pop1")
+ax[0,1].set_title("Selective pop2")
+ax[0,2].set_title("Nonselective pop")
+ax[0,3].set_title("Inhibitory pop")
+
+ax[0,0].set_title("Selective pop1")
+ax[0,1].set_title("Selective pop2")
+ax[0,2].set_title("Nonselective pop")
+ax[0,3].set_title("Inhibitory pop")
+
+
+
+plt.show()
From 479788762a30d1c0b8699484e464304e806e0dbb Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 22 Jan 2024 15:14:43 +0100
Subject: [PATCH 044/184] documentation iaf_wang_2002_exact
---
models/iaf_wang_2002_exact.h | 50 ++++++++++++++++++++++--------------
1 file changed, 31 insertions(+), 19 deletions(-)
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 74dee84432..4f206d65b4 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -61,29 +61,42 @@ extern "C" inline int iaf_wang_2002_exact_dynamics( double, const double y[], do
Short description
+++++++++++++++++
-Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
+Leaky integrate-and-fire-neuron model with conductance based synapses, and additional NMDA receptors.
Description
+++++++++++
-This model implements a version of the neuron model described in [1]_.
+``iaf_wang_2002_exact_dynamics`` is a leaky integrate-and-fire neuron model with
-It contains AMPA, GABA and NMDA synapses, where the number of NMDA ports are dependent
-on the number of presynaptic connections.
+* an exact implementation of the neuron model described in [1]_.
+* exponential conductance-based AMPA and GABA-synapses
+* NMDA synapses with slow nonlinear dynamics
+* a fixed refractory period
+* no adaptation mechanisms
-The AMPA and GABA synapses are given as alpha functions, while the NMDA synapse is modeled
-with a non-linear function described by
+Neuron and synaptic dynamics
+..................................................
+
+The membrane potential and synaptic variables evolve according to
.. math::
- \frac{ dg_j^{NMDA}(t) }{ dt } = - \frac{ g_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
- \frac{ dx_j(t) }{ dt } =- \frac{ x_j(t) }{ \tau_{NMDA,rise} } + \sum_k \delta(t - t_j^k).
-The synaptic current of NMDA is given by
+ C_\mathrm{m} \frac{dV(t)}{dt} &= -g_\mathrm{L} (V(t) - V_\mathrm{L}) - I_\mathrm{syn} (t) \\[3ex]
+ I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
+ I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
+ I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
+ I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex]
+ \frac{dS_{j,\mathrm{AMPA}}}{dt} &= -\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}}+ \alpha x_j (1 - S_{j,\mathrm{NMDA}})\\[3ex]
+ \frac{dx_j}{dt} &= - \frac{x_j}{\tau_\mathrm{NMDA,rise}} + \sum_{k \in \Delta_j} \delta (t - t_j^k)
-.. math::
- I_{NMDA}(t) = \frac{ V(t) - E_{ex} }{ 1 + [Mg^{2+}]exp(-0.062V(t))/3.57 }\sum_{j=1}w_jg_j^{NMDA},
-where `w_j` is the weight of connection with presynaptic neuron `j`.
+where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
+
+Since :math:`S_{j,\mathrm{AMPA}}` and :math:`S_{j,\mathrm{GABA}}` are piecewise exponential functions, the sums are also a piecewise exponential function, and can be stored in a single synaptic variable each, :math:`S_{\mathrm{AMPA}}` and :math:`S_{\mathrm{GABA}}` respectively. The sum over :math:`S_{j,\mathrm{NMDA}}` does not have a simple expression, and cannot be simplified. Therefore, for each synapse, we need to integrate separate state variable, which makes the model slow.
+
+The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synaptic variable from the external population is not kept in a separate variable :math:`S_{\mathrm{rec,AMPA}}`, but is taken together with the local synapses in :math:`S_{\mathrm{AMPA}}`.
Parameters
@@ -118,18 +131,17 @@ The following values can be recorded.
=========== ===========================================================
V_m Membrane potential
- s_AMPA AMPA gate
- s_GABA GABA gate
- NMDA_sum sum of NMDA over all presynaptic neurons j
+ s_AMPA sum of AMPA gating variables
+ s_GABA sum of GABA gating variables
+ s_NMDA sum of NMDA gating variables
=========== ===========================================================
.. note::
- It is possible to set values for `V_m`, `s_AMPA` and `s_GABA` when creating the model, while the
- different `s_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
+ It is possible to set values for :math:`V_\mathrm{m}`, :math:`S_\mathrm{AMPA}` and :math:`S_\mathrm{GABA}` when creating the model, while the
+ different :math:`s_{j,\mathrm{NMDA}}` (`j` represents presynaptic neuron `j`) can not be set by the user.
.. note::
- The variable `s_AMPA` and `s_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
- in [1]_. `g_{recAMPA, extAMPA, GABA, NMBA}` from [1]_ is built into the weights in this NEST model, so setting the
+ :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST model, so setting the
variables is thus done by changing the weights.
Sends
From 4a97fe2a8d20f4cc7de9d3ac1478f79fc5fa34df Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 22 Jan 2024 19:41:01 +0100
Subject: [PATCH 045/184] commit all wong files, delete later
---
models/iaf_wang_2002.cpp | 18 +++++-
models/iaf_wang_2002_exact.cpp | 6 +-
models/iaf_wang_2002_exact.h | 2 +-
pynest/examples/wang_decision_making.py | 14 +++--
pynest/examples/wang_neuron.py | 33 +++++++----
pynest/examples/wang_neuron_exact.py | 5 ++
.../examples/wong_wang_nmda_approximation.py | 55 ++++++++++++++-----
7 files changed, 95 insertions(+), 38 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 60b7a04c7b..f6921565d5 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -44,6 +44,8 @@
// Includes from standard library
#include
+#include
+#include
/* ---------------------------------------------------------------------------
* Recordables map
@@ -390,10 +392,20 @@ nest::iaf_wang_2002::pre_run_hook()
assert( V_.RefractoryCounts_ >= 0 );
// compute S_NMDA jump height variables
- const double f1 = exp(-P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA)));
+ const double f1_old = exp(-P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA)));
+ const double f2_old = -(1 - exp(P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA))));
- const double f2 = -(1 - exp(P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA))));
- V_.S_jump_0 = f1 * f2;
+ // helper vars
+ const double at = P_.alpha * P_.tau_rise_NMDA;
+ const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
+ const double exp_at = exp(-P_.alpha * P_.tau_rise_NMDA);
+
+ const double f2 = -boost::math::expint(tau_rise_tau_dec, at) * at
+ + pow(at, tau_rise_tau_dec) * boost::math::tgamma(1 - tau_rise_tau_dec);
+
+ const double f1 = exp_at;
+
+ V_.S_jump_0 = f2;
V_.S_jump_1 = f1;
}
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 18fe3dacc8..b974f0d7b6 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -66,7 +66,7 @@ RecordablesMap< iaf_wang_2002_exact >::create()
insert_( names::V_m, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::V_m > );
insert_( names::s_AMPA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::s_AMPA > );
insert_( names::s_GABA, &iaf_wang_2002_exact::get_ode_state_elem_< iaf_wang_2002_exact::State_::s_GABA > );
- insert_( names::NMDA_sum, &iaf_wang_2002_exact::get_NMDA_sum_ );
+ insert_( names::s_NMDA, &iaf_wang_2002_exact::get_s_NMDA_ );
}
}
/* ---------------------------------------------------------------------------
@@ -238,8 +238,8 @@ nest::iaf_wang_2002_exact::State_::get( DictionaryDatum& d ) const
def< double >( d, names::s_GABA, ode_state_[ s_GABA ] );
// total NMDA sum
- double NMDA_sum = get_NMDA_sum();
- def< double >( d, names::NMDA_sum, NMDA_sum );
+ double s_NMDA = get_NMDA_sum();
+ def< double >( d, names::NMDA_sum, s_NMDA );
}
void
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 4f206d65b4..70b0e8b789 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -394,7 +394,7 @@ class iaf_wang_2002_exact : public ArchivingNode
//! Get the sum of NMDA from state, used by UniversalDataLogger
double
- get_NMDA_sum_() const
+ get_s_NMDA_() const
{
return S_.get_NMDA_sum();
}
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 801b5d9c3a..aed2c31798 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -1,3 +1,7 @@
+"""
+docstring
+"""
+
import nest
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
@@ -6,6 +10,8 @@
np.random.seed(1234)
rng = np.random.default_rng()
+model = "iaf_wang_2002_exact"
+
dt = 0.1
nest.set(resolution=dt, print_time=True)
@@ -66,10 +72,10 @@
NE = 1600
NI = 400
-selective_pop1 = nest.Create("iaf_wang_2002", int(0.15 * NE), params=epop_params)
-selective_pop2 = nest.Create("iaf_wang_2002", int(0.15 * NE), params=epop_params)
-nonselective_pop = nest.Create("iaf_wang_2002", int(0.7 * NE), params=epop_params)
-inhibitory_pop = nest.Create("iaf_wang_2002", NI, params=ipop_params)
+selective_pop1 = nest.Create(model, int(0.15 * NE), params=epop_params)
+selective_pop2 = nest.Create(model, int(0.15 * NE), params=epop_params)
+nonselective_pop = nest.Create(model, int(0.7 * NE), params=epop_params)
+inhibitory_pop = nest.Create(model, NI, params=ipop_params)
mu_0 = 40.
rho_a = mu_0 / 100
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index f2968cb907..39a7d221fe 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -1,3 +1,6 @@
+"""
+docstring
+"""
import nest
import matplotlib.pyplot as plt
import numpy as np
@@ -6,7 +9,7 @@
nest.rng_seed = 12345
w_ext = 40.
-w_ex = 5.
+w_ex = 1.
w_in = -15.
params_exact = {"tau_AMPA": 2.0,
@@ -25,7 +28,7 @@
"t_ref": 2.0}
params_approx = params_exact.copy()
-del params_approx["tau_rise_NMDA"]
+params_approx["approx_t_exact"] = 200
nrn1 = nest.Create("iaf_wang_2002", params_approx)
pg = nest.Create("poisson_generator", {"rate": 50.})
@@ -36,7 +39,7 @@
mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
ampa_ext_syn_spec = {"synapse_model": "static_synapse",
"weight": w_ext,
@@ -103,11 +106,19 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
spikes = sr.get("events", "times")
spikes = times[diff < -3]
-# compute analytical solutions
-times = mm1.get("events", "times")
-# ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
-# nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
-# gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
+ a = (1 / tau_decay - 1 / tau_rise)
+
+ # argument of exp
+ arg = t * a + alpha * x0 * tau_rise * (1 - np.exp(- t / tau_rise))
+ return np.exp(arg)
+
+def nmda_fn(t, s0, x0, tau_decay, tau_rise, alpha):
+ f1 = np.exp(- t / tau_decay + alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
+
+ tvec = np.linspace(0, t, 1001)
+ f2 = alpha * x0 * np.trapz(nmda_integrand(tvec, x0, tau_decay, tau_rise, alpha), x = tvec) + s0
+ return f1, f2
fig, ax = plt.subplots(4,2)
fig.set_size_inches([12,10])
@@ -119,9 +130,8 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
ax[0,0].legend()
ax[0,0].set_title("Presynaptic neuron")
-
ax[0,1].plot(mm2.events["V_m"])
-ax[0,1].plot(mm3.events["V_m"])
+ax[0,1].plot(mm3.events["V_m"], "--")
ax[0,1].set_xlabel("time (ms)")
ax[0,1].set_ylabel("membrane potential V (mV)")
ax[0,1].set_title("Postsynaptic neuron")
@@ -140,7 +150,7 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
ax[3,1].plot(mm2.events["s_NMDA"])
-ax[3,1].plot(mm3.events["NMDA_sum"], "--")
+ax[3,1].plot(mm3.events["s_NMDA"], "--")
ax[3,1].set_xlabel("time (ms)")
ax[3,1].set_ylabel("s_NMDA")
@@ -148,6 +158,5 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
ax[2,0].axis("off")
ax[3,0].axis("off")
-
plt.show()
diff --git a/pynest/examples/wang_neuron_exact.py b/pynest/examples/wang_neuron_exact.py
index 307506618f..ff7514d9f9 100644
--- a/pynest/examples/wang_neuron_exact.py
+++ b/pynest/examples/wang_neuron_exact.py
@@ -1,3 +1,8 @@
+"""
+docstring
+"""
+
+
import nest
import matplotlib.pyplot as plt
import numpy as np
diff --git a/pynest/examples/wong_wang_nmda_approximation.py b/pynest/examples/wong_wang_nmda_approximation.py
index 5ec7366322..0655c2bd2e 100644
--- a/pynest/examples/wong_wang_nmda_approximation.py
+++ b/pynest/examples/wong_wang_nmda_approximation.py
@@ -1,3 +1,7 @@
+"""
+docstring
+"""
+
import nest
import matplotlib.pyplot as plt
import numpy as np
@@ -95,19 +99,14 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
nest.Simulate(1000.)
-# get spike times from membrane potential
-# cannot use spike_recorder because we abuse exact spike timing
-V_m = mm1.get("events", "V_m")
-times = mm1.get("events", "times")
-diff = np.ediff1d(V_m, to_begin=0.)
-spikes = sr.get("events", "times")
-spikes = times[diff < -3]
-# compute analytical solutions
-times = mm1.get("events", "times")
-# ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
-# nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
-# gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+times = mm3.get("events", "times")
+nest_nmda = mm3.get("events", "NMDA_sum")
+nest_nmda_approx = mm2.get("events", "s_NMDA")
+times -= times[(nest_nmda > 0).argmax()] - 0.1
+
+
+
from scipy.integrate import cumtrapz
def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
@@ -134,7 +133,7 @@ def nmda_fn_approx(t, x0, tau_decay, tau_rise, alpha):
t = np.arange(0.1, 1000, 0.1)
-s0 = 0.1
+s0 = 0.
f1s, f2s = [], []
for t_ in t:
f1, f2 = nmda_fn(t_, 0., 1., 100., 2., 0.5)
@@ -153,13 +152,39 @@ def nmda_approx_exp(t, tau_rise, alpha, tau_decay):
f1_exp, f2_exp = nmda_approx_exp(t, 2.0, 0.5, 100)
-i = 50
+i = 40
s_nmda_exp = (f1_exp[i] * f2_exp[i] + f1_exp[i] * s0) * np.exp(-t / 100)
plt.plot(t, s_nmda, color="C0")
plt.plot(t, s_nmda_approx, color="C1")
plt.plot(t, s_nmda_exp, "--", color="C2")
-#plt.plot(t, f1_exp * f2_exp, "--", color="C2")
plt.show()
+
+
+
+from scipy.special import expn, gamma
+def limfun(tau_rise, tau_decay, alpha):
+ f0 = np.exp(-alpha * tau_rise)
+
+ at = alpha * tau_rise
+ tr_td = tau_rise / tau_decay
+ f1 = -at * expn(tr_td, at) + at ** tr_td * gamma(1 - tr_td)
+
+ return f0, f1
+
+f0, f1 = limfun(2, 100, 0.5)
+
+
+
+
+plt.plot(t, s_nmda)
+plt.plot(times, nest_nmda)
+plt.plot(times, nest_nmda_approx)
+plt.plot(t, f1 * np.exp(-t / 100))
+
+plt.show()
+
+
+
From bf4c4b6a1ea4c42dc5e1cb5009e9ab533e708de1 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 24 Jan 2024 14:26:47 +0100
Subject: [PATCH 046/184] clean up
---
models/iaf_wang_2002.cpp | 15 ++-------------
models/iaf_wang_2002_exact.cpp | 3 ++-
2 files changed, 4 insertions(+), 14 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index f6921565d5..b3627fd14b 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -391,23 +391,13 @@ nest::iaf_wang_2002::pre_run_hook()
// since t_ref_ >= 0, this can only fail in error
assert( V_.RefractoryCounts_ >= 0 );
- // compute S_NMDA jump height variables
- const double f1_old = exp(-P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA)));
- const double f2_old = -(1 - exp(P_.alpha * P_.tau_rise_NMDA * (1 - exp(-P_.approx_t_exact / P_.tau_rise_NMDA))));
-
// helper vars
const double at = P_.alpha * P_.tau_rise_NMDA;
const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
- const double exp_at = exp(-P_.alpha * P_.tau_rise_NMDA);
- const double f2 = -boost::math::expint(tau_rise_tau_dec, at) * at
+ V_.S_jump_1 = exp(-P_.alpha * P_.tau_rise_NMDA);
+ V_.S_jump_0 = -boost::math::expint(tau_rise_tau_dec, at) * at
+ pow(at, tau_rise_tau_dec) * boost::math::tgamma(1 - tau_rise_tau_dec);
-
- const double f1 = exp_at;
-
- V_.S_jump_0 = f2;
- V_.S_jump_1 = f1;
-
}
void
@@ -464,7 +454,6 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// add incoming spikes
S_.y_[ State_::s_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
S_.y_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
-
S_.y_[ State_::s_NMDA ] += B_.spikes_[ NMDA - 1 ].get_value( lag );
if ( S_.r_ )
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index b974f0d7b6..a2fddd99a6 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -571,6 +571,8 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
// rport starts at 1, so subtract one to get correct index
B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
+ // we need to scale each individual S_j variable by its weight,
+ // so we keep track of the weight.
const size_t w_idx = rport - NMDA;
if ( B_.weights_[ w_idx ] == 0.0 )
{
@@ -578,7 +580,6 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
}
else if ( B_.weights_[ w_idx ] != e.get_weight() )
{
- // Why??
throw KernelException( "iaf_wang_2002_exact requires constant weights." );
}
}
From 9e5d9326ffa214bbb16a7b0e625ea5d46fa8669a Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Fri, 26 Jan 2024 12:53:14 +0100
Subject: [PATCH 047/184] add benchmarking script
---
pynest/examples/wang_benchmarks.py | 417 +++++++++++++++++++++++++++++
1 file changed, 417 insertions(+)
create mode 100644 pynest/examples/wang_benchmarks.py
diff --git a/pynest/examples/wang_benchmarks.py b/pynest/examples/wang_benchmarks.py
new file mode 100644
index 0000000000..33b07d0e8f
--- /dev/null
+++ b/pynest/examples/wang_benchmarks.py
@@ -0,0 +1,417 @@
+"""
+Check that NEST implementation gives same results as a reference implementation from Brian.
+Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
+a separate parameter. This is the only difference in parameterization between the two models.
+Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
+so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
+"""
+
+
+import brian2 as b2
+import nest
+import numpy as np
+import time, os
+import statistics
+import matplotlib.pyplot as plt
+from pathlib import Path
+
+
+path = Path(__file__).parent
+outfile = os.path.join(path, "wang_benchmark_log.csv")
+if not os.path.isfile(outfile):
+ with open(outfile, "w") as f:
+ f.write("brian_time,nest_time_exact,nest_time_approx,NE,NI\n")
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Parameters
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+simtime = 1000
+resolution = 0.01
+NE = 200
+NI = 200
+
+V_th = -55 * b2.mV
+V_reset = -70 * b2.mV
+t_ref = 2 * b2.ms
+
+# parameters for the equation of the neuron
+# (Inhibitory and excitatory neurons have different parameters)
+g_L = 25. * b2.nS
+C_m = 0.5 * b2.nF
+
+g_AMPA_rec = 2.0 * b2.nS
+g_AMPA_ext = 1.0 *b2.nS
+g_GABA = 1. * b2.nS
+g_NMDA = 1. * b2.nS
+
+# reversal potentials
+E_L = V_reset
+E_ex= 0. * b2.mV
+E_in = -70. * b2.mV
+
+# time constant of the receptors
+tau_AMPA= 2 * b2.ms
+tau_GABA= 5 * b2.ms
+tau_NMDA_rise = 2. * b2.ms
+tau_NMDA_decay = 100. * b2.ms
+
+# additional NMDA parameters
+alpha = 0.5 / b2.ms
+Mg2 = 1.
+
+# synaptic weights
+weight_AMPA_ext = 1.
+weight_AMPA = 1. * 50 / NE
+weight_NMDA = 1. * 50 / NE
+weight_GABA = 1. * 50 / NI
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Brian simulation
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+## Equations
+eqsE="""
+
+ dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
+ I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
+
+ I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
+ ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
+ #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
+
+ I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
+ s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
+ #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
+ #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
+
+ I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
+ s_GABA_tot :1
+
+ I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
+ s_NMDA_tot : 1
+
+ """
+
+
+
+eqsI="""
+
+ dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
+ I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA : amp
+
+ I_AMPA_ext= g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
+ ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
+ # Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
+
+
+ I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
+ s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
+ #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
+ #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
+
+ I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
+ s_GABA_tot :1
+
+ I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
+ s_NMDA_tot : 1
+
+ """
+
+eqs_ampa="""
+ s_AMPA_tot_post = w_AMPA * s_AMPA : 1 (summed)
+ ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
+ w_AMPA: 1
+ """
+
+eqs_gaba="""
+ s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
+ ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
+ w_GABA: 1
+ """
+
+eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
+ ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
+ dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
+ w_NMDA : 1
+ """
+
+
+
+#Create the two population with the corresponfing equations:
+popE = b2.NeuronGroup(NE, model=eqsE, threshold="v > V_th", reset="v = E_L", refractory=t_ref, method="rk4")
+popI= b2.NeuronGroup(NI, model=eqsI, threshold="v > V_th", reset="v = E_L", refractory=t_ref, method="rk4")
+
+
+#Set the initial value of the potential v for all the neurons
+for k in range(0,NE):
+ popE[k].v[0]=E_L
+
+for k in range(0,NI):
+ popI[k].v[0]=E_L
+
+
+# Connect the neurons of popE with the neurons of popI (for the ampa connections)
+conn= b2.Synapses(popE,popI,model=eqs_ampa,on_pre="s_AMPA+=1", method="rk4")
+conn.connect()
+conn.w_AMPA= weight_AMPA
+conn.delay = 1.0 * b2.ms
+
+# Connect the neurons of popE with the neurons of popI (for the NMDA connections)
+conn1= b2.Synapses(popE,popI,model=eqs_nmda,on_pre="x+=1", method="rk4")
+conn1.connect()
+conn1.w_NMDA= weight_NMDA
+conn1.delay = 1.0 * b2.ms
+
+# Connect the neurons of popE with the neurons of popE (for the AMPA connections)
+conn2= b2.Synapses(popE,popE,model=eqs_ampa,on_pre="s_AMPA+=1", method="rk4")
+conn2.connect()
+conn2.w_AMPA= weight_AMPA
+conn2.delay = 1.0 * b2.ms
+
+# Connect the neurons of popE with the neurons of popE (for the NMDA connections)
+conn3= b2.Synapses(popE,popE,model=eqs_nmda,on_pre="x+=1", method="rk4")
+conn3.connect()
+conn3.w_NMDA= weight_NMDA
+conn3.delay = 1.0 * b2.ms
+
+# Connect the neurons of popI with the neurons of popE (for the GABA connections)
+conn4= b2.Synapses(popI,popE,model = eqs_gaba,on_pre="s_GABA+=1", method="rk4")
+conn4.connect()
+conn4.w_GABA= weight_GABA
+conn4.delay = 1.0 * b2.ms
+
+# Connect the neurons of popI with the neurons of popI (for the GABA connections)
+conn5= b2.Synapses(popI,popI,model = eqs_gaba,on_pre="s_GABA+=1", method="rk4")
+conn5.connect()
+conn5.w_GABA= weight_GABA
+conn5.delay = 1.0 * b2.ms
+
+# To excitatory neurons
+rate_E = 4000 * b2.Hz
+ext_inputE = b2.PoissonGroup(NE, rates = rate_E)
+ext_connE = b2.Synapses(ext_inputE, popE, on_pre="s_AMPA_ext += 1")
+ext_connE.connect(j="i")
+ext_connE.delay = 1.0 * b2.ms
+
+# To inhibitory neurons
+rate_I=4000 * b2.Hz
+ext_inputI= b2.PoissonGroup(NI, rates = rate_I)
+ext_connI = b2.Synapses(ext_inputI, popI, on_pre="s_AMPA_ext += 1")
+ext_connI.connect(j="i")
+ext_connI.delay = 1.0 * b2.ms
+
+# Recorder to save the spikes of the neurons
+S_e = b2.SpikeMonitor(popE[:NE], record=True)
+S_i = b2.SpikeMonitor(popI[:NI], record=True)
+
+b2.defaultclock.dt = resolution * b2.ms
+tic = time.time()
+b2.run(simtime * b2.ms)
+toc = time.time()
+brian_time = toc - tic
+
+brian_espikes = S_e.spike_trains()
+brian_espikes = np.array(np.concatenate(tuple(brian_espikes.values()))) * 1000.
+brian_ispikes = S_i.spike_trains()
+brian_ispikes = np.array(np.concatenate(tuple(brian_ispikes.values()))) * 1000.
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# NEST simulation exact
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+nest.rng_seed = 12345
+
+nest.ResetKernel()
+nest.resolution = resolution
+
+neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
+ "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
+ "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
+ "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
+ "conc_Mg2": np.asarray(Mg2), # dimensionless
+ "E_ex": np.asarray(E_ex) * 1e3, # units mV
+ "E_in": np.asarray(E_in) * 1e3, # units mV
+ "E_L": np.asarray(E_L) * 1e3, # units mV
+ "V_th": np.asarray(V_th) * 1e3, # units mV
+ "C_m": np.asarray(C_m) * 1e12, # units pF
+ "g_L": np.asarray(g_L) * 1e9, # units nS
+ "V_reset": np.asarray(V_reset) * 1e3, # units nS
+ "alpha": np.asarray(alpha * b2.ms), # units nS
+ # DIFFERENCE: subtract 0.1 ms from t_ref
+ "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
+
+
+
+poisson = nest.Create("poisson_generator", {"rate": 4000.})
+epop = nest.Create("iaf_wang_2002_exact", NE, params=neuron_params)
+ipop = nest.Create("iaf_wang_2002_exact", NI, params=neuron_params)
+
+sr_ex = nest.Create("spike_recorder")
+sr_in = nest.Create("spike_recorder")
+
+
+# DIFFERENCE: add 0.1ms to delay
+nest_delay = 1. + b2.defaultclock.dt / b2.ms
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+ex_syn_spec_ext = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_ext) * 1e9 * weight_AMPA_ext, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
+ "receptor_type": 3,
+ "delay": nest_delay}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
+ "receptor_type": 2,
+ "delay": nest_delay}
+
+conn_spec = {"rule": "all_to_all"}
+
+nest.Connect(poisson, epop + ipop, syn_spec=ex_syn_spec_ext, conn_spec="all_to_all")
+nest.Connect(epop, sr_ex)
+nest.Connect(ipop, sr_in)
+nest.Connect(epop, epop + ipop, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(epop, epop + ipop, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(ipop, ipop + epop, syn_spec=in_syn_spec, conn_spec=conn_spec)
+
+tic = time.time()
+nest.Simulate(simtime)
+toc = time.time()
+nest_time_exact = toc - tic
+
+nest_espikes_exact = sr_ex.get("events", "times")
+nest_ispikes_exact = sr_in.get("events", "times")
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# NEST simulation approximate
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+nest.rng_seed = 12345
+
+nest.ResetKernel()
+nest.resolution = resolution
+
+neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
+ "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
+ "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
+ "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
+ "conc_Mg2": np.asarray(Mg2), # dimensionless
+ "E_ex": np.asarray(E_ex) * 1e3, # units mV
+ "E_in": np.asarray(E_in) * 1e3, # units mV
+ "E_L": np.asarray(E_L) * 1e3, # units mV
+ "V_th": np.asarray(V_th) * 1e3, # units mV
+ "C_m": np.asarray(C_m) * 1e12, # units pF
+ "g_L": np.asarray(g_L) * 1e9, # units nS
+ "V_reset": np.asarray(V_reset) * 1e3, # units nS
+ "alpha": np.asarray(alpha * b2.ms), # units nS
+ # DIFFERENCE: subtract 0.1 ms from t_ref
+ "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
+
+
+
+poisson = nest.Create("poisson_generator", {"rate": 4000.})
+epop = nest.Create("iaf_wang_2002", NE, params=neuron_params)
+ipop = nest.Create("iaf_wang_2002", NI, params=neuron_params)
+
+sr_ex = nest.Create("spike_recorder")
+sr_in = nest.Create("spike_recorder")
+
+# DIFFERENCE: add 0.1ms to delay
+nest_delay = 1. + b2.defaultclock.dt / b2.ms
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+ex_syn_spec_ext = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_ext) * 1e9 * weight_AMPA_ext, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
+ "receptor_type": 3,
+ "delay": nest_delay}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
+ "receptor_type": 2,
+ "delay": nest_delay}
+
+conn_spec = {"rule": "all_to_all"}
+
+nest.Connect(poisson, epop + ipop, syn_spec=ex_syn_spec_ext, conn_spec="all_to_all")
+nest.Connect(epop, sr_ex)
+nest.Connect(ipop, sr_in)
+nest.Connect(epop, epop + ipop, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(epop, epop + ipop, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(ipop, ipop + epop, syn_spec=in_syn_spec, conn_spec=conn_spec)
+
+tic = time.time()
+nest.Simulate(simtime)
+toc = time.time()
+nest_time_approx = toc - tic
+
+with open(outfile, "a") as f:
+ f.write(f"{brian_time},{nest_time_exact},{nest_time_approx},{NE},{NI}\n")
+
+nest_espikes_approx = sr_ex.get("events", "times")
+nest_ispikes_approx = sr_in.get("events", "times")
+
+
+# Plotting
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+print(f"Time NEST exact: {nest_time_exact}")
+print(f"Time NEST approx: {nest_time_approx}")
+print(f"Time Brian2 exact: {brian_time}")
+
+print(f"Total excitatory spikes Brian exact: {len(brian_espikes)}")
+print(f"Total inhibitory spikes Brian exact: {len(brian_ispikes)}")
+
+print(f"Total excitatory spikes NEST exact: {len(nest_espikes_exact)}")
+print(f"Total inhibitory spikes NEST exact: {len(nest_ispikes_exact)}")
+
+print(f"Total excitatory spikes NEST approx: {len(nest_espikes_approx)}")
+print(f"Total inhibitory spikes NEST approx: {len(nest_ispikes_approx)}")
+
+bins = np.arange(0, simtime+1, 1) - 0.001
+nest_ehist_exact, _ = np.histogram(nest_espikes_exact, bins=bins)
+nest_ihist_exact, _ = np.histogram(nest_ispikes_exact, bins=bins)
+
+nest_ehist_approx, _ = np.histogram(nest_espikes_approx, bins=bins)
+nest_ihist_approx, _ = np.histogram(nest_ispikes_approx, bins=bins)
+
+brian_ehist, _ = np.histogram(brian_espikes, bins=bins)
+brian_ihist, _ = np.histogram(brian_ispikes, bins=bins)
+
+fig, ax = plt.subplots(ncols=2, nrows=3, sharex=True, sharey=True)
+ax[0,0].plot(nest_ehist_exact * 1000 / NE)
+ax[1,0].plot(nest_ehist_approx * 1000 / NE)
+ax[2,0].plot(brian_ehist * 1000 / NE)
+
+ax[0,1].plot(nest_ihist_exact * 1000 / NI)
+ax[1,1].plot(nest_ihist_approx * 1000 / NI)
+ax[2,1].plot(brian_ihist * 1000 / NI)
+
+ax[0,0].set_title("excitatory")
+ax[0,1].set_title("inhibitory")
+ax[2,0].set_xlabel("time (ms)")
+ax[2,1].set_xlabel("time (ms)")
+ax[0,0].set_ylabel("NEST exact")
+ax[1,0].set_ylabel("NEST approx")
+ax[2,0].set_ylabel("Brian")
+plt.show()
+
From 565bf1b960fd6e2cf6e36e3192534b2d0a6c4671 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 30 Jan 2024 14:49:36 +0100
Subject: [PATCH 048/184] add all examples scripts, remove later
---
pynest/examples/wang_approx_compare_brian.py | 257 ++++++++++++++++++
pynest/examples/wang_benchmarks.py | 6 +-
pynest/examples/wang_compare_brian.py | 69 +++--
pynest/examples/wang_decision_making.py | 3 +-
.../examples/wong_wang_nmda_approximation.py | 34 ++-
5 files changed, 317 insertions(+), 52 deletions(-)
create mode 100644 pynest/examples/wang_approx_compare_brian.py
diff --git a/pynest/examples/wang_approx_compare_brian.py b/pynest/examples/wang_approx_compare_brian.py
new file mode 100644
index 0000000000..20a3095bbb
--- /dev/null
+++ b/pynest/examples/wang_approx_compare_brian.py
@@ -0,0 +1,257 @@
+"""
+Check that NEST implementation gives same results as a reference implementation from Brian.
+Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
+a separate parameter. This is the only difference in parameterization between the two models.
+Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
+so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
+"""
+
+
+import brian2 as b2
+import nest
+import numpy as np
+import time
+import statistics
+import matplotlib.pyplot as plt
+
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Parameters
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+V_th = -55 * b2.mV
+V_reset = -70 * b2.mV
+t_ref = 2 * b2.ms
+
+# parameters for the equation of the neuron
+# (Inhibitory and excitatory neurons have different parameters)
+g_L = 25. * b2.nS
+C_m = 0.5 * b2.nF
+
+g_AMPA_rec = 1.0 * b2.nS
+g_AMPA_ext = 100.0 *b2.nS
+g_GABA = 1.0 * b2.nS
+g_NMDA = 1.0 * b2.nS
+
+# reversal potentials
+E_L = V_reset
+E_ex= 0. * b2.mV
+E_in = -70. * b2.mV
+
+# time constant of the receptors
+tau_AMPA= 2 * b2.ms
+tau_GABA= 5 * b2.ms
+tau_NMDA_rise = 2. * b2.ms
+tau_NMDA_decay = 100. * b2.ms
+
+# additional NMDA parameters
+alpha = 0.5 / b2.ms
+Mg2 = 1.
+
+# synaptic weights
+weight_AMPA = 1.
+weight_GABA = 3.
+weight_NMDA = 2.
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Brian simulation
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+## Equations
+eqsE="""
+
+ dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
+ I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
+
+ I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
+ ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
+ #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
+
+
+ I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
+ s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
+ #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
+ #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
+
+ I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
+ s_GABA_tot :1
+
+
+ I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
+ s_NMDA_tot : 1
+
+ """
+
+eqs_ampa="""
+ s_AMPA_tot_post= w_AMPA * s_AMPA : 1 (summed)
+ ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
+ w_AMPA: 1
+ """
+
+eqs_gaba="""
+ s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
+ ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
+ w_GABA: 1
+ """
+
+eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
+ ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
+ dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
+ w_NMDA : 1
+ """
+
+nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
+nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
+
+nrn1[0].v[0]=V_reset
+nrn2[0].v[0]=V_reset
+
+times = np.array([10, 20, 40, 80, 90]) * b2.ms
+indices = np.arange(len(times))
+spikeGen = b2.SpikeGeneratorGroup(len(times), indices, times)
+
+ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1")
+ext_conn1.connect()
+ext_conn1.delay = 1.0 * b2.ms
+
+conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="rk4")
+conn2.connect()
+conn2.w_AMPA = weight_AMPA
+conn2.delay = 1.0 * b2.ms
+
+conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="rk4")
+conn3.connect()
+conn3.w_NMDA = weight_NMDA
+conn3.delay = 1.0 * b2.ms
+
+conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="rk4")
+conn4.connect()
+conn4.w_GABA = weight_GABA
+conn4.delay = 1.0 * b2.ms
+
+vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
+ampaMonitor1 = b2.StateMonitor(nrn1, "s_AMPA_ext",record=True)
+gabaMonitor1 = b2.StateMonitor(nrn1, "s_GABA_tot",record=True)
+nmdaMonitor1 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
+
+vMonitor2 = b2.StateMonitor(nrn2, "v", record=True)
+ampaMonitor2 = b2.StateMonitor(nrn2, "s_AMPA_tot", record=True)
+gabaMonitor2 = b2.StateMonitor(nrn2, "s_GABA_tot", record=True)
+nmdaMonitor2 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
+
+t_sim = 300
+b2.run(t_sim * b2.ms)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# NEST simulation
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+nest.rng_seed = 12345
+
+nest.ResetKernel()
+
+neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
+ "E_ex": np.asarray(E_ex) * 1e3, # units mV
+ "E_L": np.asarray(E_L) * 1e3, # units mV
+ "V_th": np.asarray(V_th) * 1e3, # units mV
+ "C_m": np.asarray(C_m) * 1e12, # units pF
+ "g_L": np.asarray(g_L) * 1e9, # units nS
+ "V_reset": np.asarray(V_reset) * 1e3, # units nS
+ # DIFFERENCE: subtract 0.1 ms from t_ref
+ "t_ref": np.asarray(t_ref) * 1e3 - 0.1} # units ms
+
+nrn1 = nest.Create("iaf_wang_2002", neuron_params)
+nrn2 = nest.Create("iaf_wang_2002", neuron_params)
+
+times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
+sg = nest.Create("spike_generator", {"spike_times": times})
+sr = nest.Create("spike_recorder")
+
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_GABA", "s_NMDA"], "interval": 0.1})
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_GABA", "s_NMDA"], "interval": 0.1})
+
+# DIFFERENCE: add 0.1ms to delay
+nest_delay = 1.1
+ex_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+ex_syn_spec_ext = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
+ "receptor_type": 1,
+ "delay": nest_delay}
+
+nmda_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
+ "receptor_type": 3,
+ "delay": nest_delay}
+
+in_syn_spec = {"synapse_model": "static_synapse",
+ "weight": np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
+ "receptor_type": 2,
+ "delay": nest_delay}
+
+conn_spec = {"rule": "all_to_all"}
+
+
+nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
+nest.Connect(nrn1, sr)
+nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+nest.Connect(mm1, nrn1)
+nest.Connect(mm2, nrn2)
+
+nest.Simulate(300.)
+
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Plotting
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+
+fig, ax = plt.subplots(4, 2)
+fig.set_size_inches([12,10])
+fig.subplots_adjust(hspace=0.5)
+ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
+ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), "--", label="nest")
+ax[0,0].set_xlabel("time (ms)")
+ax[0,0].set_ylabel("membrane potential V (mV)")
+ax[0,0].legend()
+ax[0,0].set_title("Presynaptic neuron")
+
+ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
+ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--")
+ax[0,1].set_xlabel("time (ms)")
+ax[0,1].set_ylabel("membrane potential V (mV)")
+ax[0,1].set_title("Postsynaptic neuron")
+
+# multiply by g_AMPA_ext since it is baked into s_AMPA in NEST
+ax[1,0].plot(ampaMonitor1.t/b2.ms, ampaMonitor1.s_AMPA_ext[0] * g_AMPA_ext / b2.nS)
+ax[1,0].plot(mm1.get("events", "times"), mm1.get("events", "s_AMPA"), "--")
+ax[1,0].set_xlabel("time (ms)")
+ax[1,0].set_ylabel("s_AMPA")
+
+ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0])
+ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--")
+ax[1,1].set_xlabel("time (ms)")
+ax[1,1].set_ylabel("s_AMPA")
+
+ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0])
+ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--")
+ax[2,1].set_xlabel("time (ms)")
+ax[2,1].set_ylabel("s_GABA")
+
+ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0])
+ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "s_NMDA"), "--")
+ax[3,1].set_xlabel("time (ms)")
+ax[3,1].set_ylabel("s_NMDA")
+
+ax[2,0].axis("off")
+ax[3,0].axis("off")
+
+plt.show()
+
+
diff --git a/pynest/examples/wang_benchmarks.py b/pynest/examples/wang_benchmarks.py
index 33b07d0e8f..e374c53851 100644
--- a/pynest/examples/wang_benchmarks.py
+++ b/pynest/examples/wang_benchmarks.py
@@ -191,14 +191,14 @@
# To excitatory neurons
rate_E = 4000 * b2.Hz
ext_inputE = b2.PoissonGroup(NE, rates = rate_E)
-ext_connE = b2.Synapses(ext_inputE, popE, on_pre="s_AMPA_ext += 1")
+ext_connE = b2.Synapses(ext_inputE, popE, on_pre="s_AMPA_ext += 1", method="rk4")
ext_connE.connect(j="i")
ext_connE.delay = 1.0 * b2.ms
# To inhibitory neurons
-rate_I=4000 * b2.Hz
+rate_I= 4000 * b2.Hz
ext_inputI= b2.PoissonGroup(NI, rates = rate_I)
-ext_connI = b2.Synapses(ext_inputI, popI, on_pre="s_AMPA_ext += 1")
+ext_connI = b2.Synapses(ext_inputI, popI, on_pre="s_AMPA_ext += 1", method="rk4")
ext_connI.connect(j="i")
ext_connI.delay = 1.0 * b2.ms
diff --git a/pynest/examples/wang_compare_brian.py b/pynest/examples/wang_compare_brian.py
index 63676ad67c..df8a39b014 100644
--- a/pynest/examples/wang_compare_brian.py
+++ b/pynest/examples/wang_compare_brian.py
@@ -51,8 +51,8 @@
# synaptic weights
weight_AMPA = 1.
-weight_GABA = 3.
-weight_NMDA = 2.
+weight_GABA = 1.
+weight_NMDA = 1.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
@@ -100,7 +100,7 @@
w_NMDA : 1
"""
-b2.defaultclock.dt = 0.001 * b2.ms
+b2.defaultclock.dt = 0.01 * b2.ms
nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
@@ -108,13 +108,13 @@
nrn1[0].v[0] = V_reset
nrn2[0].v[0] = V_reset
-times = np.array([10, 20, 40, 80, 90]) * b2.ms
+times = np.array([10, 20, 40, 80, 90, 104, 109, 115, 185, 188, 190]) * b2.ms
indices = np.arange(len(times))
spikeGen = b2.SpikeGeneratorGroup(len(times), indices, times)
-ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1")
+ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1", method="rk4")
ext_conn1.connect()
-ext_conn1.delay = 1.0 * b2.ms
+ext_conn1.delay = 0.9 * b2.ms
conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="rk4")
conn2.connect()
@@ -167,26 +167,31 @@
"V_reset": np.asarray(V_reset) * 1e3, # units nS
"alpha": np.asarray(alpha * b2.ms), # units nS
# DIFFERENCE: subtract 0.1 ms from t_ref
- "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
+ "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
-nrn1 = nest.Create("iaf_wang_2002_exact", neuron_params)
+nrn1 = nest.Create("iaf_wang_2002", neuron_params)
nrn2 = nest.Create("iaf_wang_2002_exact", neuron_params)
+nrn3 = nest.Create("iaf_wang_2002", neuron_params)
-times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
+times = np.array([10, 20, 40, 80, 90, 104, 109, 115, 185, 188, 190]) * 1.0
sg = nest.Create("spike_generator", {"spike_times": times})
sr = nest.Create("spike_recorder")
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"],
+mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
"interval": b2.defaultclock.dt / b2.ms}
)
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"],
+mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
+ "interval": b2.defaultclock.dt / b2.ms}
+)
+
+mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
"interval": b2.defaultclock.dt / b2.ms}
)
# DIFFERENCE: add 0.1ms to delay
-nest_delay = 1. + b2.defaultclock.dt / b2.ms
+nest_delay = 1.# + b2.defaultclock.dt / b2.ms
ex_syn_spec = {"synapse_model": "static_synapse",
"weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
"receptor_type": 1,
@@ -203,7 +208,7 @@
"delay": nest_delay}
in_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
+ "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
"receptor_type": 2,
"delay": nest_delay}
@@ -211,11 +216,18 @@
nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
nest.Connect(nrn1, sr)
+
nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+
+nest.Connect(nrn1, nrn3, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=in_syn_spec, conn_spec=conn_spec)
+nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
+
nest.Connect(mm1, nrn1)
nest.Connect(mm2, nrn2)
+nest.Connect(mm3, nrn3)
nest.Simulate(300.)
@@ -226,40 +238,39 @@
fig, ax = plt.subplots(4, 2)
fig.set_size_inches([12,10])
fig.subplots_adjust(hspace=0.5)
-ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
+ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2", c="black")
ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), "--", label="nest")
ax[0,0].set_xlabel("time (ms)")
ax[0,0].set_ylabel("membrane potential V (mV)")
-ax[0,0].legend()
ax[0,0].set_title("Presynaptic neuron")
-ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
-ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--")
+ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV, label="brian2", c="black")
+ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--", label="NEST exact", c="C0")
+ax[0,1].plot(mm3.get("events", "times"), mm3.get("events", "V_m"), "--", label="NEST approx", c="C1")
ax[0,1].set_xlabel("time (ms)")
ax[0,1].set_ylabel("membrane potential V (mV)")
ax[0,1].set_title("Postsynaptic neuron")
+ax[0,1].legend()
-# multiply by g_AMPA_ext since it is baked into s_AMPA in NEST
-ax[1,0].plot(ampaMonitor1.t/b2.ms, ampaMonitor1.s_AMPA_ext[0] * g_AMPA_ext / b2.nS)
-ax[1,0].plot(mm1.get("events", "times"), mm1.get("events", "s_AMPA"), "--")
-ax[1,0].set_xlabel("time (ms)")
-ax[1,0].set_ylabel("s_AMPA")
-
-ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0])
-ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--")
+ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0], c="black")
+ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--", c="C0")
+ax[1,1].plot(mm3.get("events", "times"), mm3.get("events", "s_AMPA"), "--", c="C1")
ax[1,1].set_xlabel("time (ms)")
ax[1,1].set_ylabel("s_AMPA")
-ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0])
-ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--")
+ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0], c="black")
+ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--", c="C0")
+ax[2,1].plot(mm3.get("events", "times"), mm3.get("events", "s_GABA"), "--", c="C1")
ax[2,1].set_xlabel("time (ms)")
ax[2,1].set_ylabel("s_GABA")
-ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0])
-ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "NMDA_sum"), "--")
+ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0], c="black")
+ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "s_NMDA"), "--", c="C0")
+ax[3,1].plot(mm3.get("events", "times"), mm3.get("events", "s_NMDA"), "--", c="C1")
ax[3,1].set_xlabel("time (ms)")
ax[3,1].set_ylabel("s_NMDA")
+ax[1,0].axis("off")
ax[2,0].axis("off")
ax[3,0].axis("off")
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index aed2c31798..b51fee5593 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -10,7 +10,7 @@
np.random.seed(1234)
rng = np.random.default_rng()
-model = "iaf_wang_2002_exact"
+model = "iaf_wang_2002"
dt = 0.1
nest.set(resolution=dt, print_time=True)
@@ -287,6 +287,5 @@
ax[0,3].set_title("Inhibitory pop")
-
plt.show()
diff --git a/pynest/examples/wong_wang_nmda_approximation.py b/pynest/examples/wong_wang_nmda_approximation.py
index 0655c2bd2e..db8200b72f 100644
--- a/pynest/examples/wong_wang_nmda_approximation.py
+++ b/pynest/examples/wong_wang_nmda_approximation.py
@@ -40,7 +40,7 @@
mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
+mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
ampa_ext_syn_spec = {"synapse_model": "static_synapse",
"weight": w_ext,
@@ -99,15 +99,11 @@ def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
nest.Simulate(1000.)
-
times = mm3.get("events", "times")
-nest_nmda = mm3.get("events", "NMDA_sum")
+nest_nmda = mm3.get("events", "s_NMDA")
nest_nmda_approx = mm2.get("events", "s_NMDA")
times -= times[(nest_nmda > 0).argmax()] - 0.1
-
-
-
from scipy.integrate import cumtrapz
def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
a = (1 / tau_decay - 1 / tau_rise)
@@ -161,30 +157,32 @@ def nmda_approx_exp(t, tau_rise, alpha, tau_decay):
plt.show()
-
-
-
from scipy.special import expn, gamma
def limfun(tau_rise, tau_decay, alpha):
- f0 = np.exp(-alpha * tau_rise)
+ f1 = np.exp(-alpha * tau_rise)
at = alpha * tau_rise
tr_td = tau_rise / tau_decay
- f1 = -at * expn(tr_td, at) + at ** tr_td * gamma(1 - tr_td)
-
+ f0 = -at * expn(tr_td, at) + at ** tr_td * gamma(1 - tr_td)
return f0, f1
f0, f1 = limfun(2, 100, 0.5)
-
-
-
plt.plot(t, s_nmda)
-plt.plot(times, nest_nmda)
-plt.plot(times, nest_nmda_approx)
-plt.plot(t, f1 * np.exp(-t / 100))
+# plt.plot(times, nest_nmda)
+# plt.plot(times, nest_nmda_approx)
+plt.plot(t, np.exp(-t / 100) * (f0 + s0 * f1))
+plt.plot(t, np.exp(-t / 100) * (s0 + 0.5 * (1 - s0)))
plt.show()
+a = np.exp(-t / 100) * (f0 + s0 * f1)
+b = np.exp(-t / 100) * (s0 + 0.5 * (1 - s0))
+c = s_nmda.copy()
+
+print(np.trapz(a, x=t))
+print(np.trapz(b, x=t))
+print(np.trapz(c, x=t))
+
From 490814cc9271488ca81899a1810e7718242e3150 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 5 Feb 2024 14:19:11 +0100
Subject: [PATCH 049/184] Add documentation to iaf_psc_wang, remove unused
variable t_approx
---
models/iaf_wang_2002.cpp | 11 ++----
models/iaf_wang_2002.h | 68 +++++++++++++++++++++---------------
models/iaf_wang_2002_exact.h | 4 +--
nestkernel/nest_names.cpp | 1 -
nestkernel/nest_names.h | 1 -
5 files changed, 43 insertions(+), 42 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index b3627fd14b..cc3f4906d8 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -126,7 +126,6 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, tau_rise_NMDA( 2 ) // ms
, alpha( 0.5 ) // 1 / ms
, conc_Mg2( 1 ) // mM
- , approx_t_exact ( 4)
, gsl_error_tol( 1e-3 )
{
}
@@ -193,7 +192,6 @@ nest::iaf_wang_2002::Parameters_::get( DictionaryDatum& d ) const
def< double >( d, names::tau_rise_NMDA, tau_rise_NMDA );
def< double >( d, names::alpha, alpha );
def< double >( d, names::conc_Mg2, conc_Mg2 );
- def< double >( d, names::approx_t_exact, approx_t_exact );
def< double >( d, names::gsl_error_tol, gsl_error_tol );
}
@@ -215,7 +213,6 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
updateValueParam< double >( d, names::alpha, alpha, node );
updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
- updateValueParam< double >( d, names::approx_t_exact, approx_t_exact, node );
updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
if ( V_reset >= V_th )
@@ -242,10 +239,6 @@ nest::iaf_wang_2002::Parameters_::set( const DictionaryDatum& d, Node* node )
{
throw BadProperty( "Mg2 concentration must be strictly positive." );
}
- if ( approx_t_exact <= 0.0 )
- {
- throw BadProperty( "approx_t_exact must be strictly positive." );
- }
if ( gsl_error_tol <= 0.0 )
{
throw BadProperty( "The gsl_error_tol must be strictly positive." );
@@ -395,7 +388,7 @@ nest::iaf_wang_2002::pre_run_hook()
const double at = P_.alpha * P_.tau_rise_NMDA;
const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
- V_.S_jump_1 = exp(-P_.alpha * P_.tau_rise_NMDA);
+ V_.S_jump_1 = exp(-P_.alpha * P_.tau_rise_NMDA) - 1;
V_.S_jump_0 = -boost::math::expint(tau_rise_tau_dec, at) * at
+ pow(at, tau_rise_tau_dec) * boost::math::tgamma(1 - tau_rise_tau_dec);
}
@@ -478,7 +471,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
- const double s_NMDA_delta = V_.S_jump_0 + V_.S_jump_1 * S_.s_NMDA_pre - S_.s_NMDA_pre;
+ const double s_NMDA_delta = V_.S_jump_0 + V_.S_jump_1 * S_.s_NMDA_pre;//- S_.s_NMDA_pre;
S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
SpikeEvent se;
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 086789adaa..0af576bc44 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -55,39 +55,53 @@ namespace nest
*/
extern "C" inline int iaf_wang_2002_dynamics( double, const double*, double*, void* );
-/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
-DOCUMENTATION WILL BE UPDATED
+/* BeginUserDocs: neuron, integrate-and-fire, conductance-based
Short description
+++++++++++++++++
-Leaky integrate-and-fire-neuron model with dynamic NMDA receptors.
+Leaky integrate-and-fire-neuron model with conductance based synapses, and additional NMDA receptors.
Description
+++++++++++
-This model implements a simplified version of the neuron model described in [1]_.
+``iaf_wang_2002`` is a leaky integrate-and-fire neuron model with
-It contains AMPA, GABA and NMDA synapses
+* an approximate version of the neuron model described in [1]_.
+* exponential conductance-based AMPA and GABA-synapses
+* exponential conductance-based NMDA-synapses weighted such that it approximates the original non-linear dynamics
+* a fixed refractory period
+* no adaptation mechanisms
-The equations for the synaptic currents from AMPA and GABA receptors are given by
-the following equations
+Neuron and synaptic dynamics
+..................................................
-.. math::
- I_{\mathrm{AMPA}} = g_{\mathrm{AMPA}}(V(t) - E_{\mathrm{ex}} \sum_{j=1}^{C_E} w_j s_j^{\mathrm{AMPA}}
- \frac{d}{dt}s^{\mathrm{AMPA}}_j = -\frac{s_j}{\tau_{\mathrm{AMPA}}}
+The membrane potential and synaptic variables evolve according to
.. math::
- \frac{ ds_j^{NMDA}(t) }{ dt } = - \frac{ s_j^{NMDA}(t) }{ \tau_{NMDA,decay} } + \alpha x_j(t)(1 - s_j^{NMDA}(t)) \\
- \frac{ dx_j(t) }{ dt } =- \frac{ x_j(t) }{ \tau_{NMDA,rise} } + \sum_k \delta(t - t_j^k).
-The synaptic current of NMDA is given by
+ C_\mathrm{m} \frac{dV(t)}{dt} &= -g_\mathrm{L} (V(t) - V_\mathrm{L}) - I_\mathrm{syn} (t) \\[3ex]
+ I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
+ I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
+ I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
+ I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex]
+ \frac{dS_{j,\mathrm{AMPA}}}{dt} &= -\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} (k_0 + k_1 S(t)) \delta (t - t_j^k) \\[3ex]
+
+
+where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
.. math::
- I_{NMDA}(t) = \frac{ V(t) - E_{ex} }{ 1 + [Mg^{2+}]exp(-0.062V(t))/3.57 }\sum_{j=1}w_jg_j^{NMDA},
-where `w_j` is the weight of connection with presynaptic neuron `j`.
+ k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) \\[3ex]
+ k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r} \Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 - \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
+
+where :math:`\mathrm{E_N}` is the generalized exponential integral (https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function (https://en.wikipedia.org/wiki/Gamma_function).
+
+
+The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
Parameters
@@ -95,6 +109,7 @@ Parameters
The following parameters can be set in the status dictionary.
+
=============== ======= ===========================================================
E_L mV Resting potential
E_ex mV Excitatory reversal potential
@@ -103,20 +118,17 @@ The following parameters can be set in the status dictionary.
V_reset mV Reset potential
C_m pF Membrane capacitance
g_L nS Leak conductance
- g_AMPA nS Peak recurrent AMPA conductance
- g_NMDA nS Peak recurrent NMDA conductance
- g_GABA nS Peak recurrent GABA conductance
- g_L nS Leak conductance
t_ref ms Refractory period
tau_AMPA ms Synaptic time constant for AMPA synapse
tau_GABA ms Synaptic time constant for GABA synapse
- tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
tau_rise_NMDA ms Synaptic rise time constant for NMDA synapse
+ tau_decay_NMDA ms Synaptic decay time constant for NMDA synapse
alpha 1/ms Scaling factor for NMDA synapse
conc_Mg2 mM Extracellular magnesium concentration
gsl_error_tol - GSL error tolerance
=============== ======= ===========================================================
+
Recordables
+++++++++++
@@ -124,18 +136,17 @@ The following values can be recorded.
=========== ===========================================================
V_m Membrane potential
- s_AMPA AMPA gate
- s_GABA GABA gate
- NMDA_sum sum of NMDA over all presynaptic neurons j
+ s_AMPA sum of AMPA gating variables
+ s_GABA sum of GABA gating variables
+ s_NMDA sum of NMDA gating variables
=========== ===========================================================
.. note::
- It is possible to set values for `V_m`, `g_AMPA` and `g_GABA` when creating the model, while the
- different `g_NMDA_j` (`j` represents presynaptic neuron `j`) can not be set by the user.
+ It is possible to set values for :math:`V_\mathrm{m}`, :math:`S_\mathrm{AMPA}` and :math:`S_\mathrm{GABA}` when creating the model, while the
+ different :math:`s_{j,\mathrm{NMDA}}` (`j` represents presynaptic neuron `j`) can not be set by the user.
.. note::
- The variable `g_AMPA` and `g_GABA` in the NEST implementation does not correspond to `g_{recAMPA, extAMPA, GABA}`
- in [1]_. `g_{recAMPA, extAMPA, GABA, NMBA}` from [1]_ is built into the weights in this NEST model, so setting the
+ :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST model, so setting the
variables is thus done by changing the weights.
Sends
@@ -162,6 +173,7 @@ iaf_cond_alpha, ht_neuron
EndUserDocs */
+
void register_iaf_wang_2002( const std::string& name );
class iaf_wang_2002 : public ArchivingNode
@@ -248,8 +260,6 @@ class iaf_wang_2002 : public ArchivingNode
double alpha; //!< Scaling factor for NMDA synapse in 1/ms
double conc_Mg2; //!< Extracellular Magnesium Concentration in mM
- // TODO: find better name for this variable
- double approx_t_exact; // Time at which the S_NMDA approximation is exact
double gsl_error_tol; //!< GSL Error Tolerance
//! Initialize parameters to their default values.
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 70b0e8b789..0ff2d8c439 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -66,7 +66,7 @@ Leaky integrate-and-fire-neuron model with conductance based synapses, and addit
Description
+++++++++++
-``iaf_wang_2002_exact_dynamics`` is a leaky integrate-and-fire neuron model with
+``iaf_wang_2002_exact`` is a leaky integrate-and-fire neuron model with
* an exact implementation of the neuron model described in [1]_.
* exponential conductance-based AMPA and GABA-synapses
@@ -96,7 +96,7 @@ where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets f
Since :math:`S_{j,\mathrm{AMPA}}` and :math:`S_{j,\mathrm{GABA}}` are piecewise exponential functions, the sums are also a piecewise exponential function, and can be stored in a single synaptic variable each, :math:`S_{\mathrm{AMPA}}` and :math:`S_{\mathrm{GABA}}` respectively. The sum over :math:`S_{j,\mathrm{NMDA}}` does not have a simple expression, and cannot be simplified. Therefore, for each synapse, we need to integrate separate state variable, which makes the model slow.
-The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synaptic variable from the external population is not kept in a separate variable :math:`S_{\mathrm{rec,AMPA}}`, but is taken together with the local synapses in :math:`S_{\mathrm{AMPA}}`.
+The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
Parameters
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index da681bec71..1e82ba03e5 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -65,7 +65,6 @@ const Name amplitude( "amplitude" );
const Name amplitude_times( "amplitude_times" );
const Name amplitude_values( "amplitude_values" );
const Name anchor( "anchor" );
-const Name approx_t_exact( "approx_t_exact" );
const Name archiver_length( "archiver_length" );
const Name asc_amps( "asc_amps" );
const Name asc_decay( "asc_decay" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index 4bbba53d96..dc29844c46 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -91,7 +91,6 @@ extern const Name amplitude;
extern const Name amplitude_times;
extern const Name amplitude_values;
extern const Name anchor;
-extern const Name approx_t_exact;
extern const Name archiver_length;
extern const Name asc_amps;
extern const Name asc_decay;
From a59c6aff18fb1993151ab877cc8f9a407ab28023 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 5 Feb 2024 14:23:01 +0100
Subject: [PATCH 050/184] remove unused var
---
pynest/examples/wang_neuron.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
index 39a7d221fe..339c045aeb 100644
--- a/pynest/examples/wang_neuron.py
+++ b/pynest/examples/wang_neuron.py
@@ -27,8 +27,6 @@
"alpha": 0.5,
"t_ref": 2.0}
-params_approx = params_exact.copy()
-params_approx["approx_t_exact"] = 200
nrn1 = nest.Create("iaf_wang_2002", params_approx)
pg = nest.Create("poisson_generator", {"rate": 50.})
From 1b7076a581f1300c39843cb9a7e6bc0c193b9d12 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 5 Feb 2024 14:24:28 +0100
Subject: [PATCH 051/184] remove test scripts
---
pynest/examples/wang_approx_compare_brian.py | 257 -----------
pynest/examples/wang_benchmarks.py | 417 ------------------
pynest/examples/wang_compare_brian.py | 278 ------------
pynest/examples/wang_neuron.py | 160 -------
pynest/examples/wang_neuron_exact.py | 148 -------
.../examples/wong_wang_nmda_approximation.py | 188 --------
6 files changed, 1448 deletions(-)
delete mode 100644 pynest/examples/wang_approx_compare_brian.py
delete mode 100644 pynest/examples/wang_benchmarks.py
delete mode 100644 pynest/examples/wang_compare_brian.py
delete mode 100644 pynest/examples/wang_neuron.py
delete mode 100644 pynest/examples/wang_neuron_exact.py
delete mode 100644 pynest/examples/wong_wang_nmda_approximation.py
diff --git a/pynest/examples/wang_approx_compare_brian.py b/pynest/examples/wang_approx_compare_brian.py
deleted file mode 100644
index 20a3095bbb..0000000000
--- a/pynest/examples/wang_approx_compare_brian.py
+++ /dev/null
@@ -1,257 +0,0 @@
-"""
-Check that NEST implementation gives same results as a reference implementation from Brian.
-Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
-a separate parameter. This is the only difference in parameterization between the two models.
-Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
-so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
-"""
-
-
-import brian2 as b2
-import nest
-import numpy as np
-import time
-import statistics
-import matplotlib.pyplot as plt
-
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Parameters
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-V_th = -55 * b2.mV
-V_reset = -70 * b2.mV
-t_ref = 2 * b2.ms
-
-# parameters for the equation of the neuron
-# (Inhibitory and excitatory neurons have different parameters)
-g_L = 25. * b2.nS
-C_m = 0.5 * b2.nF
-
-g_AMPA_rec = 1.0 * b2.nS
-g_AMPA_ext = 100.0 *b2.nS
-g_GABA = 1.0 * b2.nS
-g_NMDA = 1.0 * b2.nS
-
-# reversal potentials
-E_L = V_reset
-E_ex= 0. * b2.mV
-E_in = -70. * b2.mV
-
-# time constant of the receptors
-tau_AMPA= 2 * b2.ms
-tau_GABA= 5 * b2.ms
-tau_NMDA_rise = 2. * b2.ms
-tau_NMDA_decay = 100. * b2.ms
-
-# additional NMDA parameters
-alpha = 0.5 / b2.ms
-Mg2 = 1.
-
-# synaptic weights
-weight_AMPA = 1.
-weight_GABA = 3.
-weight_NMDA = 2.
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Brian simulation
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-## Equations
-eqsE="""
-
- dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
- I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
-
- I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
- ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
- #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
-
- I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
- s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
- #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
- #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
-
- I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
- s_GABA_tot :1
-
-
- I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
- s_NMDA_tot : 1
-
- """
-
-eqs_ampa="""
- s_AMPA_tot_post= w_AMPA * s_AMPA : 1 (summed)
- ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
- w_AMPA: 1
- """
-
-eqs_gaba="""
- s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
- ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
- w_GABA: 1
- """
-
-eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
- ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
- dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
- w_NMDA : 1
- """
-
-nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
-nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
-
-nrn1[0].v[0]=V_reset
-nrn2[0].v[0]=V_reset
-
-times = np.array([10, 20, 40, 80, 90]) * b2.ms
-indices = np.arange(len(times))
-spikeGen = b2.SpikeGeneratorGroup(len(times), indices, times)
-
-ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1")
-ext_conn1.connect()
-ext_conn1.delay = 1.0 * b2.ms
-
-conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="rk4")
-conn2.connect()
-conn2.w_AMPA = weight_AMPA
-conn2.delay = 1.0 * b2.ms
-
-conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="rk4")
-conn3.connect()
-conn3.w_NMDA = weight_NMDA
-conn3.delay = 1.0 * b2.ms
-
-conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="rk4")
-conn4.connect()
-conn4.w_GABA = weight_GABA
-conn4.delay = 1.0 * b2.ms
-
-vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
-ampaMonitor1 = b2.StateMonitor(nrn1, "s_AMPA_ext",record=True)
-gabaMonitor1 = b2.StateMonitor(nrn1, "s_GABA_tot",record=True)
-nmdaMonitor1 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
-
-vMonitor2 = b2.StateMonitor(nrn2, "v", record=True)
-ampaMonitor2 = b2.StateMonitor(nrn2, "s_AMPA_tot", record=True)
-gabaMonitor2 = b2.StateMonitor(nrn2, "s_GABA_tot", record=True)
-nmdaMonitor2 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
-
-t_sim = 300
-b2.run(t_sim * b2.ms)
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# NEST simulation
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-nest.rng_seed = 12345
-
-nest.ResetKernel()
-
-neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
- "E_ex": np.asarray(E_ex) * 1e3, # units mV
- "E_L": np.asarray(E_L) * 1e3, # units mV
- "V_th": np.asarray(V_th) * 1e3, # units mV
- "C_m": np.asarray(C_m) * 1e12, # units pF
- "g_L": np.asarray(g_L) * 1e9, # units nS
- "V_reset": np.asarray(V_reset) * 1e3, # units nS
- # DIFFERENCE: subtract 0.1 ms from t_ref
- "t_ref": np.asarray(t_ref) * 1e3 - 0.1} # units ms
-
-nrn1 = nest.Create("iaf_wang_2002", neuron_params)
-nrn2 = nest.Create("iaf_wang_2002", neuron_params)
-
-times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
-sg = nest.Create("spike_generator", {"spike_times": times})
-sr = nest.Create("spike_recorder")
-
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_GABA", "s_NMDA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_GABA", "s_NMDA"], "interval": 0.1})
-
-# DIFFERENCE: add 0.1ms to delay
-nest_delay = 1.1
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
- "receptor_type": 3,
- "delay": nest_delay}
-
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
- "receptor_type": 2,
- "delay": nest_delay}
-
-conn_spec = {"rule": "all_to_all"}
-
-
-nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
-nest.Connect(nrn1, sr)
-nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(mm1, nrn1)
-nest.Connect(mm2, nrn2)
-
-nest.Simulate(300.)
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Plotting
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-fig, ax = plt.subplots(4, 2)
-fig.set_size_inches([12,10])
-fig.subplots_adjust(hspace=0.5)
-ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2")
-ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), "--", label="nest")
-ax[0,0].set_xlabel("time (ms)")
-ax[0,0].set_ylabel("membrane potential V (mV)")
-ax[0,0].legend()
-ax[0,0].set_title("Presynaptic neuron")
-
-ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV)
-ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--")
-ax[0,1].set_xlabel("time (ms)")
-ax[0,1].set_ylabel("membrane potential V (mV)")
-ax[0,1].set_title("Postsynaptic neuron")
-
-# multiply by g_AMPA_ext since it is baked into s_AMPA in NEST
-ax[1,0].plot(ampaMonitor1.t/b2.ms, ampaMonitor1.s_AMPA_ext[0] * g_AMPA_ext / b2.nS)
-ax[1,0].plot(mm1.get("events", "times"), mm1.get("events", "s_AMPA"), "--")
-ax[1,0].set_xlabel("time (ms)")
-ax[1,0].set_ylabel("s_AMPA")
-
-ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0])
-ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--")
-ax[1,1].set_xlabel("time (ms)")
-ax[1,1].set_ylabel("s_AMPA")
-
-ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0])
-ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--")
-ax[2,1].set_xlabel("time (ms)")
-ax[2,1].set_ylabel("s_GABA")
-
-ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0])
-ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "s_NMDA"), "--")
-ax[3,1].set_xlabel("time (ms)")
-ax[3,1].set_ylabel("s_NMDA")
-
-ax[2,0].axis("off")
-ax[3,0].axis("off")
-
-plt.show()
-
-
diff --git a/pynest/examples/wang_benchmarks.py b/pynest/examples/wang_benchmarks.py
deleted file mode 100644
index e374c53851..0000000000
--- a/pynest/examples/wang_benchmarks.py
+++ /dev/null
@@ -1,417 +0,0 @@
-"""
-Check that NEST implementation gives same results as a reference implementation from Brian.
-Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
-a separate parameter. This is the only difference in parameterization between the two models.
-Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
-so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
-"""
-
-
-import brian2 as b2
-import nest
-import numpy as np
-import time, os
-import statistics
-import matplotlib.pyplot as plt
-from pathlib import Path
-
-
-path = Path(__file__).parent
-outfile = os.path.join(path, "wang_benchmark_log.csv")
-if not os.path.isfile(outfile):
- with open(outfile, "w") as f:
- f.write("brian_time,nest_time_exact,nest_time_approx,NE,NI\n")
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Parameters
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-simtime = 1000
-resolution = 0.01
-NE = 200
-NI = 200
-
-V_th = -55 * b2.mV
-V_reset = -70 * b2.mV
-t_ref = 2 * b2.ms
-
-# parameters for the equation of the neuron
-# (Inhibitory and excitatory neurons have different parameters)
-g_L = 25. * b2.nS
-C_m = 0.5 * b2.nF
-
-g_AMPA_rec = 2.0 * b2.nS
-g_AMPA_ext = 1.0 *b2.nS
-g_GABA = 1. * b2.nS
-g_NMDA = 1. * b2.nS
-
-# reversal potentials
-E_L = V_reset
-E_ex= 0. * b2.mV
-E_in = -70. * b2.mV
-
-# time constant of the receptors
-tau_AMPA= 2 * b2.ms
-tau_GABA= 5 * b2.ms
-tau_NMDA_rise = 2. * b2.ms
-tau_NMDA_decay = 100. * b2.ms
-
-# additional NMDA parameters
-alpha = 0.5 / b2.ms
-Mg2 = 1.
-
-# synaptic weights
-weight_AMPA_ext = 1.
-weight_AMPA = 1. * 50 / NE
-weight_NMDA = 1. * 50 / NE
-weight_GABA = 1. * 50 / NI
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Brian simulation
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-## Equations
-eqsE="""
-
- dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
- I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
-
- I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
- ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
- #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
- I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
- s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
- #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
- #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
-
- I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
- s_GABA_tot :1
-
- I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
- s_NMDA_tot : 1
-
- """
-
-
-
-eqsI="""
-
- dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
- I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA : amp
-
- I_AMPA_ext= g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
- ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
- # Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
-
- I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
- s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
- #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
- #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
-
- I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
- s_GABA_tot :1
-
- I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
- s_NMDA_tot : 1
-
- """
-
-eqs_ampa="""
- s_AMPA_tot_post = w_AMPA * s_AMPA : 1 (summed)
- ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
- w_AMPA: 1
- """
-
-eqs_gaba="""
- s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
- ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
- w_GABA: 1
- """
-
-eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
- ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
- dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
- w_NMDA : 1
- """
-
-
-
-#Create the two population with the corresponfing equations:
-popE = b2.NeuronGroup(NE, model=eqsE, threshold="v > V_th", reset="v = E_L", refractory=t_ref, method="rk4")
-popI= b2.NeuronGroup(NI, model=eqsI, threshold="v > V_th", reset="v = E_L", refractory=t_ref, method="rk4")
-
-
-#Set the initial value of the potential v for all the neurons
-for k in range(0,NE):
- popE[k].v[0]=E_L
-
-for k in range(0,NI):
- popI[k].v[0]=E_L
-
-
-# Connect the neurons of popE with the neurons of popI (for the ampa connections)
-conn= b2.Synapses(popE,popI,model=eqs_ampa,on_pre="s_AMPA+=1", method="rk4")
-conn.connect()
-conn.w_AMPA= weight_AMPA
-conn.delay = 1.0 * b2.ms
-
-# Connect the neurons of popE with the neurons of popI (for the NMDA connections)
-conn1= b2.Synapses(popE,popI,model=eqs_nmda,on_pre="x+=1", method="rk4")
-conn1.connect()
-conn1.w_NMDA= weight_NMDA
-conn1.delay = 1.0 * b2.ms
-
-# Connect the neurons of popE with the neurons of popE (for the AMPA connections)
-conn2= b2.Synapses(popE,popE,model=eqs_ampa,on_pre="s_AMPA+=1", method="rk4")
-conn2.connect()
-conn2.w_AMPA= weight_AMPA
-conn2.delay = 1.0 * b2.ms
-
-# Connect the neurons of popE with the neurons of popE (for the NMDA connections)
-conn3= b2.Synapses(popE,popE,model=eqs_nmda,on_pre="x+=1", method="rk4")
-conn3.connect()
-conn3.w_NMDA= weight_NMDA
-conn3.delay = 1.0 * b2.ms
-
-# Connect the neurons of popI with the neurons of popE (for the GABA connections)
-conn4= b2.Synapses(popI,popE,model = eqs_gaba,on_pre="s_GABA+=1", method="rk4")
-conn4.connect()
-conn4.w_GABA= weight_GABA
-conn4.delay = 1.0 * b2.ms
-
-# Connect the neurons of popI with the neurons of popI (for the GABA connections)
-conn5= b2.Synapses(popI,popI,model = eqs_gaba,on_pre="s_GABA+=1", method="rk4")
-conn5.connect()
-conn5.w_GABA= weight_GABA
-conn5.delay = 1.0 * b2.ms
-
-# To excitatory neurons
-rate_E = 4000 * b2.Hz
-ext_inputE = b2.PoissonGroup(NE, rates = rate_E)
-ext_connE = b2.Synapses(ext_inputE, popE, on_pre="s_AMPA_ext += 1", method="rk4")
-ext_connE.connect(j="i")
-ext_connE.delay = 1.0 * b2.ms
-
-# To inhibitory neurons
-rate_I= 4000 * b2.Hz
-ext_inputI= b2.PoissonGroup(NI, rates = rate_I)
-ext_connI = b2.Synapses(ext_inputI, popI, on_pre="s_AMPA_ext += 1", method="rk4")
-ext_connI.connect(j="i")
-ext_connI.delay = 1.0 * b2.ms
-
-# Recorder to save the spikes of the neurons
-S_e = b2.SpikeMonitor(popE[:NE], record=True)
-S_i = b2.SpikeMonitor(popI[:NI], record=True)
-
-b2.defaultclock.dt = resolution * b2.ms
-tic = time.time()
-b2.run(simtime * b2.ms)
-toc = time.time()
-brian_time = toc - tic
-
-brian_espikes = S_e.spike_trains()
-brian_espikes = np.array(np.concatenate(tuple(brian_espikes.values()))) * 1000.
-brian_ispikes = S_i.spike_trains()
-brian_ispikes = np.array(np.concatenate(tuple(brian_ispikes.values()))) * 1000.
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# NEST simulation exact
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-nest.rng_seed = 12345
-
-nest.ResetKernel()
-nest.resolution = resolution
-
-neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
- "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
- "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
- "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
- "conc_Mg2": np.asarray(Mg2), # dimensionless
- "E_ex": np.asarray(E_ex) * 1e3, # units mV
- "E_in": np.asarray(E_in) * 1e3, # units mV
- "E_L": np.asarray(E_L) * 1e3, # units mV
- "V_th": np.asarray(V_th) * 1e3, # units mV
- "C_m": np.asarray(C_m) * 1e12, # units pF
- "g_L": np.asarray(g_L) * 1e9, # units nS
- "V_reset": np.asarray(V_reset) * 1e3, # units nS
- "alpha": np.asarray(alpha * b2.ms), # units nS
- # DIFFERENCE: subtract 0.1 ms from t_ref
- "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
-
-
-
-poisson = nest.Create("poisson_generator", {"rate": 4000.})
-epop = nest.Create("iaf_wang_2002_exact", NE, params=neuron_params)
-ipop = nest.Create("iaf_wang_2002_exact", NI, params=neuron_params)
-
-sr_ex = nest.Create("spike_recorder")
-sr_in = nest.Create("spike_recorder")
-
-
-# DIFFERENCE: add 0.1ms to delay
-nest_delay = 1. + b2.defaultclock.dt / b2.ms
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_ext) * 1e9 * weight_AMPA_ext, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
- "receptor_type": 3,
- "delay": nest_delay}
-
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
- "receptor_type": 2,
- "delay": nest_delay}
-
-conn_spec = {"rule": "all_to_all"}
-
-nest.Connect(poisson, epop + ipop, syn_spec=ex_syn_spec_ext, conn_spec="all_to_all")
-nest.Connect(epop, sr_ex)
-nest.Connect(ipop, sr_in)
-nest.Connect(epop, epop + ipop, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(epop, epop + ipop, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(ipop, ipop + epop, syn_spec=in_syn_spec, conn_spec=conn_spec)
-
-tic = time.time()
-nest.Simulate(simtime)
-toc = time.time()
-nest_time_exact = toc - tic
-
-nest_espikes_exact = sr_ex.get("events", "times")
-nest_ispikes_exact = sr_in.get("events", "times")
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# NEST simulation approximate
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-nest.rng_seed = 12345
-
-nest.ResetKernel()
-nest.resolution = resolution
-
-neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
- "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
- "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
- "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
- "conc_Mg2": np.asarray(Mg2), # dimensionless
- "E_ex": np.asarray(E_ex) * 1e3, # units mV
- "E_in": np.asarray(E_in) * 1e3, # units mV
- "E_L": np.asarray(E_L) * 1e3, # units mV
- "V_th": np.asarray(V_th) * 1e3, # units mV
- "C_m": np.asarray(C_m) * 1e12, # units pF
- "g_L": np.asarray(g_L) * 1e9, # units nS
- "V_reset": np.asarray(V_reset) * 1e3, # units nS
- "alpha": np.asarray(alpha * b2.ms), # units nS
- # DIFFERENCE: subtract 0.1 ms from t_ref
- "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
-
-
-
-poisson = nest.Create("poisson_generator", {"rate": 4000.})
-epop = nest.Create("iaf_wang_2002", NE, params=neuron_params)
-ipop = nest.Create("iaf_wang_2002", NI, params=neuron_params)
-
-sr_ex = nest.Create("spike_recorder")
-sr_in = nest.Create("spike_recorder")
-
-# DIFFERENCE: add 0.1ms to delay
-nest_delay = 1. + b2.defaultclock.dt / b2.ms
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_ext) * 1e9 * weight_AMPA_ext, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
- "receptor_type": 3,
- "delay": nest_delay}
-
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
- "receptor_type": 2,
- "delay": nest_delay}
-
-conn_spec = {"rule": "all_to_all"}
-
-nest.Connect(poisson, epop + ipop, syn_spec=ex_syn_spec_ext, conn_spec="all_to_all")
-nest.Connect(epop, sr_ex)
-nest.Connect(ipop, sr_in)
-nest.Connect(epop, epop + ipop, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(epop, epop + ipop, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(ipop, ipop + epop, syn_spec=in_syn_spec, conn_spec=conn_spec)
-
-tic = time.time()
-nest.Simulate(simtime)
-toc = time.time()
-nest_time_approx = toc - tic
-
-with open(outfile, "a") as f:
- f.write(f"{brian_time},{nest_time_exact},{nest_time_approx},{NE},{NI}\n")
-
-nest_espikes_approx = sr_ex.get("events", "times")
-nest_ispikes_approx = sr_in.get("events", "times")
-
-
-# Plotting
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-print(f"Time NEST exact: {nest_time_exact}")
-print(f"Time NEST approx: {nest_time_approx}")
-print(f"Time Brian2 exact: {brian_time}")
-
-print(f"Total excitatory spikes Brian exact: {len(brian_espikes)}")
-print(f"Total inhibitory spikes Brian exact: {len(brian_ispikes)}")
-
-print(f"Total excitatory spikes NEST exact: {len(nest_espikes_exact)}")
-print(f"Total inhibitory spikes NEST exact: {len(nest_ispikes_exact)}")
-
-print(f"Total excitatory spikes NEST approx: {len(nest_espikes_approx)}")
-print(f"Total inhibitory spikes NEST approx: {len(nest_ispikes_approx)}")
-
-bins = np.arange(0, simtime+1, 1) - 0.001
-nest_ehist_exact, _ = np.histogram(nest_espikes_exact, bins=bins)
-nest_ihist_exact, _ = np.histogram(nest_ispikes_exact, bins=bins)
-
-nest_ehist_approx, _ = np.histogram(nest_espikes_approx, bins=bins)
-nest_ihist_approx, _ = np.histogram(nest_ispikes_approx, bins=bins)
-
-brian_ehist, _ = np.histogram(brian_espikes, bins=bins)
-brian_ihist, _ = np.histogram(brian_ispikes, bins=bins)
-
-fig, ax = plt.subplots(ncols=2, nrows=3, sharex=True, sharey=True)
-ax[0,0].plot(nest_ehist_exact * 1000 / NE)
-ax[1,0].plot(nest_ehist_approx * 1000 / NE)
-ax[2,0].plot(brian_ehist * 1000 / NE)
-
-ax[0,1].plot(nest_ihist_exact * 1000 / NI)
-ax[1,1].plot(nest_ihist_approx * 1000 / NI)
-ax[2,1].plot(brian_ihist * 1000 / NI)
-
-ax[0,0].set_title("excitatory")
-ax[0,1].set_title("inhibitory")
-ax[2,0].set_xlabel("time (ms)")
-ax[2,1].set_xlabel("time (ms)")
-ax[0,0].set_ylabel("NEST exact")
-ax[1,0].set_ylabel("NEST approx")
-ax[2,0].set_ylabel("Brian")
-plt.show()
-
diff --git a/pynest/examples/wang_compare_brian.py b/pynest/examples/wang_compare_brian.py
deleted file mode 100644
index df8a39b014..0000000000
--- a/pynest/examples/wang_compare_brian.py
+++ /dev/null
@@ -1,278 +0,0 @@
-"""
-Check that NEST implementation gives same results as a reference implementation from Brian.
-Note that in the NEST model, the constant "g" parameter is baked into the synaptic weight, instead of being
-a separate parameter. This is the only difference in parameterization between the two models.
-Also, in the NEST model, the weight for the NMDA receptor is applied AFTER computing the s_NMDA values,
-so the in the recorded value of NMDA_sum, the weights are not yet applied. The end result (V_m) is still the same.
-"""
-
-
-import brian2 as b2
-import nest
-import numpy as np
-import time
-import statistics
-import matplotlib.pyplot as plt
-
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Parameters
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-V_th = -55 * b2.mV
-V_reset = -70 * b2.mV
-t_ref = 2 * b2.ms
-
-# parameters for the equation of the neuron
-# (Inhibitory and excitatory neurons have different parameters)
-g_L = 25. * b2.nS
-C_m = 0.5 * b2.nF
-
-g_AMPA_rec = 1.0 * b2.nS
-g_AMPA_ext = 100.0 *b2.nS
-g_GABA = 1.0 * b2.nS
-g_NMDA = 1.0 * b2.nS
-
-# reversal potentials
-E_L = V_reset
-E_ex= 0. * b2.mV
-E_in = -70. * b2.mV
-
-# time constant of the receptors
-tau_AMPA= 2 * b2.ms
-tau_GABA= 5 * b2.ms
-tau_NMDA_rise = 2. * b2.ms
-tau_NMDA_decay = 100. * b2.ms
-
-# additional NMDA parameters
-alpha = 0.5 / b2.ms
-Mg2 = 1.
-
-# synaptic weights
-weight_AMPA = 1.
-weight_GABA = 1.
-weight_NMDA = 1.
-
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Brian simulation
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-## Equations
-eqsE="""
-
- dv / dt = (- g_L * (v - E_L) - I_syn) / C_m : volt (unless refractory)
- I_syn = I_AMPA_rec + I_AMPA_ext + I_GABA + I_NMDA: amp
-
- I_AMPA_ext = g_AMPA_ext * (v - E_ex) * s_AMPA_ext : amp
- ds_AMPA_ext / dt = - s_AMPA_ext / tau_AMPA : 1
- #Here I don"t need the summed variable because the neuron receive inputs from only one Poisson generator. Each neuron need only one s.
-
- I_AMPA_rec = g_AMPA_rec * (v - E_ex) * 1 * s_AMPA_tot : amp
- s_AMPA_tot : 1 #the eqs_ampa solve many s and sum them and give the summed value here
- #Each neuron receives inputs from many neurons. Each of them has his own differential equation s_AMPA (where I have the deltas with the spikes).
- #I then sum all the solutions s of the differential equations and I obtain s_AMPA_tot_post.
-
- I_GABA= g_GABA * (v - E_in) * s_GABA_tot : amp
- s_GABA_tot :1
-
- I_NMDA = g_NMDA * (v - E_ex) / (1 + Mg2 * exp(-0.062 * v / mV) / 3.57) * s_NMDA_tot : amp
- s_NMDA_tot : 1
-
- """
-
-eqs_ampa="""
- s_AMPA_tot_post = w_AMPA * s_AMPA : 1 (summed)
- ds_AMPA / dt = - s_AMPA / tau_AMPA : 1 (clock-driven)
- w_AMPA: 1
- """
-
-eqs_gaba="""
- s_GABA_tot_post= w_GABA* s_GABA : 1 (summed)
- ds_GABA/ dt = - s_GABA/ tau_GABA : 1 (clock-driven)
- w_GABA: 1
- """
-
-eqs_nmda="""s_NMDA_tot_post = w_NMDA * s_NMDA : 1 (summed)
- ds_NMDA / dt = - s_NMDA / tau_NMDA_decay + alpha * x * (1 - s_NMDA) : 1 (clock-driven)
- dx / dt = - x / tau_NMDA_rise : 1 (clock-driven)
- w_NMDA : 1
- """
-
-b2.defaultclock.dt = 0.01 * b2.ms
-
-nrn1 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
-nrn2 = b2.NeuronGroup(1, model=eqsE, threshold="v > V_th", reset="v = V_reset", refractory=t_ref, method="rk4")
-
-nrn1[0].v[0] = V_reset
-nrn2[0].v[0] = V_reset
-
-times = np.array([10, 20, 40, 80, 90, 104, 109, 115, 185, 188, 190]) * b2.ms
-indices = np.arange(len(times))
-spikeGen = b2.SpikeGeneratorGroup(len(times), indices, times)
-
-ext_conn1 = b2.Synapses(spikeGen, nrn1, on_pre="s_AMPA_ext += 1", method="rk4")
-ext_conn1.connect()
-ext_conn1.delay = 0.9 * b2.ms
-
-conn2 = b2.Synapses(nrn1, nrn2, model=eqs_ampa, on_pre="s_AMPA+=1", method="rk4")
-conn2.connect()
-conn2.w_AMPA = weight_AMPA
-conn2.delay = 1.0 * b2.ms
-
-conn3= b2.Synapses(nrn1,nrn2,model=eqs_nmda,on_pre="x+=1", method="rk4")
-conn3.connect()
-conn3.w_NMDA = weight_NMDA
-conn3.delay = 1.0 * b2.ms
-
-conn4 = b2.Synapses(nrn1, nrn2, model=eqs_gaba, on_pre="s_GABA+=1", method="rk4")
-conn4.connect()
-conn4.w_GABA = weight_GABA
-conn4.delay = 1.0 * b2.ms
-
-vMonitor1 = b2.StateMonitor(nrn1, "v",record=True)
-ampaMonitor1 = b2.StateMonitor(nrn1, "s_AMPA_ext",record=True)
-gabaMonitor1 = b2.StateMonitor(nrn1, "s_GABA_tot",record=True)
-nmdaMonitor1 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
-
-vMonitor2 = b2.StateMonitor(nrn2, "v", record=True)
-ampaMonitor2 = b2.StateMonitor(nrn2, "s_AMPA_tot", record=True)
-gabaMonitor2 = b2.StateMonitor(nrn2, "s_GABA_tot", record=True)
-nmdaMonitor2 = b2.StateMonitor(nrn2, "s_NMDA_tot", record=True)
-
-t_sim = 300
-b2.run(t_sim * b2.ms)
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# NEST simulation
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-nest.rng_seed = 12345
-
-nest.ResetKernel()
-nest.resolution = b2.defaultclock.dt / b2.ms
-
-neuron_params = {"tau_AMPA": np.asarray(tau_AMPA) * 1e3, # units ms
- "tau_GABA": np.asarray(tau_GABA) * 1e3, # units ms
- "tau_rise_NMDA": np.asarray(tau_NMDA_rise) * 1e3, # units ms
- "tau_decay_NMDA": np.asarray(tau_NMDA_decay) * 1e3, # units ms
- "conc_Mg2": np.asarray(Mg2), # dimensionless
- "E_ex": np.asarray(E_ex) * 1e3, # units mV
- "E_in": np.asarray(E_in) * 1e3, # units mV
- "E_L": np.asarray(E_L) * 1e3, # units mV
- "V_th": np.asarray(V_th) * 1e3, # units mV
- "C_m": np.asarray(C_m) * 1e12, # units pF
- "g_L": np.asarray(g_L) * 1e9, # units nS
- "V_reset": np.asarray(V_reset) * 1e3, # units nS
- "alpha": np.asarray(alpha * b2.ms), # units nS
- # DIFFERENCE: subtract 0.1 ms from t_ref
- "t_ref": np.asarray(t_ref) * 1e3 - b2.defaultclock.dt / b2.ms} # units ms
-
-
-nrn1 = nest.Create("iaf_wang_2002", neuron_params)
-nrn2 = nest.Create("iaf_wang_2002_exact", neuron_params)
-nrn3 = nest.Create("iaf_wang_2002", neuron_params)
-
-times = np.array([10, 20, 40, 80, 90, 104, 109, 115, 185, 188, 190]) * 1.0
-sg = nest.Create("spike_generator", {"spike_times": times})
-sr = nest.Create("spike_recorder")
-
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
- "interval": b2.defaultclock.dt / b2.ms}
-)
-
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
- "interval": b2.defaultclock.dt / b2.ms}
-)
-
-mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
- "interval": b2.defaultclock.dt / b2.ms}
-)
-
-# DIFFERENCE: add 0.1ms to delay
-nest_delay = 1.# + b2.defaultclock.dt / b2.ms
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_rec) * 1e9 * weight_AMPA, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_AMPA_ext) * 1e9, # units nS
- "receptor_type": 1,
- "delay": nest_delay}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": np.asarray(g_NMDA) * 1e9 * weight_NMDA, # units nS
- "receptor_type": 3,
- "delay": nest_delay}
-
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": -np.asarray(g_GABA) * 1e9 * weight_GABA, # units nS
- "receptor_type": 2,
- "delay": nest_delay}
-
-conn_spec = {"rule": "all_to_all"}
-
-nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
-nest.Connect(nrn1, sr)
-
-nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-
-nest.Connect(nrn1, nrn3, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=in_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-
-nest.Connect(mm1, nrn1)
-nest.Connect(mm2, nrn2)
-nest.Connect(mm3, nrn3)
-
-nest.Simulate(300.)
-
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-# Plotting
-# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
-
-fig, ax = plt.subplots(4, 2)
-fig.set_size_inches([12,10])
-fig.subplots_adjust(hspace=0.5)
-ax[0,0].plot(vMonitor1.t / b2.ms, vMonitor1.v[0] / b2.mV, label="brian2", c="black")
-ax[0,0].plot(mm1.get("events", "times"), mm1.get("events", "V_m"), "--", label="nest")
-ax[0,0].set_xlabel("time (ms)")
-ax[0,0].set_ylabel("membrane potential V (mV)")
-ax[0,0].set_title("Presynaptic neuron")
-
-ax[0,1].plot(vMonitor2.t / b2.ms, vMonitor2.v[0] / b2.mV, label="brian2", c="black")
-ax[0,1].plot(mm2.get("events", "times"), mm2.get("events", "V_m"), "--", label="NEST exact", c="C0")
-ax[0,1].plot(mm3.get("events", "times"), mm3.get("events", "V_m"), "--", label="NEST approx", c="C1")
-ax[0,1].set_xlabel("time (ms)")
-ax[0,1].set_ylabel("membrane potential V (mV)")
-ax[0,1].set_title("Postsynaptic neuron")
-ax[0,1].legend()
-
-ax[1,1].plot(ampaMonitor2.t/b2.ms, ampaMonitor2.s_AMPA_tot[0], c="black")
-ax[1,1].plot(mm2.get("events", "times"), mm2.get("events", "s_AMPA"), "--", c="C0")
-ax[1,1].plot(mm3.get("events", "times"), mm3.get("events", "s_AMPA"), "--", c="C1")
-ax[1,1].set_xlabel("time (ms)")
-ax[1,1].set_ylabel("s_AMPA")
-
-ax[2,1].plot(gabaMonitor2.t/b2.ms, gabaMonitor2.s_GABA_tot[0], c="black")
-ax[2,1].plot(mm2.get("events", "times"), mm2.get("events", "s_GABA"), "--", c="C0")
-ax[2,1].plot(mm3.get("events", "times"), mm3.get("events", "s_GABA"), "--", c="C1")
-ax[2,1].set_xlabel("time (ms)")
-ax[2,1].set_ylabel("s_GABA")
-
-ax[3,1].plot(nmdaMonitor2.t/b2.ms, nmdaMonitor2.s_NMDA_tot[0], c="black")
-ax[3,1].plot(mm2.get("events", "times"), mm2.get("events", "s_NMDA"), "--", c="C0")
-ax[3,1].plot(mm3.get("events", "times"), mm3.get("events", "s_NMDA"), "--", c="C1")
-ax[3,1].set_xlabel("time (ms)")
-ax[3,1].set_ylabel("s_NMDA")
-
-ax[1,0].axis("off")
-ax[2,0].axis("off")
-ax[3,0].axis("off")
-
-plt.show()
-
diff --git a/pynest/examples/wang_neuron.py b/pynest/examples/wang_neuron.py
deleted file mode 100644
index 339c045aeb..0000000000
--- a/pynest/examples/wang_neuron.py
+++ /dev/null
@@ -1,160 +0,0 @@
-"""
-docstring
-"""
-import nest
-import matplotlib.pyplot as plt
-import numpy as np
-
-nest.ResetKernel()
-nest.rng_seed = 12345
-
-w_ext = 40.
-w_ex = 1.
-w_in = -15.
-
-params_exact = {"tau_AMPA": 2.0,
- "tau_GABA": 5.0,
- "tau_rise_NMDA": 2.0,
- "tau_decay_NMDA": 100.0,
- "conc_Mg2": 1.0,
- "E_ex": 0.0,
- "E_in": -70.0,
- "E_L": -70.0,
- "V_th": -55.0,
- "C_m": 500.0,
- "g_L": 25.0,
- "V_reset": -70.0,
- "alpha": 0.5,
- "t_ref": 2.0}
-
-
-nrn1 = nest.Create("iaf_wang_2002", params_approx)
-pg = nest.Create("poisson_generator", {"rate": 50.})
-sr = nest.Create("spike_recorder")
-nrn2 = nest.Create("iaf_wang_2002", params_approx)
-
-nrn3 = nest.Create("iaf_wang_2002_exact", params_exact)
-
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-
-ampa_ext_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ext,
- "receptor_type": 1}
-
-ampa_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 1}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 3}
-
-gaba_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_in,
- "receptor_type": 2}
-
-conn_spec = {"rule": "all_to_all"}
-
-def s_soln(w, t, tau):
- isyn = np.zeros_like(t)
- useinds = t >= 0.
- isyn[useinds] = w * np.exp(-t[useinds] / tau)
- return isyn
-
-def spiketrain_response(t, tau, spiketrain, w):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- response += s_soln(w, t_, tau)
- return response
-
-def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- w_ = w * alpha * (1 - response[zero_arg])
- w_ = min(w_, 1 - response[zero_arg])
- response += s_soln(w_, t_, tau)
- return response
-
-nest.Connect(pg, nrn1, syn_spec=ampa_ext_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, sr)
-nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(mm1, nrn1)
-
-nest.Connect(mm2, nrn2)
-nest.Connect(mm3, nrn3)
-
-nest.Simulate(1000.)
-
-# get spike times from membrane potential
-# cannot use spike_recorder because we abuse exact spike timing
-V_m = mm1.get("events", "V_m")
-times = mm1.get("events", "times")
-diff = np.ediff1d(V_m, to_begin=0.)
-spikes = sr.get("events", "times")
-spikes = times[diff < -3]
-
-def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
- a = (1 / tau_decay - 1 / tau_rise)
-
- # argument of exp
- arg = t * a + alpha * x0 * tau_rise * (1 - np.exp(- t / tau_rise))
- return np.exp(arg)
-
-def nmda_fn(t, s0, x0, tau_decay, tau_rise, alpha):
- f1 = np.exp(- t / tau_decay + alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
-
- tvec = np.linspace(0, t, 1001)
- f2 = alpha * x0 * np.trapz(nmda_integrand(tvec, x0, tau_decay, tau_rise, alpha), x = tvec) + s0
- return f1, f2
-
-fig, ax = plt.subplots(4,2)
-fig.set_size_inches([12,10])
-fig.subplots_adjust(hspace=0.5)
-
-ax[0,0].plot(mm1.events["V_m"])
-ax[0,0].set_xlabel("time (ms)")
-ax[0,0].set_ylabel("membrane potential V (mV)")
-ax[0,0].legend()
-ax[0,0].set_title("Presynaptic neuron")
-
-ax[0,1].plot(mm2.events["V_m"])
-ax[0,1].plot(mm3.events["V_m"], "--")
-ax[0,1].set_xlabel("time (ms)")
-ax[0,1].set_ylabel("membrane potential V (mV)")
-ax[0,1].set_title("Postsynaptic neuron")
-
-
-ax[1,1].plot(mm2.events["s_AMPA"])
-ax[1,1].plot(mm3.events["s_AMPA"], "--")
-ax[1,1].set_xlabel("time (ms)")
-ax[1,1].set_ylabel("s_AMPA")
-
-
-ax[2,1].plot(mm2.events["s_GABA"])
-ax[2,1].plot(mm3.events["s_GABA"], "--")
-ax[2,1].set_xlabel("time (ms)")
-ax[2,1].set_ylabel("s_GABA")
-
-
-ax[3,1].plot(mm2.events["s_NMDA"])
-ax[3,1].plot(mm3.events["s_NMDA"], "--")
-ax[3,1].set_xlabel("time (ms)")
-ax[3,1].set_ylabel("s_NMDA")
-
-ax[1,0].axis("off")
-ax[2,0].axis("off")
-ax[3,0].axis("off")
-
-plt.show()
-
diff --git a/pynest/examples/wang_neuron_exact.py b/pynest/examples/wang_neuron_exact.py
deleted file mode 100644
index ff7514d9f9..0000000000
--- a/pynest/examples/wang_neuron_exact.py
+++ /dev/null
@@ -1,148 +0,0 @@
-"""
-docstring
-"""
-
-
-import nest
-import matplotlib.pyplot as plt
-import numpy as np
-
-nest.rng_seed = 12345
-
-nest.ResetKernel()
-
-alpha = 0.5
-tau_AMPA = 2.0
-tau_GABA = 5.0
-tau_rise_NMDA = 2.0
-tau_NMDA = 100.0
-
-Mg2 = 1.0
-
-t_ref = 2.0
-
-# reversal potentials
-E_ex = 0.
-E_in = -70.0
-E_L = -70.0
-
-V_th = -55.0
-V_reset = -70.
-C_m = 500.0
-g_L = 25.0
-
-
-# set through synaptic weights
-g_AMPA_rec = 1.0
-g_AMPA_ext = 100.0
-g_GABA = 1.0
-g_NMDA = 1.0
-
-neuron_params = {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_rise_NMDA": tau_rise_NMDA,
- "tau_decay_NMDA": tau_NMDA,
- "conc_Mg2": Mg2,
- "E_ex": E_ex,
- "E_in": E_in,
- "E_L": E_L,
- "V_th": V_th,
- "C_m": C_m,
- "g_L": g_L,
- "t_ref": t_ref}
-
-
-nrn1 = nest.Create("iaf_wang_2002_exact", neuron_params)
-nrn2 = nest.Create("iaf_wang_2002_exact", neuron_params)
-
-times = np.array([10.0, 20.0, 40.0, 80.0, 90.0])
-sg = nest.Create("spike_generator", {"spike_times": times})
-sr = nest.Create("spike_recorder")
-
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "NMDA_sum", "s_GABA"], "interval": 0.1})
-
-ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": g_AMPA_rec,
- "receptor_type": 1}
-
-ex_syn_spec_ext = {"synapse_model": "static_synapse",
- "weight": g_AMPA_ext,
- "receptor_type": 1}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": g_NMDA,
- "receptor_type": 3}
-
-in_syn_spec = {"synapse_model": "static_synapse",
- "weight": g_GABA,
- "receptor_type": 2}
-
-conn_spec = {"rule": "all_to_all"}
-
-def s_soln(w, t, tau):
- isyn = np.zeros_like(t)
- useinds = t >= 0.
- isyn[useinds] = w * np.exp(-t[useinds] / tau)
- return isyn
-
-def spiketrain_response(t, tau, spiketrain, w):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- response += s_soln(w, t_, tau)
- return response
-
-def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- w_ = w * alpha * (1 - response[zero_arg])
- w_ = min(w_, 1 - response[zero_arg])
- response += s_soln(w_, t_, tau)
- return response
-
-nest.Connect(sg, nrn1, syn_spec=ex_syn_spec_ext, conn_spec=conn_spec)
-nest.Connect(nrn1, sr)
-nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(mm1, nrn1)
-
-nest.Connect(mm2, nrn2)
-
-nest.Simulate(300.)
-
-# get spike times from membrane potential
-# cannot use spike_recorder because we abuse exact spike timing
-V_m = mm1.get("events", "V_m")
-times = mm1.get("events", "times")
-diff = np.ediff1d(V_m, to_begin=0.)
-spikes = sr.get("events", "times")
-spikes = times[diff < -3]
-
-# compute analytical solutimes = mm1.get("events", "times")
-ampa_soln = spiketrain_response(times, tau_AMPA, spikes, g_AMPA_rec)
-nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, g_NMDA, alpha)
-gaba_soln = spiketrain_response(times, tau_GABA, spikes, g_GABA)
-
-fig, ax = plt.subplots(4,2)
-ax[0,0].plot(mm1.events["V_m"])
-ax[0,1].plot(mm2.events["V_m"])
-
-ax[1,0].plot(mm1.events["s_AMPA"])
-ax[1,1].plot(mm2.events["s_AMPA"])
-ax[1,1].plot(ampa_soln, '--')
-
-ax[2,0].plot(mm1.events["s_GABA"])
-ax[2,1].plot(mm2.events["s_GABA"])
-ax[2,1].plot(gaba_soln, '--')
-
-ax[3,0].plot(mm1.events["NMDA_sum"])
-ax[3,1].plot(mm2.events["NMDA_sum"])
-ax[3,1].plot(nmda_soln, '--')
-
-plt.show()
-
diff --git a/pynest/examples/wong_wang_nmda_approximation.py b/pynest/examples/wong_wang_nmda_approximation.py
deleted file mode 100644
index db8200b72f..0000000000
--- a/pynest/examples/wong_wang_nmda_approximation.py
+++ /dev/null
@@ -1,188 +0,0 @@
-"""
-docstring
-"""
-
-import nest
-import matplotlib.pyplot as plt
-import numpy as np
-
-nest.ResetKernel()
-nest.rng_seed = 12345
-
-w_ext = 40.
-w_ex = 1.
-w_in = -15.
-
-params_exact = {"tau_AMPA": 2.0,
- "tau_GABA": 5.0,
- "tau_rise_NMDA": 2.0,
- "tau_decay_NMDA": 100.0,
- "conc_Mg2": 1.0,
- "E_ex": 0.0,
- "E_in": -70.0,
- "E_L": -70.0,
- "V_th": -55.0,
- "C_m": 500.0,
- "g_L": 25.0,
- "V_reset": -70.0,
- "alpha": 0.5,
- "t_ref": 2.0}
-
-params_approx = params_exact.copy()
-
-nrn1 = nest.Create("iaf_wang_2002", params_approx)
-pg = nest.Create("inhomogeneous_poisson_generator", {"rate_values": [400., 0.],
- "rate_times": [0.1, 10.]})
-sr = nest.Create("spike_recorder")
-nrn2 = nest.Create("iaf_wang_2002", params_approx)
-
-nrn3 = nest.Create("iaf_wang_2002_exact", params_exact)
-
-mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-mm3 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-
-ampa_ext_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ext,
- "receptor_type": 1}
-
-ampa_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 1}
-
-nmda_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 3}
-
-gaba_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_in,
- "receptor_type": 2}
-
-conn_spec = {"rule": "all_to_all"}
-
-def s_soln(w, t, tau):
- isyn = np.zeros_like(t)
- useinds = t >= 0.
- isyn[useinds] = w * np.exp(-t[useinds] / tau)
- return isyn
-
-def spiketrain_response(t, tau, spiketrain, w):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- response += s_soln(w, t_, tau)
- return response
-
-def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
- response = np.zeros_like(t)
- for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
- w_ = w * alpha * (1 - response[zero_arg])
- w_ = min(w_, 1 - response[zero_arg])
- response += s_soln(w_, t_, tau)
- return response
-
-nest.Connect(pg, nrn1, syn_spec=ampa_ext_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, sr)
-nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=ampa_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=gaba_syn_spec, conn_spec=conn_spec)
-nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec, conn_spec=conn_spec)
-nest.Connect(mm1, nrn1)
-
-nest.Connect(mm2, nrn2)
-nest.Connect(mm3, nrn3)
-
-nest.Simulate(1000.)
-
-times = mm3.get("events", "times")
-nest_nmda = mm3.get("events", "s_NMDA")
-nest_nmda_approx = mm2.get("events", "s_NMDA")
-times -= times[(nest_nmda > 0).argmax()] - 0.1
-
-from scipy.integrate import cumtrapz
-def nmda_integrand(t, x0, tau_decay, tau_rise, alpha):
- a = (1 / tau_decay - 1 / tau_rise)
-
- # argument of exp
- arg = t * a + alpha * x0 * tau_rise * (1 - np.exp(- t / tau_rise))
- return np.exp(arg)
-
-def nmda_fn(t, s0, x0, tau_decay, tau_rise, alpha):
- f1 = np.exp(- t / tau_decay - alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
-
- tvec = np.linspace(0, t, 1001)
- integrand = nmda_integrand(tvec, x0, tau_decay, tau_rise, alpha)
-# f2 = alpha * x0 * np.trapz(integrand, x = tvec) + s0
- f2 = np.trapz(integrand, x = tvec)
- return f1, f2 # f1 * f2
-
-def nmda_fn_approx(t, x0, tau_decay, tau_rise, alpha):
- f1 = np.exp(-t / tau_decay - alpha * x0 * tau_rise * (1 - np.exp(-t / tau_rise)))
- f2 = (np.exp((1 - np.exp(-t / tau_rise)) * alpha * tau_rise) - 1) / alpha
-
- return f1, f2
-
-
-t = np.arange(0.1, 1000, 0.1)
-s0 = 0.
-f1s, f2s = [], []
-for t_ in t:
- f1, f2 = nmda_fn(t_, 0., 1., 100., 2., 0.5)
- f1s.append(f1)
- f2s.append(f2)
-
-s_nmda = np.array([f1s[i] * (f2s[i] * 0.5 + s0) for i, _ in enumerate(f1s)])
-
-f1_approx, f2_approx = nmda_fn_approx(t, 1, 100, 2, 0.5)
-s_nmda_approx = f1_approx * (f2_approx * 0.5 + s0)
-
-def nmda_approx_exp(t, tau_rise, alpha, tau_decay):
- f1 = np.exp(-alpha * tau_rise * (1 - np.exp(-t / tau_rise)))
- f2 = -(1 - np.exp(alpha * tau_rise * (1 - np.exp(-t / tau_rise))))
- return f1, f2
-
-f1_exp, f2_exp = nmda_approx_exp(t, 2.0, 0.5, 100)
-
-i = 40
-s_nmda_exp = (f1_exp[i] * f2_exp[i] + f1_exp[i] * s0) * np.exp(-t / 100)
-
-plt.plot(t, s_nmda, color="C0")
-plt.plot(t, s_nmda_approx, color="C1")
-plt.plot(t, s_nmda_exp, "--", color="C2")
-
-plt.show()
-
-from scipy.special import expn, gamma
-def limfun(tau_rise, tau_decay, alpha):
- f1 = np.exp(-alpha * tau_rise)
-
- at = alpha * tau_rise
- tr_td = tau_rise / tau_decay
- f0 = -at * expn(tr_td, at) + at ** tr_td * gamma(1 - tr_td)
- return f0, f1
-
-f0, f1 = limfun(2, 100, 0.5)
-
-plt.plot(t, s_nmda)
-# plt.plot(times, nest_nmda)
-# plt.plot(times, nest_nmda_approx)
-plt.plot(t, np.exp(-t / 100) * (f0 + s0 * f1))
-plt.plot(t, np.exp(-t / 100) * (s0 + 0.5 * (1 - s0)))
-
-plt.show()
-
-
-a = np.exp(-t / 100) * (f0 + s0 * f1)
-b = np.exp(-t / 100) * (s0 + 0.5 * (1 - s0))
-c = s_nmda.copy()
-
-print(np.trapz(a, x=t))
-print(np.trapz(b, x=t))
-print(np.trapz(c, x=t))
-
-
From ed03c83bc19239ee37a3923c777fef57cfbd4fa7 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 5 Feb 2024 14:39:20 +0100
Subject: [PATCH 052/184] clean up
---
models/iaf_wang_2002.h | 8 +-------
models/iaf_wang_2002_exact.cpp | 5 -----
models/iaf_wang_2002_exact.h | 1 -
3 files changed, 1 insertion(+), 13 deletions(-)
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 0af576bc44..b65d5ad612 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -95,12 +95,11 @@ where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets f
.. math::
- k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) \\[3ex]
+ k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) - 1 \\[3ex]
k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r} \Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 - \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
where :math:`\mathrm{E_N}` is the generalized exponential integral (https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function (https://en.wikipedia.org/wiki/Gamma_function).
-
The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
@@ -141,10 +140,6 @@ The following values can be recorded.
s_NMDA sum of NMDA gating variables
=========== ===========================================================
-.. note::
- It is possible to set values for :math:`V_\mathrm{m}`, :math:`S_\mathrm{AMPA}` and :math:`S_\mathrm{GABA}` when creating the model, while the
- different :math:`s_{j,\mathrm{NMDA}}` (`j` represents presynaptic neuron `j`) can not be set by the user.
-
.. note::
:math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST model, so setting the
variables is thus done by changing the weights.
@@ -173,7 +168,6 @@ iaf_cond_alpha, ht_neuron
EndUserDocs */
-
void register_iaf_wang_2002( const std::string& name );
class iaf_wang_2002 : public ArchivingNode
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index a2fddd99a6..c237ff20ca 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -183,21 +183,16 @@ nest::iaf_wang_2002_exact::Parameters_::set( const DictionaryDatum& d, Node* nod
updateValueParam< double >( d, names::V_reset, V_reset, node );
updateValueParam< double >( d, names::t_ref, t_ref, node );
updateValueParam< double >( d, names::E_L, E_L, node );
-
updateValueParam< double >( d, names::E_ex, E_ex, node );
updateValueParam< double >( d, names::E_in, E_in, node );
-
updateValueParam< double >( d, names::C_m, C_m, node );
updateValueParam< double >( d, names::g_L, g_L, node );
-
updateValueParam< double >( d, names::tau_AMPA, tau_AMPA, node );
updateValueParam< double >( d, names::tau_GABA, tau_GABA, node );
updateValueParam< double >( d, names::tau_rise_NMDA, tau_rise_NMDA, node );
updateValueParam< double >( d, names::tau_decay_NMDA, tau_decay_NMDA, node );
-
updateValueParam< double >( d, names::alpha, alpha, node );
updateValueParam< double >( d, names::conc_Mg2, conc_Mg2, node );
-
updateValueParam< double >( d, names::gsl_error_tol, gsl_error_tol, node );
if ( V_reset >= V_th )
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 0ff2d8c439..59e21e122e 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -226,7 +226,6 @@ class iaf_wang_2002_exact : public ArchivingNode
};
-
// make dynamics function quasi-member
friend int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
From 9ba059c6daa246be122929e6a3088c3daf1b5b21 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Mon, 5 Feb 2024 15:17:52 +0100
Subject: [PATCH 053/184] add comments to wang_decision_making.py
---
pynest/examples/wang_decision_making.py | 171 +++++++++++++++++-------
1 file changed, 126 insertions(+), 45 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index b51fee5593..1e74403dc3 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -1,5 +1,44 @@
+# -*- coding: utf-8 -*-
+#
+# brunel_alpha_nest.py
+#
+# This file is part of NEST.
+#
+# Copyright (C) 2004 The NEST Initiative
+#
+# NEST is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# NEST is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with NEST. If not, see .
+
"""
-docstring
+Decision making in recurrent network with NMDA-dynamics
+------------------------------------------------------------
+
+This script simulates the network modelled in [1]_.
+An excitatory and an inhibitory population receives input
+from an external population modelled as a Poisson process.
+Two different subsets of the excitatory population,
+comprising 15% of the total population each, receive additional
+inputs from a time-inhomogeneous Poisson process, where the
+coherence between the two signals can be varied. Local inhibition
+mediates a winner-takes-all comptetion, and the activity of
+one of the sub-population is suppressed.
+
+References
+~~~~~~~~~~
+.. [1] Wang X-J (2002). Probabilistic Decision Making by Slow Reverberation in
+Cortical Circuits. Neuron, Volume 36, Issue 5, Pages 955-968.
+https://doi.org/10.1016/S0896-6273(02)01092-9.
+
"""
import nest
@@ -10,11 +49,16 @@
np.random.seed(1234)
rng = np.random.default_rng()
+# Use approximate model, can be replaced by "iaf_wang_2002_exact"
model = "iaf_wang_2002"
dt = 0.1
nest.set(resolution=dt, print_time=True)
+##################################################
+# Set parameter values, taken from [1]_.
+
+# conductances
g_AMPA_ex = 0.05
g_AMPA_ext_ex = 2.1
g_NMDA_ex = 0.165
@@ -25,8 +69,7 @@
g_NMDA_in = 0.13
g_GABA_in = 1.0
-
-# Parameters from paper
+# neuron parameters
epop_params = {"tau_GABA": 5.0,
"tau_AMPA": 2.0,
"tau_decay_NMDA": 100.0,
@@ -43,7 +86,6 @@
"t_ref": 2.0 # refreactory period
}
-
ipop_params = {"tau_GABA": 5.0,
"tau_AMPA": 2.0,
"tau_decay_NMDA": 100.0,
@@ -60,37 +102,54 @@
"t_ref": 1.0 # refreactory period
}
-simtime = 4000.
+# synaptic weights
+w_plus = 1.7
+w_minus = 1 - f * (w_plus - 1) / (1 - f)
+
+
+# signals to the two different excitatory sub-populations
+# the signal is given by a time-inhomogeneous Poisson process,
+# where the expectations are constant over intervals of 50ms,
+# and then change. The values for each interval are normally
+# distributed, with means mu_a and mu_b, and standard deviation
+# sigma.
signal_start = 1000.
signal_duration = 2000.
signal_update_interval = 50.
f = 0.15 # proportion of neurons receiving signal inputs
-w_plus = 1.7
-w_minus = 1 - f * (w_plus - 1) / (1 - f)
+# compute expectations of the time-inhomogeneous Poisson processes
+mu_0 = 40. # base rate
+rho_a = mu_0 / 100 # scaling factors coherence
+rho_b = rho_a
+c = 0. # coherence
+sigma = 4. # standard deviation
+mu_a = mu_0 + rho_a * c # expectation for pop A
+mu_b = mu_0 - rho_b * c # expectation for pop B
+
+# sample values for the Poisson process
+num_updates = int(signal_duration / signal_update_interval)
+update_times = np.arange(0, signal_duration, signal_update_interval)
+update_times[0] = 0.1
+rates_a = np.random.normal(mu_a, sigma, size=num_updates)
+rates_b = np.random.normal(mu_b, sigma, size=num_updates)
+
+
+
delay = 0.5
+# number of neurons in each population
NE = 1600
NI = 400
+
+##################################################
+# Create neurons and devices
+
selective_pop1 = nest.Create(model, int(0.15 * NE), params=epop_params)
selective_pop2 = nest.Create(model, int(0.15 * NE), params=epop_params)
nonselective_pop = nest.Create(model, int(0.7 * NE), params=epop_params)
inhibitory_pop = nest.Create(model, NI, params=ipop_params)
-mu_0 = 40.
-rho_a = mu_0 / 100
-rho_b = rho_a
-c = 0.
-sigma = 4.
-mu_a = mu_0 + rho_a * c
-mu_b = mu_0 - rho_b * c
-
-num_updates = int(signal_duration / signal_update_interval)
-update_times = np.arange(0, signal_duration, signal_update_interval)
-update_times[0] = 0.1
-rates_a = np.random.normal(mu_a, sigma, size=num_updates)
-rates_b = np.random.normal(mu_b, sigma, size=num_updates)
-
poisson_a = nest.Create("inhomogeneous_poisson_generator",
params={"origin": signal_start-0.1,
"start": 0.,
@@ -107,6 +166,20 @@
poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
+sr_nonselective = nest.Create("spike_recorder")
+sr_selective1 = nest.Create("spike_recorder")
+sr_selective2 = nest.Create("spike_recorder")
+sr_inhibitory = nest.Create("spike_recorder")
+
+mm_selective1 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+mm_selective2 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+mm_nonselective = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
+
+
+##################################################
+# Define synapse specifications
+
syn_spec_pot_AMPA = {"synapse_model": "static_synapse", "weight":w_plus * g_AMPA_ex, "delay":delay, "receptor_type": 1}
syn_spec_pot_NMDA = {"synapse_model": "static_synapse", "weight":w_plus * g_NMDA_ex, "delay":delay, "receptor_type": 3}
@@ -124,19 +197,10 @@
exte_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_ex, "delay":0.1, "receptor_type": 1}
exti_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_in, "delay":0.1, "receptor_type": 1}
-sr_nonselective = nest.Create("spike_recorder")
-sr_selective1 = nest.Create("spike_recorder")
-sr_selective2 = nest.Create("spike_recorder")
-sr_inhibitory = nest.Create("spike_recorder")
-mm_selective1 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-mm_selective2 = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-mm_nonselective = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-mm_inhibitory = nest.Create("multimeter", {"record_from": ["V_m", "s_NMDA", "s_AMPA", "s_GABA"]})
-
-
-# # # Create connections
+##################################################
+# Create connections
# from external
nest.Connect(poisson_0, nonselective_pop + selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=exte_syn_spec)
@@ -185,15 +249,21 @@
nest.Connect(inhibitory_pop, sr_inhibitory)
-
-# multimeters
+# multimeters record from single neuron from each population.
+# since the network is fully connected, it's the same for all
+# neurons in the same population.
nest.Connect(mm_selective1, selective_pop1[0])
nest.Connect(mm_selective2, selective_pop2[0])
nest.Connect(mm_nonselective, nonselective_pop[0])
nest.Connect(mm_inhibitory, inhibitory_pop[0])
+##################################################
+# Run simulation
nest.Simulate(5000.)
+
+##################################################
+# Collect data from simulation
spikes_nonselective = sr_nonselective.get("events", "times")
spikes_selective1 = sr_selective1.get("events", "times")
spikes_selective2 = sr_selective2.get("events", "times")
@@ -220,51 +290,62 @@
s_NMDA_inhibitory = mm_inhibitory.get("events", "s_NMDA")
-res = 1.0
+
+##################################################
+# Plots
+
+# bins for histograms
+res = 1.0
bins = np.arange(0, 4001, res) - 0.001
fig, ax = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True)
fig.tight_layout()
-d = NE * f * (res / 1000)
+
+# selective populations
+num = NE * f * (res / 1000)
hist1, _ = np.histogram(spikes_selective1, bins=bins)
hist2, _ = np.histogram(spikes_selective2, bins=bins)
-
-ax[0,0].plot(hist1 / d)
+ax[0,0].plot(hist1 / num)
ax[0,0].set_title("Selective pop A")
-ax[0,1].plot(hist2 / d)
+ax[0,1].plot(hist2 / num)
ax[0,1].set_title("Selective pop B")
-d = NE * (1 - 2*f) * res / 1000
+# nonselective population
+num = NE * (1 - 2*f) * res / 1000
hist, _ = np.histogram(spikes_nonselective, bins=bins)
-ax[1,0].plot(hist / d)
+ax[1,0].plot(hist / num)
ax[1,0].set_title("Nonselective pop")
-d = NI * res / 1000
+# inhibitory population
+num = NI * res / 1000
hist, _ = np.histogram(spikes_inhibitory, bins=bins)
-ax[1,1].plot(hist / d)
+ax[1,1].plot(hist / num)
ax[1,1].set_title("Inhibitory pop")
-
fig, ax = plt.subplots(ncols=4, nrows=4, sharex=True, sharey="row")
fig.tight_layout()
-
+# AMPA conductances
ax[0,0].plot(s_AMPA_selective1)
ax[0,1].plot(s_AMPA_selective2)
ax[0,2].plot(s_AMPA_nonselective)
ax[0,3].plot(s_AMPA_inhibitory)
+# NMDA conductances
ax[1,0].plot(s_NMDA_selective1)
ax[1,1].plot(s_NMDA_selective2)
ax[1,2].plot(s_NMDA_nonselective)
ax[1,3].plot(s_NMDA_inhibitory)
+
+# GABA conductances
ax[2,0].plot(s_GABA_selective1)
ax[2,1].plot(s_GABA_selective2)
ax[2,2].plot(s_GABA_nonselective)
ax[2,3].plot(s_GABA_inhibitory)
+# Membrane potential
ax[3,0].plot(vm_selective1)
ax[3,1].plot(vm_selective2)
ax[3,2].plot(vm_nonselective)
From c980c87e1948c65fd95c40550d8decb23365f2a6 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 10:02:36 +0100
Subject: [PATCH 054/184] update doc
---
models/iaf_wang_2002.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index b65d5ad612..72cc641d2a 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -98,7 +98,7 @@ where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets f
k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) - 1 \\[3ex]
k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r} \Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 - \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
-where :math:`\mathrm{E_N}` is the generalized exponential integral (https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function (https://en.wikipedia.org/wiki/Gamma_function).
+where :math:`\mathrm{E_N}` is the generalized exponential integral (https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function (https://en.wikipedia.org/wiki/Gamma_function). For these values of :math:`k_0` and :math:`k_1`, the approximate model will approach the exact model for large t.
The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
From 99766edb70ea1593660342837374eeaf3cb6b952 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 11:18:08 +0100
Subject: [PATCH 055/184] update test
---
testsuite/pytests/test_iaf_wang_2002.py | 118 ++++++++++++++----------
1 file changed, 70 insertions(+), 48 deletions(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index 18b355b9d0..30d15315d9 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -31,17 +31,33 @@
import numpy as np
import numpy.testing as nptest
import pytest
+from scipy.special import expn, gamma
+
+
+w_ex = 40.
+w_in = -15.
+alpha = 0.5
+tau_AMPA = 2.0
+tau_GABA = 5.0
+tau_rise_NMDA = 1.8
+tau_decay_NMDA = 100.0
+
def s_soln(w, t, tau):
"""
- Solution for GABA/AMPA receptors
+ Solution for synaptic variables
"""
isyn = np.zeros_like(t)
useinds = t >= 0.
isyn[useinds] = w * np.exp(-t[useinds] / tau)
return isyn
+
def spiketrain_response(t, tau, spiketrain, w):
+ """
+ Response for AMPA/NMDA
+ """
+
response = np.zeros_like(t)
for sp in spiketrain:
t_ = t - 1. - sp
@@ -49,75 +65,81 @@ def spiketrain_response(t, tau, spiketrain, w):
response += s_soln(w, t_, tau)
return response
-def spiketrain_response_nmda(t, tau, spiketrain, w, alpha):
+
+def spiketrain_response_nmda(t, spiketrain):
"""
- Solution for NMDA receptors
+ Response for NMDA
"""
+ tr = tau_rise_NMDA / tau_decay_NMDA
+ at = alpha * tau_rise_NMDA
+ k_0 = -expn(tr, at) * at + at**tr * gamma(1 - tr)
+ k_1 = np.exp(-alpha * tau_rise_NMDA) - 1
+
response = np.zeros_like(t)
for sp in spiketrain:
t_ = t - 1. - sp
zero_arg = t_ == 0.
- w_ = w * alpha * (1 - response[zero_arg])
- w_ = min(w_, 1 - response[zero_arg])
- response += s_soln(w_, t_, tau)
+ s0 = response[zero_arg]
+ w = k_0 + k_1 * s0
+ response += s_soln(w, t_, tau_decay_NMDA)
+ response *= w_ex
return response
def test_wang():
- w_ex = 40.
- w_in = -15.
- alpha = 0.5
- tau_AMPA = 2.0
- tau_GABA = 5.0
- tau_NMDA = 100.0
-
# Create 2 neurons, so that the Wang dynamics are present
nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
"tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA})
-
- pg = nest.Create("poisson_generator", {"rate": 50.})
- sr = nest.Create("spike_recorder", {"time_in_steps": True})
+ "tau_decay_NMDA": tau_decay_NMDA,
+ "tau_rise_NMDA": tau_rise_NMDA})
+
nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
"tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_NMDA,
+ "tau_decay_NMDA": tau_decay_NMDA,
+ "tau_rise_NMDA": tau_rise_NMDA,
"t_ref": 0.})
-
- mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
- mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1})
-
- ex_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_ex,
- "receptor_type": 0}
-
- in_syn_spec = {"synapse_model": "static_synapse",
- "weight": w_in}
-
- conn_spec = {"rule": "all_to_all"}
-
- nest.Connect(pg, nrn1, syn_spec=ex_syn_spec, conn_spec=conn_spec)
+
+ pg = nest.Create("poisson_generator", {"rate": 50.})
+ sr = nest.Create("spike_recorder", {"time_in_steps": True})
+
+ mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
+ "interval": 0.1,
+ "time_in_steps": True})
+ mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
+ "interval": 0.1,
+ "time_in_steps": True})
+
+ ampa_syn_spec = {"weight": w_ex,
+ "receptor_type": 1}
+
+ gaba_syn_spec = {"weight": w_in,
+ "receptor_type": 2}
+
+ nmda_syn_spec = {"weight": w_ex,
+ "receptor_type": 3}
+
+ nest.Connect(pg, nrn1, syn_spec=ampa_syn_spec)
nest.Connect(nrn1, sr)
- nest.Connect(nrn1, nrn2, syn_spec=ex_syn_spec, conn_spec=conn_spec)
- nest.Connect(nrn1, nrn2, syn_spec=in_syn_spec, conn_spec=conn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec)
nest.Connect(mm1, nrn1)
-
nest.Connect(mm2, nrn2)
-
+
nest.Simulate(1000.)
-
- # get spike times from membrane potential
- # cannot use spike_recorder because we abuse exact spike timing
- V_m = mm1.get("events", "V_m")
- times = mm1.get("events", "times")
- diff = np.ediff1d(V_m, to_begin=0.)
- spikes = sr.get("events", "times")
- spikes = times[diff < -3]
-
+
+ spikes = sr.get("events", "times") * nest.resolution
+
# compute analytical solutions
- times = mm1.get("events", "times")
+ times = mm1.get("events", "times") * nest.resolution
ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
- nmda_soln = spiketrain_response_nmda(times, tau_NMDA, spikes, w_ex, alpha)
+ nmda_soln = spiketrain_response_nmda(times, spikes)
gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
-
+
+ import matplotlib.pyplot as plt
+ plt.plot(mm2.events["s_NMDA"])
+ plt.plot(nmda_soln)
+ plt.show()
+
nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
nptest.assert_array_almost_equal(nmda_soln, mm2.events["s_NMDA"])
From a2dae5b92c2e56a9d186cf1f0306a0107d029617 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 11:19:15 +0100
Subject: [PATCH 056/184] remove comment
---
models/iaf_wang_2002.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index cc3f4906d8..7dd2eb9030 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -471,8 +471,8 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
- const double s_NMDA_delta = V_.S_jump_0 + V_.S_jump_1 * S_.s_NMDA_pre;//- S_.s_NMDA_pre;
- S_.s_NMDA_pre += s_NMDA_delta; // guaranteed to be <= 1.
+ const double s_NMDA_delta = V_.S_jump_0 + V_.S_jump_1 * S_.s_NMDA_pre;
+ S_.s_NMDA_pre += s_NMDA_delta;
SpikeEvent se;
se.set_offset( s_NMDA_delta );
From 79e383f6a8f0dcb6955d72c7bdd872a80250c510 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 11:49:34 +0100
Subject: [PATCH 057/184] add receptor types dictionary
---
models/iaf_wang_2002.h | 6 ++
pynest/examples/wang_decision_making.py | 90 ++++++++++++++++++-------
testsuite/pytests/test_iaf_wang_2002.py | 9 ++-
3 files changed, 78 insertions(+), 27 deletions(-)
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 72cc641d2a..7bca23552a 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -446,6 +446,12 @@ iaf_wang_2002::get_status( DictionaryDatum& d ) const
S_.get( d );
ArchivingNode::get_status( d );
+ DictionaryDatum receptor_type = new Dictionary();
+ ( *receptor_type )[ names::AMPA ] = AMPA;
+ ( *receptor_type )[ names::GABA ] = GABA;
+ ( *receptor_type )[ names::NMDA ] = NMDA;
+ ( *d )[ names::receptor_types ] = receptor_type;
+
( *d )[ names::recordables ] = recordablesMap_.get_list();
}
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 1e74403dc3..450ebf535d 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -102,11 +102,6 @@
"t_ref": 1.0 # refreactory period
}
-# synaptic weights
-w_plus = 1.7
-w_minus = 1 - f * (w_plus - 1) / (1 - f)
-
-
# signals to the two different excitatory sub-populations
# the signal is given by a time-inhomogeneous Poisson process,
# where the expectations are constant over intervals of 50ms,
@@ -133,6 +128,9 @@
rates_a = np.random.normal(mu_a, sigma, size=num_updates)
rates_b = np.random.normal(mu_b, sigma, size=num_updates)
+# synaptic weights
+w_plus = 1.7
+w_minus = 1 - f * (w_plus - 1) / (1 - f)
delay = 0.5
@@ -145,9 +143,9 @@
##################################################
# Create neurons and devices
-selective_pop1 = nest.Create(model, int(0.15 * NE), params=epop_params)
-selective_pop2 = nest.Create(model, int(0.15 * NE), params=epop_params)
-nonselective_pop = nest.Create(model, int(0.7 * NE), params=epop_params)
+selective_pop1 = nest.Create(model, int(f * NE), params=epop_params)
+selective_pop2 = nest.Create(model, int(f * NE), params=epop_params)
+nonselective_pop = nest.Create(model, int((1 - 2 * f) * NE), params=epop_params)
inhibitory_pop = nest.Create(model, NI, params=ipop_params)
poisson_a = nest.Create("inhomogeneous_poisson_generator",
@@ -180,22 +178,66 @@
##################################################
# Define synapse specifications
-syn_spec_pot_AMPA = {"synapse_model": "static_synapse", "weight":w_plus * g_AMPA_ex, "delay":delay, "receptor_type": 1}
-syn_spec_pot_NMDA = {"synapse_model": "static_synapse", "weight":w_plus * g_NMDA_ex, "delay":delay, "receptor_type": 3}
-
-syn_spec_dep_AMPA = {"synapse_model": "static_synapse", "weight":w_minus * g_AMPA_ex, "delay":delay, "receptor_type": 1}
-syn_spec_dep_NMDA = {"synapse_model": "static_synapse", "weight":w_minus * g_NMDA_ex, "delay":delay, "receptor_type": 3}
-
-ie_syn_spec = {"synapse_model": "static_synapse", "weight": -1.0 * g_GABA_ex, "delay":delay, "receptor_type": 2}
-ii_syn_spec = {"synapse_model": "static_synapse", "weight": -1.0 * g_GABA_in, "delay":delay, "receptor_type": 2}
-
-ei_syn_spec_AMPA = {"synapse_model": "static_synapse", "weight": 1.0 * g_AMPA_in, "delay":delay, "receptor_type": 1}
-ei_syn_spec_NMDA = {"synapse_model": "static_synapse", "weight": 1.0 * g_NMDA_in, "delay":delay, "receptor_type": 3}
-ee_syn_spec_AMPA = {"synapse_model": "static_synapse", "weight": 1.0 * g_AMPA_ex, "delay":delay, "receptor_type": 1}
-ee_syn_spec_NMDA = {"synapse_model": "static_synapse", "weight": 1.0 * g_NMDA_ex, "delay":delay, "receptor_type": 3}
-
-exte_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_ex, "delay":0.1, "receptor_type": 1}
-exti_syn_spec = {"synapse_model": "static_synapse", "weight":g_AMPA_ext_in, "delay":0.1, "receptor_type": 1}
+receptor_types = selective_pop1[0].get("receptor_types")
+
+syn_spec_pot_AMPA = {"synapse_model": "static_synapse",
+ "weight":w_plus * g_AMPA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["AMPA"]}
+syn_spec_pot_NMDA = {"synapse_model": "static_synapse",
+ "weight":w_plus * g_NMDA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["NMDA"]}
+
+syn_spec_dep_AMPA = {"synapse_model": "static_synapse",
+ "weight":w_minus * g_AMPA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["AMPA"]}
+
+syn_spec_dep_NMDA = {"synapse_model": "static_synapse",
+ "weight":w_minus * g_NMDA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["NMDA"]}
+
+ie_syn_spec = {"synapse_model": "static_synapse",
+ "weight": -1.0 * g_GABA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["GABA"]}
+
+ii_syn_spec = {"synapse_model": "static_synapse",
+ "weight": -1.0 * g_GABA_in,
+ "delay":delay,
+ "receptor_type": receptor_types["GABA"]}
+
+ei_syn_spec_AMPA = {"synapse_model": "static_synapse",
+ "weight": 1.0 * g_AMPA_in,
+ "delay":delay,
+ "receptor_type": receptor_types["AMPA"]}
+
+ei_syn_spec_NMDA = {"synapse_model": "static_synapse",
+ "weight": 1.0 * g_NMDA_in,
+ "delay":delay,
+ "receptor_type": receptor_types["NMDA"]}
+
+ee_syn_spec_AMPA = {"synapse_model": "static_synapse",
+ "weight": 1.0 * g_AMPA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["AMPA"]}
+
+ee_syn_spec_NMDA = {"synapse_model": "static_synapse",
+ "weight": 1.0 * g_NMDA_ex,
+ "delay":delay,
+ "receptor_type": receptor_types["NMDA"]}
+
+exte_syn_spec = {"synapse_model": "static_synapse",
+ "weight":g_AMPA_ext_ex,
+ "delay":0.1,
+ "receptor_type": receptor_types["AMPA"]}
+
+exti_syn_spec = {"synapse_model": "static_synapse",
+ "weight":g_AMPA_ext_in,
+ "delay":0.1,
+ "receptor_type": receptor_types["AMPA"]}
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index 30d15315d9..e2959ff88d 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -98,6 +98,9 @@ def test_wang():
"tau_rise_NMDA": tau_rise_NMDA,
"t_ref": 0.})
+ receptor_types = nrn1.get("receptor_types")
+
+
pg = nest.Create("poisson_generator", {"rate": 50.})
sr = nest.Create("spike_recorder", {"time_in_steps": True})
@@ -109,13 +112,13 @@ def test_wang():
"time_in_steps": True})
ampa_syn_spec = {"weight": w_ex,
- "receptor_type": 1}
+ "receptor_type": receptor_types["AMPA"]}
gaba_syn_spec = {"weight": w_in,
- "receptor_type": 2}
+ "receptor_type": receptor_types["GABA"]}
nmda_syn_spec = {"weight": w_ex,
- "receptor_type": 3}
+ "receptor_type": receptor_types["NMDA"]}
nest.Connect(pg, nrn1, syn_spec=ampa_syn_spec)
nest.Connect(nrn1, sr)
From 5c14f8a5dccb42ad16e8b9aaca00bac80027e40f Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 11:52:04 +0100
Subject: [PATCH 058/184] black
---
pynest/examples/wang_decision_making.py | 351 ++++++++++--------
testsuite/pytests/test_iaf_wang_2002.py | 77 ++--
testsuite/pytests/test_iaf_wang_2002_exact.py | 36 +-
3 files changed, 252 insertions(+), 212 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 450ebf535d..3acb81aef0 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -70,37 +70,39 @@
g_GABA_in = 1.0
# neuron parameters
-epop_params = {"tau_GABA": 5.0,
- "tau_AMPA": 2.0,
- "tau_decay_NMDA": 100.0,
- "tau_rise_NMDA": 2.0,
- "alpha": 0.5,
- "conc_Mg2": 1.0,
- "g_L": 25., # leak conductance
- "E_L": -70.0, # leak reversal potential
- "E_ex": 0.0, # excitatory reversal potential
- "E_in": -70.0, # inhibitory reversal potential
- "V_reset": -55.0, # reset potential
- "V_th": -50.0, # threshold
- "C_m": 500.0, # membrane capacitance
- "t_ref": 2.0 # refreactory period
- }
-
-ipop_params = {"tau_GABA": 5.0,
- "tau_AMPA": 2.0,
- "tau_decay_NMDA": 100.0,
- "tau_rise_NMDA": 2.0,
- "alpha": 0.5,
- "conc_Mg2": 1.0,
- "g_L": 20., # leak conductance
- "E_L": -70.0, # leak reversal potential
- "E_ex": 0.0, # excitatory reversal potential
- "E_in": -70.0, # inhibitory reversal potential
- "V_reset": -55.0, # reset potential
- "V_th": -50.0, # threshold
- "C_m": 200.0, # membrane capacitance
- "t_ref": 1.0 # refreactory period
- }
+epop_params = {
+ "tau_GABA": 5.0,
+ "tau_AMPA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "tau_rise_NMDA": 2.0,
+ "alpha": 0.5,
+ "conc_Mg2": 1.0,
+ "g_L": 25.0, # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
+ "C_m": 500.0, # membrane capacitance
+ "t_ref": 2.0, # refreactory period
+}
+
+ipop_params = {
+ "tau_GABA": 5.0,
+ "tau_AMPA": 2.0,
+ "tau_decay_NMDA": 100.0,
+ "tau_rise_NMDA": 2.0,
+ "alpha": 0.5,
+ "conc_Mg2": 1.0,
+ "g_L": 20.0, # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
+ "C_m": 200.0, # membrane capacitance
+ "t_ref": 1.0, # refreactory period
+}
# signals to the two different excitatory sub-populations
# the signal is given by a time-inhomogeneous Poisson process,
@@ -108,18 +110,18 @@
# and then change. The values for each interval are normally
# distributed, with means mu_a and mu_b, and standard deviation
# sigma.
-signal_start = 1000.
-signal_duration = 2000.
-signal_update_interval = 50.
-f = 0.15 # proportion of neurons receiving signal inputs
+signal_start = 1000.0
+signal_duration = 2000.0
+signal_update_interval = 50.0
+f = 0.15 # proportion of neurons receiving signal inputs
# compute expectations of the time-inhomogeneous Poisson processes
-mu_0 = 40. # base rate
-rho_a = mu_0 / 100 # scaling factors coherence
+mu_0 = 40.0 # base rate
+rho_a = mu_0 / 100 # scaling factors coherence
rho_b = rho_a
-c = 0. # coherence
-sigma = 4. # standard deviation
-mu_a = mu_0 + rho_a * c # expectation for pop A
-mu_b = mu_0 - rho_b * c # expectation for pop B
+c = 0.0 # coherence
+sigma = 4.0 # standard deviation
+mu_a = mu_0 + rho_a * c # expectation for pop A
+mu_b = mu_0 - rho_b * c # expectation for pop B
# sample values for the Poisson process
num_updates = int(signal_duration / signal_update_interval)
@@ -148,21 +150,29 @@
nonselective_pop = nest.Create(model, int((1 - 2 * f) * NE), params=epop_params)
inhibitory_pop = nest.Create(model, NI, params=ipop_params)
-poisson_a = nest.Create("inhomogeneous_poisson_generator",
- params={"origin": signal_start-0.1,
- "start": 0.,
- "stop": signal_duration,
- "rate_times": update_times,
- "rate_values": rates_a})
-
-poisson_b = nest.Create("inhomogeneous_poisson_generator",
- params={"origin": signal_start-0.1,
- "start": 0.,
- "stop": signal_duration,
- "rate_times": update_times,
- "rate_values": rates_b})
-
-poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.})
+poisson_a = nest.Create(
+ "inhomogeneous_poisson_generator",
+ params={
+ "origin": signal_start - 0.1,
+ "start": 0.0,
+ "stop": signal_duration,
+ "rate_times": update_times,
+ "rate_values": rates_a,
+ },
+)
+
+poisson_b = nest.Create(
+ "inhomogeneous_poisson_generator",
+ params={
+ "origin": signal_start - 0.1,
+ "start": 0.0,
+ "stop": signal_duration,
+ "rate_times": update_times,
+ "rate_values": rates_b,
+ },
+)
+
+poisson_0 = nest.Create("poisson_generator", params={"rate": 2400.0})
sr_nonselective = nest.Create("spike_recorder")
sr_selective1 = nest.Create("spike_recorder")
@@ -180,72 +190,97 @@
receptor_types = selective_pop1[0].get("receptor_types")
-syn_spec_pot_AMPA = {"synapse_model": "static_synapse",
- "weight":w_plus * g_AMPA_ex,
- "delay":delay,
- "receptor_type": receptor_types["AMPA"]}
-syn_spec_pot_NMDA = {"synapse_model": "static_synapse",
- "weight":w_plus * g_NMDA_ex,
- "delay":delay,
- "receptor_type": receptor_types["NMDA"]}
-
-syn_spec_dep_AMPA = {"synapse_model": "static_synapse",
- "weight":w_minus * g_AMPA_ex,
- "delay":delay,
- "receptor_type": receptor_types["AMPA"]}
-
-syn_spec_dep_NMDA = {"synapse_model": "static_synapse",
- "weight":w_minus * g_NMDA_ex,
- "delay":delay,
- "receptor_type": receptor_types["NMDA"]}
-
-ie_syn_spec = {"synapse_model": "static_synapse",
- "weight": -1.0 * g_GABA_ex,
- "delay":delay,
- "receptor_type": receptor_types["GABA"]}
-
-ii_syn_spec = {"synapse_model": "static_synapse",
- "weight": -1.0 * g_GABA_in,
- "delay":delay,
- "receptor_type": receptor_types["GABA"]}
-
-ei_syn_spec_AMPA = {"synapse_model": "static_synapse",
- "weight": 1.0 * g_AMPA_in,
- "delay":delay,
- "receptor_type": receptor_types["AMPA"]}
-
-ei_syn_spec_NMDA = {"synapse_model": "static_synapse",
- "weight": 1.0 * g_NMDA_in,
- "delay":delay,
- "receptor_type": receptor_types["NMDA"]}
-
-ee_syn_spec_AMPA = {"synapse_model": "static_synapse",
- "weight": 1.0 * g_AMPA_ex,
- "delay":delay,
- "receptor_type": receptor_types["AMPA"]}
-
-ee_syn_spec_NMDA = {"synapse_model": "static_synapse",
- "weight": 1.0 * g_NMDA_ex,
- "delay":delay,
- "receptor_type": receptor_types["NMDA"]}
-
-exte_syn_spec = {"synapse_model": "static_synapse",
- "weight":g_AMPA_ext_ex,
- "delay":0.1,
- "receptor_type": receptor_types["AMPA"]}
-
-exti_syn_spec = {"synapse_model": "static_synapse",
- "weight":g_AMPA_ext_in,
- "delay":0.1,
- "receptor_type": receptor_types["AMPA"]}
-
+syn_spec_pot_AMPA = {
+ "synapse_model": "static_synapse",
+ "weight": w_plus * g_AMPA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["AMPA"],
+}
+syn_spec_pot_NMDA = {
+ "synapse_model": "static_synapse",
+ "weight": w_plus * g_NMDA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["NMDA"],
+}
+
+syn_spec_dep_AMPA = {
+ "synapse_model": "static_synapse",
+ "weight": w_minus * g_AMPA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["AMPA"],
+}
+
+syn_spec_dep_NMDA = {
+ "synapse_model": "static_synapse",
+ "weight": w_minus * g_NMDA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["NMDA"],
+}
+
+ie_syn_spec = {
+ "synapse_model": "static_synapse",
+ "weight": -1.0 * g_GABA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["GABA"],
+}
+
+ii_syn_spec = {
+ "synapse_model": "static_synapse",
+ "weight": -1.0 * g_GABA_in,
+ "delay": delay,
+ "receptor_type": receptor_types["GABA"],
+}
+
+ei_syn_spec_AMPA = {
+ "synapse_model": "static_synapse",
+ "weight": 1.0 * g_AMPA_in,
+ "delay": delay,
+ "receptor_type": receptor_types["AMPA"],
+}
+
+ei_syn_spec_NMDA = {
+ "synapse_model": "static_synapse",
+ "weight": 1.0 * g_NMDA_in,
+ "delay": delay,
+ "receptor_type": receptor_types["NMDA"],
+}
+
+ee_syn_spec_AMPA = {
+ "synapse_model": "static_synapse",
+ "weight": 1.0 * g_AMPA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["AMPA"],
+}
+
+ee_syn_spec_NMDA = {
+ "synapse_model": "static_synapse",
+ "weight": 1.0 * g_NMDA_ex,
+ "delay": delay,
+ "receptor_type": receptor_types["NMDA"],
+}
+
+exte_syn_spec = {
+ "synapse_model": "static_synapse",
+ "weight": g_AMPA_ext_ex,
+ "delay": 0.1,
+ "receptor_type": receptor_types["AMPA"],
+}
+
+exti_syn_spec = {
+ "synapse_model": "static_synapse",
+ "weight": g_AMPA_ext_in,
+ "delay": 0.1,
+ "receptor_type": receptor_types["AMPA"],
+}
##################################################
# Create connections
# from external
-nest.Connect(poisson_0, nonselective_pop + selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=exte_syn_spec)
+nest.Connect(
+ poisson_0, nonselective_pop + selective_pop1 + selective_pop2, conn_spec="all_to_all", syn_spec=exte_syn_spec
+)
nest.Connect(poisson_0, inhibitory_pop, conn_spec="all_to_all", syn_spec=exti_syn_spec)
nest.Connect(poisson_a, selective_pop1, conn_spec="all_to_all", syn_spec=exte_syn_spec)
@@ -286,7 +321,9 @@
nest.Connect(selective_pop2, sr_selective2)
# from inhibitory pop
-nest.Connect(inhibitory_pop, selective_pop1 + selective_pop2 + nonselective_pop, conn_spec="all_to_all", syn_spec=ie_syn_spec)
+nest.Connect(
+ inhibitory_pop, selective_pop1 + selective_pop2 + nonselective_pop, conn_spec="all_to_all", syn_spec=ie_syn_spec
+)
nest.Connect(inhibitory_pop, inhibitory_pop, conn_spec="all_to_all", syn_spec=ii_syn_spec)
nest.Connect(inhibitory_pop, sr_inhibitory)
@@ -301,7 +338,7 @@
##################################################
# Run simulation
-nest.Simulate(5000.)
+nest.Simulate(5000.0)
##################################################
@@ -332,12 +369,11 @@
s_NMDA_inhibitory = mm_inhibitory.get("events", "s_NMDA")
-
##################################################
# Plots
# bins for histograms
-res = 1.0
+res = 1.0
bins = np.arange(0, 4001, res) - 0.001
fig, ax = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True)
@@ -347,68 +383,67 @@
num = NE * f * (res / 1000)
hist1, _ = np.histogram(spikes_selective1, bins=bins)
hist2, _ = np.histogram(spikes_selective2, bins=bins)
-ax[0,0].plot(hist1 / num)
-ax[0,0].set_title("Selective pop A")
-ax[0,1].plot(hist2 / num)
-ax[0,1].set_title("Selective pop B")
+ax[0, 0].plot(hist1 / num)
+ax[0, 0].set_title("Selective pop A")
+ax[0, 1].plot(hist2 / num)
+ax[0, 1].set_title("Selective pop B")
# nonselective population
-num = NE * (1 - 2*f) * res / 1000
+num = NE * (1 - 2 * f) * res / 1000
hist, _ = np.histogram(spikes_nonselective, bins=bins)
-ax[1,0].plot(hist / num)
-ax[1,0].set_title("Nonselective pop")
+ax[1, 0].plot(hist / num)
+ax[1, 0].set_title("Nonselective pop")
# inhibitory population
num = NI * res / 1000
hist, _ = np.histogram(spikes_inhibitory, bins=bins)
-ax[1,1].plot(hist / num)
-ax[1,1].set_title("Inhibitory pop")
+ax[1, 1].plot(hist / num)
+ax[1, 1].set_title("Inhibitory pop")
fig, ax = plt.subplots(ncols=4, nrows=4, sharex=True, sharey="row")
fig.tight_layout()
# AMPA conductances
-ax[0,0].plot(s_AMPA_selective1)
-ax[0,1].plot(s_AMPA_selective2)
-ax[0,2].plot(s_AMPA_nonselective)
-ax[0,3].plot(s_AMPA_inhibitory)
+ax[0, 0].plot(s_AMPA_selective1)
+ax[0, 1].plot(s_AMPA_selective2)
+ax[0, 2].plot(s_AMPA_nonselective)
+ax[0, 3].plot(s_AMPA_inhibitory)
# NMDA conductances
-ax[1,0].plot(s_NMDA_selective1)
-ax[1,1].plot(s_NMDA_selective2)
-ax[1,2].plot(s_NMDA_nonselective)
-ax[1,3].plot(s_NMDA_inhibitory)
+ax[1, 0].plot(s_NMDA_selective1)
+ax[1, 1].plot(s_NMDA_selective2)
+ax[1, 2].plot(s_NMDA_nonselective)
+ax[1, 3].plot(s_NMDA_inhibitory)
# GABA conductances
-ax[2,0].plot(s_GABA_selective1)
-ax[2,1].plot(s_GABA_selective2)
-ax[2,2].plot(s_GABA_nonselective)
-ax[2,3].plot(s_GABA_inhibitory)
+ax[2, 0].plot(s_GABA_selective1)
+ax[2, 1].plot(s_GABA_selective2)
+ax[2, 2].plot(s_GABA_nonselective)
+ax[2, 3].plot(s_GABA_inhibitory)
# Membrane potential
-ax[3,0].plot(vm_selective1)
-ax[3,1].plot(vm_selective2)
-ax[3,2].plot(vm_nonselective)
-ax[3,3].plot(vm_inhibitory)
+ax[3, 0].plot(vm_selective1)
+ax[3, 1].plot(vm_selective2)
+ax[3, 2].plot(vm_nonselective)
+ax[3, 3].plot(vm_inhibitory)
-ax[0,0].set_ylabel("S_AMPA")
-ax[1,0].set_ylabel("S_NMDA")
-ax[2,0].set_ylabel("S_GABA")
-ax[3,0].set_ylabel("V_m")
+ax[0, 0].set_ylabel("S_AMPA")
+ax[1, 0].set_ylabel("S_NMDA")
+ax[2, 0].set_ylabel("S_GABA")
+ax[3, 0].set_ylabel("V_m")
-ax[0,0].set_title("Selective pop1")
-ax[0,1].set_title("Selective pop2")
-ax[0,2].set_title("Nonselective pop")
-ax[0,3].set_title("Inhibitory pop")
+ax[0, 0].set_title("Selective pop1")
+ax[0, 1].set_title("Selective pop2")
+ax[0, 2].set_title("Nonselective pop")
+ax[0, 3].set_title("Inhibitory pop")
-ax[0,0].set_title("Selective pop1")
-ax[0,1].set_title("Selective pop2")
-ax[0,2].set_title("Nonselective pop")
-ax[0,3].set_title("Inhibitory pop")
+ax[0, 0].set_title("Selective pop1")
+ax[0, 1].set_title("Selective pop2")
+ax[0, 2].set_title("Nonselective pop")
+ax[0, 3].set_title("Inhibitory pop")
plt.show()
-
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index e2959ff88d..90f0c1b807 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -34,8 +34,8 @@
from scipy.special import expn, gamma
-w_ex = 40.
-w_in = -15.
+w_ex = 40.0
+w_in = -15.0
alpha = 0.5
tau_AMPA = 2.0
tau_GABA = 5.0
@@ -48,7 +48,7 @@ def s_soln(w, t, tau):
Solution for synaptic variables
"""
isyn = np.zeros_like(t)
- useinds = t >= 0.
+ useinds = t >= 0.0
isyn[useinds] = w * np.exp(-t[useinds] / tau)
return isyn
@@ -60,8 +60,8 @@ def spiketrain_response(t, tau, spiketrain, w):
response = np.zeros_like(t)
for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
+ t_ = t - 1.0 - sp
+ zero_arg = t_ == 0.0
response += s_soln(w, t_, tau)
return response
@@ -72,53 +72,55 @@ def spiketrain_response_nmda(t, spiketrain):
"""
tr = tau_rise_NMDA / tau_decay_NMDA
at = alpha * tau_rise_NMDA
- k_0 = -expn(tr, at) * at + at**tr * gamma(1 - tr)
+ k_0 = -expn(tr, at) * at + at ** tr * gamma(1 - tr)
k_1 = np.exp(-alpha * tau_rise_NMDA) - 1
-
+
response = np.zeros_like(t)
for sp in spiketrain:
- t_ = t - 1. - sp
- zero_arg = t_ == 0.
+ t_ = t - 1.0 - sp
+ zero_arg = t_ == 0.0
s0 = response[zero_arg]
w = k_0 + k_1 * s0
response += s_soln(w, t_, tau_decay_NMDA)
response *= w_ex
return response
+
def test_wang():
# Create 2 neurons, so that the Wang dynamics are present
- nrn1 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_decay_NMDA,
- "tau_rise_NMDA": tau_rise_NMDA})
-
- nrn2 = nest.Create("iaf_wang_2002", {"tau_AMPA": tau_AMPA,
- "tau_GABA": tau_GABA,
- "tau_decay_NMDA": tau_decay_NMDA,
- "tau_rise_NMDA": tau_rise_NMDA,
- "t_ref": 0.})
+ nrn1 = nest.Create(
+ "iaf_wang_2002",
+ {"tau_AMPA": tau_AMPA, "tau_GABA": tau_GABA, "tau_decay_NMDA": tau_decay_NMDA, "tau_rise_NMDA": tau_rise_NMDA},
+ )
+
+ nrn2 = nest.Create(
+ "iaf_wang_2002",
+ {
+ "tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_decay_NMDA,
+ "tau_rise_NMDA": tau_rise_NMDA,
+ "t_ref": 0.0,
+ },
+ )
receptor_types = nrn1.get("receptor_types")
-
- pg = nest.Create("poisson_generator", {"rate": 50.})
+ pg = nest.Create("poisson_generator", {"rate": 50.0})
sr = nest.Create("spike_recorder", {"time_in_steps": True})
- mm1 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
- "interval": 0.1,
- "time_in_steps": True})
- mm2 = nest.Create("multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"],
- "interval": 0.1,
- "time_in_steps": True})
+ mm1 = nest.Create(
+ "multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1, "time_in_steps": True}
+ )
+ mm2 = nest.Create(
+ "multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1, "time_in_steps": True}
+ )
- ampa_syn_spec = {"weight": w_ex,
- "receptor_type": receptor_types["AMPA"]}
+ ampa_syn_spec = {"weight": w_ex, "receptor_type": receptor_types["AMPA"]}
- gaba_syn_spec = {"weight": w_in,
- "receptor_type": receptor_types["GABA"]}
+ gaba_syn_spec = {"weight": w_in, "receptor_type": receptor_types["GABA"]}
- nmda_syn_spec = {"weight": w_ex,
- "receptor_type": receptor_types["NMDA"]}
+ nmda_syn_spec = {"weight": w_ex, "receptor_type": receptor_types["NMDA"]}
nest.Connect(pg, nrn1, syn_spec=ampa_syn_spec)
nest.Connect(nrn1, sr)
@@ -128,7 +130,7 @@ def test_wang():
nest.Connect(mm1, nrn1)
nest.Connect(mm2, nrn2)
- nest.Simulate(1000.)
+ nest.Simulate(1000.0)
spikes = sr.get("events", "times") * nest.resolution
@@ -139,10 +141,11 @@ def test_wang():
gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
import matplotlib.pyplot as plt
+
plt.plot(mm2.events["s_NMDA"])
plt.plot(nmda_soln)
plt.show()
- nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
- nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
- nptest.assert_array_almost_equal(nmda_soln, mm2.events["s_NMDA"])
+ nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
+ nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
+ nptest.assert_array_almost_equal(nmda_soln, mm2.events["s_NMDA"])
diff --git a/testsuite/pytests/test_iaf_wang_2002_exact.py b/testsuite/pytests/test_iaf_wang_2002_exact.py
index 5632218cd0..2bb4dd47bd 100644
--- a/testsuite/pytests/test_iaf_wang_2002_exact.py
+++ b/testsuite/pytests/test_iaf_wang_2002_exact.py
@@ -35,41 +35,43 @@ def test_multiple_NMDA_ports(self):
Check that setting multiple NMDA receptors works
"""
# Create the new model, noise and detectors
- neuron = nest.Create('iaf_wang_2002')
- poiss = nest.Create('poisson_generator')
- poiss.rate = 6400.
+ neuron = nest.Create("iaf_wang_2002")
+ poiss = nest.Create("poisson_generator")
+ poiss.rate = 6400.0
- voltmeter = nest.Create('voltmeter')
- voltmeter.set(record_from=['V_m', 'g_AMPA', 'g_GABA', 'NMDA_sum'])
+ voltmeter = nest.Create("voltmeter")
+ voltmeter.set(record_from=["V_m", "g_AMPA", "g_GABA", "NMDA_sum"])
# Connect to NMDA receptor several times to check that we create new ports every time.
- receptors = neuron.get('receptor_types')
+ receptors = neuron.get("receptor_types")
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['AMPA']})
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['GABA']})
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
- nest.Connect(poiss, neuron, syn_spec={'receptor_type': receptors['NMDA']})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["AMPA"]})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["GABA"]})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
+ nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
nest.Connect(voltmeter, neuron)
# Check if NMDA sum is 0 before simulating
- self.assertEqual(neuron.NMDA_sum, 0.)
+ self.assertEqual(neuron.NMDA_sum, 0.0)
# Simulate
- nest.Simulate(1000.)
+ nest.Simulate(1000.0)
# Check sum NMDA after simulating
- self.assertTrue(neuron.NMDA_sum > 0.)
+ self.assertTrue(neuron.NMDA_sum > 0.0)
# Check g_AMPA after simulating
- self.assertTrue(voltmeter.get('events', 'g_AMPA').any() > 0.)
+ self.assertTrue(voltmeter.get("events", "g_AMPA").any() > 0.0)
+
def suite():
- suite = unittest.makeSuite(IafWang2002TestCase, 'test')
+ suite = unittest.makeSuite(IafWang2002TestCase, "test")
return suite
+
def run():
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite())
From 24d21ce84e95cb28a33a1835496ad8562afbfd97 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 12:27:17 +0100
Subject: [PATCH 059/184] clang-format
---
models/iaf_wang_2002.cpp | 22 +++++++-------
models/iaf_wang_2002.h | 54 ++++++++++++++++++++--------------
models/iaf_wang_2002_exact.cpp | 4 +--
models/iaf_wang_2002_exact.h | 54 ++++++++++++++++++++--------------
4 files changed, 77 insertions(+), 57 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 7dd2eb9030..42f32ddd70 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -25,8 +25,8 @@
#ifdef HAVE_GSL
// Includes from libnestutil:
-#include "dictdatum.h"
#include "dict_util.h"
+#include "dictdatum.h"
#include "numerics.h"
// Includes from nestkernel:
@@ -93,7 +93,7 @@ nest::iaf_wang_2002_dynamics( double, const double y[], double f[], void* pnode
const double I_rec_GABA = ( y[ S::V_m ] - node.P_.E_in ) * y[ S::s_GABA ];
const double I_rec_NMDA = ( y[ S::V_m ] - node.P_.E_ex )
- / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * y[ S::s_NMDA ];
+ / ( 1 + node.P_.conc_Mg2 * std::exp( -0.062 * y[ S::V_m ] ) / 3.57 ) * y[ S::s_NMDA ];
const double I_syn = I_AMPA + I_rec_GABA + I_rec_NMDA + node.B_.I_stim_;
@@ -123,7 +123,7 @@ nest::iaf_wang_2002::Parameters_::Parameters_()
, tau_AMPA( 2.0 ) // ms
, tau_GABA( 5.0 ) // ms
, tau_decay_NMDA( 100 ) // ms
- , tau_rise_NMDA( 2 ) // ms
+ , tau_rise_NMDA( 2 ) // ms
, alpha( 0.5 ) // 1 / ms
, conc_Mg2( 1 ) // mM
, gsl_error_tol( 1e-3 )
@@ -251,7 +251,7 @@ nest::iaf_wang_2002::State_::get( DictionaryDatum& d ) const
def< double >( d, names::V_m, y_[ V_m ] ); // Membrane potential
def< double >( d, names::s_AMPA, y_[ s_AMPA ] );
def< double >( d, names::s_GABA, y_[ s_GABA ] );
- def< double >( d, names::s_NMDA, y_[ s_NMDA] );
+ def< double >( d, names::s_NMDA, y_[ s_NMDA ] );
}
void
@@ -388,9 +388,9 @@ nest::iaf_wang_2002::pre_run_hook()
const double at = P_.alpha * P_.tau_rise_NMDA;
const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
- V_.S_jump_1 = exp(-P_.alpha * P_.tau_rise_NMDA) - 1;
- V_.S_jump_0 = -boost::math::expint(tau_rise_tau_dec, at) * at
- + pow(at, tau_rise_tau_dec) * boost::math::tgamma(1 - tau_rise_tau_dec);
+ V_.S_jump_1 = exp( -P_.alpha * P_.tau_rise_NMDA ) - 1;
+ V_.S_jump_0 = -boost::math::expint( tau_rise_tau_dec, at ) * at
+ + pow( at, tau_rise_tau_dec ) * boost::math::tgamma( 1 - tau_rise_tau_dec );
}
void
@@ -413,7 +413,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
for ( long lag = from; lag < to; ++lag )
{
double t = 0.0;
-
+
// numerical integration with adaptive step size control:
// ------------------------------------------------------
// gsl_odeiv_evolve_apply performs only a single numerical
@@ -435,7 +435,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
&t, // from t
B_.step_, // to t <= step
&B_.integration_step_, // integration step size
- S_.y_ ); // neuronal state
+ S_.y_ ); // neuronal state
if ( status != GSL_SUCCESS )
{
@@ -462,7 +462,7 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::V_m ] = P_.V_reset;
// get previous spike time
- double t_lastspike = get_spiketime_ms();
+ double t_lastspike = get_spiketime_ms();
// log spike with ArchivingNode
set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
@@ -510,7 +510,7 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
{
B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
- else
+ else
{
B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
}
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 7bca23552a..062e846a30 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -84,23 +84,33 @@ The membrane potential and synaptic variables evolve according to
C_\mathrm{m} \frac{dV(t)}{dt} &= -g_\mathrm{L} (V(t) - V_\mathrm{L}) - I_\mathrm{syn} (t) \\[3ex]
I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
- I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
- I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex]
- \frac{dS_{j,\mathrm{AMPA}}}{dt} &= -\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} (k_0 + k_1 S(t)) \delta (t - t_j^k) \\[3ex]
+ I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in
+\Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex] I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in
+\Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex] \frac{dS_{j,\mathrm{AMPA}}}{dt} &=
+-\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t
+- t_j^k) \\[3ex] \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in
+\Delta_j} (k_0 + k_1 S(t)) \delta (t - t_j^k) \\[3ex]
-where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
+where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory
+neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
.. math::
k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) - 1 \\[3ex]
- k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r} \Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 - \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
+ k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r}
+\Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 -
+\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
-where :math:`\mathrm{E_N}` is the generalized exponential integral (https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function (https://en.wikipedia.org/wiki/Gamma_function). For these values of :math:`k_0` and :math:`k_1`, the approximate model will approach the exact model for large t.
+where :math:`\mathrm{E_N}` is the generalized exponential integral
+(https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function
+(https://en.wikipedia.org/wiki/Gamma_function). For these values of :math:`k_0` and :math:`k_1`, the approximate model
+will approach the exact model for large t.
-The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
+The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`,
+:math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights.
+Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
Parameters
@@ -141,8 +151,8 @@ The following values can be recorded.
=========== ===========================================================
.. note::
- :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST model, so setting the
- variables is thus done by changing the weights.
+ :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST
+model, so setting the variables is thus done by changing the weights.
Sends
+++++
@@ -183,14 +193,14 @@ class iaf_wang_2002 : public ArchivingNode
* see http://www.gotw.ca/gotw/005.htm.
*/
- using Node::handles_test_event;
using Node::handle;
+ using Node::handles_test_event;
//! Used to validate that we can send SpikeEvent to desired target:port.
size_t send_test_event( Node&, size_t, synindex, bool ) override;
void handle( SpikeEvent& ) override; //!< accept spikes
- void handle( CurrentEvent& ) override; //!< accept current
+ void handle( CurrentEvent& ) override; //!< accept current
void handle( DataLoggingRequest& ) override; //!< allow recording with multimeter
size_t handles_test_event( SpikeEvent&, size_t ) override;
@@ -205,10 +215,10 @@ class iaf_wang_2002 : public ArchivingNode
void set_status( const DictionaryDatum& ) override;
bool
- is_off_grid() const override
- {
- return true;
- }
+ is_off_grid() const override
+ {
+ return true;
+ }
private:
void init_state_() override;
@@ -219,7 +229,7 @@ class iaf_wang_2002 : public ArchivingNode
/**
* Synapse types to connect to
- **/
+ **/
enum SynapseTypes
{
INF_SPIKE_RECEPTOR = 0,
@@ -289,9 +299,9 @@ class iaf_wang_2002 : public ArchivingNode
};
double y_[ STATE_VEC_SIZE ]; //!< state vector, must be C-array for GSL solver
- double s_NMDA_pre; // for determining (unweighted) alpha * (1 - s_NMDA) term on
- // pre-synaptic side
- int r_; //!< number of refractory steps remaining
+ double s_NMDA_pre; // for determining (unweighted) alpha * (1 - s_NMDA) term on
+ // pre-synaptic side
+ int r_; //!< number of refractory steps remaining
State_( const Parameters_& ); //!< Default initialization
State_( const State_& );
@@ -352,7 +362,7 @@ class iaf_wang_2002 : public ArchivingNode
double I_stim_;
};
-// Variables class -------------------------------------------------------
+ // Variables class -------------------------------------------------------
/**
* Internal variables of the model.
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index c237ff20ca..f1408b85d3 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -25,8 +25,8 @@
#ifdef HAVE_GSL
// Includes from libnestutil:
-#include "dictdatum.h"
#include "dict_util.h"
+#include "dictdatum.h"
#include "numerics.h"
// Includes from nestkernel:
@@ -555,7 +555,7 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
{
B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
- else
+ else
{
B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
}
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 59e21e122e..78f314a980 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -52,7 +52,7 @@ namespace nest
* @note No point in declaring it inline, since it is called
* through a function pointer.
* @param void* Pointer to model neuron instance.
-**/
+ **/
extern "C" inline int iaf_wang_2002_exact_dynamics( double, const double y[], double f[], void* pnode );
@@ -84,19 +84,28 @@ The membrane potential and synaptic variables evolve according to
C_\mathrm{m} \frac{dV(t)}{dt} &= -g_\mathrm{L} (V(t) - V_\mathrm{L}) - I_\mathrm{syn} (t) \\[3ex]
I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
- I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
- I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex]
- \frac{dS_{j,\mathrm{AMPA}}}{dt} &= -\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}}+ \alpha x_j (1 - S_{j,\mathrm{NMDA}})\\[3ex]
- \frac{dx_j}{dt} &= - \frac{x_j}{\tau_\mathrm{NMDA,rise}} + \sum_{k \in \Delta_j} \delta (t - t_j^k)
+ I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in
+\Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex] I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in
+\Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex] \frac{dS_{j,\mathrm{AMPA}}}{dt} &=
+-\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t
+- t_j^k) \\[3ex] \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}}+ \alpha x_j (1
+- S_{j,\mathrm{NMDA}})\\[3ex] \frac{dx_j}{dt} &= - \frac{x_j}{\tau_\mathrm{NMDA,rise}} + \sum_{k \in \Delta_j} \delta
+(t - t_j^k)
-where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
+where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory
+neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
-Since :math:`S_{j,\mathrm{AMPA}}` and :math:`S_{j,\mathrm{GABA}}` are piecewise exponential functions, the sums are also a piecewise exponential function, and can be stored in a single synaptic variable each, :math:`S_{\mathrm{AMPA}}` and :math:`S_{\mathrm{GABA}}` respectively. The sum over :math:`S_{j,\mathrm{NMDA}}` does not have a simple expression, and cannot be simplified. Therefore, for each synapse, we need to integrate separate state variable, which makes the model slow.
+Since :math:`S_{j,\mathrm{AMPA}}` and :math:`S_{j,\mathrm{GABA}}` are piecewise exponential functions, the sums are also
+a piecewise exponential function, and can be stored in a single synaptic variable each, :math:`S_{\mathrm{AMPA}}` and
+:math:`S_{\mathrm{GABA}}` respectively. The sum over :math:`S_{j,\mathrm{NMDA}}` does not have a simple expression, and
+cannot be simplified. Therefore, for each synapse, we need to integrate separate state variable, which makes the model
+slow.
-The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`, :math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights. Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
+The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`,
+:math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights.
+Additionally, the synapses from the external population is not separated from the recurrent AMPA-synapses.
Parameters
@@ -137,12 +146,13 @@ The following values can be recorded.
=========== ===========================================================
.. note::
- It is possible to set values for :math:`V_\mathrm{m}`, :math:`S_\mathrm{AMPA}` and :math:`S_\mathrm{GABA}` when creating the model, while the
- different :math:`s_{j,\mathrm{NMDA}}` (`j` represents presynaptic neuron `j`) can not be set by the user.
+ It is possible to set values for :math:`V_\mathrm{m}`, :math:`S_\mathrm{AMPA}` and :math:`S_\mathrm{GABA}` when
+creating the model, while the different :math:`s_{j,\mathrm{NMDA}}` (`j` represents presynaptic neuron `j`) can not be
+set by the user.
.. note::
- :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST model, so setting the
- variables is thus done by changing the weights.
+ :math:`g_{\mathrm{\{\{rec,AMPA\}, \{ext,AMPA\}, GABA, NMBA}\}}` from [1]_ is built into the weights in this NEST
+model, so setting the variables is thus done by changing the weights.
Sends
+++++
@@ -183,12 +193,12 @@ class iaf_wang_2002_exact : public ArchivingNode
* see http://www.gotw.ca/gotw/005.htm.
*/
- using Node::handles_test_event;
using Node::handle;
+ using Node::handles_test_event;
/**
* Used to validate that we can send SpikeEvent to desired target:port.
- **/
+ **/
size_t send_test_event( Node& target, size_t receptor_type, synindex, bool ) override;
void handle( SpikeEvent& ) override; //!< accept spikes
@@ -215,7 +225,7 @@ class iaf_wang_2002_exact : public ArchivingNode
/**
* Synapse types to connect to
- **/
+ **/
enum SynapseTypes
{
INF_SPIKE_RECEPTOR = 0,
@@ -254,7 +264,7 @@ class iaf_wang_2002_exact : public ArchivingNode
/**
* Initialize parameters to their default values.
- **/
+ **/
Parameters_();
void get( DictionaryDatum& ) const; //!< Store current values in dictionary
@@ -325,7 +335,7 @@ class iaf_wang_2002_exact : public ArchivingNode
/**
* Logger for all analog data
- **/
+ **/
UniversalDataLogger< iaf_wang_2002_exact > logger_;
// -----------------------------------------------------------------------
@@ -431,13 +441,13 @@ iaf_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
{
if ( receptor_type == NMDA )
{
- // give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
+ // give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
++S_.num_ports_;
return S_.num_ports_;
}
- else
+ else
{
- return receptor_type;
+ return receptor_type;
}
}
}
From 7c9c02ac3d1a03c3b11ef3f04f5b4ae7dfbf6e20 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 12:56:37 +0100
Subject: [PATCH 060/184] formatting
---
pynest/examples/wang_decision_making.py | 6 +++---
testsuite/pytests/test_iaf_wang_2002.py | 3 +--
2 files changed, 4 insertions(+), 5 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 3acb81aef0..6a731f8882 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -35,15 +35,15 @@
References
~~~~~~~~~~
-.. [1] Wang X-J (2002). Probabilistic Decision Making by Slow Reverberation in
-Cortical Circuits. Neuron, Volume 36, Issue 5, Pages 955-968.
+.. [1] Wang X-J (2002). Probabilistic Decision Making by Slow Reverberation in
+Cortical Circuits. Neuron, Volume 36, Issue 5, Pages 955-968.
https://doi.org/10.1016/S0896-6273(02)01092-9.
"""
-import nest
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
+import nest
import numpy as np
np.random.seed(1234)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index 90f0c1b807..d7ddb46a41 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -33,7 +33,6 @@
import pytest
from scipy.special import expn, gamma
-
w_ex = 40.0
w_in = -15.0
alpha = 0.5
@@ -72,7 +71,7 @@ def spiketrain_response_nmda(t, spiketrain):
"""
tr = tau_rise_NMDA / tau_decay_NMDA
at = alpha * tau_rise_NMDA
- k_0 = -expn(tr, at) * at + at ** tr * gamma(1 - tr)
+ k_0 = -expn(tr, at) * at + at**tr * gamma(1 - tr)
k_1 = np.exp(-alpha * tau_rise_NMDA) - 1
response = np.zeros_like(t)
From 6bee519bb09ef3d86188067e3ba92a69bff26a38 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 12:56:51 +0100
Subject: [PATCH 061/184] test for exact version
---
testsuite/pytests/test_iaf_wang_2002_exact.py | 160 +++++++++++-------
1 file changed, 101 insertions(+), 59 deletions(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002_exact.py b/testsuite/pytests/test_iaf_wang_2002_exact.py
index 2bb4dd47bd..877f175b52 100644
--- a/testsuite/pytests/test_iaf_wang_2002_exact.py
+++ b/testsuite/pytests/test_iaf_wang_2002_exact.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# test_iaf_wang_2002.py
+# test_iaf_wang_2002_exact.py
#
# This file is part of NEST.
#
@@ -19,63 +19,105 @@
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see .
-import unittest
-import nest
-import numpy as np
-
-
-class IafWang2002TestCase(unittest.TestCase):
- """Tests for iaf_wang_2002"""
-
- def setup(self):
- nest.ResetKernel()
-
- def test_multiple_NMDA_ports(self):
- """
- Check that setting multiple NMDA receptors works
- """
- # Create the new model, noise and detectors
- neuron = nest.Create("iaf_wang_2002")
- poiss = nest.Create("poisson_generator")
- poiss.rate = 6400.0
-
- voltmeter = nest.Create("voltmeter")
- voltmeter.set(record_from=["V_m", "g_AMPA", "g_GABA", "NMDA_sum"])
-
- # Connect to NMDA receptor several times to check that we create new ports every time.
- receptors = neuron.get("receptor_types")
-
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["AMPA"]})
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["GABA"]})
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
- nest.Connect(poiss, neuron, syn_spec={"receptor_type": receptors["NMDA"]})
-
- nest.Connect(voltmeter, neuron)
+"""
+Tests synaptic dynamics of iaf_wang_2002_exact. Since the neuron is conductance based,
+it is impossible to analytically confirm the membrane potential. We can confirm the
+AMPA and GABA values exactly, and upper and lower bounds on the NMDA values.
+"""
- # Check if NMDA sum is 0 before simulating
- self.assertEqual(neuron.NMDA_sum, 0.0)
- # Simulate
- nest.Simulate(1000.0)
-
- # Check sum NMDA after simulating
- self.assertTrue(neuron.NMDA_sum > 0.0)
-
- # Check g_AMPA after simulating
- self.assertTrue(voltmeter.get("events", "g_AMPA").any() > 0.0)
-
-
-def suite():
- suite = unittest.makeSuite(IafWang2002TestCase, "test")
- return suite
-
-
-def run():
- runner = unittest.TextTestRunner(verbosity=2)
- runner.run(suite())
-
-
-if __name__ == "__main__":
- run()
+import nest
+import numpy as np
+import numpy.testing as nptest
+import pytest
+from scipy.special import expn, gamma
+
+w_ex = 40.0
+w_in = -15.0
+alpha = 0.5
+tau_AMPA = 2.0
+tau_GABA = 5.0
+tau_rise_NMDA = 1.8
+tau_decay_NMDA = 100.0
+
+
+def s_soln(w, t, tau):
+ """
+ Solution for synaptic variables
+ """
+ isyn = np.zeros_like(t)
+ useinds = t >= 0.0
+ isyn[useinds] = w * np.exp(-t[useinds] / tau)
+ return isyn
+
+
+def spiketrain_response(t, tau, spiketrain, w):
+ """
+ Response for AMPA/NMDA
+ """
+
+ response = np.zeros_like(t)
+ for sp in spiketrain:
+ t_ = t - 1.0 - sp
+ zero_arg = t_ == 0.0
+ response += s_soln(w, t_, tau)
+ return response
+
+
+def test_wang():
+ # Create 2 neurons, so that the Wang dynamics are present
+ nrn1 = nest.Create(
+ "iaf_wang_2002_exact",
+ {"tau_AMPA": tau_AMPA, "tau_GABA": tau_GABA, "tau_decay_NMDA": tau_decay_NMDA, "tau_rise_NMDA": tau_rise_NMDA},
+ )
+
+ nrn2 = nest.Create(
+ "iaf_wang_2002_exact",
+ {
+ "tau_AMPA": tau_AMPA,
+ "tau_GABA": tau_GABA,
+ "tau_decay_NMDA": tau_decay_NMDA,
+ "tau_rise_NMDA": tau_rise_NMDA,
+ "t_ref": 0.0,
+ },
+ )
+
+ receptor_types = nrn1.get("receptor_types")
+
+ pg = nest.Create("poisson_generator", {"rate": 50.0})
+ sr = nest.Create("spike_recorder", {"time_in_steps": True})
+
+ mm1 = nest.Create(
+ "multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1, "time_in_steps": True}
+ )
+ mm2 = nest.Create(
+ "multimeter", {"record_from": ["V_m", "s_AMPA", "s_NMDA", "s_GABA"], "interval": 0.1, "time_in_steps": True}
+ )
+
+ ampa_syn_spec = {"weight": w_ex, "receptor_type": receptor_types["AMPA"]}
+
+ gaba_syn_spec = {"weight": w_in, "receptor_type": receptor_types["GABA"]}
+ nmda_syn_spec = {"weight": w_ex, "receptor_type": receptor_types["NMDA"]}
+
+ nest.Connect(pg, nrn1, syn_spec=ampa_syn_spec)
+ nest.Connect(nrn1, sr)
+ nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec)
+ nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec)
+ nest.Connect(mm1, nrn1)
+ nest.Connect(mm2, nrn2)
+
+ nest.Simulate(1000.0)
+
+ spikes = sr.get("events", "times") * nest.resolution
+
+ # compute analytical solutions
+ times = mm1.get("events", "times") * nest.resolution
+ ampa_soln = spiketrain_response(times, tau_AMPA, spikes, w_ex)
+ gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
+
+ nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
+ nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
+
+ assert mm2.events["s_NMDA"].max() < w_ex
+ assert mm2.events["s_NMDA"].min() >= 0.0
From cf8b82ab2b5d40de57c7340b4ce1db434cf02612 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:04:38 +0100
Subject: [PATCH 062/184] rename NMDA_sum
---
models/iaf_wang_2002_exact.cpp | 4 ++--
models/iaf_wang_2002_exact.h | 4 ++--
nestkernel/nest_names.cpp | 1 -
nestkernel/nest_names.h | 1 -
pynest/examples/wang_decision_making.py | 2 +-
5 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index f1408b85d3..01cb3c2830 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -233,8 +233,8 @@ nest::iaf_wang_2002_exact::State_::get( DictionaryDatum& d ) const
def< double >( d, names::s_GABA, ode_state_[ s_GABA ] );
// total NMDA sum
- double s_NMDA = get_NMDA_sum();
- def< double >( d, names::NMDA_sum, s_NMDA );
+ double s_NMDA = get_s_NMDA();
+ def< double >( d, names::s_NMDA, s_NMDA );
}
void
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 78f314a980..751bdfd64e 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -308,7 +308,7 @@ class iaf_wang_2002_exact : public ArchivingNode
//! Get the sum of NMDA over all presynaptic neurons
double
- get_NMDA_sum() const
+ get_s_NMDA() const
{
double NMDA_sum = 0.0;
for ( size_t i = s_NMDA_base; i < state_vec_size; i += 2 )
@@ -405,7 +405,7 @@ class iaf_wang_2002_exact : public ArchivingNode
double
get_s_NMDA_() const
{
- return S_.get_NMDA_sum();
+ return S_.get_s_NMDA();
}
// Data members -----------------------------------------------------------
diff --git a/nestkernel/nest_names.cpp b/nestkernel/nest_names.cpp
index 1e82ba03e5..01f89fcd3f 100644
--- a/nestkernel/nest_names.cpp
+++ b/nestkernel/nest_names.cpp
@@ -313,7 +313,6 @@ const Name music_channel( "music_channel" );
const Name N( "N" );
const Name NMDA( "NMDA" );
-const Name NMDA_sum( "NMDA_sum" );
const Name N_channels( "N_channels" );
const Name N_NaP( "N_NaP" );
const Name N_T( "N_T" );
diff --git a/nestkernel/nest_names.h b/nestkernel/nest_names.h
index dc29844c46..5303c0e8cd 100644
--- a/nestkernel/nest_names.h
+++ b/nestkernel/nest_names.h
@@ -339,7 +339,6 @@ extern const Name music_channel;
extern const Name N;
extern const Name NMDA;
-extern const Name NMDA_sum;
extern const Name N_channels;
extern const Name N_NaP;
extern const Name N_T;
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 6a731f8882..fa1500444c 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -42,9 +42,9 @@
"""
import matplotlib.pyplot as plt
-from matplotlib.gridspec import GridSpec
import nest
import numpy as np
+from matplotlib.gridspec import GridSpec
np.random.seed(1234)
rng = np.random.default_rng()
From d8774f8bf133934ea21d6adfb2e7f3566a31e9e6 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:05:02 +0100
Subject: [PATCH 063/184] correct copyright header
---
pynest/examples/wang_decision_making.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index fa1500444c..6c7c7eb0c7 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
-# brunel_alpha_nest.py
+# wang_decision_making.py
#
# This file is part of NEST.
#
From bd8e1011f36d7c5e8aa5bb1d4dc0f11a47b25232 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:13:39 +0100
Subject: [PATCH 064/184] remove plotting
---
testsuite/pytests/test_iaf_wang_2002.py | 6 ------
1 file changed, 6 deletions(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index d7ddb46a41..f9dfca9f10 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -139,12 +139,6 @@ def test_wang():
nmda_soln = spiketrain_response_nmda(times, spikes)
gaba_soln = spiketrain_response(times, tau_GABA, spikes, np.abs(w_in))
- import matplotlib.pyplot as plt
-
- plt.plot(mm2.events["s_NMDA"])
- plt.plot(nmda_soln)
- plt.show()
-
nptest.assert_array_almost_equal(ampa_soln, mm2.events["s_AMPA"])
nptest.assert_array_almost_equal(gaba_soln, mm2.events["s_GABA"])
nptest.assert_array_almost_equal(nmda_soln, mm2.events["s_NMDA"])
From be9f99f5c4aae37a7c4615e58160482a1bf7ccb2 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:17:06 +0100
Subject: [PATCH 065/184] update docstring
---
testsuite/pytests/test_iaf_wang_2002.py | 10 ++++++----
testsuite/pytests/test_iaf_wang_2002_exact.py | 8 +++++---
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index f9dfca9f10..c93a415a6a 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -20,10 +20,12 @@
# along with NEST. If not, see .
"""
-Tests synaptic dynamics of iaf_wang_2002. Since the neuron is conductance based,
-it is impossible to analytically confirm the membrane potential, but all the
-synaptic currents can be computed analytically (for the simplified implementation
-we use). The integration of the membrane potential is not tested here.
+Tests synaptic dynamics of the approximate model iaf_wang_2002.
+
+Since the neuron is conductance based, it is impossible to analytically
+confirm the membrane potential, but all the synaptic currents can be
+computed analytically (for the simplified implementation we use).
+The integration of the membrane potential is not tested here.
"""
diff --git a/testsuite/pytests/test_iaf_wang_2002_exact.py b/testsuite/pytests/test_iaf_wang_2002_exact.py
index 877f175b52..39b0c86aa5 100644
--- a/testsuite/pytests/test_iaf_wang_2002_exact.py
+++ b/testsuite/pytests/test_iaf_wang_2002_exact.py
@@ -20,9 +20,11 @@
# along with NEST. If not, see .
"""
-Tests synaptic dynamics of iaf_wang_2002_exact. Since the neuron is conductance based,
-it is impossible to analytically confirm the membrane potential. We can confirm the
-AMPA and GABA values exactly, and upper and lower bounds on the NMDA values.
+Tests synaptic dynamics of the exact model iaf_wang_2002_exact.
+
+Since the neuron is conductance based, it is impossible to analytically
+confirm the membrane potential. We can confirm the AMPA and GABA values
+exactly, and upper and lower bounds on the NMDA values.
"""
From 3743147ceb23d26aabbc2561758a259000f30fb1 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:21:36 +0100
Subject: [PATCH 066/184] flake8
---
testsuite/pytests/test_iaf_wang_2002.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index c93a415a6a..70d222d77d 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -23,7 +23,7 @@
Tests synaptic dynamics of the approximate model iaf_wang_2002.
Since the neuron is conductance based, it is impossible to analytically
-confirm the membrane potential, but all the synaptic currents can be
+confirm the membrane potential, but all the synaptic currents can be
computed analytically (for the simplified implementation we use).
The integration of the membrane potential is not tested here.
"""
From a303a2995394034595aa9fb410eb15965ce87d24 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Wed, 7 Feb 2024 13:28:26 +0100
Subject: [PATCH 067/184] more comments
---
pynest/examples/wang_decision_making.py | 77 +++++++++++++------------
1 file changed, 39 insertions(+), 38 deletions(-)
diff --git a/pynest/examples/wang_decision_making.py b/pynest/examples/wang_decision_making.py
index 6c7c7eb0c7..19bedf1956 100644
--- a/pynest/examples/wang_decision_making.py
+++ b/pynest/examples/wang_decision_making.py
@@ -58,50 +58,51 @@
##################################################
# Set parameter values, taken from [1]_.
-# conductances
-g_AMPA_ex = 0.05
-g_AMPA_ext_ex = 2.1
-g_NMDA_ex = 0.165
-g_GABA_ex = 1.3
-
-g_AMPA_in = 0.04
-g_AMPA_ext_in = 1.62
-g_NMDA_in = 0.13
-g_GABA_in = 1.0
+# conductances excitatory population
+g_AMPA_ex = 0.05 # recurrent AMPA conductance
+g_AMPA_ext_ex = 2.1 # external AMPA conductance
+g_NMDA_ex = 0.165 # recurrent GABA conductance
+g_GABA_ex = 1.3 # recurrent GABA conductance
+
+# conductances inhibitory population
+g_AMPA_in = 0.04 # recurrent AMPA conductance
+g_AMPA_ext_in = 1.62 # external AMPA conductance
+g_NMDA_in = 0.13 # recurrent GABA conductance
+g_GABA_in = 1.0 # recurrent GABA conductance
# neuron parameters
epop_params = {
- "tau_GABA": 5.0,
- "tau_AMPA": 2.0,
- "tau_decay_NMDA": 100.0,
- "tau_rise_NMDA": 2.0,
- "alpha": 0.5,
- "conc_Mg2": 1.0,
- "g_L": 25.0, # leak conductance
- "E_L": -70.0, # leak reversal potential
- "E_ex": 0.0, # excitatory reversal potential
- "E_in": -70.0, # inhibitory reversal potential
- "V_reset": -55.0, # reset potential
- "V_th": -50.0, # threshold
- "C_m": 500.0, # membrane capacitance
- "t_ref": 2.0, # refreactory period
+ "tau_GABA": 5.0, # GABA decay time constant
+ "tau_AMPA": 2.0, # AMPA decay time constant
+ "tau_decay_NMDA": 100.0, # NMDA decay time constant
+ "tau_rise_NMDA": 2.0, # NMDA rise time constant
+ "alpha": 0.5, # NMDA parameter
+ "conc_Mg2": 1.0, # Magnesium concentration
+ "g_L": 25.0, # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
+ "C_m": 500.0, # membrane capacitance
+ "t_ref": 2.0, # refreactory period
}
ipop_params = {
- "tau_GABA": 5.0,
- "tau_AMPA": 2.0,
- "tau_decay_NMDA": 100.0,
- "tau_rise_NMDA": 2.0,
- "alpha": 0.5,
- "conc_Mg2": 1.0,
- "g_L": 20.0, # leak conductance
- "E_L": -70.0, # leak reversal potential
- "E_ex": 0.0, # excitatory reversal potential
- "E_in": -70.0, # inhibitory reversal potential
- "V_reset": -55.0, # reset potential
- "V_th": -50.0, # threshold
- "C_m": 200.0, # membrane capacitance
- "t_ref": 1.0, # refreactory period
+ "tau_GABA": 5.0, # GABA decay time constant
+ "tau_AMPA": 2.0, # AMPA decay time constant
+ "tau_decay_NMDA": 100.0, # NMDA decay time constant
+ "tau_rise_NMDA": 2.0, # NMDA rise time constant
+ "alpha": 0.5, # NMDA parameter
+ "conc_Mg2": 1.0, # Magnesium concentration
+ "g_L": 20.0, # leak conductance
+ "E_L": -70.0, # leak reversal potential
+ "E_ex": 0.0, # excitatory reversal potential
+ "E_in": -70.0, # inhibitory reversal potential
+ "V_reset": -55.0, # reset potential
+ "V_th": -50.0, # threshold
+ "C_m": 200.0, # membrane capacitance
+ "t_ref": 1.0, # refreactory period
}
# signals to the two different excitatory sub-populations
From 041a7b4ba5fd58fcfc614edd51b604db6eb0c138 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 13 Feb 2024 11:24:01 +0100
Subject: [PATCH 068/184] fixes from review
---
models/iaf_wang_2002.cpp | 35 +++++++++-------------------------
models/iaf_wang_2002.h | 31 +++++++++---------------------
models/iaf_wang_2002_exact.cpp | 19 ++++--------------
models/iaf_wang_2002_exact.h | 30 ++++++++++++++---------------
4 files changed, 37 insertions(+), 78 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index 42f32ddd70..a24196911e 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -274,8 +274,6 @@ nest::iaf_wang_2002::iaf_wang_2002()
, B_( *this )
{
recordablesMap_.create();
-
- calibrate();
}
/* ---------------------------------------------------------------------------
@@ -385,22 +383,14 @@ nest::iaf_wang_2002::pre_run_hook()
assert( V_.RefractoryCounts_ >= 0 );
// helper vars
- const double at = P_.alpha * P_.tau_rise_NMDA;
+ const double alpha_tau = P_.alpha * P_.tau_rise_NMDA;
const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
V_.S_jump_1 = exp( -P_.alpha * P_.tau_rise_NMDA ) - 1;
- V_.S_jump_0 = -boost::math::expint( tau_rise_tau_dec, at ) * at
- + pow( at, tau_rise_tau_dec ) * boost::math::tgamma( 1 - tau_rise_tau_dec );
+ V_.S_jump_0 = -boost::math::expint( tau_rise_tau_dec, alpha_tau ) * alpha_tau
+ + pow( alpha_tau, tau_rise_tau_dec ) * boost::math::tgamma( 1 - tau_rise_tau_dec );
}
-void
-nest::iaf_wang_2002::calibrate()
-{
- B_.logger_.init();
-
- // internals V_
- V_.RefractoryCounts_ = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
-}
/* ---------------------------------------------------------------------------
* Update and spike handling functions
@@ -445,9 +435,9 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
// add incoming spikes
- S_.y_[ State_::s_AMPA ] += B_.spikes_[ AMPA - 1 ].get_value( lag );
- S_.y_[ State_::s_GABA ] += B_.spikes_[ GABA - 1 ].get_value( lag );
- S_.y_[ State_::s_NMDA ] += B_.spikes_[ NMDA - 1 ].get_value( lag );
+ S_.y_[ State_::s_AMPA ] += B_.spikes_[ SynapseTypes::AMPA - 1 ].get_value( lag );
+ S_.y_[ State_::s_GABA ] += B_.spikes_[ SynapseTypes::GABA - 1 ].get_value( lag );
+ S_.y_[ State_::s_NMDA ] += B_.spikes_[ SynapseTypes::NMDA - 1 ].get_value( lag );
if ( S_.r_ )
{
@@ -462,12 +452,12 @@ nest::iaf_wang_2002::update( Time const& origin, const long from, const long to
S_.y_[ State_::V_m ] = P_.V_reset;
// get previous spike time
- double t_lastspike = get_spiketime_ms();
+ const double t_lastspike = get_spiketime_ms();
// log spike with ArchivingNode
set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
- double t_spike = get_spiketime_ms();
+ const double t_spike = get_spiketime_ms();
// compute current value of s_NMDA and add NMDA update to spike offset
S_.s_NMDA_pre = S_.s_NMDA_pre * exp( -( t_spike - t_lastspike ) / P_.tau_decay_NMDA );
@@ -506,14 +496,7 @@ nest::iaf_wang_2002::handle( SpikeEvent& e )
if ( rport < NMDA )
{
- if ( e.get_weight() > 0 )
- {
- B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
- }
- else
- {
- B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
- }
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
}
else
{
diff --git a/models/iaf_wang_2002.h b/models/iaf_wang_2002.h
index 062e846a30..903b58a096 100644
--- a/models/iaf_wang_2002.h
+++ b/models/iaf_wang_2002.h
@@ -84,14 +84,10 @@ The membrane potential and synaptic variables evolve according to
C_\mathrm{m} \frac{dV(t)}{dt} &= -g_\mathrm{L} (V(t) - V_\mathrm{L}) - I_\mathrm{syn} (t) \\[3ex]
I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
- I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in
-\Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex] I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in
-\Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex] \frac{dS_{j,\mathrm{AMPA}}}{dt} &=
--\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t
-- t_j^k) \\[3ex] \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in
-\Delta_j} (k_0 + k_1 S(t)) \delta (t - t_j^k) \\[3ex]
-
+ I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
+ I_\mathrm{GABA} &= (V(t) - V_I)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex] \frac{dS_{j,\mathrm{AMPA}}}{dt} &= -\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}} + \sum_{k \in \Delta_j} (k_0 + k_1 S(t)) \delta (t - t_j^k) \\[3ex]
where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory
neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
@@ -99,14 +95,9 @@ neurons respectively, and :math:`\Delta_j` is an index set for the spike times o
.. math::
k_0 &= \mathrm{exp}(-\alpha \tau_\mathrm{r}) - 1 \\[3ex]
- k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r}
-\Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 -
-\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
+ k_1 &= -\alpha \tau_\mathrm{r} \mathrm{E_N} \Big[ \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}, \alpha \tau_\mathrm{r} \Big] + (\alpha \tau_\mathrm{r})^{\frac{\tau_\mathrm{r}}{\tau_\mathrm{d}}} \Gamma \Big[ 1 - \frac{\tau_\mathrm{r}}{\tau_\mathrm{d}} \Big]
-where :math:`\mathrm{E_N}` is the generalized exponential integral
-(https://en.wikipedia.org/wiki/Exponential_integral#Generalization), and :math:`\Gamma` is the gamma-function
-(https://en.wikipedia.org/wiki/Gamma_function). For these values of :math:`k_0` and :math:`k_1`, the approximate model
-will approach the exact model for large t.
+where :math:`\mathrm{E_N}` is the `generalized exponential integral `_, and :math:`\Gamma` is the `gamma function `_. For these values of :math:`k_0` and :math:`k_1`, the approximate model will approach the exact model for large t.
The specification of this model differs slightly from the one in [1]_. The parameters :math:`g_\mathrm{AMPA}`,
:math:`g_\mathrm{GABA}`, and :math:`g_\mathrm{NMDA}` have been absorbed into the respective synaptic weights.
@@ -174,7 +165,7 @@ References
See also
++++++++
-iaf_cond_alpha, ht_neuron
+iaf_wang_2002_exact
EndUserDocs */
@@ -224,7 +215,6 @@ class iaf_wang_2002 : public ArchivingNode
void init_state_() override;
void pre_run_hook() override;
void init_buffers_() override;
- void calibrate();
void update( Time const&, const long, const long ) override;
/**
@@ -413,12 +403,9 @@ iaf_wang_2002::handles_test_event( SpikeEvent&, size_t receptor_type )
if ( not( INF_SPIKE_RECEPTOR < receptor_type and receptor_type < SUP_SPIKE_RECEPTOR ) )
{
throw UnknownReceptorType( receptor_type, get_name() );
- return 0;
- }
- else
- {
- return receptor_type;
}
+
+ return receptor_type;
}
inline size_t
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index 01cb3c2830..ca984622c1 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -95,7 +95,7 @@ nest::iaf_wang_2002_exact::Parameters_::Parameters_()
nest::iaf_wang_2002_exact::State_::State_( const Parameters_& p )
: state_vec_size( 0 )
, ode_state_( nullptr )
- , num_ports_( 2 )
+ , num_ports_( SynapseTypes::NMDA )
, r_( 0 )
{
ode_state_ = new double[ s_NMDA_base ];
@@ -114,7 +114,7 @@ nest::iaf_wang_2002_exact::State_::State_( const State_& s )
, num_ports_( s.num_ports_ )
, r_( s.r_ )
{
- assert( s.num_ports_ == 2 );
+ assert( s.num_ports_ == SynapseTypes::NMDA );
assert( state_vec_size == s_NMDA_base );
ode_state_ = new double[ s_NMDA_base ];
@@ -256,8 +256,6 @@ nest::iaf_wang_2002_exact::iaf_wang_2002_exact()
, B_( *this )
{
recordablesMap_.create();
-
- calibrate();
}
/* ---------------------------------------------------------------------------
@@ -311,7 +309,7 @@ nest::iaf_wang_2002_exact::init_state_()
assert( S_.state_vec_size == State_::s_NMDA_base );
double* old_state = S_.ode_state_;
- S_.state_vec_size = State_::s_NMDA_base + 2 * ( S_.num_ports_ - 2 );
+ S_.state_vec_size = State_::s_NMDA_base + 2 * ( S_.num_ports_ - SynapseTypes::NMDA );
S_.ode_state_ = new double[ S_.state_vec_size ];
assert( S_.ode_state_ );
@@ -340,7 +338,7 @@ nest::iaf_wang_2002_exact::init_buffers_()
B_.currents_.clear(); // includes resize
- B_.weights_.resize( S_.num_ports_ - NMDA + 1, 0.0 );
+ B_.weights_.resize( S_.num_ports_ - SynapseTypes::NMDA + 1, 0.0 );
B_.logger_.reset(); // includes resize
ArchivingNode::clear_history();
@@ -394,15 +392,6 @@ nest::iaf_wang_2002_exact::pre_run_hook()
assert( V_.RefractoryCounts >= 0 );
}
-void
-nest::iaf_wang_2002_exact::calibrate()
-{
- B_.logger_.init();
-
- // internals V_
- V_.RefractoryCounts = Time( Time::ms( ( double ) ( P_.t_ref ) ) ).get_steps();
-}
-
/* ---------------------------------------------------------------------------
* Update and spike handling functions
* --------------------------------------------------------------------------- */
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index 751bdfd64e..b13909fc56 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -85,14 +85,12 @@ The membrane potential and synaptic variables evolve according to
I_\mathrm{syn}(t) &= I_\mathrm{AMPA}(t) + I_\mathrm{NMDA}(t) + I_\mathrm{GABA}(t) (t) \\[3ex]
I_\mathrm{AMPA} &= (V(t) - V_E)\sum_{j \in \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{AMPA}}(t) \\[3ex]
I_\mathrm{NMDA} &= \frac{(V(t) - V_E)}{1+[\mathrm{Mg^{2+}}]\mathrm{exp}(-0.062V(t))/3.57}\sum_{j \in
-\Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex] I_\mathrm{GABA} &= (V(t) - V_E)\sum_{j \in
-\Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex] \frac{dS_{j,\mathrm{AMPA}}}{dt} &=
--\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
- \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t
-- t_j^k) \\[3ex] \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}}+ \alpha x_j (1
-- S_{j,\mathrm{NMDA}})\\[3ex] \frac{dx_j}{dt} &= - \frac{x_j}{\tau_\mathrm{NMDA,rise}} + \sum_{k \in \Delta_j} \delta
-(t - t_j^k)
-
+ \Gamma_\mathrm{ex}}^{N_E}w_jS_{j,\mathrm{NMDA}}(t) \\[3ex]
+ I_\mathrm{GABA} &= (V(t) - V_I)\sum_{j \in \Gamma_\mathrm{in}}^{N_E}w_jS_{j,\mathrm{GABA}}(t) \\[5ex]
+ \frac{dS_{j,\mathrm{AMPA}}}{dt} &=-\frac{j,S_{\mathrm{AMPA}}}{\tau_\mathrm{AMPA}}+\sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{GABA}}}{dt} &= -\frac{S_{j,\mathrm{GABA}}}{\tau_\mathrm{GABA}} + \sum_{k \in \Delta_j} \delta (t - t_j^k) \\[3ex]
+ \frac{dS_{j,\mathrm{NMDA}}}{dt} &= -\frac{S_{j,\mathrm{NMDA}}}{\tau_\mathrm{NMDA,decay}}+ \alpha x_j (1 - S_{j,\mathrm{NMDA}})\\[3ex]
+ \frac{dx_j}{dt} &= - \frac{x_j}{\tau_\mathrm{NMDA,rise}} + \sum_{k \in \Delta_j} \delta (t - t_j^k)
where :math:`\Gamma_\mathrm{ex}` and :math:`\Gamma_\mathrm{in}` are index sets for presynaptic excitatory and inhibitory
neurons respectively, and :math:`\Delta_j` is an index set for the spike times of neuron :math:`j`.
@@ -174,7 +172,7 @@ References
See also
++++++++
-iaf_cond_alpha, ht_neuron
+iaf_wang_2002
EndUserDocs */
@@ -220,7 +218,6 @@ class iaf_wang_2002_exact : public ArchivingNode
void init_state_() override;
void pre_run_hook() override;
void init_buffers_() override;
- void calibrate();
void update( Time const&, const long, const long ) override;
/**
@@ -285,13 +282,16 @@ class iaf_wang_2002_exact : public ArchivingNode
*/
struct State_
{
- //! Symbolic indices to the elements of the state vector y
+ /**
+ * Symbolic indices to the elements of the state vector y
+ * (x_NMDA_1, G_NMDA_1), (x_NMDA_2, G_NMDA_2), (x_NMDA_3, G_NMDA_3), ..., (x_NMDA_j, G_NMDA_j)
+ */
enum StateVecElems
{
V_m = 0,
s_AMPA,
s_GABA,
- s_NMDA_base, // (x_NMDA_1, G_NMDA_1), (x_NMDA_2, G_NMDA_2), (x_NMDA_3, G_NMDA_3), ..., (x_NMDA_j, G_NMDA_j)
+ s_NMDA_base,
};
size_t state_vec_size;
@@ -311,9 +311,9 @@ class iaf_wang_2002_exact : public ArchivingNode
get_s_NMDA() const
{
double NMDA_sum = 0.0;
- for ( size_t i = s_NMDA_base; i < state_vec_size; i += 2 )
+ for ( size_t i = s_NMDA_base + 1; i < state_vec_size; i += 2 )
{
- NMDA_sum += ode_state_[ i + 1 ];
+ NMDA_sum += ode_state_[ i ];
}
return NMDA_sum;
}
@@ -439,7 +439,7 @@ iaf_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
}
else
{
- if ( receptor_type == NMDA )
+ if ( receptor_type == SynapseTypes::NMDA )
{
// give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
++S_.num_ports_;
From 4810fe8f580bc3b4f7b61ae08d7adb35f9233de9 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 13 Feb 2024 13:34:26 +0100
Subject: [PATCH 069/184] update inhibitory weight signs
---
models/iaf_wang_2002_exact.cpp | 2 +-
models/iaf_wang_2002_exact.h | 2 +-
testsuite/pytests/test_iaf_wang_2002.py | 7 +++++--
testsuite/pytests/test_iaf_wang_2002_exact.py | 2 +-
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index ca984622c1..d9856172fb 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -488,7 +488,7 @@ nest::iaf_wang_2002_exact::update( Time const& origin, const long from, const lo
const size_t si = i - ( NMDA - 1 );
assert( si >= 0 );
- assert( State_::s_NMDA_base + si * 2 < S_.state_vec_size );
+ assert( State_::s_NMDA_base + si * 2 <= S_.state_vec_size );
S_.ode_state_[ State_::s_NMDA_base + si * 2 ] += B_.spikes_.at( i ).get_value( lag );
}
diff --git a/models/iaf_wang_2002_exact.h b/models/iaf_wang_2002_exact.h
index b13909fc56..42c993e57c 100644
--- a/models/iaf_wang_2002_exact.h
+++ b/models/iaf_wang_2002_exact.h
@@ -441,7 +441,7 @@ iaf_wang_2002_exact::handles_test_event( SpikeEvent&, size_t receptor_type )
{
if ( receptor_type == SynapseTypes::NMDA )
{
- // give each NMDA synapse a unique rport, starting from 2 (num_ports_ is initialized to 2)
+ // give each NMDA synapse a unique rport, starting from 3 (num_ports_ is initialized to 3)
++S_.num_ports_;
return S_.num_ports_;
}
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index 70d222d77d..c46cc6e9bc 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -36,7 +36,7 @@
from scipy.special import expn, gamma
w_ex = 40.0
-w_in = -15.0
+w_in = 15.0
alpha = 0.5
tau_AMPA = 2.0
tau_GABA = 5.0
@@ -73,8 +73,11 @@ def spiketrain_response_nmda(t, spiketrain):
"""
tr = tau_rise_NMDA / tau_decay_NMDA
at = alpha * tau_rise_NMDA
- k_0 = -expn(tr, at) * at + at**tr * gamma(1 - tr)
+ k_0 = -expn(0, at) * at + at**tr * gamma(1 - tr)
+ print(tr)
k_1 = np.exp(-alpha * tau_rise_NMDA) - 1
+ print(expn(0, at))
+ print(at)
response = np.zeros_like(t)
for sp in spiketrain:
diff --git a/testsuite/pytests/test_iaf_wang_2002_exact.py b/testsuite/pytests/test_iaf_wang_2002_exact.py
index 39b0c86aa5..8582f61db2 100644
--- a/testsuite/pytests/test_iaf_wang_2002_exact.py
+++ b/testsuite/pytests/test_iaf_wang_2002_exact.py
@@ -35,7 +35,7 @@
from scipy.special import expn, gamma
w_ex = 40.0
-w_in = -15.0
+w_in = 15.0
alpha = 0.5
tau_AMPA = 2.0
tau_GABA = 5.0
From 55e5d603c5202521a23494a043fd06e32f6e7e76 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 13 Feb 2024 13:58:26 +0100
Subject: [PATCH 070/184] fix bug, wrong SynapseTypes:: logic
---
models/iaf_wang_2002_exact.cpp | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index d9856172fb..a6a2c106c0 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -95,7 +95,7 @@ nest::iaf_wang_2002_exact::Parameters_::Parameters_()
nest::iaf_wang_2002_exact::State_::State_( const Parameters_& p )
: state_vec_size( 0 )
, ode_state_( nullptr )
- , num_ports_( SynapseTypes::NMDA )
+ , num_ports_( SynapseTypes::GABA ) // only AMPA/GABA for now, add NMDA later
, r_( 0 )
{
ode_state_ = new double[ s_NMDA_base ];
@@ -114,7 +114,7 @@ nest::iaf_wang_2002_exact::State_::State_( const State_& s )
, num_ports_( s.num_ports_ )
, r_( s.r_ )
{
- assert( s.num_ports_ == SynapseTypes::NMDA );
+ assert( s.num_ports_ == SynapseTypes::GABA );
assert( state_vec_size == s_NMDA_base );
ode_state_ = new double[ s_NMDA_base ];
@@ -309,7 +309,7 @@ nest::iaf_wang_2002_exact::init_state_()
assert( S_.state_vec_size == State_::s_NMDA_base );
double* old_state = S_.ode_state_;
- S_.state_vec_size = State_::s_NMDA_base + 2 * ( S_.num_ports_ - SynapseTypes::NMDA );
+ S_.state_vec_size = State_::s_NMDA_base + 2 * ( S_.num_ports_ - SynapseTypes::GABA );
S_.ode_state_ = new double[ S_.state_vec_size ];
assert( S_.ode_state_ );
@@ -338,7 +338,7 @@ nest::iaf_wang_2002_exact::init_buffers_()
B_.currents_.clear(); // includes resize
- B_.weights_.resize( S_.num_ports_ - SynapseTypes::NMDA + 1, 0.0 );
+ B_.weights_.resize( S_.num_ports_ - SynapseTypes::GABA + 1, 0.0 );
B_.logger_.reset(); // includes resize
ArchivingNode::clear_history();
From 4536e5936625268d8faeb445a683271abee3df08 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Tue, 13 Feb 2024 14:11:58 +0100
Subject: [PATCH 071/184] specify AMPA/GABA by receptor_type instead of weight
sign
---
models/iaf_wang_2002_exact.cpp | 25 +++++++------------------
1 file changed, 7 insertions(+), 18 deletions(-)
diff --git a/models/iaf_wang_2002_exact.cpp b/models/iaf_wang_2002_exact.cpp
index a6a2c106c0..a3cb59e7de 100644
--- a/models/iaf_wang_2002_exact.cpp
+++ b/models/iaf_wang_2002_exact.cpp
@@ -538,25 +538,14 @@ nest::iaf_wang_2002_exact::handle( SpikeEvent& e )
const double steps = e.get_rel_delivery_steps( kernel().simulation_manager.get_slice_origin() );
const auto rport = e.get_rport();
- if ( rport < NMDA )
- {
- if ( e.get_weight() > 0 )
- {
- B_.spikes_[ AMPA - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
- }
- else
- {
- B_.spikes_[ GABA - 1 ].add_value( steps, -e.get_weight() * e.get_multiplicity() );
- }
- }
- else
- {
- // spikes_ has 2 + N elements, where N is number of NMDA synapses
- // rport starts at 1, so subtract one to get correct index
- B_.spikes_[ rport - 1 ].add_value( steps, e.get_multiplicity() );
- // we need to scale each individual S_j variable by its weight,
- // so we keep track of the weight.
+ B_.spikes_[ rport - 1 ].add_value( steps, e.get_weight() * e.get_multiplicity() );
+
+
+ if ( rport >= NMDA )
+ // we need to scale each individual S_j variable by its weight,
+ // so we store them
+ {
const size_t w_idx = rport - NMDA;
if ( B_.weights_[ w_idx ] == 0.0 )
{
From 301eb017866651798c386d42ff2661187aeba5a0 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 15 Feb 2024 18:07:56 +0100
Subject: [PATCH 072/184] change expn function to avoid truncation of n
---
models/iaf_wang_2002.cpp | 6 ++++--
testsuite/pytests/test_iaf_wang_2002.py | 9 ++++-----
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/models/iaf_wang_2002.cpp b/models/iaf_wang_2002.cpp
index a24196911e..26dde512ae 100644
--- a/models/iaf_wang_2002.cpp
+++ b/models/iaf_wang_2002.cpp
@@ -44,7 +44,6 @@
// Includes from standard library
#include
-#include
#include
/* ---------------------------------------------------------------------------
@@ -385,9 +384,12 @@ nest::iaf_wang_2002::pre_run_hook()
// helper vars
const double alpha_tau = P_.alpha * P_.tau_rise_NMDA;
const double tau_rise_tau_dec = P_.tau_rise_NMDA / P_.tau_decay_NMDA;
+ const double expint = boost::math::gamma_q(1 - tau_rise_tau_dec, alpha_tau)
+ * boost::math::tgamma(1 - tau_rise_tau_dec) * pow(alpha_tau, tau_rise_tau_dec - 1);
+
V_.S_jump_1 = exp( -P_.alpha * P_.tau_rise_NMDA ) - 1;
- V_.S_jump_0 = -boost::math::expint( tau_rise_tau_dec, alpha_tau ) * alpha_tau
+ V_.S_jump_0 = -expint * alpha_tau
+ pow( alpha_tau, tau_rise_tau_dec ) * boost::math::tgamma( 1 - tau_rise_tau_dec );
}
diff --git a/testsuite/pytests/test_iaf_wang_2002.py b/testsuite/pytests/test_iaf_wang_2002.py
index c46cc6e9bc..d646fffb7e 100644
--- a/testsuite/pytests/test_iaf_wang_2002.py
+++ b/testsuite/pytests/test_iaf_wang_2002.py
@@ -33,7 +33,7 @@
import numpy as np
import numpy.testing as nptest
import pytest
-from scipy.special import expn, gamma
+from scipy.special import gamma, gammaincc
w_ex = 40.0
w_in = 15.0
@@ -73,11 +73,10 @@ def spiketrain_response_nmda(t, spiketrain):
"""
tr = tau_rise_NMDA / tau_decay_NMDA
at = alpha * tau_rise_NMDA
- k_0 = -expn(0, at) * at + at**tr * gamma(1 - tr)
- print(tr)
+ expn = gammaincc(1 - tr, at) * gamma(1 - tr) * at ** (tr - 1)
+
+ k_0 = -expn * at + at**tr * gamma(1 - tr)
k_1 = np.exp(-alpha * tau_rise_NMDA) - 1
- print(expn(0, at))
- print(at)
response = np.zeros_like(t)
for sp in spiketrain:
From 05403dad0c4082f6d3c6966dfda41e474ddec3a8 Mon Sep 17 00:00:00 2001
From: janeirik <>
Date: Thu, 15 Feb 2024 19:10:05 +0100
Subject: [PATCH 073/184] begin model_details notebook, still missing a bit
---
.../wong_approximate_implementation.ipynb | 327 ++++++++++++++++++
1 file changed, 327 insertions(+)
create mode 100644 doc/model_details/wong_approximate_implementation.ipynb
diff --git a/doc/model_details/wong_approximate_implementation.ipynb b/doc/model_details/wong_approximate_implementation.ipynb
new file mode 100644
index 0000000000..3f16689051
--- /dev/null
+++ b/doc/model_details/wong_approximate_implementation.ipynb
@@ -0,0 +1,327 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "0dd90717-10b0-47da-b891-eec7bf86a8a5",
+ "metadata": {},
+ "source": [
+ "# Approximate NMDA dynamics\n",
+ "\n",
+ "In this notebook, we will describe the approximation we employ, and compare the dynamics to the exact implementation. The full sub-threshold dynamics are given by the following equations\n",
+ "\n",
+ "$$\\begin{align}\n",
+ " I_\\mathrm{syn}(t) &= \n",
+ " + I_\\mathrm{rec, AMPA}(t) \n",
+ " + I_\\mathrm{rec, NMDA}(t) \n",
+ " + I_\\mathrm{rec, GABA}(t) \\mathrm{,}\\\\[1.5ex]\n",
+ " I_\\mathrm{ext,AMPA} &= g_\\mathrm{ext,AMPA}(V(t) - V_E)S_{j,\\mathrm{ext, AMPA}}(t)\\mathrm{,}\\\\\n",
+ " I_\\mathrm{rec,AMPA} &= g_\\mathrm{rec,AMPA}(V(t) - V_E)\\sum_{j=1}^{N_E}w_jS_{j,\\mathrm{rec,AMPA}}(t) \\mathrm{,}\\\\\n",
+ " I_\\mathrm{rec,NMDA} &= \\frac{g_\\mathrm{rec,NMDA}(V(t) - V_E)}{1+[\\mathrm{Mg^{2+}}]\\mathrm{exp}(-0.062V(t))/3.57}\\sum_{j=1}^{N_E}w_jS_{j,\\mathrm{NMDA}}(t) \\mathrm{,}\\\\\n",
+ " I_\\mathrm{rec,GABA} &= g_\\mathrm{rec,GABA}(V(t) - V_E)\\sum_{j=1}^{N_E}w_jS_{j,\\mathrm{GABA}}(t) \\mathrm{.}\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "where the variables $S_{j,\\mathrm{ext,AMPA}},S_{j,\\mathrm{rec,AMPA}},S_{j,\\mathrm{NMDA}},\\ \\mathrm{ and }\\ S_{j,\\mathrm{GABA}}$\n",
+ "are governed by the equations\n",
+ "\n",
+ "$$\\begin{align}\n",
+ " \\frac{dS_{j,\\mathrm{AMPA}}}{dt} &= -\\frac{S_{j,\\mathrm{AMPA}}}{\\tau_\\mathrm{AMPA}}+\\sum_k \\delta (t - t_j^k) \\mathrm{,} \\\\\n",
+ " \\frac{dS_{j,\\mathrm{GABA}}}{dt} &= -\\frac{S_{j,\\mathrm{GABA}}}{\\tau_\\mathrm{GABA}} + \\sum_k \\delta (t - t_j^k) \\mathrm{,} \\\\\n",
+ " \\frac{dS_{j,\\mathrm{NMDA}}}{dt} &= -\\frac{S_{j,\\mathrm{NMDA}}}{\\tau_\\mathrm{NMDA,decay}}+ \\alpha x_j (1 - S_{j,\\mathrm{NMDA}})\\mathrm{,}\\\\\n",
+ " \\frac{dx_j}{dt} &= - \\frac{x_j}{\\tau_\\mathrm{NMDA,rise}} + \\sum_k \\delta (t - t_j^k) \\mathrm{.}\n",
+ "\\end{align}\n",
+ "$$\n",
+ "\n",
+ "We will from now on only focus on the last two equations, which are the subjects of the approximation in the model. We drop the subscript NMDA in the following. Between spikes, plugging in the solution for $x$ on the interval $[0, t]$, we get the following equation for $S_j$\n",
+ "$$\n",
+ "\\begin{align}\n",
+ " \\frac{dS_{j}}{dt} + \\bigg(\\frac{1}{\\tau_\\mathrm{d}} + \\alpha x_j^0 \\mathrm{exp}\\bigg[-\\frac{t}{\\tau_\\mathrm{r}}\\bigg] \\bigg) S_{j,\\mathrm{NMDA}} &= \\alpha x_j^0 \\mathrm{exp}\\bigg[-\\frac{t}{\\tau_\\mathrm{r}}\\bigg] \\mathrm{,}\n",
+ "\\end{align}\n",
+ "$$\n",
+ "for which the formal solution can easily be found by an integrating factor:\n",
+ "$$\n",
+ " S_{j}(t) = \\mathrm{exp}\\Bigg[-\\int_0^t \\frac{1}{\\tau_\\mathrm{d}} + \\alpha x_j^0 \\mathrm{exp}\\bigg[-\\frac{t'}{\\tau_\\mathrm{r}} \\bigg] dt' \\Bigg] \n",
+ " \\Bigg( \\int_0^t \\mathrm{exp}\\Bigg[\\int_0^{t'} \\frac{1}{\\tau_\\mathrm{d}} + \\alpha x_j^0 \\mathrm{exp}\\bigg[-\\frac{t''}{\\tau_\\mathrm{r}} \\bigg] dt'' \\Bigg]\\alpha x_j^0 \\mathrm{exp}\\bigg[-\\frac{t'}{\\tau_\\mathrm{r}}\\bigg] dt' + S_{j}^0 \\Bigg) \\mathrm{.}\n",
+ "$$\n",
+ "\n",
+ "The first and innermost integrals can be solved, which gives\n",
+ "$$\n",
+ " S_{j}(t) \n",
+ " = \n",
+ " \\mathrm{exp}\\Bigg[-\\frac{t}{\\tau_\\mathrm{d}} - \\alpha x_j^{k-1} \\tau_\\mathrm{r} \\bigg( 1-\\mathrm{exp}\\bigg[-\\frac{t}{\\tau_\\mathrm{r}} \\bigg] \\bigg) \\Bigg]\n",
+ " \\Bigg( \\int_0^{t} \\mathrm{exp}\\Bigg[(t') \\bigg( \\frac{1}{\\tau_\\mathrm{d}} - \\frac{1}{\\tau_\\mathrm{r}} \\bigg) + \\alpha x_j^0 \\tau_\\mathrm{r} \\bigg( 1 - \\mathrm{exp}\\bigg[-\\frac{t'}{\\tau_\\mathrm{r}} \\bigg] \\bigg) \\Bigg]\\alpha x_j^0 dt' + S_{j}^0 \\Bigg) \\mathrm{.}\n",
+ "$$\n",
+ "\n",
+ "Since we have two different time scales in the exponential inside the remaining integral, there is no exact solution for arbitrary limits of integration. We would like to approximate this function with an exponential function, such that we can integrate the sum of multiple such functions in a single variable. Our approximate function will then have the dynamics\n",
+ "\n",
+ "$$\n",
+ "\\frac{d}{dt}\\hat{S_j} = - \\frac{\\hat{S_j}}{\\tau_d} + \\sum_k c_k \\delta (t - t_j^k)\n",
+ "$$\n",
+ "\n",
+ "WILL BE COMPLETED SHORTLY\n",
+ "\n",
+ "However, in the limit $t \\to \\infty$, it has the following solution (found by Mathematica)\n",
+ "\n",
+ "$$\n",
+ "\\lim_{t \\to \\infty} \\int_0^t \n",
+ " \\mathrm{exp}\\Bigg[t' \\bigg( \\frac{1}{\\tau_\\mathrm{d}} - \\frac{1}{\\tau_\\mathrm{r}} \\bigg) + \\alpha x_0 \\tau_\\mathrm{r} \\bigg( 1 - \\mathrm{exp}\\bigg[-\\frac{t'}{\\tau_\\mathrm{r}} \\bigg] \\bigg) \\Bigg]\\alpha x_0 dt' \\\\\n",
+ " =\n",
+ " \\mathrm{exp}\\Big[\\alpha x_0 \\tau_\\mathrm{r}\\Big] \\bigg( \n",
+ " -\\alpha x_0 \\tau_\\mathrm{r} \\mathrm{ExpEn}\\Big[\\frac{\\tau_\\mathrm{r}}{\\tau_\\mathrm{d}}, \\alpha x_0 \\tau_\\mathrm{r} \\Big] \n",
+ " + (\\alpha x_0 \\tau_\\mathrm{r})^\\frac{\\tau_\\mathrm{r}}{\\tau_\\mathrm{d}}\\mathrm{Gamma}\\Big[1 - \\frac{\\tau_\\mathrm{r}}{\\tau_\\mathrm{d}}\\Big] \\bigg) \\mathrm{.}\n",
+ "$$\n",
+ "\n",
+ "\n",
+ "The first term in the solution for $S_j$ obviously converges, so we get"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "524ec469-ba8d-4b0d-a9e9-1941828f16d5",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ " -- N E S T --\n",
+ " Copyright (C) 2004 The NEST Initiative\n",
+ "\n",
+ " Version: 3.6.0-post0.dev0\n",
+ " Built: Feb 15 2024 17:55:17\n",
+ "\n",
+ " This program is provided AS IS and comes with\n",
+ " NO WARRANTY. See the file LICENSE for details.\n",
+ "\n",
+ " Problems or suggestions?\n",
+ " Visit https://www.nest-simulator.org\n",
+ "\n",
+ " Type 'nest.help()' to find out more about NEST.\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "import nest\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "import numpy as np"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "3ec686e2-2ae2-4479-9b57-7d6151f7a1f1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "w_ext = 40.\n",
+ "w_ex = 1.\n",
+ "w_in = 15.\n",
+ "\n",
+ "params = {\"tau_AMPA\": 2.0,\n",
+ " \"tau_GABA\": 5.0,\n",
+ " \"tau_rise_NMDA\": 2.0,\n",
+ " \"tau_decay_NMDA\": 100.0,\n",
+ " \"conc_Mg2\": 1.0,\n",
+ " \"E_ex\": 0.0,\n",
+ " \"E_in\": -70.0,\n",
+ " \"E_L\": -70.0,\n",
+ " \"V_th\": -55.0,\n",
+ " \"C_m\": 500.0,\n",
+ " \"g_L\": 25.0,\n",
+ " \"V_reset\": -70.0,\n",
+ " \"alpha\": 0.5,\n",
+ " \"t_ref\": 2.0}"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "77c6bf6b-c422-46e3-a88e-4925e9bd7842",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Feb 15 18:17:24 NodeManager::add_node [Info]: \n",
+ " Neuron models emitting precisely timed spikes exist: the kernel property \n",
+ " off_grid_spiking has been set to true.\n",
+ " \n",
+ " NOTE: Mixing precise-spiking and normal neuron models may lead to inconsistent results.\n",
+ "\n",
+ "Feb 15 18:17:24 NodeManager::add_node [Info]: \n",
+ " Neuron models emitting precisely timed spikes exist: the kernel property \n",
+ " off_grid_spiking has been set to true.\n",
+ " \n",
+ " NOTE: Mixing precise-spiking and normal neuron models may lead to inconsistent results.\n",
+ "\n",
+ "Feb 15 18:17:24 NodeManager::prepare_nodes [Info]: \n",
+ " Preparing 8 nodes for simulation.\n",
+ "\n",
+ "Feb 15 18:17:24 SimulationManager::start_updating_ [Info]: \n",
+ " Number of local nodes: 8\n",
+ " Simulation time (ms): 1000\n",
+ " Number of OpenMP threads: 1\n",
+ " Number of MPI processes: 1\n",
+ "\n",
+ "Feb 15 18:17:24 SimulationManager::run [Info]: \n",
+ " Simulation finished.\n"
+ ]
+ }
+ ],
+ "source": [
+ "nest.ResetKernel()\n",
+ "nest.rng_seed = 12345\n",
+ "\n",
+ "# pre-synaptic neuron, must be approximate model since the post-synaptic approximate model needs the offset\n",
+ "nrn1 = nest.Create(\"iaf_wang_2002\", params)\n",
+ "nrn2 = nest.Create(\"iaf_wang_2002\", params)\n",
+ "nrn3 = nest.Create(\"iaf_wang_2002_exact\", params)\n",
+ "\n",
+ "pg = nest.Create(\"poisson_generator\", {\"rate\": 50.})\n",
+ "sr = nest.Create(\"spike_recorder\", {\"time_in_steps\": True})\n",
+ "\n",
+ "mm1 = nest.Create(\"multimeter\", {\"record_from\": [\"V_m\", \"s_AMPA\", \"s_NMDA\", \"s_GABA\"], \"interval\": 0.1})\n",
+ "mm2 = nest.Create(\"multimeter\", {\"record_from\": [\"V_m\", \"s_AMPA\", \"s_NMDA\", \"s_GABA\"], \"interval\": 0.1})\n",
+ "mm3 = nest.Create(\"multimeter\", {\"record_from\": [\"V_m\", \"s_AMPA\", \"s_NMDA\", \"s_GABA\"], \"interval\": 0.1})\n",
+ "\n",
+ "ampa_ext_syn_spec = {\"synapse_model\": \"static_synapse\",\n",
+ " \"weight\": w_ext,\n",
+ " \"receptor_type\": 1}\n",
+ "\n",
+ "ampa_syn_spec = {\"synapse_model\": \"static_synapse\",\n",
+ " \"weight\": w_ex,\n",
+ " \"receptor_type\": 1}\n",
+ "\n",
+ "nmda_syn_spec = {\"synapse_model\": \"static_synapse\",\n",
+ " \"weight\": w_ex,\n",
+ " \"receptor_type\": 3}\n",
+ "\n",
+ "gaba_syn_spec = {\"synapse_model\": \"static_synapse\",\n",
+ " \"weight\": w_in,\n",
+ " \"receptor_type\": 2}\n",
+ "\n",
+ "nest.Connect(pg, nrn1, syn_spec=ampa_ext_syn_spec)\n",
+ "nest.Connect(nrn1, sr)\n",
+ "nest.Connect(nrn1, nrn2, syn_spec=ampa_syn_spec)\n",
+ "nest.Connect(nrn1, nrn2, syn_spec=gaba_syn_spec)\n",
+ "nest.Connect(nrn1, nrn2, syn_spec=nmda_syn_spec)\n",
+ "nest.Connect(nrn1, nrn3, syn_spec=ampa_syn_spec)\n",
+ "nest.Connect(nrn1, nrn3, syn_spec=gaba_syn_spec)\n",
+ "nest.Connect(nrn1, nrn3, syn_spec=nmda_syn_spec)\n",
+ "nest.Connect(mm1, nrn1)\n",
+ "\n",
+ "nest.Connect(mm2, nrn2)\n",
+ "nest.Connect(mm3, nrn3)\n",
+ "\n",
+ "nest.Simulate(1000.)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "9df3957c-7903-4e7f-a767-e98d121dbbd0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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m8jq1atJRkYG6dixI7FarexvP/zwQ7Jw4UKyfv168scff5BVq1aR3r17k86dOxOz2cyuN3fuXAKAZGZmkmeffZb88ssv5O233ybh4eGkb9++7LoHDhwg2dnZpG/fvmTXrl1k165d5MCBA+wxAiArVqxgt3vo0CESERFBsrKyyLJly8iWLVvIF198Qe644w5SV1fn9row28rKyiKjR48mP/zwA/nhhx9Iz549SWxsLKmpqWHX3bp1K9FoNGTw4MFk7dq1ZPPmzWTKlCkubWGO0d09cOHCBXZZZmYmSUlJIdnZ2WT58uVk27ZtZO/eveTUqVMkMjKStG/fnnz++edk06ZN5O677yYAyBtvvMH+3pdrJCMTTOR+W0bGu77dbreTUaNGEZVKRV566SXy66+/ksWLF7P9ntFoJIR47r+NRiPZvHkzAUCmTp3K9o1nz54lhBAyatQo0qZNG/Lxxx+T7du3kx9++IHMmTOHrFmzhhBCSGlpKdFqteSFF17gtbWyspLo9Xry7LPPsssyMzNJ27ZtSbdu3cjnn39OfvnlF3L77bcTAOSPP/5g1zt16hT517/+RdasWUO2b99ONm7cSKZOnUoUCgXZtm0bux7T16anp5Nrr72WfPvtt+Trr78mV111FVGr1WTnzp2EEEIKCgrI448/TgCQ7777jj3G2tpaQgghQ4YMIUOGDGG3a7FYyPXXX09UKhV55plnyE8//UTWr19P/v3vf5PVq1dLXrshQ4aQ+Ph40rFjR7Js2TLy22+/kUcffZQAIKtWrWLXa2xsJH369CEJCQnk7bffJr///jt57733SHR0NBk2bBix2+28Y+SOBxgAkLlz57KfuWOh559/nvz222/khx9+8Ppe8eUaych4S0CCfuvWrWTSpEkkPDycREdHk0ceeSRY7ZKRaTUwnf7u3buJxWIh9fX1ZOPGjaRNmzYkMjKSlJSUEEJoQQ+ALF++nPf71atXEwDk22+/5S3ft28fAUA++OADQggh33zzDQFADh065LYtdXV1JDIyksyYMYO3vFu3buT6669nPzNicezYsbz11q1bRwCQXbt2iW7fbrcTi8VC8vLyCADy448/st8xndjMmTN5v/nyyy8JAPLFF1+wy7p3787ruBnEOs1hw4aRmJgYUlZW5va4xWC21bNnT5743bt3LwHAGxB06dKF9O3bl1gsFt42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QoKNoffTghoMUeLpVLZUdSyTpXmT4knLeCc1vJwSThd0d0JfDMmkeNYgW+iVCr8nfoSJCZln3xtau4U+WJn/hfhsofdh25eyhZ4hGP32vHnzWMumu39///0364L9wgsv4NZbb8WVV16JFStWgKIofP311wCAkSNHsi75Wq0WnTp1wrhx4wAASqVSdP/Mdh955BE88MAD6Nu3L9555x107tzZxVLLZfbs2aitrWX/FRQU+HTclwq33norbrnlFnTs2BF9+vTBZ599hqNHj+LEiRPsOtOnT8ett96Krl274sMPP0R0dDQ+++wz3nbmzp2LESNGoGfPnli1ahVKS0vx/fffs99bLBZ88MEHGDhwIDp37oyioiKsX78en376KQYPHozevXvjyy+/RGFhIWv1Hjt2LB566CHce++9mDZtGnQ6HV5//XXJ44mLi8OSJUvQuXNnPPjgg+jcuTMMBgP+/e9/o2PHjpg9ezY0Gg0vhOTFF1/EwIEDkZWVhQkTJuDpp5/GunXrANAeIGFh4QClgCYqDrroOOj1epf9fvnll2hqasLnn3+OHj16YNiwYXj//ffx3//+F6WlzrDI2NhYvP/+++jSpQvGjx+PcePG+ZUH4Mzvy/G18kVcu3/GJRneI0NjtdnxovJzPK9aDUNSP+whXXHRIP4uk5EJJj5b6P/880+sWLEC33zzDWw2G2677Ta88soruO6660LRvsuKOqNr5nrhoL7CIdzdCf3moDmtt56SlNVKZPtfvTcf+3KrsS+3GlOvbQedWsmL12Via/0VP7w4YC8FqRCPLvctpOhDWV1ADG8vAS8ZJEe485NESseaS03EcLPcB+MMaFVKvyd+hIO6Ch+e8VZfh57bviBuVyyZo7SF3vtt8/IrBLXVoSeY/fb06dNx1113Sa6TlZXFxsB369aNXa7VapGdnY38fGeViqeeegozZ85EcXExYmNjkZubi9mzZ6Ndu3ai205JSXHZLgB07dqVt10hWq3W7/hovVqJEwtG+fXbQNGrfRMD586dw0svvYTdu3ejoqKCnQDJz89nz9mAAQPY9VUqFfr16+dSCYG7TlxcHDp37sxbR6PRoFevXuznkydPQqVSoX///uyy+Ph4l98tXrwYPXr0wLp16/D333+7hFwI6d69Oy/ZYVJSEnr06MF+ViqViI+PR1mZszzYN998w4aWNDQ0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f/zjH3z22Wd89913dOnSJXg8MzMToNF7k5mZidfrpbCwsNFrdu3aVafe3bt3t7l7vHjxYvLz8xkyZAh2ux273c68efN4/vnnsdvtwfcj96xGVlYWAwYMqHWsf//+bN26FZDPWX1uv/127rrrLs455xwOOOAAJk6cyM0338yjjz4KyD1rSizvT2ZmZp16CgsL8fl8Yd9DCeiFEEIIIcLkdDoZMmQIc+bMqXV8zpw5jBw5soVaFRuGYXD99dczY8YM5s6dS48ePWqd79GjB5mZmbXujdfrZd68ecF7M2TIEBwOR61rcnNzWblyZfCaESNGUFxczK+//hq8ZuHChRQXF7e5ezxmzBhWrFjBsmXLgv+GDh3K+eefz7Jly+jZs6fcs30cdthhdbZDXLt2Ld26dQPkc1afiooKVLV2eGez2YLb1sk9a1ws78+IESNYuXIlubm5wWtmz56Ny+ViyJAh4TU8rBR6QgghhBDCMIyabetef/11Y9WqVcZNN91kJCYmGps3b27pplnqmmuuMVJTU43vv//eyM3NDf6rqKgIXvPYY48ZqampxowZM4wVK1YY5557br1bP3Xp0sX45ptvjCVLlhhHH310vVs/DRo0yFiwYIGxYMEC44ADDmgTW2M1R2iWe8OQe7avX3/91bDb7cbDDz9srFu3znjvvfeMhIQE49133w1eI/estosuusjo3LlzcNu6GTNmGB06dDDuuOOO4DV/93tWWlpqLF261Fi6dKkBGM8884yxdOnS4Hajsbo/1dvWjRkzxliyZInxzTffGF26dJFt64QQQgghYunFF180unXrZjidTuPggw8Obt32VwbU++/NN98MXqPruvGvf/3LyMzMNFwul3HkkUcaK1asqFVOZWWlcf311xvp6elGfHy8cdJJJxlbt26tdc2ePXuM888/30hOTjaSk5ON888/3ygsLIzBu7TevgG93LO6Pv/8c2PgwIGGy+Uy+vXrZ7zyyiu1zss9q62kpMS48cYbja5duxpxcXFGz549jXvuucfweDzBa/7u9+y7776r9+/XRRddZBhGbO/Pli1bjBNPPNGIj4830tPTjeuvv95wu91hvyfFMAwjvDF9IYQQQgghhBBCtDRZQy+EEEIIIYQQQrRBEtALIYQQQgghhBBtkAT0QgghhBBCCCFEGyQBvRBCCCGEEEII0QZJQC+EEEIIIYQQQrRBEtALIYQQQgghhBBtkAT0QgghhBBCCCFEGyQBvRBCCCGEEEII0QZJQC+EEEIIIcRfyPfff4+iKBQVFbVI/XPnzqVfv37oum5ZHYcccggzZsywrHwh2goJ6IUQQgghhGijRo0axU033VTr2MiRI8nNzSU1NbVF2nTHHXdwzz33oKrWhRr//Oc/ueuuuyztNBCiLZCAXgghhBBCiL8Qp9NJZmYmiqLEvO758+ezbt06zjzzTEvrOfHEEykuLmbWrFmW1iNEaycBvRBCCCGEEG3QxRdfzLx583juuedQFAVFUdi8eXOdKfdTp06lXbt2fPHFF/Tt25eEhATOOOMMysvLeeutt+jevTtpaWn84x//QNO0YPler5c77riDzp07k5iYyLBhw/j+++8bbdP06dMZO3YscXFxwWP33XcfBx10EG+88QZdu3YlKSmJa665Bk3TeOKJJ8jMzKRTp048/PDDtcq677776Nq1Ky6Xi+zsbG644YbgOZvNxgknnMC0adMiv5FCtGH2lm6AEEIIIYQQInzPPfcca9euZeDAgTzwwAMAdOzYkc2bN9e5tqKigueff57p06dTWlrKhAkTmDBhAu3atWPmzJls3LiR008/ncMPP5yzzz4bgEsuuYTNmzczffp0srOz+eSTTzj++ONZsWIF++23X71t+uGHHzj33HPrHN+wYQNfffUVX3/9NRs2bOCMM85g06ZN9OnTh3nz5jF//nwuvfRSxowZw/Dhw/noo4949tlnmT59Ovvvvz95eXksX768VpmHHnooTzzxRIR3UYi2TQJ6IYQQQggh2qDU1FScTicJCQlkZmY2eq3P52PKlCn06tULgDPOOIN33nmHXbt2kZSUxIABAxg9ejTfffcdZ599Nhs2bGDatGls376d7OxsAG677Ta+/vpr3nzzTR555JF669m8eXPw+lC6rvPGG2+QnJwcrGvNmjXMnDkTVVXp27cvjz/+ON9//z3Dhw9n69atZGZmcswxx+BwOOjatSuHHnporTI7d+7M1q1b0XXd0vX6QrRm8skXQgghhBDiLy4hISEYzANkZGTQvXt3kpKSah3Lz88HYMmSJRiGQZ8+fUhKSgr+mzdvHhs2bGiwnsrKylrT7at1796d5OTkWnUNGDCgViAeWv+ZZ55JZWUlPXv25IorruCTTz7B7/fXKjM+Ph5d1/F4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55ZbbsFms/n+pDQhhBBCCOGzOXPmaAaDQXv77be1bdu2abfeeqsWFhamZWRkHOqmBdWJJ56ovffee9qWLVu0DRs2aDNmzNB69uypVVVVec556qmntIiICG3u3Lna5s2btfPPP19LTk7WKioqPOdcf/31Wo8ePbSFCxdq69at04499lht5MiRmsPh8Jxz0kknacOGDdP++OMP7Y8//tCGDRumnXrqqZ36fANt9erVWq9evbQRI0Zot956q+e43LOmSkpKtLS0NO2KK67QVq1ape3fv19btGiRtmfPHs85cs+aeuyxx7S4uDjt+++/1/bv36998cUXWnh4uPbiiy96zvm737P58+drDzzwgDZ37lwN0L7++usmj3fW/XE4HNqwYcO0Y489Vlu3bp22cOFCrXv37tpNN93k83OSgF4IIYQQwg8TJkzQrr/++ibHBg0apN17772HqEWHRmFhoQZoS5cu1TRN01wul5aUlKQ99dRTnnMsFosWFRWlvfHGG5qmaVpZWZlmMBi0OXPmeM7JycnRVFXVfvrpJ03TNG3btm0aoK1cudJzzooVKzRA27FjR2c8tYCrrKzU+vfvry1cuFA7+uijPQG93LPm7rnnHm3KlCmtPi73rLkZM2ZoV155ZZNjM2fO1C655BJN0+SeHezggL4z78/8+fM1VVW1nJwczzmzZ8/WTCaTVl5e7tPzkCn3QgghhBA+stlsrF27lunTpzc5Pn36dP74449D1KpDo7y8HIDY2FgA9u/fT35+fpN7YzKZOProoz33Zu3atdjt9ibndO/enWHDhnnOWbFiBVFRUUycONFzzqRJk4iKiuqy9/if//wnM2bMYNq0aU2Oyz1rbt68eYwbN45zzz2XhIQERo8ezdtvv+15XO5Zc1OmTOGXX35h165dAGzcuJHffvuNU045BZB71p7OvD8rVqxg2LBhdO/e3XPOiSeeiNVqbbKsxBt635+qEEIIIcTfW1FREU6nk8TExCbHExMTyc/PP0St6nyapnH77bczZcoUhg0bBuB5/i3dm4yMDM85RqORmJiYZufUX5+fn09CQkKzOhMSErrkPZ4zZw7r1q3jzz//bPaY3LPm9u3bx+uvv87tt9/O/fffz+rVq7nlllswmUxcdtllcs9acM8991BeXs6gQYPQ6XQ4nU4ef/xxLrzwQkDeZ+3pzPuTn5/frJ6YmBiMRqPP91ACeiGEEEIIPymK0uR3TdOaHfsru+mmm9i0aRO//fZbs8f8uTcHn9PS+V3xHmdlZXHrrbeyYMECzGZzq+fJPWvgcrkYN24cTzzxBACjR49m69atvP7661x22WWe8+SeNfjss8/4+OOP+fTTTxk6dCgbNmxg1qxZdO/encsvv9xzntyztnXW/QnUPZQp90IIIYQQPoqPj0en0zUbSSksLGw26vJXdfPNNzNv3jwWL15MSkqK53hSUhJAm/cmKSkJm81GaWlpm+cUFBQ0q/fAgQNd7h6vXbuWwsJCxo4di16vR6/Xs3TpUv773/+i1+s9z0fuWYPk5GSGDBnS5NjgwYPJzMwE5H3Wkrvuuot7772XCy64gOHDh3PppZdy22238eSTTwJyz9rTmfcnKSmpWT2lpaXY7Xaf76EE9EIIIYQQPjIajYwdO5aFCxc2Ob5w4UKOOOKIQ9SqzqFpGjfddBNfffUVv/76K717927yeO/evUlKSmpyb2w2G0uXLvXcm7Fjx2IwGJqck5eXx5YtWzznTJ48mfLyclavXu05Z9WqVZSXl3e5e3z88cezefNmNmzY4Pk3btw4Lr74YjZs2ECfPn3knh3kyCOPbLYd4q5du0hLSwPkfdaSmpoaVLVpeKfT6Tzb1sk9a1tn3p/JkyezZcsW8vLyPOcsWLAAk8nE2LFjfWu4Tyn0hBBCCCGEpmkN29a988472rZt27RZs2ZpYWFhWnp6+qFuWlDdcMMNWlRUlLZkyRItLy/P86+mpsZzzlNPPaVFRUVpX331lbZ582btwgsvbHHrp5SUFG3RokXaunXrtOOOO67FrZ9GjBihrVixQluxYoU2fPjwLrE1ljcaZ7nXNLlnB1u9erWm1+u1xx9/XNu9e7f2ySefaKGhodrHH3/sOUfuWVOXX3651qNHD8+2dV999ZUWHx+v3X333Z5z/u73rLKyUlu/fr22fv16DdCef/55bf369Z7tRjvr/tRvW3f88cdr69at0xYtWqSlpKTItnVCCCGEEJ3p1Vdf1dLS0jSj0aiNGTPGs3XbXxnQ4r/33nvPc47L5dIeeughLSkpSTOZTNpRRx2lbd68uUk5tbW12k033aTFxsZqISEh2qmnnqplZmY2Oae4uFi7+OKLtYiICC0iIkK7+OKLtdLS0k54lsF3cEAv96y57777Ths2bJhmMpm0QYMGaW+99VaTx+WeNVVRUaHdeuutWs+ePTWz2az16dNHe+CBBzSr1eo55+9+zxYvXtzi36/LL79c07TOvT8ZGRnajBkztJCQEC02Nla76aabNIvF4vNzUjRN03wb0xdCCCGEEEIIIcShJmvohRBCCCGEEEKILkgCeiGEEEIIIYQQoguSgF4IIYQQQgghhOiCJKAXQgghhBBCCCG6IAnohRBCCCGEEEKILkgCeiGEEEIIIYQQoguSgF4IIYQQQgghhOiCJKAXQgghhBBCCCG6IAnohRBCCCGE+AtZsmQJiqJQVlZ2SOr/9ddfGTRoEC6XK2h1jB8/nq+++ipo5QvRVUhAL4QQQgghRBd1zDHHMGvWrCbHjjjiCPLy8oiKijokbbr77rt54IEHUNXghRoPPvgg9957b1A7DYToCiSgF0IIIYQQ4i/EaDSSlJSEoiidXvcff/zB7t27Offcc4Naz4wZMygvL+fnn38Oaj1CHO4koBdCCCGEEKILuuKKK1i6dCkvvfQSiqKgKArp6enNpty///77REdH8/333zNw4EBCQ0M555xzqK6u5oMPPqBXr17ExMRw880343Q6PeXbbDbuvvtuevToQVhYGBMnTmTJkiVttmnOnDlMnz4ds9nsOfbwww8zatQo3n33XXr27El4eDg33HADTqeTZ555hqSkJBISEnj88ceblPXwww/Ts2dPTCYT3bt355ZbbvE8ptPpOOWUU5g9e3bHb6QQXZj+UDdACCGEEEII4buXXnqJXbt2MWzYMB599FEAunXrRnp6erNza2pq+O9//8ucOXOorKxk5syZzJw5k+joaObPn8++ffs4++yzmTJlCueffz4A//jHP0hPT2fOnDl0796dr7/+mpNOOonNmzfTv3//Ftu0bNkyLrzwwmbH9+7dy48//shPP/3E3r17Oeecc9i/fz8DBgxg6dKl/PHHH1x55ZUcf/zxTJo0iS+//JIXXniBOXPmMHToUPLz89m4cWOTMidMmMAzzzzTwbsoRNcmAb0QQgghhBBdUFRUFEajkdDQUJKSkto812638/rrr9O3b18AzjnnHD766CMKCgoIDw9nyJAhHHvssSxevJjzzz+fvXv3Mnv2bLKzs+nevTsAd955Jz/99BPvvfceTzzxRIv1pKene85vzOVy8e677xIREeGpa+fOncyfPx9VVRk4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PqtaffZPz549ceTIEaSnp+titm/fDrVajcDAQOMabtQSekREREQkhCjftm7VqlXi2LFjYsqUKcLa2lqkpqY2dNNM6uWXXxYajUbs3LlTpKen6478/HxdzPvvvy80Go3YtGmTSE5OFk888USFWz+5u7uLHTt2iMTERPGf//ynwq2fOnfuLOLj40V8fLzo1KmTWWyNVR13rnIvBPvsbvv37xcKhUK899574tSpU+Kbb74RVlZW4uuvv9bFsM/0PfPMM6J169a6bes2bdoknJycxMyZM3Uxzb3P8vLyRFJSkkhKShIAxCeffCKSkpJ0243WV/+UbVsXGhoqEhMTxY4dO4S7uzu3rSMiIiKqT59//rnw9PQUKpVKdOvWTbd1W1MGoMJjzZo1uhitVivmzp0rXF1dhVqtFg888IBITk7WK+fmzZti4sSJwsHBQVhaWoqhQ4eKtLQ0vZgrV66Ip556Stja2gpbW1vx1FNPiWvXrtXDXZre3Qk9+8zQTz/9JPz9/YVarRa+vr5ixYoVeu+zz/Tl5uaKyZMnizZt2ggLCwvRtm1bMWfOHFFQUKCLae599scff1T49+uZZ54RQtRv/5w7d06Eh4cLS0tL4eDgICZOnChu3bpl9D1JQghh3Jg+ERERERERETU0PkNPREREREREZIaY0BMRERERERGZISb0RERERERERGaICT0RERERERGRGWJCT0RERERERGSGmNATERERERERmSEm9ERERERERERmiAk9ERERERERkRliQk9ERERE1ITs3LkTkiQhOzu7Qer//fff4evrC61Wa7I6unfvjk2bNpmsfCJzwYSeiIiIiMhMPfTQQ5gyZYreuV69eiE9PR0ajaZB2jRz5kzMmTMHMpnpUo0333wTs2bNMumXBkTmgAk9EREREVETolKp4OrqCkmS6r3uuLg4nDp1CqNGjTJpPeHh4cjJycG2bdtMWg9RY8eEnoiIiIjIDD377LPYtWsXFi9eDEmSIEkSUlNTDabcr127Fi1atMDWrVvh4+MDKysrjBw5Ejdu3MCXX34JLy8v2Nvb45VXXkFJSYmu/MLCQsycOROtW7eGtbU1goODsXPnzirbFB0djbCwMFhYWOjOzZs3D127dsXq1avRpk0b2NjY4OWXX0ZJSQk++OADuLq6wtnZGe+9955eWfPmzUObNm2gVqvh5uaGSZMm6d6Ty+UYMmQI1q9fX/uOJDJjioZuABERERERGW/x4sU4efIk/P39MX/+fABAy5YtkZqaahCbn5+PJUuWIDo6Gnl5eRgxYgRGjBiBFi1aICYmBmfPnsVjjz2GPn36ICIiAgDw3HPPITU1FdHR0XBzc8PmzZsxaNAgJCcno127dhW2affu3XjiiScMzp85cwa//PILfv31V5w5cwYjR45ESkoK2rdvj127diEuLg7PP/88QkNDERISgu+//x6ffvopoqOj0bFjR2RkZODw4cN6Zfbo0QMffPBBLXuRyLwxoSciIiIiMkMajQYqlQpWVlZwdXWtMraoqAjLli3DfffdBwAYOXIkvvrqK1y6dAk2Njbw8/NDv3798McffyAiIgJnzpzB+vXrceHCBbi5uQEApk+fjl9//RVr1qzBggULKqwnNTVVF38nrVaL1atXw9bWVlfXiRMnEBMTA5lMBh8fHyxatAg7d+5ESEgI0tLS4Orqiv79+0OpVKJNmzbo0aOHXpmtW7dGWloatFqtSZ/XJ2rM+JtPRERERNTEWVlZ6ZJ5AHBxcYGXlxdsbGz0zmVmZgIAEhMTIYRA+/btYWNjozt27dqFM2fOVFrPzZs39abbl/Hy8oKtra1eXX5+fnqJ+J31jxo1Cjdv3kTbtm0xduxYbN68GcXFxXplWlpaQqvVoqCgwMjeIGo6OEJPRERERNTEKZVKvdeSJFV4rmzVeK1WC7lcjoSEBMjlcr24O78EuJuTkxOuXbtW6/o9PDxw4sQJxMbGYseOHRg/fjw+/PBD7Nq1S3fd1atXYWVlBUtLy6punahJY0JPRERERGSmVCqV3kJ2dSUgIAAlJSXIzMxE3759jbru2LFjddIGS0tLDB8+HMOHD8eECRPg6+uL5ORkdOvWDQBw5MgR3X8TNVdM6ImIiIiIzJSXlxf27duH1NRU2NjYwMHBoU7Kbd++PZ566imMGTMGH3/8MQICApCVlYXff/8dnTp1wpAhQyq8buDAgfjyyy9rXf/atWtRUlKC4OBgWFlZ4auvvoKlpSU8PT11MXv27EFYWFit6yIyZ3yGnoiIiIjITE2fPh1yuRx+fn5o2bIl0tLS6qzsNWvWYMyYMXj11Vfh4+OD4cOHY9++ffDw8Kj0mqeffhrHjh3DiRMnalV3ixYtsHLlSvTu3RudO3fGb7/9hp9++gmOjo4AgIsXLyIuLg7PPfdcreohMneSEEI0dCOIiIiIiKhpmDlzJnJycvDFF1+YrI4ZM2YgJycHK1asMFkdROaAI/RERERERFRn5syZA09PT5M821/G2dkZ77zzjsnKJzIXHKEnIiIiIiIiMkMcoSciIiIiIiIyQ0zoiYiIiIiIiMwQE3oiIiIiIiIiM8SEnoiIiIiIiMgMMaEnIiIiIiIiMkNM6ImIiIiIiIjMEBN6IiIiIiIiIjPEhJ6IiIiIiIjIDDGhJyIiIiIiIjJD/x/RjPLytG43JgAAAABJRU5ErkJggg==",
+ "text/plain": [
+ "