1
|
1
|
import numpy as np
|
2
|
|
|
3
|
1
|
from nengo import LIF
|
4
|
1
|
from nengo.builder.builder import Builder
|
5
|
1
|
from nengo.builder.neurons import SimNeurons
|
6
|
1
|
from nengo.neurons import NeuronType
|
7
|
|
|
8
|
1
|
__all__ = ['Unit', 'Tanh', 'init_lif']
|
9
|
|
|
10
|
|
|
11
|
1
|
def _sample_lif_state(sim, ens, x0, rng):
|
12
|
|
"""Sample the LIF's voltage/refractory assuming uniform within ISI.
|
13
|
|
"""
|
14
|
1
|
lif = ens.neuron_type
|
15
|
1
|
params = sim.model.params
|
16
|
|
|
17
|
1
|
eval_points = x0[None, :]
|
18
|
1
|
x = np.dot(eval_points, params[ens].encoders.T / ens.radius)[0]
|
19
|
1
|
a = ens.neuron_type.rates(x, params[ens].gain, params[ens].bias)
|
20
|
1
|
J = params[ens].gain * x + params[ens].bias
|
21
|
|
|
22
|
|
# fast-forward to a random time within the ISI for any active neurons
|
23
|
1
|
is_active = a > 0
|
24
|
1
|
t_isi = np.zeros_like(a)
|
25
|
1
|
t_isi[is_active] = rng.rand(np.count_nonzero(is_active)) / a[is_active]
|
26
|
|
|
27
|
|
# work backwards through the LIF solution to find the corresponding voltage
|
28
|
|
# since the refractory period is at the beginning of the ISI, we must
|
29
|
|
# set that first, then subtract and use the remaining delta
|
30
|
1
|
refractory_time = np.where(
|
31
|
|
is_active & (t_isi < lif.tau_ref),
|
32
|
|
sim.dt + (lif.tau_ref - t_isi), 0) # dt immediately subtracted
|
33
|
1
|
delta_t = (t_isi - lif.tau_ref).clip(0)
|
34
|
1
|
voltage = -J * np.expm1(-delta_t / lif.tau_rc)
|
35
|
|
|
36
|
|
# fast-forward to the steady-state for any subthreshold neurons
|
37
|
1
|
subthreshold = ~is_active
|
38
|
1
|
voltage[subthreshold] = J[subthreshold].clip(0)
|
39
|
|
|
40
|
1
|
return voltage, refractory_time
|
41
|
|
|
42
|
|
|
43
|
1
|
def init_lif(sim, ens, x0=None, rng=None):
|
44
|
|
"""Initialize an ensemble of LIF Neurons to represent ``x0``.
|
45
|
|
|
46
|
|
Must be called from within a simulator context block, and before
|
47
|
|
the simulation (see example below).
|
48
|
|
|
49
|
|
Parameters
|
50
|
|
----------
|
51
|
|
sim : :class:`nengo.Simulator`
|
52
|
|
The created simulator, from whose context the call is within.
|
53
|
|
ens : :class:`nengo.Ensemble`
|
54
|
|
The ensemble of LIF neurons to be initialized.
|
55
|
|
x0 : ``(d,) array_like``, optional
|
56
|
|
A ``d``-dimensional state-vector that the
|
57
|
|
ensemble should be initialized to represent, where
|
58
|
|
``d = ens.dimensions``. Defaults to the zero vector.
|
59
|
|
rng : :class:`numpy.random.RandomState` or ``None``, optional
|
60
|
|
Random number generator state.
|
61
|
|
|
62
|
|
Returns
|
63
|
|
-------
|
64
|
|
v : ``(n,) np.array``
|
65
|
|
Array of initialized voltages, where ``n = ens.n_neurons``.
|
66
|
|
r : ``(n,) np.array``
|
67
|
|
Array of initialized refractory times, where ``n = ens.n_neurons``.
|
68
|
|
|
69
|
|
Notes
|
70
|
|
-----
|
71
|
|
This will not initialize the synapses.
|
72
|
|
|
73
|
|
Examples
|
74
|
|
--------
|
75
|
|
>>> import nengo
|
76
|
|
>>> from nengolib import Network
|
77
|
|
>>> from nengolib.neurons import init_lif
|
78
|
|
>>>
|
79
|
|
>>> with Network() as model:
|
80
|
|
>>> u = nengo.Node(0)
|
81
|
|
>>> x = nengo.Ensemble(100, 1)
|
82
|
|
>>> nengo.Connection(u, x)
|
83
|
|
>>> p_v = nengo.Probe(x.neurons, 'voltage')
|
84
|
|
>>>
|
85
|
|
>>> with nengo.Simulator(model, dt=1e-4) as sim:
|
86
|
|
>>> init_lif(sim, x)
|
87
|
|
>>> sim.run(0.01)
|
88
|
|
>>>
|
89
|
|
>>> import matplotlib.pyplot as plt
|
90
|
|
>>> plt.title("Initialized LIF Voltage Traces")
|
91
|
|
>>> plt.plot(1e3 * sim.trange(), sim.data[p_v])
|
92
|
|
>>> plt.xlabel("Time (ms)")
|
93
|
|
>>> plt.ylabel("Voltage (Unitless)")
|
94
|
|
>>> plt.show()
|
95
|
|
"""
|
96
|
|
|
97
|
1
|
if rng is None:
|
98
|
1
|
rng = sim.rng
|
99
|
|
|
100
|
1
|
if x0 is None:
|
101
|
1
|
x0 = np.zeros(ens.dimensions)
|
102
|
|
else:
|
103
|
1
|
x0 = np.atleast_1d(x0)
|
104
|
1
|
if x0.shape != (ens.dimensions,):
|
105
|
1
|
raise ValueError(
|
106
|
|
"x0 must be an array of length %d" % ens.dimensions)
|
107
|
|
|
108
|
1
|
if not isinstance(ens.neuron_type, LIF):
|
109
|
1
|
raise ValueError("ens.neuron_type=%r must be an instance of "
|
110
|
|
"nengo.LIF" % ens.neuron_type)
|
111
|
|
|
112
|
1
|
vr = _sample_lif_state(sim, ens, x0, rng)
|
113
|
|
|
114
|
|
# https://github.com/nengo/nengo/issues/1415
|
115
|
1
|
signal = sim.model.sig[ens.neurons]
|
116
|
1
|
sim.signals[signal['voltage']], sim.signals[signal['refractory_time']] = vr
|
117
|
1
|
return vr
|
118
|
|
|
119
|
|
|
120
|
1
|
class Unit(NeuronType):
|
121
|
|
"""A neuron model with gain=1 and bias=0 on its input."""
|
122
|
|
|
123
|
1
|
def rates(self, x, gain, bias):
|
124
|
|
raise NotImplementedError("unit does not support decoding")
|
125
|
|
|
126
|
1
|
def gain_bias(self, max_rates, intercepts):
|
127
|
1
|
return np.ones_like(max_rates), np.zeros_like(max_rates)
|
128
|
|
|
129
|
|
|
130
|
1
|
class Tanh(Unit):
|
131
|
|
"""Common hyperbolic tangent neural nonlinearity."""
|
132
|
|
|
133
|
1
|
def step_math(self, dt, J, output):
|
134
|
1
|
output[...] = np.tanh(J)
|
135
|
|
|
136
|
|
|
137
|
1
|
@Builder.register(Unit)
|
138
|
|
def build_unit(model, unit, neurons):
|
139
|
|
"""Adds all unit neuron types to the nengo reference backend."""
|
140
|
1
|
model.add_op(SimNeurons(neurons=unit,
|
141
|
|
J=model.sig[neurons]['in'],
|
142
|
|
output=model.sig[neurons]['out']))
|