Skip to content

Commit c4b3836

Browse files
authored
Merge pull request #2986 from janskaar/sli2py_iaf_psc_exp_multisynapse
bug fix iaf_psc_exp_multisynapse / sli2py test_iaf_psc_exp_multisynapse
2 parents 1ab8c5d + 2301f04 commit c4b3836

File tree

7 files changed

+207
-329
lines changed

7 files changed

+207
-329
lines changed

models/iaf_psc_alpha_multisynapse.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ iaf_psc_alpha_multisynapse::Parameters_::Parameters_()
110110
iaf_psc_alpha_multisynapse::State_::State_()
111111
: I_const_( 0.0 )
112112
, V_m_( 0.0 )
113-
, current_( 0.0 )
114113
, refractory_steps_( 0 )
115114
{
116115
y1_syn_.clear();
@@ -338,11 +337,9 @@ iaf_psc_alpha_multisynapse::update( Time const& origin, const long from, const l
338337
// neuron not refractory
339338
S_.V_m_ = V_.P30_ * ( S_.I_const_ + P_.I_e_ ) + V_.P33_ * S_.V_m_;
340339

341-
S_.current_ = 0.0;
342340
for ( size_t i = 0; i < P_.n_receptors_(); i++ )
343341
{
344342
S_.V_m_ += V_.P31_syn_[ i ] * S_.y1_syn_[ i ] + V_.P32_syn_[ i ] * S_.y2_syn_[ i ];
345-
S_.current_ += S_.y2_syn_[ i ];
346343
}
347344

348345
// lower bound of membrane potential

models/iaf_psc_alpha_multisynapse.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -214,8 +214,6 @@ class iaf_psc_alpha_multisynapse : public ArchivingNode
214214
std::vector< double > y2_syn_;
215215
//! This is the membrane potential RELATIVE TO RESTING POTENTIAL.
216216
double V_m_;
217-
double current_; //! This is the current in a time step. This is only here
218-
//! to allow logging
219217

220218
int refractory_steps_; //!< Number of refractory steps remaining
221219

@@ -300,7 +298,7 @@ class iaf_psc_alpha_multisynapse : public ArchivingNode
300298
}
301299
else if ( elem == State_::I )
302300
{
303-
return S_.current_;
301+
return std::accumulate( S_.y2_syn_.begin(), S_.y2_syn_.end(), 0.0 );
304302
}
305303
else
306304
{

models/iaf_psc_exp_multisynapse.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222

2323
#include "iaf_psc_exp_multisynapse.h"
2424

25-
2625
// Includes from libnestutil:
2726
#include "dict_util.h"
2827
#include "exceptions.h"
@@ -107,7 +106,6 @@ iaf_psc_exp_multisynapse::Parameters_::Parameters_()
107106
iaf_psc_exp_multisynapse::State_::State_()
108107
: I_const_( 0.0 )
109108
, V_m_( 0.0 )
110-
, current_( 0.0 )
111109
, refractory_steps_( 0 )
112110
{
113111
i_syn_.clear();
@@ -313,17 +311,16 @@ iaf_psc_exp_multisynapse::update( const Time& origin, const long from, const lon
313311
{
314312
S_.V_m_ = S_.V_m_ * V_.P22_ + ( P_.I_e_ + S_.I_const_ ) * V_.P20_; // not sure about this
315313

316-
S_.current_ = 0.0;
317314
for ( size_t i = 0; i < P_.n_receptors_(); i++ )
318315
{
319316
S_.V_m_ += V_.P21_syn_[ i ] * S_.i_syn_[ i ];
320-
S_.current_ += S_.i_syn_[ i ]; // not sure about this
321317
}
322318
}
323319
else
324320
{
325321
--S_.refractory_steps_; // neuron is absolute refractory
326322
}
323+
327324
for ( size_t i = 0; i < P_.n_receptors_(); i++ )
328325
{
329326
// exponential decaying PSCs

models/iaf_psc_exp_multisynapse.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,7 @@ class iaf_psc_exp_multisynapse : public ArchivingNode
211211

212212
double I_const_; //!< synaptic dc input current, variable 0
213213
std::vector< double > i_syn_;
214-
double V_m_; //!< membrane potential, variable 2
215-
double current_; //!< This is the current in a time step. This is only
216-
//!< here to allow logging
214+
double V_m_; //!< membrane potential, variable 2
217215

218216
//! absolute refractory counter (no membrane potential propagation)
219217
int refractory_steps_;
@@ -300,7 +298,7 @@ class iaf_psc_exp_multisynapse : public ArchivingNode
300298
}
301299
else if ( elem == State_::I )
302300
{
303-
return S_.current_;
301+
return std::accumulate( S_.i_syn_.begin(), S_.i_syn_.end(), 0.0 );
304302
}
305303
else
306304
{

testsuite/pytests/test_iaf_psc_alpha_multisynapse.py renamed to testsuite/pytests/sli2py_neurons/test_iaf_psc_alpha_multisynapse.py

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import numpy as np
2929
import numpy.testing as nptest
3030
import pytest
31+
from scipy.linalg import expm
3132

3233

3334
@pytest.fixture(autouse=True)
@@ -37,8 +38,7 @@ def reset():
3738

3839
def alpha_fn(t, tau_syn):
3940
vals = np.zeros_like(t)
40-
zero_inds = t <= 0.0
41-
nonzero_inds = ~zero_inds
41+
nonzero_inds = t > 0.0
4242
vals[nonzero_inds] = np.e / tau_syn * t[nonzero_inds] * np.exp(-t[nonzero_inds] / tau_syn)
4343
return vals
4444

@@ -50,6 +50,16 @@ def test_I_syn_1_in_recordables():
5050
assert "I_syn_1" in nrn.get("recordables")
5151

5252

53+
def alpha_psc_voltage_response(t, tau_syn, tau_m, C_m, w):
54+
vals = np.zeros_like(t)
55+
nonzero_inds = t > 0.0
56+
A = np.array([[-1.0 / tau_syn, 0.0, 0.0], [1.0, -1.0 / tau_syn, 0.0], [0.0, 1.0 / C_m, -1.0 / tau_m]])
57+
58+
expAt = expm(A[None, ...] * t[nonzero_inds, None, None]) # shape (t, 3, 3)
59+
vals[nonzero_inds] = expAt[:, 2, 0] * w * np.e / tau_syn # first two state variables are 0
60+
return vals
61+
62+
5363
def test_resize_recordables():
5464
"""
5565
Test resizing of recordables.
@@ -80,40 +90,57 @@ def test_simulation_against_analytical_soln():
8090
from multiple different synaptic ports are the same as the analytical solution.
8191
"""
8292

83-
tau_syn = [2.0, 20.0, 60.0, 100.0]
84-
delays = [100.0, 200.0, 500.0, 1200.0]
85-
weight = 1.0
86-
spike_time = 10.0
87-
simtime = 2500.0
93+
tau_syns = [2.0, 20.0, 60.0, 100.0]
94+
delays = [7.0, 5.0, 2.0, 1.0]
95+
weights = [30.0, 50.0, 20.0, 10.0]
96+
C_m = 250.0
97+
tau_m = 15.0
98+
spike_time = 1.0
99+
simtime = 100.0
100+
dt = 1.0
101+
102+
nest.set(resolution=dt)
88103

89104
nrn = nest.Create(
90105
"iaf_psc_alpha_multisynapse",
91106
params={
92-
"C_m": 250.0,
107+
"C_m": C_m,
93108
"E_L": 0.0,
94109
"V_m": 0.0,
95110
"V_th": 1500.0,
96111
"I_e": 0.0,
97-
"tau_m": 15.0,
98-
"tau_syn": tau_syn,
112+
"tau_m": tau_m,
113+
"tau_syn": tau_syns,
99114
},
100115
)
101-
sg = nest.Create("spike_generator", params={"spike_times": [spike_time]})
102116

103-
for i, syn_id in enumerate(range(1, 5)):
104-
syn_spec = {"synapse_model": "static_synapse", "delay": delays[i], "weight": weight, "receptor_type": syn_id}
117+
sg = nest.Create("spike_generator", params={"spike_times": [spike_time]})
105118

119+
for syn_idx, (delay, weight) in enumerate(zip(delays, weights)):
120+
syn_spec = {
121+
"synapse_model": "static_synapse",
122+
"delay": delay,
123+
"weight": weight,
124+
"receptor_type": syn_idx + 1,
125+
}
106126
nest.Connect(sg, nrn, conn_spec="one_to_one", syn_spec=syn_spec)
107127

108-
mm = nest.Create("multimeter", params={"record_from": ["I_syn_1", "I_syn_2", "I_syn_3", "I_syn_4"]})
128+
mm = nest.Create(
129+
"multimeter",
130+
params={"record_from": ["I_syn_1", "I_syn_2", "I_syn_3", "I_syn_4", "V_m", "I_syn"], "interval": dt},
131+
)
109132

110133
nest.Connect(mm, nrn)
111134
nest.Simulate(simtime)
112135
times = mm.get("events", "times")
113-
I_syn = np.sum([mm.get("events", f"I_syn_{i}") for i in range(1, 5)], axis=0)
114136

115-
I_syn_analytical = np.zeros_like(times, dtype=np.float64)
116-
for i in range(4):
117-
I_syn_analytical += alpha_fn(times - delays[i] - spike_time, tau_syn[i])
137+
I_syns_analytical = []
138+
V_m_analytical = np.zeros_like(times)
139+
for weight, delay, tau_s in zip(weights, delays, tau_syns):
140+
I_syns_analytical.append(alpha_fn(times - delay - spike_time, tau_s) * weight)
141+
V_m_analytical += alpha_psc_voltage_response(times - delay - spike_time, tau_s, tau_m, C_m, weight)
142+
143+
for idx, I_syn_analytical in enumerate(I_syns_analytical):
144+
nptest.assert_array_almost_equal(mm.get("events", f"I_syn_{idx+1}"), I_syn_analytical)
118145

119-
nptest.assert_array_almost_equal(I_syn, I_syn_analytical)
146+
nptest.assert_array_almost_equal(mm.get("events", "V_m"), V_m_analytical)
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# test_iaf_psc_exp_multisynapse.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+
Test ``iaf_psc_exp_multisynapse`` recordables and simulated PSCs against expectation.
24+
"""
25+
26+
27+
import nest
28+
import numpy as np
29+
import numpy.testing as nptest
30+
import pytest
31+
32+
33+
@pytest.fixture(autouse=True)
34+
def reset():
35+
nest.ResetKernel()
36+
37+
38+
def exp_psc_fn(t, tau_syn):
39+
vals = np.zeros_like(t)
40+
nonzero_inds = t > 0.0
41+
vals[nonzero_inds] = np.exp(-t[nonzero_inds] / tau_syn)
42+
return vals
43+
44+
45+
def exp_psc_voltage_response(t, tau_syn, tau_m, C_m, w):
46+
vals = np.zeros_like(t)
47+
nonzero_inds = t > 0.0
48+
delta_e = np.exp(-t[nonzero_inds] / tau_m) - np.exp(-t[nonzero_inds] / tau_syn)
49+
vals[nonzero_inds] = w / (C_m * (1.0 / tau_syn - 1.0 / tau_m)) * delta_e
50+
return vals
51+
52+
53+
def test_set_synaptic_time_constants():
54+
"""Tests that synaptic time constants can be set correctly"""
55+
taus = [2.0, 20.0, 60.0, 100.0]
56+
nrn = nest.Create("iaf_psc_exp_multisynapse")
57+
nrn.set(tau_syn=taus)
58+
nptest.assert_array_almost_equal(nrn.get("tau_syn"), taus)
59+
60+
61+
def test_simulation_against_analytical_solution():
62+
"""
63+
Test simulated PSCs against analytical expectation.
64+
65+
This test checks that the integration of the exponential currents of inputs
66+
from multiple different synaptic ports are the same as the analytical solution.
67+
"""
68+
69+
tau_syns = [2.0, 20.0, 60.0, 100.0]
70+
delays = [7.0, 5.0, 2.0, 1.0]
71+
weights = [30.0, 50.0, 20.0, 10.0]
72+
C_m = 250.0
73+
tau_m = 15.0
74+
spike_time = 0.1
75+
simtime = 8.0
76+
dt = 0.1
77+
78+
nest.set(resolution=dt)
79+
80+
nrn = nest.Create(
81+
"iaf_psc_exp_multisynapse",
82+
params={
83+
"C_m": C_m,
84+
"E_L": 0.0,
85+
"V_m": 0.0,
86+
"V_th": 1500.0,
87+
"I_e": 0.0,
88+
"tau_m": tau_m,
89+
"tau_syn": tau_syns,
90+
},
91+
)
92+
93+
sg = nest.Create("spike_generator", params={"spike_times": [spike_time]})
94+
95+
for syn_idx, (delay, weight) in enumerate(zip(delays, weights)):
96+
syn_spec = {
97+
"synapse_model": "static_synapse",
98+
"delay": delay,
99+
"weight": weight,
100+
"receptor_type": syn_idx + 1,
101+
}
102+
nest.Connect(sg, nrn, conn_spec="one_to_one", syn_spec=syn_spec)
103+
104+
mm = nest.Create(
105+
"multimeter",
106+
params={"record_from": ["I_syn_1", "I_syn_2", "I_syn_3", "I_syn_4", "V_m", "I_syn"], "interval": dt},
107+
)
108+
109+
nest.Connect(mm, nrn, syn_spec={"delay": 0.1})
110+
nest.Simulate(simtime)
111+
times = mm.get("events", "times")
112+
113+
I_syns_analytical = []
114+
V_m_analytical = np.zeros_like(times)
115+
for weight, delay, tau_s in zip(weights, delays, tau_syns):
116+
I_syns_analytical.append(exp_psc_fn(times - delay - spike_time, tau_s) * weight)
117+
V_m_analytical += exp_psc_voltage_response(times - delay - spike_time, tau_s, tau_m, C_m, weight)
118+
119+
for idx, I_syn_analytical in enumerate(I_syns_analytical):
120+
nptest.assert_array_almost_equal(mm.get("events", f"I_syn_{idx+1}"), I_syn_analytical)
121+
nptest.assert_array_almost_equal(mm.get("events", "V_m"), V_m_analytical)
122+
123+
124+
# The following tests address #800
125+
# - Test that the default recordables are V_m, w and I_syn_1
126+
# - Test that the recordable I_syn's change when changing the number of receptor ports
127+
128+
129+
def test_default_recordables():
130+
nrn = nest.Create("iaf_psc_exp_multisynapse")
131+
recordables = nrn.get("recordables")
132+
assert len(recordables) == 3
133+
assert "I_syn" in recordables
134+
assert "I_syn_1" in recordables
135+
assert "V_m" in recordables
136+
137+
138+
def test_resize_recordables():
139+
"""
140+
Test resizing of recordables.
141+
142+
This test ensures that recordables are updated correctly when the number
143+
of synaptic ports are changed.
144+
"""
145+
146+
tau_syn1 = [5.0, 1.0, 25.0]
147+
tau_syn2 = [5.0, 1.0]
148+
tau_syn3 = [5.0, 1.0, 25.0, 50.0]
149+
150+
nrn = nest.Create("iaf_psc_alpha_multisynapse", params={"tau_syn": tau_syn1})
151+
assert len(nrn.recordables) == 5
152+
153+
nrn.set(tau_syn=tau_syn2)
154+
assert len(nrn.recordables) == 4
155+
156+
nrn.set(tau_syn=tau_syn3)
157+
assert len(nrn.recordables) == 6

0 commit comments

Comments
 (0)