{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial 4: Muti-population recurrent network (with BioNet) \n",
    "\n",
    "Here we will create a heterogenous yet relatively small network consisting of hundreds of cells recurrently connected. All cells will belong to one of four \"cell-types\". Two of these cell types will be biophysically detailed cells, i.e. containing a full morphology and somatic and dendritic channels and receptors. The other two will be point-neuron models, which lack a full morphology or channels but still act to provide inhibitory and excitory dynamics.\n",
    "\n",
    "As input to drive the simulation, we will also create an external network of \"virtual cells\" that synapse directly onto our internal cells and provide spike trains stimulus\n",
    "\n",
    "**Note** - scripts and files for running this tutorial can be found in the directory [sources/chapter04/](https://github.com/AllenInstitute/bmtk/tree/develop/docs/tutorial/sources/chapter04)\n",
    "\n",
    "requirements:\n",
    "* bmtk\n",
    "* NEURON 7.4+"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Building the network\n",
    "\n",
    "#### cells\n",
    "\n",
    "This network will loosely resemble the mouse V1 cortical column. Along the center of the column will be a population of 50 biophysically detailed neurons: 40 excitatory Scnn1a cells and 10 inhibitory PV cells."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "from bmtk.builder.networks import NetworkBuilder\n",
    "from bmtk.builder.auxi.node_params import positions_columinar, xiter_random\n",
    "\n",
    "net = NetworkBuilder(\"V1\")\n",
    "net.add_nodes(\n",
    "    N=80, pop_name='Scnn1a',\n",
    "    positions=positions_columinar(N=80, center=[0, 50.0, 0], max_radius=30.0, height=100.0),\n",
    "    rotation_angle_yaxis=xiter_random(N=80, min_x=0.0, max_x=2*np.pi),\n",
    "    rotation_angle_zaxis=xiter_random(N=80, min_x=0.0, max_x=2*np.pi),\n",
    "    tuning_angle=np.linspace(start=0.0, stop=360.0, num=80, endpoint=False),\n",
    "    location='VisL4',\n",
    "    ei='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",
    "\n",
    "net.add_nodes(\n",
    "    N=20, pop_name='PV',\n",
    "    positions=positions_columinar(N=20, center=[0, 50.0, 0], max_radius=30.0, height=100.0),\n",
    "    rotation_angle_yaxis=xiter_random(N=20, min_x=0.0, max_x=2*np.pi),\n",
    "    rotation_angle_zaxis=xiter_random(N=20, min_x=0.0, max_x=2*np.pi),\n",
    "    location='VisL4',\n",
    "    ei='i',\n",
    "    model_type='biophysical',\n",
    "    model_template='ctdb:Biophys1.hoc',\n",
    "    model_processing='aibs_perisomatic',\n",
    "    dynamics_params='472912177_fit.json',\n",
    "    morphology='Pvalb_470522102_m.swc'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To set the position and rotation of each cell, we use the built in function positions_columinar and xiter_random, which returns a list of values given the parameters. A user could set the values themselves using a list (or function that returns a list) of size N. The parameters like location, ei (potential), params_file, etc. are cell-type parameters, and will be used for all N cells of that type.\n",
    "\n",
    "The excitory cells are also given a tuning_angle parameter. An instrinsic \"tuning angle\" is a property found in some cells in the visual cortex. In this model, we will use this property to determine number of strenght of connections between subsets of cells by using custom functions. But in general most models will not have or use a tuning angle, but they may require some other parameter. In general, users can assign whatever custom parameters they want to cells and cell-types and use them as properties for creating connections and running simulations.\n",
    "\n",
    "Next we continue to create our point (integrate-and-fire) neurons. Notice they don't have properities like y/z rotation or morphology, as they wouldn't apply to point neurons."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.add_nodes(\n",
    "    N=200, pop_name='LIF_exc',\n",
    "    positions=positions_columinar(N=200, center=[0, 50.0, 0], min_radius=30.0, max_radius=60.0, height=100.0),\n",
    "    tuning_angle=np.linspace(start=0.0, stop=360.0, num=200, endpoint=False),\n",
    "    location='VisL4',\n",
    "    ei='e',\n",
    "    model_type='point_process',\n",
    "    model_template='nrn:IntFire1',\n",
    "    dynamics_params='IntFire1_exc_1.json'\n",
    ")\n",
    "\n",
    "net.add_nodes(\n",
    "    N=100, pop_name='LIF_inh',\n",
    "    positions=positions_columinar(N=100, center=[0, 50.0, 0], min_radius=30.0, max_radius=60.0, height=100.0),\n",
    "    location='VisL4',\n",
    "    ei='i',\n",
    "    model_type='point_process',\n",
    "    model_template='nrn:IntFire1',\n",
    "    dynamics_params='IntFire1_inh_1.json'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### connections\n",
    "\n",
    "Now we want to create connections between the cells. Depending on the model type, and whether or not the presynpatic \"source\" cell is excitory or inhibitory, we will have different synpatic model and parameters. Using the source and target filter parameters, we can create different connection types.\n",
    "\n",
    "To determine excitory-to-excitory connection matrix we want to use distance and tuning_angle property. To do this we create a customized function \"dist_tuning_connector\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import random\n",
    "import math\n",
    "\n",
    "def dist_tuning_connector(source, target, d_weight_min, d_weight_max, d_max, t_weight_min, t_weight_max, nsyn_min,\n",
    "                          nsyn_max):\n",
    "    if source['node_id'] == target['node_id']:\n",
    "        # Avoid self-connections.n_nodes\n",
    "        return None\n",
    "\n",
    "    r = np.linalg.norm(np.array(source['positions']) - np.array(target['positions']))\n",
    "    if r > d_max:\n",
    "        dw = 0.0\n",
    "    else:\n",
    "        t = r / d_max\n",
    "        dw = d_weight_max * (1.0 - t) + d_weight_min * t\n",
    "\n",
    "    if dw <= 0:\n",
    "        # drop the connection if the weight is too low\n",
    "        return None\n",
    "\n",
    "    # next create weights by orientation tuning [ aligned, misaligned ] --> [ 1, 0 ], Check that the orientation\n",
    "    # tuning property exists for both cells; otherwise, ignore the orientation tuning.\n",
    "    if 'tuning_angel' in source and 'tuning_angle' in target:\n",
    "\n",
    "        # 0-180 is the same as 180-360, so just modulo by 180\n",
    "        delta_tuning = math.fmod(abs(source['tuning_angle'] - target['tuning_angle']), 180.0)\n",
    "\n",
    "        # 90-180 needs to be flipped, then normalize to 0-1\n",
    "        delta_tuning = delta_tuning if delta_tuning < 90.0 else 180.0 - delta_tuning\n",
    "\n",
    "        t = delta_tuning / 90.0\n",
    "        tw = t_weight_max * (1.0 - t) + t_weight_min * t\n",
    "    else:\n",
    "        tw = dw\n",
    "\n",
    "    # drop the connection if the weight is too low\n",
    "    if tw <= 0:\n",
    "        return None\n",
    "\n",
    "    # filter out nodes by treating the weight as a probability of connection\n",
    "    if random.random() > tw:\n",
    "        return None\n",
    "\n",
    "    # Add the number of synapses for every connection.\n",
    "    # It is probably very useful to take this out into a separate function.\n",
    "    return random.randint(nsyn_min, nsyn_max)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This first two parameters of this function is \"source\" and \"target\" and are required for all custom connector functions. These are node objects which gives a representation of a single source and target cell, with properties that can be accessed like a python dictionary. When The Network Builder is creating the connection matrix, it will call this function for all possible source-target pairs. The user doesn't call this function directly.\n",
    "\n",
    "The remaining parameters are optional. Using these parameters, plus the distance and angles between source and target cells, this function determines the number of connections between each given source and target cell. If there are none you can return either None or 0.\n",
    "\n",
    "To create these connections we call add_edges method of the builder. We use the source and target parameter to filter out only excitory-to-excitory connections. We must also take into consideration the model type (biophysical or integrate-and-fire) of the target when setting parameters. We pass in the function throught the connection_rule parameter, and the function parameters (except source and target) through connection_params. (If our dist_tuning_connector function didn't have any parameters other than source and target, we could just not set connection_params)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bmtk.builder.connection_map.ConnectionMap at 0x7ff7bc90d828>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "net.add_edges(\n",
    "    source={'ei': 'e'}, target={'pop_name': 'Scnn1a'},\n",
    "    connection_rule=dist_tuning_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 0.34, 'd_max': 300.0, 't_weight_min': 0.5,\n",
    "                       't_weight_max': 1.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=6.4e-05,\n",
    "    weight_function='gaussianLL',\n",
    "    weight_sigma=50.0,\n",
    "    distance_range=[30.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",
    "net.add_edges(\n",
    "    source={'ei': 'e'}, target={'pop_name': 'LIF_exc'},\n",
    "    connection_rule=dist_tuning_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 0.34, 'd_max': 300.0, 't_weight_min': 0.5,\n",
    "                       't_weight_max': 1.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.0019,\n",
    "    weight_function='gaussianLL',\n",
    "    weight_sigma=50.0,\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousExc.json',\n",
    "    model_template='exp2syn'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Similarly we create the other types of connections. But since either the source, target, or both cells will not have the tuning_angle parameter, we don't want to use dist_tuning_connector. Instead we can use the built-in distance_connector function which just creates connections determined by distance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<bmtk.builder.connection_map.ConnectionMap at 0x7ff77de9a7b8>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.builder.auxi.edge_connectors import distance_connector\n",
    "\n",
    "### Generating I-to-I connections\n",
    "net.add_edges(\n",
    "    source={'ei': 'i'}, target={'ei': 'i', 'model_type': 'biophysical'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 1.0, 'd_max': 160.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.0002,\n",
    "    weight_function='wmax',\n",
    "    distance_range=[0.0, 1e+20],\n",
    "    target_sections=['somatic', 'basal'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='GABA_InhToInh.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "net.add_edges(\n",
    "    source={'ei': 'i'}, target={'ei': 'i', 'model_type': 'point_process'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 1.0, 'd_max': 160.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.00225,\n",
    "    weight_function='wmax',\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousInh.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "### Generating I-to-E connections\n",
    "net.add_edges(\n",
    "    source={'ei': 'i'}, target={'ei': 'e', 'model_type': 'biophysical'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 1.0, 'd_max': 160.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.00018,\n",
    "    weight_function='wmax',\n",
    "    distance_range=[0.0, 50.0],\n",
    "    target_sections=['somatic', 'basal', 'apical'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='GABA_InhToExc.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "net.add_edges(\n",
    "    source={'ei': 'i'}, target={'ei': 'e', 'model_type': 'point_process'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 1.0, 'd_max': 160.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.009,\n",
    "    weight_function='wmax',\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousInh.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "### Generating E-to-I connections\n",
    "net.add_edges(\n",
    "    source={'ei': 'e'}, target={'pop_name': 'PV'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 0.26, 'd_max': 300.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.00035,\n",
    "    weight_function='wmax',\n",
    "    distance_range=[0.0, 1e+20],\n",
    "    target_sections=['somatic', 'basal'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='AMPA_ExcToInh.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "net.add_edges(\n",
    "    source={'ei': 'e'}, target={'pop_name': 'LIF_inh'},\n",
    "    connection_rule=distance_connector,\n",
    "    connection_params={'d_weight_min': 0.0, 'd_weight_max': 0.26, 'd_max': 300.0, 'nsyn_min': 3, 'nsyn_max': 7},\n",
    "    syn_weight=0.0043,\n",
    "    weight_function='wmax',\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousExc.json',\n",
    "    model_template='exp2syn'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally we build the network (this may take a bit of time since it's essentially iterating over all 400x400 possible connection combinations), and save the nodes and edges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "net.build()\n",
    "net.save_nodes(output_dir='sim_ch04/network')\n",
    "net.save_edges(output_dir='sim_ch04/network')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Building external network\n",
    "\n",
    "Next we want to create an external network consisting of virtual cells that form a feedforward network onto our V1, which will provide input during the simulation. We will call this LGN, since the LGN is the primary input the layer 4 cells of the V1 (if we wanted to we could also create multiple external networks and run simulations on any number of them). \n",
    "\n",
    "First we build our LGN nodes. Then we must import the V1 network nodes, and create connections between LGN --> V1."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bmtk.builder.networks import NetworkBuilder\n",
    "\n",
    "lgn = NetworkBuilder('LGN')\n",
    "lgn.add_nodes(\n",
    "    N=500,\n",
    "    pop_name='tON',\n",
    "    potential='exc',\n",
    "    model_type='virtual'\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As before, we will use a customized function to determine the number of connections between each source and target pair, however this time our connection_rule is a bit different\n",
    "\n",
    "In the previous example, our connection_rule function's first two arguments were the presynaptic and postsynaptic cells, which allowed us to choose how many synaptic connections between the pairs existed based on individual properties:\n",
    "```python\n",
    "def connection_fnc(source, target, ...):\n",
    "    source['param'] # presynaptic cell params\n",
    "    target['param'] # postsynaptic cell params\n",
    "    ...\n",
    "    return nsyns # number of connections between pair\n",
    "```\n",
    "\n",
    "But for our LGN --> V1 connection, we do things a bit differently. We want to make sure that for every source cell, there are a limited number of presynaptic targets. This is a not really possible with a function that iterates on a one-to-one basis. So instead we have a connector function who's first parameter is a list of all N source cell, and the second parameter is a single target cell. We return an array of integers, size N; which each index representing the number of synaptics between sources and the target. \n",
    "\n",
    "To tell the builder to use this schema, we must set iterator='all_to_one' in the add_edges method. (By default this is set to 'one_to_one'. You can also use 'one_to_all' iterator which will pass in a single source and all possible targets)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def select_source_cells(sources, target, nsources_min=10, nsources_max=30, nsyns_min=3, nsyns_max=12):\n",
    "    total_sources = len(sources)\n",
    "    nsources = np.random.randint(nsources_min, nsources_max)\n",
    "    selected_sources = np.random.choice(total_sources, nsources, replace=False)\n",
    "    syns = np.zeros(total_sources)\n",
    "    syns[selected_sources] = np.random.randint(nsyns_min, nsyns_max, size=nsources)\n",
    "    return syns\n",
    "\n",
    "lgn.add_edges(\n",
    "    source=lgn.nodes(), target=net.nodes(pop_name='Scnn1a'),\n",
    "    iterator='all_to_one',\n",
    "    connection_rule=select_source_cells,\n",
    "    connection_params={'nsources_min': 10, 'nsources_max': 25},\n",
    "    syn_weight=4e-03,\n",
    "    weight_function='wmax',\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",
    "lgn.add_edges(\n",
    "    source=lgn.nodes(), target=net.nodes(pop_name='PV1'),\n",
    "    connection_rule=select_source_cells,\n",
    "    connection_params={'nsources_min': 15, 'nsources_max': 30},\n",
    "    iterator='all_to_one',\n",
    "    syn_weight=0.001,\n",
    "    weight_function='wmax',\n",
    "    distance_range=[0.0, 1.0e+20],\n",
    "    target_sections=['somatic', 'basal'],\n",
    "    delay=2.0,\n",
    "    dynamics_params='AMPA_ExcToInh.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "lgn.add_edges(\n",
    "    source=lgn.nodes(),  target=net.nodes(pop_name='LIF_exc'),\n",
    "    connection_rule=select_source_cells,\n",
    "    connection_params={'nsources_min': 10, 'nsources_max': 25},\n",
    "    iterator='all_to_one',\n",
    "    syn_weight= 0.045,\n",
    "    weight_function='wmax',\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousExc.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "lgn.add_edges(\n",
    "    source=lgn.nodes(),  target=net.nodes(pop_name='LIF_inh'),\n",
    "    connection_rule=select_source_cells,\n",
    "    connection_params={'nsources_min': 15, 'nsources_max': 30},\n",
    "    iterator='all_to_one',\n",
    "    syn_weight=0.02,\n",
    "    weight_function='wmax',\n",
    "    delay=2.0,\n",
    "    dynamics_params='instanteneousExc.json',\n",
    "    model_template='exp2syn'\n",
    ")\n",
    "\n",
    "lgn.build()\n",
    "lgn.save_nodes(output_dir='sim_ch04/network')\n",
    "lgn.save_edges(output_dir='sim_ch04/network')"
   ]
  },
  {
   "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 can copy the files from an existing simuatlion, execute the following command:\n",
    "\n",
    "```bash\n",
    "$ python -m bmtk.utils.sim_setup  \\\n",
    "   --report-vars v                \\\n",
    "   --report-nodes 10,80           \\\n",
    "   --network sim_ch04/network     \\\n",
    "   --dt 0.1                       \\\n",
    "   --tstop 3000.0                 \\  \n",
    "   --include-examples             \\\n",
    "   --compile-mechanisms           \\ \n",
    "   bionet sim_ch04\n",
    "```\n",
    "\n",
    "$ python -m bmtk.utils.sim_setup --report-vars v --report-nodes 0,80,100,300 --network sim_ch04/network --dt 0.1 --tstop 3000.0  --include-examples --compile-mechanisms bionet sim_ch04\n",
    "\n",
    "or run it directly in python"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bmtk.utils.sim_setup import build_env_bionet\n",
    "\n",
    "build_env_bionet(\n",
    "    base_dir='sim_ch04',\n",
    "    config_file='config.json',\n",
    "    network_dir='sim_ch04/network',\n",
    "    tstop=3000.0, dt=0.1,\n",
    "    report_vars=['v'],     # Record membrane potential (default soma)\n",
    "    include_examples=True,    # Copies components files\n",
    "    compile_mechanisms=True   # Will try to compile NEURON mechanisms\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This will fill out the **sim_ch04** with all the files we need to get started to run the simulation. Of interest includes\n",
    "\n",
    "* **circuit_config.json** - A configuration file that contains the location of the network files we created above. Plus location of neuron and synpatic models, templates, morphologies and mechanisms required to build our instantiate individual cell models.\n",
    "\n",
    "\n",
    "* **simulation_config.json** - contains information about the simulation. Including initial conditions and run-time configuration (_run_ and _conditions_). In the _inputs_ section we define what external sources we will use to drive the network (in this case a current clamp). And in the _reports_ section we define the variables (soma membrane potential and calcium) that will be recorded during the simulation \n",
    "\n",
    "\n",
    "* **run_bionent.py** - A script for running our simulation. Usually this file doesn't need to be modified.\n",
    "\n",
    "\n",
    "* **components/biophysical_neuron_models/** - The parameter file for the cells we're modeling. Originally [downloaded from the Allen Cell Types Database](http://celltypes.brain-map.org/neuronal_model/download/482934212). These files were automatically copies over when we used the _include-examples_ directive. If using a differrent or extended set of cell models place them here\n",
    "\n",
    "\n",
    "* **components/biophysical_neuron_models/** - The morphology file for our cells. Originally [downloaded from the Allen Cell Types Database](http://celltypes.brain-map.org/neuronal_model/download/482934212) and copied over using the _include_examples_.\n",
    "\n",
    "\n",
    "* **components/point_neuron_models/** - The parameter file for our LIF_exc and LIF_inh cells.\n",
    "\n",
    "\n",
    "* **components/synaptic_models/** - Parameter files used to create different types of synapses.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### lgn input\n",
    "\n",
    "We need to provide our LGN external network cells with spike-trains so they can activate our recurrent network. Previously we showed how to do this by generating csv files. We can also use NWB files, which are a common format for saving electrophysiological data in neuroscience.\n",
    "\n",
    "We can use any NWB file generated experimentally or computationally, but for this example we will use a preexsting one. First download the file:\n",
    "```bash\n",
    " $ wget https://github.com/AllenInstitute/bmtk/blob/develop/docs/examples/spikes_inputs/lgn_spikes.h5?raw=true -O lgn_spikes.h5\n",
    "```\n",
    "or copy from [here](https://github.com/AllenInstitute/bmtk/tree/develop/docs/examples/spikes_inputs/lgn_spikes.nwb).\n",
    "\n",
    "\n",
    "Then we must edit the **simulation_config.json** file to tell the simulator to find the nwb file and which network to associate it with.\n",
    "\n",
    "```json\n",
    "{\n",
    "  \"inputs\": {\n",
    "    \"LGN_spikes\": {\n",
    "      \"input_type\": \"spikes\",\n",
    "      \"module\": \"sonata\",\n",
    "      \"input_file\": \"$BASE_DIR/lgn_spikes.h5\",\n",
    "      \"node_set\": \"LGN\"\n",
    "    }\n",
    "  }\n",
    "}\n",
    "```\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Running the simulation\n",
    "\n",
    "\n",
    "We are close to running our simulation, however unlike in previous chapters we need a little more programming before we can begin. \n",
    "\n",
    "For most of the connections we added the parameter weight_function='wmax'. This is a built-in function that tells the simulator when creating a connection between two cells, just use the 'weight_max' value assigned to that given edge-type. \n",
    "\n",
    "However, when creating excitatory-to-excitatory connections we used weight_function='gaussianLL'. This is because we want to use the tuning_angle parameter, when avaiable, to determine the synaptic strength between two connections. First we create the function which takes in target, source and connection properties (which are just the edge-type and properties set in the add_edges method). Then we must register the function with the BioNet simulator:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "from bmtk.simulator.bionet.pyfunction_cache import add_weight_function\n",
    "\n",
    "def gaussianLL(edge_props, source, target):\n",
    "    src_tuning = source['tuning_angle']\n",
    "    tar_tuning = target['tuning_angle']\n",
    "    w0 = edge_props[\"syn_weight\"]\n",
    "    sigma = edge_props[\"weight_sigma\"]\n",
    "\n",
    "    delta_tuning = abs(abs(abs(180.0 - abs(float(tar_tuning) - float(src_tuning)) % 360.0) - 90.0) - 90.0)\n",
    "    return w0 * math.exp(-(delta_tuning / sigma) ** 2)\n",
    "\n",
    "add_weight_function(gaussianLL)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The weights will be adjusted before each simulation, and the function can be changed between different runs.. Simply opening the edge_types.csv file with a text editor and altering the weight_function column allows users to take an existing network and readjust weights on-the-fly.\n",
    "\n",
    "Finally we are ready to run the simulation. Note that because this is a 400 cell simulation, this may be computationally intensive for some older computers and may take anywhere between a few minutes to half-an-hour to complete."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 08:13:26,256 [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 08:13:26,386 [INFO] Building cells.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Building cells.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 08:13:34,215 [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 08:14:57,315 [INFO] Building virtual cell stimulations for LGN_spikes\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Building virtual cell stimulations for LGN_spikes\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2022-02-18 08:15:31,294 [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 08:15:31,296 [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 08:15:31,298 [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 08:16:05,859 [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 08:16:41,807 [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 08:17:19,343 [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 08:17:59,416 [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 08:18:40,657 [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 08:19:26,193 [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 08:19:26,250 [INFO] Simulation completed in 3.0 minutes, 54.96 seconds \n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO:NEURONIOUtils:Simulation completed in 3.0 minutes, 54.96 seconds \n"
     ]
    }
   ],
   "source": [
    "from bmtk.simulator import bionet\n",
    "\n",
    "\n",
    "conf = bionet.Config.from_json('sim_ch04/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 results\n",
    "\n",
    "Results of the simulation, as specified in the config, are saved into the output directory. Using the analyzer functions, we can do things like plot the raster plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 45556 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 45556 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from bmtk.analyzer.spike_trains import plot_raster, plot_rates_boxplot\n",
    "\n",
    "plot_raster(config_file='sim_ch04/config.json', group_by='pop_name')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "and the rates of each node"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 9284 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "[encoded data, 9284 characters]\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "plot_rates_boxplot(config_file='sim_ch04/config.json', group_by='pop_name')"
   ]
  },
  {
   "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": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 35844 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_ch04/config.json', group_by='pop_name', report_name='v_report')"
   ]
  },
  {
   "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
}
