The Izhikevich neuron model is a quadratic integrate-and-fire type model with a recovery variable. It is able to replicate several characteristics of biological neurons while remaining computationally efficient.

The Izhikevich neuron model is implemented in Nengo.
To use it, use a `nengo.Izhikevich`

instance
as the `neuron_type`

of an ensemble.

In [ ]:

```
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import nengo
%load_ext nengo.ipynb
from nengo.utils.matplotlib import rasterplot
```

In [2]:

```
with nengo.Network(seed=0) as model:
u = nengo.Node(lambda t: np.sin(2 * np.pi * t))
ens = nengo.Ensemble(10, dimensions=1, neuron_type=nengo.Izhikevich())
nengo.Connection(u, ens)
```

In [3]:

```
with model:
out_p = nengo.Probe(ens, synapse=0.03)
spikes_p = nengo.Probe(ens.neurons)
voltage_p = nengo.Probe(ens.neurons, 'voltage')
recovery_p = nengo.Probe(ens.neurons, 'recovery')
```

In [4]:

```
with nengo.Simulator(model) as sim:
sim.run(1.0)
t = sim.trange()
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(t, sim.data[out_p])
plt.ylabel("Decoded output")
ax = plt.subplot(2, 1, 2)
rasterplot(t, sim.data[spikes_p], ax=ax)
plt.ylabel("Neuron");
```

One thing you might notice is a slight bump in the decoded value at the start of the simulation. This occurs becuase of the adaptive nature of the Izhikevic model; it is easier to initiate the first spike than it is to ellicit further spikes.

Let's use a constant input and look at the first 100 ms of the simulation in more detail to see the difference between the first spike and subsequent spikes.

In [5]:

```
def izh_plot(sim):
t = sim.trange()
plt.figure(figsize=(12, 10))
plt.subplot(4, 1, 1)
plt.plot(t, sim.data[out_p])
plt.ylabel("Decoded output")
plt.xlim(right=t[-1])
ax = plt.subplot(4, 1, 2)
rasterplot(t, sim.data[spikes_p], ax=ax)
plt.ylabel("Neuron")
plt.xlim(right=t[-1])
plt.subplot(4, 1, 3)
plt.plot(t, sim.data[voltage_p])
plt.ylabel("Voltage")
plt.xlim(right=t[-1])
plt.subplot(4, 1, 4)
plt.plot(t, sim.data[recovery_p])
plt.ylabel("Recovery")
plt.xlim(right=t[-1])
u.output = 0.2
with nengo.Simulator(model) as sim:
sim.run(0.1)
izh_plot(sim)
```

Those neurons that have an encoder of -1 receive negative current, and therefore remain at a low voltage.

Those neurons that have an encoder of 1 receive positive current, and start spiking rapidly. However, as they spike, the recovery variable grows, until it reaches a balance with the voltage such that the cells spike regularly.

This occurs because, by default, we use a set of parameters that models a "regular spiking" neuron. We can use parameters that model several different classes of neurons instead.

In [6]:

```
ens.neuron_type = nengo.Izhikevich(reset_voltage=-55, reset_recovery=4)
with nengo.Simulator(model) as sim:
sim.run(0.4)
izh_plot(sim)
```

In [7]:

```
ens.neuron_type = nengo.Izhikevich(reset_voltage=-50, reset_recovery=2)
with nengo.Simulator(model) as sim:
sim.run(0.4)
izh_plot(sim)
```

In [8]:

```
ens.neuron_type = nengo.Izhikevich(tau_recovery=0.1)
with nengo.Simulator(model) as sim:
sim.run(0.4)
izh_plot(sim)
```

In [9]:

```
ens.neuron_type = nengo.Izhikevich(coupling=0.25)
with nengo.Simulator(model) as sim:
sim.run(0.4)
izh_plot(sim)
```

In [10]:

```
ens.neuron_type = nengo.Izhikevich(tau_recovery=0.1, coupling=0.26)
with nengo.Simulator(model) as sim:
sim.run(0.4)
izh_plot(sim)
```

Unfortuantely, Izhikevich neurons can't necessarily be used in all of the situations that LIFs are used, due to the more complex dynamics illustrated above.

The way that Nengo encodes and decodes information
with neurons is informed by the tuning curves
of those neurons.
With Izhikevich neurons, the firing rate
with a certain input current `J`

changes;
the spike rate is initially higher due
to the adaptation illustrated above.

We try our best to generate tuning curves for Izhikevich neurons.

In [11]:

```
from nengo.utils.ensemble import tuning_curves
ens.neuron_type = nengo.Izhikevich()
ens.n_neurons = 30
with nengo.Simulator(model) as sim:
plt.figure()
plt.plot(*tuning_curves(ens, sim));
```

But these are not as accurate and clean as LIF curves, which is detrimental for function decoding.

In [12]:

```
u.output = lambda t: np.sin(2 * np.pi * t)
with model:
square = nengo.Ensemble(30, dimensions=1, neuron_type=nengo.Izhikevich())
nengo.Connection(ens, square, function=lambda x: x ** 2)
square_p = nengo.Probe(square, synapse=0.03)
```

In [13]:

```
with nengo.Simulator(model) as sim:
sim.run(2.0)
t = sim.trange()
plt.figure(figsize=(12, 3))
plt.plot(t, sim.data[out_p], label="Ensemble 1 (sin wave)")
plt.plot(t, sim.data[square_p], label="Ensemble 2 (sin^2)")
plt.legend(loc="best");
```

Some of these weird dynamics can be overcome by using Izhikevich neurons with different parameters.

In [14]:

```
square.neuron_type = nengo.Izhikevich(tau_recovery=0.2)
with nengo.Simulator(model) as sim:
sim.run(2.0)
t = sim.trange()
plt.figure(figsize=(12, 3))
plt.plot(t, sim.data[out_p], label="Ensemble 1 (sin wave)")
plt.plot(t, sim.data[square_p], label="Ensemble 2 (sin^2)")
plt.legend(loc="best");
```

Download `izhikevich`

as a Jupyter notebook or Python script.