1# -*- coding: utf-8 -*- 2""" 3Definition of NativeElectrodeType class for NEST. 4 5:copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. 6:license: CeCILL, see LICENSE for details. 7""" 8 9import numpy as np 10import nest 11from pyNN.common import Population, PopulationView, Assembly 12from pyNN.parameters import ParameterSpace 13from pyNN.nest.simulator import state 14from pyNN.nest.cells import get_defaults 15from pyNN.models import BaseCurrentSource 16from .conversion import make_sli_compatible 17 18 19class NestCurrentSource(BaseCurrentSource): 20 """Base class for a nest source of current to be injected into a neuron.""" 21 22 def __init__(self, **parameters): 23 self._device = nest.Create(self.nest_name) 24 self.cell_list = [] 25 self.parameter_space = ParameterSpace(self.default_parameters, 26 self.get_schema(), 27 shape=(1,)) 28 if parameters: 29 self.parameter_space.update(**parameters) 30 31 self.min_delay = state.min_delay 32 self.timestep = state.dt # NoisyCurrentSource has a parameter called "dt", so use "timestep" here 33 state.current_sources.append(self) 34 35 def inject_into(self, cells): 36 for id in cells: 37 if id.local and not id.celltype.injectable: 38 raise TypeError("Can't inject current into a spike source.") 39 if isinstance(cells, (Population, PopulationView, Assembly)): 40 self.cell_list = [cell for cell in cells] 41 else: 42 self.cell_list = cells 43 nest.Connect(self._device, self.cell_list, syn_spec={"delay": state.min_delay}) 44 45 def _reset(self): 46 # after a reset, need to adjust parameters for time offset 47 updated_params = {} 48 for name, value in self.parameter_space.items(): 49 if name in ("start", "stop", "amplitude_times"): 50 updated_params[name] = value 51 if updated_params: 52 state.set_status(self._device, updated_params) 53 54 def _delay_correction(self, value): 55 """ 56 A change in a device requires a min_delay to take effect at the target 57 """ 58 corrected = value - self.min_delay 59 # set negative times to zero 60 if isinstance(value, np.ndarray): 61 corrected = np.where(corrected > 0, corrected, 0.0) 62 else: 63 corrected = max(corrected, 0.0) 64 return corrected 65 66 def record(self): 67 self.i_multimeter = nest.Create( 68 'multimeter', params={'record_from': ['I'], 'interval': state.dt}) 69 nest.Connect(self.i_multimeter, self._device) 70 71 def _get_data(self): 72 events = nest.GetStatus(self.i_multimeter)[0]['events'] 73 # Similar to recording.py: NEST does not record values at 74 # the zeroth time step, so we add them here. 75 t_arr = np.insert(np.array(events['times']), 0, 0.0) 76 i_arr = np.insert(np.array(events['I']/1000.0), 0, 0.0) 77 # NEST and pyNN have different concepts of current initiation times 78 # To keep this consistent across simulators, we will have current 79 # initiating at the electrode at t_start and effect on cell at next dt 80 # This requires padding min_delay equivalent period with 0's 81 pad_length = int(self.min_delay/self.timestep) 82 i_arr = np.insert(i_arr[:-pad_length], 0, [0]*pad_length) 83 return t_arr, i_arr 84 85 86def native_electrode_type(model_name): 87 """ 88 Return a new NativeElectrodeType subclass. 89 """ 90 assert isinstance(model_name, str) 91 default_parameters, default_initial_values = get_defaults(model_name) 92 return type(model_name, 93 (NativeElectrodeType,), 94 {'nest_name': model_name, 95 'default_parameters': default_parameters, 96 'default_initial_values': default_initial_values, 97 }) 98 99 100# Should be usable with any NEST current generator 101class NativeElectrodeType(NestCurrentSource): 102 103 _is_computed = True 104 _is_playable = True 105 106 def __init__(self, **parameters): 107 NestCurrentSource.__init__(self, **parameters) 108 self.parameter_space.evaluate(simplify=True) 109 state.set_status(self._device, 110 make_sli_compatible(self.parameter_space.as_dict())) 111