{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Synaptic Connections\n",
"\n",
"[![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/brainpy/brainpy/blob/master/docs/tutorial_toolbox/synaptic_connections.ipynb)\n",
"[![Open in Kaggle](https://kaggle.com/static/images/open-in-kaggle.svg)](https://kaggle.com/kernels/welcome?src=https://github.com/brainpy/brainpy/blob/master/docs/tutorial_toolbox/synaptic_connections.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"@[Tianqiu Zhang](mailto:tianqiuakita@gmail.com)\n",
"@[Xiaoyu Chen](mailto:c-xy17@tsinghua.org.cn) \n",
"@[Sichao He](mailto:20301038@bjtu.edu.cn)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Synaptic connections is an essential part for building a neural dynamic system. BrainPy provides several commonly used connection methods in the [brainpy.connect](../apis/auto/building/connect.rst) module (which can be accessed by the shortcut `bp.conn`) that can help users to easily construct many types of synaptic connection, inclulding built-in and self-customized connectors."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## An Overview of BrainPy Connectors"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we provide an overview of BrainPy connectors. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Connector.require()\n",
"This method returns the connection properties required by users. The connection properties are elaborated in the following sections in detail. Here is a brief summary of the connection properties users can require.\n",
"\n",
"| Connection properties | Structure | Definition | \n",
"| :- | :- | :- |\n",
"| `conn_mat` | 2-D array (matrix) | Dense connection matrix | \n",
"| `pre_ids` | 1-D array (vector) | Indices of the pre-synaptic neuron group |\n",
"| `post_ids` | 1-D array (vector) | Indices of the post-synaptic neuron group |\n",
"| `pre2post` | tuple (vector, vector) | The post-synaptic neuron indices and the corresponding pre-synaptic neuron pointers | \n",
"| `post2pre` | tuple (vector, vector) | The pre-synaptic neuron indices and the corresponding post-synaptic neuron pointers |\n",
"| `pre2syn` | tuple (vector, vector) | The synapse indices sorted by pre-synaptic neurons and corresponding pre-synaptic neuron pointers | \n",
"| `post2syn` | tuple (vector, vector) | The synapse indices sorted by post-synaptic neurons and corresponding post-synaptic neuron pointers |"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Users can implement this method by following sentence:\n",
"```python\n",
"pre_ids, post_ids, pre2post, conn_mat = conn.require('pre_ids', 'post_ids', 'pre2post', 'conn_mat')\n",
"```\n",
"\n",
"```{note}\n",
"Note that this method can return multiple connection properties.\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Connection Properties"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"There are multiple connection properties that can be required by users."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 1. `conn_mat`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The matrix-based synaptic connection is one of the most intuitive ways to build synaptic computations. The connection matrix between two neuron groups can be easily obtained through the function of `connector.requires('conn_mat')`. Each connection matrix is an array with the shape of $(n_{pre}, n_{post})$:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 2. `pre_ids` and `post_ids`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using *vectors* to store the connection between neuron groups is a much more efficient way to reduce memory when the connection matrix is sparse. For the connction matrix `conn_mat` defined above, we can align the connected pre-synaptic neurons and the post-synaptic neurons by two one-dimensional arrays: *pre_ids* and *post_ids*."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this way, we only need two vectors (`pre_ids` and `post_ids`) to store the synaptic connection. `syn_id` in the figure indicates the indices of each neuron pair, i.e. each synapse."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 3. `pre2post` and `post2pre`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Another two synaptic structures are `pre2post` and `post2pre`. They establish the mapping between the pre- and post-synaptic neurons.\n",
"\n",
"`pre2post` is a tuple containing two vectors, one of which is the post-synaptic neuron indices and the other is the corresponding pre-synaptic neuron pointers. For example, the following figure shows the indices of the pre-synaptic neurons and the post-synaptic neurons to which the pre-synaptic neurons project:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To record the connection, firstly the post_ids are concatenated as a single vector call the **post-synaptic index vector (indices)**. Because the post-synaptic neuron indices have been sorted by the pre-synaptic neuron indices, it is sufficient to record only the starting position of each pre-synaptic neuron index. Therefore, the pre-synaptic neuron indices and the end of the last pre-synaptic neuron index together make up the **pre-synaptic index pointer vector (indptr)**, which is illustrated in the figure below."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The post-synaptic neuron indices to which pre-synaptic neuron $i$ projects can be obtained by array slicing:\n",
"\n",
"```python\n",
"indices[indptr[i], indptr[i+1]]\n",
"```\n",
"\n",
"Similarly, `post2pre` is a 2-element tuple containing the pre-synaptic neuron indices and the corresponding post-synaptic neuron pointers. Taking the connection in the illutration aobve as an example, the post-synaptic neuron indices and the pre-synaptic neuron indices to which the post-synaptic neurons project is shown as:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The **pre-synaptic index vector (indices)** and the **post-synaptic index pointer vector (indptr)** are listed below:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When the connection is sparse, `pre2post` (or `post2pre`) is a very efficient way to store the connection, since the lengths of the two vectors in the tuple are $n_{synapse}$ and $n_{pre}$ ($n_{post}$), respectively."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 4. `pre2syn` and `post2syn`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The last two properties are `pre2syn` and `post2syn` that record pre- and post-synaptic projection, respectively. \n",
"\n",
"For **`pre2syn`**, similar to `pre2post` and `post2pre`, there is a **synapse index vector** and a **pre-synaptic index pointer vector** that refers to the starting position of each pre-synaptic neuron index at the synapse index vector.\n",
"\n",
"Below is the same example identifying the connection by pre-synaptic neuron indices and the synapses belonging to them."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For better understanding, The synapse indices, pre- and post-synaptic neuron indices are shown as below:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The **pre-synaptic index pointer vector** is computed in the same way as in `pre2post`:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similarly, **`post2syn`** is a also tuple containing the synapse neuron indices and the corresponding post-synaptic neuron pointers."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The only different from `pre2syn` is that the synapse indices is (most of the time) originally sorted by pre-synaptic neurons, but when computing `post2syn`, synapses should be sorted by post-synaptic neuron indices:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The **synapse index vector** (the first row) and the **post-synaptic index pointer vector** (the last row) are listed below:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.088624Z",
"start_time": "2023-04-15T20:16:14.589294Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"'2.4.0'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import brainpy as bp\n",
"import brainpy.math as bm\n",
"\n",
"bp.math.set_platform('cpu')\n",
"\n",
"bp.__version__"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.506149Z",
"start_time": "2023-04-15T20:16:14.599858Z"
}
},
"outputs": [],
"source": [
"import networkx as nx\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Built-in regular connections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.One2One\n",
"\n",
"The neurons in the pre-synaptic neuron group only connect to the neurons\n",
"in the same position of the post-synaptic group. Thus, this connection\n",
"requires the indices of two neuron groups same. Otherwise, an error will\n",
"occurs.\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.506149Z",
"start_time": "2023-04-15T20:16:14.912523Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"One2One"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conn = bp.connect.One2One()\n",
"conn(pre_size=10, post_size=10)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"where `pre_size` denotes the size of the pre-synaptic neuron group, `post_size` denotes the size of the post-synaptic neuron group.\n",
"Note that parameter `size` can be *int*, *tuple of int* or *list of int* where each element represent each dimension of neuron group.\n",
"\n",
"In One2One connection, particularly, `pre_size` and `post_size` must be the same."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Class `One2One` is inherited from `TwoEndConnector`. Users can use method `require` or `requires` to get specific connection properties.\n",
"\n",
"Here is an example:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.537839Z",
"start_time": "2023-04-15T20:16:14.927742Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"pre_ids: [0 1 2 3 4]\n",
"post_ids: [0 1 2 3 4]\n",
"pre2post: (DeviceArray([0, 1, 2, 3, 4], dtype=int32), DeviceArray([0, 1, 2, 3, 4, 5], dtype=int32))\n"
]
}
],
"source": [
"size = 5\n",
"conn = bp.connect.One2One()(pre_size=size, post_size=size)\n",
"res = conn.require('pre_ids', 'post_ids', 'pre2post', 'conn_mat')\n",
"\n",
"print('pre_ids:', res[0])\n",
"print('post_ids:', res[1])\n",
"print('pre2post:', res[2])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.All2All\n",
"\n",
"All neurons of the post-synaptic population form connections with all\n",
"neurons of the pre-synaptic population (dense connectivity). Users can\n",
"choose whether connect the neurons at the same position\n",
"(`include_self=True or False`).\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.573936Z",
"start_time": "2023-04-15T20:16:14.942497Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"All2All(include_self=False)"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conn = bp.connect.All2All(include_self=False)\n",
"conn(pre_size=size, post_size=size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Class `All2All` is inherited from `TwoEndConnector`. Users can use method `require` or `requires` to get specific connection properties.\n",
"\n",
"Here is an example:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.604712Z",
"start_time": "2023-04-15T20:16:14.958032Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pre_ids: [0 0 0 0 1 1 1 1 2 2 2 2 3 3 3 3 4 4 4 4]\n",
"post_ids: [1 2 3 4 0 2 3 4 0 1 3 4 0 1 2 4 0 1 2 3]\n",
"pre2post: (DeviceArray([1, 2, 3, 4, 0, 2, 3, 4, 4, 3, 1, 0, 0, 1, 2, 4, 0, 1, 2, 3], dtype=int32), DeviceArray([ 0, 4, 8, 12, 16, 20], dtype=int32))\n",
"conn_mat: [[False True True True True]\n",
" [ True False True True True]\n",
" [ True True False True True]\n",
" [ True True True False True]\n",
" [ True True True True False]]\n"
]
}
],
"source": [
"conn = bp.connect.All2All(include_self=False)(pre_size=size, post_size=size)\n",
"res = conn.require('pre_ids', 'post_ids', 'pre2post', 'conn_mat')\n",
"\n",
"print('pre_ids:', res[0])\n",
"print('post_ids:', res[1])\n",
"print('pre2post:', res[2])\n",
"print('conn_mat:', res[3])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.GridFour\n",
"\n",
"`GridFour` is the four nearest neighbors connection. Each neuron connect to its\n",
"nearest four neurons.\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.616454Z",
"start_time": "2023-04-15T20:16:14.973548Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"GridFour(include_self=False, periodic_boundary=False)"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"conn = bp.connect.GridFour(include_self=False)\n",
"conn(pre_size=size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Class `GridFour` is inherited from `OneEndConnector`, therefore there is only one parameter `pre_size` representing the size of neuron group, which should be **two-dimensional geometry**.\n",
"\n",
"Here is an example:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"ExecuteTime": {
"end_time": "2023-04-15T20:16:15.776501Z",
"start_time": "2023-04-15T20:16:14.990563Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"pre_ids [ 0 0 1 1 1 2 2 2 3 3 4 4 4 5 5 5 5 6 6 6 6 7 7 7\n",
" 8 8 8 9 9 9 9 10 10 10 10 11 11 11 12 12 13 13 13 14 14 14 15 15]\n"
]
}
],
"source": [
"size = (4, 4)\n",
"conn = bp.connect.GridFour(include_self=False)(pre_size=size)\n",
"res = conn.require('pre_ids', 'conn_mat')\n",
"\n",
"print('pre_ids', res[0])"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"ename": "AttributeError",
"evalue": "module 'networkx' has no attribute 'from_numpy_matrix'",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[11], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;66;03m# Using NetworkX to visualize network connection\u001b[39;00m\n\u001b[1;32m----> 2\u001b[0m G \u001b[38;5;241m=\u001b[39m \u001b[43mnx\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfrom_numpy_matrix\u001b[49m(res[\u001b[38;5;241m1\u001b[39m])\n\u001b[0;32m 3\u001b[0m nx\u001b[38;5;241m.\u001b[39mdraw(G, with_labels\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[0;32m 4\u001b[0m plt\u001b[38;5;241m.\u001b[39mshow()\n",
"\u001b[1;31mAttributeError\u001b[0m: module 'networkx' has no attribute 'from_numpy_matrix'"
]
}
],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(res[1])\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.GridEight\n",
"\n",
"`GridEight` is eight nearest neighbors connection. Each neuron connect to its\n",
"nearest eight neurons.\n",
"\n",
""
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.GridEight(include_self=False)\n",
"conn(pre_size=size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Class `GridEight` is inherited from `GridN`, which will be introduced as followed.\n",
"\n",
"Here is an example:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"size = (4, 4)\n",
"conn = bp.connect.GridEight(include_self=False)(pre_size=size)\n",
"res = conn.require('pre_ids', 'conn_mat')\n",
"\n",
"print('pre_ids', res[0])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Take the central point (id = 4) as an example, its neighbors are all the other point except itself.\n",
"Therefore, its row in `conn_mat` has `True` for all values except itself."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(res[1])\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.GridN\n",
"\n",
"`GridN` is also a nearest neighbors connection. Each neuron connect to its\n",
"nearest $(2N+1) \\cdot (2N+1)$ neurons (if including itself).\n",
"\n",
"\n",
"\n",
"Here are some examples to fully understand `GridN`. It is slightly different from `GridEight`: `GridEight` is equivalent to `GridN` when N = 1.\n",
"\n",
"- When N = 1:\n",
" $\\begin{bmatrix}\n",
" x & x & x\\\\\n",
" x & I & x\\\\\n",
" x & x & x\n",
" \\end{bmatrix}$\n",
"\n",
"- When N = 2:\n",
" $ \\begin{bmatrix}\n",
" x & x & x & x & x\\\\\n",
" x & x & x & x & x\\\\\n",
" x & x & I & x & x\\\\\n",
" x & x & x & x & x\\\\\n",
" x & x & x & x & x\n",
" \\end{bmatrix} $"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.GridN(N=2, include_self=False)\n",
"conn(pre_size=size)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here is an example:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"size = (4, 4)\n",
"conn = bp.connect.GridN(N=1, include_self=False)(pre_size=size)\n",
"res = conn.require('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(res)\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Built-in random connections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.FixedProb\n",
"\n",
"For each post-synaptic neuron, there is a fixed probability that it forms a connection\n",
"with a neuron of the pre-synaptic population. It is basically a all_to_all projection,\n",
"except some synapses are not created, making the projection sparser.\n",
"\n",
"\n",
"\n",
"Class `brainpy.connect.FixedProb` is inherited from `TwoEndConnector`, and it receives three settings:\n",
"- `prob`: Fixed probability for connection with a pre-synaptic neuron for each post-synaptic neuron.\n",
"- `pre_ratio`: The ratio of pre-synaptic neurons to connect.\n",
"- `include_self`: Whether connect to inself.\n",
"- `allow_multi_conn`: Whether allow one pre-synaptic neuron connects to multiple post-synaptic neurons.\n",
"- `seed`: Seed the random generator.\n",
"\n",
"And there are two parameters passed in for calling instance of class: `pre_size` and `post_size`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.FixedProb(prob=0.5, include_self=False, seed=134)\n",
"conn(pre_size=4, post_size=4)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.FixedPreNum\n",
"\n",
"Each neuron in the post-synaptic population receives connections from a\n",
"fixed number of neurons of the pre-synaptic population chosen randomly.\n",
"It may happen that two post-synaptic neurons are connected to the same\n",
"pre-synaptic neuron and that some pre-synaptic neurons are connected to\n",
"nothing.\n",
"\n",
"\n",
"\n",
"Class `brainpy.connect.FixedPreNum` is inherited from `TwoEndConnector`, and it receives three settings:\n",
"- `num`: The conn probability (if \"num\" is float) or the fixed number of connectivity (if \"num\" is int).\n",
"- `include_self`: Whether connect to inself.\n",
"- `allow_multi_conn`: Whether allow one pre-synaptic neuron connects to multiple post-synaptic neurons.\n",
"- `seed`: Seed the random generator.\n",
"\n",
"And there are two parameters passed in for calling instance of class: `pre_size` and `post_size`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.FixedPreNum(num=2, include_self=True, seed=1234)\n",
"conn(pre_size=4, post_size=4)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.FixedPostNum\n",
"\n",
"Each neuron in the pre-synaptic population sends a connection to a fixed number of neurons\n",
"of the post-synaptic population chosen randomly. It may happen that two pre-synaptic neurons\n",
"are connected to the same post-synaptic neuron and that some post-synaptic neurons receive\n",
"no connection at all.\n",
"\n",
"\n",
"\n",
"Class `brainpy.connect.FixedPostNum` is inherited from `TwoEndConnector`, and it receives three settings:\n",
"- `num`: The conn probability (if \"num\" is float) or the fixed number of connectivity (if \"num\" is int).\n",
"- `allow_multi_conn`: Whether allow one pre-synaptic neuron connects to multiple post-synaptic neurons.\n",
"- `include_self`: Whether connect to inself.\n",
"- `seed`: Seed the random generator.\n",
"\n",
"And there are two parameters passed in for calling instance of class: `pre_size` and `post_size`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.FixedPostNum(num=2, include_self=True, seed=1234)\n",
"conn(pre_size=4, post_size=4)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.FixedTotalNum\n",
"\n",
"Connections between pre-synaptic and post-synaptic neurons are determined by \n",
"a specified total number or propotion of connections.\n",
"\n",
"Class `brainpy.connect.FixedTotalNum` is inherited from `TwoEndConnector`, and it receives two settings:\n",
"- `num`: The total number of connections (if \"num\" is float) or the fixed number of connectivity (if \"num\" is int).\n",
"- `allow_multi_conn`: Whether allow one pre-synaptic neuron connects to multiple post-synaptic neurons.\n",
"- `seed`: Seed the random generator.\n",
"\n",
"And there are two parameters passed in for calling instance of class: `pre_size` and `post_size`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.FixedTotalNum(num=8, allow_multi_conn=False, seed=1234)\n",
"conn(pre_size=3, post_size=4)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.GaussianProb\n",
"\n",
"\n",
"Builds a Gaussian connection pattern between the two populations, where\n",
"the connection probability decay according to the gaussian function.\n",
"\n",
"Specifically,\n",
"\n",
"$$\n",
"p=\\exp\\left(-\\frac{(x-x_c)^2+(y-y_c)^2}{2\\sigma^2}\\right)\n",
"$$\n",
"\n",
"where $(x, y)$ is the position of the pre-synaptic neuron\n",
"and $(x_c,y_c)$ is the position of the post-synaptic neuron.\n",
"\n",
"For example, in a $30 \\textrm{x} 30$ two-dimensional networks, when\n",
"$\\beta = \\frac{1}{2\\sigma^2} = 0.1$, the connection pattern is shown\n",
"as the follows:\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`GaussianProb` is inherited from `OneEndConnector`, and it receives four settings:\n",
"\n",
"- `sigma`: (float) Width of the Gaussian function.\n",
"- `encoding_values`: (optional, list, tuple, int, float) The value ranges to encode for neurons at each axis.\n",
"- `periodic_boundary` : (bool) Whether the neuron encode the value space with the periodic boundary.\n",
"- `normalize`: (bool) Whether normalize the connection probability.\n",
"- `include_self` : (bool) Whether create the conn at the same position.\n",
"- `seed`: (bool) The random seed."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.GaussianProb(sigma=2, periodic_boundary=True, normalize=True, include_self=True, seed=21)\n",
"conn(pre_size=10)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(conn.require('conn_mat'))\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.ProbDist\n",
"\n",
"Establishes a distance-based connection pattern between two populations,\n",
" where the connection probability is contingent upon whether the actual \n",
" distance between neurons is less than or equal to a given dist. \n",
" If the distance between two neurons is less than or equal to dist, \n",
" they have a connection probability of prob. For pre-synaptic neurons, \n",
" only a proportion specified by pre_ratio will attempt to connect to\n",
" post-synaptic neurons.\n",
"\n",
"Specifically, Given two points, $P_1$ and $P_2$ in $n$ -dimensional space:\n",
"\n",
"$$ P_1 = (x_{11}, x_{12}, \\dots, x_{1n}) $$\n",
"$$ P_2 = (x_{21}, x_{22}, \\dots, x_{2n}) $$\n",
"\n",
"The distance $d$ between them in an $n$ -dimensional space can be computed as:\n",
"\n",
"$$ d = \\sqrt{(x_{11} - x_{21})^2 + (x_{12} - x_{22})^2 + \\dots + (x_{1n} - x_{2n})^2} $$\n",
"\n",
"In a vectorized form, this can be expressed as:\n",
"\n",
"$$ d = \\sqrt{\\sum_{i=1}^{n}(x_{1i} - x_{2i})^2} $$\n",
"\n",
"This general formula calculates the Euclidean distance between two points in \\( n \\)-dimensional space and is valid for any dimension \\( n \\).\n",
"\n",
"In the context of the provided code:\n",
"- $P_1$ would be the position of the pre-synaptic neuron.\n",
"- $P_2$ would be the position of each post-synaptic neuron being considered.\n",
"\n",
"---\n",
"\n",
"The decision to connect the neurons is then based on:\n",
"1. The distance $d$ should be less than or equal to `dist`.\n",
"2. Random generation based on the `prob` parameter."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`ProbDist` is inherited from `TwoEndConnector`, and it receives four settings:\n",
"\n",
"- `dist`: (float, int) The maximum distance between two points.\n",
"- `prob`: (float) The connection probability, within 0. and 1.\n",
"- `pre_ratio`: (float) The ratio of pre-synaptic neurons to connect.\n",
"- `seed`: (optional, int) The random seed.\n",
"- `include_self`: Whether include the point at the same position."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.ProbDist(dist=10, prob=0.5, pre_ratio=0.5, seed=1234, include_self=True)\n",
"conn(pre_size=100, post_size=100)\n",
"mat = conn.require(\"conn_mat\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.SmallWorld"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`SmallWorld` is a connector class to help build a [small-world network](https://en.wikipedia.org/wiki/Small-world_network) [1]. small-world network is defined to be a network where the typical distance L between two randomly chosen nodes (the number of steps required) grows proportionally to the logarithm of the number of nodes N in the network, that is:\n",
"\n",
"$$\n",
"L\\propto \\log N\n",
"$$\n",
"\n",
"[1] Duncan J. Watts and Steven H. Strogatz, Collective dynamics of small-world networks, Nature, 393, pp. 440–442, 1998."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Currently, `SmallWorld` only support a one-dimensional network with the ring structure. It receives four settings:\n",
"\n",
"- `num_neighbor`: the number of the nearest neighbors to connect.\n",
"- `prob`: the probability of rewiring each edge.\n",
"- `directed`: whether the edge is the directed (\"directed=True\") or undirected (\"directed=False\") connection.\n",
"- `include_self`: whether allow to connect to itself."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.SmallWorld(num_neighbor=5, prob=0.2, directed=False, include_self=False)\n",
"conn(pre_size=10, post_size=10)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(conn.require('conn_mat'))\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.ScaleFreeBA"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`ScaleFreeBA` is a connector class to help build a random scale-free network according to the [Barabási–Albert preferential attachment model](https://en.wikipedia.org/wiki/Barab%C3%A1si%E2%80%93Albert_model) [2]. `ScaleFreeBA` receives the following settings:\n",
"\n",
"- `m`: Number of edges to attach from a new node to existing nodes.\n",
"- `directed`: whether the edge is the directed (\"directed=True\") or undirected (\"directed=False\") connection.\n",
"- `seed`: Indicator of random number generation state.\n",
"\n",
"[2] A. L. Barabási and R. Albert “Emergence of scaling in random networks”, Science 286, pp 509-512, 1999."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.ScaleFreeBA(m=5, directed=False, seed=12345)\n",
"conn(pre_size=10, post_size=10)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Using NetworkX to visualize network connection\n",
"G = nx.from_numpy_matrix(conn.require('conn_mat'))\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.ScaleFreeBADual"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`ScaleFreeBADual` is a connector class to help build a random scale-free network according to the dual Barabási–Albert preferential attachment model [3]. ScaleFreeBA receives the following settings:\n",
"\n",
"- `p`: The probability of attaching $m_1$ edges (as opposed to $m_2$ edges).\n",
"- `m1` : Number of edges to attach from a new node to existing nodes with probability $p$.\n",
"- `m2`: Number of edges to attach from a new node to existing nodes with probability $1-p$. \n",
"- `directed`: whether the edge is the directed (\"directed=True\") or undirected (\"directed=False\") connection.\n",
"- `seed`: Indicator of random number generation state.\n",
"\n",
"[3] N. Moshiri. \"The dual-Barabasi-Albert model\", arXiv:1810.10538."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.ScaleFreeBADual(m1=3, m2=5, p=0.5, directed=False, seed=12345)\n",
"conn(pre_size=10, post_size=10)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### brainpy.connect.PowerLaw"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"`PowerLaw` is a connector class to help build a random graph with powerlaw degree distribution and approximate average clustering [4]. It receives the following settings:\n",
"\n",
"- `m` : the number of random edges to add for each new node\n",
"- `p` : Probability of adding a triangle after adding a random edge\n",
"- `directed`: whether the edge is the directed (\"directed=True\") or undirected (\"directed=False\") connection.\n",
"- `seed` : Indicator of random number generation state.\n",
"\n",
"[4] P. Holme and B. J. Kim, “Growing scale-free networks with tunable clustering”, Phys. Rev. E, 65, 026107, 2002."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.PowerLaw(m=3, p=0.5, directed=False, seed=12345)\n",
"conn(pre_size=10, post_size=10)\n",
"conn.require('conn_mat')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Encapsulate your existing connections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"BrainPy also allows users to encapsulate existing connections with convenient class interfaces. Users can provide connection types as:\n",
"- Index projection;\n",
"- Dense matrix;\n",
"- Sparse matrix.\n",
"\n",
"Then users should provide `pre_size` and `post_size` information in order to instantiate the connection. In such a way, based on the following connection classes, users can generate any other synaptic structures (such like `pre2post`, `pre2syn`, `conn_mat`, etc.) easily."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `bp.conn.IJConn`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, let’s take a simple connection as an example. In this example, we create a connection which receives users’ handful index projection by using `bp.conn.IJConn`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pre_list = np.array([0, 1, 2])\n",
"post_list = np.array(([0, 0, 0]))\n",
"conn = bp.conn.IJConn(i=pre_list, j=post_list)\n",
"conn = conn(pre_size=5, post_size=3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('pre2post')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"conn.requires('pre2syn')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `bp.conn.MatConn`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In next example, we create a connection which receives user's handful dense connection matrix by using `bp.conn.MatConn`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"bp.math.random.seed(123)\n",
"conn = bp.connect.MatConn(conn_mat=np.random.randint(2, size=(5, 3), dtype=bp.math.bool_))\n",
"conn = conn(pre_size=5, post_size=3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('pre2post')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.require('pre2syn')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### `bp.conn.SparseMatConn`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In last example, we create a connection which receives user's handful sparse connection matrix by using `bp.conn.sparseMatConn`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from scipy.sparse import csr_matrix\n",
"\n",
"conn_mat = np.random.randint(2, size=(5, 3), dtype=bp.math.bool_)\n",
"sparse_mat = csr_matrix(conn_mat)\n",
"conn = bp.conn.SparseMatConn(sparse_mat)\n",
"conn = conn(pre_size=sparse_mat.shape[0], post_size=sparse_mat.shape[1])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('conn_mat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('pre2post')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn.requires('post2syn')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using NetworkX to provide connections and pass into `Connector`\n",
"\n",
"NetworkX is a Python package for the creation, manipulation, and study of the structure, dynamics, and functions of complex networks.\n",
"\n",
"Users can design their own complex netork by using `NetworkX`.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import networkx as nx\n",
"G = nx.Graph()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"By definition, a `Graph` is a collection of nodes (vertices) along with identified pairs of nodes (called edges, links, etc).\n",
"\n",
"To learn more about `NetowrkX`, please check the official documentation: [NetworkX tutorial](https://networkx.org/documentation/stable/tutorial.html)\n",
"\n",
"Using class [`brainpy.connect.MatConn`](https://brainpy.readthedocs.io/en/latest/apis/simulation/generated/brainpy.simulation.connect.MatConn.html) to construct connections is recommended here.\n",
"- Dense adjacency matrix: a two-dimensional ndarray.\n",
"\n",
"Here gives an example to illustrate how to transform a random graph into your synaptic connections by using dense adjacency matrix."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"G = nx.fast_gnp_random_graph(5, 0.5) # initialize a random graph G\n",
"B = nx.adjacency_matrix(G)\n",
"A = np.array(nx.adjacency_matrix(G).todense()) # get dense adjacency matrix of G\n",
"\n",
"print('dense adjacency matrix:')\n",
"print(A)\n",
"nx.draw(G, with_labels=True)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Users can use class `MatConn` inherited from `TwoEndConnector` to construct connections. **A dense adjacency matrix** should be passed in when initializing `MatConn` class.\n",
"Note that when calling the instance of the class, users should pass in two parameters: `pre_size` and `post_size`.\n",
"In this case, users can use the shape of dense adjacency matrix as the parameters."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = bp.connect.MatConn(A)(pre_size=A.shape[0], post_size=A.shape[1])\n",
"res = conn.require('conn_mat')\n",
"\n",
"print(res)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Customize your connections"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"BrainPy allows users to customize their connections. The following requirements should be satisfied:\n",
"\n",
"- Your connection class should inherit from `brainpy.connect.TwoEndConnector` or `brainpy.connect.OneEndConnector`.\n",
"- `__init__` function should be implemented and essential parameters should be initialized.\n",
"- Users should also overwrite `build_csr()`, `build_coo()` or `build_mat()` function to describe how to build your connection.\n",
"\n",
"Let's take an example to illustrate the details of customization."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class FixedProb(bp.connect.TwoEndConnector):\n",
" \"\"\"Connect the post-synaptic neurons with fixed probability.\n",
"\n",
" Parameters\n",
" ----------\n",
" prob : float\n",
" The conn probability.\n",
" include_self : bool\n",
" Whether to create (i, i) connection.\n",
" seed : optional, int\n",
" Seed the random generator.\n",
" \"\"\"\n",
"\n",
" def __init__(self, prob, include_self=True, seed=None):\n",
" super(FixedProb, self).__init__()\n",
" assert 0. <= prob <= 1.\n",
" self.prob = prob\n",
" self.include_self = include_self\n",
" self.seed = seed\n",
" self.rng = np.random.RandomState(seed=seed)\n",
"\n",
" def build_csr(self):\n",
" ind = []\n",
" count = np.zeros(self.pre_num, dtype=np.uint32)\n",
"\n",
" def _random_prob_conn(rng, pre_i, num_post, prob, include_self):\n",
" p = rng.random(num_post) <= prob\n",
" if (not include_self) and pre_i < num_post:\n",
" p[pre_i] = False\n",
" conn_j = np.asarray(np.where(p)[0], dtype=np.uint32)\n",
" return conn_j\n",
"\n",
" for i in range(self.pre_num):\n",
" posts = _random_prob_conn(self.rng, pre_i=i, num_post=self.post_num,\n",
" prob=self.prob, include_self=self.include_self)\n",
" ind.append(posts)\n",
" count[i] = len(posts)\n",
"\n",
" ind = np.concatenate(ind)\n",
" indptr = np.concatenate(([0], count)).cumsum()\n",
"\n",
" return ind, indptr"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then users can initialize the your own connections as below:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"conn = FixedProb(prob=0.5, include_self=True)(pre_size=5, post_size=5)\n",
"conn.require('conn_mat')"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
},
"latex_envs": {
"LaTeX_envs_menu_present": true,
"autoclose": false,
"autocomplete": true,
"bibliofile": "biblio.bib",
"cite_by": "apalike",
"current_citInitial": 1,
"eqLabelWithNumbers": true,
"eqNumInitial": 1,
"hotkeys": {
"equation": "Ctrl-E",
"itemize": "Ctrl-I"
},
"labels_anchors": false,
"latex_user_defs": false,
"report_style_numbering": false,
"user_envs_cfg": false
},
"toc": {
"base_numbering": 1,
"nav_menu": {
"height": "411px",
"width": "316px"
},
"number_sections": false,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "243.068px"
},
"toc_section_display": true,
"toc_window_display": true
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 4
}