.. currentmodule:: brian

Connections
===========

Building connections
--------------------
First, one must define which neuron groups are connected and which state variable receives the spikes.
The following instruction::

  myconnection=Connection(group1,group2,'ge')

defines a connection from group ``group1`` to ``group2``, acting on variable ``ge``. When
neurons from group ``group1`` spike, the variable ``ge`` of the target neurons in group
``group2`` are incremented. When the connection object is initialised, the list of connections
is empty. It can be created in several ways. First, explicitly::

  myconnection[2,5]=3*nS

This instruction connects neuron 2 from ``group1`` to neuron 5 from ``group2`` with synaptic weight
3 nS. Units should match the units of the variable defined at initialisation time (``ge``).

The matrix of synaptic weights can be defined directly with the method :meth:`Connection.connect`::

  W=rand(len(group1),len(group2))*nS
  myconnection.connect(group1,group2,W)

Here a matrix with random elements is used to define the synaptic weights from ``group1``
to ``group2``. It is possible to build the matrix by block by using subgroups, e.g.::

  W=rand(20,30)*nS
  myconnection.connect(group1[0:20],group2[10:40],W=W)

There are several handy functions available to set the synaptic weights:
:meth:`~Connection.connect_full`, :meth:`~Connection.connect_random` and
:meth:`~Connection.connect_one_to_one`. The first one
is used to set uniform weights for all pairs of neurons in the (sub)groups::

  myconnection.connect_full(group1[0:20],group2[10:40],weight=5*nS)

The second one is used to set uniform weights for random pairs of neurons in the (sub)groups::

  myconnection.connect_random(group1[0:20],group2[10:40],sparseness=0.02,weight=5*nS)

Here the third argument (0.02) is the probability that a synaptic connection exists between two neurons.
The number of presynaptic neurons can be made constant by setting the keyword ``fixed=True`` 
(probability * number of neurons in ``group1``).
Finally, the method :meth:`~Connection.connect_one_to_one` connects neuron i from the first
group to neuron i from the second group::

  myconnection.connect_one_to_one(group1,group2,weight=3*nS)
  
Both groups must have the same number of neurons.

If you are connecting the whole groups, you can omit the first two arguments, e.g.::

  myconnection.connect_full(weight=5*nS)
  
connects ``group1`` to ``group2`` with weights 5 nS.

Building connections with connectivity functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is a simple and efficient way to build heterogeneous connections, by passing functions
instead of constants to the methods :meth:`~Connection.connect_full` and :meth:`~Connection.connect_random`.
The function must return the synaptic weight for a given pair of neuron (i,j).
For example::

  myconnection.connect_full(group1,group2,weight=lambda i,j:(1+cos(i-j))*2*nS)

where i (j) indexes neurons in ``group1`` (``group2``). This is the same as doing by hand::

  for i in range(len(group1)):
    for j in range(len(group2)):
      myconnection[i,j]=(1+cos(i-j))*2*nS

but it is much faster because the construction is vectorised, i.e., the function is called for every i
with j being the entire row of target indexes. Thus, the implementation is closer to::

  for i in range(len(group1)):
      myconnection[i,j]=(1+cos(i-arange(len(group2)))*2*nS
      
The method :meth:`~Connection.connect_random` also accepts functional arguments for the
weights and connection probability. For that method, it is possible to pass a function
with no argument, as in the following example::

  myconnection.connect_random(group1,group2,0.1,weight=lambda:rand()*nS)

Here each synaptic weight is random (between 0 and 1 nS).
Alternatively, the connection probability can also be a function, e.g.::

  myconnection.connect_random(group1,group2,0.1,weight=1*nS,sparseness=lambda i,j:exp(-abs(i-j)*.1))

The weight or the connection probability may both be functions (with 0 or 2 arguments).

Delays
------
Transmission delays can be introduced with the keyword ``delay``, passed at initialisation time.
There are two types of delays, homogeneous (all synapses have the same delay) and heterogeneous
(all synapses can have different delays). The former is more computationally efficient than the
latter. An example of homogeneous delays::

  myconnection=Connection(group1,group2,'ge',delay=3*ms)

If you have limited heterogeneity, you can use several Connection objects, e.g.::

  myconnection_fast=Connection(group1,group2,'ge',delay=1*ms)
  myconnection_slow=Connection(group1,group2,'ge',delay=5*ms)

For a highly heterogeneous set of delays, initialise the connection with ``delay=True``, set
a maximum delay with for example ``max_delay=5*ms`` and
then use the ``delay`` keyword in the :meth:`~DelayConnection.connect_random` and
:meth:`~DelayConnection.connect_full` methods::

  myconnection=Connection(group1,group2,'ge',delay=True,max_delay=5*ms)
  myconnection.connect_full(group1,group2,weight=3*nS,delay=(0*ms,5*ms))
 
The code above initialises the delays uniformly randomly between 0ms and 5ms. You can also
set ``delay`` to be a function of no variables, where it will be called once for each synapse,
or of two variables ``i, j`` where it will be called once for each row, as in the case of the
weights in the section above.

Alternatively, you can set the delays as follows::

  myconnection.delay[i,j] = 3*ms
  
See the reference documentation for :class:`Connection` and :class:`DelayConnection` for
more details.

Connection structure
--------------------
The underlying data structure used to store the synaptic connections is by default a sparse
matrix. If the connections are dense, it is more efficient to use a dense matrix, which can be
set at initialisation time::

  myconnection=Connection(group1,group2,'ge',structure='dense')

The sparse matrix structure is fixed during a run, new synapses cannot be added or deleted,
but there is a dynamic sparse matrix structure. It is less computationally efficient, but
allows runtime adding and deleting of synaptic connections. Use the ``structure='dynamic'``
keyword. For more details see the reference documentation for :class:`Connection`.

Modulation
----------
The synaptic weights can be modulated by a state variable of the presynaptic neurons with
the keyword ``modulation``::

  myconnection=Connection(group1,group2,'ge',modulation='u')

When a spike is produced by a presynaptic neuron (``group1``), the variable ge of each postsynaptic
neuron (``group2``) is incremented by the synaptic weight multiplied by the value of the variable u
of the presynaptic neuron. This is useful to implement short-term plasticity.

Direct connection
-----------------
In some cases, it is useful to connect a group directly to another one, in a one-to-one fashion.
The most efficient way to implement it is with the class :class:`IdentityConnection`::

  myconnection=IdentityConnection(group1,group2,'ge',weight=1*nS)
  
With this structure, the synaptic weights are homogeneous (it is not possible to define them
independently). When neuron i from ``group1`` spikes, the variable ge of neuron i from ``group2``
is increased by 1 nS. A typical application is when defining inputs to a network.

Simple connections
------------------

If your connection just connects one group to another in a simple way, you can initialise
the weights and delays at the time you initialise the :class:`Connection` object by using
the ``weight``, ``sparseness`` and ``delay`` keywords. For example::

  myconnection = Connection(group1, group2, 'ge', weight=1*nS, sparseness=0.1,
                            delay=(0*ms, 5*ms), max_delay=5*ms)

This would be equivalent to::

  myconnection = Connection(group1, group2, 'ge', delay=True, max_delay=5*ms)
  myconnection.connect_random(group1, group2, weight=1*nS, delay=(0*ms, 5*ms))

If the ``sparseness`` value is omitted or set to value 1, full connectivity is assumed,
otherwise random connectivity.

NOTE: in this case the ``delay`` keyword used without the ``weight`` keyword has no effect.