|
| 1 | +#!/usr/bin/env python3 |
| 2 | +""" |
| 3 | +Module for working with NSG. See https://github.com/OpenSourceBrain/pynsgr |
| 4 | +
|
| 5 | +File: pyneuroml/nsgr/__init__.py |
| 6 | +
|
| 7 | +Copyright 2023 NeuroML contributors |
| 8 | +""" |
| 9 | + |
| 10 | + |
| 11 | +import logging |
| 12 | +import os |
| 13 | +import pathlib |
| 14 | +import shutil |
| 15 | +import time |
| 16 | +import typing |
| 17 | +from zipfile import ZipFile |
| 18 | + |
| 19 | +from pyneuroml.archive import get_model_file_list |
| 20 | +from pyneuroml.pynml import run_lems_with |
| 21 | +from pyneuroml.utils import get_files_generated_after, get_pyneuroml_tempdir |
| 22 | +from pynsgr.commands.nsgr_submit import nsgr_submit |
| 23 | + |
| 24 | +logger = logging.getLogger(__name__) |
| 25 | +logger.setLevel(logging.DEBUG) |
| 26 | + |
| 27 | + |
| 28 | +def run_on_nsg( |
| 29 | + engine: str, |
| 30 | + lems_file_name: str, |
| 31 | + nsg_sim_config: typing.Dict[typing.Any, typing.Any] = {}, |
| 32 | + run_dir: typing.Optional[str] = None, |
| 33 | + dry_run: bool = False, |
| 34 | + *engine_args: typing.Any, |
| 35 | + **engine_kwargs: typing.Any, |
| 36 | +): |
| 37 | + """Run NeuroML/LEMS simulation on NSG. |
| 38 | +
|
| 39 | + Since the versions of tools on NSG may differ from what you have installed |
| 40 | + locally, this method first generates the simulation engine specific files |
| 41 | + (runner script for NEURON and mod files, for example) for the provided |
| 42 | + engine in a new folder, zips them up, writes the config files, and then |
| 43 | + submits the job to NSG using pynsgr. |
| 44 | +
|
| 45 | + The data generated by the job (if the command is not interrupted) is also |
| 46 | + downloaded to the newly created directory. |
| 47 | +
|
| 48 | + Please ensure that you have set up an account and have your NSG |
| 49 | + configuration file populated as noted in pynsgr. |
| 50 | +
|
| 51 | + - https://nsgr.sdsc.edu:8443/restusers/documentation |
| 52 | + - https://nsgr.sdsc.edu:8443/restusers/docs/tools |
| 53 | +
|
| 54 | + Default for the nsg_sim_config is below, keys provided by the user in |
| 55 | + nsg_sim_config overwrite these: |
| 56 | +
|
| 57 | + .. code:: python |
| 58 | +
|
| 59 | + nsg_sim_config_dict = { |
| 60 | + "number_cores_": "1", |
| 61 | + "number_nodes_": "1", |
| 62 | + "tasks_per_node_": "1", |
| 63 | + "runtime_": "0.5", |
| 64 | + 'toolId': "PY_EXPANSE", |
| 65 | + 'nrnivmodl_o_': "1" |
| 66 | + } |
| 67 | +
|
| 68 | + .. versionadded:: 1.0.10 |
| 69 | +
|
| 70 | + :param engine: name of engine: suffixes of the run_lems_with.. functions |
| 71 | + :type engine: str |
| 72 | + :param lems_file_name: name of LEMS simulation file |
| 73 | + :type lems_file_name: str |
| 74 | + :param nsg_sim_config: dict containing params and values that will be |
| 75 | + printed to testParam.properties |
| 76 | + :type nsg_sim_config: dict |
| 77 | + :param run_dir: directory in which model files are copied, backend specific |
| 78 | + files are generated, and from which the archive is sent to NSG. Results |
| 79 | + from the NSG run will also be downloaded to this directory. |
| 80 | +
|
| 81 | + By default, this is the directory that the command is run from (".") |
| 82 | +
|
| 83 | + It is good practice to separate directories where simulations are run |
| 84 | + from the source of the model/simulations. |
| 85 | + :type run_dir: str |
| 86 | + :param *engine_args: positional args to be passed to the engine runner |
| 87 | + function |
| 88 | + :param **engine_kwargs: keyword args to be be passed to the engine runner |
| 89 | + function |
| 90 | + :param dry_run: do everything but do not submit |
| 91 | + :type dry_run: bool |
| 92 | + """ |
| 93 | + supported_engines = ["jneuroml_neuron", "jneuroml_netpyne"] |
| 94 | + if engine not in supported_engines: |
| 95 | + print(f"Engine {engine} is not currently supported on NSG") |
| 96 | + print(f"Supported engines are: {supported_engines}") |
| 97 | + return |
| 98 | + |
| 99 | + logger.debug(f"NSGR: engine is {engine}") |
| 100 | + |
| 101 | + zipfile_name = lems_file_name.replace(".xml", "") + "_NSG.zip" |
| 102 | + # default dictionary |
| 103 | + nsg_sim_config_dict = { |
| 104 | + "number_cores_": "1", |
| 105 | + "number_nodes_": "1", |
| 106 | + "tasks_per_node_": "1", |
| 107 | + "runtime_": "0.5", |
| 108 | + "toolId": "PY_EXPANSE", |
| 109 | + "nrnivmodl_o_": "1", |
| 110 | + } |
| 111 | + |
| 112 | + # update dict based on user values |
| 113 | + for key, val in nsg_sim_config.items(): |
| 114 | + nsg_sim_config_dict[key] = val |
| 115 | + |
| 116 | + if run_dir is None: |
| 117 | + run_dir = "." |
| 118 | + |
| 119 | + tdir = get_pyneuroml_tempdir(rootdir=run_dir, prefix="pyneuroml") |
| 120 | + os.mkdir(tdir) |
| 121 | + |
| 122 | + logger.debug("Getting list of model files") |
| 123 | + model_file_list = [] # type: list |
| 124 | + lems_def_dir = None |
| 125 | + lems_def_dir = get_model_file_list( |
| 126 | + lems_file_name, model_file_list, ".", lems_def_dir |
| 127 | + ) |
| 128 | + |
| 129 | + for model_file in model_file_list: |
| 130 | + logger.debug(f"Copying: {model_file} -> {tdir + '/' + model_file}") |
| 131 | + # if model file has directory structures in it, recreate the dirs in |
| 132 | + # the temporary directory |
| 133 | + if len(model_file.split("/")) > 1: |
| 134 | + # throw error if files in parent directories are referred to |
| 135 | + if "../" in model_file: |
| 136 | + raise ValueError( |
| 137 | + """ |
| 138 | + Cannot handle parent directories because we |
| 139 | + cannot create these directories correctly in |
| 140 | + the temporary location. Please re-organize |
| 141 | + your code such that all included files are in |
| 142 | + sub-directories of this main directory. |
| 143 | + """ |
| 144 | + ) |
| 145 | + |
| 146 | + model_file_path = pathlib.Path(tdir + "/" + model_file) |
| 147 | + parent = model_file_path.parent |
| 148 | + parent.mkdir(parents=True, exist_ok=True) |
| 149 | + shutil.copy(model_file, tdir + "/" + model_file) |
| 150 | + |
| 151 | + if lems_def_dir is not None: |
| 152 | + logger.info(f"Removing LEMS definitions directory {lems_def_dir}") |
| 153 | + shutil.rmtree(lems_def_dir) |
| 154 | + |
| 155 | + os.chdir(tdir) |
| 156 | + logger.info(f"Generating simulator specific files in {tdir}") |
| 157 | + start_time = time.time() - 1.0 |
| 158 | + |
| 159 | + if engine == "jneuroml_neuron": |
| 160 | + run_lems_with( |
| 161 | + engine, |
| 162 | + lems_file_name=lems_file_name, |
| 163 | + compile_mods=False, |
| 164 | + only_generate_scripts=True, |
| 165 | + *engine_args, |
| 166 | + **engine_kwargs, |
| 167 | + ) |
| 168 | + elif engine == "jneuroml_netpyne": |
| 169 | + run_lems_with( |
| 170 | + engine, |
| 171 | + lems_file_name=lems_file_name, |
| 172 | + only_generate_scripts=True, |
| 173 | + *engine_args, |
| 174 | + **engine_kwargs, |
| 175 | + ) |
| 176 | + |
| 177 | + generated_files = get_files_generated_after( |
| 178 | + start_time, ignore_suffixes=["xml", "nml"] |
| 179 | + ) |
| 180 | + logger.debug(f"Generated files are: {generated_files}") |
| 181 | + |
| 182 | + logger.info("Generating zip file") |
| 183 | + runner_file = "" |
| 184 | + # NSG requires that the top level directory exist |
| 185 | + nsg_dir = pathlib.Path(zipfile_name.replace(".zip", "")) |
| 186 | + logger.debug(f"Creating directory and moving generated files to it: {nsg_dir}") |
| 187 | + |
| 188 | + # remove it if it exists |
| 189 | + if nsg_dir.is_dir(): |
| 190 | + shutil.rmtree(str(nsg_dir)) |
| 191 | + nsg_dir.mkdir() |
| 192 | + |
| 193 | + with ZipFile(zipfile_name, "w") as archive: |
| 194 | + for f in generated_files: |
| 195 | + if engine == "jneuroml_neuron": |
| 196 | + if f.endswith("_nrn.py"): |
| 197 | + runner_file = f |
| 198 | + elif engine == "jneuroml_netpyne": |
| 199 | + if f.endswith("_netpyne.py"): |
| 200 | + runner_file = f |
| 201 | + fpath = pathlib.Path(f) |
| 202 | + moved_path = fpath.rename(nsg_dir / fpath) |
| 203 | + archive.write(str(moved_path)) |
| 204 | + |
| 205 | + logger.debug("Printing testParam.properties") |
| 206 | + nsg_sim_config_dict["filename_"] = runner_file |
| 207 | + logger.debug(f"NSG sim config is: {nsg_sim_config_dict}") |
| 208 | + |
| 209 | + with open("testParam.properties", "w") as file: |
| 210 | + for key, val in nsg_sim_config_dict.items(): |
| 211 | + print(f"{key}={val}", file=file) |
| 212 | + |
| 213 | + logger.debug("Printing testInput.properties") |
| 214 | + with open("testInput.properties", "w") as file: |
| 215 | + print(f"infile_=@./{zipfile_name}", file=file) |
| 216 | + |
| 217 | + print(f"{zipfile_name} generated") |
| 218 | + # uses argv, where the first argument is the script itself, so we must pass |
| 219 | + # something as the 0th index of the list |
| 220 | + if not dry_run: |
| 221 | + if nsgr_submit(["", ".", "validate"]) == 0: |
| 222 | + print("Attempting to submit to NSGR") |
| 223 | + return nsgr_submit(["", ".", "run"]) |
| 224 | + else: |
| 225 | + print("Dry run mode enabled. Not submitting to NSG.") |
| 226 | + |
| 227 | + return True |
0 commit comments