<jupyter_start><jupyter_text>The Spiking Neuron Model - Coding Challenge Problems (Part 4)  Integrate-and-Fire with Synaptic Conductance
This interactive document is meant to be followed as the reader makes their way through chapter: *The Spiking Neuron Model*.  Each model presented in the chapter will have a section consisting of a step-by-step walkthrough of a simple Python implementation.  This is followed by an interface to run simulations with different parameter values to answer the Coding Challenge Problems.

For each model covered in the chapter, there is a section called **Coding Challenge Problems.**  This is where you will find user-interface components such as value sliders for various parameters.  Use these controls to answer the questions from the text.

**Content Creator**: <NAME>

**Content Reviewer**: <NAME>  How It WorksGoogle Colab Notebooks have both *Content* cells and *Code* cells. As you progress through the notebook, you MUST make sure to run each code cell as you come to them.  Otherwise, you may run into errors when executing a code cell. Each code cell has a Play button next to it which will execute the code. (Some code may be hidden by default. This is generally because the code is more complex and is not necessary to understand in order to complete the model implementations or to answer the chapter Coding Challenge Problems).**IMPORTANT**: You have been provided a link to view a **copy** of the original notebooks. You will find that you can edit the content of any cell. If you accidently change a cell, such as a line of code and/or run into errors as you try to run subsequent blocks, simply refresh the page, OR go to the *Runtime menu* and select *Restart runtime*. It is also suggested that you go to the *Edit menu* and select *Clear all outputs*. This will always allow you to revert the notebook to the original version (though you will have to run each code block again.)For each model covered in the chapter, there is a section called **Coding Challenge Problems**. This is where you will find user-interface components such as value sliders for various parameters. Use these controls to answer the questions from the text.  Execute the code block. **Initialize Setup**<jupyter_code>#@title Initialize Setup
#@markdown **(No need to understand this code, simply make sure you run this first).**
import sys
import functools as ft
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import ipywidgets as widgets
import scipy as sc

# [BLOCK TAG: INIT]

try:
  blockSet = [ ]
except:
  print('Something went wrong! Try Refreshing the page.')

blockTags = ['INIT','VP1','NP1','SS1','SS2','CS1','CS2','VR1']

def pushBlockStack(tag):
    if tag in blockSet:
        return 1
    indx = blockTags.index(tag)
    if len(blockSet) != indx:
        print('ERROR: BLOCK TAG:',tag,'executed out of sequence. Missing BLOCK TAG:', blockTags[indx-1])
        return 0
    else:
        blockSet.append(tag)
        return 1

def printError():
  message = 'Something went wrong!\n\n'  
  message = message + 'Check for the following:\n\n'
  message = message + '\t1.  All previous code blocks have been run the order they appear and output a success message.\n'
  message = message + '\t2.  No other code has been altered.\n\n'
  message = message + 'and then try running the code block again.'
  message = message + ' If there is still an error when executing the code block, try the following:\n\n'
  message = message + '\t1.  Go to the \'Runtime\' menu and select \'Restart Runtime\', then in the \'Edit\' menu, select \'Clear all outputs\'.\n'
  message = message + '\t2.  Refresh the page.\n\n'
  message = message + 'and be sure to run each of the previous code blocks again beginning with \'Initialize Setup\'.\n'
  print(message)
  return 0

def printSuccess(block):
  success = 0
  if len(block) == 0 or pushBlockStack(block) != 0:
    message = 'Success!  Move on to the next section.'
    print(message)
    success = 1
  return success

def checkVoltageParameters(Vrest, Vth, Vreset, Vspike):
  print('Checking Voltage Parameters... ')
  try:
      check_Vrest = Vrest
      check_Vth = Vth
      check_Vreset = Vreset
      check_Vspike = Vspike
  except:
    return 0
  else:
    vals = [Vrest, Vth, Vreset, Vspike]
    correct_vals = [-70, -54, -80, 0]
    if ft.reduce(lambda i, j : i and j, map(lambda m, k: m == k, vals, correct_vals),  False): 
      return 0
  return 1
 
def checkNeuronProperties(A, rm, Rm, Cm, tau_m, g_s, p_max, tau_s, EL, Es):
  print('Checking Neuron Properties... ')
  try:
      check_A = A
      check_rm = rm
      check_Cm = Cm
      check_tau_m = tau_m
      check_g_s = g_s
      check_p_max = p_max
      check_tau_s = tau_s
      check_EL = EL
      check_Es = Es
  except:
    return 0
  else:
    vals = [A, rm, Rm, Cm, tau_m, g_s, p_max, tau_s, EL, Es]
    correct_vals = [0.1, 1, 10, 1, 10, 0.5, 0.5, 10, -70, 0]
    if ft.reduce(lambda i, j : i and j, map(lambda m, k: m == k, vals, correct_vals),  False): 
      return 0
  return 1

def checkSimulationSetup(Vrest, Vinitial, t0, dt, t_final, time):
  print('Checking Simulation Setup...\n')
  try:
    check_Vrest = Vrest
    check_Vinitial = Vinitial
    check_t0 = t0
    check_dt = dt
    check_t_final = t_final
    check_time = time
  except:
    return 0
  else:
    vals = [Vrest, Vinitial, t0, dt, t_final]
    correct_vals = [-70, -70, 0, 1, 500]
    if ft.reduce(lambda i, j : i and j, map(lambda m, k: m == k, vals, correct_vals),  False):  
      if len(time) != 500 or time[0] != 0 or time[-1] != 499:
        return 0
  return 1

def checkValues():
  chk = 3
  if checkVoltageParameters(Vrest, Vth, Vreset, Vspike) < 1:
    print('FAIL\n')
    chk = chk - 1
  else:
    print('PASS\n')
  if checkNeuronProperties(A, rm, Rm, Cm, tau_m, g_s, p_max, tau_s, EL, Es) < 1:
    print('FAIL\n')
    chk = chk - 1  
  else:
    print('PASS\n')
  if checkSimulationSetup(Vrest, Vinitial, t0, dt, t_final, time) < 1:
    print('FAIL\n')
    chk = chk - 1
  else:
    print('PASS\n')
  return chk

def pre_spike_to_str(times):
  ret = ''
  for t in times:
    ret = ret + str(t) + ', '
  ret = ret[:-2]
  return ret

def pre_spike_to_list(times):
  ret = []
  pre_spike_list = times.split(', ')
  for t in pre_spike_list:
    ret.append(int(t))
  return ret

try:
  check_sys = sys
except:
  printError()
else:
  modulename = 'functools'
  if modulename not in sys.modules:
    printError()
  else:
    printSuccess('INIT')<jupyter_output><empty_output><jupyter_text>WalkthroughThe goal of this section is to extend the integrate-and-fire neuron model to incorporate *synaptic conductance*.A synaptic conductance is the product of synaptic transmission, that is, molecules called neurotransmitters are released across the synaptic cleft and bind to receptors on the postsynaptic neuron. Just as with persistent and transient conductance (seen in the Hodgkin-Huxley spiking neuron model), synaptic transmission and therefore synaptic conductance is modelled with parameters representing a maximal conductance and the open probability of an ion conducting channel. Membrane EquationRecall that the membrane equation is expressed as follows> $ \displaystyle \tau_{m}\frac{dV}{dt} = E_{L} - V(t) + r_{m}\overline{g}_{s}P_s(V-E_s) + R_{m}I_{e} $however, for this spiking neuron model, we will not be using any injected current $I_e$.  Thus, the membrane equation simplifies to> $ \displaystyle \tau_{m}\frac{dV}{dt} = E_{L} - V(t) + r_{m}\overline{g}_{s}P_s(V-E_s) $The parameter, $P_s$ represents the *open channel probability*, or the probability that a postsynaptic ion channel opens given that a spike was fired by the presynaptic neuron.  To compute $P_s$, under the assumption that multiple presynaptic spikes occur over the course of a model simulation, we use the following pair of differential equations> $ \displaystyle \tau_{s}\frac{dP_s}{dt} = eP_{max}S_{pre} - P_{s} $   with $ e = \exp(1)$, and  >   $ \displaystyle \tau_{s}\frac{dS_{pre}}{dt} = -S_{pre} $where $S_{pre}$ is set to 1 at each presynaptic spike and decays exponentially in the absence of spikes at the same rate as the open probability, $\tau_s$.  This parameter serves to increase the open channel probability by $eP_{max}$ at each presynaptic spike.  Voltage ParametersAs in the integrate-and-fire walkthrough, we begin by defining the voltage parameters that determine the *resting, threshold, spike*, and *reset* membrane potential values.*   $ V_{rest} = -70\;$*mV**   $ V_{th} = -54\;$*mV**   $ V_{reset} = -80\;$*mV**   $ V_{spike} = 0\;$*mV*<jupyter_code># [BLOCK TAG: VP1]

try:
  check_BlockSet = blockSet
except:
  print('ERROR: BLOCK TAG: VP1 executed out of sequence. Missing BLOCK TAG: INIT')
else:
  try: 
    ##################################################################################
    # Voltage Parameters - Units mV (1 mV = 1e-3 Volts)
    Vrest = -70
    Vth = -54
    Vreset = -80
    Vspike = 0
    ##################################################################################
  except:
    printError()
  else:
    printSuccess('VP1')<jupyter_output><empty_output><jupyter_text>Neuron PropertiesWe now define values for the Membrane Resistance $ R_{m} $, the Membrane Capacitance, $ C_{m} $, and calculate the Membrane Time Constant $\tau_{m}$.*    $ \displaystyle R_{m} = 10\;M\Omega $*    $ C_{m} = 1\;$*nF**    $ \tau_{m} = R_{m}C_{m} = 10\;$*ms*Let the neuronal surface area:*   $ A = 0.1\;$*mm*$^2$Thus,*   $ r_{m} = R_m \times A = 10 \times 0.1 = 1\;M\Omega \times\;$*mm*$^2$To incorporate a synaptic conductance into the integrate-and-fire model, we must explicitly define additional parameters including the specific membrane resistance $r_m$, and properties necessary to compute the evolution of the open channel probability, $P_s$; the corresponding maximal conductance per unit area, $\overline{g}_{s}$,the maximum value of $P_s$, denoted $P_{max}$, and $\tau_{s}$ which controls the speed at which the open channel probability evolves.*   $ P_s = 0 $*   $ \overline{g}_s = 0.5\;$*mS* / *mm$^2$**   $P_{max} = 1 $*    $\tau_{s} = 10\;$*ms*Last, we set the *reversal potential*, $ E_{L} $, to be equal to the neuron at rest, $V_{rest}$, and the reversal potential corresponding to the synaptic conductance, $E_{s}$.*    $ E_{K} = V_{rest} = -70\;$*mV**    $ E_{s} = 0\;$*mV*<jupyter_code># [BLOCK TAG: NP1]

try:
  ##################################################################################
  # Total Membrane Resistance - Units M_Ohm (1 M_Ohm = 1e6 Ohms)
  Rm = 10

  # Total Membrane Capacitance - Units nF (1 nF = 1e-6 Farads)
  Cm = 1

  # Membrane Time Constant - Units ms (1 ms = 1e-3 Seconds)
  tau_m = Rm*Cm

  # Neuron Surface Area - Units mm^2
  A = 0.1

  # Specific Membrane Resistance - Units M_Ohm * mm^2
  rm = Rm*A

  # Specific Conductance - Units mS / mm^2 (1 mS = 1e-3 Siemens)
  g_s = 0.5

  # Maximum Open Channel Probability
  p_max = 0.5

  # Synaptic Conductance Time Constant
  tau_s = 10

  # Leakage Conductance Reversal Potential
  EL = Vrest

  # Synaptic Conductance Reversal Potential
  Es =0
  ##################################################################################
except:
  printError()
else:
  printSuccess('NP1')<jupyter_output><empty_output><jupyter_text>Simulation SetupWe want to run a simulation to observe the evolution of the membrane potential $V$ overtime.  We begin with the neuron at resting conditions: $V = V_{rest} = -70\;$*mV*.<jupyter_code># [BLOCK TAG: SS1]

try:
  ##################################################################################
  # Initial Voltage
  Vinitial = Vrest

  # Initial Open Channel Probability 
  Ps_initial = 0

  # Initial Synaptic Current
  i_s_initial = rm*g_s*Ps_initial*(Vinitial - Es)
  ##################################################################################
except:
  printError()
else:
  printSuccess('SS1')<jupyter_output><empty_output><jupyter_text>We will be running a 500 ms simulation.  The following lines of code setup a time span for the simulation.  This is simply a matter of defining the start time $t_{0} = 0$ and the total length (in ms) of the simulation: $t_{final} = 500$.  

Throughout the simulation, we calculate the membrane potential $V$ at each *time-step*.  The time-step is the change in time for each iteration of the simulation, for example if $t_{0} = 0$, the next computation of $V$ is performed at $t_{0} + dt$.  

Thus, by setting $dt = 1$ (in ms), the simulation will compute $V$ at time $t = 1, 2, \ldots, t_{final}$.  For $dt = 1$, we compute $V$ at every 1 ms until the end of the simulation is reached.<jupyter_code># [BLOCK TAG: SS2]

try:
  ##################################################################################
  # Simulation Time Span (0 to 500ms, dt = 1ms)
  t0 = 0
  dt = 1
  t_final = 500

  # What does the range() function do?
  time = range(t0, t_final, dt)
  ##################################################################################
except:
  printError()
else:
  printSuccess('SS2')<jupyter_output><empty_output><jupyter_text>Computing and Storing $\frac{dV}{dt}$, $P_{s}[t]$, and $i_{s}[t]$We are about ready to finish the code implementation for simulating an integrate-and-fire model neuron with synaptic conductance.We need an array or list to store the values of the membrane potential $V$, $ P_{s}$, and the synaptic current $i_{s}$ at each time step.  To do this, we simply create empty lists $V[t]$, $Ps[t]$, and $i_{s}[t]$ with a length equal to the number of time-steps of our simulation.  We then set $V[0] = V_{initial} $,  $Ps[t] = Ps_{initial}$, and $i_{s}[0] =  i_{s_{\Large initial}}$ as we begin our simulation at resting conditions. We also initialize $ S_{pre} = 0 $.Last,  we 'artificially' set the presynaptic spike times to $ \displaystyle t =  [0, 50, 150, 190, 300, 320, 400, 410] $.<jupyter_code># [BLOCK TAG: CS1]

try:
  ##################################################################################
  # Create a list V[t] to store the value of V at each time-step dt
  V = [0] * len(time)

  # Set the initial value at time t = t0 to the initial value Vinitial
  V[0] =  Vinitial

  # Create a list Ps[t] to store the value of P_s at each time-step dt
  Ps = [0] * len(time)

  # Set the initial value at time t = t0 to the initial value Ps_initial
  Ps[0] =  Ps_initial

  # Create a list i_s(t) to store the value of synaptic current i_s at each time-step dt
  i_s = [0] * len(time)

  # Set the initial value at time t = t0 to the initial value i_s_initial
  i_s[0] =  i_s_initial

  # Times t at which the presynaptic neuron spikes
  S_pre_t= [0, 50, 150, 190, 300, 320, 400, 410]
  S_pre = 0

  # Create a list Spre[t] to store the value of S_pre at each time-step dt
  Spre  = [0] * len(time)

  # Set the initial value at time t = t0 to the initial value S_pre
  Spre[0] = S_pre
  ##################################################################################
except:
  printError()
else:
  printSuccess('CS1')<jupyter_output><empty_output><jupyter_text>Finally, we run our simulation according to the updated *pseudocode*---*for each time-step from $t = t_{0}$ to $t = t_{final}$*> *If the presynaptic neuron did spike*>> $ \displaystyle S_{pre} = 1 $> *otherwise*>> *Use Euler's Method of Numerical Integration*>>  $ \displaystyle dS_{pre} = -S_{pre}[t]\frac{dt}{\tau_{s}}  $>> *Update* $S_{pre}[t+1] = S_{pre}[t] +dS_{pre} $> *Use Euler's Method of Numerical Integration*> $\displaystyle dP_s = \frac{dt}{\tau_s}(eP_{max}S_{pre} - P_{s})$> *Update* $P_s[t+1] = P_s[t] + dP_s$> *Use Euler's Method of Numerical Integration*> $\displaystyle dV = \frac{dt}{\tau_{m}}(E_{L} - r_{m}\overline{g}_{s}P_s[t](V[t] - E_{s}) )$> *Update* $V[t+1] = V[t] + dV$> *If $\ V[t+1] \geq V_{th} $* >> $V[t] = V_{spike}$>> $V[t+1] = V_{reset}$*end*---This translates to the following Python code.Here we make use of the **numpy** library (to learn more about how to use this library, go to  https://numpy.org/doc/stable/).<jupyter_code># [BLOCK TAG: CS2]

try:
  chk = checkValues()
except:
  printError()
else:
  try:
    ##################################################################################
    # For each timestep we compute V and store the value
    for t in time[0:-2]:

        # If presynaptic neuron spikes
        if t  in S_pre_t:
            S_pre = 1
        # otherwise
        else:
            dS_pre = (dt/tau_s)*(-S_pre)
            S_pre = S_pre + dS_pre

        # Store value of Spre
        Spre[t+1] = S_pre

        # Using Euler's Method for Numerical Integration (See Chapter Text)
        # We compute the change in open probability as follows (using the model equation)
        dPs = (dt/tau_s)*(np.exp(1)*p_max*S_pre - Ps[t])

        # Store this new value into our list
        Ps[t+1] = Ps[t] + dPs

        # Store the synaptic current value
        i_s[t] = rm*g_s*Ps[t]*V[t]

        # Using Euler's Method for Numerical Integration (See Chapter Text)
        # We compute the change in voltage dV as follows (using the model equation)
        dV = (dt/tau_m)*(EL - V[t] - i_s[t])

        # Store this new value into our list
        V[t+1] = V[t] + dV

        # If the voltage value we computed crosses the threshold value Vth, a spike occurs.
        if V[t+1] >= Vth:
            # In the event of a spike, the membrane potential spikes up to Vspike mV
            V[t] = Vspike
            # Followed by immediate hyperpolarization to its Vreset value.
            V[t+1] = Vreset
    ##################################################################################     
  except:
    printError()
  else:
    if chk == 3:
      printSuccess('CS2')
    else:
      printError()<jupyter_output><empty_output><jupyter_text>Visualizing ResultsNow we have values of $V$, $P_s$, and the synaptic current $i_s$ for each time-step of the simulation, we can visualize the results by using Python to plot the data.   This makes use of another widely used library **plotly** (to learn more about plotting data with this library, go to https://plotly.com/python/reference/index/).<jupyter_code># [BLOCK TAG: VR1]

try:
  if 'CS2' not in blockSet:
    print('ERROR: BLOCK TAG: VR1 executed out of sequence. Missing BLOCK TAG: CS2')
  else:
    try:
      ##################################################################################
      # Plotting
      x = list(time[0:-2])

      # Plot data
      fig = make_subplots(
          rows=3, cols=1,  shared_xaxes = True, vertical_spacing=0.1, 
          subplot_titles=('V over Time', 'i_s over Time', 'P_s over Time')
      )

      # Add traces
      fig.add_trace(go.Scatter(name='V', x=x, y=V), row=1, col=1)
      fig.add_trace(go.Scatter(name='i_s', x=x, y=i_s), row=2, col=1)
      fig.add_trace(go.Scatter(name='P_s', x=x, y=Ps), row=3, col=1)
      fig.add_trace(go.Scatter(name='S_pre', x=x, y=Spre), row=3, col=1)

      fig.add_shape(name = 'V_{th}',
                    type='line',
                    x0=min(x),
                    y0=-54,
                    x1=max(x),
                    y1=-54,
                    line=dict(color='Red'),
                    line_dash='dash', row=1, col=1)

      # Update xaxis properties
      fig.update_xaxes(title_text="Time t (ms)", row=3, col=1)

      # Update yaxis properties
      fig.update_yaxes(title_text="Membrane Potential V (mV)", range=[-90,10], row=1, col=1)
      fig.update_yaxes(title_text="Synaptic Current i_s (nA)", row=2, col=1)
      fig.update_yaxes(title_text="P_s (Probability)", range=[0,1], row=3, col=1)

      # Update title and size
      fig.update_layout(height=800, width=700, 
                                      title_text='Integrate-and-Fire Model Neuron with Synaptic Conductance',
                                      showlegend = True)

      # Update theme
      fig.layout.template = 'plotly_dark'

      # Show figure
      fig.show()
      ##################################################################################

      printSuccess('VR1')
    except:
      printError()
except:
  printError()<jupyter_output><empty_output><jupyter_text>Extending the Model: Synaptic Conductance - Full Code<jupyter_code>import numpy as np
from plotly.subplots import make_subplots
import plotly.graph_objects as go

# Voltage Parameters - Units mV (1 mV = 1e-3 Volts)
Vrest = -70
Vth = -54
Vreset = -80
Vspike = 0

# Total Membrane Resistance - Units M_Ohm (1 M_Ohm = 1e6 Ohms)
Rm = 10

# Total Membrane Capacitance - Units nF (1 nF = 1e-6 Farads)
Cm = 1

# Membrane Time Constant - Units ms (1 ms = 1e-3 Seconds)
tau_m = Rm*Cm

# Neuron Surface Area - Units mm^2
A = 0.1

# Specific Membrane Resistance - Units M_Ohm * mm^2
rm = Rm*A

# Specific Conductance - Units mS / mm^2 (1 mS = 1e-3 Siemens)
g_s = 0.5

# Maximum Open Channel Probability
p_max = 0.5

# Synaptic Conductance Time Constant
tau_s = 10

# Leakage Conductance Reversal Potential
EL = Vrest

# Synaptic Conductance Reversal Potential
Es =0

# Initial Voltage
Vinitial = Vrest

# Initial Open Channel Probability 
Ps_initial = 0

# Initial Synaptic Current
i_s_initial = rm*g_s*Ps_initial*(Vinitial - Es)

# Simulation Time Span (0 to 500ms, dt = 1ms)
t0 = 0
dt = 1
t_final = 500

# What does the range() function do?
time = range(t0, t_final, dt)

# Create a list V[t] to store the value of V at each time-step dt
V = [0] * len(time)

# Set the initial value at time t = t0 to the initial value Vinitial
V[0] =  Vinitial

# Create a list Ps[t] to store the value of P_s at each time-step dt
Ps = [0] * len(time)

# Set the initial value at time t = t0 to the initial value Ps_initial
Ps[0] =  Ps_initial

# Create a list i_s(t) to store the value of synaptic current i_s at each time-step dt
i_s = [0] * len(time)

# Set the initial value at time t = t0 to the initial value i_s_initial
i_s[0] =  i_s_initial

# Times t at which the presynaptic neuron spikes
S_pre_t= [0, 50, 150, 190, 300, 320, 400, 410]
S_pre = 0

# Create a list Spre[t] to store the value of S_pre at each time-step dt
Spre  = [0] * len(time)

# Set the initial value at time t = t0 to the initial value S_pre
Spre[0] = S_pre

# For each timestep we compute V and store the value
for t in time[0:-2]:

    # If presynaptic neuron spikes
    if t  in S_pre_t:
        S_pre = 1
    # Otherwise
    else:
        dS_pre = (dt/tau_s)*(-S_pre)
        S_pre = S_pre + dS_pre

    # Store value of Spre
    Spre[t+1] = S_pre

    # Using Euler's Method for Numerical Integration (See Chapter Text)
    # We compute the change in open probability as follows (using the model equation)
    dPs = (dt/tau_s)*(np.exp(1)*p_max*S_pre - Ps[t])

    # Store this new value into our list
    Ps[t+1] = Ps[t] + dPs

    # Store the synaptic current value
    i_s[t] = rm*g_s*Ps[t]*V[t]

    # Using Euler's Method for Numerical Integration (See Chapter Text)
    # We compute the change in voltage dV as follows (using the model equation)
    dV = (dt/tau_m)*(EL - V[t] - i_s[t])

    # Store this new value into our list
    V[t+1] = V[t] + dV

    # If the voltage value we computed crosses the threshold value Vth, a spike occurs.
    if V[t+1] >= Vth:
        # In the event of a spike, the membrane potential spikes up to Vspike mV
        V[t] = Vspike
        # Followed by immediate hyperpolarization to its Vreset value.
        V[t+1] = Vreset

# Plotting
x = list(time[0:-2])

# Plot data
fig = make_subplots(
    rows=3, cols=1,  shared_xaxes = True, vertical_spacing=0.1, 
    subplot_titles=('V over Time', 'i_s over Time', 'P_s over Time')
)

# Add traces
fig.add_trace(go.Scatter(name='V', x=x, y=V), row=1, col=1)
fig.add_trace(go.Scatter(name='i_s', x=x, y=i_s), row=2, col=1)
fig.add_trace(go.Scatter(name='P_s', x=x, y=Ps), row=3, col=1)
fig.add_trace(go.Scatter(name='S_pre', x=x, y=Spre), row=3, col=1)

fig.add_shape(name = 'V_{th}',
              type='line',
              x0=min(x),
              y0=-54,
              x1=max(x),
              y1=-54,
              line=dict(color='Red'),
              line_dash='dash', row=1, col=1)

# Update xaxis properties
fig.update_xaxes(title_text="Time t (ms)", row=3, col=1)

# Update yaxis properties
fig.update_yaxes(title_text="Membrane Potential V (mV)", range=[-90,10], row=1, col=1)
fig.update_yaxes(title_text="Synaptic Current i_s (nA)", row=2, col=1)
fig.update_yaxes(title_text="P_s (Probability)", range=[0,1], row=3, col=1)

# Update title and size
fig.update_layout(height=800, width=700, 
                                title_text='Integrate-and-Fire Model Neuron with Synaptic Conductance',
                                showlegend = True)

# Update theme
fig.layout.template = 'plotly_dark'

# Show figure
fig.show()<jupyter_output><empty_output><jupyter_text>Simulations for Coding Challenge Problems<jupyter_code>#@title Run Simulation
#@markdown Execute the code block and use the sliders to set values in order to answer the Coding Challenge Problems in the chapter text.

#@markdown (Tip: Select a slider and use the left and right arrow keys to slide to the desired value.)
import numpy as np
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import ipywidgets as widgets

# Voltage Parameters - Units mV (1 mV = 1e-3 Volts)
Vrest = -70
Vth = -54
Vreset = -80
Vspike = 0

# Total Membrane Resistance - Units M_Ohm (1 M_Ohm = 1e6 Ohms)
Rm = 10

# Total Membrane Capacitance - Units nF (1 nF = 1e-6 Farads)
Cm = 1

# Membrane Time Constant - Units ms (1 ms = 1e-3 Seconds)
tau_m = Rm*Cm

# Neuron Surface Area - Units mm^2
A = 0.1

# Specific Membrane Resistance - Units M_Ohm * mm^2
rm = Rm*A

# Specific Conductance - Units mS / mm^2 (1 mS = 1e-3 Siemens)
g_s = 0.5

# Maximum Open Channel Probability
p_max = 0.5

# Synaptic Conductance Time Constant
tau_s = 10

# Leakage Conductance Reversal Potential
EL = Vrest

# Synaptic Conductance Reversal Potential
Es =0

# Initial Voltage
Vinitial = Vrest

# Initial Open Channel Probability 
Ps_initial = 0

# Initial Synaptic Current
i_s_initial = rm*g_s*Ps_initial*(Vinitial - Es)

# Simulation Time Span (0 to 500ms, dt = 1ms)
t0 = 0
dt = 1
t_final = 500

# What does the range() function do?
time = range(t0, t_final, dt)

# Create a list V[t] to store the value of V at each time-step dt
V = [0] * len(time)

# Set the initial value at time t = t0 to the initial value Vinitial
V[0] =  Vinitial

# Create a list Ps[t] to store the value of P_s at each time-step dt
Ps = [0] * len(time)

# Set the initial value at time t = t0 to the initial value Ps_initial
Ps[0] =  Ps_initial

# Create a list i_s(t) to store the value of synaptic current i_s at each time-step dt
i_s = [0] * len(time)

# Set the initial value at time t = t0 to the initial value i_s_initial
i_s[0] =  i_s_initial

# Times t at which the presynaptic neuron spikes
S_pre_t= [0, 50, 150, 190, 300, 320, 400, 410]
S_pre = 0

# Create a list Spre[t] to store the value of S_pre at each time-step dt
Spre  = [0] * len(time)

# Set the initial value at time t = t0 to the initial value S_pre
Spre[0] = S_pre

def compute_iaf_neuron(p_max, g_s, tau_m, Rm):

  rm = Rm*A

  # For each timestep we compute V and store the value
  for t in time[0:-2]:

      # If presynaptic neuron spikes
      if t  in S_pre_t:
          S_pre = 1
      # Otherwise
      else:
          dS_pre = (dt/tau_s)*(-S_pre)
          S_pre = S_pre + dS_pre

      # Store value of Spre
      Spre[t+1] = S_pre

      # Using Euler's Method for Numerical Integration (See Chapter Text)
      # We compute the change in open probability as follows (using the model equation)
      dPs = (dt/tau_s)*(np.exp(1)*p_max*S_pre- Ps[t])

      # Store this new value into our list
      Ps[t+1] = Ps[t] + dPs

      # Store the synaptic current value
      i_s[t] = rm*g_s*Ps[t]*V[t]

      # Using Euler's Method for Numerical Integration (See Chapter Text)
      # We compute the change in voltage dV as follows (using the model equation)
      dV = (dt/tau_m)*(EL - V[t] - i_s[t])

      # Store this new value into our list
      V[t+1] = V[t] + dV

      # If the voltage value we computed crosses the threshold value Vth, a spike occurs.
      if V[t+1] >= Vth:
          # In the event of a spike, the membrane potential spikes up to Vspike mV
          V[t] = Vspike
          # Followed by immediate hyperpolarization to its Vreset value.
          V[t+1] = Vreset
  return V

def plot_iaf_neuron(V, time):
  # Plotting
  x = list(time[0:-2])

  # Plot data
  fig = make_subplots(
      rows=3, cols=1,  shared_xaxes = True, vertical_spacing=0.1, 
      subplot_titles=('V over Time', 'i_s over Time', 'P_s over Time')
  )

  # Add traces
  fig.add_trace(go.Scatter(name='V', x=x, y=V), row=1, col=1)
  fig.add_trace(go.Scatter(name='i_s', x=x, y=i_s), row=2, col=1)
  fig.add_trace(go.Scatter(name='P_s', x=x, y=Ps), row=3, col=1)
  fig.add_trace(go.Scatter(name='S_pre', x=x, y=Spre), row=3, col=1)

  fig.add_shape(name = 'V_{th}',
                type='line',
                x0=min(x),
                y0=-54,
                x1=max(x),
                y1=-54,
                line=dict(color='Red'),
                line_dash='dash', row=1, col=1)

  # Update xaxis properties
  fig.update_xaxes(title_text="Time t (ms)", row=3, col=1)

  # Update yaxis properties
  fig.update_yaxes(title_text="Membrane Potential V (mV)", range=[-90,10], row=1, col=1)
  fig.update_yaxes(title_text="Synaptic Current i_s (nA)", row=2, col=1)
  fig.update_yaxes(title_text="P_s (Probability)", range=[0,1], row=3, col=1)

  # Update title and size
  fig.update_layout(height=800, width=700, 
                                  title_text='Integrate-and-Fire Model Neuron with Synaptic Conductance',
                                  showlegend = True)

  # Update theme
  fig.layout.template = 'plotly_dark'

  # Show figure
  fig.show()

style = {'description_width':'auto'}
@widgets.interact(
  p_max = widgets.FloatSlider(
    value=0.5,
    min=0.1,
    max=1.0,
    step=0.1,
   description='p_max',
    style = style,
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='1.1f'
  ),
  g_s = widgets.FloatSlider(
    value=0.5,
    min=0.1,
    max=1.5,
    step=0.1,
   description='g_s',
    style = style,
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='1.1f'
  ),
  tau_m = widgets.FloatSlider(
    value=10,
    min=5,
    max=25,
    step=1,
   description='tau_m',
    style = style,
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='d'
  )
)
def simulate_iaf_neuron(p_max=0.5, g_s=0.5, tau_m = 10):
  Rm = tau_m/Cm
  V = compute_iaf_neuron(p_max, g_s, tau_m, Rm)
  plot_iaf_neuron(V, time)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14564859897462354, 'fraction_numerical': 0.020281817302421564, 'mean_word_length': 3.293189368770764, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 71, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '188441', 'license': 'MIT', 'n_tokens_mistral': 11178, 'n_tokens_neox': 10191, 'n_words': 3592, 'path': 'Synaptic-Conductance/SpikingNeuronModel_IAFSYN.ipynb', 'repo_name': 'mbohling/spiking-neuron-model', 'size': '31038'}