# -*- coding: utf-8 -*-
"""
Created on 2020/8/5
@project: SPAIC
@filename: Neuron
@author: Hong Chaofei
@contact: hongchf@gmail.com
@description:
定义神经集群和神经元模型。
神经元集群保存神经元数目、神经元编号、类型、模型、模型参数、神经元位置等信息,参与网络构建
"""
# print('__file__={0:<35} | __name__={1:<20} | __package__={2:<20}'.format(__file__,__name__,str(__package__)))
import numpy as np
from ..Network import Assembly
from abc import ABC, abstractmethod
from collections import OrderedDict
from ..Network.BaseModule import VariableAgent
from ..Network.Operator import Op
from ..IO.Initializer import BaseInitializer
from ..IO.Initializer import uniform
import re
# from brian2 import *
[docs]class NeuronGroup(Assembly):
'''Class for a group of neurons.
'''
_class_label = '<neg>'
_is_terminal = True
def __init__(self, num=None,
model=None,
shape=None,
neuron_type=('excitatory', 'inhibitory', 'pyramidal', '...'),
neuron_position='x, y, z',
name=None,
parameter_variables: list = None,
varible_ranges: dict = None,
prefer_device=None,
**kwargs
):
super(NeuronGroup, self).__init__(name=name)
self.set_num_shape(num=num, shape=shape)
self.outlayer = kwargs.get("outlayer", False)
# self.model = model
if neuron_type == ('excitatory', 'inhibitory', 'pyramidal', '...'):
self.type = ['nontype']
elif isinstance(neuron_type, list):
self.type = neuron_type
else:
self.type = [neuron_type]
if neuron_position == 'x, y, z':
self.position = []
else:
neuron_position = np.array(neuron_position)
assert neuron_position.shape[0] == num, " Neuron_position not equal to neuron number"
self.position = neuron_position
self.parameters = kwargs
if isinstance(model, str):
self.model_class = NeuronModel.apply_model(model)
self.model_name = model # self.model -> self.model_name
self.model = None
elif isinstance(model, NeuronModel):
self.model = model
self.model_class = None
self.model_name = 'custom_model'
else:
raise ValueError("only support set neuron model with string or NeuronModel class constructed by @custom_model()")
# variables that can be trained
if parameter_variables is None:
self._parameter_var_names = []
else:
assert isinstance(parameter_variables, list)
self._parameter_var_names = parameter_variables
# variables that needs clamp
if varible_ranges is None:
self._variablel_ranges = dict()
else:
assert isinstance(varible_ranges, dict)
self._variablel_ranges = varible_ranges
self._var_names = list()
self._var_dict = dict()
self._operations = OrderedDict()
self._init_operations = OrderedDict()
self.prefer_device = prefer_device
[docs] def set_num_shape(self, num, shape):
self.num = num
self.shape = shape
if self.shape is not None:
num = int(np.prod(self.shape))
if self.num is None:
self.num = num
else:
assert self.num == num, "the neuron number is not accord with neuron shape"
elif self.num is not None:
self.shape = [self.num]
else:
raise ValueError("neither neuron number nor neuron shape is defined")
[docs] def set_parameter(self):
pass
[docs] def get_model(self):
return self.model
[docs] def add_neuron_label(self, key: str):
if isinstance(key, str) or isinstance(key, VariableAgent):
return self._add_label(key)
elif isinstance(key, list) or isinstance(key, tuple):
keys = []
for k in key:
keys.append(self._add_label(k))
return keys
[docs] def add_variable_kwargs(self, key):
t_kwargs = dict()
if key in self._parameter_var_names:
t_kwargs['is_parameter'] = True
if key in self._variablel_ranges:
min, max = self._variablel_ranges[key]
if min is not None:
assert isinstance(min, float)
t_kwargs['min'] = min
if max is not None:
assert isinstance(max, float)
t_kwargs['max'] = max
return t_kwargs
[docs] def build(self, backend):
'''
Parameters
----------
backend : Backend.Backend
Returns
-------
'''
self._backend = backend
prefer_device=self.prefer_device if self.prefer_device != None else None
# batch_size = self._backend.get_batch_size()
# if(self.parameters is not None):
# self.model = self.model_class(**self.model_parameters)
# else:
# self.model = self.model_class()
if self.model_class is not None:
if 'dt' not in self.parameters:
self.parameters['dt'] = self._backend.dt
self.parameters['neuron_num'] = self.num
self.model = self.model_class(**self.parameters)
dt = backend.dt
for (key, tau_var) in self.model._tau_variables.items():
t_kwargs = self.add_variable_kwargs(key)
tau_var = self.sequence_to_tensor(tau_var)
self.model._tau_variables[key] = tau_var
key = self.add_neuron_label(key)
tau_var = torch.exp(-dt / tau_var)
# tau_var = np.exp(-dt / tau_var)
# shape = ()
if tau_var.numel() > 1:
shape = list(tau_var.shape)
assert tau_var.numel() == self.num, "The number of tau should equal the number of neurons."
else:
shape = ()
# if tau_var.size > 1:
# shape = tau_var.shape
# assert tau_var.size == self.num, "The number of tau should equal the number of neurons."
# else:
# shape = ()
self.variable_to_backend(key, shape, value=tau_var, prefer_device=prefer_device, **t_kwargs)
for (key, membrane_tau_var) in self.model._membrane_variables.items():
t_kwargs = self.add_variable_kwargs(key)
membrane_tau_var = self.sequence_to_tensor(membrane_tau_var)
self.model._membrane_variables[key] = membrane_tau_var
key = self.add_neuron_label(key)
# TODO: 计划把membrane tau反过来了变成membrane_tau/dt,需要把用的模型也改一下
membrane_tau_var = dt/membrane_tau_var
shape = (1, *self.shape) # (1, neuron_num)
self.variable_to_backend(key, shape, value=membrane_tau_var, prefer_device=prefer_device, **t_kwargs)
for (key, var) in self.model._variables.items():
# add the rule to extend new dimension before shape (for slif model)
t_kwargs = self.add_variable_kwargs(key)
var = self.sequence_to_tensor(var)
self.model._variables[key] = var
extend_tag = re.search("\[\d*\]", key)
if extend_tag is not None:
extend_tag = int(key[extend_tag.start() + 1:extend_tag.end() - 1])
key = self.add_neuron_label(key)
if extend_tag is not None:
shape = (1, extend_tag, *self.shape)
else:
shape = (1, *self.shape) # (batch_size, shape)
if hasattr(var, 'shape') and len(var.shape) > 0:
# adding more dimensions after the neuron shape, assuming neuron'shape in var.shape is 1
if len(var.shape) > len(shape):
var_shape = list(var.shape)
for i, s in enumerate(shape):
if s != var_shape[i]:
assert var_shape[i] == 1
var_shape[i] = shape[i]
var = var.expand(var_shape)
shape = var.shape
elif var.shape[0] > 1 and var.shape[0]==shape[1]:
# for batch dim
var = var.unsqueeze(dim=0)
self.variable_to_backend(key, shape, value=var, prefer_device=prefer_device, **t_kwargs)
for (key, var) in self.model._parameter_variables.items():
t_kwargs = self.add_variable_kwargs(key)
t_kwargs['is_parameter'] = True
t_kwargs['min'] = 0.0
t_kwargs['max'] = 1.0
var = self.sequence_to_tensor(var)
self.model._parameter_variables[key] = var
key = self.add_neuron_label(key)
# is pararmeter by default?
if isinstance(var, np.ndarray):
if var.size > 1:
shape = var.shape
# assert var.size == self.num, "The number of tau should equal the number of neurons."
else:
shape = ()
elif isinstance(var, torch.Tensor):
shape = var.shape
elif hasattr(var, '__iter__'):
var = np.array(var)
if var.size > 1:
shape = var.shape
assert var.size == self.num, "The number of tau should equal the number of neurons."
else:
shape = ()
else:
shape = ()
self.variable_to_backend(key, shape, value=var, prefer_device=prefer_device, **t_kwargs)
for (key, var) in self.model._constant_variables.items():
var = self.sequence_to_tensor(var)
self.model._constant_variables[key] = var
key = self.add_neuron_label(key)
shape = None
self.variable_to_backend(key, shape, value=var, is_constant=True, prefer_device=prefer_device)
op_count = 0
for op in self.model._operations:
addcode_op = Op(owner=self)
if isinstance(op[1], str):
op_name = f'{op_count}:{op[1]}'
addcode_op.func_name = op[1]
addcode_op.output = self.add_neuron_label(op[0])
if len(op) > 3: # 为了解决历史的单一list格式的问题
addcode_op.input = self.add_neuron_label(op[2:])
else:
addcode_op.input = self.add_neuron_label(op[2])
addcode_op.place = prefer_device
backend.add_operation(addcode_op)
else:
op_name = f'{op_count}:custom_function'
addcode_op.func_name = op[1]
addcode_op.output = self.add_neuron_label(op[0])
if len(op) > 3: # 为了解决历史的单一list格式的问题
addcode_op.input = self.add_neuron_label(op[2:])
else:
addcode_op.input = self.add_neuron_label(op[2])
addcode_op.place = prefer_device
backend.register_standalone(addcode_op)
op_count += 1
self._operations[op_name] = addcode_op
for op in self.model._init_operations:
addcode_op = []
for ind, value in enumerate(op):
if ind != 1:
addcode_op.append(self.add_neuron_label(value))
else:
addcode_op.append(value)
if len(op) > 3:
addcode_op[2] = addcode_op[2:]
addcode_op = addcode_op[:3]
op_count += 1
self.init_op_to_backend(addcode_op[0], addcode_op[1], addcode_op[2], prefer_device)
op_name = str(op_count) + ':' + 'custom_initial_function'
self._init_operations[op_name] = addcode_op
if self.model_name == "slif" or self.model_name == 'selif':
self.model.build((1, *self.shape), backend)
self.model.outlayer = self.outlayer
update_code = self.model.update_op_code
intital_code = self.model.initial_op_code
self.init_op_to_backend(intital_code[0], intital_code[1], intital_code[2], prefer_device)
backend.register_standalone(Op(self.add_neuron_label(update_code[0]), update_code[1],
[self.add_neuron_label(update_code[2])], prefer_device, owner=self))
backend.register_standalone(Op(self.add_neuron_label('V'), self.model.return_V, [], prefer_device, owner=self))
backend.register_standalone(Op(self.add_neuron_label('S'), self.model.return_S, [], prefer_device, owner=self))
[docs] @staticmethod
def custom_model(input_vars, output_vars, new_vars_dict, equation_type=('iterative','euler_iterative','exp_euler_iterative','ode'), backend='torch', custom_function_name='custom', base_model=None, add_threshold=True):
'''
Examples:
@NeuronGroup.custom_model(input_vars=['M', 'S', 'WgtSum'], output_vars=['V', 'M', 'S'],
new_vars_dict={'V':0, 'M':0, 'S':0, 'WgtSum':0}, equation_type='exp_euler_iterative')
def func(M, S, WgtSum):
M = (WgtSum-M)/tau
S = (WgtSum-S)/tau
V = M - S
return V, M, S
NeuronGroup(...., model=func)
'''
assert backend == 'torch'
if base_model is None:
model = NeuronModel()
elif isinstance(base_model, NeuronModel):
model = base_model
else:
raise ValueError("base model is given wrong type")
model.name = custom_function_name
if equation_type == 'iterative' or equation_type == 'ode':
for key, value in new_vars_dict.items():
if '[constant]' in key:
model._constant_variables[key.replace('[constant]','')] = value
else:
model._variables[key] = value
elif equation_type == 'euler_iterative':
for key, value in new_vars_dict.items():
if '[constant]' in key:
model._constant_variables[key.replace('[constant]','')] = value
elif 'tau' in key.lower():
model._membrane_variables[key] = value
else:
model._variables[key] = value
elif equation_type == 'exp_euler_iterative':
for key, value in new_vars_dict.items():
if '[constant]' in key:
model._constant_variables[key.replace('[constant]','')] = value
elif 'tau' in key.lower():
model._tau_variables[key] = value
else:
model._variables[key] = value
new_vars_dict = dict()
new_vars_dict.update(model._variables)
new_vars_dict.update(model._tau_variables)
new_vars_dict.update(model._membrane_variables)
new_vars_dict.update(model._constant_variables)
for var in input_vars:
if isinstance(var, VariableAgent):
continue
elif var not in new_vars_dict:
if '[updated]' in var:
if var.replace('[updated]', '') in new_vars_dict:
continue
else:
raise ValueError("The variable %s is not in model variable dict and not a Variable of other modules"%var)
for var in output_vars:
if isinstance(var, VariableAgent):
continue
elif var not in new_vars_dict:
if '[updated]' in var:
if var.replace('[updated]', '') in new_vars_dict:
continue
else:
raise ValueError("The variable %s is not in model variable dict and not a Variable of other modules"%var)
def model_function(func):
op_code = [output_vars, func,input_vars ]
model._operations.append(op_code)
if add_threshold == True:
model._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
return model
return model_function
[docs] def sequence_to_tensor(self, var):
# import collections.abc
# if isinstance(var, collections.abc.Iterable):
# return torch.Tensor(var)
# return var
if isinstance(var, BaseInitializer):
return var
return torch.tensor(var, dtype=self._backend.data_type, device=self._backend.device0)
[docs]class NeuronModel(ABC):
'''
op -> (return_name, operation_name, input_name1, input_name2...)
'''
#: A dictionary mapping neuron model names to `Model` objects
neuron_models = dict()
def __init__(self, **kwargs):
super(NeuronModel, self).__init__()
self.name = 'none'
self._operations = []
self._init_operations = []
self._variables = dict()
# self._tau_constant_variables = dict()
self._tau_variables = dict()
self._parameter_variables = dict()
self._membrane_variables = dict()
self._constant_variables = dict()
# self.neuron_parameters = dict()
[docs] @staticmethod
def register(name, model):
'''
Register a neuron model. Registered neuron models can be referred to
# via their name.
Parameters
----------
name : str
A short name for the state updater (e.g. `'lif'`)
model : `NeuronModel`
The neuron model object, e.g. an `CLIFModel`, 'SLIFModel'.
'''
# only deal with lower case names -- we don't want to have 'LIF' and
# 'lif', for example
name = name.lower()
if name in NeuronModel.neuron_models:
raise ValueError(('A model with the name "%s" has already been registered') % name)
if not issubclass(model, NeuronModel):
raise ValueError(('Given model of type %s does not seem to be a valid NeuronModel.' % str(type(model))))
NeuronModel.neuron_models[name] = model
model.name = name
[docs] @staticmethod
def apply_model(model_name):
'''
Parameters
----------
model_name : str
Returns
-------
'''
model_name = model_name.lower()
if model_name not in NeuronModel.neuron_models:
raise ValueError(('Given model name is not in the model list'))
else:
return NeuronModel.neuron_models[model_name]
# @abstractmethod
# def get_var(self):
# NotImplementedError()
# @abstractmethod
# def get_op(self):
# NotImplementedError()
# @abstractmethod
# def get_tau(self):
# NotImplementedError()
[docs]class CLIFModel(NeuronModel):
"""
Current LIF 3-kernel model:
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * Isyn^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(CLIFModel, self).__init__()
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 12.0)
# self.neuron_parameters['tau_q'] = kwargs.get('tau_q', 8.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['M'] = 0.0
self._variables['S'] = 0.0
self._variables['E'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._tau_variables['tauM'] = np.asarray(kwargs.get('tau_m', 20.0)) # self.neuron_parameters['tau_m']
self._tau_variables['tauP'] = np.asarray(kwargs.get('tau_p', 12.0)) # self.neuron_parameters['tau_p']
self._tau_variables['tauQ'] = np.asarray(kwargs.get('tau_q', 8.0)) # self.neuron_parameters['tau_q']
beta = self._tau_variables['tauP'] / self._tau_variables['tauQ']
V0 = (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
self._parameter_variables['V0'] = V0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
# self._operations.append(('I_che', 'var_mult', 'V0', 'WgtSum[updated]'))
# self._operations.append(('I', 'add', 'I_che[updated]', 'I_ele'))
self._operations.append(('I', 'var_mult', 'V0', 'Isyn[updated]'))
self._operations.append(('M', 'var_linear', 'tauP', 'M', 'I[updated]'))
self._operations.append(('S', 'var_linear', 'tauQ', 'S', 'I[updated]'))
self._operations.append(('PSP', 'minus', 'M[updated]', 'S[updated]'))
self._operations.append(('V', 'minus', 'PSP', 'E'))
self._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vth', 'O[updated]'))
self._operations.append(('E', 'var_linear', 'tauM', 'E', 'Resetting'))
NeuronModel.register("clif", CLIFModel)
[docs]class CLIF2kModel(NeuronModel):
"""
Current LIF 2-kernel model:
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * Isyn^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(CLIF2kModel, self).__init__()
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 12.0)
# self.neuron_parameters['tau_q'] = kwargs.get('tau_q', 8.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['M'] = 0.0
self._variables['S'] = 0.0
self._variables['E'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._tau_variables['tauM'] = np.asarray(kwargs.get('tau_m', 20.0)) # self.neuron_parameters['tau_m']
self._tau_variables['tauS'] = np.asarray(kwargs.get('tau_s', 12.0)) # self.neuron_parameters['tau_s']
beta = self._tau_variables['tauM'] / self._tau_variables['tauS']
V0 = (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
self._parameter_variables['V0'] = V0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
# self._operations.append(('I_che', 'var_mult', 'V0', 'WgtSum[updated]'))
# self._operations.append(('I', 'add', 'I_che[updated]', 'I_ele'))
self._operations.append(('I', 'var_mult', 'V0', 'Isyn[updated]'))
self._operations.append(('M', 'var_linear', 'tauM', 'M', 'I[updated]'))
self._operations.append(('S', 'var_linear', 'tauS', 'S', 'I[updated]'))
self._operations.append(('PSP', 'minus', 'M[updated]', 'S[updated]'))
self._operations.append(('V', 'minus', 'PSP', 'E'))
self._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vth', 'O[updated]'))
self._operations.append(('E', 'var_linear', 'tauM', 'E', 'Resetting'))
NeuronModel.register("clif2k", CLIF2kModel)
[docs]class PLIF2kModel(NeuronModel):
"""
Current LIF 2-kernel model:
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * Isyn^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(PLIF2kModel, self).__init__()
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 12.0)
# self.neuron_parameters['tau_q'] = kwargs.get('tau_q', 8.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['M'] = 0.0
self._variables['S'] = 0.0
self._variables['E'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._parameter_variables['tauM'] = np.asarray(kwargs.get('tau_m', 0.9)) # self.neuron_parameters['tau_m']
self._parameter_variables['tauS'] = np.asarray(kwargs.get('tau_s', 0.6)) # self.neuron_parameters['tau_s']
# beta = self._tau_variables['tauM'] / self._tau_variables['tauS']
# V0 = (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
# self._parameter_variables['V0'] = V0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
# self._operations.append(('I_che', 'var_mult', 'V0', 'WgtSum[updated]'))
# self._operations.append(('I', 'add', 'I_che[updated]', 'I_ele'))
# self._operations.append(('I', 'var_mult', 'V0', 'Isyn[updated]'))
self._operations.append(('Mtemp', 'add', 'M', 'Isyn[updated]'))
self._operations.append(('M', 'var_mult', 'tauM', 'Mtemp'))
self._operations.append(('Stemp', 'add', 'S', 'Isyn[updated]'))
self._operations.append(('S', 'var_mult', 'tauS', 'Stemp'))
self._operations.append(('PSP', 'minus', 'M[updated]', 'S[updated]'))
self._operations.append(('Resetting', 'var_mult', 'Vth', 'O'))
self._operations.append(('E', 'var_linear', 'tauM', 'E', 'Resetting'))
self._operations.append(('V', 'minus', 'PSP', 'E[updated]'))
self._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
NeuronModel.register("pclif", PLIF2kModel)
[docs]class AdaptiveCLIFModel(NeuronModel):
"""
Current LIF 3-kernel model:
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * Isyn^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(AdaptiveCLIFModel, self).__init__()
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 12.0)
# self.neuron_parameters['tau_q'] = kwargs.get('tau_q', 8.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['M'] = 0.0
self._variables['S'] = 0.0
self._variables['E'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._tau_variables['tauM'] = np.asarray(kwargs.get('tau_m', 20.0)) # self.neuron_parameters['tau_m']
self._tau_variables['tauP'] = np.asarray(kwargs.get('tau_p', 12.0)) # self.neuron_parameters['tau_p']
self._tau_variables['tauQ'] = np.asarray(kwargs.get('tau_q', 8.0)) # self.neuron_parameters['tau_q']
self._tau_variables['tauA'] = np.asarray(kwargs.get('tau_a', 2000.0)) # self.neuron_parameters['tau_q']
beta = self._tau_variables['tauP'] / self._tau_variables['tauQ']
V0 = (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
self._parameter_variables['V0'] = V0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._variables['Vth1[stay]'] = 0 #uniform(0, 1.0)
self._variables['beta_adaptive'] = kwargs.get('beta_adaptive', 0.1)
# self._operations.append(('I_che', 'var_mult', 'V0', 'WgtSum[updated]'))
# self._operations.append(('I', 'add', 'I_che[updated]', 'I_ele'))
self._operations.append(('Vth1[stay]', 'var_linear', 'tauA', 'Vth1[stay]', 'O'))
# self._operations.append(('Vth', 'add', 'Vth1[stay][updated]', 'Vth0'))
self._operations.append(('I', 'var_mult', 'V0', 'Isyn[updated]'))
self._operations.append(('M', 'var_linear', 'tauP', 'M', 'I[updated]'))
self._operations.append(('S', 'var_linear', 'tauQ', 'S', 'I[updated]'))
self._operations.append(('PSP', 'minus', 'M[updated]', 'S[updated]'))
self._operations.append(('EE', 'var_linear', 'beta_adaptive', 'Vth1[stay][updated]', 'E'))
self._operations.append(('V', 'minus', 'PSP', 'EE'))
self._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vth', 'O[updated]'))
self._operations.append(('E', 'var_linear', 'tauM', 'E', 'Resetting'))
NeuronModel.register("aclif", AdaptiveCLIFModel)
[docs]class IFModel(NeuronModel):
"""
IF model:
V(t) = V(t-1) * (1 - O(t-1)) + Isyn[t] - ConstantDecay
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(IFModel, self).__init__()
# self.neuron_parameters['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._constant_variables['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._operations.append(('Vtemp', 'add', 'V', 'Isyn[updated]'))
self._operations.append(('Vtemp1', 'minus', 'Vtemp', 'ConstantDecay'))
self._operations.append(('O', 'threshold', 'Vtemp1', 'Vth'))
# self._operations.append(('Resetting', 'var_mult', 'Vtemp1', 'O[updated]'))
self._operations.append(('V', 'reset', 'Vtemp1', 'O[updated]'))
NeuronModel.register("if", IFModel)
[docs]class IFSoftModel(NeuronModel):
"""
IF soft reset model:
V(t) = V(t-1) * (1 - O(t-1)) + Isyn[t] - ConstantDecay
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(IFSoftModel, self).__init__()
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._constant_variables['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._operations.append(('Vtemp', 'add', 'V', 'Isyn[updated]'))
self._operations.append(('Vtemp1', 'minus', 'Vtemp', 'ConstantDecay'))
self._operations.append(('O', 'threshold', 'Vtemp1', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vtemp1', 'O[updated]'))
self._operations.append(('V', 'minus', 'Vtemp1', 'Resetting'))
NeuronModel.register("ifsoftreset", IFSoftModel)
[docs]class IFBModel(NeuronModel):
"""
IF model:
V(t) = V(t-1) * (1 - O(t-1)) + Isyn[t] - ConstantDecay
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(IFBModel, self).__init__()
# self.neuron_parameters['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._constant_variables['bias'] = kwargs.get('bias', 0)
# self._operations.append(('Vtemp', 'add', 'V', 'Isyn[updated]'))
self._operations.append(('Itemp', 'add', 'Isyn[updated]', 'bias'))
self._operations.append(('Vtemp', 'add', 'V', 'Itemp'))
# self._operations.append(('Vtemp1', 'minus', 'Vtemp', 'ConstantDecay'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vtemp', 'O[updated]'))
self._operations.append(('V', 'minus', 'Vtemp', 'Resetting'))
NeuronModel.register("ifb", IFBModel)
[docs]class NonSpikingIFBModel(NeuronModel):
"""
IFB model:
V(t) = V(t-1) * (1 - O(t-1)) + Isyn[t] - ConstantDecay
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(NonSpikingIFBModel, self).__init__()
# self.neuron_parameters['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['V'] = 0.0
self._variables['Isyn'] = 0.0
self._constant_variables['bias'] = kwargs.get('bias', 0)
self._operations.append(('Itemp', 'add', 'Isyn[updated]', 'bias'))
self._operations.append(('V', 'add', 'V', 'Itemp'))
NeuronModel.register("nonspikingifb", NonSpikingIFBModel)
[docs]class NullModel(NeuronModel):
"""
return 'O'
"""
def __init__(self, **kwargs):
super(NullModel, self).__init__()
# self.neuron_parameters['ConstantDecay'] = kwargs.get('ConstantDecay', 0.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['Isyn'] = 0.0
self._variables['O'] = 0.0
self._operations.append(('O', 'assign', 'Isyn[updated]'))
NeuronModel.register("null", NullModel)
import torch
[docs]class QLIFModel(NeuronModel):
"""
LIF neuron model for Q-Backprop learning
E[t] = beta_m*E[t-1] + Vth*O[t-1]
V[t] = WgtSum(PSP[t]) - E[t]
O[t] = spike_function(V[t])
# Q for non-spiking
Mp[t] = beta_m*Mp[t-1] + WpSum[t]
Sp[t] = beta_s*Sp[t-1] + WpSum[t]
P[t] = Mp[t] - Sp[t]
# Q for spiking
Mq[t] = beta_m*Mq[t-1] + WqSum[t]
Sq[t] = beta_s*Sq[t-1] + WqSum[t]
Q[t] = Mq[t] - Sq[t]
R[t] = ~O[t]*beta_s*R[t-1] + O[t]
F[t] = 1 - R[t]
QP[t] = Q[t] - P[t]
BQ[t] = R[t]*Q[t] + F[t]*P[t]
TP[t] = R[t]*P[t] + F[t]*Reward - P[t-1]
TQ[t] = F[t]*Q[t] + R[t]*Reward - Q[t-1]
"""
# WgtSum(BQ_post[t - 1]) / WgtSum)
def __init__(self, **kwargs):
super(QLIFModel, self).__init__()
self._tau_variables['Beta_m'] = kwargs.get('tau_m', 20.0)
self._tau_variables['Beta_s'] = kwargs.get('tau_s', 8.0)
self._parameter_variables['Vth'] = kwargs.get('v_th', 1.0)
self._constant_variables['One'] = 1.0
self._variables['E'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = 0.0
self._variables['P'] = 0.0
self._variables['Mp'] = 0.0
self._variables['Sp'] = 0.0
self._variables['Q'] = 0.0
self._variables['Mq'] = 0.0
self._variables['Sq'] = 0.0
self._variables['R'] = 0.0
self._variables['F'] = 0.0
self._variables['QP'] = 0.0
self._variables['BQ'] = 0.0
self._variables['TP'] = 0.0
self._variables['TQ'] = 0.0
self._variables['PSP'] = 0.0
self._variables['WpSum'] = 0.0
self._variables['WqSum'] = 0.0
self._variables['Reward'] = 0.0
self._operations.append(('Resetting', 'var_mult', 'Vth', 'O'))
self._operations.append(('E', 'var_linear', 'Beta_m', 'E', 'Resetting'))
self._operations.append(('SumPSP', 'reduce_sum', 'PSP'))
self._operations.append(('V', 'minus', 'SumPSP', 'E[updated]'))
self._operations.append(('Mp', 'var_linear', 'Beta_m', 'Mp', 'WpSum'))
self._operations.append(('Sp', 'var_linear', 'Beta_s', 'Sp', 'WpSum'))
self._operations.append(('P', 'minus', 'Mp[updated]', 'Sp[updated]'))
self._operations.append(('Mq', 'var_linear', 'Beta_m', 'Mq', 'WpSum'))
self._operations.append(('Sq', 'var_linear', 'Beta_s', 'Sq', 'WpSum'))
self._operations.append(('Q', 'minus', 'Mq[updated]', 'Sq[updated]'))
self._operations.append(('RTmp', 'var_mult', 'Beta_s', 'R'))
self._operations.append(('', 'var_linear', 'Beta_s', 'R'))
NeuronModel.register("qlif", QLIFModel)
[docs]class SELIFModel(NeuronModel): #Exponential Model
"""
SpikeProp LIF 3-kernel model:
V[t] = WgtSum[t-1] - E[t-1]
I[t] = spike_func(V[t])
M[t] = betaM * M[t-1] + I[t]
S[t] = betaS * S[t-1] + I[t]
E[t] = betaM * E[t-1] + Vth * I[t]
O[t] = M[t] − S[t]
"""
def __init__(self,
tau_m=12.0,
tau_p=6.0,
tau_q=2.0,
tau_r=16.0,
v_th=1.0,
v_reset=2.0,
outlayer=False,
**kwargs
):
super(SELIFModel, self).__init__()
# from spaic.Learning.TRUE_Learner import TRUE_SpikeProp
# initial value for state variables
self._variables['[2]O'] = 0.0
self._variables['V'] = 0.0
self._variables['dV'] = 0.0
self._variables['S'] = 0.0
self._variables['WgtSum'] = 0.0
self.tau_m = tau_m
self.tau_e = tau_p
self.tau_s = tau_q
self.tau_r = tau_r
self.v_th = v_th
self.v_reset = 1.0 * v_th
self.outlayer = outlayer
self.beta = tau_p / tau_q
self.V0 = (1 / (self.beta - 1)) * (self.beta ** (self.beta / (self.beta - 1)))
# self.delat_m = self.tau_m/(self.tau_m-self.tau_s)
# self.delat_s = self.tau_s/(self.tau_m-self.tau_s)
# self.delat_ms = self.tau_m*self.tau_s/(self.tau_m-self.tau_s)
self.update_op_code = ('[2]O', self.update, 'WgtSum[updated]') # 'WgtSum[updated]'
self.return_op_code = (None, self.return_V, [])
self.initial_op_code = (None, self.initial, [])
'''
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * WgtSum^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
'''
[docs] def attach_learner(self, learner):
self.learner = learner
[docs] def build(self, shape, backend):
self.dt = backend.dt
self.M_initial = torch.zeros(shape, device=backend.device)
self.S_initial = torch.zeros(shape, device=backend.device)
self.R_initial = torch.zeros(shape, device=backend.device)
self.V_initial = torch.zeros(shape, device=backend.device)
self.beta_m = np.exp(-backend.dt / self.tau_m)
self.beta_s = np.exp(-backend.dt / self.tau_s)
self.beta_e = np.exp(-backend.dt / self.tau_e)
self.beta_r = np.exp(-backend.dt / self.tau_r)
self.running_var = None
self.running_mean = None
self.decay = 0.9999
self.initial()
self.rec_E = []
[docs] def initial(self):
self.M = self.M_initial
self.S = self.S_initial
self.R = self.R_initial
self.V = self.V_initial
self.O = None
self.rec_E = []
[docs] def norm_hook(self, grad):
if self.running_var is None:
self.running_var = torch.norm(grad, dim=0) * 0
self.running_mean = torch.mean(grad, dim=0) * 0
else:
self.running_var = self.decay * self.running_var + (1 - self.decay) * torch.norm(grad, dim=0)
self.running_mean = self.decay * self.running_mean + (1 - self.decay) * torch.mean(grad, dim=0)
return (grad - self.running_mean) / (1.0e-10 + self.running_var)
[docs] def update(self, WgtSum):
# with torch.no_grad():
# self.dV = self.E / self.tau_m +self.S / self.tau_s - self.M / self.tau_m
I = self.V0 * WgtSum
# WgtSum.register_hook(self.norm_hook)
if I.dim() == self.M.dim() + 1:
Ii = I[:, 0, ...]
I0 = I[:, 1, ...]
self.M = self.beta_e * self.M + (Ii - I0 / self.tau_e)
self.S = self.beta_s * self.S + (Ii - I0 / self.tau_s)
else:
self.M = self.beta_e * self.M + I
self.S = self.beta_s * self.S + I
if self.O is not None:
Oi = self.O[:, 0, ...] #+ 0.9*self.O[:, 0, ...].detach() # *self.O[:, 0, ...].gt(0.0)
Ot = self.O[:, 1, ...] #+ 0.9*self.O[:, 1, ...].detach()
else:
Oi = 0.0
Ot = 0.0
# expv = torch.clamp_max(torch.exp(2.5 * self.V)-1, 12)
expv = 2.0*torch.pow(torch.clamp(self.V,-10,self.v_th), 2.0)
self.R = self.beta_r*self.R + (1-self.beta_r)*self.V + 10.0*(Oi-Ot/self.tau_r)
self.dV = (expv - self.V + (self.M - self.S) + 0.5-0.5*self.R) / self.tau_m
# with torch.no_grad():
# self.ddV = (self.dV * (0.2 * expv - 1) + self.S / self.tau_s - self.M / self.tau_e) / self.tau_m
# self.dV2 = self.dV + self.ddV * self.dt
self.V = self.V + self.dV * self.dt - self.v_reset*(Oi-Ot/self.tau_m)# + self.ddV * self.dt ** 2 / 2.0
# self.P = self.M + self.S
self.O = self.learner.threshold(self.V, self.dV, self.v_th)
#
# self.rec_E.append(self.Vmax)
# if (I is not None) and (I.requires_grad == True):
# I.retain_grad()
# self.rec_E.append(I)
return self.O
[docs] def return_V(self):
return self.V
[docs] def return_M(self):
return self.M
[docs] def return_S(self):
return self.S
[docs] def return_dV(self):
return self.dV
@property
def E_values(self):
return torch.stack(self.rec_E, dim=-1).cpu().detach().numpy()
@property
def E_grads(self):
grads = []
for v in self.rec_E:
if v.grad is not None:
grads.append(v.grad.cpu().numpy())
else:
grads.append(torch.zeros_like(v).cpu().numpy())
grads = np.stack(grads[1:], axis=-1)
return grads
NeuronModel.register("selif", SELIFModel)
[docs]class SLIFModel(NeuronModel):
"""
SpikeProp LIF 3-kernel model:
V[t] = WgtSum[t-1] - E[t-1]
I[t] = spike_func(V[t])
M[t] = betaM * M[t-1] + I[t]
S[t] = betaS * S[t-1] + I[t]
E[t] = betaM * E[t-1] + Vth * I[t]
O[t] = M[t] − S[t]
"""
def __init__(self,
tau_m=20.0,
tau_p=20.0,
tau_q=8.0,
v_th=1.0,
v_reset=2.0,
outlayer=False,
):
super(SLIFModel, self).__init__()
# from spaic.Learning.TRUE_Learner import TRUE_SpikeProp
# initial value for state variables
self._variables['[2]O'] = 0.0
self._variables['V'] = 0.0
self._variables['dV'] = 0.0
self._variables['S'] = 0.0
self._variables['Isyn'] = 0.0
self.tau_m = tau_m
self.tau_e = tau_p
self.tau_s = tau_q
self.v_th = v_th
self.v_reset = 5.0 * v_th
self.outlayer = outlayer
self.beta = tau_m / tau_q
self.V0 = (1 / (self.beta - 1)) * (self.beta ** (self.beta / (self.beta - 1)))
# self.delat_m = self.tau_m/(self.tau_m-self.tau_s)
# self.delat_s = self.tau_s/(self.tau_m-self.tau_s)
# self.delat_ms = self.tau_m*self.tau_s/(self.tau_m-self.tau_s)
self.update_op_code = ('[2]O', self.update, 'Isyn[updated]') # 'WgtSum[updated]'
self.return_op_code = (None, self.return_V, [])
self.initial_op_code = (None, self.initial, [])
'''
V(t) = M(t) − S(t) − E(t)
I^n[t] = V0 * WgtSum^n[t-1] #sum(w * O^(n-1)[t])
M^n[t] = betaM * M^n[t-1] + I^n[t-1]
S^n[t] = betaS * S^n[t-1] + I^n[t-1]
E^n[t] = betaM * E^n[t-1] + Vth * O^n[t-1]
O^n[t] = spike_func(V^n[t-1])
'''
[docs] def attach_learner(self, learner):
self.learner = learner
[docs] def build(self, shape, backend):
self.dt = backend.dt
self.M_initial = torch.zeros(shape, device=backend.device)
self.S_initial = torch.zeros(shape, device=backend.device)
self.E_initial = torch.zeros(shape, device=backend.device)
self.V_initial = torch.zeros(shape, device=backend.device)
self.O_initial = torch.zeros((1,2,1), device=backend.device)
self.beta_m = np.exp(-backend.dt / self.tau_m)
self.beta_s = np.exp(-backend.dt / self.tau_s)
self.beta_e = np.exp(-backend.dt / self.tau_e)
self.deta_m = (1 - self.dt/(2*self.tau_m))/self.tau_m
self.deta_s = (1 - self.dt/(2*self.tau_s))/self.tau_s
self.running_var = None
self.running_mean = None
self.decay = 0.9999
self.initial()
self.rec_E = []
[docs] def initial(self):
self.M = self.M_initial
self.S = self.S_initial
self.E = self.E_initial
self.V = self.V_initial
self.O = None
self.rec_E = []
[docs] def norm_hook(self, grad):
if self.running_var is None:
self.running_var = torch.norm(grad, dim=0) * 0
self.running_mean = torch.mean(grad, dim=0) * 0
else:
self.running_var = self.decay * self.running_var + (1 - self.decay) * torch.norm(grad, dim=0)
self.running_mean = self.decay * self.running_mean + (1 - self.decay) * torch.mean(grad, dim=0)
return (grad - self.running_mean) / (1.0e-10 + self.running_var)
[docs] def update(self, WgtSum):
# with torch.no_grad():
# self.dV = self.E / self.tau_m +self.S / self.tau_s - self.M / self.tau_m
I = self.V0 * WgtSum
# WgtSum.register_hook(self.norm_hook)
# Oi = self.O[:, 0, ...]
# Ot = self.O[:, 1, ...]
if self.O is not None:
Oi = 0.1*self.O[:, 0, ...] + 0.9*self.O[:, 0, ...].detach()
Ot = 0.1*self.O[:, 1, ...] + 0.9*self.O[:, 1, ...].detach()
else:
Oi = 0
Ot = 0
if I.dim() == self.M.dim() + 1:
Ii = I[:, 0, ...]
I0 = I[:, 1, ...]
self.M = self.beta_m * self.M + Ii - I0 / self.tau_m - self.v_reset*(Oi-Ot/self.tau_m)
self.S = self.beta_s * self.S + Ii - I0 / self.tau_s
else:
self.M = self.beta_m * self.M + I - self.v_reset*(Oi-Ot/self.tau_m)
self.S = self.beta_s * self.S + I
self.V = self.M - self.S
# self.P = self.M + self.S
self.dV = self.S*self.deta_s - self.M*self.deta_m
#
# if self.O is not None:
# self.E = self.E*Oi.lt(1.0).float()# + (0.5*self.E.detach() - 0.5*self.E)*Oi.ge(1.0).float()
# MSbase = torch.clamp_min(self.M*self.tau_s/(self.S*self.tau_m+1.0e-20), 0)
# self.Vmax = self.M*MSbase**self.delat_s - self.S*MSbase**self.delat_m
self.O = self.learner.threshold(self.V, self.dV, self.v_th)
#
# self.rec_E.append(self.Vmax)
# if (I is not None) and (I.requires_grad == True):
# I.retain_grad()
# self.rec_E.append(I)
return self.O
[docs] def return_V(self):
return self.V
[docs] def return_M(self):
return self.M
[docs] def return_S(self):
return self.S
[docs] def return_dV(self):
return self.dV
@property
def E_values(self):
return torch.stack(self.rec_E, dim=-1).cpu().detach().numpy()
@property
def E_grads(self):
grads = []
for v in self.rec_E:
if v.grad is not None:
grads.append(v.grad.cpu().numpy())
else:
grads.append(torch.zeros_like(v).cpu().numpy())
grads = np.stack(grads[1:], axis=-1)
return grads
# for ii in range(2):
# out.append(test.update(I))
NeuronModel.register("slif", SLIFModel)
[docs]class SELIFDebugModel(NeuronModel):
def __init__(self,
tau_m=20.0, tau_p=20.0, tau_q=8.0, v_th=1.0,
outlayer=False
):
super(SELIFDebugModel, self).__init__()
# from spaic.Learning.TRUE_Learner import TRUE_SpikeProp
# initial value for state variables
self._variables['[2]O'] = 0.0
self._variables['V'] = 0.0
self._variables['dV'] = 0.0
self._variables['S'] = 0.0
self._variables['WgtSum'] = 0.0
self._variables['cumV'] = 0.0
self.tau_m = tau_m
self.tau_e = tau_p
self.tau_s = tau_q
self.v_th = v_th
self.v_reset = 5.0 * v_th
self.outlayer = outlayer
self.beta = tau_m / tau_q
self.V0 = (1 / (self.beta - 1)) * (self.beta ** (self.beta / (self.beta - 1)))
self.update_op_code = ('[2]O', self.update, 'WgtSum[updated]') # 'WgtSum[updated]'
self.return_op_code = (None, self.return_V, [])
self.initial_op_code = (None, self.initial, [])
[docs] def attach_learner(self, learner):
self.learner = learner
[docs] def build(self, shape, backend):
self.dt = backend.dt
self.M_initial = torch.zeros(shape, device=backend.device)
self.S_initial = torch.zeros(shape, device=backend.device)
self.V_initial = torch.zeros(shape, device=backend.device)
self.beta_m = np.exp(-backend.dt / self.tau_m)
self.beta_s = np.exp(-backend.dt / self.tau_s)
self.beta_e = np.exp(-backend.dt / self.tau_e)
self.initial()
self.rec_E = []
[docs] def initial(self):
self.M = self.M_initial
self.S = self.S_initial
self.V = self.V_initial
self.cumV = self.V_initial
self.O = None
self.rec_E = []
[docs] def update(self, WgtSum):
I = self.V0 * WgtSum
if I.dim() == self.M.dim() + 1:
Ii = I[:, 0, ...]
I0 = I[:, 1, ...]
self.M = self.beta_e * self.M + (Ii - I0 / self.tau_e)
self.S = self.beta_s * self.S + (Ii - I0 / self.tau_s)
else:
self.M = self.beta_e * self.M + I
self.S = self.beta_s * self.S + I
if self.O is not None:
Oi = self.O[:, 0, ...] #+ 0.9*self.O[:, 0, ...].detach() # *self.O[:, 0, ...].gt(0.0)
Ot = self.O[:, 1, ...] #+ 0.9*self.O[:, 1, ...].detach()
else:
Oi = 0.0
Ot = 0.0
# expv = torch.clamp_max(torch.exp(2.5 * self.V)-1, 12)
expv = 2.0*torch.pow(torch.clamp(self.V,-10,self.v_th), 2.0)
self.dV = (expv - self.V + (self.M - self.S)) / self.tau_m
self.V = self.V + self.dV * self.dt - self.v_reset*(Oi-Ot/self.tau_m)
self.O = self.learner.threshold(self.V, self.dV, self.v_th)
return self.O
[docs] def return_V(self):
return self.V
[docs]class LIFModel(NeuronModel):
"""
LIF model:
# V(t) = tuaM * V^n[t-1] + Isyn[t] # tauM: constant membrane time (tauM=RmCm)
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(LIFModel, self).__init__()
# initial value for state variables
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 0.0)
self._tau_variables['tauM'] = kwargs.get('tau_m', 8.0)
self._operations.append(('Vtemp', 'var_linear', 'tauM', 'V', 'Isyn[updated]'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
self._operations.append(('V', 'reset', 'Vtemp', 'O[updated]'))
NeuronModel.register("lif", LIFModel)
[docs]class PLIFModel(NeuronModel):
"""
LIF model:
# V(t) = tuaM * V^n[t-1] + Isyn[t] # tauM: constant membrane time (tauM=RmCm)
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(PLIFModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0) # 20
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1)
# self.neuron_parameters['v_reset'] = kwargs.get('v_reset', 0.0)
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
# self._variables['b'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
# self._variables['I'] = 0.0
self._constant_variables['Vth'] = kwargs.get('v_th', 1)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 0.0)
import math
tau = kwargs.get('tau_m', 2.0) # dt/taum
tau = -math.log(tau - 1.)
self._parameter_variables['tau'] = tau # dt/taum
# self.mem = self.mem + ((inputs - self.mem) * self.w.sigmoid()) * self.dt
self._operations.append(('tau_temp', 'sigmoid', 'tau'))
self._operations.append(('Vtemp', 'minus', 'Isyn[updated]', 'V'))
self._operations.append(('Vtemp1', 'var_linear', 'tau_temp', 'Vtemp', 'V'))
# self._operations.append(('Vtemp', 'var_linear', 'tauM', 'V', 'Isyn[updated]'))
self._operations.append(('O', 'threshold', 'Vtemp1', 'Vth'))
self._operations.append(('V', 'reset', 'Vtemp1', 'O'))
# self._operations.append(('tau_temp', 'sigmoid', 'tau'))
# self._operations.append(('Vtemp', 'var_linear', 'tau_temp', 'V', 'Isyn[updated]'))
# self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
# self._operations.append(('V', 'reset', 'Vtemp', 'O'))
NeuronModel.register("plif", PLIFModel)
# class linearDecayLIFModel(NeuronModel):
# """
# LIF model:
# # V(t) = tuaM * V^n[t-1] + Isyn[t] # tauM: constant membrane time (tauM=RmCm)
# O^n[t] = spike_func(V^n[t-1])
# """
#
# def __init__(self, **kwargs):
# super(ConstantDecayLIFModel, self).__init__()
# # initial value for state variables
# self._variables['V'] = 0.0
# self._variables['O'] = 0.0
# self._variables['Isyn'] = 0.0
#
# self._parameter_variables['Vth'] = kwargs.get('v_th', 1)
# self._constant_variables['Vreset'] = kwargs.get('v_reset', 0.0)
# self._constant_variables['Vdecay'] = kwargs.get('v_decay', 1.0)
#
# self._operations.append(('Vtemp', 'var_linear', 'tauM', 'V', 'Isyn[updated]'))
# self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
# self._operations.append(('Resetting', 'var_mult', 'Vtemp', 'O[updated]'))
# self._operations.append(('V', 'minus', 'Vtemp', 'Resetting'))
#
# NeuronModel.register("constantdecaylif", ConstantDecayLIFModel)
[docs]class ConstantCurrentLIFModel(NeuronModel):
"""
ConstantCurrentLIF model:
V(t) = V^n[t-1] + (dt/taum) * (Ureset-V^n[t-1]+I) # tauM: constant membrane time (tauM=RmCm) Isyn = I*Weight
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(ConstantCurrentLIFModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 20.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
# self.neuron_parameters['v_reset'] = kwargs.get('v_reset', 0.0)
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['I'] = 0.0
self._variables['Isyn'] = 0.0
# beta = self.neuron_parameters['tau_p'] / self.neuron_parameters['tau_q']
# V0 = 1.0 # (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
# self._constant_variables['V0'] = V0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 0.0)
self._membrane_variables['tauM'] = kwargs.get('tau_m', 20.0)
# self._operations.append(('I', 'var_mult', 'V0', 'I_synapse'))
self._operations.append(('decayV', 'minus', 'Isyn', 'V'))
self._operations.append(('Vtemp', 'var_linear', 'tauM', 'decayV', 'V'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
self._operations.append(('Resetting', 'var_mult', 'Vtemp', 'O'))
self._operations.append(('V', 'minus', 'Vtemp', 'Resetting'))
NeuronModel.register("constantcurrentlif", ConstantCurrentLIFModel)
[docs]class NonSpikingLIFModel(NeuronModel):
"""
NonSpikingLIF model:
# V(t) = -tuaM * V^n[t-1] + M^n[t] - S^n[t] # tauM: constant membrane time (tauM=RmCm)
V(t) = V^n[t-1] + (dt/taum) * (PSP-V^n[t-1]) # tauM: constant membrane time (tauM=RmCm)
I^n[t] = V0 * Isyn^n[t-1] # sum(w * O^(n-1)[t])
M^n[t] = tauP * M^n[t-1] + I^n[t-1] # tauP: decaying time constants of membrane integration
S^n[t] = tauQ * S^n[t-1] + I^n[t-1] # tauQ: decaying time constants of synaptic currents
PSP = M - S
"""
def __init__(self, **kwargs):
super(NonSpikingLIFModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 4.0)
# self.neuron_parameters['tau_q'] = kwargs.get('tau_q', 1.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 1.0)
self._variables['I'] = 0.0
self._variables['M'] = 0.0
self._variables['S'] = 0.0
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
self._tau_variables['tauP'] = np.asarray(kwargs.get('tau_p', 4.0))
self._tau_variables['tauQ'] = np.asarray(kwargs.get('tau_q', 1.0))
beta = self._tau_variables['tauP'] / self._tau_variables['tauQ']
V0 = (1 / (beta - 1)) * (beta ** (beta / (beta - 1)))
self._constant_variables['V0'] = V0
self._membrane_variables['tauM'] = kwargs.get('tau_m', 1.0)
# self._tau_constant_variables['tauM'] = kwargs.get('tau_m', 1.0)
self._operations.append(('I', 'var_mult', 'V0', 'Isyn'))
self._operations.append(('M', 'var_linear', 'tauP', 'M', 'I'))
self._operations.append(('S', 'var_linear', 'tauQ', 'S', 'I'))
self._operations.append(('PSP', 'minus', 'M', 'S'))
self._operations.append(('decayV', 'minus', 'PSP', 'V'))
self._operations.append(('V', 'var_linear', 'tauM', 'decayV', 'V'))
NeuronModel.register("nonspikinglif", NonSpikingLIFModel)
[docs]class LIFMModel(NeuronModel):
"""
LIF model:
# I_che = tauP*I + Isyn^n[t-1] + b^n # sum(w * O^(n-1)[t])
# I = I_che + I_ele
# F = tauM * exp(-O^n[t-1] / tauM)
# V(t) = V^n[t-1] * F + I
# O^(n)[t] = spike_func(V^n(t))
"""
def __init__(self, **kwargs):
super(LIFMModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['tau_p'] = kwargs.get('tau_p', 1.0)
# self.neuron_parameters['tau_m'] = kwargs.get('tau_m', 10.0)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', 1.0)
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
# self._variables['b'] = 0.0
# self._variables['I_che'] = 0.0
# self._variables['I_ele'] = 0.0
self._variables['I'] = 0.0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
# self._constant_variables['Vreset'] = v_reset
self._tau_variables['tauM'] = kwargs.get('tau_m', 10.0)
self._tau_variables['tauP'] = kwargs.get('tau_p', 1.0)
self._operations.append(('I', 'var_linear', 'tauP', 'I', 'Isyn[updated]'))
# self._operations.append(('I_che', 'var_linear', 'tauP', 'I_che', 'PSP'))
# self._operations.append(('I', 'add', 'I_che[updated]', 'I_ele'))
self._operations.append(('Vtemp', 'var_linear', 'V', 'tauM', 'I[updated]'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
self._operations.append(('Vreset', 'var_mult', 'Vtemp', 'O[updated]'))
self._operations.append(('V', 'minus', 'Vtemp', 'Vreset'))
NeuronModel.register("lifm", LIFMModel)
[docs]class IZHModel(NeuronModel):
"""
IZH model:
.. math::
V = V + dt / tauM * (C1 * V * V + C2 * V + C3 - U + I) # tauM=1 此处加tauM是为了添加op时和LIF模型保存一致 \\
V = V + dt / tauM * (V* (C1 * V + C2) + C3 - U + I) # 由上式拆分而来 \\
U = U + a. * (b. * V - U) \\
O^n[t] = spike\_func(V^n[t-1])
if V > Vth, \\
then V = C, U = U + d
References:
Izhikevich, E. M. (2003). Simple model of spiking neurons. IEEE Transactions on neural networks, 14(6), 1569-1572.
"""
def __init__(self, **kwargs):
super(IZHModel, self).__init__()
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = -65.0
self._variables['U'] = 1.0
self._variables['Isyn'] = 0.0 # 1.8
self._constant_variables['a'] = kwargs.get('a', 0.02)
self._constant_variables['b'] = kwargs.get('b', 0.2)
self._constant_variables['Vth'] = np.asarray(kwargs.get('v_th', 30))# 30.0
self._constant_variables['Vreset'] = np.asarray(kwargs.get('Vreset', -65.0))
self._constant_variables['d'] = kwargs.get('d', 8.0) # 8.0
self._constant_variables['C1'] = 0.04
self._constant_variables['C2'] = 5
self._constant_variables['C3'] = 140
self._membrane_variables['tauM'] = 1.0
# V = V + dt / tauM * (C1 * V * V + C2 * V + C3 - U + I)
# V = V + dt / tauM * (V* (C1 * V + C2) + C3 - U + I)
# U = U + dt /tauM * a. * (b. * V - U)
self._operations.append(('temp_V1', 'var_linear', 'C1', 'V', 'C2'))
self._operations.append(('temp_V2', 'var_linear', 'temp_V1', 'V', 'C3'))
self._operations.append(('temp_V3', 'minus', 'temp_V2', 'U'))
self._operations.append(('temp_V4', 'add', 'temp_V3', 'Isyn[updated]'))
self._operations.append(('temp_V', 'var_linear', 'tauM', 'temp_V4', 'V'))
self._operations.append(('temp_U1', 'var_mult', 'b', 'V'))
self._operations.append(('temp_U2', 'minus', 'temp_U1', 'U'))
self._operations.append(('temp_U', 'var_linear', 'a', 'temp_U2', 'U'))
self._operations.append(('O', 'threshold', 'temp_V', 'Vth'))
# if V > Vth,
# then V <- C, U <- U + d
self._operations.append(('VResetting', 'minus', 'Vreset', 'temp_V'))
self._operations.append(('V', 'var_linear', 'O[updated]', 'VResetting', 'temp_V'))
self._operations.append(('U', 'var_linear', 'd', 'O[updated]', 'temp_U'))
NeuronModel.register("izh", IZHModel)
[docs]class aEIFModel(NeuronModel):
"""
aEIF model:
.. math::
V = V + dt / tauM * (EL - V + EXP - U + I^n[t]) \\
U = U + dt / tauW * (a * (V - EL) - U) \\
EXP = delta\_t * delta\_t2 * exp(dv\_th/delta\_t2) \\
dv = V - EL \\
dv\_th = V - Vth
O^n[t] = spike\_func(V^n[t-1]) \\
If V > 20: \\
then V = EL, U = U + b
References:
Brette, R., & Gerstner, W. (2005). Adaptive exponential integrate-and-fire model as an
effective description of neuronal activity. Journal of neurophysiology, 94(5), 3637-3642.
"""
def __init__(self, **kwargs):
super(aEIFModel, self).__init__()
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = -70.6
# self._variables['Vt'] = -70.6
self._variables['U'] = 0.0
self._variables['Isyn'] = 0.0
self._variables['EXP'] = 0.0
self._constant_variables['EL'] = kwargs.get('EL', -70.6)
self._constant_variables['a'] = kwargs.get('a', 4) #0.05
self._constant_variables['b'] = kwargs.get('b', 0.0805)
self._constant_variables['Vth'] = kwargs.get('v_th', -50.4)
self._constant_variables['Vspk'] = kwargs.get('Vspk', 20)
self._constant_variables['delta_t'] = kwargs.get('delta_t', 2.0)
self._membrane_variables['C'] = kwargs.get('C', 281)
self._membrane_variables['tauW'] = kwargs.get('tau_w', 144) #144
self._constant_variables['gL'] = kwargs.get('gL', 30)
# V = V + dt / tauM * (gL * (EL - V + EXP) - U + I ^ n[t])
# U = U + dt / tauW * (a * (V - EL) - U)
# EXP = delta_t * exp(du_th / delta_t2)
# du = V - EL
# du_th = V - Vth
# I ^ n[t] = V0 * WgtSum ^ n[t - 1]
# O ^ n[t] = spike_func(V ^ n[t - 1])
self._operations.append(('dv', 'minus', 'V', 'EL'))
self._operations.append(('dv_th', 'minus', 'V', 'Vth'))
self._operations.append(('EXP_T1', 'div', 'dv_th', 'delta_t'))
self._operations.append(('EXP_T2', 'exp', 'EXP_T1'))
self._operations.append(('EXP', 'var_mult', 'delta_t', 'EXP_T2'))
self._operations.append(('temp_V0', 'minus', 'EXP[updated]', 'dv'))
self._operations.append(('temp_V1', 'var_mult', 'gL', 'temp_V0'))
self._operations.append(('temp_V2', 'minus', 'temp_V1', 'U'))
self._operations.append(('temp_V3', 'add', 'temp_V2', 'Isyn[updated]'))
self._operations.append(('Vt', 'var_linear', 'C', 'temp_V3', 'V'))
self._operations.append(('temp_U1', 'var_mult', 'a', 'dv'))
self._operations.append(('temp_U2', 'minus', 'temp_U1', 'U'))
self._operations.append(('Ut', 'var_linear', 'tauW', 'temp_U2', 'U'))
self._operations.append(('O', 'threshold', 'Vt', 'Vspk'))
self._operations.append(('Vtemp2', 'var_mult', 'Vt', 'O[updated]'))
self._operations.append(('Vtemp3', 'minus', 'Vt', 'Vtemp2'))
self._operations.append(('V', 'var_linear', 'EL', 'O[updated]', 'Vtemp3'))
self._operations.append(('U', 'var_linear', 'b', 'O[updated]', 'Ut'))
NeuronModel.register("aeif", aEIFModel)
NeuronModel.register("adex", aEIFModel)
[docs]class GLIFModel(NeuronModel):
"""
Current GLIF5 model:
V = V + dt / C * (I + I1 + I2 - (V - E_L) / R)
Theta_s = Theta_s - dt * b_s * Theta_s
I_j = I_j - dt * k_j * I_j (j = 1,2)
Theta_v = Theta_v + dt * (a_v * (V - E_L) - b_v * Theta_v)
v_th = Theta_v + Theta_s + Theta_inf
O = spike_func(V)
Reset function:
V = E_L + f_v * (V - E_L) - delta_v
Theta_s = Theta_s + delta_Theta_s
I_j = f_j * I_j + delta_I_j (j = 1, 2)
Theta_v = Theta_v
References:
Teeter, C., Iyer, R., Menon, V., Gouwens, N., Feng, D., Berg, J., ... & Mihalas, S. (2018). Generalized leaky
integrate-and-fire models classify multiple neuron types. Nature communications, 9(1), 1-15.
"""
def __init__(self, **kwargs):
super(GLIFModel, self).__init__()
self._variables['V'] = 0.0
self._variables['Theta_s'] = 0.1
self._variables['Theta_v'] = 0.2
self._variables['I1'] = 0.08
self._variables['I2'] = 0.06
self._variables['I'] = 0.0
self._variables['S'] = 0.0
self._variables['O'] = 0.0
self._variables['Vth'] = 1.0
self._variables['Isyn'] = 0.0
# self._constant_variables['deltaV'] = kwargs.get('E_L', 0.0) - kwargs.get('delta_v', 0.05)
self._membrane_variables['C'] = kwargs.get('C', 60.0)
self._membrane_variables['b_s'] = -1 / kwargs.get('b_s', 0.02)
self._membrane_variables['k1'] = -1 / kwargs.get('k_1', 0.8)
self._membrane_variables['k2'] = -1 / kwargs.get('k_2', 0.6)
self._membrane_variables['a_v'] = 1 / kwargs.get('a_v', 0.12)
self._membrane_variables['b_v'] = -1 / kwargs.get('b_v', 0.3)
self._constant_variables['R'] = -1 / kwargs.get('R', 2)
self._constant_variables['E_L'] = kwargs.get('E_L', 0.0)
self._constant_variables['Theta_inf'] = kwargs.get('Theta_inf', 1.0)
self._constant_variables['f_v'] = kwargs.get('f_v', 0.1)
self._constant_variables['delta_v'] = kwargs.get('delta_v', 0.05)
self._constant_variables['delta_Theta_s'] = kwargs.get('delta_Theta_s', 0.2)
self._constant_variables['f1'] = kwargs.get('f1', 1.0)
self._constant_variables['f2'] = kwargs.get('f2', 1.0)
self._constant_variables['delta_I1'] = kwargs.get('delta_I1', 0.1)
self._constant_variables['delta_I2'] = kwargs.get('delta_I2', 0.1)
# I_j = I_j - dt * k_j * I_j
self._operations.append(('I1temp', 'var_linear', 'k1', 'I1', 'I1'))
self._operations.append(('I2temp', 'var_linear', 'k2', 'I2', 'I2'))
# I_sum = I + I1 + I2
self._operations.append(('I_sum1', 'add', 'I1temp', 'I2temp'))
self._operations.append(('I_sum', 'add', 'I_sum1', 'Isyn[updated]'))
# dv = V - E_L
self._operations.append(('dv', 'minus', 'V', 'E_L'))
# V_up = I + I1 + I2 - (V - E_L) / R
self._operations.append(('V_up', 'var_linear', 'dv', 'R', 'I_sum'))
# V = V + dt / C * (I + I1 + I2 - (V - E_L) / R)
self._operations.append(('Vtemp', 'var_linear', 'C', 'V_up', 'V'))
# Theta_s = Theta_s - dt * b_s * Theta_s
self._operations.append(('Theta_s_temp', 'var_linear', 'b_s', 'Theta_s', 'Theta_s'))
# Theta_v = Theta_v + dt * (a_v * (V - E_L) - b_v * Theta_v)
# Theta_temp =Theta_v + dt * a_v * (V - E_L)
# Theta_v = Theta_temp - dt * b_v * Theta_v
self._operations.append(('Theta_temp', 'var_linear', 'a_v', 'dv', 'Theta_v'))
self._operations.append(('Theta_v', 'var_linear', 'b_v', 'Theta_v', 'Theta_temp'))
# v_th = Theta_v + Theta_s + Theta_inf
self._operations.append(('Theta', 'add', 'Theta_v[updated]', 'Theta_s_temp'))
self._operations.append(('Vth', 'add', 'Theta', 'Theta_inf'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth[updated]'))
# V = E_L + f_v * (V - E_L) - delta_v
self._operations.append(('dv_reset', 'minus', 'Vtemp', 'E_L'))
self._operations.append(('Vreset1', 'var_linear', 'f_v', 'dv_reset', 'E_L'))
self._operations.append(('Vreset2', 'minus', 'Vreset1', 'delta_v'))
self._operations.append(('Vreset', 'minus', 'Vreset2', 'Vtemp'))
self._operations.append(('V', 'var_linear', 'Vreset', 'O[updated]', 'Vtemp'))
# Theta_s = Theta_s + delta_Theta_s
self._operations.append(('Theta_s', 'var_linear', 'delta_Theta_s', 'O[updated]', 'Theta_s_temp'))
# I_j = f_j * I_j + delta_I_j (j = 1, 2)
self._operations.append(('I1_temp1', 'var_linear', 'f1', 'I1temp', 'delta_I1'))
self._operations.append(('I1_temp2', 'minus', 'I1_temp1', 'I1temp'))
self._operations.append(('I1', 'var_linear', 'I1_temp1', 'O[updated]', 'I1temp'))
self._operations.append(('I2_temp1', 'var_linear', 'f2', 'I2temp', 'delta_I2'))
self._operations.append(('I2_temp2', 'minus', 'I2_temp1', 'I2temp'))
self._operations.append(('I2', 'var_linear', 'I2_temp1', 'O[updated]', 'I2temp'))
NeuronModel.register("glif", GLIFModel)
[docs]class HodgkinHuxleyModel(NeuronModel):
"""
Hodgkin-Huxley model:
V = V + dt/tau_v * (I - Ik)
Ik = NA + K + L
NA = g_NA * m^3 * h * (V - V_NA)
K = g_K * n^4 * (V - V_K)
L = g_L * (V - V_L)
Na activation:
m = m + dt/tau_m * (alpha_m * (1-m) - beta_m * m)
K activation:
n = n + dt/tau_n * (alpha_n * (1-n) - beta_n * n)
Na inactivation:
h = h + dt/tau_h * (alpha_h * (1-h) - beta_h * h)
original function1:
alpha_m = 0.1 * (-V + 25) / (exp((-V+25)/10) - 1)
beta_m = 4 * exp(-V/18)
alpha_n = 0.01 * (-V + 10) / (exp((-V+10)/10) - 1)
beta_n = 0.125 * exp(-V/80)
alpha_h = 0.07 * exp(-V/20)
beta_h = 1/(exp((-V+30)/10) + 1)
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(HodgkinHuxleyModel, self).__init__()
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['V'] = kwargs.get('V', 0.0)
self._variables['m'] = kwargs.get('m', 0.5)
self._variables['n'] = kwargs.get('n', 0.5)
self._variables['h'] = kwargs.get('h', 0.06)
self._variables['Isyn'] = 0.0
self._constant_variables['Vth'] = kwargs.get('v_th', 1.0)
# self._constant_variables['Vreset'] = 0.0
self._membrane_variables['tauM'] = 1.0
self._membrane_variables['tauN'] = 1.0
self._membrane_variables['tauH'] = 1.0
self._membrane_variables['tauV'] = 1.0
self._constant_variables['1'] = 1.0
# self._constant_variables['65'] = kwargs.get('V65', 0.0)
self._constant_variables['Vreset'] = kwargs.get('Vreset', 0.0)
self._constant_variables['dt'] = kwargs.get('dt', 0.1)
self._constant_variables['g_NA'] = kwargs.get('g_NA', 120.0)
self._constant_variables['g_K'] = kwargs.get('g_K', 36.0)
self._constant_variables['g_L'] = kwargs.get('g_L', 0.3)
self._constant_variables['E_NA'] = kwargs.get('E_NA', 120.0)
self._constant_variables['E_K'] = kwargs.get('E_K', -12.0)
self._constant_variables['E_L'] = kwargs.get('E_L', 10.6)
self._constant_variables['a_m1'] = kwargs.get('alpha_m1', 0.1)
self._constant_variables['a_m2'] = kwargs.get('alpha_m2', 25.0)
self._constant_variables['a_m3'] = kwargs.get('alpha_m3', 10.0)
self._constant_variables['b_m1'] = kwargs.get('beta_m1', 4.0)
self._constant_variables['b_m2'] = kwargs.get('beta_m2', 0.0)
self._constant_variables['b_m3'] = kwargs.get('beta_m3', 18.0)
self._constant_variables['a_n1'] = kwargs.get('alpha_n1', 0.01)
self._constant_variables['a_n2'] = kwargs.get('alpha_n2', 10.0)
self._constant_variables['a_n3'] = kwargs.get('alpha_n3', 10.0)
self._constant_variables['b_n1'] = kwargs.get('beta_n1', 0.125)
self._constant_variables['b_n2'] = kwargs.get('beta_n2', 0.0)
self._constant_variables['b_n3'] = kwargs.get('beta_n3', 80.0)
self._constant_variables['a_h1'] = kwargs.get('alpha_h1', 0.07)
self._constant_variables['a_h2'] = kwargs.get('alpha_h2', 0.0)
self._constant_variables['a_h3'] = kwargs.get('alpha_h3', 20.0)
self._constant_variables['b_h1'] = kwargs.get('beta_h1', 1.0)
self._constant_variables['b_h2'] = kwargs.get('beta_h2', 30.0)
self._constant_variables['b_h3'] = kwargs.get('beta_h3', 10.0)
self._operations.append(('V65', 'add', 'V', 'Vreset'))
# a_m = a_m1 * (-V + a_m2) / (exp((-V + a_m2) / a_m3) - 1)
# b_m = b_m1 * exp((-V + b_m2) / b_m3)
# alpha_m
self._operations.append(('Vam', 'minus', 'a_m2', 'V65'))
self._operations.append(('Vamd', 'div', 'Vam', 'a_m3'))
self._operations.append(('expVamd1', 'exp', 'Vamd'))
self._operations.append(('expVamd', 'minus', 'expVamd1', '1'))
self._operations.append(('amtemp', 'div', 'Vam', 'expVamd'))
self._operations.append(('a_m', 'var_mult', 'a_m1', 'amtemp'))
# beta_m
self._operations.append(('Vbm', 'minus', 'b_m2', 'V65'))
self._operations.append(('Vbmd', 'div', 'Vbm', 'b_m3'))
self._operations.append(('expVbmd', 'exp', 'Vbmd'))
self._operations.append(('b_m', 'var_mult', 'b_m1', 'expVbmd'))
# a_n = a_n1 * (-V + a_n2) / (exp((-V + a_n2) / a_n3) - 1)
# b_n = b_n1 * exp((-V + b_n2) / b_n3)
# alpha_n
self._operations.append(('Van', 'minus', 'a_n2', 'V65'))
self._operations.append(('Vand', 'div', 'Van', 'a_n3'))
self._operations.append(('expVand1', 'exp', 'Vand'))
self._operations.append(('expVand', 'minus', 'expVand1', '1'))
self._operations.append(('antemp', 'div', 'Van', 'expVand'))
self._operations.append(('a_n', 'var_mult', 'a_n1', 'antemp'))
# beta_n
self._operations.append(('Vbn', 'minus', 'b_n2', 'V65'))
self._operations.append(('Vbnd', 'div', 'Vbn', 'b_n3'))
self._operations.append(('expVbnd', 'exp', 'Vbnd'))
self._operations.append(('b_n', 'var_mult', 'b_n1', 'expVbnd'))
# a_h = a_h1 * exp((-V + a_h2) / a_h3)
# b_h = b_h1 / (exp((-V + b_h2) / b_h3 + 1)
# alpha_h
self._operations.append(('Vah', 'minus', 'a_h2', 'V65'))
self._operations.append(('Vahd', 'div', 'Vah', 'a_h3'))
self._operations.append(('expVahd', 'exp', 'Vahd'))
self._operations.append(('a_h', 'var_mult', 'a_h1', 'expVahd'))
# beta_h
self._operations.append(('Vbh', 'minus', 'b_h2', 'V65'))
self._operations.append(('Vbhd', 'div', 'Vbh', 'b_h3'))
self._operations.append(('expVbhd1', 'exp', 'Vbhd'))
self._operations.append(('expVbhd', 'add', 'expVbhd1', '1'))
self._operations.append(('b_h', 'div', 'b_h1', 'expVbhd'))
# m = m + alpha_m * (1 - m) - beta_m * m
# n = n + alpha_n * (1 - n) - beta_n * n
# h = h + alpha_h * (1 - h) - beta_h * h
# m
self._operations.append(('mtemp1', 'minus', '1', 'm'))
self._operations.append(('mtemp2', 'var_mult', 'a_m', 'mtemp1'))
self._operations.append(('betam', 'var_mult', 'b_m', 'm'))
self._operations.append(('mtemp3', 'minus', 'mtemp2', 'betam'))
self._operations.append(('mtemp4', 'var_mult', 'tauM', 'mtemp3'))
self._operations.append(('m', 'add', 'mtemp4', 'm'))
# n
self._operations.append(('ntemp1', 'minus', '1', 'n'))
self._operations.append(('ntemp2', 'var_mult', 'a_n', 'ntemp1'))
self._operations.append(('betan', 'var_mult', 'b_n', 'n'))
self._operations.append(('ntemp3', 'minus', 'ntemp2', 'betan'))
self._operations.append(('ntemp4', 'var_mult', 'tauN', 'ntemp3'))
self._operations.append(('n', 'add', 'ntemp4', 'n'))
# h
self._operations.append(('htemp1', 'minus', '1', 'h'))
self._operations.append(('htemp2', 'var_mult', 'a_h', 'htemp1'))
self._operations.append(('betah', 'var_mult', 'b_h', 'h'))
self._operations.append(('htemp3', 'minus', 'htemp2', 'betah'))
self._operations.append(('htemp4', 'var_mult', 'tauH', 'htemp3'))
self._operations.append(('h', 'add', 'htemp4', 'h'))
# g_NAm3h
self._operations.append(('m2', 'var_mult', 'm[updated]', 'm[updated]'))
self._operations.append(('m3', 'var_mult', 'm2', 'm[updated]'))
self._operations.append(('m3h', 'var_mult', 'm3', 'h[updated]'))
self._operations.append(('g_NAm3h', 'var_mult', 'g_NA', 'm3h'))
# g_Kn4
self._operations.append(('n2', 'var_mult', 'n[updated]', 'n[updated]'))
self._operations.append(('n4', 'var_mult', 'n2', 'n2'))
self._operations.append(('g_Kn4', 'var_mult', 'g_K', 'n4'))
self._operations.append(('d_NA', 'minus', 'V', 'E_NA'))
self._operations.append(('d_K', 'minus', 'V', 'E_K'))
self._operations.append(('d_L', 'minus', 'V', 'E_L'))
# Ik, NA, K, L
self._operations.append(('NA', 'var_mult', 'g_NAm3h', 'd_NA'))
self._operations.append(('K', 'var_mult', 'g_Kn4', 'd_K'))
self._operations.append(('L', 'var_mult', 'g_L', 'd_L'))
self._operations.append(('Ik1', 'add', 'NA', 'K'))
self._operations.append(('Ik2', 'add', 'Ik1', 'L'))
self._operations.append(('Ik', 'var_mult', 'tauV', 'Ik2'))
# I
self._operations.append(('Vtemp', 'minus', 'Isyn[updated]', 'Ik'))
self._operations.append(('V', 'add', 'V', 'Vtemp'))
# O
self._operations.append(('O', 'threshold', 'V[updated]', 'Vth'))
NeuronModel.register("hh", HodgkinHuxleyModel)
[docs]class LIFSTDPEXModel(NeuronModel):
"""
LIF model:
V(t) = decay_v * (v - v_rest) + v_rest + I^n[t]
I^n[t] = V0 * Isyn^n[t] #V0 = 1
theta(t) = decay_th * theta[t-1]
if v >= (vth + theta) then s_out = 1; else s_out = 0;
Reset:
V(t) = s_out * v_reset + (1 - s_out) * v; theta = theta + s_out * th_inc
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(LIFSTDPEXModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['decay_v'] = kwargs.get('decay_v', np.exp(-1/100))
# self.neuron_parameters['decay_th'] = kwargs.get('decay_th', np.exp(-1/1e7))
# self.neuron_parameters['th_inc'] = kwargs.get('th_inc', 0.05)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', -52.0)
# self.neuron_parameters['v_rest'] = kwargs.get('v_rest', -65.0)
# self.neuron_parameters['v_reset'] = kwargs.get('v_reset', -60.0)
self._variables['I'] = 0.0
self._variables['V'] = -65.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
self._variables['theta[stay]'] = 0.0
self._variables['Vth_theta'] = 0.0
# self._constant_variables['V0'] = 1
self._constant_variables['Vth'] = kwargs.get('v_th', -52.0)
self._constant_variables['Vreset'] = kwargs.get('v_reset', -60.0)
self._constant_variables['Vrest'] = kwargs.get('v_rest', -65.0)
self._constant_variables['th_inc'] = kwargs.get('th_inc', 0.05)
self._constant_variables['decay_th'] = kwargs.get('decay_th', np.exp(-1/1e7))
self._constant_variables['decay_v'] = kwargs.get('decay_v', np.exp(-1/100))
# self._operations.append(('I', 'var_mult', 'V0', 'I_synapse[updated]'))
self._operations.append(('PSP1', 'minus', 'V', 'Vrest'))
self._operations.append(('PSP2', 'var_linear', 'decay_v', 'PSP1', 'Vrest'))
self._operations.append(('Vtemp', 'add', 'PSP2', 'Isyn[updated]'))
self._operations.append(('theta_temp', 'var_mult', 'decay_th', 'theta[stay]'))
self._operations.append(('Vth_theta', 'add', 'Vth', 'theta_temp'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth_theta'))
self._operations.append(('Resetting1', 'var_mult', 'Vreset', 'O[updated]'))
self._operations.append(('Resetting2', 'var_mult', 'Vtemp', 'O[updated]'))
self._operations.append(('Resetting3', 'minus', 'Vtemp', 'Resetting2'))
self._operations.append(('V', 'add', 'Resetting1', 'Resetting3'))
self._operations.append(('Resetting_theta', 'var_mult', 'O[updated]', 'th_inc'))
self._operations.append(('theta[stay]', 'add', 'theta_temp', 'Resetting_theta'))
NeuronModel.register("lifstdp_ex", LIFSTDPEXModel)
[docs]class ALIFSTDPEXModel(NeuronModel):
"""
LIF model:
V(t) = decay_v * (v - v_rest) + v_rest + I^n[t]
I^n[t] = V0 * Isyn^n[t] #V0 = 1
theta(t) = decay_th * theta[t-1]
if v >= (vth + theta) then s_out = 1; else s_out = 0;
Reset:
V(t) = s_out * v_reset + (1 - s_out) * v; theta = theta + s_out * th_inc
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(ALIFSTDPEXModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['decay_v'] = kwargs.get('decay_v', np.exp(-1/100))
# self.neuron_parameters['decay_th'] = kwargs.get('decay_th', np.exp(-1/1e7))
# self.neuron_parameters['th_inc'] = kwargs.get('th_inc', 0.05)
# self.neuron_parameters['v_th'] = kwargs.get('v_th', -52.0)
# self.neuron_parameters['v_rest'] = kwargs.get('v_rest', -65.0)
# self.neuron_parameters['v_reset'] = kwargs.get('v_reset', -60.0)
self._variables['I'] = 0.0
self._variables['V'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
self._variables['theta[stay]'] = 0.0
self._variables['Vth_theta'] = 2.0
# self._constant_variables['V0'] = 1
self._constant_variables['Vth'] = kwargs.get('v_th', 2.0)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 2.0)
self._constant_variables['Vrest'] = kwargs.get('v_rest', 0.0)
self._constant_variables['th_inc'] = kwargs.get('th_inc', 0.001)
self._constant_variables['decay_th'] = kwargs.get('decay_th', np.exp(-1/100000.0))
self._constant_variables['decay_v'] = kwargs.get('decay_v', np.exp(-1/100.0))
# self._operations.append(('I', 'var_mult', 'V0', 'I_synapse[updated]'))
self._operations.append(('PSP1', 'minus', 'V', 'Vrest'))
self._operations.append(('PSP2', 'var_linear', 'decay_v', 'PSP1', 'Vrest'))
self._operations.append(('Vtemp', 'add', 'PSP2', 'Isyn[updated]'))
self._operations.append(('theta_temp', 'var_mult', 'decay_th', 'theta[stay]'))
self._operations.append(('Vth_theta', 'add', 'Vth', 'theta_temp'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth_theta'))
self._operations.append(('Resetting1', 'var_mult', 'Vreset', 'O[updated]'))
self._operations.append(('V', 'minus', 'Vtemp', 'Resetting1'))
self._operations.append(('Resetting_theta1', 'var_mult', 'O[updated]', 'th_inc'))
self._operations.append(('Resetting_theta', 'var_mult', 'Resetting_theta1', 'Vth_theta'))
self._operations.append(('theta[stay]', 'add', 'theta_temp', 'Resetting_theta'))
NeuronModel.register("alifstdp_ex", ALIFSTDPEXModel)
[docs]class LIFSTDPIHModel(NeuronModel):
"""
LIF model:
V(t) = decay_v * (v - v_rest) + v_rest + I^n[t]
I^n[t] = V0 * Isyn^n[t] #V0 = 1
Reset:
V(t) = s_out * v_reset + (1 - s_out) * v;
O^n[t] = spike_func(V^n[t-1])
"""
def __init__(self, **kwargs):
super(LIFSTDPIHModel, self).__init__()
# initial value for state variables
# self.neuron_parameters['decay_v'] = kwargs.get('decay_v', np.exp(-1/10))
# self.neuron_parameters['v_th'] = kwargs.get('v_th', -40.0)
# self.neuron_parameters['v_rest'] = kwargs.get('v_rest', -60.0)
# self.neuron_parameters['v_reset'] = kwargs.get('v_reset', -45.0)
self._variables['I'] = 0.0
self._variables['V'] = -60.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0
# self._constant_variables['V0'] = 1
self._constant_variables['Vth'] = kwargs.get('v_th', -40.0)
self._constant_variables['Vreset'] = kwargs.get('v_reset', -45.0)
self._constant_variables['Vrest'] = kwargs.get('v_rest', -60.0)
self._constant_variables['decay_v'] = kwargs.get('decay_v', np.exp(-1/10))
# self._operations.append(('I', 'var_mult', 'V0', 'I_synapse[updated]'))
self._operations.append(('PSP1', 'minus', 'V', 'Vrest'))
self._operations.append(('PSP2', 'var_linear', 'decay_v', 'PSP1', 'Vrest'))
self._operations.append(('Vtemp', 'add', 'PSP2', 'Isyn[updated]'))
self._operations.append(('O', 'threshold', 'Vtemp', 'Vth'))
self._operations.append(('Resetting1', 'var_mult', 'Vreset', 'O[updated]'))
self._operations.append(('Resetting2', 'var_mult', 'Vtemp', 'O[updated]'))
self._operations.append(('Resetting3', 'minus', 'Vtemp', 'Resetting2'))
self._operations.append(('V', 'add', 'Resetting1', 'Resetting3'))
NeuronModel.register("lifstdp_ih", LIFSTDPIHModel)
[docs]class DiehlAndCookModelInt(NeuronModel):
"""
Layer of leaky integrate-and-fire (LIF) neurons with adaptive thresholds (modified for Diehl & Cook 2015
replication), based on integer arithmetic:
if thresh > max_threshold: thresh = thresh/2
# Decay voltages.
V(t) = ((self.decay * (v - self.rest)) >> shift_voltage) + self.rest # decay = 1013; v_rest = 0; shift_voltage = 10
# Integrate inputs.
V(t) = V(t) + (self.refrac_count <= 0).int() * ((Isyn^n[t] * self.weight_sum_coef) >> self.shift_weight_sum_coef)
refrac_count=0; weight_sum_coef = 154; shift_weight_sum_coef = 10
# Decrement refractory counters.
self.refrac_count -= 1
# Check for spiking neurons.
O^n[t] = spike_func(V^n[t-1])
# Refractoriness, voltage reset, and adaptive thresholds.
self.refrac_count.masked_fill_(self.s, self.reset_refrac) self.reset_refrac = 5
self.v.masked_fill_(self.s, self.reset)
self.thresh = self.thresh + self.thresh_plus * self.s.int().sum(0)
# Voltage clipping to lower bound.
if self.lbound is not None:
self.v.masked_fill_(self.v < self.lbound, self.lbound)
"""
def __init__(self, **kwargs):
super(DiehlAndCookModelInt, self).__init__()
# initial value for state variables
self._variables['V'] = 0
self._variables['O'] = 0
self._variables['Isyn'] = 0
self._variables['thresh'] = kwargs.get('thresh', 213)
self._variables['refrac_count'] = kwargs.get('refrac_count', 0)
self._constant_variables['max_threshold'] = kwargs.get('max_threshold', 4915)
self._constant_variables['decay'] = kwargs.get('decay', 1013)
self._constant_variables['shift_voltage'] = kwargs.get('shift_voltage', 10)
self._constant_variables['weight_sum_coef'] = kwargs.get('weight_sum_coef', 154)
self._constant_variables['shift_weight_sum_coef'] = kwargs.get('shift_weight_sum_coef', 10)
self._constant_variables['weight_sum_min'] = kwargs.get('weight_sum_min', -32767)
self._constant_variables['weight_sum_max'] = kwargs.get('weight_sum_max', 32767)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 0)
self._constant_variables['Vrest'] = kwargs.get('v_rest', 0)
self._constant_variables['init_refrac'] = kwargs.get('init_refrac', 0)
self._constant_variables['reset_refrac'] = kwargs.get('reset_refrac', 5)
self._constant_variables['delta'] = kwargs.get('delta', 1)
self._constant_variables['thresh_plus'] = kwargs.get('thresh_plus', 66)
self._constant_variables['lbound'] = kwargs.get('lbound', -32767)
# self._operations.append(('halve', 'gt', 'thresh', 'max_threshold'))
# self._operations.append(('shiftthresh', 'right_shift', 'thresh', 'halve'))
# self._operations.append(('decayV', 'var_mult', 'decay', 'V'))
# self._operations.append(('shiftDecayV', 'right_shift', 'decayV', 'shift_voltage'))
# self._operations.append(('refrac_mask', 'le', 'refrac_count', 'init_refrac'))
# self._operations.append(('decayIsyn', 'var_mult', 'weight_sum_coef', 'Isyn[updated]'))
# self._operations.append(('shiftDecayIsyn', 'right_shift', 'decayIsyn', 'shift_weight_sum_coef'))
# self._operations.append(('refrac_Isyn', 'var_mult', 'refrac_mask', 'shiftDecayIsyn'))
# self._operations.append(('Vtemp', 'add', 'shiftDecayV', 'refrac_Isyn'))
# self._operations.append(('refrac_count_temp', 'minus', 'refrac_count', 'delta'))
# self._operations.append(('O', 'threshold', 'Vtemp', 'shiftthresh'))
# self._operations.append(('refrac_count', 'reset', 'refrac_count_temp', 'O[updated]', 'reset_refrac'))
# self._operations.append(('resetV', 'reset', 'Vtemp', 'O[updated]', 'Vreset'))
# self._operations.append(('sumO', 'sum', 'O[updated]'))
# self._operations.append(('thresh', 'var_linear', 'thresh_plus', 'sumO', 'shiftthresh'))
# self._operations.append(('v_mask', 'lt', 'resetV', 'lbound'))
# self._operations.append(('V', 'reset', 'resetV', 'v_mask', 'lbound'))
self._operations.append((['V', 'O', 'thresh', 'refrac_count'], self.update,
['Isyn[updated]', 'V', 'thresh', 'refrac_count', 'decay', 'Vrest', 'shift_voltage', 'max_threshold',
'weight_sum_coef', 'shift_weight_sum_coef', 'reset_refrac', 'Vreset', 'thresh_plus',
'lbound', 'weight_sum_min', 'weight_sum_max']))
[docs] def update(self, x, v, thresh, refrac_count, decay, rest, shift_voltage, max_threshold, weight_sum_coef,
shift_weight_sum_coef, refrac, reset, thresh_plus, lbound, weight_sum_min, weight_sum_max):
x_ = x.int().clamp(min=weight_sum_min, max=weight_sum_max)
# Halve the firing threshold (including the initial firing threshold and
# the adaptive firing threshold) if the adaptive firing threshold exceeds the
# maximum adaptive firing threshold.
# At every time step, neurons halve their firing thresholds before further
# updating the adaptive firing threshold, in response to excessive adaptive
# firing thresholds computed at the last time step. This ensures that weight
# updates at the last time step can access the excessive adaptive firing
# thresholds before they are halved.
halve = (thresh > max_threshold)
thresh >>= halve # Note: thresh >>= halve 不能改写为 thresh = thresh >> halve,会导致量化版本的STDP训练没效果
del halve
# Decay voltages.
v = ((decay * (v - rest)) >> shift_voltage) + rest
# Integrate inputs.
v += (refrac_count <= 0).int() * ((x_ * weight_sum_coef) >> shift_weight_sum_coef)
# Decrement refractory counters.
refrac_count -= 1
# Check for spiking neurons.
s = v > thresh
# Refractoriness, voltage reset, and adaptive thresholds.
refrac_count.masked_fill_(s, refrac)
v.masked_fill_(s, reset)
thresh += thresh_plus * s.int().sum(0)
# Voltage clipping to lower bound.
v.masked_fill_(v < lbound, lbound)
return v, s.to(dtype=torch.int32), thresh, refrac_count
NeuronModel.register("diehlAndCook", DiehlAndCookModelInt)
[docs]class LIFInt(NeuronModel):
"""
Layer of leaky integrate-and-fire (LIF) neurons based on integer arithmetic:
"""
def __init__(self, **kwargs):
super(LIFInt, self).__init__()
# initial value for state variables
self._variables['V'] = 0
self._variables['O'] = 0
self._variables['Isyn'] = 0
self._variables['refrac_count'] = kwargs.get('refrac_count', 0)
self._constant_variables['thresh'] = kwargs.get('thresh', 1)
self._constant_variables['decay'] = kwargs.get('decay', 1)
self._constant_variables['Vreset'] = kwargs.get('v_reset', 0)
self._constant_variables['Vrest'] = kwargs.get('v_rest', 0)
self._constant_variables['reset_refrac'] = kwargs.get('reset_refrac', 0)
self._constant_variables['delta'] = kwargs.get('delta', 1)
self._constant_variables['reset_isyn'] = kwargs.get('reset_isyn', 0.0)
# self._operations.append(('Vdifference', 'minus', 'V', 'Vrest'))
# self._operations.append(('decayV', 'var_linear', 'decay', 'Vdifference', 'Vrest'))
# self._operations.append(('refrac_mask', 'gt', 'refrac_count', 'reset_refrac'))
# self._operations.append(('Isynreset', 'reset', 'Isyn[updated]', 'refrac_mask', 'reset_isyn'))
# self._operations.append(('refrac_count_temp', 'minus', 'refrac_count', 'delta'))
# self._operations.append(('Vtemp', 'add', 'decayV', 'Isynreset'))
# self._operations.append(('O', 'threshold', 'Vtemp', 'thresh'))
# self._operations.append(('refrac_count', 'reset', 'refrac_count_temp', 'O[updated]', 'reset_refrac'))
# self._operations.append(('V', 'reset', 'Vtemp', 'O[updated]', 'Vreset'))
self._operations.append((['V', 'O', 'refrac_count'], self.update,
['Isyn[updated]', 'V', 'thresh', 'refrac_count', 'decay', 'Vrest', 'reset_refrac', 'Vreset']))
[docs] def update(self, x, v, thresh, refrac_count, decay, rest, refrac, reset):
# Decay voltages.
v = decay * (v - rest) + rest
# Integrate inputs.
x.masked_fill_(refrac_count > 0, 0.0)
# Decrement refractory counters.
refrac_count -= 1
v += x # interlaced
# Check for spiking neurons.
s = v >= thresh
# Refractoriness and voltage reset.
refrac_count.masked_fill_(s, refrac)
v.masked_fill_(s, reset)
return v, s.to(dtype=torch.int32), refrac_count
NeuronModel.register("lifint", LIFInt)
[docs]class CANN_MeanFieldModel(NeuronModel):
"""
Mean Field Model used in "Fung CC, Wong KY, Wu S. A moving bump in a continuous manifold: a comprehensive study of
the tracking dynamics of continuous attractor neural networks. Neural Comput. 2010 Mar;22(3):752-92. doi: 10.1162/neco.2009.07-08-824. "
"Wu S, Wong KY, Fung CC, Mi Y, Zhang W. Continuous Attractor Neural Networks: Candidate of a Canonical Model for
Neural Information Representation.F1000Res. 2016 Feb 10;5:F1000 Faculty Rev-156. doi: 10.12688/f1000research.7387.1. "
U = U + dt/tau * (Iext + rho*WgtSum - U)
O = U^2/(1 + k*rho*sum(U^2))
(WgtSum = weight*O_pre)
"""
def __init__(self, **kwargs):
super(CANN_MeanFieldModel, self).__init__()
self._constant_variables['rho'] = kwargs.get('rho', 0.02)
self._constant_variables['k_rho'] = kwargs.get('k', 0.01)
self._constant_variables['1'] = 1
self._constant_variables['2'] = 2
self._membrane_variables['tau'] = kwargs.get('tau', 1.0)
self._variables['Iext'] = 0.0
self._variables['Isyn'] = 0.0
self._variables['U'] = 0.0
self._variables['O'] = 0.0
self._operations.append(('Isum', 'var_linear', 'rho', 'Isyn', 'Iext'))
self._operations.append(('dU', 'minus', 'Isum', 'U'))
self._operations.append(('U', 'var_linear', 'tau', 'dU', 'U'))
self._operations.append(('ReU', 'relu', 'U[updated]'))
self._operations.append(('U2', 'var_mult', 'ReU', 'ReU'))
self._operations.append(('SumU2', 'reduce_sum', 'U2', '1'))
self._operations.append(('RBase','var_linear', 'k_rho', 'SumU2', '1'))
self._operations.append(('O', 'div', 'U2', 'RBase'))
NeuronModel.register("cann_field", CANN_MeanFieldModel)
[docs]class MeanFieldModel(NeuronModel):
"""
Mean Field Model of LIF neuron "
U = U + dt/tau * (rho*(Iext + Isyn) - U)
O = relu(U)
(WgtSum = weight*O_pre)
"""
def __init__(self, **kwargs):
super(MeanFieldModel, self).__init__()
self._constant_variables['rho'] = kwargs.get('rho', 0.1)
self._constant_variables['1'] = 1
self._constant_variables['2'] = 2
self._membrane_variables['tau'] = kwargs.get('tau', 1.0)
self._variables['Iext'] = 0.0
self._variables['Isyn'] = 0.0
self._variables['U'] = 0.0
self._variables['O'] = 0.0
self._operations.append(('Isum', 'var_linear', 'rho', 'Isyn', 'Iext'))
self._operations.append(('dU', 'minus', 'Isum', 'U'))
self._operations.append(('U', 'var_linear', 'tau', 'dU', 'U'))
self._operations.append(('O', 'relu', 'U[updated]'))
NeuronModel.register("meanfield", MeanFieldModel)
[docs]class SimpleRateModel(NeuronModel):
"""
Rate model "
U = U + dt/tau * (sigmoid(Iext + WgtSum) - U)
(WgtSum = weight*O_pre)
"""
def __init__(self, **kwargs):
super(MeanFieldModel, self).__init__()
self._membrane_variables['tau'] = kwargs.get('tau', 1.0)
self._variables['Iext'] = 0.0
self._variables['WgtSum'] = 0.0
self._variables['U'] = 0.0
self._operations.append(('Isum', 'add', 'WgtSum', 'Iext'))
self._operations.append(('F', 'sigmoid', 'Isum'))
self._operations.append(('dU', 'minus', 'F', 'U'))
self._operations.append(('U', 'var_linear', 'tau', 'dU', 'U'))
[docs]class Darwin_CLIF(NeuronModel):
"""
I = I*P4 + Wgt_sum
V = V*P0 + I + P1
"""
def __init__(self, **kwargs):
super(Darwin_CLIF, self).__init__()
self.dt = kwargs.get('dt', 0.1)
self.tau_m = kwargs.get('tau_m', 12.0)
self.tau_s = kwargs.get('tau_s', 8.0)
self.bias = kwargs.get('bias', 0.0)
self.v_th = kwargs.get('v_th', 16384)
self._constant_variables['P0'] = np.round(np.exp(-self.dt / self.tau_m) * 2.0 ** 8) / 2.0 ** 8 #non-negative
self._constant_variables['P4'] = np.round(np.exp(-self.dt / self.tau_s) * 2.0 ** 8) / 2.0 ** 8 #non-negative
self._constant_variables['P1'] = np.round(self.bias*2.0**15)/2.0*15
self._constant_variables['Vth'] = self.v_th
self._variables['V'] = uniform(10000, 15000)
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0 # named WgtSum in Darwin
self._operations.append((['V', 'I', 'O'], self.update, ['V', 'I', 'Isyn', 'P0', 'P4', 'P1', 'Vth']))
[docs] def update(self, V, I, Isyn, P0, P4, P1, Vth):
I = self.quantize_16(I*P4 + Isyn)
V = self.quantize_16(V*P0 + I + P1)
O = (V > Vth).to(torch.float)
V = V - V*O
return V, I, O
[docs] def quantize_16(self, x):
x = torch.round(torch.clamp(x+2**15, 0, 2**16)) - 2**15
return x
NeuronModel.register("darwin_clif", Darwin_CLIF)
[docs]class Darwin_ALIF(NeuronModel):
"""
Adaptive threshold LIF
I = I*P4 + Wgt_sum
V = V*P0 + I + P1
Vth = Vth*P2 + C1
if spike:
Vth = Vth + C2
"""
def __init__(self, **kwargs):
super(Darwin_ALIF, self).__init__()
self.dt = kwargs.get('dt', 0.1)
self.tau_m = kwargs.get('tau_m', 12.0)
self.tau_s = kwargs.get('tau_s', 8.0)
self.bias = kwargs.get('bias', 0.0)
self.v_th = kwargs.get('v_th', 16384)
self._constant_variables['P0'] = np.round(np.exp(-self.dt / self.tau_m) * 2.0 ** 8) / 2.0 ** 8 # non-negative
self._constant_variables['P4'] = np.round(np.exp(-self.dt / self.tau_s) * 2.0 ** 8) / 2.0 ** 8 # non-negative
self._constant_variables['P1'] = np.round(self.bias * 2.0 ** 15) / 2.0 * 15
self._constant_variables['P2'] = 0.999
self._constant_variables['C1'] = np.round(self.v_th*0.001)
self._constant_variables['C2'] = np.round(self.v_th*0.2)
self._variables['Vth'] = self.v_th
self._variables['V'] = uniform(-30000, 15000)
self._variables['I'] = 0.0
self._variables['O'] = 0.0
self._variables['Isyn'] = 0.0 # named WgtSum in Darwin
self._operations.append((['V', 'I', 'O', 'Vth'], self.update, ['V', 'I', 'Isyn', 'P0', 'P4', 'P1', 'Vth', 'P2', 'C1', 'C2']))
[docs] def update(self, V, I, Isyn, P0, P4, P1, Vth, P2, C1, C2):
I = self.quantize_16(I * P4 + Isyn)
V = self.quantize_16(V * P0 + I + P1)
O = (V > Vth).to(torch.float)
V = V - V * O
Vth = P2*Vth + C1 + O*C2
return V, I, O, Vth
[docs] def quantize_16(self, x):
x = torch.round(torch.clamp(x + 2 ** 15, 0, 2 ** 16)) - 2 ** 15
return x
NeuronModel.register("darwin_alif", Darwin_ALIF)
[docs]class Darwin_Random(NeuronModel):
def __init__(self, **kwargs):
super(Darwin_Random, self).__init__()
self.dt = kwargs.get('dt', 0.1)
self.tau_m = kwargs.get("tau_m", 10.0)
self.std = kwargs.get('std', 0.0)
self.bias = kwargs.get('bias', 0.0)
self.v_th = kwargs.get('v_th', 15000)
self._variables['V'] = uniform(10000, 15000)
self._variables['O'] = 0.0
self._constant_variables['P0'] = np.round(np.exp(-self.dt / self.tau_m) * 2.0 ** 16) / 2.0 ** 16 # non-negative
self._constant_variables['bias'] = np.round(self.bias)
self._constant_variables['std'] = np.round(self.std)
self._constant_variables['Vth'] = self.v_th
self._operations.append((['V', 'O'], self.update, ['V', 'P0', 'bias', 'std', 'Vth']))
[docs] def update(self, V, P0, bias, std, Vth):
V = P0*V + torch.round(std*torch.rand_like(V)-std/2.0) + bias
O = (V > Vth).to(torch.float)
V = V - V * O
return V, O
NeuronModel.register("darwin_random", Darwin_Random)