# Lecture 2E: The Adaptive Exponential Integrator Cell
In this tutorial, we will study a more complex variation of integrate-and-fire
spiking dynamics in-built to ngc-learn, specifically the adaptive exponential
(AdEx) integrate-and-fire biophysical neuronal cell model.
## Using and Probing an AdEx Cell
Go ahead and make a new folder for this study and create a Python script,
i.e., `run_adexcell.py`, to write your code for this part of the tutorial.
Now let's set up the controller for this lesson's simulation and construct a
single component system made up of an AdEx cell.
### Instantiating the AdEx Neuronal Cell
Constructing/setting up a dynamical system made up of only a single
AdEx cell amounts to the following:
```python
from jax import numpy as jnp, random, jit
import numpy as np
from ngclearn import Context, MethodProcess
## import model-specific mechanisms
from ngclearn.components.neurons.spiking.adExCell import AdExCell
## create seeding keys (JAX-style)
dkey = random.PRNGKey(1234)
dkey, *subkeys = random.split(dkey, 6)
T = 10000 ## number of simulation steps to run
dt = 0.1 # ms ## compute integration time constant
## AdEx cell hyperparameters
v0 = -70. ## initial membrane potential (for its reset condition)
w0 = 0. ## initial recovery value (for its reset condition)
## create simple system with only one AdEx
with Context("Model") as model:
cell = AdExCell(
"z0", n_units=1, tau_m=15., resist_m=1., tau_w=400., v_sharpness=2.,
intrinsic_mem_thr=-55., v_thr=5., v_rest=-72., v_reset=-75., a=0.1,
b=0.75, v0=v0, w0=w0, integration_type="euler", key=subkeys[0]
)
## create and compile core simulation commands
advance_process = (MethodProcess("advance_proc")
>> cell.advance_state)
reset_process = (MethodProcess("reset_proc")
>> cell.reset)
## set up non-compiled utility commands
def clamp(x):
cell.j.set(x)
```
In effect, the AdEx two-dimensional differential equation system [1]-[2] offers
a more complex model of spiking cellular activation and deactivation
dynamics. Notably, the AdEx cell, like many of the more complex cells such
as the Izhikevich cell, is composed of a membrane potential `v` and a
recovery/adaptation variable `w`. The core voltage dynamics are a nonlinear
variation of those of the LIF cell and notably have some empirical support
from experimental neuroscience. More importantly, the AdEx model
can account for different neuronal firing patterns driven by injection of
constant electrical, such as bursting, initial bursting, and adaptation.
Formally, the core dynamics of the AdEx cell can be written out as follows:
$$
\tau_m \frac{\partial \mathbf{v}_t}{\partial t} &=
-(\mathbf{v}_t - v_{rest}) + s_v \exp\Big(\frac{\mathbf{v}_t - v_{intr}}{s_v}\Big) - R \mathbf{w}_t + R \mathbf{j}_t \\
\tau_w \frac{\partial \mathbf{w}_t}{\partial t} &= -\mathbf{w}_t + (\mathbf{v}_t - v_{rest}) * a
$$
where, furthermore, if a spike occurs, the recovery variable $\mathbf{w}_t$ is
reset according to $\mathbf{w}_t \leftarrow \mathbf{w}_t + \mathbf{s}_t \odot (\mathbf{w}_t + b)$.
$a$ and $b$ are factors that drive the recovery variable's dynamics
(scaling and shifting, respectively), $R$ is the membrane resistance, $\tau_m$ is the
membrane time constant, and $\tau_w$ is the recovery time constant. For the
voltage dynamics, beyond the standard LIF coefficients, two new ones that
shape the nonlinearity of the dynamics are $s_V$ -- the slope factor (this
controls the sharpness of action potential initiation )-- and $v_{intr}$ -- the
intrinsic membrane threshold.
### Simulating an AdEx Neuronal Cell
To see how the AdEx cell works, we can next write some code for visualizing how
the node's membrane potential and coupled recovery variable evolve with time
(over a period of about `1000` milliseconds). We will inject an electrical
current `j` into the AdEx cell (specifically `19` amperes) and observe how the cell
produces its action potentials. Specifically, we create the simulation of this
constant current injection with the code below:
```python
curr_in = []
mem_rec = []
recov_rec = []
spk_rec = []
i_app = 19. ## electrical current to inject into AdEx cell
data = jnp.asarray([[i_app]], dtype=jnp.float32)
time_span = []
reset_process.run()
t = 0.
for ts in range(T):
x_t = data
## pass in t and dt and run step forward of simulation
clamp(x_t)
advance_process.run(t=t, dt=dt) # run one step of dynamics
t = t + dt
## naively extract simple statistics at time ts and print them to I/O
v = cell.v.get()
w = cell.w.get()
s = cell.s.get()
curr_in.append(data)
mem_rec.append(v)
recov_rec.append(w)
spk_rec.append(s)
## print stats to I/O (overriding previous print-outs to reduce clutter)
print("\r {}: s {} ; v {} ; w {}".format(ts, s, v, w), end="")
time_span.append((ts)*dt)
print()
```
and we can plot the neuron's voltage `v` and recovery variable `w` dynamics with
the following:
```python
import matplotlib #.pyplot as plt
matplotlib.use('Agg')
import matplotlib.pyplot as plt
cmap = plt.cm.jet
import matplotlib.patches as mpatches #used to write custom legends
## Post-process statistics (convert to arrays) and create plot
time_span = np.asarray(time_span)
curr_in = np.squeeze(np.asarray(curr_in))
mem_rec = np.squeeze(np.asarray(mem_rec))
recov_rec = np.squeeze(np.asarray(recov_rec))
spk_rec = np.squeeze(np.asarray(spk_rec))
# Plot the AdEx cell trajectory
n_plots = 1
fig, ax = plt.subplots(1, n_plots, figsize=(5*n_plots,5))
ax_ptr = ax
ax_ptr.set(
xlabel='Time', ylabel='Voltage (v)', title="AdEx Voltage Dynamics"
)
v = ax_ptr.plot(time_span, mem_rec, color='C0')
ax_ptr.legend([v[0]],['v'])
plt.tight_layout()
plt.savefig("{0}".format("adex_v_plot.jpg"))
fig, ax = plt.subplots(1, n_plots, figsize=(5*n_plots,5))
ax_ptr = ax
ax_ptr.set(
xlabel='Time', ylabel='Recovery (w)', title="AdEx Recovery Dynamics"
)
w = ax_ptr.plot(time_span, recov_rec, color='C1', alpha=.5)
ax_ptr.legend([w[0]],['w'])
plt.tight_layout()
plt.savefig("{0}".format("adex_w_plot.jpg"))
plt.close()
```
You should get two plots that depict the evolution of the AdEx cell's voltage
and recovery, i.e., saved as `adex_v_plot.jpg` and `adex_w_plot.jpg` locally to
disk, like the ones below:
```{eval-rst}
.. table::
:align: center
+------------------------------------------------------------+------------------------------------------------------------+
| .. image:: ../../images/tutorials/neurocog/adex_v_plot.jpg | .. image:: ../../images/tutorials/neurocog/adex_w_plot.jpg |
| :scale: 60% | :scale: 60% |
| :align: center | :align: center |
+------------------------------------------------------------+------------------------------------------------------------+
```
A useful note is that the AdEx cell above used Euler integration to step through its
dynamics (this is the default/base routine for all cell components in ngc-learn);
however, one could configure it to use the midpoint method for integration
by setting its argument `integration_type = rk2` in cases where more
accuracy in the dynamics is needed (at the cost of additional computational time).
## References
[1] Fourcaud-Trocme, Nicolas, David Hansel, Carl Van Vreeswijk, and
Nicolas Brunel. "How spike generation mechanisms determine the neuronal response
to fluctuating inputs." Journal of neuroscience 23, no. 37 (2003): 11628-11640.
[2] Brette, Romain, and Wulfram Gerstner. "Adaptive exponential
integrate-and-fire model as an effective description of neuronal activity."
Journal of neurophysiology 94.5 (2005): 3637-3642.