From 977b5e1b3c63170399358532802cb89bb82e3b53 Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Sat, 15 Jul 2023 07:50:24 -0700 Subject: [PATCH 1/4] add ignore-and-fire neuron test --- models/neurons/ignore_and_fire.nestml | 53 +++++++++++++ .../codegeneration/nest_code_generator.py | 2 +- .../synapse_post_neuron_transformer.py | 3 + tests/nest_tests/test_ignore_and_fire.py | 77 +++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 models/neurons/ignore_and_fire.nestml create mode 100644 tests/nest_tests/test_ignore_and_fire.py diff --git a/models/neurons/ignore_and_fire.nestml b/models/neurons/ignore_and_fire.nestml new file mode 100644 index 000000000..87136f505 --- /dev/null +++ b/models/neurons/ignore_and_fire.nestml @@ -0,0 +1,53 @@ +""" +ignore_and_fire - Neuron generating spikes at fixed intervals irrespective of inputs +###################################################################################### + +Description ++++++++++++ + +The ``ignore_and_fire`` neuron is a neuron model generating spikes at a predefined ``firing_rate`` with a constant inter-spike interval ("fire"), irrespective of its inputs ("ignore"). In this simplest version of the ``ignore_and_fire`` neuron, the inputs from other neurons or devices are not processed at all (*). The ``ignore_and_fire`` neuron is primarily used for neuronal-network model verification and validation purposes, in particular, to evaluate the correctness and performance of connectivity generation and inter-neuron communication. It permits an easy scaling of the network size and/or connectivity without affecting the output spike statistics. The amount of network traffic is predefined by the user, and therefore fully controllable and predictable, irrespective of the network size and structure. + +To create asynchronous activity for a population of ``ignore_and_fire`` neurons, the firing ``phase``s can be randomly initialised. Note that the firing ``phase`` is a real number, defined as the time to the next spike relative to the firing period. + +(*) The model can easily be extended and equipped with any arbitrary input processing (such as calculating input currents with alpha-function shaped PSC kernels or updating the gating variables in the Hodgkin-Huxley model) or (after-) spike generation dynamics to make it more similar and comparable to other non-ignorant neuron models. In such extended ignore_and_fire models, the spike emission process would still be decoupled from the intrinsic neuron dynamics. + +Authors ++++++++ + +Tetzlaff (February 2021; January 2022) + +""" + +neuron ignore_and_fire: + + state: + phase real = 1. ## relative time to next spike (in (0,1]) + + parameters: + firing_rate Bq = 10. Bq ## firing rate + + internals: + firing_period_steps integer = steps( 1. / firing_rate ) ## firing period in steps + phase_steps integer = steps( max(0.,phase) / firing_rate ) ## firing phase in steps + + input: + spikes Bq <- spike ## the neuron receives spikes, but is not processing them + + output: + spike + + update: + integrate_odes() + if phase_steps == 0: + emit_spike() + phase_steps = firing_period_steps - 1 + #println("spike") + else: + phase_steps -= 1 + + phase = 1. * phase_steps / firing_period_steps + + #println(" ") + #println("firing_period_steps={firing_period_steps}") + #println("phase_steps={phase_steps}") + #println("phase={phase}") diff --git a/pynestml/codegeneration/nest_code_generator.py b/pynestml/codegeneration/nest_code_generator.py index 0aa0a48a1..644f2667a 100644 --- a/pynestml/codegeneration/nest_code_generator.py +++ b/pynestml/codegeneration/nest_code_generator.py @@ -281,7 +281,7 @@ def analyse_neuron(self, neuron: ASTNeuron) -> Tuple[Dict[str, ASTAssignment], D self.non_equations_state_variables[neuron.get_name()].extend( ASTUtils.all_variables_defined_in_block(neuron.get_state_blocks())) - return [], [], [], [] + return {}, {}, [], [] if len(neuron.get_equations_blocks()) > 1: raise Exception("Only one equations block per model supported for now") diff --git a/pynestml/transformers/synapse_post_neuron_transformer.py b/pynestml/transformers/synapse_post_neuron_transformer.py index 226b742ae..134c14d5c 100644 --- a/pynestml/transformers/synapse_post_neuron_transformer.py +++ b/pynestml/transformers/synapse_post_neuron_transformer.py @@ -223,6 +223,9 @@ def transform_neuron_synapse_pair_(self, neuron, synapse): new_neuron = neuron.clone() new_synapse = synapse.clone() + new_neuron.accept(ASTSymbolTableVisitor()) + new_synapse.accept(ASTSymbolTableVisitor()) + assert len(new_neuron.get_equations_blocks()) <= 1, "Only one equations block per neuron supported for now." assert len(new_synapse.get_equations_blocks()) <= 1, "Only one equations block per synapse supported for now." assert len(new_neuron.get_state_blocks()) <= 1, "Only one state block supported per neuron for now." diff --git a/tests/nest_tests/test_ignore_and_fire.py b/tests/nest_tests/test_ignore_and_fire.py new file mode 100644 index 000000000..9aa987b0f --- /dev/null +++ b/tests/nest_tests/test_ignore_and_fire.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +# +# test_ignore_and_fire.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +import numpy as np +import os +import pytest + +import nest + +from pynestml.frontend.pynestml_frontend import generate_nest_target + +try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.ticker + import matplotlib.pyplot as plt + TEST_PLOTS = True +except Exception: + TEST_PLOTS = False + + +class TestIgnoreAndFire: + + neuron_model_name = "ignore_and_fire_nestml__with_stdp_nestml" + synapse_model_name = "stdp_nestml__with_ignore_and_fire_nestml" + + @pytest.fixture(scope="module", autouse=True) + def setUp(self): + """Generate the model code""" + + codegen_opts = {"neuron_synapse_pairs": [{"neuron": "ignore_and_fire", + "synapse": "stdp", + "post_ports": ["post_spikes"]}]} + + files = [os.path.join("models", "neurons", "ignore_and_fire.nestml"), + os.path.join("models", "synapses", "stdp_synapse.nestml")] + input_path = [os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( + os.pardir, os.pardir, s))) for s in files] + generate_nest_target(input_path=input_path, + logging_level="DEBUG", + suffix="_nestml", + codegen_opts=codegen_opts) + + def test_ignore_and_fire_with_stdp(self): + pre_spike_times = [10., 40., 50.] + resolution = 1. # [ms] + sim_time = 100. # [ms] + + nest.set_verbosity("M_ALL") + nest.Install("nestmlmodule") + nest.ResetKernel() + nest.SetKernelStatus({"resolution": resolution}) + + pre_neuron = nest.Create(self.neuron_model_name) + post_neuron = nest.Create(self.neuron_model_name) + + nest.Connect(pre_neuron, post_neuron, syn_spec={"synapse_model": self.synapse_model_name}) + + nest.Simulate(sim_time) From 77367fb8347fafb588f8525b2a8a7b7ce0eb75ff Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Tue, 8 Aug 2023 08:05:03 -0700 Subject: [PATCH 2/4] add ignore-and-fire neuron --- doc/models_library/ignore_and_fire.rst | 68 ++++++++++++++++++++++++ doc/models_library/index.rst | 9 ++++ models/neurons/ignore_and_fire.nestml | 5 -- tests/nest_tests/test_ignore_and_fire.py | 15 +++++- 4 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 doc/models_library/ignore_and_fire.rst diff --git a/doc/models_library/ignore_and_fire.rst b/doc/models_library/ignore_and_fire.rst new file mode 100644 index 000000000..cf5c8d82b --- /dev/null +++ b/doc/models_library/ignore_and_fire.rst @@ -0,0 +1,68 @@ +ignore_and_fire +############### + + +ignore_and_fire - Neuron generating spikes at fixed intervals irrespective of inputs + +Description ++++++++++++ + +The ``ignore_and_fire`` neuron is a neuron model generating spikes at a predefined ``firing_rate`` with a constant inter-spike interval ("fire"), irrespective of its inputs ("ignore"). In this simplest version of the ``ignore_and_fire`` neuron, the inputs from other neurons or devices are not processed at all (*). The ``ignore_and_fire`` neuron is primarily used for neuronal-network model verification and validation purposes, in particular, to evaluate the correctness and performance of connectivity generation and inter-neuron communication. It permits an easy scaling of the network size and/or connectivity without affecting the output spike statistics. The amount of network traffic is predefined by the user, and therefore fully controllable and predictable, irrespective of the network size and structure. + +To create asynchronous activity for a population of ``ignore_and_fire`` neurons, the firing ``phase``s can be randomly initialised. Note that the firing ``phase`` is a real number, defined as the time to the next spike relative to the firing period. + +(*) The model can easily be extended and equipped with any arbitrary input processing (such as calculating input currents with alpha-function shaped PSC kernels or updating the gating variables in the Hodgkin-Huxley model) or (after-) spike generation dynamics to make it more similar and comparable to other non-ignorant neuron models. In such extended ignore_and_fire models, the spike emission process would still be decoupled from the intrinsic neuron dynamics. + +Authors ++++++++ + +Tetzlaff (February 2021; January 2022) + + + + +Parameters +++++++++++ +.. csv-table:: + :header: "Name", "Physical unit", "Default value", "Description" + :widths: auto + + + "firing_rate", "Bq", "10.0Bq", "# firing rate" + + + +State variables ++++++++++++++++ + +.. csv-table:: + :header: "Name", "Physical unit", "Default value", "Description" + :widths: auto + + + "phase", "real", "1.0", "# relative time to next spike (in (0,1])" + + + + +Equations ++++++++++ + + + + + +Source code ++++++++++++ + +The model source code can be found in the NESTML models repository here: `ignore_and_fire `_. + +Characterisation +++++++++++++++++ + +.. include:: ignore_and_fire_characterisation.rst + + +.. footer:: + + Generated at 2023-08-08 15:01:40.284515 \ No newline at end of file diff --git a/doc/models_library/index.rst b/doc/models_library/index.rst index c95819a56..1345204a1 100644 --- a/doc/models_library/index.rst +++ b/doc/models_library/index.rst @@ -292,6 +292,15 @@ Wang-Buzsaki model with multiple synapses Source file: `wb_cond_multisyn.nestml `_ + +:doc:`ignore_and_fire ` +------------------------------------ + +Neuron generating spikes at fixed intervals irrespective of inputs + +Source file: `ignore_and_fire.nestml `_ + + Synapse models ~~~~~~~~~~~~~~ diff --git a/models/neurons/ignore_and_fire.nestml b/models/neurons/ignore_and_fire.nestml index 87136f505..20cc8e2ba 100644 --- a/models/neurons/ignore_and_fire.nestml +++ b/models/neurons/ignore_and_fire.nestml @@ -46,8 +46,3 @@ neuron ignore_and_fire: phase_steps -= 1 phase = 1. * phase_steps / firing_period_steps - - #println(" ") - #println("firing_period_steps={firing_period_steps}") - #println("phase_steps={phase_steps}") - #println("phase={phase}") diff --git a/tests/nest_tests/test_ignore_and_fire.py b/tests/nest_tests/test_ignore_and_fire.py index 9aa987b0f..ae5946c7d 100644 --- a/tests/nest_tests/test_ignore_and_fire.py +++ b/tests/nest_tests/test_ignore_and_fire.py @@ -60,9 +60,8 @@ def setUp(self): codegen_opts=codegen_opts) def test_ignore_and_fire_with_stdp(self): - pre_spike_times = [10., 40., 50.] resolution = 1. # [ms] - sim_time = 100. # [ms] + sim_time = 1001. # [ms] nest.set_verbosity("M_ALL") nest.Install("nestmlmodule") @@ -71,7 +70,19 @@ def test_ignore_and_fire_with_stdp(self): pre_neuron = nest.Create(self.neuron_model_name) post_neuron = nest.Create(self.neuron_model_name) + pre_neuron.firing_rate = 10. + post_neuron.firing_rate = 100. + pre_sr = nest.Create("spike_recorder") + post_sr = nest.Create("spike_recorder") + nest.Connect(pre_neuron, pre_sr) + nest.Connect(post_neuron, post_sr) nest.Connect(pre_neuron, post_neuron, syn_spec={"synapse_model": self.synapse_model_name}) nest.Simulate(sim_time) + + n_ev_pre = len(pre_sr.get("events")["times"]) + n_ev_post = len(post_sr.get("events")["times"]) + + assert n_ev_pre == 10 + assert n_ev_post == 100 From 69f67232cfeb9dd2a02f1316f0724ffd9c6c293a Mon Sep 17 00:00:00 2001 From: clinssen Date: Wed, 9 Aug 2023 10:09:53 +0200 Subject: [PATCH 3/4] Update tests/nest_tests/test_ignore_and_fire.py Co-authored-by: Pooja Babu <75320801+pnbabu@users.noreply.github.com> --- tests/nest_tests/test_ignore_and_fire.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/nest_tests/test_ignore_and_fire.py b/tests/nest_tests/test_ignore_and_fire.py index ae5946c7d..fee21bfd9 100644 --- a/tests/nest_tests/test_ignore_and_fire.py +++ b/tests/nest_tests/test_ignore_and_fire.py @@ -84,5 +84,5 @@ def test_ignore_and_fire_with_stdp(self): n_ev_pre = len(pre_sr.get("events")["times"]) n_ev_post = len(post_sr.get("events")["times"]) - assert n_ev_pre == 10 - assert n_ev_post == 100 + assert n_ev_pre == pre_neuron.firing_rate + assert n_ev_post == post_neuron.firing_rate From c786c6d9fcb3a827f25f87c5fafdc81644745330 Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Wed, 9 Aug 2023 01:57:14 -0700 Subject: [PATCH 4/4] make test ignore NEST2 --- tests/nest_tests/test_ignore_and_fire.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/nest_tests/test_ignore_and_fire.py b/tests/nest_tests/test_ignore_and_fire.py index ae5946c7d..1dff7d886 100644 --- a/tests/nest_tests/test_ignore_and_fire.py +++ b/tests/nest_tests/test_ignore_and_fire.py @@ -25,6 +25,7 @@ import nest +from pynestml.codegeneration.nest_tools import NESTTools from pynestml.frontend.pynestml_frontend import generate_nest_target try: @@ -59,6 +60,8 @@ def setUp(self): suffix="_nestml", codegen_opts=codegen_opts) + @pytest.mark.skipif(NESTTools.detect_nest_version().startswith("v2"), + reason="This test does not support NEST 2") def test_ignore_and_fire_with_stdp(self): resolution = 1. # [ms] sim_time = 1001. # [ms]