Skip to content

Commit

Permalink
Add CoCo to check if the random number functions are declared only in…
Browse files Browse the repository at this point in the history
… update block for NEST simulator
  • Loading branch information
pnbabu committed Sep 25, 2024
1 parent eaf1b3c commit 2514e8a
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 4 deletions.
69 changes: 69 additions & 0 deletions pynestml/cocos/co_co_nest_random_functions_legally_used.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
#
# co_co_timestep_function_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 <http://www.gnu.org/licenses/>.

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_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`` block. 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):
# the random function is correctly defined within the update block, 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)
11 changes: 9 additions & 2 deletions pynestml/cocos/co_cos_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@

from pynestml.cocos.co_co_all_variables_defined import CoCoAllVariablesDefined
from pynestml.cocos.co_co_inline_expression_not_assigned_to import CoCoInlineExpressionNotAssignedTo
from pynestml.cocos.co_co_input_port_not_assigned_to import CoCoInputPortNotAssignedTo
from pynestml.cocos.co_co_cm_channel_model import CoCoCmChannelModel
from pynestml.cocos.co_co_cm_continuous_input_model import CoCoCmContinuousInputModel
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_input_port_not_assigned_to import CoCoInputPortNotAssignedTo
from pynestml.cocos.co_co_integrate_odes_params_correct import CoCoIntegrateODEsParamsCorrect
from pynestml.cocos.co_co_correct_numerator_of_unit import CoCoCorrectNumeratorOfUnit
Expand All @@ -43,6 +41,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
Expand Down Expand Up @@ -401,6 +400,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):
"""
Expand Down
15 changes: 13 additions & 2 deletions pynestml/codegeneration/nest_code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -170,7 +171,16 @@ 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 neuron in neurons:
# Check if the random number functions are used only in the update block.
CoCosManager.check_co_co_nest_random_functions_legally_used(neuron)
if Logger.has_errors(neuron):
raise Exception("Error(s) occurred during code generation")

for synapse in synapses:
# Check if the random number functions are used only in the update block.
CoCosManager.check_co_co_nest_random_functions_legally_used(synapse)

synapse_name_stripped = removesuffix(removesuffix(synapse.name.split("_with_")[0], "_"), FrontendConfiguration.suffix)

# special case for NEST delay variable (state or parameter)
Expand All @@ -184,8 +194,9 @@ def run_nest_target_specific_cocos(self, neurons: Sequence[ASTModel], synapses:
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):
raise Exception("Error(s) occurred during code generation")

if Logger.has_errors(synapse):
raise Exception("Error(s) occurred during code generation")

def setup_printers(self):
self._constant_printer = ConstantPrinter()
Expand Down
6 changes: 6 additions & 0 deletions pynestml/utils/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class MessageCode(Enum):
SYNS_BAD_BUFFER_COUNT = 107
CM_NO_V_COMP = 108
MECHS_DICTIONARY_INFO = 109
RANDOM_FUNCTIONS_LEGALLY_USED = 110


class Messages:
Expand Down Expand Up @@ -1312,3 +1313,8 @@ def get_mechs_dictionary_info(cls, chan_info, syns_info, conc_info, con_in_info)
message += "con_in_info:\n" + con_in_info + "\n"

return MessageCode.MECHS_DICTIONARY_INFO, message

@classmethod
def get_random_functions_legally_used(cls, name):
message = "The function '" + name + "' can only be used in the update block."
return MessageCode.RANDOM_FUNCTIONS_LEGALLY_USED, message
56 changes: 56 additions & 0 deletions tests/nest_tests/nest_random_functions_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# -*- 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 <http://www.gnu.org/licenses/>.
import os

import pytest

from pynestml.frontend.pynestml_frontend import generate_nest_target


class TestNestRandomFunctions:
"""
Tests for random number functions in NEST are declared only in ``update`` 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"}})
46 changes: 46 additions & 0 deletions tests/nest_tests/resources/random_functions_illegal_neuron.nestml
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
"""

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
73 changes: 73 additions & 0 deletions tests/nest_tests/resources/random_functions_illegal_synapse.nestml
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
"""

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()

0 comments on commit 2514e8a

Please sign in to comment.