Spaces:
Running
Running
File size: 5,728 Bytes
31226fd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | """Versioned, deterministic single-neuron dynamics models for MHRN.
The registry in this module is deliberately small. A model is added only when
its transition equations, spike rule, reset rule and version are explicit.
This keeps model selection a scientific treatment rather than an untracked
implementation detail.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Final
class NeuronModel(str, Enum):
"""Implemented membrane-dynamics model families."""
IZHIKEVICH = "izhikevich-2003"
LEAKY_INTEGRATE_AND_FIRE = "lif-current-v1"
@dataclass(frozen=True, slots=True)
class NeuronModelDescriptor:
"""Stable provenance metadata for one implemented dynamics model."""
model: NeuronModel
version: str
family: str
state_variables: tuple[str, ...]
integration_rule: str
spike_rule: str
reset_rule: str
canonical: bool
citation: str
def to_dict(self) -> dict[str, object]:
return {
"model": self.model.value,
"version": self.version,
"family": self.family,
"state_variables": list(self.state_variables),
"integration_rule": self.integration_rule,
"spike_rule": self.spike_rule,
"reset_rule": self.reset_rule,
"canonical": self.canonical,
"citation": self.citation,
}
MODEL_REGISTRY: Final[dict[NeuronModel, NeuronModelDescriptor]] = {
NeuronModel.IZHIKEVICH: NeuronModelDescriptor(
model=NeuronModel.IZHIKEVICH,
version="mhrn-1.0",
family="quadratic integrate-and-fire with recovery variable",
state_variables=("v", "u"),
integration_rule="two half Euler steps for v, one Euler step for u",
spike_rule="v >= 30 mV plus optional adaptive threshold offset",
reset_rule="v = c; u = u + d",
canonical=True,
citation=(
"E. M. Izhikevich, Simple model of spiking neurons, "
"IEEE Transactions on Neural Networks 14(6), 2003, "
"doi:10.1109/TNN.2003.820440"
),
),
NeuronModel.LEAKY_INTEGRATE_AND_FIRE: NeuronModelDescriptor(
model=NeuronModel.LEAKY_INTEGRATE_AND_FIRE,
version="mhrn-1.0",
family="current-based leaky integrate-and-fire",
state_variables=("v",),
integration_rule="forward Euler membrane leak/current integration",
spike_rule="v >= configured lif_threshold plus optional adaptation",
reset_rule="v = configured lif_reset",
canonical=False,
citation="Reference current-based LIF implementation; model parameters are persisted.",
),
}
_MODEL_ALIASES: Final[dict[str, NeuronModel]] = {
"izhikevich": NeuronModel.IZHIKEVICH,
"izhikevich-2003": NeuronModel.IZHIKEVICH,
"izh": NeuronModel.IZHIKEVICH,
"lif": NeuronModel.LEAKY_INTEGRATE_AND_FIRE,
"leaky-integrate-and-fire": NeuronModel.LEAKY_INTEGRATE_AND_FIRE,
"lif-current-v1": NeuronModel.LEAKY_INTEGRATE_AND_FIRE,
}
def coerce_neuron_model(value: NeuronModel | str) -> NeuronModel:
"""Resolve a model enum or documented alias to a stable model identifier."""
if isinstance(value, NeuronModel):
return value
key = str(value).strip().lower()
try:
return _MODEL_ALIASES[key]
except KeyError as exc:
supported = ", ".join(model.value for model in NeuronModel)
raise ValueError(
f"Unknown neuron model {value!r}; supported: {supported}"
) from exc
def get_model_descriptor(value: NeuronModel | str) -> NeuronModelDescriptor:
"""Return immutable provenance metadata for an implemented model."""
return MODEL_REGISTRY[coerce_neuron_model(value)]
def available_neuron_models() -> tuple[NeuronModelDescriptor, ...]:
"""Return model descriptors in stable enum order."""
return tuple(MODEL_REGISTRY[model] for model in NeuronModel)
def integrate_membrane(
model: NeuronModel | str,
*,
v: float,
u: float,
input_current: float,
dt_ms: float,
a: float,
b: float,
lif_resting_potential: float,
lif_tau_m_ms: float,
lif_resistance: float,
) -> tuple[float, float]:
"""Advance only membrane-model state by one deterministic time step."""
selected = coerce_neuron_model(model)
if selected is NeuronModel.IZHIKEVICH:
half_dt = 0.5 * dt_ms
v += half_dt * (0.04 * v * v + 5.0 * v + 140.0 - u + input_current)
v += half_dt * (0.04 * v * v + 5.0 * v + 140.0 - u + input_current)
u += dt_ms * a * (b * v - u)
return v, u
dv = ((lif_resting_potential - v) + lif_resistance * input_current) * (
dt_ms / lif_tau_m_ms
)
return v + dv, u
def spike_threshold(
model: NeuronModel | str,
*,
izhikevich_threshold: float,
lif_threshold: float,
) -> float:
"""Return the non-adaptive spike threshold for a model."""
selected = coerce_neuron_model(model)
if selected is NeuronModel.IZHIKEVICH:
return izhikevich_threshold
return lif_threshold
def apply_spike_reset(
model: NeuronModel | str,
*,
v: float,
u: float,
c: float,
d: float,
lif_reset: float,
) -> tuple[float, float]:
"""Apply the model-specific state transition immediately after a spike."""
selected = coerce_neuron_model(model)
if selected is NeuronModel.IZHIKEVICH:
return c, u + d
return lif_reset, u
__all__ = [
"MODEL_REGISTRY",
"NeuronModel",
"NeuronModelDescriptor",
"apply_spike_reset",
"available_neuron_models",
"coerce_neuron_model",
"get_model_descriptor",
"integrate_membrane",
"spike_threshold",
]
|