# Build Synapses¶

When you have some neurons, you need to build synapses to connect them. This tutorial will show you how to use synapse models to connect neurons to networks.

As neuron models, the *definition* and *usage* of the synapse model are separated from each other. Specifically, two classes should be used:

`brainpy.SynType`

: Define the abstract synapse model.`brainpy.SynConn`

: Use the abstract synapse model to generate a concrete synapse connection.

We will first take a look at the definition with `brainpy.SynType`

, then in the second part, we will show the usage with `brainpy.SynConn`

.

Before we start, let’s import the BrainPy and Numpy packages.

```
[1]:
```

```
import brainpy as bp
import numpy as np
```

## brainpy.SynType¶

You can define any abstract synapse type with `SynType`

, which is very flexible.

As neuron models, four parameters should be specified to initialize a `SynType`

:

`name`

: The synapse model name.`steps`

: The step functions to update at each time step. You can define your own update logic functions.`requires`

: The data required to run this synapse model, such as synaptic states and neuronal states of the connected neurons.`mode`

: Whether define the model based on scalar, vector, or matrix.

We provide a data structure `brainpy.types.SynState`

to support the synapse state management.

Three kinds of definition provided in BrainPy to define a `SynType`

:

`mode = 'scalar'`

: Synapse state`ST`

represents the state of a single synapse connection. And, each item in`ST`

is a scalar.`mode = 'vector'`

: Synapse state`ST`

represents the state of a group of synapse connections. And each item in`ST`

is a vector,`mode = 'matrix'`

: Synapse state`ST`

represents the state of a group of synapse connections. And each item in`ST`

is a matrix with the shape of`(num_pre, num_post)`

.

The definition logic of scalar-based models may be more straightforward than vector- and matrix- based models. We will first introduce the definition of a simple synapse model in scalar-based mode.

### Example: AMPA synapse model (scalar mode)¶

Let’s first take the AMPA synapse model as an example to see how to define a `SynType`

in BrainPy.

The formal equations of an AMPA synapse is given by:

where \(\bar{g}_{syn}\) is the maximum synaptic conductance, \(s\) is the gating variable, and \(V\) is the membrane potential of the postsynaptic neuron. The time constant \(\tau_{decay}\) is about 2ms and the equilibrium potential \(E_{syn}\) for AMPA synapse is 0.

```
[2]:
```

```
# parameters we need #
# ------------------ #
tau_decay = 2. # time constant of the dacay after synapse respond to a neurontransmitter.
g_max = .10 # Voltage-controlled conductance per unit area
# associated with the Sodium (Na) and Potassium (K) ion-channels on the synapse (postsynaptic membrane).
E = 0. # The equilibrium potentials for the synapse.
```

Please check Differential equations to see how BrainPy supports differential equations.

```
[3]:
```

```
# dynamics of gating variable
@bp.integrate
def ints(s, t):
return - s / tau_decay
```

Here, let’s first define the state of a synapse model.

```
[4]:
```

```
ST=bp.types.SynState(['s'], help='AMPA synapse state.')
```

In `ST`

, the dynamical variable \(s\) is included.

Since a synapse connects a presynaptic neuron and a postsynaptic neuron, we need to know the state of the two neurons.

```
[5]:
```

```
pre=bp.types.NeuState(['spike'], help='Presynaptic neuron state must have "sp" item.')
post=bp.types.NeuState(['V', 'input'], help='Postsynaptic neuron state must have "V" and "inp" item.')
```

From the equations of the AMPA synapse, we need to know whether the presynaptic neuron (`pre`

) provides a \(spike\) at current time. We also need to know the current membrane potential \(V\) of the postsynaptic neuron, and add synaptic current to the \(input\) item of the `post`

.

Based on the synapse state `ST`

and neuron states, the update logic of the synapse model from the current time point (\(t\)) to the next time point \((t + dt)\) can be defined as:

```
[6]:
```

```
def update(ST, _t, pre):
s = ints(ST['s'], _t)
if pre['spike'] == True:
s += 1
ST['s'] = s
```

In this example, the `update()`

function of AMPA model needs three data:

`ST`

: The synapse state.`_t`

: The system time at current point.`pre`

: The neuron state of the presynaptic neuron.

We also need to define an output logic to compute the synaptic current and add it to the postsynaptic inputs.

The synaptic delay between a presynaptic spike and the postsynaptic change can be implemented with a `@bp.delayed`

decorator.

```
[7]:
```

```
@bp.delayed
def output(ST, post):
I_syn = - g_max * ST['s'] * (post['V'] - E)
post['input'] += I_syn
```

Putting together, an AMPA synapse model is defined as:

```
[8]:
```

```
AMPA = bp.SynType(name='AMPA_synapse',
ST=ST,
requires=dict(pre=pre, post=post),
steps=(update, output),
mode='scalar')
```

Here, we should note that we just define an abstract AMPA synapse model. This model can run with any number of synapse connections. Only after defining a concrete synapse connection, can we use it to construct a network.

### Example: AMPA synapse model (matrix mode)¶

In matrix mode, each item in the synapse state `ST`

is a matrix.

The differential equation part is the same as the scalar mode, and we also need a `SynState`

and the `NeuState`

of presynaptic and postsynaptic neurons.

```
[9]:
```

```
tau_decay = 2.
g_max = .10
E = 0.
@bp.integrate
def ints(s, t):
return - s / tau_decay
ST=bp.types.SynState(['s', 'g'], help='AMPA synapse state.')
pre=bp.types.NeuState(['spike'], help='Presynaptic neuron state must have "sp" item.')
post=bp.types.NeuState(['V', 'input'], help='Presynaptic neuron state must have "V" and "inp" item.')
```

We also need to define a connectivity matrix to specify the connectivity patterns between the presynaptic neurons and postsynaptic neurons, which can be defined with `brainpy.types.MatConn()`

.

```
[10]:
```

```
conn_mat=bp.types.MatConn()
```

Here is an example of the connectivity matrix:

The update and output are also similar to the scalar mode, but notice that the `pre`

and `post`

here are vectors, so all the operations are vectors.

```
[11]:
```

```
def update(ST, _t, pre, conn_mat):
s = ints(ST['s'], _t)
s += pre['spike'].reshape((-1, 1)) * conn_mat
ST['s'] = s
ST['g'] = g_max * s
@bp.delayed
def output(ST, post):
g = np.sum(ST['g'], axis=0)
post['input'] -= g * (post['V'] - E)
AMPA = bp.SynType(name='AMPA_synapse',
ST=ST,
requires=dict(pre=pre, post=post, conn_mat=conn_mat),
steps=(update, output),
mode='matrix')
```

### Vector mode¶

In vector mode, each item in the synapse state `ST`

is a vector.

Let’s look at the synaptic connections in vector form.

#### Synaptic connectivity¶

Suppose we have two vectors of neurons and a vector of synapses connecting the neurons within the two neuron vectors. Many different connectivities are possible, and we use \(index\) to recognize different synapses.

Each synapse receives information from one presynaptic neuron, and, commonly, different synapses get inconsistent signals. Therefore, it is helpful to specify a map from the presynaptic neuron vector to the synapses vector.

For example, we have a connectivity as below:

Where 1 and 0 indicate the presence and absence of synaptic connections, respectively. We can then arrange the synapses in the following manner:

We can create a `pre2syn`

list, the indexes of this list correspond to the indexes of the presynaptic neurons vector, and the elements indicate the indexes of synapses that having connections to the neuron. Here, the first neuron connects the 3rd, 5th, and 7th neurons with synapses 0, 1, 2, so we store [0, 1, 2] as the first element of the `pre2syn`

list. Thus, if the first neuron fire, then we can get the indexes of synapses by `syn_ids = pre2syn[0]`

and changes the states of those
synapses.

Similarly, we can use a `post2syn`

list to indicate the connections between synapses and postsynaptic neurons. The indexes of this list correspond to the indexes of the presynaptic neurons vector, and the elements indicate the indexes of synapses that having connections to the neuron.

We can also create a map between two neurons vectors using a `pre2post`

list and a `post2pre`

list.

Other mapping ways are also possible.

### Example: AMPA synapse model (vector mode)¶

Now let’s see how to implement a vector-based synapses model by taking AMPA model as example. The formal equations of an AMPA synapse is the same as the scalar-based one:

where \(\bar{g}_{syn}\) is the maximum synaptic conductance, \(s\) is the gating variable, and \(V\) is the membrane potential of the postsynaptic neuron. The time constant \(\tau_{decay}\) is about 2ms and the equilibrium potential \(E_{syn}\) for AMPA synapse is 0.

The differential equation part is the same as the scalar and matrix mode, and we also need a `SynState`

and the `NeuState`

of presynaptic and postsynaptic neurons.

```
[12]:
```

```
tau_decay = 2.
g_max = .10
E = 0.
@bp.integrate
def ints(s, t):
return - s / tau_decay
ST=bp.types.SynState(['s'], help='AMPA synapse state.')
pre=bp.types.NeuState(['spike'], help='Presynaptic neuron state must have "sp" item.')
post=bp.types.NeuState(['V', 'input'], help='Presynaptic neuron state must have "V" and "inp" item.')
```

For the mapping between synapse and neurons, BrainPy provides `brainpy.types.ListConn`

.

```
[13]:
```

```
pre2syn = bp.types.ListConn()
post2syn = bp.types.ListConn()
```

Assume the items in the synapse state `ST`

and neuron states `pre`

and `post`

are vectors, and we have the mapping lists `pre2syn`

and `post2syn`

, the update logic of vector-based AMPA synapse model is:

```
[14]:
```

```
def update(ST, _t, pre, pre2syn):
s = ints(ST['s'], _t)
spikeike_idx = np.where(pre['spike'] > 0.)[0]
for i in spikeike_idx:
syn_idx = pre2syn[i]
s[syn_idx] += 1.
# update values
ST['s'] = s
@bp.delayed
def output(ST, post, post2syn):
post_cond = np.zeros(len(post2syn), dtype=np.float_)
for post_id, syn_ids in enumerate(post2syn):
post_cond[post_id] = np.sum(g_max * ST['s'][syn_ids])
post['input'] -= post_cond * (post['V'] - E)
AMPA_vector = bp.SynType(name='AMPA_synapse',
ST=ST,
requires=dict(pre=pre, post=post,
pre2syn=pre2syn, post2syn=post2syn),
steps=(update, output),
mode='vector')
```

## brainpy.SynConn¶

Synapse connections determine the architecture of a network. A `brainpy.SynConn`

receives the following parameters:

`model`

: The synapse type will be used to generate a synapse connection.`pre_group`

: The presynaptic neuron group.`post_group`

: The postsynaptic neuron group.`conn`

: The connection method to create synaptic connectivity between the neuron groups.`monitors`

: The items to monitor (record the history values.)`delay`

: The time of the synapse delay (in milliseconds).

BrainPy pre-defines several commonly used connection methods in `brainpy.connect`

, read Usage of connect module for more details.

Let’s take our defined AMPA model as an exmaple.

We can get pre-defined neuron models from the `bpmodels`

package. Here we use the leaky intergrate-and-fire (LIF) model to create neuron groups

```
[15]:
```

```
from bpmodels.neurons import get_LIF
LIF = get_LIF(V_rest=-65., V_reset=-65., V_th=-55.)
pre = bp.NeuGroup(LIF, 1, monitors=['spike', 'V'])
pre.ST['V'] = -65.
post = bp.NeuGroup(LIF, 1, monitors=['V'])
post.ST['V'] = -65.
```

```
[16]:
```

```
syn = bp.SynConn(model=AMPA, pre_group=pre, post_group=post,
conn=bp.connect.All2All(),
monitors=['s'], delay=1.5)
```

You can specify the synapse behavior by using `syn.runner.set_schedule`

.

```
[17]:
```

```
syn.runner.set_schedule(['input', 'update', 'output', 'monitor'])
```

Note that you cannot run the synapse connection (unlike neuron groups). You have to run them in a network.

```
[18]:
```

```
net = bp.Network(pre, syn, post)
net.run(duration=100., inputs=(pre, "ST.input", 20.))
```