Integrate-and-fire
The leaky integrate-and-fire neuron, tau v' = -(v - rest) + R I, which spikes and resets at a threshold.
- Name:
integrate_and_fire - Field:
biology/neuroscience - Formalism: ordinary differential equations
- References: Lapicque 1907; Gerstner et al. 2014
Factory
@register_model(
field='biology/neuroscience',
tags=('neuron', 'spiking', 'events'),
references=('Lapicque 1907', 'Gerstner et al. 2014'),
defaults={'until': 100.0, 'dt': 0.1},
)
def integrate_and_fire(
v: float = -65.0,
*,
rest: float = -65.0,
threshold: float = -50.0,
reset: float = -65.0,
resistance: float = 10.0,
tau: float = 10.0,
I: float = 2.0,
) -> System:
"""Return the leaky integrate-and-fire neuron, `tau v' = -(v - rest) + R I`, which spikes and resets at a threshold.
With `R I` above `threshold - rest` it fires periodically with period `tau ln(R I / (R I - threshold + rest))`
when `reset = rest`.
Args:
v:
The initial membrane potential in mV.
rest:
The resting potential in mV.
threshold:
The potential at which the neuron spikes in mV.
reset:
The potential after a spike in mV.
resistance:
The membrane resistance in MΩ.
tau:
The membrane time constant in ms.
I:
The input current in nA.
Returns:
system:
The model, with the membrane potential `neuron.v` and its spikes recorded as `spike` events.
Examples:
>>> from skcomplex.models.biology.neuroscience import integrate_and_fire
>>> from skcomplex.engine.runtime import Simulation
>>> trajectory = Simulation(integrate_and_fire(), dt=0.1).check().prepare().run(until=50.0).trajectory_
>>> round(trajectory.events[0].time, 2)
13.86
"""
neuron = LeakyIntegrateAndFireElement('neuron', v=v, rest=rest, resistance=resistance, tau=tau, I=I)
spike = SpikeResetInteraction('spike', target='neuron', threshold=threshold, after=reset)
return System(elements=[neuron], interactions=[spike])
Components
class LeakyIntegrateAndFireElement(Element):
"""Leaky integrate-and-fire neuron, whose potential relaxes to `rest + resistance I` over the time `tau`.
The potential follows `tau v' = -(v - rest) + resistance I`. A spike reset fires the neuron. Units are mV, ms, MΩ,
and nA.
Args:
label:
The label of the neuron.
visual:
How the element is drawn, or `None` for `default_visual_`.
v:
The initial potential.
rest:
The resting potential.
resistance:
The membrane resistance.
tau:
The membrane time constant.
I:
The input current.
Examples:
>>> from skcomplex.models.biology.neuroscience import LeakyIntegrateAndFireElement
>>> LeakyIntegrateAndFireElement('neuron').tau
10.0
"""
def __init__(
self,
label: str,
visual: VisualSpec | None = None,
*,
v: Annotated[float, StateSpec()] = -65.0,
rest: Annotated[float, ParameterSpec()] = -65.0,
resistance: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
tau: Annotated[float, ParameterSpec(domain=RealSet.open(0, oo))] = 10.0,
I: Annotated[float, ParameterSpec()] = 2.0,
) -> None:
super().__init__(label, visual=visual)
self.v = v
self.rest = rest
self.resistance = resistance
self.tau = tau
self.I = I
def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
"""Return the leaky integration of the input current.
Args:
s:
The namespace of the symbols the laws can use.
Returns:
laws:
The laws of the component.
"""
return [RateLaw(s.v, (-(s.v - s.rest) + s.resistance * s.I) / s.tau)]
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) == (I_neuron*resistance_neuron + rest_neuron - v_neuron(t))/tau_neuron