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