BEP-2: Spike-timing-dependent plasticity

Interface
=========
How to specify STDP:
* presynaptic group
* postsynaptic group
* connection matrix
* STDP rule

One possibility is to pass a Connection object, which holds
the first 3 elements. Alternatively, keyword arguments could be
passed when initialising the Connection object. Or a Connection
method could be used: myconnection.set_STDP(...).

How to specify the STDP rule
----------------------------
* Differential equations
^^^^^^^^^^^^^^^^^^^^^^^^
This is the most general way of specifying the STDP rule.
One can define directly the synaptic model, e.g.:

pre:
	dA_pre/dt=-A_pre/tau_pre : 1
post:
	dA_post/dt=-A_post/tau_post : 1
spike_pre:
	A_pre+=dA_pre
	w=clip(w+A_post,0,gmax) # or w+=A_post and use automatic clipping
spike_post:
	A_post+=dA_post
	w=clip(w+A_pre,0,gmax)

How to pass this model?
Possibly like NeuronGroup (advantage: same mechanism):
STDP(eqs_pre='dA_pre/...',spikes_pre='A_pre...',eqs_post='dA_post/...',spikes_post='A_post...')
and w is a special identifier meaning synaptic weight.

Note that it is necessary to define pre and post-synaptic variables,
in order to interpret the expressions in spike_pre and spike_post.
But these could in principle be guessed. Presynaptic (postsynaptic) variables are those variables
modified by presynaptic (postsynaptic) spikes. All identifiers in the "spike_pre" statement
that are in left-hand sides are modified. This is not trivial to do but possible.

Syntax:
stdp=STDP(C,eqs='''
                dA_pre/...
                dA_post/...''', # equations string (with units etc)
                pre='A_pre...', # python statements
                post='A_post...'[,
                bounds=(0,gmax)]) # clipping

	For the future?
	eqs="""
	pre:
		dA_pre/dt=-A_pre/tau_pre : 1
		spike:
			A_pre+=dA_pre
			w=clip(w+A_post,0,gmax) # or w+=A_post and use automatic clipping
	post:
		dA_post/dt=-A_post/tau_post : 1
		spike:
			A_post+=dA_post
			w=clip(w+A_pre,0,gmax)
	"""
	myconnection=Connection(...,STDP=eqs)

Implementation:
[DONE]* get identifiers from pre and post code
[DONE]* find which ones are modified (e.g. regular expression matching; careful with comments)
[DONE]* separate differential equations in pre/post
[DONE]* check pre/post consistency
* create virtual groups (inherit NeuronGroup; Group?), pre and post
[DONE]* pre/post code: add [spikes,:] etc
[DONE]* bounds: add one line to pre/post code (clip(w,min,max,w))
* event-driven code; do some speed tests
* create forward and backward Connection objects; propagate does pre or post code and
event-driven updates

* Presynaptic and postsynaptic groups are created, with the corresponding
variables (A_pre, A_post) and differential equations.
* Operation associated to presynaptic spikes:
	A_pre[spikes]+=dA_pre # insertion of [spikes] after presynaptic variables
	w[spikes,:]=clip(w[spikes,:]+A_post,0,gmax) # insertion of [spikes,:] after w
  and w is the connection matrix.
* Same for postsynaptic spikes except [:,spikes] is inserted.
To implement the last 2 operations, there should be a forward and backward propagate
function in Connection, that would be called by the network object.

Notes:
* In some rules (last developments by W. Gerstner and M. van Rossum), the differential
system can depend on the membrane potential (or another variable) of the postsynaptic
neuron (not of the presynaptic one). This could be included in the specification above,
but only for the postsynaptic side (I don't think it is possible to vectorise if it is
on the presynaptic side). We should probably check that in those papers.

* Phenomenological
^^^^^^^^^^^^^^^^^^
The user must provide:
* f(t>0) and f(t<0) (weight modification function)
* consider all or nearest spikes, for both presynaptic and postsynaptic spikes
* weight update rule, for t>0 and t<0. E.g.: w+=dw or w+=(1-w)*dw
This is less general than the specification based on differential systems (e.g. triplet
rules are not possible).

Example:
set_STDP(f_pre="Apre*exp(-t/tau_pre)",
         f_post="Apost*exp(-t/tau_post)",
         pre='all',post='nearest',
         update_pre='(1-w)*dw',update_post='dw')
with defaults pre='all', post='all', update_pre='dw', update_post='dw'.
[the _pre and _post suffixes are not good; should causal/anticausal or something like that;
or maybe _prepost and _postpre, or pos/neg]. Alternatively, bounds="soft"/"hard"/"semisoft".
Bounds should also be specified, if necessary.

The functions (f_pre, f_post) would be turned into differential systems
(see experimental/integrodiff). Interaction "all" converts to Apre[spikes]+=1,
interaction "nearest" converts to Apre[spikes]=1.

* Standard predefined types
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Typically, most people use exponential STDP, with various types of interactions
(all-to-all/nearest neighbour, additive/multiplicative etc). Essentially,
this only applies to the definition of differential equations, i.e., of the
weight change function (f_pre and f_post).

Example:
myconnection.set_exponentialSTDP(Apre=,Apost=,tau_pre=,tau_post=)
# and all other keyword arguments as above (all/nearest/)

Event-driven implementation
===========================
In an event-driven implementation, pre and postsynaptic variables are only updated at
spike times. In the example above:
A_pre[spikes]=A_pre[spikes]*exp(-(t-lastt[spikes])/tau_pre), where lastt is a vector of
last update times.
And: lastt[spikes]=t

In that case, 2 possibilities:
* specific neuron groups with no update method, but instead another method for specific
updates. One idea: def update(self,index=None), where index is the list of neurons to update. 
* no groups, the vectors A_pre and A_post are stored and managed by the Connection object or
a STDP object. That does not seem very general, however.
* no groups, the vectors A_pre and A_post are stored in the pre and postsynaptic groups,
but updated somewhere else (at spike times).

The last possibility looks better.

How to calculate exact solutions of linear differential systems:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An idea could be to use some matric decomposition:
http://en.wikipedia.org/wiki/Matrix_decomposition
such as diagonalization (but that is not always possible) or (most general) the
Jordan decomposition.
Alternatively, one could use expm(At), where t has to be calculated for each updated
neuron index (too slow?). The scipy.linalg function expm is written in Pure Python using
the Pad approximation (rational approximation), so it might be possible to vectorise it.
