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)