====== Implementing multiple synaptic states (the old way) ======
Note, that starting with Auryn v0.7 there is a new (and better way) of dealing with [[multiple synaptic state variables]].
===== Aims =====
Our aim is to introduce a meta-variable ''lpw'' which characterizes synaptic strength as it is transmitted. It is a low-pass filtered version of the plastic weight ''w''. Doing so will require to store both variables for each synapse. Therefore, each synapse possesses two states that it will need to keep track of. Currently, Auryn does not provide matrix classes which implement this directly. Therefore, we need do things manually.
===== Walk-through =====
To achieve our aim we will base this tutorial on the [[manual:TripletConnection]] which implements the minimal triplet model by Pfister and Gerstner (2006). In the following we will address these steps:
- We will copy and rename ''TripletConnection'' into a new class called ''LPTripletConnection''
- We will then insert a second [[manual:SimpleMatrix]] matrix container into the class which will hold our meta-variable ''lpw''.
- We will then modify the propagate function to propagate ''lpw'' as the synaptic weight to postsynaptic neurons.
- Finally we will implement an ''evolve'' function for the connection in which we implement the low pass filtering of ''w'' to yield ''lpw''.
==== Copying TripletSTDPConnection source files ====
To prepare our new class we start by navigating to Auryn's ''src'' directory. And then copying ''TripletConnection.h'' and ''TripletConnection.cpp'' to ''LPTripletConnection.h'' and ''LPTripletConnection.cpp'' respectively. Note, that you can name the class differently, but you will have to stick with then new name you choose throughout this walk-through. Here, I simply put LP for low-pass, but a shorter or different name might seem more appropriate for you.
zenke@cashew:~/auryn/src$ cp TripletConnection.h LPTripletConnection.h
zenke@cashew:~/auryn/src$ cp TripletConnection.cpp LPTripletConnection.cpp
Once the files are copied, open them in your preferred browser and replace all occurrences of ''TripletConnection'' with ''LPTripletConnection''. Make sure to also replace the include guards in the header file which are in all caps ''TRIPLETCONNECTION_H_'' becomes ''LPTRIPLETCONNECTION_H_''. Congrats! At this point it should be possible to build Auryn with the new object. However, so far we have not really implemented any new functionality. The new class will therefore behave exactly the same way as the original [[manual:TripletConnection]].
Now is generally a good time to start updating the Doxygen string in the header file just above the keyword ''class''. Add a short description of what you are planning to do with this connection, otherwise this is only forgotten later and you will have created ambiguous documentation which is only going to confuse you later.
==== Adding a second SimpleMatrix container ====
To store synaptic weights Auryn uses the class [[manual:SimpleMatrix]]. The simple denotes that it can hold only a single value (a [[manual:ComplexMatrix]] is planned for one of the downstream revisions of Auryn, but not implemented yet). Since we only need a second value to store the low pass filtered version of the weight (the one we called ''lpw'' so far), we will just go with not so need, but fully functional solution of using two [[manual:SimpleMatrix]] instances.
First add the following line to the private variable declarations in the newly created header (.h) file
private:
ForwardMatrix * lpw;
Then open the .cpp file and find the function ''init()''. At the end of this function add the following code which will instantiate our new ''lpw_matrix''
lpw = new ForwardMatrix ( w );
This tells Auryn to create a matrix and clone all its properties (such as dimensions, sparseness, etc) from ''w'' which is the default name of the weight matrix in all sparse connections (i.e. also the descendants of [[manual:SparseConnection]]). As a next step find the function ''free()'' in the .cpp file and add the line
delete lpw;
You have now created and allocated an object which will house your variable ''lpw'' for each synapse. However, so far this object is not referenced from anywhere, which we will change in the next step. For now it is a good idea to test if ''LPTripletConnection'' still compiles without a problem.
==== Modify STDP/Plasticity to act on new meta-variable ====
Since in [[manual:TripletConnection]] the weight ''w'' is directly affected by STDP and it also represents the synaptic weight propagated downstream in the case of a presynaptic action potential, we have to break this symmetry of implementation now. To do so, find the ''propagate_forward()'' function in the .cpp file which is responsible for propagating spikes and for handling plasticity events occurring with a presynaptic spike. Find the lines
AurynWeight value = fwd_data[c-fwd_ind];
transmit( *c , value );
What happens here is the following: ''c'' is a pointer to the position of postsynaptic index of the synaptic weight in the sparse matrix object. ''fwd_data'' is a predefined pointer to the beginning of the dense vector storing all nonzero weights of the sparse matrix (it is initialized in ''init_shortcuts()''). Finally ''transmit( *c, value)'' takes this value and sends it to the downstream neuron with the [[manual:NeuronID]] stored in ''*c''. This way a spike gets propagated to all the postsynaptic partners of a neuron. However, currently ''fwd_data'' points to our ''w'' matrix object, but we would like to propagate a low pass filtered version of this which will ultimately be stored in ''lpw''. To do this we only need to change these two lines to
AurynWeight value = lpw->get_data_begin()[c-fwd_ind];
transmit( *c , value );
This change will now propagate weights stored in ''lpw'', but so far these weights are not plastic, which we will change in the next step. For now it is a good idea to save the changes and try compiling the new class again to see if everything runs without error.
==== Implementing the evolve function to do the low-pass filtering ====
So far the values stored in ''lpw'' do not change over time. We are now going to change that by implementing the method ''evolve()'' which will do the actual low-pass filtering of ''w'' and store the results in ''lpw''. Since this function is called in every simulation time step and it affects all the weights (~O(N^2)) is is a good idea to wrap slow processes in a construct that only runs them every so many time steps. To do this we start by adding the following lines to the header .h file.
AurynFloat tau_lp;
AurynFloat delta_lp;
AurynTime timestep_lp;
just below the definition of ''lpw''.
Then at the end of ''init()'' in the .cpp file we initialize these values with the following code
tau_lp = 120;
timestep_lp = 1e-3*tau_lp/dt;
delta_lp = 1.0*timestep_lp/tau_lp*dt;
Here ''tau_lp'' corresponds to the filter time constant of two minutes.
Now find the function ''evolve()'' in the .cpp file (which should be empty) and add the following code
if ( sys->get_clock()%timestep_lp == 0 && stdp_active ) {
for (AurynLong i = 0 ; i < lpw->get_nonzero() ; ++i ) {
AurynWeight * wval = w->get_data_begin()+i;
AurynWeight * lpwval = lpw->get_data_begin()+i;
AurynFloat dw = ( *wval - *lpwval ) * delta_lp;
*lpwval += dw;
}
}
which now implements the low-pass filter. Importantly, the update off all ''lpw'' values is only run when the condition ''sys->get_clock()%timestep_lp == 0'' is fulfilled (every timestep_lp time steps). Note, the important computation happens in the line ''AurynFloat dw = ( *wval - *lpwval ) * delta_lp;'' which could be modified by additional functions to achieve non-linear filtering effects such as could be caused by finite availability of for instance AMPA receptors or scaffolding proteins necessary to implement plastic changes.