Skip to content

Commit eec6c89

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

24 files changed

+1057
-1010
lines changed

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_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: 37 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,45 @@ 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+
print("Cjecking " + kernelName + " stwit " + lhs_var.get_complete_name())
111+
if kernelName == lhs_var.get_complete_name():
112+
# kernel name appears on lhs of declaration, assume it is initial state
113+
correct = True
114+
parent = None # break out of outer loop
115+
break
116+
98117
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())
118+
# 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'``
119+
correct = True
120+
break
121+
122+
if isinstance(parent, ASTFunctionCall):
123+
func_name = parent.get_name()
124+
if func_name == PredefinedFunctions.CONVOLVE:
125+
# kernel name is used inside convolve call
126+
correct = True
127+
128+
if not correct:
129+
code, message = Messages.get_kernel_outside_convolve(kernelName)
130+
Logger.log_message(code=code,
131+
message=message,
132+
log_level=LoggingLevel.ERROR,
133+
error_position=node.get_source_position())
110134

111135

112136
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/nest_code_generator.py

Lines changed: 2 additions & 1 deletion
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
@@ -898,8 +899,8 @@ def update_symbol_table(self, neuron) -> None:
898899
"""
899900
SymbolTable.delete_model_scope(neuron.get_name())
900901
symbol_table_visitor = ASTSymbolTableVisitor()
901-
symbol_table_visitor.after_ast_rewrite_ = True
902902
neuron.accept(symbol_table_visitor)
903+
CoCosManager.check_cocos(neuron, after_ast_rewrite=True)
903904
SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope())
904905

905906
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -740,8 +740,8 @@ def update_symbol_table(self, neuron, kernel_buffers):
740740
"""
741741
SymbolTable.delete_model_scope(neuron.get_name())
742742
symbol_table_visitor = ASTSymbolTableVisitor()
743-
symbol_table_visitor.after_ast_rewrite_ = True
744743
neuron.accept(symbol_table_visitor)
744+
CoCosManager.check_cocos(neuron, after_ast_rewrite=True)
745745
SymbolTable.add_model_scope(neuron.get_name(), neuron.get_scope())
746746

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

pynestml/codegeneration/spinnaker_code_generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ def generate_code(self, models: Sequence[ASTModel]) -> None:
216216
for model in models:
217217
cloned_model = model.clone()
218218
cloned_model.accept(ASTSymbolTableVisitor())
219+
CoCosManager.check_cocos(cloned_model)
219220
cloned_models.append(cloned_model)
220221

221222
self.codegen_cpp.generate_code(cloned_models)
@@ -224,6 +225,7 @@ def generate_code(self, models: Sequence[ASTModel]) -> None:
224225
for model in models:
225226
cloned_model = model.clone()
226227
cloned_model.accept(ASTSymbolTableVisitor())
228+
CoCosManager.check_cocos(cloned_model)
227229
cloned_models.append(cloned_model)
228230

229231
self.codegen_py.generate_code(cloned_models)

pynestml/frontend/frontend_configuration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ def handle_module_name(cls, module_name):
244244

245245
@classmethod
246246
def handle_target_platform(cls, target_platform: Optional[str]):
247-
if target_platform is None or target_platform.upper() == 'NONE':
248-
target_platform = '' # make sure `target_platform` is always a string
247+
if target_platform is None:
248+
target_platform = "NONE" # make sure `target_platform` is always a string
249249

250250
from pynestml.frontend.pynestml_frontend import get_known_targets
251251

pynestml/frontend/pynestml_frontend.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,10 @@ def code_generator_from_target_name(target_name: str, options: Optional[Mapping[
131131
return SpiNNakerCodeGenerator(options)
132132

133133
if target_name.upper() == "NONE":
134-
# dummy/null target: user requested to not generate any code
134+
# dummy/null target: user requested to not generate any code (for instance, when just doing validation of a model)
135135
code, message = Messages.get_no_code_generated()
136136
Logger.log_message(None, code, message, None, LoggingLevel.INFO)
137-
return CodeGenerator("", options)
137+
return CodeGenerator(options)
138138

139139
# cannot reach here due to earlier assert -- silence static checker warnings
140140
assert "Unknown code generator requested: " + target_name
@@ -426,8 +426,9 @@ def get_parsed_models():
426426
if len(compilation_units) > 0:
427427
# generate a list of all models
428428
models: Sequence[ASTModel] = []
429-
for compilationUnit in compilation_units:
430-
models.extend(compilationUnit.get_model_list())
429+
for compilation_unit in compilation_units:
430+
CoCosManager.check_model_names_unique(compilation_unit)
431+
models.extend(compilation_unit.get_model_list())
431432

432433
# check that no models with duplicate names have been defined
433434
CoCosManager.check_no_duplicate_compilation_unit_names(models)
@@ -443,6 +444,9 @@ def get_parsed_models():
443444

444445
return models, False
445446

447+
# no models, no errors
448+
return [], False
449+
446450

447451
def transform_models(transformers, models):
448452
for transformer in transformers:
@@ -481,7 +485,14 @@ def process():
481485
models, errors_occurred = get_parsed_models()
482486

483487
if not errors_occurred:
488+
# validation
489+
for model in models:
490+
CoCosManager.check_cocos(model)
491+
492+
# transformation(s)
484493
models = transform_models(transformers, models)
494+
495+
# generate code
485496
generate_code(code_generator, models)
486497

487498
# perform build

0 commit comments

Comments
 (0)