from netpyne import specs, sim
netParams = specs.NetParams()
simConfig = specs.SimConfig()

"""
Explanation: Running NetPyNE in a Jupyter Notebook
Preliminaries
Hopefully you already completed these preliminaries by following the instructions at https://github.com/Neurosim-lab/netpyne/blob/development/netpyne/tutorials/README.md.  We will now walk you through how we installed the NetPyNE tutorials.
We don't want to affect your system in any way, so we will operate from a virtual environment. These preliminary steps must be completed before going through this tutorial. You can't complete the preliminary steps from within Jupyter because you can't enter a virtual environment in Jupyter, you have to switch to a kernel made from your virtual environment.
First we will empty your path of all but essentials. Then we will create and activate a virtual environment. Then we will update pip and install some necessary packages in the virtual environment, and finally we will create a kernel from the virtual environment that can be used by Jupyter.  
Create and activate a virtual environment
First, open a Terminal and switch to the directory where you downloaded this notebook:
cd netpyne_tuts

Next, clear your PATH of all but the essentials. Don't worry, your normal PATH will return the next time you open a Terminal.
export PATH=/bin:/usr/bin

Next, create a virtual environment named "env":
python3 -m venv env

Check to see where you are currently running Python from:
which python3

Enter your new virtual environment:
source env/bin/activate

You should see in your prompt that you are in env.  
Now see where you are running Python from:
which python3

It should come from inside your new virtual environment.  Any changes we make here will only exist in the env directory that was created here.  
To exit your virtual environment, enter:
deactivate

Your prompt should reflect the change.  To get back in, enter:
source env/bin/activate

Update pip and install packages
We will now update pip and install some necessary packages in the virtual environment. From inside your virtual environment, enter:
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade ipython
python3 -m pip install --upgrade ipykernel
python3 -m pip install --upgrade jupyter

Make a Jupyter kernel out of this virtual environment
Now we will create a kernel that can be used by Jupyter Notebooks.  Enter:
ipython kernel install --user --name=env

Install NEURON and NetPyNE
python3 -m pip install --upgrade neuron
python3 -m pip install --upgrade netpyne

Launch this notebook in Jupyter Notebook
Now we will launch Jupyter from within the virtual environment.  Enter:
jupyter notebook netpyne_tut1.ipynb

This should open a web browser with Jupyter running this notebook.  From the menu bar, click on Kernel, hover over Change kernel and select env.  We are now operating in the virtual environment (see env in the upper right instead of Python 3) and can begin the tutorial.
Single line command
Entering the following single line command should perform all the previous steps and launch this tutorial in a Jupyter notebook in your web browser:
git clone https://github.com/Neurosim-lab/netpyne.git &amp;&amp; cd netpyne/netpyne/tutorials/netpyne_tut1 &amp;&amp; export PATH=/bin:/usr/bin &amp;&amp; python3 -m venv env &amp;&amp; source env/bin/activate &amp;&amp; python3 -m pip install --upgrade pip &amp;&amp; python3 -m pip install --upgrade ipython &amp;&amp; python3 -m pip install --upgrade ipykernel &amp;&amp; python3 -m pip install --upgrade jupyter &amp;&amp; ipython kernel install --user --name=env &amp;&amp; jupyter notebook netpyne_tut1.ipynb

To run this again in the future
Be sure to enter your virtual environment before running Jupyter!
cd netpyne_tuts
source env/bin/activate
jupyter notebook netpyne_tut1.ipynb

And then make sure you are in the env kernel in Jupyter.
Tutorial 1 -- a simple network with one population
Now we are ready to start NetPyNE Tutorial 1, which will create a simple network model we can simulate.  We will create a fairly simple network model of 40 pyramidal-like, two-compartment neurons with standard Hodgkin-Huxley dynamics in the somas and passive dynamics in the dendrites.  We will then connect the neurons randomly with a 10% probability of connection using a standard double-exponential synapse model.  Finally, we will add a current clamp stimulus to one cell to activate the network.  Then we will explore the model.
Instantiate network parameters and simulation configuration
You need two things to define a model/simulation in NetPyNE: 1) the parameters of the network and all its components (netParams) and 2) the configuration of the simulation (simConfig).  These requirements exist as objects in NetPyNE.  Let's instantiate them now.
End of explanation
"""


netParams.cellParams['pyr'] = {}
netParams.cellParams['pyr']['secs'] = {}

"""
Explanation: These NetPyNE objects come with a lot of defaults set which you can explore with tab completion, but we'll focus on that more later.
We are going to plunge ahead and build our model: a simple network of 40 pyramidal-like two-compartment neurons with standard Hodgkin-Huxley dynamics in the soma and passive dynamics in the dendrite.  
Create a cell model
First we will add a cell type to our model by adding a dictionary named pyr to the Cell Parameters dictionary (cellParams) in the Network Parameters dictionary (netParams).  We will then add an empty dictionary named secs to hold our compartments.
End of explanation
"""


netParams.cellParams['pyr']['secs']['soma'] = {}

netParams.cellParams['pyr']['secs']['soma']['geom'] = {
    "diam": 12,
    "L": 12,
    "Ra": 100.0,
    "cm": 1
    }

netParams.cellParams['pyr']['secs']['soma']['mechs'] = {"hh": {
    "gnabar": 0.12,
    "gkbar": 0.036,
    "gl": 0.0003,
    "el": -54.3
    }}

"""
Explanation: Specify the soma compartment properties
Now we will define our soma, by adding a geom dictionary defining the geometry of the soma and a mechs dictionary defining the biophysical mechanics being added to the soma.
End of explanation
"""


dend = {}

dend['geom'] = {"diam": 1.0,
                "L": 200.0,
                "Ra": 100.0,
                "cm": 1,
               }

dend['mechs'] = {"pas": 
                    {"g": 0.001,
                     "e": -70}
                }

"""
Explanation: The hh mechanism is builtin to NEURON, but you can see its .mod file here:
https://github.com/neuronsimulator/nrn/blob/master/src/nrnoc/hh.mod
It is the original Hodgkin-Huxley treatment for the set of sodium, potassium, and leakage channels found in the squid giant axon membrane.
Specify the dendrite compartment properties
Next will do the same thing for the dendrite compartment, but we will do it slightly differently.  We will first build up a dend dictionary and then add it to the cell model dictionary pyr when we are done.
End of explanation
"""


dend['topol'] = {"parentSec": "soma",
                 "parentX": 1.0,
                 "childX": 0,
                }

"""
Explanation: The pas mechanim is a simple leakage channel and is builtin to NEURON.  Its .mod file is available here:
https://github.com/neuronsimulator/nrn/blob/master/src/nrnoc/passive.mod
In order to connect the dendrite compartment to the soma compartment, we must add a topol dictionary to our dend dictionary.
End of explanation
"""


netParams.cellParams['pyr']['secs']['dend'] = dend

"""
Explanation: With our dend section dictionary complete, we must now add it to the pyr cell dictionary.
End of explanation
"""


netParams.popParams['E'] = {
    "cellType": "pyr",
    "numCells": 40,
}

"""
Explanation: Our two-compartment cell model is now completely specified.  Our next step is to create a population of these cells.
Create a population of cells
NetPyNE uses populations of cells to specify connectivity.  In this tutorial, we will create just one population which we will call E (for excitatory).  It will be made of the pyr cells we just specified, and we want 40 of them.
End of explanation
"""


netParams.synMechParams['exc'] = {
    "mod": "Exp2Syn",
    "tau1": 0.1,
    "tau2": 1.0,
    "e": 0
}

"""
Explanation: Create a synaptic model
We need a synaptic mechanism to connect our cells with.  We will create one called exc by adding a dictionary to the synaptic mechanism parameters dictionary (synMechParams).  The synapse mod used (Exp2Syn) is a simple double-exponential which is builtin to NEURON.  It's .mod file is available here:
https://github.com/neuronsimulator/nrn/blob/master/src/nrnoc/exp2syn.mod
End of explanation
"""


netParams.connParams['E->E'] = {
    "preConds": {"pop": "E"},
    "postConds": {"pop": "E"},
    "weight": 0.005,
    "probability": 0.1,
    "delay": 5.0,
    "synMech": "exc",
    "sec": "dend",
    "loc": 1.0,
}

"""
Explanation: Connect the cells
Now we will specify the connectivity in our model by adding an entry to the connParams dictionary.  We will call our connectivity rule E->E as it will define connectivity from our E population to our E population.
We will use the synMech exc, which we defined above.  For this synaptic mechanism, a weight of about 0.005 is appropriate.  These cells will have a 10% probability of getting connected, and will be activated five milliseconds after an action potential occurs in the presynaptic cell.  Synapses will occur on the dend section at its very end (location 1.0)
End of explanation
"""


simConfig.filename = "netpyne_tut1"
simConfig.duration = 200.0
simConfig.dt = 0.1

"""
Explanation: Set up the simulation configuration
End of explanation
"""


simConfig.recordCells = [0]
simConfig.recordTraces = {
    "V_soma": {
        "sec": "soma",
        "loc": 0.5,
        "var": "v",
    },
    "V_dend": {
        "sec": "dend",
        "loc": 1.0,
        "var": "v",
    }
}

"""
Explanation: We will record from from the first cell (0) and we will record the voltage in the middle of the soma and the end of the dendrite.
End of explanation
"""


simConfig.analysis = {
    "plotTraces": {
        "include": [0],
        "saveFig": True,
    },
    "plotRaster": {
        "saveFig": True,
    }
}

"""
Explanation: Finally we will set up some plots to be automatically generated and saved.
End of explanation
"""


%matplotlib inline

"""
Explanation: To see plots in the notebook, we first have to enter the following command.
End of explanation
"""


sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)

"""
Explanation: Create, simulate, and analyze the model
Use one simple command to create, simulate, and analyze the model.
End of explanation
"""


fig, figData = sim.analysis.plotTraces(overlay=True)

"""
Explanation: We can see that there was no spiking in the network, and thus the spike raster was not plotted.  But there should be one new file in your directory: netpyne_tut1_traces.png.  Take a look.  Not too interesting, the cell just settles into its resting membrane potential.
Let's overlay the traces.
End of explanation
"""


fig, figData = sim.analysis.plot2Dnet()

"""
Explanation: Plot the 2D connectivity of the network
Now we can take a look at the physical layout of our network model.  You can see all the options available for plot2Dnet here:
http://netpyne.org/netpyne.analysis.network.html#netpyne.analysis.network.plot2Dnet
End of explanation
"""


fig, figData = sim.analysis.plotConn()

"""
Explanation: Plot the connectivity matrix
You can see all the options available for plotConn here:
http://netpyne.org/netpyne.analysis.network.html#netpyne.analysis.network.plotConn
End of explanation
"""


fig, figData = sim.analysis.plotConn(feature='weight', groupBy='cell')

"""
Explanation: Not very interesting with just one population, but we can also look at the cellular level connectivity.
End of explanation
"""


netParams.stimSourceParams['IClamp1'] = {
    "type": "IClamp",
    "dur": 5,
    "del": 20,
    "amp": 0.1,
}

"""
Explanation: Add a stimulation
We'll need to kickstart this network to see some activity -- let's inject current into one of the cells.  First we need to add an entry to the Stimulation Source Parameters dictionary (stimSourceParams).  We'll call our stimulation IClamp1, and we'll use the standard NEURON type: IClamp.  The current injection will last for a duration of 20 ms, it will start at a delay of 5 ms, and it will have an amplitude of 0.1 nanoAmps.
End of explanation
"""


netParams.stimTargetParams['IClamp1->cell0'] = {
    "source": "IClamp1",
    "conds": {"cellList": [0]},
    "sec": "dend",
    "loc": 1.0,
}

"""
Explanation: Now we need to add a target for our stimulation.  We do that by adding a dictionary to the Stimulation Target Parameters dictionary (stimTargetParams).  We'll call this connectivity rule IClamp1->cell0, because it will go from the source we just created (IClamp1) and the first cell in our population.  The stimulation (current injection in this case) will occur in our dend section at the very tip (location of 1.0).
End of explanation
"""


sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)

"""
Explanation: Create, simulate, and analyze the model
End of explanation
"""


fig, figData = sim.analysis.plotTraces(overlay=True)

fig, figData = sim.analysis.plotRaster(marker='o', markerSize=50)

"""
Explanation: Now we see spiking in the network, and the raster plot appears.  Let's improve the plots a little bit.
End of explanation
"""


fig, figData = sim.analysis.plotConn()

"""
Explanation: You can see all of the options available in plotTraces here:
http://netpyne.org/netpyne.analysis.traces.html#netpyne.analysis.traces.plotTraces
You can see all of the options available in plotRaster here:
http://netpyne.org/netpyne.analysis.spikes.html#netpyne.analysis.spikes.plotRaster
Plot the connectivity matrix
End of explanation
"""


simConfig.recordTraces = {}
simConfig.analysis['plotRaster'] = False

"""
Explanation: Record and plot a variety of traces
Now let's explore the model by recording and plotting a variety of traces.  First let's clear our recordTraces dictionary and turn off the automatic raster plot.
End of explanation
"""


simConfig.recordTraces['gNa'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gna'}
simConfig.recordTraces['gK'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gk'}
simConfig.recordTraces['gL'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gl'}

"""
Explanation: Record and plot the somatic conductances
Let's record and plot the somatic conductances.  We need to take a look at the hh mod file to see what the variables are called.  The file is available here: https://github.com/neuronsimulator/nrn/blob/master/src/nrnoc/hh.mod
We can see that the conductances are called gna, gk, and gl.  Let's set up recording for these conductances in the middle of the soma.
End of explanation
"""


sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)

"""
Explanation: Then we can re-run the simulation.
End of explanation
"""


fig, figData = sim.analysis.plotTraces(timeRange=[90, 110], overlay=True)

"""
Explanation: Let's zoom in on one spike and overylay the traces.
End of explanation
"""


simConfig.recordTraces = {}
simConfig.recordTraces['iSyn0'] = {'sec': 'dend', 'loc': 1.0, 'synMech': 'exc', 'var': 'i'}
simConfig.recordTraces['V_dend'] = {'sec': 'dend', 'loc': 1.0, 'var': 'v'}

sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)

"""
Explanation: Record from synapses
Our synapses are set up to use Exp2Syn, which is builtin to NEURON.  Its mod file is available here: https://github.com/neuronsimulator/nrn/blob/master/src/nrnoc/exp2syn.mod
Looking in the file, we can see that its current variable is called i.  Let's record that and the voltage in the dendrite.
End of explanation
"""


sim.net.allCells[0].keys()

"""
Explanation: That's the first synapse created in that location, but there are likely multiple synapses.  Let's plot all the synaptic currents entering cell 0.  First we need to see what they are.  The network is defined in sim.net.  Type in sim.net. and then push Tab to see what's available.
The data for cell 0 is in sim.net.allCells[0].
End of explanation
"""


sim.net.allCells[0]['conns']

"""
Explanation: The connections coming onto the cell are in conns.
End of explanation
"""


simConfig.recordTraces = {}
simConfig.recordTraces['V_soma'] = {'sec': 'soma', 'loc': 0.5, 'var': 'v'}
simConfig.recordTraces['V_dend'] = {'sec': 'dend', 'loc': 1.0, 'var': 'v'}

syn_plots = {}
for index, presyn in enumerate(sim.net.allCells[0]['conns']):    
    trace_name = 'i_syn_' + str(presyn['preGid'])
    syn_plots[trace_name] = None 
    simConfig.recordTraces[trace_name] = {'sec': 'dend', 'loc': 1.0, 'synMech': 'exc', 'var': 'i', 'index': index}

print(simConfig.recordTraces)

sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)

"""
Explanation: So we want to record six synaptic currents.  Lets do that in a for loop at the same time creating a dictionary to hold the synaptic trace names as keys (and later the trace arrays as values).
End of explanation
"""


sim.allSimData.keys()

"""
Explanation: Extracting recorded data
Let's make our synaptic currents plot nicer.  We'll make a figure with two plots, the top one will be the somatic and dendritic voltage and the bottom plot will be all of the synaptic currents overlaid.
First we'll have to extract the data.  Simulation data gets stored in the dictionary sim.allSimData.
End of explanation
"""


sim.allSimData.V_soma.keys()


"""
Explanation: spkt is an array of the times of all spikes in the network.  spkid is an array of the universal index (GID) of the cell spiking.   t is an array of the time for traces.  Our traces appear as we named them, and each is a dictionary with its key being cell_GID and its value being the array of the trace.
End of explanation
"""


time = sim.allSimData['t']
v_soma = sim.allSimData['V_soma']['cell_0']
v_dend = sim.allSimData['V_dend']['cell_0']

for syn_plot in syn_plots:
    syn_plots[syn_plot] = sim.allSimData[syn_plot]['cell_0']

"""
Explanation: So let's extract our data.
End of explanation
"""


import matplotlib.pyplot as plt
fig = plt.figure()

plt.subplot(211)
plt.plot(time, v_soma, label='v_soma')
plt.plot(time, v_dend, label='v_dend')
plt.legend()
plt.xlabel('Time (ms)')
plt.ylabel('Membrane potential (mV)')

plt.subplot(212)
for syn_plot in syn_plots:
    plt.plot(time, syn_plots[syn_plot], label=syn_plot)
plt.legend()
plt.xlabel('Time (ms)')
plt.ylabel('Synaptic current (nA)')

plt.savefig('syn_currents.jpg', dpi=600)

"""
Explanation: And now we can make our custom plot.
End of explanation
"""


%reset

"""
Explanation: Cleaning up our figure (reducing font size, etc.) will be left as an exercise.  See the matplotlib users guide here:
https://matplotlib.org/users/index.html
Now we will put all of this together into a single file.  But first, let's clear our workspace with the following command.
End of explanation
"""


from netpyne import specs, sim
netParams = specs.NetParams()
simConfig = specs.SimConfig()

# Create a cell type
# ------------------

netParams.cellParams['pyr'] = {}
netParams.cellParams['pyr']['secs'] = {}

# Add a soma section
netParams.cellParams['pyr']['secs']['soma'] = {}
netParams.cellParams['pyr']['secs']['soma']['geom'] = {
    "diam": 12,
    "L": 12,
    "Ra": 100.0,
    "cm": 1
    }

# Add hh mechanism to soma
netParams.cellParams['pyr']['secs']['soma']['mechs'] = {"hh": {
    "gnabar": 0.12,
    "gkbar": 0.036,
    "gl": 0.0003,
    "el": -54.3
    }}

# Add a dendrite section
dend = {}
dend['geom'] = {"diam": 1.0,
                "L": 200.0,
                "Ra": 100.0,
                "cm": 1,
               }

# Add pas mechanism to dendrite
dend['mechs'] = {"pas": 
                    {"g": 0.001,
                     "e": -70}
                }

# Connect the dendrite to the soma
dend['topol'] = {"parentSec": "soma",
                 "parentX": 1.0,
                 "childX": 0,
                }

# Add the dend dictionary to the cell parameters dictionary
netParams.cellParams['pyr']['secs']['dend'] = dend

# Create a population of these cells
# ----------------------------------
netParams.popParams['E'] = {
    "cellType": "pyr",
    "numCells": 40,
}

# Add Exp2Syn synaptic mechanism
# ------------------------------
netParams.synMechParams['exc'] = {
    "mod": "Exp2Syn",
    "tau1": 0.1,
    "tau2": 1.0,
    "e": 0
}

# Define the connectivity
# -----------------------
netParams.connParams['E->E'] = {
    "preConds": {"pop": "E"},
    "postConds": {"pop": "E"},
    "weight": 0.005,
    "probability": 0.1,
    "delay": 5.0,
    "synMech": "exc",
    "sec": "dend",
    "loc": 1.0,
}

# Add a stimulation
# -----------------
netParams.stimSourceParams['IClamp1'] = {
    "type": "IClamp",
    "dur": 5,
    "del": 20,
    "amp": 0.1,
}

# Connect the stimulation
# -----------------------
netParams.stimTargetParams['IClamp1->cell0'] = {
    "source": "IClamp1",
    "conds": {"cellList": [0]},
    "sec": "dend",
    "loc": 1.0,
}

# Set up the simulation configuration
# -----------------------------------

simConfig.filename = "netpyne_tut1"
simConfig.duration = 200.0
simConfig.dt = 0.1

# Record from cell 0
simConfig.recordCells = [0]

# Record the voltage at the soma and the dendrite
simConfig.recordTraces = {
    "V_soma": {
        "sec": "soma",
        "loc": 0.5,
        "var": "v",
    },
    "V_dend": {
        "sec": "dend",
        "loc": 1.0,
        "var": "v",
    }
}

# Record somatic conductances
#simConfig.recordTraces['gNa'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gna'}
#simConfig.recordTraces['gK'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gk'}
#simConfig.recordTraces['gL'] = {'sec': 'soma', 'loc': 0.5, 'mech': 'hh', 'var': 'gl'}

# Automatically generate some figures
simConfig.analysis = {
    "plotTraces": {
        "include": [0],
        "saveFig": True,
        "overlay": True,
    },
    "plotRaster": {
        "saveFig": True,
        "marker": "o",
        "markerSize": 50,
    },
    "plotConn": {
        "saveFig": True,
        "feature": "weight",
        "groupby": "cell",
        "markerSize": 50,
    },
    "plot2Dnet": {
        "saveFig": True,
    },
}


# Create, simulate, and analyze the model
# ---------------------------------------
sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)


# Set up the recording for the synaptic current plots
syn_plots = {}
for index, presyn in enumerate(sim.net.allCells[0]['conns']):    
    trace_name = 'i_syn_' + str(presyn['preGid'])
    syn_plots[trace_name] = None 
    simConfig.recordTraces[trace_name] = {'sec': 'dend', 'loc': 1.0, 'synMech': 'exc', 'var': 'i', 'index': index}

    
# Create, simulate, and analyze the model
# ---------------------------------------
sim.createSimulateAnalyze(netParams=netParams, simConfig=simConfig)
    

# Extract the data
# ----------------
time = sim.allSimData['t']
v_soma = sim.allSimData['V_soma']['cell_0']
v_dend = sim.allSimData['V_dend']['cell_0']

for syn_plot in syn_plots:
    syn_plots[syn_plot] = sim.allSimData[syn_plot]['cell_0']

    
# Plot our custom figure
# ----------------------
import matplotlib.pyplot as plt
fig = plt.figure()

plt.subplot(211)
plt.plot(time, v_soma, label='v_soma')
plt.plot(time, v_dend, label='v_dend')
plt.legend()
plt.xlabel('Time (ms)')
plt.ylabel('Membrane potential (mV)')

plt.subplot(212)
for syn_plot in syn_plots:
    plt.plot(time, syn_plots[syn_plot], label=syn_plot)
plt.legend()
plt.xlabel('Time (ms)')
plt.ylabel('Synaptic current (nA)')

plt.savefig('syn_currents.jpg', dpi=600)

"""
Explanation: This tutorial in a single Python file
End of explanation
"""


