Source code for model.modules.sli
"""
Model of saturable leaky integrator
"""
import torch
from typing import Tuple, NamedTuple
from norse.torch.module.snn import SNNCell, SNN
__all__ = (
"SLIState",
"SLIParameters",
"SLICell",
)
[docs]
class SLIState(NamedTuple):
"""State of a saturable-leaky-integrator"""
v: torch.Tensor
"""membrane voltage"""
i: torch.Tensor
"""input current"""
[docs]
class SLIParameters(NamedTuple):
"""Parameters of a saturable leaky integrator"""
tau_syn_inv: torch.Tensor = torch.as_tensor(1.0 / 5e-3)
"""inverse synaptic time constant"""
tau_mem_inv: torch.Tensor = torch.as_tensor(1.0 / 1e-2)
"""inverse membrane time constant"""
v_leak: torch.Tensor = torch.as_tensor(0.0)
"""leak potential"""
v_st: torch.Tensor = torch.as_tensor(1.0)
"""saturation potential in mV"""
[docs]
class SLI(SNN):
"""A neuron layer that wraps a saturable-leaky-integrator :class:`SLICell` in time.
The layer iterates over the _outer_ dimension of the input.
"""
def __init__(self, p: SLIParameters = SLIParameters(), **kwargs):
"""
:param p: parameters of the leaky integrator
:type p: SLIParameters, optional
:param dt: integration timestep to use
:type p: float, optional
"""
super().__init__(
activation=sli_feed_forward_step,
state_fallback=self.initial_state,
p=p,
**kwargs,
)
[docs]
def initial_state(self, input_tensor: torch.Tensor) -> SLIState:
state = SLIState(
v=torch.full(
input_tensor.shape[1:], # Assume first dimension is time
self.p.v_leak.detach(),
device=input_tensor.device,
dtype=torch.float32,
),
i=torch.zeros(
*input_tensor.shape[1:],
device=input_tensor.device,
dtype=torch.float32,
),
)
state.v.requires_grad = True
return state
[docs]
class SLICell(SNNCell):
"""Cell for a leaky-integrator"""
def __init__(self, p: SLIParameters = SLIParameters(), **kwargs):
"""
:param p: parameters of the leaky integrator
:type p: SLIParameters, optional
:param dt: integration timestep to use
:type p: float, optional
"""
super().__init__(
activation=sli_feed_forward_step,
state_fallback=self.initial_state,
p=p,
**kwargs,
)
[docs]
def initial_state(self, input_tensor: torch.Tensor) -> SLIState:
state = SLIState(
v=self.p.v_leak.detach(),
i=torch.zeros(
*input_tensor.shape,
device=input_tensor.device,
dtype=input_tensor.dtype,
),
)
state.v.requires_grad = True
return state
[docs]
def sli_feed_forward_step(
input_tensor: torch.Tensor,
state: SLIState,
p: SLIParameters = SLIParameters(),
dt: float = 0.001,
) -> Tuple[torch.Tensor, SLIState]:
# compute current jumps
i_jump = state.i + input_tensor * torch.sigmoid(p.v_st - torch.abs(state.v))
# compute voltage updates
dv = dt * p.tau_mem_inv * ((p.v_leak - state.v) + i_jump)
v_new = state.v + dv
# compute current updates
di = -dt * p.tau_syn_inv * i_jump
i_decayed = i_jump + di
return v_new, SLIState(v_new, i_decayed)