1 /*
2 * pp_cond_exp_mc_urbanczik.h
3 *
4 * This file is part of NEST.
5 *
6 * Copyright (C) 2004 The NEST Initiative
7 *
8 * NEST is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * NEST is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with NEST. If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22
23 #ifndef PP_COND_EXP_MC_URBANCZIK_H
24 #define PP_COND_EXP_MC_URBANCZIK_H
25
26 // Generated includes:
27 #include "config.h"
28
29 #ifdef HAVE_GSL
30
31 // C++ includes:
32 #include <vector>
33
34 // C includes:
35 #include <gsl/gsl_errno.h>
36 #include <gsl/gsl_matrix.h>
37 #include <gsl/gsl_odeiv.h>
38
39 // Includes from nestkernel:
40 #include "connection.h"
41 #include "event.h"
42 #include "nest_types.h"
43 #include "random_generators.h"
44 #include "recordables_map.h"
45 #include "ring_buffer.h"
46 #include "universal_data_logger.h"
47 #include "urbanczik_archiving_node.h"
48 #include "urbanczik_archiving_node_impl.h"
49
50 // Includes from sli:
51 #include "dictdatum.h"
52 #include "name.h"
53
54 namespace nest
55 {
56 /**
57 * Function computing right-hand side of ODE for GSL solver.
58 * @note Must be declared here so we can befriend it in class.
59 * @note Must have C-linkage for passing to GSL.
60 * @note No point in declaring it inline, since it is called
61 * through a function pointer.
62 */
63 extern "C" int pp_cond_exp_mc_urbanczik_dynamics( double, const double*, double*, void* );
64
65 /** @BeginDocumentation
66 Name: pp_cond_exp_mc_urbanczik_parameters - Helper class for pp_cond_exp_mc_urbanczik
67
68 Description:
69 pp_cond_exp_mc_urbanczik_parameters is a helper class for the pp_cond_exp_mc_urbanczik neuron model
70 that contains all parameters of the model that are needed to compute the weight changes of a
71 connected urbanczik_synapse in the base class UrbanczikArchivingNode.
72
73 Author: Jonas Stapmanns, David Dahmen, Jan Hahne
74
75 SeeAlso: pp_cond_exp_mc_urbanczik
76 */
77 class pp_cond_exp_mc_urbanczik_parameters
78 {
79 friend class pp_cond_exp_mc_urbanczik;
80 friend class UrbanczikArchivingNode< pp_cond_exp_mc_urbanczik_parameters >;
81
82 private:
83 //! Compartments, NCOMP is number
84 enum Compartments_
85 {
86 SOMA = 0,
87 DEND,
88 NCOMP
89 };
90
91 double phi_max; //!< Parameter of the rate function
92 double rate_slope; //!< Parameter of the rate function
93 double beta; //!< Parameter of the rate function
94 double theta; //!< Parameter of the rate function
95 double phi( double u );
96 double h( double u );
97
98 public:
99 // The Urbanczik parameters need to be public within this class as they are passed to the GSL solver
100 double g_conn[ NCOMP ]; //!< Conductances connecting compartments in nS
101 double g_L[ NCOMP ]; //!< Leak Conductance in nS
102 double C_m[ NCOMP ]; //!< Capacity of the membrane in pF
103 double E_L[ NCOMP ]; //!< Reversal Potential in mV
104 double tau_syn_ex[ NCOMP ]; //!< Rise time of excitatory synaptic conductance in ms
105 double tau_syn_in[ NCOMP ]; //!< Rise time of inhibitory synaptic conductance in ms
106 };
107
108 /* BeginUserDocs: neuron, point process, conductance-based
109
110 Short description
111 +++++++++++++++++
112
113 Two-compartment point process neuron with conductance-based synapses
114
115 Description
116 +++++++++++
117
118 pp_cond_exp_mc_urbanczik is an implementation of a two-compartment spiking
119 point process neuron with conductance-based synapses as it is used
120 in [1]_. It is capable of connecting to an :doc:`Urbanczik synapse
121 <urbanczik_synapse>`.
122
123 The model has two compartments: soma and dendrite, labeled as s and p,
124 respectively. Each compartment can receive spike events and current input
125 from a current generator. Additionally, an external (rheobase) current can be
126 set for each compartment.
127
128 Synapses, including those for injection external currents, are addressed through
129 the receptor types given in the receptor_types entry of the state dictionary.
130 Note that in contrast to the single-compartment models, all
131 synaptic weights must be positive numbers! The distinction between excitatory
132 and inhibitory synapses is made explicitly by specifying the receptor type of
133 the synapse. For example, receptor_type=dendritic_exc results in an excitatory
134 input and receptor_type=dendritic_inh results in an inhibitory input to the
135 dendritic compartment.
136
137 .. _multicompartment-models:
138
139 Multicompartment models and synaptic delays
140 +++++++++++++++++++++++++++++++++++++++++++
141
142 Note that in case of multicompartment models that represent the dendrite
143 explicitly, the interpretation of the synaptic delay in NEST requires careful
144 consideration. In NEST, the delay is at least one simulation time step and is
145 assumed to be located entirely at the postsynaptic side. For point neurons, it
146 represents the time it takes for an incoming spike to travel along the
147 postsynaptic dendrite before it reaches the soma, see :ref:`panel a)
148 <fig-multicompartment>`. Conversely, if the synaptic weight depends on the
149 state of the postsynaptic neuron, the delay also represents the time it takes
150 for the information on the state to propagate back through the dendrite to the
151 synapse.
152
153 For multicompartment models in NEST, this means the delay is positioned directly
154 behind the incoming synapse, that is, before the first dendritic compartment on the
155 postsynaptic side, see :ref:`panel b) <fig-multicompartment>`. Therefore, the
156 delay specified in the synapse model does *not* account for any delay that might
157 be associated with information traveling through the explicitly modeled
158 dendritic compartments.
159
160 In the :ref:`Urbanczik synapse <urbanczik_synapse>`, the change of the synaptic
161 weight is driven by an error signal, which is the difference between the firing
162 rate of the soma (derived from the somatic spike train :math:`S_{post}`) and the
163 dendritic prediction of the firing rate of the soma (derived from the dendritic
164 membrane potential :math:`V`). The original publication [1]_ does not assume any
165 delay in the interaction between the soma and the dendritic compartment.
166 Therefore, we evaluate the firing rate and the dendritic prediction at equal
167 time points to calculate the error signal at that time point. Due to the
168 synaptic delay :math:`d`, the synapse combines a delayed version of the error
169 signal with the presynaptic spike train (:math:`S_{pre}`), see :ref:`panel c)
170 <fig-multicompartment>`.
171
172 .. _fig-multicompartment::
173
174 .. figure:: ../static/img/multicompartment.png
175 :width: 75 %
176
177 a) Two point neurons (red circles *pre* and *post*) connected via a synapse.
178 In NEST, the delay is entirely on the postsynaptic side, and in the case of point
179 neurons, it is interpreted as the dendritic delay. b) Two two-compartment
180 neuron models composed of a somatic (green) and a dendritic (blue)
181 compartment. The soma of the presynaptic neuron is connected to the dendrite
182 of the postsynaptic neuron. The synaptic delay is located behind the synapse
183 and before the dendrite. c) Time trace of the State variables that enter the
184 Urbanczik-Senn rule. Due to the synaptic delay :math:`d`, the presynaptic
185 spike train (top) is combined with a delayed version of the postsynaptic
186 quantities; the dendritic membrane potential (middle) and the somatic spike
187 train (bottom).
188
189
190 Parameters
191 ++++++++++
192
193 The following parameters can be set in the status dictionary. Parameters
194 for each compartment are collected in a sub-dictionary; these sub-dictionaries
195 are called "soma" and "dendritic", respectively. In the list below,
196 these parameters are marked with an asterisk.
197
198 ============ ===== =====================================================
199 V_m* mV Membrane potential
200 E_L* mV Leak reversal potential
201 C_m* pF Capacity of the membrane
202 E_ex* mV Excitatory reversal potential
203 E_in* mV Inhibitory reversal potential
204 g_L* nS Leak conductance
205 tau_syn_ex* ms Rise time of the excitatory synaptic alpha function
206 tau_syn_in* ms Rise time of the inhibitory synaptic alpha function
207 I_e* pA Constant input current
208 g_sp nS Coupling between soma and dendrite
209 g_ps nS Coupling between dendrite and soma
210 t_ref ms Duration of refractory period
211 ============ ===== =====================================================
212
213 See :doc:`../auto_examples/urbanczik_synapse_example` to learn more.
214
215 Remarks:
216
217 The neuron model uses standard units of NEST instead of the unitless quantities
218 used in [1]_.
219
220 .. note::
221 All parameters that occur for both compartments are stored as C arrays, with
222 index 0 being soma.
223
224 Sends
225 +++++
226
227 SpikeEvent
228
229 Receives
230 ++++++++
231
232 SpikeEvent, CurrentEvent, DataLoggingRequest
233
234 References
235 ++++++++++
236
237 .. [1] R. Urbanczik, W. Senn (2014). Learning by the Dendritic Prediction of
238 Somatic Spiking. Neuron, 81, 521 - 528.
239
240 See also
241 ++++++++
242
243 urbanczik_synapse
244
245 EndUserDocs */
246
247 class pp_cond_exp_mc_urbanczik : public UrbanczikArchivingNode< pp_cond_exp_mc_urbanczik_parameters >
248 {
249
250 // Boilerplate function declarations --------------------------------
251
252 public:
253 pp_cond_exp_mc_urbanczik();
254 pp_cond_exp_mc_urbanczik( const pp_cond_exp_mc_urbanczik& );
255 ~pp_cond_exp_mc_urbanczik();
256
257 /**
258 * Import sets of overloaded virtual functions.
259 * @see Technical Issues / Virtual Functions: Overriding, Overloading, and
260 * Hiding
261 */
262 using Node::handle;
263 using Node::handles_test_event;
264
265 port send_test_event( Node&, rport, synindex, bool );
266
267 void handle( SpikeEvent& );
268 void handle( CurrentEvent& );
269 void handle( DataLoggingRequest& );
270
271 port handles_test_event( SpikeEvent&, rport );
272 port handles_test_event( CurrentEvent&, rport );
273 port handles_test_event( DataLoggingRequest&, rport );
274
275 void get_status( DictionaryDatum& ) const;
276 void set_status( const DictionaryDatum& );
277
278 private:
279 void init_buffers_();
280 void calibrate();
281 void update( Time const&, const long, const long );
282
283 // Enumerations and constants specifying structure and properties ----
284
285 //! Compartments, NCOMP is number
286 enum Compartments_
287 {
288 SOMA = 0,
289 DEND,
290 NCOMP
291 };
292
293 /**
294 * Minimal spike receptor type.
295 * @note Start with 1 so we can forbid port 0 to avoid accidental
296 * creation of connections with no receptor type set.
297 */
298 static const port MIN_SPIKE_RECEPTOR = 1;
299
300 /**
301 * Spike receptors.
302 */
303 enum SpikeSynapseTypes
304 {
305 SOMA_EXC = MIN_SPIKE_RECEPTOR,
306 SOMA_INH,
307 DEND_EXC,
308 DEND_INH,
309 SUP_SPIKE_RECEPTOR
310 };
311
312 static const size_t NUM_SPIKE_RECEPTORS = SUP_SPIKE_RECEPTOR - MIN_SPIKE_RECEPTOR;
313
314 /**
315 * Minimal current receptor type.
316 * @note Start with SUP_SPIKE_RECEPTOR to avoid any overlap and
317 * accidental mix-ups.
318 */
319 static const port MIN_CURR_RECEPTOR = SUP_SPIKE_RECEPTOR;
320
321 /**
322 * Current receptors.
323 */
324 enum CurrentSynapseTypes
325 {
326 I_SOMA = MIN_CURR_RECEPTOR,
327 I_DEND,
328 SUP_CURR_RECEPTOR
329 };
330
331 static const size_t NUM_CURR_RECEPTORS = SUP_CURR_RECEPTOR - MIN_CURR_RECEPTOR;
332
333 // Friends --------------------------------------------------------
334
335 friend int pp_cond_exp_mc_urbanczik_dynamics( double, const double*, double*, void* );
336
337 friend class RecordablesMap< pp_cond_exp_mc_urbanczik >;
338 friend class UniversalDataLogger< pp_cond_exp_mc_urbanczik >;
339
340
341 // Parameters ------------------------------------------------------
342
343 /**
344 * Independent parameters of the model.
345 * These parameters must be passed to the iteration function that
346 * is passed to the GSL ODE solvers. Since the iteration function
347 * is a C++ function with C linkage, the parameters can be stored
348 * in a C++ struct with member functions, as long as we just pass
349 * it by void* from C++ to C++ function. The struct must be public,
350 * though, since the iteration function is a function with C-linkage,
351 * whence it cannot be a member function of pp_cond_exp_mc_urbanczik.
352 * @note One could achieve proper encapsulation by an extra level
353 * of indirection: Define the iteration function as a member
354 * function, plus an additional wrapper function with C linkage.
355 * Then pass a struct containing a pointer to the node and a
356 * pointer-to-member-function to the iteration function as void*
357 * to the wrapper function. The wrapper function can then invoke
358 * the iteration function on the node (Stroustrup, p 418). But
359 * this appears to involved, and the extra indirections cost.
360 */
361 struct Parameters_
362 {
363 double t_ref; //!< Refractory period in ms
364 double E_ex[ NCOMP ]; //!< Excitatory reversal Potential in mV
365 double E_in[ NCOMP ]; //!< Inhibitory reversal Potential in mV
366 double I_e[ NCOMP ]; //!< Constant Current in pA
367
368 pp_cond_exp_mc_urbanczik_parameters urbanczik_params;
369
370 /** Dead time in ms. */
371 double dead_time_;
372
373 Parameters_(); //!< Sets default parameter values
374 Parameters_( const Parameters_& ); //!< needed to copy C-arrays
375 Parameters_& operator=( const Parameters_& ); //!< needed to copy C-arrays
376
377 void get( DictionaryDatum& ) const; //!< Store current values in dictionary
378 void set( const DictionaryDatum& ); //!< Set values from dicitonary
379 };
380
381
382 // State variables ------------------------------------------------------
383
384 /**
385 * State variables of the model.
386 * @note Copy constructor required because of C-style array.
387 */
388 public:
389 struct State_
390 {
391
392 /**
393 * Elements of state vector.
394 * For the multicompartmental case here, these are offset values.
395 * The state variables are stored in contiguous blocks for each
396 * compartment, beginning with the soma.
397 */
398 enum StateVecElems_
399 {
400 V_M = 0,
401 G_EXC,
402 G_INH,
403 I_EXC, // in the paper it is I_dnd which accounts for both excitation and inhibition
404 I_INH,
405 STATE_VEC_COMPS
406 };
407
408 //! total size of state vector
409 static const size_t STATE_VEC_SIZE = STATE_VEC_COMPS * NCOMP;
410
411 //! neuron state, must be C-array for GSL solver
412 double y_[ STATE_VEC_SIZE ];
413 int r_; //!< number of refractory steps remaining
414
415 State_( const Parameters_& ); //!< Default initialization
416 State_( const State_& );
417
418 State_& operator=( const State_& );
419
420 void get( DictionaryDatum& ) const;
421 void set( const DictionaryDatum&, const Parameters_& );
422
423 /**
424 * Compute linear index into state array from compartment and element.
425 * @param comp compartment index
426 * @param elem elemet index
427 * @note compartment argument is not of type Compartments_, since looping
428 * over enumerations does not work.
429 */
430 static size_t
idxState_431 idx( size_t comp, StateVecElems_ elem )
432 {
433 assert( comp * STATE_VEC_COMPS + elem < STATE_VEC_SIZE );
434 return comp * STATE_VEC_COMPS + elem;
435 }
436 };
437
438 private:
439 // Internal buffers --------------------------------------------------------
440
441 /**
442 * Buffers of the model.
443 */
444 struct Buffers_
445 {
446 Buffers_( pp_cond_exp_mc_urbanczik& ); //!<Sets buffer pointers to 0
447 //! Sets buffer pointers to 0
448 Buffers_( const Buffers_&, pp_cond_exp_mc_urbanczik& );
449
450 //! Logger for all analog data
451 UniversalDataLogger< pp_cond_exp_mc_urbanczik > logger_;
452
453 /** buffers and sums up incoming spikes/currents
454 * @note Using STL vectors here to ensure initialization.
455 */
456 std::vector< RingBuffer > spikes_;
457 std::vector< RingBuffer > currents_;
458
459 /** GSL ODE stuff */
460 gsl_odeiv_step* s_; //!< stepping function
461 gsl_odeiv_control* c_; //!< adaptive stepsize control function
462 gsl_odeiv_evolve* e_; //!< evolution function
463 gsl_odeiv_system sys_; //!< struct describing system
464
465 // IntergrationStep_ should be reset with the neuron on ResetNetwork,
466 // but remain unchanged during calibration. Since it is initialized with
467 // step_, and the resolution cannot change after nodes have been created,
468 // it is safe to place both here.
469 double step_; //!< step size in ms
470 double IntegrationStep_; //!< current integration time step, updated by GSL
471
472 /**
473 * Input currents injected by CurrentEvent.
474 * This variable is used to transport the current applied into the
475 * _dynamics function computing the derivative of the state vector.
476 * It must be a part of Buffers_, since it is initialized once before
477 * the first simulation, but not modified before later Simulate calls.
478 */
479 double I_stim_[ NCOMP ]; //!< External Stimulus in pA
480 };
481
482 // Internal variables ---------------------------------------------
483
484 /**
485 * Internal variables of the model.
486 */
487 struct Variables_
488 {
489 int RefractoryCounts_;
490
491 double h_; //!< simulation time step in ms
492 RngPtr rng_; //!< random number generator of my own thread
493 poisson_distribution poisson_dist_; //!< poisson distribution
494 };
495
496 // Access functions for UniversalDataLogger -------------------------------
497
498 /**
499 * Read out state vector elements, used by UniversalDataLogger
500 * First template argument is component "name", second compartment "name".
501 */
502 template < State_::StateVecElems_ elem, Compartments_ comp >
503 double
get_y_elem_()504 get_y_elem_() const
505 {
506 return S_.y_[ S_.idx( comp, elem ) ];
507 }
508
509 //! Read out number of refractory steps, used by UniversalDataLogger
510 double
get_r_()511 get_r_() const
512 {
513 return Time::get_resolution().get_ms() * S_.r_;
514 }
515
516 // Data members ----------------------------------------------------
517
518 Parameters_ P_;
519 State_ S_;
520 Variables_ V_;
521 Buffers_ B_;
522
523 //! Table of compartment names
524 static std::vector< Name > comp_names_;
525
526 //! Dictionary of receptor types, leads to seg fault on exit, see #328
527 // static DictionaryDatum receptor_dict_;
528
529 //! Mapping of recordables names to access functions
530 static RecordablesMap< pp_cond_exp_mc_urbanczik > recordablesMap_;
531 };
532
533
534 // Inline functions of pp_cond_exp_mc_urbanczik_parameters
535 inline double
phi(double u)536 pp_cond_exp_mc_urbanczik_parameters::phi( double u )
537 {
538 return phi_max / ( 1.0 + rate_slope * exp( beta * ( theta - u ) ) );
539 }
540
541 inline double
h(double u)542 pp_cond_exp_mc_urbanczik_parameters::h( double u )
543 {
544 return 15.0 * beta / ( 1.0 + ( 1.0 / rate_slope ) * exp( -beta * ( theta - u ) ) );
545 }
546
547
548 // Inline functions of pp_cond_exp_mc_urbanczik
549 inline port
send_test_event(Node & target,rport receptor_type,synindex,bool)550 pp_cond_exp_mc_urbanczik::send_test_event( Node& target, rport receptor_type, synindex, bool )
551 {
552 SpikeEvent e;
553 e.set_sender( *this );
554 return target.handles_test_event( e, receptor_type );
555 }
556
557 inline port
handles_test_event(SpikeEvent &,rport receptor_type)558 pp_cond_exp_mc_urbanczik::handles_test_event( SpikeEvent&, rport receptor_type )
559 {
560 if ( receptor_type < MIN_SPIKE_RECEPTOR || receptor_type >= SUP_SPIKE_RECEPTOR )
561 {
562 if ( receptor_type < 0 || receptor_type >= SUP_CURR_RECEPTOR )
563 {
564 throw UnknownReceptorType( receptor_type, get_name() );
565 }
566 else
567 {
568 throw IncompatibleReceptorType( receptor_type, get_name(), "SpikeEvent" );
569 }
570 }
571 return receptor_type - MIN_SPIKE_RECEPTOR;
572 }
573
574 inline port
handles_test_event(CurrentEvent &,rport receptor_type)575 pp_cond_exp_mc_urbanczik::handles_test_event( CurrentEvent&, rport receptor_type )
576 {
577 if ( receptor_type < MIN_CURR_RECEPTOR || receptor_type >= SUP_CURR_RECEPTOR )
578 {
579 if ( receptor_type >= 0 && receptor_type < MIN_CURR_RECEPTOR )
580 {
581 throw IncompatibleReceptorType( receptor_type, get_name(), "CurrentEvent" );
582 }
583 else
584 {
585 throw UnknownReceptorType( receptor_type, get_name() );
586 }
587 }
588 return receptor_type - MIN_CURR_RECEPTOR;
589 }
590
591 inline port
handles_test_event(DataLoggingRequest & dlr,rport receptor_type)592 pp_cond_exp_mc_urbanczik::handles_test_event( DataLoggingRequest& dlr, rport receptor_type )
593 {
594 if ( receptor_type != 0 )
595 {
596 if ( receptor_type < 0 || receptor_type >= SUP_CURR_RECEPTOR )
597 {
598 throw UnknownReceptorType( receptor_type, get_name() );
599 }
600 else
601 {
602 throw IncompatibleReceptorType( receptor_type, get_name(), "DataLoggingRequest" );
603 }
604 }
605 return B_.logger_.connect_logging_device( dlr, recordablesMap_ );
606 }
607
608 inline void
get_status(DictionaryDatum & d)609 pp_cond_exp_mc_urbanczik::get_status( DictionaryDatum& d ) const
610 {
611 P_.get( d );
612 S_.get( d );
613 UrbanczikArchivingNode< pp_cond_exp_mc_urbanczik_parameters >::get_status( d );
614
615 ( *d )[ names::recordables ] = recordablesMap_.get_list();
616
617 /**
618 * @todo dictionary construction should be done only once for
619 * static member in default c'tor, but this leads to
620 * a seg fault on exit, see #328
621 */
622 DictionaryDatum receptor_dict_ = new Dictionary();
623 ( *receptor_dict_ )[ names::soma_exc ] = SOMA_EXC;
624 ( *receptor_dict_ )[ names::soma_inh ] = SOMA_INH;
625 ( *receptor_dict_ )[ names::soma_curr ] = I_SOMA;
626
627 ( *receptor_dict_ )[ names::dendritic_exc ] = DEND_EXC;
628 ( *receptor_dict_ )[ names::dendritic_inh ] = DEND_INH;
629 ( *receptor_dict_ )[ names::dendritic_curr ] = I_DEND;
630
631 ( *d )[ names::receptor_types ] = receptor_dict_;
632 }
633
634 inline void
set_status(const DictionaryDatum & d)635 pp_cond_exp_mc_urbanczik::set_status( const DictionaryDatum& d )
636 {
637 Parameters_ ptmp = P_; // temporary copy in case of errors
638 ptmp.set( d ); // throws if BadProperty
639 State_ stmp = S_; // temporary copy in case of errors
640 stmp.set( d, ptmp ); // throws if BadProperty
641
642 // We now know that (ptmp, stmp) are consistent. We do not
643 // write them back to (P_, S_) before we are also sure that
644 // the properties to be set in the parent class are internally
645 // consistent.
646 UrbanczikArchivingNode< pp_cond_exp_mc_urbanczik_parameters >::set_status( d );
647
648 // if we get here, temporaries contain consistent set of properties
649 P_ = ptmp;
650 S_ = stmp;
651 }
652
653 } // namespace
654
655
656 #endif // HAVE_GSL
657 #endif // PP_COND_EXP_MC_URBANCZIK_H
658