Source code for devices.dynapse.parameters.translation.core

"""
Dynap-SE2 simulation core implementation.
Use this module converting a hardware configuration to a simulation setting

* Non User facing *
"""

from __future__ import annotations

from typing import Any, Callable, Dict, Tuple
from dataclasses import dataclass, replace

import logging
import numpy as np


from rockpool.devices.dynapse.parameters import param_to_analog, analog_to_param

from rockpool.devices.dynapse.lookup import (
    sim2device_se2,
    default_layout,
    default_weights,
    default_time_constants,
    default_gain_ratios,
    default_currents,
)

from rockpool.devices.dynapse.samna_alias import Dynapse2Core

from rockpool.typehints import FloatVector

from .low_level import DynapSimCurrents, DynapSimLayout, DynapSimWeightBits
from .high_level import DynapSimTime, DynapSimGain
from .high_level.high import DynapSimCoreHigh

__all__ = ["DynapSimCore"]


[docs]@dataclass class DynapSimCore(DynapSimCurrents, DynapSimLayout, DynapSimWeightBits): """ DynapSimCore stores the simulation currents and manages the conversion from configuration objects. It also provides easy update mechanisms using coarse&fine values, high-level parameter representations and etc. .. code-block:: python :caption: Device -> Simulation current (pseudo-code) simcore = DynapSimCore.from_Dynapse2Core(config.chips[0].cores[0]) Itau_ampa = simcore.Itau_ampa """
[docs] @classmethod def from_specification( cls, Idc: FloatVector = default_currents["Idc"], If_nmda: FloatVector = default_currents["If_nmda"], r_gain_ahp: FloatVector = default_gain_ratios["r_gain_ahp"], r_gain_ampa: FloatVector = default_gain_ratios["r_gain_ampa"], r_gain_gaba: FloatVector = default_gain_ratios["r_gain_gaba"], r_gain_nmda: FloatVector = default_gain_ratios["r_gain_nmda"], r_gain_shunt: FloatVector = default_gain_ratios["r_gain_shunt"], r_gain_mem: FloatVector = default_gain_ratios["r_gain_mem"], t_pulse_ahp: FloatVector = default_time_constants["t_pulse_ahp"], t_pulse: FloatVector = default_time_constants["t_pulse"], t_ref: FloatVector = default_time_constants["t_ref"], Ispkthr: FloatVector = default_currents["Ispkthr"], tau_ahp: FloatVector = default_time_constants["tau_ahp"], tau_ampa: FloatVector = default_time_constants["tau_ampa"], tau_gaba: FloatVector = default_time_constants["tau_gaba"], tau_nmda: FloatVector = default_time_constants["tau_nmda"], tau_shunt: FloatVector = default_time_constants["tau_shunt"], tau_mem: FloatVector = default_time_constants["tau_mem"], Iw_0: FloatVector = default_weights["Iw_0"], Iw_1: FloatVector = default_weights["Iw_1"], Iw_2: FloatVector = default_weights["Iw_2"], Iw_3: FloatVector = default_weights["Iw_3"], Iw_ahp: FloatVector = default_currents["Iw_ahp"], C_ahp: FloatVector = default_layout["C_ahp"], C_ampa: FloatVector = default_layout["C_ampa"], C_gaba: FloatVector = default_layout["C_gaba"], C_nmda: FloatVector = default_layout["C_nmda"], C_pulse_ahp: FloatVector = default_layout["C_pulse_ahp"], C_pulse: FloatVector = default_layout["C_pulse"], C_ref: FloatVector = default_layout["C_ref"], C_shunt: FloatVector = default_layout["C_shunt"], C_mem: FloatVector = default_layout["C_mem"], Io: FloatVector = default_layout["Io"], kappa_n: FloatVector = default_layout["kappa_n"], kappa_p: FloatVector = default_layout["kappa_p"], Ut: FloatVector = default_layout["Ut"], Vth: FloatVector = default_layout["Vth"], ) -> DynapSimCore: """ from_specification is a class factory method helping DynapSimCore object construction using higher level representaitons of the currents like gain ratio or time constant whenever applicable. :param Idc: Constant DC current injected to membrane in Amperes, defaults to default_currents["Idc"] :type Idc: FloatVector, optional :param If_nmda: NMDA gate soft cut-off current setting the NMDA gating voltage in Amperes, defaults to default_currents["If_nmda"] :type If_nmda: FloatVector, optional :param r_gain_ahp: spike frequency adaptation block gain ratio, defaults to default_gain_ratios["r_gain_ahp"] :type r_gain_ahp: FloatVector, optional :param r_gain_ampa: xcitatory AMPA synpse gain ratio, defaults to default_gain_ratios["r_gain_ampa"] :type r_gain_ampa: FloatVector, optional :param r_gain_gaba: inhibitory GABA synpse gain ratio, defaults to default_gain_ratios["r_gain_gaba"] :type r_gain_gaba: FloatVector, optional :param r_gain_nmda: excitatory NMDA synpse gain ratio, defaults to default_gain_ratios["r_gain_nmda"] :type r_gain_nmda: FloatVector, optional :param r_gain_shunt: inhibitory SHUNT synpse gain ratio, defaults to default_gain_ratios["r_gain_shunt"] :type r_gain_shunt: FloatVector, optional :param r_gain_mem: neuron membrane gain ratio, defaults to default_gain_ratios["r_gain_mem"] :type r_gain_mem: FloatVector, optional :param t_pulse_ahp: the spike pulse width for spike frequency adaptation circuit in seconds, defaults to default_time_constants["t_pulse_ahp"] :type t_pulse_ahp: FloatVector, optional :param t_pulse: the spike pulse width for neuron membrane in seconds, defaults to default_time_constants["t_pulse"] :type t_pulse: FloatVector, optional :param t_ref: refractory period of the neurons in seconds, defaults to default_time_constants["t_ref"] :type t_ref: FloatVector, optional :param Ispkthr: spiking threshold current, neuron spikes if :math:`I_{mem} > I_{spkthr}` in Amperes, defaults to default_currents["Ispkthr"] :type Ispkthr: FloatVector, optional :param tau_ahp: Spike frequency leakage time constant in seconds, defaults to default_time_constants["tau_ahp"] :type tau_ahp: FloatVector, optional :param tau_ampa: AMPA synapse leakage time constant in seconds, defaults to default_time_constants["tau_ampa"] :type tau_ampa: FloatVector, optional :param tau_gaba: GABA synapse leakage time constant in seconds, defaults to default_time_constants["tau_gaba"] :type tau_gaba: FloatVector, optional :param tau_nmda: NMDA synapse leakage time constant in seconds, defaults to default_time_constants["tau_nmda"] :type tau_nmda: FloatVector, optional :param tau_shunt: SHUNT synapse leakage time constant in seconds, defaults to default_time_constants["tau_shunt"] :type tau_shunt: FloatVector, optional :param tau_mem: Neuron membrane leakage time constant in seconds, defaults to default_time_constants["tau_mem"] :type tau_mem: FloatVector, optional :param Iw_0: weight bit 0 current of the neurons of the core in Amperes, defaults to default_weights["Iw_0"] :type Iw_0: FloatVector, optional :param Iw_1: weight bit 1 current of the neurons of the core in Amperes, defaults to default_weights["Iw_1"] :type Iw_1: FloatVector, optional :param Iw_2: weight bit 2 current of the neurons of the core in Amperes, defaults to default_weights["Iw_2"] :type Iw_2: FloatVector, optional :param Iw_3: weight bit 3 current of the neurons of the core in Amperes, defaults to default_weights["Iw_3"] :type Iw_3: FloatVector, optional :param Iw_ahp: spike frequency adaptation weight current of the neurons of the core in Amperes, defaults to default_currents["Iw_ahp"] :type Iw_ahp: FloatVector, optional :param C_ahp: AHP synapse capacitance in Farads, defaults to default_layout["C_ahp"] :type C_ahp: FloatVector, optional :param C_ampa: AMPA synapse capacitance in Farads, defaults to default_layout["C_ampa"] :type C_ampa: FloatVector, optional :param C_gaba: GABA synapse capacitance in Farads, defaults to default_layout["C_gaba"] :type C_gaba: FloatVector, optional :param C_nmda: NMDA synapse capacitance in Farads, defaults to default_layout["C_nmda"] :type C_nmda: FloatVector, optional :param C_pulse_ahp: spike frequency adaptation circuit pulse-width creation sub-circuit capacitance in Farads, defaults to default_layout["C_pulse_ahp"] :type C_pulse_ahp: FloatVector, optional :param C_pulse: pulse-width creation sub-circuit capacitance in Farads, defaults to default_layout["C_pulse"] :type C_pulse: FloatVector, optional :param C_ref: refractory period sub-circuit capacitance in Farads, defaults to default_layout["C_ref"] :type C_ref: FloatVector, optional :param C_shunt: SHUNT synapse capacitance in Farads, defaults to default_layout["C_shunt"] :type C_shunt: FloatVector, optional :param C_mem: neuron membrane capacitance in Farads, defaults to default_layout["C_mem"] :type C_mem: FloatVector, optional :param Io: Dark current in Amperes that flows through the transistors even at the idle state, defaults to default_layout["Io"] :type Io: FloatVector, optional :param kappa_n: Subthreshold slope factor (n-type transistor), defaults to default_layout["kappa_n"] :type kappa_n: FloatVector, optional :param kappa_p: Subthreshold slope factor (p-type transistor), defaults to default_layout["kappa_p"] :type kappa_p: FloatVector, optional :param Ut: Thermal voltage in Volts, defaults to default_layout["Ut"] :type Ut: FloatVector, optional :param Vth: The cut-off Vgs potential of the transistors in Volts (not type specific), defaults to default_layout["Vth"] :type Vth: FloatVector, optional :return: DynapSimCore object instance :rtype: DynapSimCore """ # Depended default parameter initialization Idc = Io if Idc is None else Idc If_nmda = Io if If_nmda is None else If_nmda # Construct the core with compulsory low level current parameters _core = cls( Idc=Idc, If_nmda=If_nmda, Ispkthr=Ispkthr, Iw_0=Iw_0, Iw_1=Iw_1, Iw_2=Iw_2, Iw_3=Iw_3, Iw_ahp=Iw_ahp, C_ahp=C_ahp, C_ampa=C_ampa, C_gaba=C_gaba, C_nmda=C_nmda, C_pulse_ahp=C_pulse_ahp, C_pulse=C_pulse, C_ref=C_ref, C_shunt=C_shunt, C_mem=C_mem, Io=Io, kappa_n=kappa_n, kappa_p=kappa_p, Ut=Ut, Vth=Vth, ) # Set the Itau currents _time = DynapSimTime( t_pulse_ahp, t_pulse, t_ref, tau_ahp, tau_ampa, tau_gaba, tau_nmda, tau_shunt, tau_mem, ) _core = _time.update_DynapSimCore(_core) # Set Igain currents depending on the ratio between related Itau currents _gain = DynapSimGain( r_gain_ahp, r_gain_ampa, r_gain_gaba, r_gain_nmda, r_gain_shunt, r_gain_mem, ) _core = _gain.update_DynapSimCore(_core) return _core
[docs] @classmethod def from_Dynapse2Core(cls, core: Dynapse2Core) -> DynapSimCore: """ from_Dynapse2Core is a class factory method which uses samna configuration objects to extract the simulation currents :return: a dynapse core simulation object whose parameters are imported from a samna configuration object :rtype: DynapSimCore """ _current = lambda name: param_to_analog(name, core.parameters[name]) _dict = {sim: _current(param) for sim, param in sim2device_se2.items()} _mod = cls(**_dict) return _mod
[docs] def export_Dynapse2Parameters(self) -> Dict[str, Tuple[np.uint8, np.uint8]]: """ export_Dynapse2Parameters converts all current values to their coarse-fine value representations for device configuration :return: a dictionary of mapping between parameter names and respective coarse-fine values :rtype: Dict[str, Tuple[np.uint8, np.uint8]] """ converter = lambda sim, param: analog_to_param( param, self.__getattribute__(sim) ) param_dict = { param: converter(sim, param) for sim, param in sim2device_se2.items() } return param_dict
[docs] def update(self, attr: str, value: Any) -> DynapSimCore: """ update_current updates an attribute and returns a new object, does not change the original object. :param attr: any attribute that belongs to DynapSimCore object :type attr: str :param value: the new value to set :type value: Any :return: updated DynapSimCore object :rtype: DynapSimCore """ if attr in list(self.__dict__.keys()): _updated = replace(self) _updated.__setattr__(attr, value) self.compare(self, _updated) return _updated
def __update_high_level( self, obj: DynapSimCoreHigh, attr_getter: Callable[[str], Any], attr: str, value: Any, ) -> DynapSimCore: """ __update_high_level updates high level representations of the current values like time constants and gain ratios. The current values are updated accordingly without changing the original object. :param obj: the high level object that stores the projections of the current values :type obj: DynapSimCoreHigh :param attr_getter: a function to get the high level attribute from the high level object :type attr_getter: Callable[[str], Any] :param attr: any attribute that belongs to any DynapSimCoreHigh object :type attr: str :param value: the new value to set :type value: Any :return: updated DynapSimCore object :rtype: DynapSimCore """ if attr in list(obj.__dict__.keys()): obj.__setattr__(attr, value) _updated = obj.update_DynapSimCore(self) logging.info( f" {attr} value changed from {attr_getter(attr)} to {obj.__getattribute__(attr)}" ) self.compare(self, _updated) return _updated
[docs] def update_time_constant(self, attr: str, value: Any) -> DynapSimCore: """ update_time_constant updates currents setting time constant attributes :param attr: any attribute that belongs to any DynapSimTime object :type attr: str :param value: the new value to set :type value: Any :return: updated DynapSimCore object :rtype: DynapSimCore """ return self.__update_high_level( obj=DynapSimTime(), attr_getter=lambda name: self.time.__getattribute__(name), attr=attr, value=value, )
[docs] def update_gain_ratio(self, attr: str, value: Any) -> DynapSimCore: """ update_gain_ratio updates currents setting gain ratio (Igain/Itau) attributes :param attr: any attribute that belongs to any DynapSimGain object :type attr: str :param value: the new value to set :type value: Any :return: updated DynapSimCore object :rtype: DynapSimCore """ return self.__update_high_level( obj=DynapSimGain(), attr_getter=lambda name: self.gain.__getattribute__(name), attr=attr, value=value, )
[docs] @staticmethod def compare(core1: DynapSimCore, core2: DynapSimCore) -> Dict[str, Tuple[Any]]: """ compare compares two DynapSimCore objects detects the different values set :param core1: the first core object :type core1: DynapSimCore :param core2: the second core object to compare against the first one :type core2: DynapSimCore :return: a dictionary of changed values :rtype: Dict[str, Tuple[Any]] """ changed = {} for key in core1.__dict__: val1 = core1.__getattribute__(key) val2 = core2.__getattribute__(key) if val1 != val2: changed[key] = (val1, val2) logging.info(f" {key} value changed from {val1} to {val2}") return changed
@property def layout(self) -> DynapSimLayout: """layout returns a subset of object which belongs to DynapSimLayout""" __dict = dict.fromkeys(DynapSimLayout.__annotations__.keys()) for key in __dict: __dict[key] = self.__getattribute__(key) return DynapSimLayout(**__dict) @property def currents(self) -> DynapSimCurrents: """currents returns a subset of object which belongs to DynapSimCurrents""" __dict = dict.fromkeys(DynapSimCurrents.__annotations__.keys()) for key in __dict: __dict[key] = self.__getattribute__(key) return DynapSimCurrents(**__dict) @property def weight_bits(self) -> DynapSimWeightBits: """weight_bits returns a subset of object which belongs to DynapSimWeightBits""" __dict = dict.fromkeys(DynapSimWeightBits.__annotations__.keys()) for key in __dict: __dict[key] = self.__getattribute__(key) return DynapSimWeightBits(**__dict) @property def time(self) -> DynapSimTime: """time creates the high level time constants set by currents Ipulse_ahp, Ipulse, Iref, Itau_ahp, Itau_ampa, Itau_gaba, Itau_nmda, Itau_shunt, Itau_mem""" return DynapSimTime.from_DynapSimCore(self) @property def gain(self) -> DynapSimGain: """gain creates the high level gain ratios set by currents : Igain_ahp, Igain_ampa, Igain_gaba, Igain_nmda, Igain_shunt, Igain_mem""" return DynapSimGain.from_DynapSimCore(self)