|
| 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