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