Description
Describe the bug
While running some experiments together with @TeEsTeBe, we found the implementation of the aeif_psc_alpha
neuron model appears to be numerically unstable for certain RNG seeds, with two different behaviors as described below:
To Reproduce
Steps to reproduce the behavior:
- Consider the following example script
import nest
import numpy as np
nest.ResetKernel()
nest.set_verbosity('M_ALL')
neuron_pars = {
'E1': { # IT cell firing profile
# "model": "aeif_psc_alpha",
"C_m": 200., # capacity of the membrane (pF)
"g_L": 12., # leak conductance (nS)
"E_L": -70., # leak reversal potential (mV) [in other models: resting potential]
"V_th": -50., # spike initiation threshold (mV)
"Delta_T": 2., # slope factor (mV)
"a": 2., # sub-threshold adaptation (ns)
"tau_w": 300., # adaptation time constant (ms) [large]
"b": 60., # spike-triggered adaptaton (pA)
"V_reset": -58., # reset value for Vm after a spike (mV)
"I_e": 0., # constant external input current (pA)
'tau_syn_ex': 5.,
'tau_syn_in': 10.,
"t_ref": 0.1
},
'E2': { # PT cell firing profile
# "model": "aeif_psc_alpha",
"C_m": 200., # capacity of the membrane (pF)
"g_L": 10., # leak conductance (nS)
"E_L": -58., # leak reversal potential (mV) [in other models: resting potential]
"V_th": -50., # spike initiation threshold (mV)
"Delta_T": 2., # slope factor (mV)
"a": 2., # sub-threshold adaptation (ns)
"tau_w": 120., # adaptation time constant (ms) [large]
"b": 100., # spike-triggered adaptaton (pA)
"V_reset": -46., # reset value for Vm after a spike (mV)
"I_e": 0., # constant external input current (pA)
'tau_syn_ex': 5.,
'tau_syn_in': 10.,
"t_ref": 0.1
},
'I1': {
# 'model': 'iaf_psc_exp',
'C_m': 250.,
'E_L': -70.0,
'I_e': 0.,
'V_m': -70.0,
'V_th': -55.0,
'V_reset': -70.0,
't_ref': 2.,
'tau_m': 20.,
'tau_syn_ex': 5.,
'tau_syn_in': 10.
},
'I2': {
# 'model': 'iaf_psc_exp',
'C_m': 250.,
'E_L': -70.0,
'I_e': 0.,
'V_m': -70.0,
'V_th': -55.0,
'V_reset': -70.0,
't_ref': 2.,
'tau_m': 20.,
'tau_syn_ex': 5.,
'tau_syn_in': 10.
}
}
kernel_pars = {'resolution': 0.1, 'data_prefix': 'asd',
'data_path': 'asd',
'overwrite_files': True, 'print_time': True, 'total_num_virtual_procs': 6,
'local_num_threads': 6,
'rng_seed': 130030284 # !!! -> NEST hangs
# 'rng_seed': 1234 # !!! -> Error message
}
nest.SetKernelStatus(kernel_pars)
np_rng = np.random.default_rng(kernel_pars['rng_seed'])
print(f"NEST RNG: {nest.GetKernelStatus('rng_seed')}")
NE = 2000
NI = 500
for pop_name, pars in neuron_pars.items():
model = 'aeif_psc_alpha' if pop_name[0] == 'E' else 'iaf_psc_exp'
nest.CopyModel(model, pop_name)
accepted_keys = list(nest.GetDefaults(model).keys())
accepted_keys.remove('model')
nest_dict = {k: v for k, v in pars.items() if k in accepted_keys}
nest.SetDefaults(pop_name, pars)
# create populations
E1 = nest.Create('E1', NE)
I1 = nest.Create('I1', NI)
E2 = nest.Create('E2', NE)
I2 = nest.Create('I2', NI)
populations = [E1, I1, E2, I2]
# randomize Vm
for pop in populations:
pop.V_m = list(np_rng.uniform(low=pop[0].E_L, high=pop[0].V_th, size=len(pop)))
# create input
pg = nest.Create('poisson_generator', 1, {'rate': 2400.})
# connect input
for pop, w in zip(populations, [10., 40., 10., 20.]):
nest.Connect(pg, pop, 'all_to_all', syn_spec={'weight': w})
epsilon = 0.1
delay = 1.5
wscale = 1.
wE_IT = 17. * wscale
wE_PT = 32. * wscale
wE_IT_to_PT = 14.7 * wscale
gamma = 10. # scaling factor for the inhibitory synapses.
nu_x = 12. # external background input intensity.
wIx = 4.960628287400623
conn_specs = [
# module 1 internal
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon / 2.},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
# module 2 internal
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon / 2.},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon},
# feed-forward excitation
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon / 2.},
# feedback excitation
{'allow_autapses': False, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': epsilon / 2.},
]
syn_specs = [
# module 1 internal
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_IT},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_IT * 2},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': -gamma * wE_IT},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': -gamma * wIx},
# module 2 internal
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_PT},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_PT},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': -gamma * wE_PT},
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': -gamma * wIx},
# # feed-forward
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_IT_to_PT},
# feedback
{'synapse_model': 'static_synapse', 'delay': delay, 'weight': wE_PT}, # no data for this connection, assumed
]
connections = [
# module 1 internal
(E1, E1), # E1 ---> E1 (recurrent E)
(I1, E1), # E1 ---> I1
(E1, I1), # I1 ---> E1
(I1, I1), # I1 ---> I1
# # module 2 internal
(E2, E2),
(I2, E2),
(E2, I2),
(I2, I2),
]
for idx, conn_tuple in enumerate(connections):
nest.Connect(conn_tuple[1], conn_tuple[0], conn_spec=conn_specs[idx], syn_spec=syn_specs[idx])
spike_recorders = nest.Create("spike_recorder", 4)
for idx in range(4):
nest.Connect([E1, I1, E2, I2][idx], spike_recorders[idx])
nest.Simulate(1.)
nest.Simulate(35.)
# print output
sr = spike_recorders[0]
print("Spikes:", sr.events['times'][:10])
print("Spikes:", sr.events['senders'][:10])
from matplotlib import pyplot as plt
fig, axes = plt.subplots(1, 4, figsize=(20, 6))
cnt = 0
for idx, sr in enumerate(spike_recorders):
axes[idx].scatter(sr.events['times'], sr.events['senders'], s=1)
axes[cnt].set_xlim((-5, 40))
cnt += 1
fig.tight_layout()
fig.savefig(f"raster_merged_test.jpg")
-
Used parameters
See script. We tried two different RNG seeds, which lead to two different behaviors:-
for
{ ... 'rng_seed': 130030284}
, the simulation hangs at 31 ms, likely due to the GSL solver not converging. -
for
{ ... 'rng_seed': 1234}
, the simulation exits with the error messagenest.lib.hl_api_exceptions.NumericalInstability: NumericalInstability in SLI function Simulate_d: NEST detected a numerical instability while updating aeif_psc_alpha.
-
-
Command to run
Execute above script using Python 3.8+. -
How to see error
See point 2., for the two different RNG seeds.
Expected behavior
The simulation should exit after 35.0 ms without errors.
Desktop/Environment (please complete the following information):
We tested the script in two different environments on different PCs, also using multiple NEST versions 3.x
- OS: Linux Mint 19.3, kernel 5.4.0-050400-generic / Ubuntu 20.4
- Python-Version: 3.9.0 / 3.8.8
- NEST-Version: 3.0, 3.3, master@e0f8991aa
- Installation: manual install inside Miniconda env, see attached file.
Additional context
Add any other context about the problem here.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status