Skip to content

Commit

Permalink
Merge remote-tracking branch 'clinssen/symboltable_checks' into nestm…
Browse files Browse the repository at this point in the history
…l_include
  • Loading branch information
C.A.P. Linssen committed Oct 7, 2024
2 parents 2057f13 + fcffa1e commit 5f93cdb
Show file tree
Hide file tree
Showing 46 changed files with 2,037 additions and 1,291 deletions.
91 changes: 91 additions & 0 deletions .github/workflows/continuous_benchmarking.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
on:
pull_request_target:
types: [opened, reopened, edited, synchronize]

jobs:
fork_pr_requires_review:
environment: ${{ (github.event.pull_request.head.repo.full_name == github.repository && 'internal') || 'external' }}
runs-on: ubuntu-latest
steps:
- run: true

benchmark_fork_pr_branch:
needs: fork_pr_requires_review
name: Continuous Benchmarking Fork PRs with Bencher
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false

- uses: bencherdev/bencher@main

# Setup Python version
- name: Setup Python 3.8
uses: actions/setup-python@v5
with:
python-version: 3.8

# Install dependencies
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install libltdl7-dev libgsl0-dev libncurses5-dev libreadline6-dev pkg-config
sudo apt-get install python3-all-dev python3-matplotlib python3-numpy python3-scipy ipython3
# Install Python dependencies
- name: Python dependencies
run: |
python -m pip install --upgrade pip pytest jupyterlab matplotlib pycodestyle scipy pandas pytest-benchmark
python -m pip install -r requirements.txt
# Install NEST simulator
- name: NEST simulator
run: |
python -m pip install cython
echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE"
NEST_SIMULATOR=$(pwd)/nest-simulator
NEST_INSTALL=$(pwd)/nest_install
echo "NEST_SIMULATOR = $NEST_SIMULATOR"
echo "NEST_INSTALL = $NEST_INSTALL"
git clone --depth=1 https://github.com/nest/nest-simulator
mkdir nest_install
echo "NEST_INSTALL=$NEST_INSTALL" >> $GITHUB_ENV
cd nest_install
cmake -DCMAKE_INSTALL_PREFIX=$NEST_INSTALL $NEST_SIMULATOR
make && make install
cd ..
# Install NESTML (repeated)
- name: Install NESTML
run: |
export PYTHONPATH=${{ env.PYTHONPATH }}:${{ env.NEST_INSTALL }}/lib/python3.8/site-packages
#echo PYTHONPATH=`pwd` >> $GITHUB_ENV
echo "PYTHONPATH=$PYTHONPATH" >> $GITHUB_ENV
python setup.py install
- name: Track Fork PR Benchmarks with Bencher
env:
LD_LIBRARY_PATH: ${{ env.NEST_INSTALL }}/lib/nest
run: |
echo "NEST_INSTALL = $NEST_INSTALL"
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{ env.NEST_INSTALL }}/lib/nest bencher run \
--project nestml \
--token '${{ secrets.BENCHER_API_TOKEN }}' \
--branch '${{ github.event.number }}/merge' \
--branch-start-point '${{ github.base_ref }}' \
--branch-start-point-hash '${{ github.event.pull_request.base.sha }}' \
--branch-reset \
--github-actions "${{ secrets.GITHUB_TOKEN }}" \
--testbed ubuntu-latest \
--adapter python_pytest \
--file results.json \
--err \
'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{ env.NEST_INSTALL }}/lib/nest python3 -m pytest --benchmark-json results.json -s $GITHUB_WORKSPACE/tests/nest_continuous_benchmarking/test_nest_continuous_benchmarking.py'
- name: Setup tmate session
if: ${{ failure() }}
uses: mxschmitt/action-tmate@v3
Original file line number Diff line number Diff line change
Expand Up @@ -1347,7 +1347,7 @@
" NESTCodeGeneratorUtils.generate_code_for(nestml_neuron_model,\n",
" nestml_synapse_model,\n",
" codegen_opts=codegen_opts,\n",
" logging_level=\"INFO\") # try \"INFO\" or \"DEBUG\" for more debug information"
" logging_level=\"WARNING\") # try \"INFO\" or \"DEBUG\" for more debug information"
]
},
{
Expand Down
38 changes: 8 additions & 30 deletions pynestml/cocos/co_co_all_variables_defined.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,10 @@ class CoCoAllVariablesDefined(CoCo):
"""

@classmethod
def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):
def check_co_co(cls, node: ASTModel):
"""
Checks if this coco applies for the handed over neuron. Models which contain undefined variables are not correct.
:param node: a single neuron instance.
:param after_ast_rewrite: indicates whether this coco is checked after the code generator has done rewriting of the abstract syntax tree. If True, checks are not as rigorous. Use False where possible.
"""
# for each variable in all expressions, check if the variable has been defined previously
expression_collector_visitor = ASTExpressionCollectorVisitor()
Expand All @@ -62,32 +61,6 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):

# test if the symbol has been defined at least
if symbol is None:
if after_ast_rewrite: # after ODE-toolbox transformations, convolutions are replaced by state variables, so cannot perform this check properly
symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE)
if symbol2 is not None:
# an inline expression defining this variable name (ignoring differential order) exists
if "__X__" in str(symbol2): # if this variable was the result of a convolution...
continue
else:
# for kernels, also allow derivatives of that kernel to appear

inline_expr_names = []
inline_exprs = []
for equations_block in node.get_equations_blocks():
inline_expr_names.extend([inline_expr.variable_name for inline_expr in equations_block.get_inline_expressions()])
inline_exprs.extend(equations_block.get_inline_expressions())

if var.get_name() in inline_expr_names:
inline_expr_idx = inline_expr_names.index(var.get_name())
inline_expr = inline_exprs[inline_expr_idx]
from pynestml.utils.ast_utils import ASTUtils
if ASTUtils.inline_aliases_convolution(inline_expr):
symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE)
if symbol2 is not None:
# actually, no problem detected, skip error
# XXX: TODO: check that differential order is less than or equal to that of the kernel
continue

# check if this symbol is actually a type, e.g. "mV" in the expression "(1 + 2) * mV"
symbol2 = var.get_scope().resolve_to_symbol(var.get_complete_name(), SymbolKind.TYPE)
if symbol2 is not None:
Expand All @@ -106,9 +79,14 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):
# in this case its ok if it is recursive or defined later on
continue

if symbol.is_predefined:
continue

if symbol.block_type == BlockType.LOCAL and symbol.get_referenced_object().get_source_position().before(var.get_source_position()):
continue

# check if it has been defined before usage, except for predefined symbols, input ports and variables added by the AST transformation functions
if (not symbol.is_predefined) \
and symbol.block_type != BlockType.INPUT \
if symbol.block_type != BlockType.INPUT \
and not symbol.get_referenced_object().get_source_position().is_added_source_position():
# except for parameters, those can be defined after
if ((not symbol.get_referenced_object().get_source_position().before(var.get_source_position()))
Expand Down
1 change: 1 addition & 0 deletions pynestml/cocos/co_co_function_unique.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ def check_co_co(cls, model: ASTModel):
log_level=LoggingLevel.ERROR,
message=message, code=code)
checked.append(funcA)

checked_funcs_names.append(func.get_name())
6 changes: 3 additions & 3 deletions pynestml/cocos/co_co_illegal_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
#
# 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.meta_model.ast_inline_expression import ASTInlineExpression

from pynestml.utils.ast_source_location import ASTSourceLocation
from pynestml.meta_model.ast_declaration import ASTDeclaration
from pynestml.cocos.co_co import CoCo
from pynestml.meta_model.ast_declaration import ASTDeclaration
from pynestml.meta_model.ast_inline_expression import ASTInlineExpression
from pynestml.symbols.error_type_symbol import ErrorTypeSymbol
from pynestml.symbols.predefined_types import PredefinedTypes
from pynestml.utils.ast_source_location import ASTSourceLocation
from pynestml.utils.logger import LoggingLevel, Logger
from pynestml.utils.logging_helper import LoggingHelper
from pynestml.utils.messages import Messages
Expand Down
73 changes: 73 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,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 <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_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)
49 changes: 36 additions & 13 deletions pynestml/cocos/co_co_no_kernels_except_in_convolve.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@
from typing import List

from pynestml.cocos.co_co import CoCo
from pynestml.meta_model.ast_declaration import ASTDeclaration
from pynestml.meta_model.ast_external_variable import ASTExternalVariable
from pynestml.meta_model.ast_function_call import ASTFunctionCall
from pynestml.meta_model.ast_kernel import ASTKernel
from pynestml.meta_model.ast_model import ASTModel
from pynestml.meta_model.ast_node import ASTNode
from pynestml.meta_model.ast_variable import ASTVariable
from pynestml.symbols.predefined_functions import PredefinedFunctions
from pynestml.symbols.symbol import SymbolKind
from pynestml.utils.logger import Logger, LoggingLevel
from pynestml.utils.messages import Messages
Expand Down Expand Up @@ -89,24 +92,44 @@ def visit_variable(self, node: ASTNode):
if not (isinstance(node, ASTExternalVariable) and node.get_alternate_name()):
code, message = Messages.get_no_variable_found(kernelName)
Logger.log_message(node=self.__neuron_node, code=code, message=message, log_level=LoggingLevel.ERROR)

continue

if not symbol.is_kernel():
continue

if node.get_complete_name() == kernelName:
parent = node.get_parent()
if parent is not None:
parent = node
correct = False
while parent is not None and not isinstance(parent, ASTModel):
parent = parent.get_parent()
assert parent is not None

if isinstance(parent, ASTDeclaration):
for lhs_var in parent.get_variables():
if kernelName == lhs_var.get_complete_name():
# kernel name appears on lhs of declaration, assume it is initial state
correct = True
parent = None # break out of outer loop
break

if isinstance(parent, ASTKernel):
continue
grandparent = parent.get_parent()
if grandparent is not None and isinstance(grandparent, ASTFunctionCall):
grandparent_func_name = grandparent.get_name()
if grandparent_func_name == 'convolve':
continue
code, message = Messages.get_kernel_outside_convolve(kernelName)
Logger.log_message(code=code,
message=message,
log_level=LoggingLevel.ERROR,
error_position=node.get_source_position())
# kernel name is used inside kernel definition, e.g. for a node ``g``, it appears in ``kernel g'' = -1/tau**2 * g - 2/tau * g'``
correct = True
break

if isinstance(parent, ASTFunctionCall):
func_name = parent.get_name()
if func_name == PredefinedFunctions.CONVOLVE:
# kernel name is used inside convolve call
correct = True

if not correct:
code, message = Messages.get_kernel_outside_convolve(kernelName)
Logger.log_message(code=code,
message=message,
log_level=LoggingLevel.ERROR,
error_position=node.get_source_position())


class KernelCollectingVisitor(ASTVisitor):
Expand Down
3 changes: 0 additions & 3 deletions pynestml/cocos/co_co_v_comp_exists.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ def check_co_co(cls, neuron: ASTModel):
Models which are supposed to be compartmental but do not contain
state variable called v_comp are not correct.
:param neuron: a single neuron instance.
:param after_ast_rewrite: indicates whether this coco is checked
after the code generator has done rewriting of the abstract syntax tree.
If True, checks are not as rigorous. Use False where possible.
"""
from pynestml.codegeneration.nest_compartmental_code_generator import NESTCompartmentalCodeGenerator

Expand Down
Loading

0 comments on commit 5f93cdb

Please sign in to comment.