{
  "cells": [
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "%matplotlib inline"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Gap Junctions: Inhibitory network example\n\nThis script simulates an inhibitory network of 500 Hodgkin-Huxley neurons.\nWithout the gap junctions (meaning for ``gap_weight = 0.0``) the network shows\nan asynchronous irregular state that is caused by the external excitatory\nPoissonian drive being balanced by the inhibitory feedback within the\nnetwork. With increasing `gap_weight` the network synchronizes:\n\nFor a lower gap weight of 0.3 nS the network remains in an asynchronous\nstate. With a weight of 0.54 nS the network switches randomly between the\nasynchronous to the synchronous state, while for a gap weight of 0.7 nS a\nstable synchronous state is reached.\n\nThis example is also used as test case 2 (see Figure 9 and 10)\nin [1]_.\n\n## References\n\n.. [1] Hahne et al. (2015) A unified framework for spiking and gap-junction\n       interactions in distributed neuronal network simulations, Front.\n       Neuroinform. http://dx.doi.org/10.3389/neuro.11.012.2008\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "import nest\nimport matplotlib.pyplot as plt\nimport numpy\n\nn_neuron = 500\ngap_per_neuron = 60\ninh_per_neuron = 50\ndelay = 1.0\nj_exc = 300.\nj_inh = -50.\nthreads = 8\nstepsize = 0.05\nsimtime = 501.\ngap_weight = 0.3\n\nnest.ResetKernel()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "First we set the random seed, adjust the kernel settings and create\n``hh_psc_alpha_gap`` neurons, ``spike_recorder`` and ``poisson_generator``.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "numpy.random.seed(1)\n\nnest.SetKernelStatus({'resolution': 0.05,\n                      'total_num_virtual_procs': threads,\n                      'print_time': True,\n                      # Settings for waveform relaxation\n                      # 'use_wfr': False uses communication in every step\n                      # instead of an iterative solution\n                      'use_wfr': True,\n                      'wfr_comm_interval': 1.0,\n                      'wfr_tol': 0.0001,\n                      'wfr_max_iterations': 15,\n                      'wfr_interpolation_order': 3})\n\nneurons = nest.Create('hh_psc_alpha_gap', n_neuron)\n\nsr = nest.Create(\"spike_recorder\")\npg = nest.Create(\"poisson_generator\", params={'rate': 500.0})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Each neuron shall receive ``inh_per_neuron = 50`` inhibitory synaptic inputs\nthat are randomly selected from all other neurons, each with synaptic\nweight ``j_inh = -50.0`` pA and a synaptic delay of 1.0 ms. Furthermore each\nneuron shall receive an excitatory external Poissonian input of 500.0 Hz\nwith synaptic weight ``j_exc = 300.0`` pA and the same delay.\nThe desired connections are created with the following commands:\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "conn_dict = {'rule': 'fixed_indegree',\n             'indegree': inh_per_neuron,\n             'allow_autapses': False,\n             'allow_multapses': True}\n\nsyn_dict = {'synapse_model': 'static_synapse',\n            'weight': j_inh,\n            'delay': delay}\n\nnest.Connect(neurons, neurons, conn_dict, syn_dict)\n\nnest.Connect(pg, neurons, 'all_to_all',\n             syn_spec={'synapse_model': 'static_synapse',\n                       'weight': j_exc,\n                       'delay': delay})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Then the neurons are connected to the ``spike_recorder`` and the initial\nmembrane potential of each neuron is set randomly between -40 and -80 mV.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "nest.Connect(neurons, sr)\n\nneurons.V_m = nest.random.uniform(min=-80., max=-40.)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "Finally gap junctions are added to the network. $(60*500)/2$ ``gap_junction``\nconnections are added randomly resulting in an average of 60 gap-junction\nconnections per neuron. We must not use the ``fixed_indegree`` oder\n``fixed_outdegree`` functionality of ``nest.Connect()`` to create the\nconnections, as ``gap_junction`` connections are bidirectional connections\nand we need to make sure that the same neurons are connected in both ways.\nThis is achieved by creating the connections on the Python level with the\n`random` module of the Python Standard Library and connecting the neurons\nusing the ``make_symmetric`` flag for ``one_to_one`` connections.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "n_connection = int(n_neuron * gap_per_neuron / 2)\nneuron_list = neurons.tolist()\nconnections = numpy.random.choice(neuron_list, [n_connection, 2])\n\nfor source_node_id, target_node_id in connections:\n    nest.Connect(nest.NodeCollection([source_node_id]),\n                 nest.NodeCollection([target_node_id]),\n                 {'rule': 'one_to_one', 'make_symmetric': True},\n                 {'synapse_model': 'gap_junction', 'weight': gap_weight})"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "In the end we start the simulation and plot the spike pattern.\n\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "collapsed": false
      },
      "outputs": [],
      "source": [
        "nest.Simulate(simtime)\n\ntimes = sr.get('events', 'times')\nspikes = sr.get('events', 'senders')\nn_spikes = sr.n_events\n\nhz_rate = (1000.0 * n_spikes / simtime) / n_neuron\n\nplt.figure(1)\nplt.plot(times, spikes, 'o')\nplt.title('Average spike rate (Hz): %.2f' % hz_rate)\nplt.xlabel('time (ms)')\nplt.ylabel('neuron no')\nplt.show()"
      ]
    }
  ],
  "metadata": {
    "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.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}