{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 3: Multi-cell, single population network (with BioNet)\n",
    "\n",
    "In this tutorial, we will create a more complex network that contains multiple biophysical cells, but all of them having the same cell-type (we will cover hetergenous networks in the next tutorial). The network will contain recurrent connections, as well as external input that provide the next with stimululation.\n",
    "\n",
    "**Note** - scripts and files for running this tutorial can be found in the directory [sources/chapter03/](https://github.com/AllenInstitute/bmtk/tree/develop/docs/tutorial/sources/chapter03)\n",
    "\n",
    "requirements:\n",
    "* bmtk\n",
    "* NEURON 7.4+"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Building the Network\n",
    "\n",
    "First we will build our internal network, which consists of 100 different cells. All the cells are of the same type (we'll show how to build a heterogeneous network in the next tutorial), however they all have a different location and y-axis rotation.\n",
    "\n",
    "#### nodes "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from bmtk.builder.networks import NetworkBuilder\n",
    "from bmtk.builder.auxi.node_params import positions_columinar, xiter_random\n",
    "\n",
    "cortex = NetworkBuilder('mcortex')\n",
    "cortex.add_nodes(\n",
    "    N=100,\n",
    "    pop_name='Scnn1a',\n",
    "    positions=positions_columinar(N=100, center=[0, 50.0, 0], max_radius=30.0, height=100.0),\n",
    "    rotation_angle_yaxis=xiter_random(N=100, min_x=0.0, max_x=2*np.pi),\n",
    "    rotation_angle_zaxis=3.646878266,\n",
    "    potental='exc',\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"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The parameter N is used to indicate the number of cells in our population. The positions of each cell is defined by the columinar built-in method, which will random place our cells in a column (users can define their own positions as shown here). The rotation_angel_yaxis is similarl defined by a built-in function that will randomly assign each cell a given y angle.\n",
    "\n",
    "One thing to note is that while yaxis is defined by a function which returns a lists of values, the zaxis is defined by a single value. This means that all cells will share the zaxis. we could alteratively give all cells the same y-axis rotation:\n",
    "```python\n",
    "    rotation_angle_yaxis=rotation_value\n",
    "```\n",
    "or give all cells a unique z-rotation angle\n",
    "```python\n",
    "    rotation_angle_zaxis=xiter_random(N=100, min_x=0.0, max_x=2*np.pi)\n",
    "```\n",
    "and in general, it is at the discretion of the modeler to choose what parameters are unqiue to each cell, and what parameters are global to the cell-type."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### edges\n",
    "\n",
    "Next we want to add recurrent edges. To create the connections we will use the built-in distance_connector function, which will assign the number of connections between two cells randomly (between range nsyn_min and nsysn_max) but weighted by distance. The other parameters, including the synaptic model (AMPA_ExcToExc) will be shared by all connections.\n",
    "\n",
    "To use this, or even customized, connection functions, we must pass in the name of our connection function using the \"connection_rule\" parameter, and the function parameters through \"connection_params\" as a dictionary, which will looks something like:\n",
    "```python\n",
    "    connection_rule=<name_of_function>\n",
    "    connection_params={'param_arg1': val1, 'param_arg2': val2, ...}\n",
    "```\n",
    "The connection_rule method isn't explicitly called by the script. Rather when the build() method is called, the connection_rule will iterate through every source/target node pair, and use the rule and build a connection matrix.\n",
    "\n",
    "\n",
    "After building the connections based on our connection function, we will save the nodes and edges files into the network/ directory."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bmtk.builder.connection_map.ConnectionMap at 0x7f461499a780>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.builder.auxi.edge_connectors import distance_connector\n",
    "\n",
    "cortex.add_edges(\n",
    "    source={'pop_name': 'Scnn1a'}, target={'pop_name': 'Scnn1a'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 0.34, 'd_max': 50.0, 'nsyn_min': 0, 'nsyn_max': 10},\n",
    "    syn_weight=2.0e-04,\n",
    "    distance_range=[30.0, 150.0],\n",
    "    target_sections=['basal', 'apical', 'soma'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='AMPA_ExcToExc.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "cortex.build()\n",
    "cortex.save_nodes(output_dir='sim_ch03/network')\n",
    "cortex.save_edges(output_dir='sim_ch03/network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### External network\n",
    "\n",
    "After building our internal network, we will build the external thalamic network which will provide input (see previous tutorial for more detail). Our thalamic network will consist of 100 \"filter\" cells, which aren't actual cells by just place holders for spike-trains."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "thalamus = NetworkBuilder('mthalamus')\n",
    "thalamus.add_nodes(\n",
    "    N=100,\n",
    "    pop_name='tON',\n",
    "    potential='exc',\n",
    "    model_type='virtual'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The external network doesn't have recurrent connections. Rather all the cells are feedforward onto the internal network. To do this is in a separate script which must reload the saved mcortex cell files using the import function. Then we create an edge with the thalamus nodes as the sources and the cortext nodes as the targets. This time we use the built-in connect_random connection rule, which will randomly assign each thalamus --> cortex connection between 0 and 12 synaptic connections."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bmtk.builder.auxi.edge_connectors import connect_random\n",
    "\n",
    "thalamus.add_edges(\n",
    "    source=thalamus.nodes(), target=cortex.nodes(),\n",
    "    connection_rule=connect_random,\n",
    "    connection_params={'nsyn_min': 0, 'nsyn_max': 12},\n",
    "    syn_weight=1.0e-04,\n",
    "    distance_range=[0.0, 150.0],\n",
    "    target_sections=['basal', 'apical'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='AMPA_ExcToExc.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "thalamus.build()\n",
    "thalamus.save_nodes(output_dir='sim_ch03/network')\n",
    "thalamus.save_edges(output_dir='sim_ch03/network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Spike Trains\n",
    "\n",
    "We next need to create the individual spike trains for our thalamic filter cells. We will use a Poission distrubition to create a random distribution of spikes for our 300 hundred cells each firing at ~ 15 Hz over a 3 second window. Then we can save our spike trains as a [SONATA file](https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md#spike-file) under **sim_ch03/inputs** directory. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>node_ids</th>\n",
       "      <th>timestamps</th>\n",
       "      <th>population</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>120.051599</td>\n",
       "      <td>mthalamus</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>0</td>\n",
       "      <td>317.919543</td>\n",
       "      <td>mthalamus</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>0</td>\n",
       "      <td>404.393517</td>\n",
       "      <td>mthalamus</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>0</td>\n",
       "      <td>514.393950</td>\n",
       "      <td>mthalamus</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>0</td>\n",
       "      <td>538.898219</td>\n",
       "      <td>mthalamus</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   node_ids  timestamps population\n",
       "0         0  120.051599  mthalamus\n",
       "1         0  317.919543  mthalamus\n",
       "2         0  404.393517  mthalamus\n",
       "3         0  514.393950  mthalamus\n",
       "4         0  538.898219  mthalamus"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.utils.reports.spike_trains import PoissonSpikeGenerator\n",
    "\n",
    "psg = PoissonSpikeGenerator(population='mthalamus')\n",
    "psg.add(node_ids=range(100),  # Have 10 nodes to match mthalamus\n",
    "        firing_rate=15.0,    # 15 Hz, we can also pass in a nonhomoegenous function/array\n",
    "        times=(0.0, 3.0))    # Firing starts at 0 s up to 3 s\n",
    "psg.to_sonata('sim_ch03/inputs/mthalamus_spikes.h5')\n",
    "\n",
    "# Let's do a quick check that we have reasonable results. Should see somewhere on the order of 15*3*100 = 4500\n",
    "# spikes\n",
    "psg.to_dataframe().head()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Setting up BioNet\n",
    "\n",
    "#### file structure.\n",
    "\n",
    "Before running a simulation, we will need to create the runtime environment, including parameter files, run-script and configuration files. You've already completed Chapter 02 tutorial you can just copy the files to **sim_ch03** (just make sure not to overwrite the **network** and **inputs** directory).\n",
    "\n",
    "Or create them from scracth by either running the command:\n",
    "```bash\n",
    "$ python -m bmtk.utils.sim_setup  \\\n",
    "   --report-vars v,cai            \\                             \n",
    "   --network sim_ch03/network     \\                              \n",
    "   --spikes-inputs mthalamus:sim_ch03/inputs/mthalamus_spikes.h5 \\\n",
    "   --dt 0.1             \\\n",
    "   --tstop 3000.0       \\  \n",
    "   --include-examples   \\\n",
    "   --compile-mechanisms \\ \n",
    "   bionet sim_ch03\n",
    "```\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "config.json\n"
     ]
    }
   ],
   "source": [
    "from bmtk.utils.sim_setup import build_env_bionet\n",
    "\n",
    "build_env_bionet(\n",
    "    base_dir='sim_ch03',\n",
    "    config_file='config.json',\n",
    "    network_dir='sim_ch03/network',\n",
    "    tstop=3000.0, dt=0.1,\n",
    "    report_vars=['v', 'cai'],     # Record membrane potential and calcium (default soma)\n",
    "    spikes_inputs=[('mthalamus',   # Name of population which spikes will be generated for\n",
    "                    'sim_ch03/inputs/mthalamus_spikes.h5')],\n",
    "    include_examples=True,    # Copies components files\n",
    "    compile_mechanisms=True   # Will try to compile NEURON mechanisms\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It's a good idea to check the configuration files **sim_ch03/circuit_config.json** and **sim_ch03/simulation_config.json**, especially to make sure that bmtk will know to use our generated spikes file (if you don't see the below section in the simulation_config.json file go ahead and add it). \n",
    "\n",
    "```json\n",
    "{\n",
    "\n",
    "    \n",
    "  \"inputs\": {\n",
    "    \"tc_spikes\": {\n",
    "      \"input_type\": \"spikes\",\n",
    "      \"module\": \"csv\",\n",
    "      \"input_file\": \"${BASE_DIR}/mthalamus_spikes.csv\",\n",
    "      \"node_set\": \"mthalamus\"\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Running the simulation\n",
    "\n",
    "Once our config file is setup we can run a simulation either through the command line:\n",
    "```bash\n",
    "$ cd sim_ch03\n",
    "$ python run_bionet.py config.json\n",
    "```\n",
    "\n",
    "or through the script"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:37,997 [INFO] Created log file\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Created log file\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:38,089 [INFO] Building cells.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Building cells.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:46,048 [INFO] Building recurrent connections\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Building recurrent connections\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:47,199 [INFO] Building virtual cell stimulations for mthalamus_spikes\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Building virtual cell stimulations for mthalamus_spikes\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:56,580 [INFO] Running simulation for 3000.000 ms with the time step 0.100 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Running simulation for 3000.000 ms with the time step 0.100 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:56,582 [INFO] Starting timestep: 0 at t_sim: 0.000 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Starting timestep: 0 at t_sim: 0.000 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:32:56,582 [INFO] Block save every 5000 steps\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Block save every 5000 steps\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:33:34,556 [INFO]     step:5000 t_sim:500.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:5000 t_sim:500.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:34:10,838 [INFO]     step:10000 t_sim:1000.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:10000 t_sim:1000.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:34:48,306 [INFO]     step:15000 t_sim:1500.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:15000 t_sim:1500.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:35:29,660 [INFO]     step:20000 t_sim:2000.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:20000 t_sim:2000.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:36:09,063 [INFO]     step:25000 t_sim:2500.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:25000 t_sim:2500.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:36:47,843 [INFO]     step:30000 t_sim:3000.00 ms\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:    step:30000 t_sim:3000.00 ms\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 07:36:47,903 [INFO] Simulation completed in 3.0 minutes, 51.32 seconds \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Simulation completed in 3.0 minutes, 51.32 seconds \n"
     ]
    }
   ],
   "source": [
    "from bmtk.simulator import bionet\n",
    "\n",
    "\n",
    "conf = bionet.Config.from_json('sim_ch03/config.json')\n",
    "conf.build_env()\n",
    "net = bionet.BioNetwork.from_config(conf)\n",
    "sim = bionet.BioSimulator.from_config(conf, network=net)\n",
    "sim.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Analyzing the run.\n",
    "\n",
    "If successful, we should have our results in the **output** directory. We can use the analyzer to plot a raster of the spikes over time:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 44472 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from bmtk.analyzer.spike_trains import plot_raster\n",
    "\n",
    "\n",
    "_ = plot_raster(config_file='sim_ch03/config.json')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In our config file we used the cell_vars and node_id_selections parameters to save the calcium influx and membrane potential of selected cells. We can also use the analyzer to display these traces:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 27848 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 35932 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from bmtk.analyzer.compartment import plot_traces\n",
    "\n",
    "_ = plot_traces(config_file='sim_ch03/config.json', report_name='v_report')\n",
    "_ = plot_traces(config_file='sim_ch03/config.json', report_name='cai_report')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. Modifying the network"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Customized node params\n",
    "\n",
    "When building our cortex nodes, we used some built-in functions to set certain parameters like positions and y-axis rotations:\n",
    "```python\n",
    "cortex.add_nodes(N=100,\n",
    "                 pop_name='Scnn1a',\n",
    "                 positions=positions_columinar(N=100, center=[0, 50.0, 0], max_radius=30.0, height=100.0),\n",
    "                 rotation_angle_yaxis=xiter_random(N=100, min_x=0.0, max_x=2*np.pi),\n",
    "                 ...\n",
    "```\n",
    "\n",
    "These functions will assign every cell a unique value in the positions and rotation_angle_yaxis parameters, unlike the pop_name parameter which will be the same for all 100 cells. We can verify by the following code:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cell 0: pop_name: Scnn1a, positions: [-7.76668485  0.38215656 -7.4268604 ], angle_yaxis: 0.43207320140931793\n",
      "cell 1: pop_name: Scnn1a, positions: [-0.45636819 84.90726294 -4.33892126], angle_yaxis: 5.501379786273044\n"
     ]
    }
   ],
   "source": [
    "cortex_nodes = list(cortex.nodes())\n",
    "n0 = cortex_nodes[0]\n",
    "n1 = cortex_nodes[1]\n",
    "print('cell 0: pop_name: {}, positions: {}, angle_yaxis: {}'.format(n0['pop_name'], n0['positions'], n0['rotation_angle_yaxis']))\n",
    "print('cell 1: pop_name: {}, positions: {}, angle_yaxis: {}'.format(n1['pop_name'], n1['positions'], n1['rotation_angle_yaxis']))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Network Builder contains a growing number of built-in functions. However for advanced networks a modeler will probably want to assign parameters using their own functions. To do so, a modeler only needs to passes in, or alternatively create a function that returns, a list of size N. When saving the network, each individual position will be saved in the nodes.h5 file assigned to each cell by gid.\n",
    "\n",
    "```python\n",
    "def cortex_positions(N):\n",
    "    # codex to create a list/numpy array of N (x, y, z) positions.\n",
    "    return [...]\n",
    "\n",
    "cortex.add_nodes(N=100,\n",
    "                 positions=cortex_positions(100),\n",
    "                 ...\n",
    "```\n",
    "\n",
    "or if we wanted we could give all cells the same position (The builder has no restrictions on this, however this may cause issues if you're trying to create connections based on distance). When saving the network, the same position is assigned as a global cell-type property, and thus saved in the node_types.csv file.\n",
    "```python\n",
    "cortex.add_nodes(N=100,\n",
    "                 positions=np.ndarray([100.23, -50.67, 89.01]),\n",
    "                 ...\n",
    "```\n",
    "\n",
    "We can use the same logic not just for positions and rotation_angle, but for any parameter we choose."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Customized connector functions\n",
    "\n",
    "When creating edges, we used the built-in distance_connector function to help create the connection matrix. There are a number of built-in connection functions, but we also allow modelers to create their own. To do so, the modeler must create a function that takes in a source, target, and a variable number of parameters, and pass back a natural number representing the number of connections.\n",
    "\n",
    "The Builder will iterate over that function passing in every source/target node pair (filtered by the source and target parameters in add_edges()). The source and target parameters are essentially dictionaries that can be used to fetch properties of the nodes. A typical example would look like:\n",
    "\n",
    "```python\n",
    "def customized_connector(source, target, param1, param2, param3):\n",
    "    if source.node_id == target.node_id:\n",
    "        # necessary if we don't want autapses\n",
    "        return 0\n",
    "    source_pot = source['potential']\n",
    "    target_pot = target['potential']\n",
    "    # some code to determine number of connections\n",
    "    return n_synapses\n",
    "    \n",
    "...\n",
    "cortex.add_edges(source=<source_nodes>, target=<target_nodes>,\n",
    "                 connection_rule=customized_connector,\n",
    "                 connection_params={'param1': <p1>, 'param2': <p2>, 'param3': <p3>},\n",
    "                 ...\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "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
}
