{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# BMTK Builder (A Quick Introduction)\n",
    "\n",
    "The Brain Modeling Toolkit (bmtk) was designed to handle large-scale network simulations with pre-set connectivity matrices. Whereas other simulation tools will build and simulate a network in a single script, the bmtk splits up these two processes by saving networks to a file. The advantages of doing it this way includes:\n",
    "* Significantly faster when running multiple simulations on the same network.\n",
    "* Easy to update and adjust parameters with little-to-no programming required.\n",
    "* Improves reproducability of simulations.\n",
    "\n",
    "Before running a simulation, users should either obtain existing network model files, or as described in this tutorial use the BMTK Builder to create their own from scratch. By default, the bmtk uses the recently developed SONATA dataformat to represent networks and network parameters - for a further information please see the [SONATA documentation](https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Nodes\n",
    "\n",
    "Brain networks are represented with directed graph so every network needs nodes. (The simplest network consisting of one node and no edges, usually for single-cell simulations). The bmtk is designed to work across different levels of abstraction, so a node can represent a single biophysically detailed cell, a point-cell model, a population of cells, or even an entire brain region. \n",
    "\n",
    "To create our node(s) we use the NetworkBuilder class in bmtk.builder, then simply build and save the network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bmtk.builder import NetworkBuilder\n",
    "\n",
    "# Initialize our network\n",
    "net = NetworkBuilder(\"mcortex\")\n",
    "\n",
    "# Add a population of 10 nodes (all of which share model_type, dynamics_params, etc.)\n",
    "net.add_nodes(N=10, pop_name='Scnn1a',\n",
    "              mem_potential='e',\n",
    "              model_type='biophysical',\n",
    "              model_template='ctdb:Biophys1.hoc',\n",
    "              model_processing='aibs_perisomatic',\n",
    "              dynamics_params='472363762_fit.json',\n",
    "              morphology='Scnn1a_473845048_m.swc')\n",
    "\n",
    "# If needed we can add more populations\n",
    "# net.add_nodes(N, ...)\n",
    "\n",
    "# Builds the network files\n",
    "net.build()\n",
    "\n",
    "# Save the network into the specificed directory\n",
    "net.save_nodes(output_dir='network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When the NetworkBuilder is instantiated we pass in a name, in this case calling it \"**mcortex**\" because we will be using mouse-cortex models - But you can use any name you want. Just be careful, as often a complete simulation will contain multiple networks (the bmtk/SONATA was designed largely to allow different parts of the network to be built indepenently), so having descriptive naming convention is important."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The add_nodes method is then used to add nodes to the network. The first parameter, **N=10**, indicates that we are adding 10 individual nodes each sharing the same pop_name, mem_potential, model_type etc.\n",
    "\n",
    "All of the other parameters are completely dependent on the type of network we are looking to build. In this case we want to build a network that runs in BioNet so the parameters are carefully choosen with:\n",
    "* *__pop_name__*, *__mem_potential__* - optional parameters, not directly used by BioNet but will be helpfull in the descripion\n",
    "* *__model_type__*, *__model_template__*, *__model_processing__* - [Attributes used by BioNet](https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md#nodes---required-attributes) as instructions on how to build our NEURON-based cell models. All our cells are biophysically-detailed models, using customized templates and functions to build each cell.\n",
    "* *__dynamics_params__*, *__morphology__* - Indicates to BioNet the electrophysiological and morphology files used to build each cell. These files can be downloaded from the [Allen Cell-Types Database](http://celltypes.brain-map.org/data).\n",
    "\n",
    "However the NetworkBuilder is simulator agnositc allowing modelers to choose whatever parameters they need depending on simulator and/or required to describe the models. For example the following could be used by another simulator to build a network describing 100 Izhikevich point neurons. Notice we no longer need parameters like morphology or model_processing, but have new parameter a, b, c, and d which would be required by any Izhikevich neuron model.\n",
    "```python\n",
    "net.add_nodes(N=100, \n",
    "              model_type='point_process',\n",
    "              model_template='nrn:Izhikevich.hoc',\n",
    "              param_a=0.05, param_b=0.25, param_c=-55.0, parm_d=10)\n",
    "```\n",
    "                    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally the network is built and saved into the _network/_ folder. When we look in the network folder there are two different files - mcortex_nodes.h5 and mcortex_node_types.csv. The individual cells are stored in the \\*nodes.h5 file. But properties that are shared by a group of nodes are stored in the \\*node_types.csv. **Node types** not only makes the format more compact and faster to read (probably not important for 10 cells, but very important when trying to run a simulation of 100K+ cells), and it also makes easier to change properties (like updating ephys params or using a different morphology) between simulations.\n",
    "\n",
    "Looking at the files"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mcortex_nodes.h5\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "
\n",
       "\n",
       "
\n",
       "  
\n",
       "    \n",
       "       \n",
       "       node_id\n",
       "       node_type_id\n",
       "    
\n",
       "  
\n",
       "  
\n",
       "    \n",
       "       0\n",
       "       0\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       1\n",
       "       1\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       2\n",
       "       2\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       3\n",
       "       3\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       4\n",
       "       4\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       5\n",
       "       5\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       6\n",
       "       6\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       7\n",
       "       7\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       8\n",
       "       8\n",
       "       100\n",
       "    
\n",
       "    \n",
       "       9\n",
       "       9\n",
       "       100\n",
       "    
\n",
       "  
\n",
       "
\n",
       "
"
      ],
      "text/plain": [
       "   node_id  node_type_id\n",
       "0        0           100\n",
       "1        1           100\n",
       "2        2           100\n",
       "3        3           100\n",
       "4        4           100\n",
       "5        5           100\n",
       "6        6           100\n",
       "7        7           100\n",
       "8        8           100\n",
       "9        9           100"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.analyzer import nodes_table\n",
    "print('mcortex_nodes.h5')\n",
    "nodes_table(nodes_file='network/mcortex_nodes.h5', population='mcortex')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mcortex_node_types.h5\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "
\n",
       "\n",
       "
\n",
       "  
\n",
       "    \n",
       "       \n",
       "       node_type_id\n",
       "       dynamics_params\n",
       "       model_type\n",
       "       model_processing\n",
       "       pop_name\n",
       "       model_template\n",
       "       morphology\n",
       "       mem_potential\n",
       "    
\n",
       "  
\n",
       "  
\n",
       "    \n",
       "       0\n",
       "       100\n",
       "       472363762_fit.json\n",
       "       biophysical\n",
       "       aibs_perisomatic\n",
       "       Scnn1a\n",
       "       ctdb:Biophys1.hoc\n",
       "       Scnn1a_473845048_m.swc\n",
       "       e\n",
       "    
\n",
       "  
\n",
       "
\n",
       "
"
      ],
      "text/plain": [
       "   node_type_id     dynamics_params   model_type  model_processing pop_name  \\\n",
       "0           100  472363762_fit.json  biophysical  aibs_perisomatic   Scnn1a   \n",
       "\n",
       "      model_template              morphology mem_potential  \n",
       "0  ctdb:Biophys1.hoc  Scnn1a_473845048_m.swc             e  "
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.analyzer import node_types_table\n",
    "print('mcortex_node_types.h5')\n",
    "node_types_table(node_types_file='network/mcortex_node_types.csv', population='mcortex')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The nodes and node-types are linked together by the node_type_id foreign key. In this case all information (expect each of the 10 unique cell ids, which were autogenerated during the build processes) is stored in the node_types file because all the properties are shared among every node."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Unique node properties\n",
    "\n",
    "Suppose we have some properties that are unqiue to each individual nodes within a node-type. Instead of calling add_nodes N times, we can just pass in a list of size N. \n",
    "\n",
    "In the following example we have two types of nodes, 10 biophysical pyramidal type cells and 5 point izhikevich type cells. For the pyramidal cells we have a new parameter 'tuning_angle' which is uniquly assigned a different value to each cell. Similarly for the Izhikevich cells the param_a and param_b parameters are now unqily assigned."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "net = NetworkBuilder(\"mcortex2\")\n",
    "net.add_nodes(N=10, pop_name='pyr',\n",
    "              model_type='biophysical',\n",
    "              model_template='ctdb:Biophys1.hoc',\n",
    "              dynamics_params='pyr_ephys.json',\n",
    "              morphology='pyr_morph.swc',\n",
    "              tuning_angle=np.linspace(0.0, 360.0, num=10, endpoint=False))\n",
    "\n",
    "net.add_nodes(N=5, pop_name='izh',\n",
    "              model_type='point_process',\n",
    "              model_template='nrn:Izhikevich.hoc',\n",
    "              param_a=[0.01, 0.02, 0.03, 0.04, 0.05], \n",
    "              param_b=np.random.rand(5),\n",
    "              param_c=-55.0, \n",
    "              d=10)\n",
    "\n",
    "net.build()\n",
    "net.save_nodes(output_dir='network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now when we look at the nodes.h5 file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mcortex_nodes.h5\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "
\n",
       "\n",
       "
\n",
       "  
\n",
       "    \n",
       "       \n",
       "       node_id\n",
       "       node_type_id\n",
       "       tuning_angle\n",
       "       param_a\n",
       "       param_b\n",
       "    
\n",
       "  
\n",
       "  
\n",
       "    \n",
       "       0\n",
       "       0\n",
       "       100\n",
       "       0.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       1\n",
       "       1\n",
       "       100\n",
       "       36.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       2\n",
       "       2\n",
       "       100\n",
       "       72.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       3\n",
       "       3\n",
       "       100\n",
       "       108.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       4\n",
       "       4\n",
       "       100\n",
       "       144.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       5\n",
       "       5\n",
       "       100\n",
       "       180.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       6\n",
       "       6\n",
       "       100\n",
       "       216.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       7\n",
       "       7\n",
       "       100\n",
       "       252.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       8\n",
       "       8\n",
       "       100\n",
       "       288.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       9\n",
       "       9\n",
       "       100\n",
       "       324.0\n",
       "       NaN\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       10\n",
       "       10\n",
       "       101\n",
       "       NaN\n",
       "       0.01\n",
       "       0.166588\n",
       "    
\n",
       "    \n",
       "       11\n",
       "       11\n",
       "       101\n",
       "       NaN\n",
       "       0.02\n",
       "       0.460439\n",
       "    
\n",
       "    \n",
       "       12\n",
       "       12\n",
       "       101\n",
       "       NaN\n",
       "       0.03\n",
       "       0.075776\n",
       "    
\n",
       "    \n",
       "       13\n",
       "       13\n",
       "       101\n",
       "       NaN\n",
       "       0.04\n",
       "       0.911263\n",
       "    
\n",
       "    \n",
       "       14\n",
       "       14\n",
       "       101\n",
       "       NaN\n",
       "       0.05\n",
       "       0.496744\n",
       "    
\n",
       "  
\n",
       "
\n",
       "
"
      ],
      "text/plain": [
       "    node_id  node_type_id  tuning_angle  param_a   param_b\n",
       "0         0           100           0.0      NaN       NaN\n",
       "1         1           100          36.0      NaN       NaN\n",
       "2         2           100          72.0      NaN       NaN\n",
       "3         3           100         108.0      NaN       NaN\n",
       "4         4           100         144.0      NaN       NaN\n",
       "5         5           100         180.0      NaN       NaN\n",
       "6         6           100         216.0      NaN       NaN\n",
       "7         7           100         252.0      NaN       NaN\n",
       "8         8           100         288.0      NaN       NaN\n",
       "9         9           100         324.0      NaN       NaN\n",
       "10       10           101           NaN     0.01  0.166588\n",
       "11       11           101           NaN     0.02  0.460439\n",
       "12       12           101           NaN     0.03  0.075776\n",
       "13       13           101           NaN     0.04  0.911263\n",
       "14       14           101           NaN     0.05  0.496744"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.analyzer import nodes_table\n",
    "print('mcortex_nodes.h5')\n",
    "nodes_table(nodes_file='network/mcortex2_nodes.h5', population='mcortex2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mcortex_node_types.h5\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "
\n",
       "\n",
       "
\n",
       "  
\n",
       "    \n",
       "       \n",
       "       node_type_id\n",
       "       dynamics_params\n",
       "       d\n",
       "       pop_name\n",
       "       model_type\n",
       "       model_template\n",
       "       morphology\n",
       "       param_c\n",
       "    
\n",
       "  
\n",
       "  
\n",
       "    \n",
       "       0\n",
       "       100\n",
       "       pyr_ephys.json\n",
       "       NaN\n",
       "       pyr\n",
       "       biophysical\n",
       "       ctdb:Biophys1.hoc\n",
       "       pyr_morph.swc\n",
       "       NaN\n",
       "    
\n",
       "    \n",
       "       1\n",
       "       101\n",
       "       NaN\n",
       "       10.0\n",
       "       izh\n",
       "       point_process\n",
       "       nrn:Izhikevich.hoc\n",
       "       NaN\n",
       "       -55.0\n",
       "    
\n",
       "  
\n",
       "
\n",
       "
"
      ],
      "text/plain": [
       "   node_type_id dynamics_params     d pop_name     model_type  \\\n",
       "0           100  pyr_ephys.json   NaN      pyr    biophysical   \n",
       "1           101             NaN  10.0      izh  point_process   \n",
       "\n",
       "       model_template     morphology  param_c  \n",
       "0   ctdb:Biophys1.hoc  pyr_morph.swc      NaN  \n",
       "1  nrn:Izhikevich.hoc            NaN    -55.0  "
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.analyzer import node_types_table\n",
    "print('mcortex_node_types.h5')\n",
    "node_types_table(node_types_file='network/mcortex2_node_types.csv', population='mcortex')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We see that tuning_angle, param's a and b are stored individually for each node where applicable."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Edges\n",
    "\n",
    "After building our nodes we want to create directed connections between them by using the add_edges method. For the simpliest types of edges, we just need to specify the number of connections between any subset of source and target nodes. For instance if we want to create synaptic connections from every pyr cell to every izh cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Create connections between pyr --> izh cells\n",
    "net.add_edges(source={'pop_name': 'pyr'}, target={'pop_name': 'izh'},\n",
    "              connection_rule=12,\n",
    "              syn_weight=5.0e-03,\n",
    "              dynamics_params='AMPA_ExcToExc.json',\n",
    "              model_template='Exp2Syn',\n",
    "              delay=2.0)\n",
    "\n",
    "# Build and save our network\n",
    "net.build()\n",
    "net.save_edges(output_dir='network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Breaking this down, first we specify which nodes to use as sources and which to use as targets by filtering on the parameters of our choice\n",
    "```python\n",
    "source={'pop_name': 'pyr'}, target={'pop_name': 'izh'}\n",
    "```\n",
    "We could also filter by more than one parameter if we wanted\n",
    "```python\n",
    "source={'pop_name': 'pyr'}, target={'pop_name': 'izh', 'param_a': 0.01, ...}\n",
    "```\n",
    "If source and/or target are not specified it uses all possible nodes.\n",
    "\n",
    "Next we want to specify the number of connections using the connection_rule parameter.\n",
    "```python\n",
    "connection_rule=12,\n",
    "```\n",
    "In this example there are (10 pyr) x (5 izh) = 50 source-target pairs, each with 12 synaptic connection. You can pass in a list of 50 integers. And in the next sections we will show how to customized connection property.\n",
    "\n",
    "Finally we add edge properties\n",
    "```python\n",
    "syn_weight=5.0e-03,\n",
    "dynamics_params='ExcToExc.json',\n",
    "model_template='Exp2Syn',\n",
    "delay=2.0)\n",
    "```\n",
    "These are properties used by BioNet/NEURON to build synapses. Like with nodes, these properties are the same for all 50 x 12 = 600 synapses. These parameters are optional, and depending on the requirements of the model/simulator, we can add or remove parameters are needed.\n",
    "\n",
    "After building and saving the edges, you'll see in the network folder two edge files were created; mocrtex2_mcortex2_edges.h5 and mocrtex2_mcortex2_edge_types.csv. Like nodes and node-types, we have edges and edge-types - although adding individual edge properties is a bit more complex."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Custom connection rules\n",
    "\n",
    "In the previous example the number of synapses/connections between any pair of source/target nodes was constant. In many cases we will need to adjust the number of connections based on the types of cells, distance between source and target, etc. Instead of passing an integer to connection_rule we can pass in a function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def like2like(source, target, min_syns, max_syns):\n",
    "    \"\"\"A simple function for setting # of synaptic connections based on pop-name attribute. \"\"\"\n",
    "    if source['node_id'] == target['node_id']:\n",
    "        # No autapses\n",
    "        return 0\n",
    "    \n",
    "    # favor like-to-like connections\n",
    "    if source['pop_name'] == target['pop_name']:\n",
    "        return max_syns\n",
    "    else:\n",
    "        return min_syns\n",
    "    \n",
    "    \n",
    "net.add_edges(source={'pop_name': 'pyr'}, target={'pop_name': 'izh'},\n",
    "              connection_rule=like2like, # Note that we are passing in the function name but not calling it\n",
    "              connection_params={'min_syns': 6, 'max_syns': 12}, # are used to set min_syns, max_syns params in like2like function\n",
    "              syn_weight=5.0e-03,\n",
    "              dynamics_params='AMPA_ExcToExc.json',\n",
    "              model_template='Exp2Syn',\n",
    "              delay=2.0)\n",
    "\n",
    "# Build and save connections\n",
    "net.build()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The like2like() function will be called during the build process for every possible source-target pair (for very larget networks with complex rules the buidling of the edges can take a very long time). All custom functions take as the first two parameters source and target. These two parameters can be used like dictionary objects to get any node property. Using the source and target properties, the function should return an integer representing the number of synapses/connections. If there are no connections for a pair the function should return 0.\n",
    "\n",
    "Besides source and target, the function also has params min_syns and max_syns. These are optional params and set through connection_params in add_edges. This will allow modelers to reuse the same connection_rule for different edge-types."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Individual edge properties"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So far the edge properties like syn_weight and delay are being stored in the edge_types.cvs file. This means that all synapses created by the same add_edges call will share these values. Often this is desirable - the bmtk simulators have a way of altering synaptic weight during run-time. But other times we may want to have a unique syn_weight, or other property, for each individual connection.\n",
    "\n",
    "Unfortantely we can't just pass in a list since, if you're using a custom connection_rule, it's hard to predetermine the total number of connections. Instead we need to again create a custom rule for setting individual synaptic parameters inside the edges.h5 file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def rand_syn_weight(source, target, min_weight, max_weight):\n",
    "    # Do some logic here \n",
    "    return np.random.uniform(min_weight, max_weight)\n",
    "\n",
    "conn = net.add_edges(connection_rule=12,\n",
    "                     dynamics_params='AMPA_ExcToExc.json',\n",
    "                     model_template='Exp2Syn',\n",
    "                     delay=2.0)\n",
    "\n",
    "conn.add_properties('syn_weight', \n",
    "                    rule=rand_syn_weight,\n",
    "                    rule_params={'min_weight': 1.0e-06, 'max_weight': 1.0e-03},\n",
    "                    dtypes=float)\n",
    "net.build()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Once again the rand_syn_weight function will be called during the build process. The function will pass in the source and target nodes for every connection, along with any option parameters set in rule_params. The function must return a numerical value according to the specified dtypes. You can also specifiy multiple params in the same call.\n",
    "\n",
    "```python\n",
    "conn.add_properties(['syn_weight', 'sec_id'], rule=my_fnc, dtypes=[np.float, np.uint])\n",
    "\n",
    "def my_fnc(source, target):\n",
    "    syn_weight = ... # float\n",
    "    sec_id = ... # integer\n",
    "    return syn_weight, sec_id\n",
    "```\n",
    "\n",
    "**Note**: Using add_properties will slow-down the network build-time and increase the size of the resulting network. If a source-target pair has N synpatic connection, the builder will call my_fnc N times, and it will store N different synaptic connections in the edges.h5. It is usually best not to use add_properties and instead let BioNet, PointNet, etc. adjust syn_weight at run-time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "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.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
