Skip to content

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