Skip to content

Numerical instability in aeif_psc_alpha neuron model #2420

Open
@zbarni

Description

@zbarni

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:

  1. 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")
  1. 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 message

      nest.lib.hl_api_exceptions.NumericalInstability: NumericalInstability in SLI function Simulate_d: NEST detected a numerical instability while updating aeif_psc_alpha.

  2. Command to run
    Execute above script using Python 3.8+.

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

S: NormalHandle this with default priorityT: DiscussionStill searching for the right way to proceed / suggestions welcomestaleAutomatic marker for inactivity, please have another look here

Type

No type

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions