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",
]