From 38a1c1035c30b3b87e5bb25433b01dcb26bde869 Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Thu, 13 Mar 2025 16:07:12 +0100 Subject: [PATCH 1/2] Fix state and parameters getter --- .../nest_desktop_code_generator.py | 2 +- .../python_standalone_target_tools.py | 45 ++++++++++++------- .../point_neuron/@NEURON_NAME@.py.jinja2 | 2 + 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/pynestml/codegeneration/nest_desktop_code_generator.py b/pynestml/codegeneration/nest_desktop_code_generator.py index 9278535d7..b34e8e6bd 100644 --- a/pynestml/codegeneration/nest_desktop_code_generator.py +++ b/pynestml/codegeneration/nest_desktop_code_generator.py @@ -70,6 +70,6 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict: namespace["nestml_version"] = pynestml.__version__ namespace["neuronName"] = neuron.get_name() namespace["neuron"] = neuron - namespace["parameters"], namespace["state"] = PythonStandaloneTargetTools.get_neuron_parameters_and_state(neuron.get_name()) + namespace["parameters"], namespace["state"] = PythonStandaloneTargetTools.get_neuron_parameters_and_state(neuron.file_path) return namespace diff --git a/pynestml/codegeneration/python_standalone_target_tools.py b/pynestml/codegeneration/python_standalone_target_tools.py index c8efdb4f7..a4fe66f70 100644 --- a/pynestml/codegeneration/python_standalone_target_tools.py +++ b/pynestml/codegeneration/python_standalone_target_tools.py @@ -18,61 +18,74 @@ # # You should have received a copy of the GNU General Public License # along with NEST. If not, see . + import importlib import os import sys +import tempfile from pynestml.frontend.frontend_configuration import FrontendConfiguration from pynestml.frontend.pynestml_frontend import generate_python_standalone_target +from pynestml.meta_model.ast_model import ASTModel from pynestml.utils.logger import LoggingLevel, Logger +from pynestml.utils.model_parser import ModelParser class PythonStandaloneTargetTools: - """ - Helper functions for Python standalone target. + r""" + Helper functions for the Python standalone target. """ @classmethod - def _get_model_parameters_and_state(cls, model_name: str): - input_path = os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join( - os.pardir, os.pardir, "models", "neurons", model_name + ".nestml")))) + def _get_model_parameters_and_state(cls, nestml_file_name: str): suffix = "_nestml" module_name = FrontendConfiguration.get_module_name() - target_path = FrontendConfiguration.get_module_name() - generate_python_standalone_target(input_path=input_path, + target_path = tempfile.mkdtemp(prefix="nestml_python_target_", suffix="", dir=".") # dir = "." is necessary for Python import + generate_python_standalone_target(input_path=nestml_file_name, target_path=target_path, suffix=suffix, module_name=module_name, - logging_level="INFO") + logging_level="ERROR") + + ast_compilation_unit = ModelParser.parse_file(nestml_file_name) + if ast_compilation_unit is None or len(ast_compilation_unit.get_model_list()) == 0: + raise Exception("Error(s) occurred during code generation; please check error messages") - py_module_name = module_name + "." + model_name + suffix + model: ASTModel = ast_compilation_unit.get_model_list()[0] + model_name = model.get_name() + + py_module_name = os.path.basename(target_path) + "." + model_name module = importlib.import_module(py_module_name) - neuron_name = "Neuron_" + model_name + suffix + "(1.0)" + neuron_name = "Neuron_" + model_name + "(1.0)" # 1.0 is a dummy value for the timestep neuron = eval("module." + neuron_name) + parameters_list = [p for p in dir(neuron.Parameters_) if not "__" in p] parameters = {p: getattr(neuron, "get_" + p)() for p in parameters_list} - state_list = [p for p in dir(neuron.State_) if not "__" in p] + if "ode_state_variable_name_to_index" in dir(neuron.State_): + state_list = neuron.State_.ode_state_variable_name_to_index.keys() + else: + state_list = [p for p in dir(neuron.State_) if not "__" in p] state_vars = {p: getattr(neuron, "get_" + p)() for p in state_list} return parameters, state_vars @classmethod - def get_neuron_parameters_and_state(cls, neuron_model_name: str) -> tuple[dict, dict]: + def get_neuron_parameters_and_state(cls, nestml_file_name: str) -> tuple[dict, dict]: r""" Get the parameters for the given neuron model. The code is generated for the model for Python standalone target The parameters and state variables are then queried by creating the neuron in Python standalone simulator. - :param neuron_model_name: Name of the neuron model + :param nestml_file_name: File name of the neuron model :return: A dictionary of parameters and state variables """ - parameters, state = cls._get_model_parameters_and_state(neuron_model_name) + parameters, state = cls._get_model_parameters_and_state(nestml_file_name) if not parameters or not state: Logger.log_message(None, -1, - "An error occurred while creating the neuron for python standalone target: " + neuron_model_name, + "An error occurred while creating the neuron for Python standalone target: " + nestml_file_name, None, LoggingLevel.ERROR) sys.exit(1) else: - Logger.log_message(None, -1, "The model parameters were successfully queried from python standalone target.", + Logger.log_message(None, -1, "The model parameters were successfully queried from Python standalone target.", None, LoggingLevel.INFO) return parameters, state diff --git a/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 b/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 index fa9e06118..122ccb033 100644 --- a/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 +++ b/pynestml/codegeneration/resources_python_standalone/point_neuron/@NEURON_NAME@.py.jinja2 @@ -166,6 +166,7 @@ class Neuron_{{neuronName}}(Neuron): {%- endif %} {%- endfor %} {%- endfilter %} + pass # in case no variables else: # internals V_ {%- filter indent(6) %} @@ -174,6 +175,7 @@ class Neuron_{{neuronName}}(Neuron): {%- include "directives_py/MemberInitialization.jinja2" %} {%- endfor %} {%- endfilter %} + pass # in case no variables {%- if neuron.get_functions()|length > 0 %} From bf1d264156d701a8b49bfb2e4069ceb8da856077 Mon Sep 17 00:00:00 2001 From: "C.A.P. Linssen" Date: Mon, 31 Mar 2025 23:51:41 +0200 Subject: [PATCH 2/2] Fix state and parameters getter --- .../python_standalone_target_tools.py | 16 ++++++++++------ .../nest_desktop_code_generator_test.py | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/pynestml/codegeneration/python_standalone_target_tools.py b/pynestml/codegeneration/python_standalone_target_tools.py index a4fe66f70..32b5ad8a5 100644 --- a/pynestml/codegeneration/python_standalone_target_tools.py +++ b/pynestml/codegeneration/python_standalone_target_tools.py @@ -20,6 +20,7 @@ # along with NEST. If not, see . import importlib +import multiprocessing import os import sys import tempfile @@ -37,14 +38,17 @@ class PythonStandaloneTargetTools: """ @classmethod def _get_model_parameters_and_state(cls, nestml_file_name: str): - suffix = "_nestml" + suffix = "" module_name = FrontendConfiguration.get_module_name() target_path = tempfile.mkdtemp(prefix="nestml_python_target_", suffix="", dir=".") # dir = "." is necessary for Python import - generate_python_standalone_target(input_path=nestml_file_name, - target_path=target_path, - suffix=suffix, - module_name=module_name, - logging_level="ERROR") + # this has to run in a different process, because otherwise the frontend configuration gets overwritten + process = multiprocessing.Process(target=generate_python_standalone_target, kwargs={"input_path": nestml_file_name, + "target_path": target_path, + "suffix": suffix, + "module_name": module_name, + "logging_level": "ERROR"}) + process.start() + process.join() # wait for code generation to complete ast_compilation_unit = ModelParser.parse_file(nestml_file_name) if ast_compilation_unit is None or len(ast_compilation_unit.get_model_list()) == 0: diff --git a/tests/nest_desktop_tests/nest_desktop_code_generator_test.py b/tests/nest_desktop_tests/nest_desktop_code_generator_test.py index 27af8d48a..3a92410b0 100644 --- a/tests/nest_desktop_tests/nest_desktop_code_generator_test.py +++ b/tests/nest_desktop_tests/nest_desktop_code_generator_test.py @@ -42,7 +42,7 @@ def test_nest_desktop_code_generator(self): target_path=target_path, target_platform=target_platform, module_name=target_path, - logging_level="INFO") + logging_level="DEBUG") # Read the parameters from the generated json file and match them with the actual values with open(os.path.join(target_path, "iaf_psc_exp_neuron.json")) as f: