Skip to content

Commit ceb4f22

Browse files
author
C.A.P. Linssen
committed
run context condition checks only once, after model parsing
1 parent e6565b5 commit ceb4f22

39 files changed

+1304
-1228
lines changed

doc/tutorials/stdp_third_factor_active_dendrite/stdp_third_factor_active_dendrite.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1347,7 +1347,7 @@
13471347
" NESTCodeGeneratorUtils.generate_code_for(nestml_neuron_model,\n",
13481348
" nestml_synapse_model,\n",
13491349
" codegen_opts=codegen_opts,\n",
1350-
" logging_level=\"INFO\") # try \"INFO\" or \"DEBUG\" for more debug information"
1350+
" logging_level=\"WARNING\") # try \"INFO\" or \"DEBUG\" for more debug information"
13511351
]
13521352
},
13531353
{

pynestml/cocos/co_co_all_variables_defined.py

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,10 @@ class CoCoAllVariablesDefined(CoCo):
4141
"""
4242

4343
@classmethod
44-
def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):
44+
def check_co_co(cls, node: ASTModel):
4545
"""
4646
Checks if this coco applies for the handed over neuron. Models which contain undefined variables are not correct.
4747
:param node: a single neuron instance.
48-
: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.
4948
"""
5049
# for each variable in all expressions, check if the variable has been defined previously
5150
expression_collector_visitor = ASTExpressionCollectorVisitor()
@@ -62,32 +61,6 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):
6261

6362
# test if the symbol has been defined at least
6463
if symbol is None:
65-
if after_ast_rewrite: # after ODE-toolbox transformations, convolutions are replaced by state variables, so cannot perform this check properly
66-
symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE)
67-
if symbol2 is not None:
68-
# an inline expression defining this variable name (ignoring differential order) exists
69-
if "__X__" in str(symbol2): # if this variable was the result of a convolution...
70-
continue
71-
else:
72-
# for kernels, also allow derivatives of that kernel to appear
73-
74-
inline_expr_names = []
75-
inline_exprs = []
76-
for equations_block in node.get_equations_blocks():
77-
inline_expr_names.extend([inline_expr.variable_name for inline_expr in equations_block.get_inline_expressions()])
78-
inline_exprs.extend(equations_block.get_inline_expressions())
79-
80-
if var.get_name() in inline_expr_names:
81-
inline_expr_idx = inline_expr_names.index(var.get_name())
82-
inline_expr = inline_exprs[inline_expr_idx]
83-
from pynestml.utils.ast_utils import ASTUtils
84-
if ASTUtils.inline_aliases_convolution(inline_expr):
85-
symbol2 = node.get_scope().resolve_to_symbol(var.get_name(), SymbolKind.VARIABLE)
86-
if symbol2 is not None:
87-
# actually, no problem detected, skip error
88-
# XXX: TODO: check that differential order is less than or equal to that of the kernel
89-
continue
90-
9164
# check if this symbol is actually a type, e.g. "mV" in the expression "(1 + 2) * mV"
9265
symbol2 = var.get_scope().resolve_to_symbol(var.get_complete_name(), SymbolKind.TYPE)
9366
if symbol2 is not None:
@@ -106,9 +79,14 @@ def check_co_co(cls, node: ASTModel, after_ast_rewrite: bool = False):
10679
# in this case its ok if it is recursive or defined later on
10780
continue
10881

82+
if symbol.is_predefined:
83+
continue
84+
85+
if symbol.block_type == BlockType.LOCAL and symbol.get_referenced_object().get_source_position().before(var.get_source_position()):
86+
continue
87+
10988
# check if it has been defined before usage, except for predefined symbols, input ports and variables added by the AST transformation functions
110-
if (not symbol.is_predefined) \
111-
and symbol.block_type != BlockType.INPUT \
89+
if symbol.block_type != BlockType.INPUT \
11290
and not symbol.get_referenced_object().get_source_position().is_added_source_position():
11391
# except for parameters, those can be defined after
11492
if ((not symbol.get_referenced_object().get_source_position().before(var.get_source_position()))

pynestml/cocos/co_co_function_unique.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ def check_co_co(cls, model: ASTModel):
6565
log_level=LoggingLevel.ERROR,
6666
message=message, code=code)
6767
checked.append(funcA)
68+
6869
checked_funcs_names.append(func.get_name())

pynestml/cocos/co_co_illegal_expression.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818
#
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
21-
from pynestml.meta_model.ast_inline_expression import ASTInlineExpression
2221

23-
from pynestml.utils.ast_source_location import ASTSourceLocation
24-
from pynestml.meta_model.ast_declaration import ASTDeclaration
2522
from pynestml.cocos.co_co import CoCo
23+
from pynestml.meta_model.ast_declaration import ASTDeclaration
24+
from pynestml.meta_model.ast_inline_expression import ASTInlineExpression
2625
from pynestml.symbols.error_type_symbol import ErrorTypeSymbol
2726
from pynestml.symbols.predefined_types import PredefinedTypes
27+
from pynestml.utils.ast_source_location import ASTSourceLocation
2828
from pynestml.utils.logger import LoggingLevel, Logger
2929
from pynestml.utils.logging_helper import LoggingHelper
3030
from pynestml.utils.messages import Messages

pynestml/cocos/co_co_no_kernels_except_in_convolve.py

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@
2222
from typing import List
2323

2424
from pynestml.cocos.co_co import CoCo
25+
from pynestml.meta_model.ast_declaration import ASTDeclaration
26+
from pynestml.meta_model.ast_external_variable import ASTExternalVariable
2527
from pynestml.meta_model.ast_function_call import ASTFunctionCall
2628
from pynestml.meta_model.ast_kernel import ASTKernel
2729
from pynestml.meta_model.ast_model import ASTModel
2830
from pynestml.meta_model.ast_node import ASTNode
2931
from pynestml.meta_model.ast_variable import ASTVariable
32+
from pynestml.symbols.predefined_functions import PredefinedFunctions
3033
from pynestml.symbols.symbol import SymbolKind
3134
from pynestml.utils.logger import Logger, LoggingLevel
3235
from pynestml.utils.messages import Messages
@@ -89,24 +92,44 @@ def visit_variable(self, node: ASTNode):
8992
if not (isinstance(node, ASTExternalVariable) and node.get_alternate_name()):
9093
code, message = Messages.get_no_variable_found(kernelName)
9194
Logger.log_message(node=self.__neuron_node, code=code, message=message, log_level=LoggingLevel.ERROR)
95+
9296
continue
97+
9398
if not symbol.is_kernel():
9499
continue
100+
95101
if node.get_complete_name() == kernelName:
96-
parent = node.get_parent()
97-
if parent is not None:
102+
parent = node
103+
correct = False
104+
while parent is not None and not isinstance(parent, ASTModel):
105+
parent = parent.get_parent()
106+
assert parent is not None
107+
108+
if isinstance(parent, ASTDeclaration):
109+
for lhs_var in parent.get_variables():
110+
if kernelName == lhs_var.get_complete_name():
111+
# kernel name appears on lhs of declaration, assume it is initial state
112+
correct = True
113+
parent = None # break out of outer loop
114+
break
115+
98116
if isinstance(parent, ASTKernel):
99-
continue
100-
grandparent = parent.get_parent()
101-
if grandparent is not None and isinstance(grandparent, ASTFunctionCall):
102-
grandparent_func_name = grandparent.get_name()
103-
if grandparent_func_name == 'convolve':
104-
continue
105-
code, message = Messages.get_kernel_outside_convolve(kernelName)
106-
Logger.log_message(code=code,
107-
message=message,
108-
log_level=LoggingLevel.ERROR,
109-
error_position=node.get_source_position())
117+
# 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'``
118+
correct = True
119+
break
120+
121+
if isinstance(parent, ASTFunctionCall):
122+
func_name = parent.get_name()
123+
if func_name == PredefinedFunctions.CONVOLVE:
124+
# kernel name is used inside convolve call
125+
correct = True
126+
127+
if not correct:
128+
code, message = Messages.get_kernel_outside_convolve(kernelName)
129+
Logger.log_message(code=code,
130+
message=message,
131+
log_level=LoggingLevel.ERROR,
132+
error_position=node.get_source_position())
110133

111134

112135
class KernelCollectingVisitor(ASTVisitor):

pynestml/cocos/co_co_v_comp_exists.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ def check_co_co(cls, neuron: ASTModel):
4343
Models which are supposed to be compartmental but do not contain
4444
state variable called v_comp are not correct.
4545
:param neuron: a single neuron instance.
46-
:param after_ast_rewrite: indicates whether this coco is checked
47-
after the code generator has done rewriting of the abstract syntax tree.
48-
If True, checks are not as rigorous. Use False where possible.
4946
"""
5047
from pynestml.codegeneration.nest_compartmental_code_generator import NESTCompartmentalCodeGenerator
5148

pynestml/cocos/co_cos_manager.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
from pynestml.cocos.co_co_priorities_correctly_specified import CoCoPrioritiesCorrectlySpecified
7070
from pynestml.meta_model.ast_model import ASTModel
7171
from pynestml.frontend.frontend_configuration import FrontendConfiguration
72+
from pynestml.utils.logger import Logger
7273

7374

7475
class CoCosManager:
@@ -123,12 +124,12 @@ def check_state_variables_initialized(cls, model: ASTModel):
123124
CoCoStateVariablesInitialized.check_co_co(model)
124125

125126
@classmethod
126-
def check_variables_defined_before_usage(cls, model: ASTModel, after_ast_rewrite: bool) -> None:
127+
def check_variables_defined_before_usage(cls, model: ASTModel) -> None:
127128
"""
128129
Checks that all variables are defined before being used.
129130
:param model: a single model.
130131
"""
131-
CoCoAllVariablesDefined.check_co_co(model, after_ast_rewrite)
132+
CoCoAllVariablesDefined.check_co_co(model)
132133

133134
@classmethod
134135
def check_v_comp_requirement(cls, neuron: ASTModel):
@@ -402,17 +403,19 @@ def check_input_port_size_type(cls, model: ASTModel):
402403
CoCoVectorInputPortsCorrectSizeType.check_co_co(model)
403404

404405
@classmethod
405-
def post_symbol_table_builder_checks(cls, model: ASTModel, after_ast_rewrite: bool = False):
406+
def check_cocos(cls, model: ASTModel, after_ast_rewrite: bool = False):
406407
"""
407408
Checks all context conditions.
408409
:param model: a single model object.
409410
"""
411+
Logger.set_current_node(model)
412+
410413
cls.check_each_block_defined_at_most_once(model)
411414
cls.check_function_defined(model)
412415
cls.check_variables_unique_in_scope(model)
413416
cls.check_inline_expression_not_assigned_to(model)
414417
cls.check_state_variables_initialized(model)
415-
cls.check_variables_defined_before_usage(model, after_ast_rewrite)
418+
cls.check_variables_defined_before_usage(model)
416419
if FrontendConfiguration.get_target_platform().upper() == 'NEST_COMPARTMENTAL':
417420
# XXX: TODO: refactor this out; define a ``cocos_from_target_name()`` in the frontend instead.
418421
cls.check_v_comp_requirement(model)
@@ -452,3 +455,5 @@ def post_symbol_table_builder_checks(cls, model: ASTModel, after_ast_rewrite: bo
452455
cls.check_co_co_priorities_correctly_specified(model)
453456
cls.check_resolution_func_legally_used(model)
454457
cls.check_input_port_size_type(model)
458+
459+
Logger.set_current_node(None)

pynestml/codegeneration/builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
2121

2222
from __future__ import annotations
23-
import subprocess
24-
import os
2523

2624
from typing import Any, Mapping, Optional
2725

2826
from abc import ABCMeta, abstractmethod
27+
import os
28+
import subprocess
2929

3030
from pynestml.exceptions.invalid_target_exception import InvalidTargetException
3131
from pynestml.frontend.frontend_configuration import FrontendConfiguration

pynestml/codegeneration/nest_code_generator.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import pynestml
2929

3030
from pynestml.cocos.co_co_nest_synapse_delay_not_assigned_to import CoCoNESTSynapseDelayNotAssignedTo
31+
from pynestml.cocos.co_cos_manager import CoCosManager
3132
from pynestml.codegeneration.code_generator import CodeGenerator
3233
from pynestml.codegeneration.code_generator_utils import CodeGeneratorUtils
3334
from pynestml.codegeneration.nest_assignments_helper import NestAssignmentsHelper
@@ -374,6 +375,9 @@ def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Di
374375
if not used_in_eq:
375376
self.non_equations_state_variables[neuron.get_name()].append(var)
376377

378+
# cache state variables before symbol table update for the sake of delay variables
379+
state_vars_before_update = neuron.get_state_symbols()
380+
377381
ASTUtils.remove_initial_values_for_kernels(neuron)
378382
kernels = ASTUtils.remove_kernel_definitions_from_equations_block(neuron)
379383
ASTUtils.update_initial_values_for_odes(neuron, [analytic_solver, numeric_solver])
@@ -388,7 +392,6 @@ def analyse_neuron(self, neuron: ASTModel) -> Tuple[Dict[str, ASTAssignment], Di
388392
neuron = ASTUtils.add_declarations_to_internals(
389393
neuron, self.analytic_solver[neuron.get_name()]["propagators"])
390394

391-
state_vars_before_update = neuron.get_state_symbols()
392395
self.update_symbol_table(neuron)
393396

394397
# Update the delay parameter parameters after symbol table update
@@ -898,8 +901,8 @@ def update_symbol_table(self, neuron) -> None:
898901
"""
899902
SymbolTable.delete_model_scope(neuron.get_name())
900903
symbol_table_visitor = ASTSymbolTableVisitor()
901-
symbol_table_visitor.after_ast_rewrite_ = True
902904
neuron.accept(symbol_table_visitor)
905+
CoCosManager.check_cocos(neuron, after_ast_rewrite=True)
903906
SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope())
904907

905908
def get_spike_update_expressions(self, neuron: ASTModel, kernel_buffers, solver_dicts, delta_factors) -> Tuple[Dict[str, ASTAssignment], Dict[str, ASTAssignment]]:

pynestml/codegeneration/nest_compartmental_code_generator.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818
#
1919
# You should have received a copy of the GNU General Public License
2020
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
21-
import shutil
21+
2222
from typing import Any, Dict, List, Mapping, Optional
2323

2424
import datetime
2525
import os
2626

2727
from jinja2 import TemplateRuntimeError
28+
29+
from odetoolbox import analysis
30+
2831
import pynestml
32+
from pynestml.cocos.co_cos_manager import CoCosManager
2933
from pynestml.codegeneration.code_generator import CodeGenerator
3034
from pynestml.codegeneration.nest_assignments_helper import NestAssignmentsHelper
3135
from pynestml.codegeneration.nest_declarations_helper import NestDeclarationsHelper
@@ -53,9 +57,9 @@
5357
from pynestml.meta_model.ast_variable import ASTVariable
5458
from pynestml.symbol_table.symbol_table import SymbolTable
5559
from pynestml.symbols.symbol import SymbolKind
60+
from pynestml.transformers.inline_expression_expansion_transformer import InlineExpressionExpansionTransformer
5661
from pynestml.utils.ast_vector_parameter_setter_and_printer import ASTVectorParameterSetterAndPrinter
5762
from pynestml.utils.ast_vector_parameter_setter_and_printer_factory import ASTVectorParameterSetterAndPrinterFactory
58-
from pynestml.transformers.inline_expression_expansion_transformer import InlineExpressionExpansionTransformer
5963
from pynestml.utils.mechanism_processing import MechanismProcessing
6064
from pynestml.utils.channel_processing import ChannelProcessing
6165
from pynestml.utils.concentration_processing import ConcentrationProcessing
@@ -72,7 +76,6 @@
7276
from pynestml.utils.synapse_processing import SynapseProcessing
7377
from pynestml.visitors.ast_random_number_generator_visitor import ASTRandomNumberGeneratorVisitor
7478
from pynestml.visitors.ast_symbol_table_visitor import ASTSymbolTableVisitor
75-
from odetoolbox import analysis
7679

7780

7881
class NESTCompartmentalCodeGenerator(CodeGenerator):
@@ -740,8 +743,8 @@ def update_symbol_table(self, neuron, kernel_buffers):
740743
"""
741744
SymbolTable.delete_model_scope(neuron.get_name())
742745
symbol_table_visitor = ASTSymbolTableVisitor()
743-
symbol_table_visitor.after_ast_rewrite_ = True
744746
neuron.accept(symbol_table_visitor)
747+
CoCosManager.check_cocos(neuron, after_ast_rewrite=True)
745748
SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope())
746749

747750
def _get_ast_variable(self, neuron, var_name) -> Optional[ASTVariable]:

0 commit comments

Comments
 (0)