Izhikevich
The Izhikevich neuron, whose four parameters give regular spiking, bursting, chattering, and more.
- Name:
izhikevich - Field:
biology/neuroscience - Formalism: ordinary differential equations
- References: Izhikevich 2003
Factory
@register_model(
field='biology/neuroscience',
tags=('neuron', 'spiking', 'events', 'bursting'),
references=('Izhikevich 2003',),
defaults={'until': 200.0, 'dt': 0.1},
)
def izhikevich(
v: float = -65.0,
u: float | None = None,
*,
a: float = 0.02,
b: float = 0.2,
c: float = -65.0,
d: float = 8.0,
I: float = 10.0,
) -> System:
"""Return the Izhikevich neuron, whose four parameters give regular spiking, bursting, chattering, and more.
The potential follows `v' = 0.04 v² + 5 v + 140 - u + I` and the recovery `u' = a (b v - u)`. When `v` reaches
30 mV the neuron spikes, `v` resets to `c`, and `u` grows by `d`. The defaults give regular spiking.
Args:
v:
The initial membrane potential in mV.
u:
The initial recovery variable, or `None` for `b v`.
a:
The time scale of the recovery.
b:
The sensitivity of the recovery to the potential.
c:
The potential after a spike.
d:
The increase of the recovery after a spike.
I:
The input current.
Returns:
system:
The model, with the potential `neuron.v`, the recovery `neuron.u`, and its spikes as `spike` events.
Examples:
>>> from skcomplex.models.biology.neuroscience import izhikevich
>>> izhikevich(d=2.0).interactions[0].kick
2.0
"""
neuron = IzhikevichElement('neuron', v=v, u=b * v if u is None else u, a=a, b=b, I=I)
spike = SpikeResetInteraction('spike', target='neuron', threshold=PEAK, after=c, kicked='u', kick=d)
return System(elements=[neuron], interactions=[spike])
Components
class IzhikevichElement(Element):
"""Izhikevich neuron, a quadratic potential `v` and a linear recovery `u`.
The potential follows `v' = 0.04 v² + 5 v + 140 - u + I` and the recovery `u' = a (b v - u)`. A spike reset fires
the neuron.
Args:
label:
The label of the neuron.
visual:
How the element is drawn, or `None` for `default_visual_`.
v:
The initial potential.
u:
The initial recovery.
a:
The time scale of the recovery.
b:
The sensitivity of the recovery to the potential.
I:
The input current.
Examples:
>>> from skcomplex.models.biology.neuroscience import IzhikevichElement
>>> IzhikevichElement('neuron').b
0.2
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
v: Annotated[float, StateSpec()] = -65.0,
u: Annotated[float, StateSpec()] = -13.0,
a: Annotated[float, ParameterSpec()] = 0.02,
b: Annotated[float, ParameterSpec()] = 0.2,
I: Annotated[float, ParameterSpec()] = 10.0,
) -> None:
super().__init__(label, visual=visual)
self.v = v
self.u = u
self.a = a
self.b = b
self.I = I
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the quadratic potential and the linear recovery.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
return [RateLaw(s.v, 0.04 * s.v**2 + 5 * s.v + 140 - s.u + s.I), RateLaw(s.u, s.a * (s.b * s.v - s.u))]
class SpikeResetInteraction(CrossingInteraction):
"""Spike of a neuron, which resets its potential when it crosses a threshold upwards and kicks a second state.
When the potential `v` rises through `threshold`, it is set to `after`, and the state `kicked`, such as a recovery
variable or a synaptic trace, grows by `kick`. Every spike is an event labelled by the interaction.
Args:
label:
The label of the interaction and of its events.
target:
The path of the neuron.
state:
The name of the potential.
threshold:
The potential of a spike.
after:
The potential after a spike.
kicked:
The name of the state a spike increments, or `None` for none.
kick:
The increment of the kicked state at a spike.
Raises:
ValidationError:
The direction is not -1, 0, or 1, or the guard or a new value is not a function.
Examples:
>>> from skcomplex.core.elements import CompartmentElement
>>> from skcomplex.core.interactions import RateInteraction
>>> from skcomplex.core.system import System
>>> from skcomplex.engine.runtime import Simulation
>>> from skcomplex.models.biology.neuroscience import SpikeResetInteraction
>>> drive = RateInteraction('drive', target='neuron', state='n', rate=1.0)
>>> spike = SpikeResetInteraction('spike', target='neuron', state='n', threshold=1.0, after=0.0)
>>> system = System(elements=[CompartmentElement('neuron')], interactions=[drive, spike])
>>> trajectory = Simulation(system, dt=0.25).check().prepare().run(until=2.1).trajectory_
>>> [round(event.time, 6) for event in trajectory.events]
[1.0, 2.0]
"""
def __init__(
self,
label: str,
*,
target: Annotated[str, RoleSpec()],
state: str = 'v',
threshold: Annotated[float, ParameterSpec()] = 1.0,
after: Annotated[float, ParameterSpec()] = 0.0,
kicked: str | None = None,
kick: Annotated[float, ParameterSpec()] = 0.0,
) -> None:
super().__init__(
label,
target=target,
guard=self._get_guard,
reset=self._get_resets(state, kicked),
direction=1,
)
self.state = state
self.threshold = threshold
self.after = after
self.kicked = kicked
self.kick = kick
def _get_guard(self, s: SimpleNamespace) -> Expression:
"""Return the potential above the threshold."""
return getattr(s.target, self.state) - s.threshold
def _get_resets(self, state: str, kicked: str | None) -> dict[str, Callable[[SimpleNamespace], object]]:
"""Return the new value of the potential and of the kicked state after a spike."""
resets: dict[str, Callable[[SimpleNamespace], object]] = {state: lambda s: s.after}
if kicked is not None:
resets[kicked] = lambda s: getattr(s.target, kicked) + s.kick
return resets
Equations
neuron.v: diff(v_neuron(t), t) == 0.04*v_neuron(t)^2 + I_neuron - u_neuron(t) + 5*v_neuron(t) + 140
neuron.u: diff(u_neuron(t), t) == (b_neuron*v_neuron(t) - u_neuron(t))*a_neuron