Skip to content

Commit

Permalink
Modify the NEST code generator to Python standalone for the NEST Desk…
Browse files Browse the repository at this point in the history
…top target
  • Loading branch information
pnbabu committed Dec 10, 2024
1 parent 3f4cb50 commit ca48b5d
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 93 deletions.
4 changes: 3 additions & 1 deletion pynestml/codegeneration/nest_desktop_code_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

from pynestml.codegeneration.code_generator import CodeGenerator
from pynestml.codegeneration.code_generator_utils import CodeGeneratorUtils
from pynestml.codegeneration.python_standalone_target_tools import PythonStandaloneTargetTools
from pynestml.frontend.frontend_configuration import FrontendConfiguration
from pynestml.meta_model.ast_model import ASTModel


Expand Down Expand Up @@ -65,5 +67,5 @@ def _get_neuron_model_namespace(self, neuron: ASTModel) -> Dict:
namespace = dict()
namespace["neuronName"] = neuron.get_name()
namespace["neuron"] = neuron
namespace["parameters"] = NESTTools.get_neuron_parameters(neuron.get_name())
namespace["parameters"], namespace["state"] = PythonStandaloneTargetTools.get_neuron_parameters_and_state(neuron.get_name())
return namespace
67 changes: 5 additions & 62 deletions pynestml/codegeneration/nest_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.

import subprocess
import os
import sys
import tempfile
import multiprocessing as mp

from pynestml.frontend.pynestml_frontend import generate_nest_target
from pynestml.utils.logger import Logger
from pynestml.utils.logger import LoggingLevel

Expand Down Expand Up @@ -96,65 +92,12 @@ def detect_nest_version(cls) -> str:
nest_version = stderr.decode("UTF-8").strip()

if nest_version == "":
Logger.log_message(None, -1, "An error occurred while importing the `nest` module in Python. Please check your NEST installation-related environment variables and paths, or specify ``nest_version`` manually in the code generator options.", None, LoggingLevel.ERROR)
sys.exit(1)

Logger.log_message(None, -1, "The NEST Simulator version was automatically detected as: " + nest_version, None, LoggingLevel.INFO)

return nest_version

@classmethod
def _get_model_parameters(cls, model_name: str, queue: mp.Queue):
try:
import nest
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"))))
target_path = "target"
suffix = "_nestml"
module_name = "nest_desktop_module"
generate_nest_target(input_path=input_path,
target_path=target_path,
suffix=suffix,
module_name=module_name,
logging_level="INFO")
# Install the nest module and query all the parameters
nest.Install(module_name)
n = nest.Create(model_name + suffix)
parameters = n.get()

except ModuleNotFoundError:
parameters = {}

queue.put(parameters)

@classmethod
def get_neuron_parameters(cls, neuron_model_name: str) -> dict:
r"""
Get the parameters for the given neuron model. The code is generated for the model and installed into NEST.
The parameters are then queried by creating the neuron in NEST.
:param neuron_model_name: Name of the neuron model
:return: A dictionary of parameters
"""
# This function internally calls the nest_code_generator which calls the detect_nest_version() function that
# uses mp.Pool. If a Pool is used here instead of a Process, it gives the error "daemonic processes are not
# allowed to have children". Since creating a Pool inside a Pool is not allowed, we create a Process
# object here instead.
_queue = mp.Queue()
p = mp.Process(target=cls._get_model_parameters, args=(neuron_model_name, _queue))
p.start()
p.join()
parameters = _queue.get()
p.close()

if not parameters:
Logger.log_message(None, -1,
"An error occurred while importing the `nest` module in Python. Please check your NEST "
"installation-related environment variables and paths, or specify ``nest_version`` "
"manually in the code generator options.",
"An error occurred while importing the `nest` module in Python. Please check your NEST installation-related environment variables and paths, or specify ``nest_version`` manually in the code generator options.",
None, LoggingLevel.ERROR)
sys.exit(1)
else:
Logger.log_message(None, -1, "The model parameters were successfully queried from NEST simulator",
None, LoggingLevel.INFO)

return parameters
Logger.log_message(None, -1, "The NEST Simulator version was automatically detected as: " + nest_version, None,
LoggingLevel.INFO)

return nest_version
78 changes: 78 additions & 0 deletions pynestml/codegeneration/python_standalone_target_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
#
# nest_tools.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 importlib
import os
import sys

from pynestml.frontend.frontend_configuration import FrontendConfiguration
from pynestml.frontend.pynestml_frontend import generate_python_standalone_target
from pynestml.utils.logger import LoggingLevel, Logger


class PythonStandaloneTargetTools:
"""
Helper functions for 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"))))
suffix = "_nestml"
module_name = FrontendConfiguration.get_module_name()
target_path = FrontendConfiguration.get_module_name()
generate_python_standalone_target(input_path=input_path,
target_path=target_path,
suffix=suffix,
module_name=module_name,
logging_level="INFO")

py_module_name = "nest_desktop_module." + model_name + suffix
module = importlib.import_module(py_module_name)
neuron_name = "Neuron_" + model_name + suffix + "(1.0)"
neuron = eval("module." + neuron_name)
parameters_list = [p for p in dir(neuron.Parameters_) if not "__" in p]
parameters = {p: eval("neuron.get_" + p + "()") for p in parameters_list}

state_list = [p for p in dir(neuron.State_) if not "__" in p]
state_vars = {p: eval("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]:
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
:return: A dictionary of parameters and state variables
"""
parameters, state = cls._get_model_parameters_and_state(neuron_model_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,
None, LoggingLevel.ERROR)
sys.exit(1)
else:
Logger.log_message(None, -1, "The model parameters were successfully queried from python standalone target.",
None, LoggingLevel.INFO)

return parameters, state
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
"elementType": "neuron",
"label": "{{neuronName}}",
"recordables": [
{%- for variable in neuron.get_state_symbols() %}
"{{variable.get_symbol_name()}}"
{%- if not loop.last %},{%- endif %}
{%- endfor %}
{%- set vars = state %}
{%- set symbols = neuron.get_state_symbols() %}
{%- include "VariableDeclaration.jinja2" %}
],
"params": [
{%- for variable in neuron.get_parameter_symbols() %}
{%- set last = loop.last %}
{%- include "ParameterDeclaration.jinja2" %}
{%- endfor %}
{%- set vars = parameters %}
{%- set symbols = neuron.get_parameter_symbols() %}
{%- include "VariableDeclaration.jinja2" %}
]
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{%- for variable in symbols %}
{%- set name = variable.get_symbol_name() %}
{%- set type_symbol = variable.get_type_symbol().print_symbol() %}
{%- set default_value = vars[variable.get_symbol_name()] %}
{%- set last = loop.last %}
{
"id": "{{name}}",
"label": "{{variable.print_comment().strip()}}",
"unit": "{{type_symbol}}",
"value": "{{default_value}}"
}{%- if not last %},{%- endif %}
{%- endfor %}
37 changes: 24 additions & 13 deletions tests/nest_desktop_tests/nest_desktop_code_generator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,37 @@ def test_nest_desktop_code_generator(self):
"""
input_path = os.path.join(os.path.realpath(os.path.join(os.path.dirname(__file__), os.path.join(
os.pardir, os.pardir, "models", "neurons", "iaf_psc_exp_neuron.nestml"))))
target_path = "target_nest_desktop"
target_path = "nest_desktop_module"
target_platform = "NEST_DESKTOP"
generate_target(input_path=input_path,
target_path=target_path,
target_platform=target_platform,
module_name=target_path,
logging_level="INFO")

# 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:
data = f.read()
json_data = json.loads(data)
actual_params = {"C_m": "250.0",
"tau_m": "10.0",
"tau_syn_inh": "2.0",
"tau_syn_exc": "2.0",
"refr_T": "2.0",
"E_L": "-70.0",
"V_reset": "-70.0",
"V_th": "-55.0",
"I_e": "0.0"}
for param_data in json_data["params"]:
_id = param_data["id"]
assert param_data["value"] == actual_params[_id]
actual_params = {"C_m": "250",
"tau_m": "10",
"tau_syn_inh": "2",
"tau_syn_exc": "2",
"refr_T": "2",
"E_L": "-70",
"V_reset": "-70",
"V_th": "-55",
"I_e": "0"}
actual_state = {
"I_syn_exc": "0",
"I_syn_inh": "0",
"V_m": "-70",
"refr_t": "0"
}
for recordables_data in json_data["params"]:
_id = recordables_data["id"]
assert recordables_data["value"] == actual_params[_id]

for recordables_data in json_data["recordables"]:
_id = recordables_data["id"]
assert recordables_data["value"] == actual_state[_id]

0 comments on commit ca48b5d

Please sign in to comment.