diff --git a/pynestml/cocos/co_co_nest_random_functions_legally_used.py b/pynestml/cocos/co_co_nest_random_functions_legally_used.py
new file mode 100644
index 000000000..81e2fc464
--- /dev/null
+++ b/pynestml/cocos/co_co_nest_random_functions_legally_used.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# co_co_nest_random_functions_legally_used.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 .
+
+from pynestml.cocos.co_co import CoCo
+from pynestml.meta_model.ast_model import ASTModel
+from pynestml.meta_model.ast_node import ASTNode
+from pynestml.meta_model.ast_on_condition_block import ASTOnConditionBlock
+from pynestml.meta_model.ast_on_receive_block import ASTOnReceiveBlock
+from pynestml.meta_model.ast_update_block import ASTUpdateBlock
+from pynestml.symbols.predefined_functions import PredefinedFunctions
+from pynestml.utils.logger import LoggingLevel, Logger
+from pynestml.utils.messages import Messages
+from pynestml.visitors.ast_visitor import ASTVisitor
+
+
+class CoCoNestRandomFunctionsLegallyUsed(CoCo):
+ """
+ This CoCo ensure that the random functions are used only in the ``update``, ``onReceive``, and ``onCondition`` blocks.
+ This CoCo is only checked for the NEST Simulator target.
+ """
+
+ @classmethod
+ def check_co_co(cls, node: ASTNode):
+ """
+ Checks the coco.
+ :param node: a single node (typically, a neuron or synapse)
+ """
+ visitor = CoCoNestRandomFunctionsLegallyUsedVisitor()
+ visitor.neuron = node
+ node.accept(visitor)
+
+
+class CoCoNestRandomFunctionsLegallyUsedVisitor(ASTVisitor):
+ def visit_function_call(self, node):
+ """
+ Visits a function call
+ :param node: a function call
+ """
+ function_name = node.get_name()
+ if function_name == PredefinedFunctions.RANDOM_NORMAL or function_name == PredefinedFunctions.RANDOM_UNIFORM \
+ or function_name == PredefinedFunctions.RANDOM_POISSON:
+ parent = node
+ while parent:
+ parent = parent.get_parent()
+
+ if isinstance(parent, ASTUpdateBlock) or isinstance(parent, ASTOnReceiveBlock) \
+ or isinstance(parent, ASTOnConditionBlock):
+ # the random function is correctly defined, hence return
+ return
+
+ if isinstance(parent, ASTModel):
+ # the random function is defined in other blocks (parameters, state, internals). Hence, an error.
+ code, message = Messages.get_random_functions_legally_used(function_name)
+ Logger.log_message(node=self.neuron, code=code, message=message, error_position=node.get_source_position(),
+ log_level=LoggingLevel.ERROR)
diff --git a/pynestml/cocos/co_cos_manager.py b/pynestml/cocos/co_cos_manager.py
index 1b7e52aba..54d8468d0 100644
--- a/pynestml/cocos/co_cos_manager.py
+++ b/pynestml/cocos/co_cos_manager.py
@@ -28,7 +28,6 @@
from pynestml.cocos.co_co_cm_synapse_model import CoCoCmSynapseModel
from pynestml.cocos.co_co_convolve_has_correct_parameter import CoCoConvolveHasCorrectParameter
from pynestml.cocos.co_co_convolve_cond_correctly_built import CoCoConvolveCondCorrectlyBuilt
-from pynestml.cocos.co_co_convolve_has_correct_parameter import CoCoConvolveHasCorrectParameter
from pynestml.cocos.co_co_correct_numerator_of_unit import CoCoCorrectNumeratorOfUnit
from pynestml.cocos.co_co_correct_order_in_equation import CoCoCorrectOrderInEquation
from pynestml.cocos.co_co_each_block_defined_at_most_once import CoCoEachBlockDefinedAtMostOnce
@@ -49,6 +48,7 @@
from pynestml.cocos.co_co_invariant_is_boolean import CoCoInvariantIsBoolean
from pynestml.cocos.co_co_kernel_type import CoCoKernelType
from pynestml.cocos.co_co_model_name_unique import CoCoModelNameUnique
+from pynestml.cocos.co_co_nest_random_functions_legally_used import CoCoNestRandomFunctionsLegallyUsed
from pynestml.cocos.co_co_no_kernels_except_in_convolve import CoCoNoKernelsExceptInConvolve
from pynestml.cocos.co_co_no_nest_name_space_collision import CoCoNoNestNameSpaceCollision
from pynestml.cocos.co_co_no_duplicate_compilation_unit_names import CoCoNoDuplicateCompilationUnitNames
@@ -418,6 +418,14 @@ def check_input_port_size_type(cls, model: ASTModel):
"""
CoCoVectorInputPortsCorrectSizeType.check_co_co(model)
+ @classmethod
+ def check_co_co_nest_random_functions_legally_used(cls, model: ASTModel):
+ """
+ Checks if the random number functions are used only in the update block.
+ :param model: a single model object.
+ """
+ CoCoNestRandomFunctionsLegallyUsed.check_co_co(model)
+
@classmethod
def post_symbol_table_builder_checks(cls, model: ASTModel, after_ast_rewrite: bool = False):
"""
diff --git a/pynestml/codegeneration/nest_code_generator.py b/pynestml/codegeneration/nest_code_generator.py
index 0551e9a6e..66e0c9e13 100644
--- a/pynestml/codegeneration/nest_code_generator.py
+++ b/pynestml/codegeneration/nest_code_generator.py
@@ -28,6 +28,7 @@
import pynestml
from pynestml.cocos.co_co_nest_synapse_delay_not_assigned_to import CoCoNESTSynapseDelayNotAssignedTo
+from pynestml.cocos.co_cos_manager import CoCosManager
from pynestml.codegeneration.code_generator import CodeGenerator
from pynestml.codegeneration.code_generator_utils import CodeGeneratorUtils
from pynestml.codegeneration.nest_assignments_helper import NestAssignmentsHelper
@@ -172,21 +173,30 @@ def __init__(self, options: Optional[Mapping[str, Any]] = None):
self.setup_printers()
def run_nest_target_specific_cocos(self, neurons: Sequence[ASTModel], synapses: Sequence[ASTModel]):
- for synapse in synapses:
- synapse_name_stripped = removesuffix(removesuffix(synapse.name.split("_with_")[0], "_"), FrontendConfiguration.suffix)
+ for model in neurons + synapses:
+ # Check if the random number functions are used in the right blocks
+ CoCosManager.check_co_co_nest_random_functions_legally_used(model)
+
+ if Logger.has_errors(model):
+ raise Exception("Error(s) occurred during code generation")
+
+ if self.get_option("neuron_synapse_pairs"):
+ for model in synapses:
+ synapse_name_stripped = removesuffix(removesuffix(model.name.split("_with_")[0], "_"),
+ FrontendConfiguration.suffix)
+ # special case for NEST delay variable (state or parameter)
+ assert synapse_name_stripped in self.get_option("delay_variable").keys(), "Please specify a delay variable for synapse '" + synapse_name_stripped + "' in the code generator options (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
+ assert ASTUtils.get_variable_by_name(model, self.get_option("delay_variable")[synapse_name_stripped]), "Delay variable '" + self.get_option("delay_variable")[synapse_name_stripped] + "' not found in synapse '" + synapse_name_stripped + "' (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
- # special case for NEST delay variable (state or parameter)
- assert synapse_name_stripped in self.get_option("delay_variable").keys(), "Please specify a delay variable for synapse '" + synapse_name_stripped + "' in the code generator options (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
- assert ASTUtils.get_variable_by_name(synapse, self.get_option("delay_variable")[synapse_name_stripped]), "Delay variable '" + self.get_option("delay_variable")[synapse_name_stripped] + "' not found in synapse '" + synapse_name_stripped + "' (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
+ # special case for NEST weight variable (state or parameter)
+ assert synapse_name_stripped in self.get_option("weight_variable").keys(), "Please specify a weight variable for synapse '" + synapse_name_stripped + "' in the code generator options (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
+ assert ASTUtils.get_variable_by_name(model, self.get_option("weight_variable")[synapse_name_stripped]), "Weight variable '" + self.get_option("weight_variable")[synapse_name_stripped] + "' not found in synapse '" + synapse_name_stripped + "' (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
- # special case for NEST weight variable (state or parameter)
- assert synapse_name_stripped in self.get_option("weight_variable").keys(), "Please specify a weight variable for synapse '" + synapse_name_stripped + "' in the code generator options (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
- assert ASTUtils.get_variable_by_name(synapse, self.get_option("weight_variable")[synapse_name_stripped]), "Weight variable '" + self.get_option("weight_variable")[synapse_name_stripped] + "' not found in synapse '" + synapse_name_stripped + "' (see https://nestml.readthedocs.io/en/latest/running/running_nest.html#dendritic-delay-and-synaptic-weight)"
+ if self.option_exists("delay_variable") and synapse_name_stripped in self.get_option("delay_variable").keys():
+ delay_variable = self.get_option("delay_variable")[synapse_name_stripped]
+ CoCoNESTSynapseDelayNotAssignedTo.check_co_co(delay_variable, model)
- if self.option_exists("delay_variable") and synapse_name_stripped in self.get_option("delay_variable").keys():
- delay_variable = self.get_option("delay_variable")[synapse_name_stripped]
- CoCoNESTSynapseDelayNotAssignedTo.check_co_co(delay_variable, synapse)
- if Logger.has_errors(synapse):
+ if Logger.has_errors(model):
raise Exception("Error(s) occurred during code generation")
def setup_printers(self):
diff --git a/pynestml/frontend/pynestml_frontend.py b/pynestml/frontend/pynestml_frontend.py
index c257822de..59b9bb5f8 100644
--- a/pynestml/frontend/pynestml_frontend.py
+++ b/pynestml/frontend/pynestml_frontend.py
@@ -464,19 +464,22 @@ def process():
Flag indicating whether errors occurred during processing
"""
- # initialize and set options for transformers, code generator and builder
- codegen_and_builder_opts = FrontendConfiguration.get_codegen_opts()
-
- transformers, codegen_and_builder_opts = transformers_from_target_name(FrontendConfiguration.get_target_platform(),
- options=codegen_and_builder_opts)
+ # initialise model transformers
+ transformers, unused_opts_transformer = transformers_from_target_name(FrontendConfiguration.get_target_platform(),
+ options=FrontendConfiguration.get_codegen_opts())
+ # initialise code generator
code_generator = code_generator_from_target_name(FrontendConfiguration.get_target_platform())
- codegen_and_builder_opts = code_generator.set_options(codegen_and_builder_opts)
+ unused_opts_codegen = code_generator.set_options(FrontendConfiguration.get_codegen_opts())
- _builder, codegen_and_builder_opts = builder_from_target_name(FrontendConfiguration.get_target_platform(), options=codegen_and_builder_opts)
+ # initialise builder
+ _builder, unused_opts_builder = builder_from_target_name(FrontendConfiguration.get_target_platform(),
+ options=FrontendConfiguration.get_codegen_opts())
- if len(codegen_and_builder_opts) > 0:
- raise CodeGeneratorOptionsException("The code generator option(s) \"" + ", ".join(codegen_and_builder_opts.keys()) + "\" do not exist.")
+ # check for unused codegen options
+ for opt_key in FrontendConfiguration.get_codegen_opts().keys():
+ if opt_key in unused_opts_transformer.keys() and opt_key in unused_opts_codegen.keys() and opt_key in unused_opts_builder.keys():
+ raise CodeGeneratorOptionsException("The code generator option \"" + opt_key + "\" does not exist.")
models, errors_occurred = get_parsed_models()
diff --git a/pynestml/utils/messages.py b/pynestml/utils/messages.py
index 22601566c..36e783260 100644
--- a/pynestml/utils/messages.py
+++ b/pynestml/utils/messages.py
@@ -133,6 +133,7 @@ class MessageCode(Enum):
NON_CONSTANT_EXPONENT = 111
RESOLUTION_FUNC_USED = 112
TIMESTEP_FUNCTION_LEGALLY_USED = 113
+ RANDOM_FUNCTIONS_LEGALLY_USED = 113
EXPONENT_MUST_BE_INTEGER = 114
@@ -1386,3 +1387,8 @@ def get_non_constant_exponent(cls) -> Tuple[MessageCode, str]:
message = "Cannot calculate value of exponent. Must be a constant value!"
return MessageCode.NON_CONSTANT_EXPONENT, message
+
+ @classmethod
+ def get_random_functions_legally_used(cls, name):
+ message = "The function '" + name + "' can only be used in the update, onReceive, or onCondition blocks."
+ return MessageCode.RANDOM_FUNCTIONS_LEGALLY_USED, message
diff --git a/tests/nest_tests/nest_random_functions_test.py b/tests/nest_tests/nest_random_functions_test.py
new file mode 100644
index 000000000..3d24ed963
--- /dev/null
+++ b/tests/nest_tests/nest_random_functions_test.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+#
+# nest_random_functions_test.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 os
+
+import pytest
+
+from pynestml.frontend.pynestml_frontend import generate_nest_target
+
+
+class TestNestRandomFunctions:
+ """
+ Tests that, for the NEST target, random number functions are called only in ``update``, ``onReceive``, and ``onCondition`` block
+ """
+
+ @pytest.mark.xfail(strict=True, raises=Exception)
+ def test_nest_random_function_neuron_illegal(self):
+ input_path = os.path.realpath(os.path.join(os.path.dirname(__file__),
+ "resources", "random_functions_illegal_neuron.nestml"))
+ generate_nest_target(input_path=input_path,
+ target_path="target",
+ logging_level="INFO",
+ suffix="_nestml")
+
+ @pytest.mark.xfail(strict=True, raises=Exception)
+ def test_nest_random_function_synapse_illegal(self):
+ input_path = [
+ os.path.realpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, "models", "neurons",
+ "iaf_psc_exp_neuron.nestml")),
+ os.path.realpath(os.path.join(os.path.dirname(__file__),
+ "resources", "random_functions_illegal_synapse.nestml"))]
+
+ generate_nest_target(input_path=input_path,
+ target_path="target",
+ logging_level="INFO",
+ suffix="_nestml",
+ codegen_opts={"neuron_synapse_pairs": [{"neuron": "iaf_psc_exp_neuron",
+ "synapse": "random_functions_illegal_synapse",
+ "post_ports": ["post_spikes"]}],
+ "weight_variable": {"stdp_synapse": "w"}})
diff --git a/tests/nest_tests/resources/random_functions_illegal_neuron.nestml b/tests/nest_tests/resources/random_functions_illegal_neuron.nestml
new file mode 100644
index 000000000..9954459d3
--- /dev/null
+++ b/tests/nest_tests/resources/random_functions_illegal_neuron.nestml
@@ -0,0 +1,46 @@
+"""
+random_functions_illegal_neuron.nestml
+######################################
+
+
+Copyright statement
++++++++++++++++++++
+
+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 .
+"""
+
+model random_functions_illegal_neuron:
+ state:
+ noise real = random_normal(0,sigma_noise)
+ v mV = -15 mV
+
+ parameters:
+ rate ms**-1 = 15.5 s**-1
+ sigma_noise real = 16.
+ u real = random_uniform(0,1)
+
+ internals:
+ poisson_input integer = random_poisson(rate * resolution() * 1E-3)
+
+ update:
+ if u < 0.5:
+ noise = 0.
+ else:
+ noise = random_normal(0,sigma_noise)
+
+ v += (poisson_input + noise) * mV
diff --git a/tests/nest_tests/resources/random_functions_illegal_synapse.nestml b/tests/nest_tests/resources/random_functions_illegal_synapse.nestml
new file mode 100644
index 000000000..473791ec7
--- /dev/null
+++ b/tests/nest_tests/resources/random_functions_illegal_synapse.nestml
@@ -0,0 +1,73 @@
+"""
+random_functions_illegal_synapse.nestml
+#######################################
+
+
+Copyright statement
++++++++++++++++++++
+
+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 .
+"""
+
+model random_functions_illegal_synapse:
+ state:
+ w real = 1 # Synaptic weight
+ pre_trace real = 0.
+ post_trace real = 0.
+
+ parameters:
+ d ms = 1 ms # Synaptic transmission delay
+ lambda real = .01
+ tau_tr_pre ms = random_normal(110 ms, 55 ms)
+ tau_tr_post ms = random_normal(5 ms, 2.5 ms)
+ alpha real = 1
+ mu_plus real = 1
+ mu_minus real = 1
+ Wmax real = 100.
+ Wmin real = 0.
+
+ equations:
+ pre_trace' = -pre_trace / tau_tr_pre
+ post_trace' = -post_trace / tau_tr_post
+
+ input:
+ pre_spikes <- spike
+ post_spikes <- spike
+
+ output:
+ spike
+
+ onReceive(post_spikes):
+ post_trace += 1
+
+ # potentiate synapse
+ w_ real = Wmax * ( w / Wmax + (lambda * ( 1. - ( w / Wmax ) )**mu_plus * pre_trace ))
+ w = min(Wmax, w_)
+
+ onReceive(pre_spikes):
+ pre_trace += 1
+
+ # depress synapse
+ w_ real = Wmax * ( w / Wmax - ( alpha * lambda * ( w / Wmax )**mu_minus * post_trace ))
+ w = max(Wmin, w_)
+
+ # deliver spike to postsynaptic partner
+ emit_spike(w, d)
+
+ update:
+ integrate_odes()