Skip to content

Commit 8b04b38

Browse files
committed
add test
1 parent 62cd73f commit 8b04b38

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# test_iaf_psc_exp_ps.py
4+
#
5+
# This file is part of NEST.
6+
#
7+
# Copyright (C) 2004 The NEST Initiative
8+
#
9+
# NEST is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU General Public License as published by
11+
# the Free Software Foundation, either version 2 of the License, or
12+
# (at your option) any later version.
13+
#
14+
# NEST is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
21+
22+
"""
23+
Synopsis: (test_iaf_psc_exp_ps_lossless) run -> compares response to current step with analytical solution and tests lossless spike detection
24+
25+
Description:
26+
test_iaf_psc_exp_ps_lossless.py is an overall test of the iaf_psc_exp_ps_lossless model connected
27+
to some useful devices.
28+
29+
The first part of this test is exactly the same as test_iaf_psc_exp_ps,
30+
demonstrating the numerical equivalency of both models in usual conditions.
31+
The only difference between the models, which is tested in the second part,
32+
is the detection of double threshold crossings during a simulation step
33+
(so the membrane potential is again below V_th at the end of the step)
34+
by the lossless model.
35+
36+
The second part tests whether the lossless spike detection algorithm [1] is
37+
working correctly.
38+
39+
The algorithm checks whether a spike is emitted on the basis of the neurons position
40+
in state space. There are 4 regions in state space (see [1]): NS1, NS2, S1 and S2.
41+
S1 corresponds to threshold crossings that would also be detected by the lossy
42+
implementation /iaf_psc_exp_ps. S2 corresponds to crossings that would be missed.
43+
The lossless model detects both.
44+
45+
The test brings 3 neurons into the state space regions NS2, S1 and S2,
46+
which is done by keeping their membrane potential close to threshold and then
47+
sending a single spike to them, which, received via different synaptic weights,
48+
sets the synaptic current such that the neurons are in the respective region.
49+
The existence and precise times of the resulting spikes are compared to reference data.
50+
51+
If you need to reproduce the reference data, ask the authors of [1] for the script
52+
regions_algorithm.py which they used to generate Fig. 6. Here you can adjust the
53+
parameters as wished and obtain the respective regions.
54+
55+
56+
References:
57+
[1] Krishnan J, Porta Mana P, Helias M, Diesmann M and Di Napoli E
58+
(2018) Perfect Detection of Spikes in the Linear Sub-threshold
59+
Dynamics of Point Neurons. Front. Neuroinform. 11:75.
60+
doi: 10.3389/fninf.2017.00075
61+
62+
Author: Jeyashree Krishnan, 2017, and Christian Keup, 2018
63+
SeeAlso: test_iaf_psc_exp, test_iaf_psc_exp_ps
64+
"""
65+
66+
import nest
67+
import numpy as np
68+
import numpy.testing as nptest
69+
import pytest
70+
71+
72+
def test_precise_spiking_dc_input():
73+
"""
74+
Exactly the same as in test_iaf_psc_exp_ps
75+
"""
76+
dt = 0.1
77+
dc_amp = 1000.0
78+
79+
nest.ResetKernel()
80+
nest.set(resolution=dt, local_num_threads=1)
81+
82+
dc_gen = nest.Create("dc_generator", {"amplitude": dc_amp})
83+
nrn = nest.Create("iaf_psc_exp_ps_lossless", 1)
84+
vm = nest.Create("voltmeter", {"interval": 0.1})
85+
86+
syn_spec = {"synapse_model": "static_synapse", "weight": 1.0, "delay": dt}
87+
nest.Connect(dc_gen, nrn, syn_spec=syn_spec)
88+
nest.Connect(vm, nrn, syn_spec=syn_spec)
89+
90+
nest.Simulate(8.0)
91+
92+
times = vm.get("events", "times")
93+
times -= dt # account for delay to multimeter
94+
95+
tau_m = nrn.get("tau_m")
96+
R = tau_m / nrn.get("C_m")
97+
theta = nrn.get("V_th")
98+
E_L = nrn.get("E_L")
99+
100+
# array for analytical solution
101+
V_m_analytical = np.empty_like(times)
102+
V_m_analytical[:] = nrn.get("E_L")
103+
104+
# first index for which the DC current is received by neuron.
105+
# DC current will be integrated from this time step
106+
start_index = 1
107+
108+
# analytical solution without delay and threshold on a grid
109+
vm_soln = E_L + (1 - np.exp(-times / tau_m)) * R * dc_amp
110+
111+
# exact time at which the neuron will spike
112+
exact_spiketime = -tau_m * np.log(1 - (theta - E_L) / (R * dc_amp))
113+
114+
# offset from grid point
115+
time_offset = exact_spiketime - (exact_spiketime // dt) * dt
116+
117+
# solution calculated on the grid, with t0 being the exact spike time
118+
vm_soln_offset = E_L + (1 - np.exp(-(times - time_offset + dt) / tau_m)) * R * dc_amp
119+
120+
# rise until threshold
121+
V_m_analytical[start_index:] = vm_soln[:-start_index]
122+
123+
# set refractory potential
124+
# first index after spike, offset by time at which DC current arrives
125+
crossing_ind = int(exact_spiketime // dt + 1) + start_index
126+
num_ref = int(nrn.get("t_ref") / dt)
127+
V_m_analytical[crossing_ind : crossing_ind + num_ref] = nrn.get("V_reset")
128+
129+
# rise after refractory period
130+
num_inds = len(times) - crossing_ind - num_ref
131+
V_m_analytical[crossing_ind + num_ref :] = vm_soln_offset[:num_inds]
132+
133+
nptest.assert_array_almost_equal(V_m_analytical, vm.get("events", "V_m"))
134+
135+
136+
def test_lossless_spike_detection():
137+
nest.ResetKernel()
138+
nest.set(local_num_threads=1, resolution=1.0) # low resolution is crucial for the test
139+
140+
params = {"tau_m": 100.0, "tau_syn_ex": 1.0, "tau_syn_in": 1.0, "C_m": 250.0, "V_th": -49.0}
141+
142+
nest.SetDefaults("iaf_psc_exp_ps_lossless", params)
143+
144+
# 3 neurons that will test the detection of different types of threshold crossing
145+
nrn_nospike = nest.Create("iaf_psc_exp_ps_lossless")
146+
nrn_missingspike = nest.Create("iaf_psc_exp_ps_lossless")
147+
nrn_spike = nest.Create("iaf_psc_exp_ps_lossless")
148+
149+
# syn weights of trigger spike that will put the nrn in the different state space regions
150+
w_nospike = 55.0
151+
w_missingspike = 70.0
152+
w_spike = 90.0
153+
154+
# send one trigger spike to the nrns at specified time:
155+
156+
sp_gen = nest.Create("spike_generator", {"precise_times": True, "spike_times": [3.0]})
157+
158+
nest.Connect(sp_gen, nrn_nospike)
159+
nest.Connect(sp_gen, nrn_missingspike)
160+
nest.Connect(sp_gen, nrn_spike)
161+
162+
# external current to keep nrns close to threshold:
163+
dc_gen = nest.Create("dc_generator", {"amplitude": 52.5})
164+
165+
nest.Connect(dc_gen, nrn_nospike, syn_spec={"delay": 1.0, "weight": 1})
166+
nest.Connect(dc_gen, nrn_missingspike, syn_spec={"delay": 1.0, "weight": 1})
167+
nest.Connect(dc_gen, nrn_spike, syn_spec={"delay": 1.0, "weight": 1})
168+
169+
# read out spike response of nrns:
170+
sr_nospike = nest.Create("spike_recorder")
171+
nest.Connect(nrn_nospike, sr_nospike)
172+
173+
sr_missingspike = nest.Create("spike_recorder")
174+
nest.Connect(nrn_missingspike, sr_missingspike)
175+
176+
sr_spike = nest.Create("spike_recorder")
177+
nest.Connect(nrn_spike, sr_spike)
178+
179+
nest.Simulate(2.0)
180+
181+
# set nrns close to threshold
182+
nrn_nospike.V_m = -49.001
183+
nrn_missingspike.V_m = -49.001
184+
nrn_spike.V_m = -49.001
185+
186+
# swich off ext. current. This effect will reach the nrns at 3.0 due to syn delay,
187+
# so that the external current will be zero when the trigger spike arrives at 4.0 .
188+
dc_gen.amplitude = 0
189+
190+
nest.Simulate(10.0)
191+
192+
# get spike times
193+
time_nospike = sr_nospike.events["times"]
194+
time_missingspike = sr_missingspike.events["times"]
195+
time_spike = sr_spike.events["times"]
196+
print(time_nospike)
197+
assert len(time_nospike) == 0
198+
assert time_missingspike == pytest.approx(4.01442)
199+
assert time_spike == pytest.approx(4.00659)
200+
201+
202+
# sr_nospike /events get /times get cva % array of spike times (this one should be empty)
203+
# Total % sum of array elements. works also for empty array
204+
# 6 ToUnitTestPrecision
205+
# /time_nospike Set
206+
207+
# sr_missingspike /events get /times get cva
208+
# Total
209+
# 6 ToUnitTestPrecision
210+
# /time_missingspike Set
211+
212+
# sr_spike /events get /times get cva
213+
# Total
214+
# 6 ToUnitTestPrecision
215+
# /time_spike Set
216+
217+
218+
# { time_nospike 0 eq } assert_or_die
219+
# { time_missingspike 4.01442 eq } assert_or_die
220+
# { time_spike 4.00659 eq } assert_or_die
221+
# pass

0 commit comments

Comments
 (0)