Skip to content

Commit

Permalink
NEURON_MODULE_OPTIONS env variable to pass in options to ivoc_main (#602
Browse files Browse the repository at this point in the history
)

* Env var NEURON_MODULE_OPTIONS whitespace separated options.
* NEURON_MODULE_OPTIONS '-mpi' takes precedence over NEURON_INIT_MPI=0
* Doc about NEURON_MODULE_OPTIONS
* test added

Co-authored-by: Alexandru Săvulescu <[email protected]>
  • Loading branch information
nrnhines and alexsavulescu committed Apr 30, 2021
1 parent e2a0acc commit d294f25
Show file tree
Hide file tree
Showing 5 changed files with 287 additions and 32 deletions.
57 changes: 57 additions & 0 deletions docs/python/envvariables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,63 @@ With a bash shell, these are set by, e.g. (on ubuntu)
extern NEURONHOME=/where/neuron/was/installed/share/nrn
NEURON_MODULE_OPTIONS
--------------------
The option arguments allowed when nrniv is launched can be passed to
the neuron module (``import neuron``) when python is launched by using
the ``NEURON_MODULE_OPTIONS`` environment variable.
See the output of ``nrniv -h`` for a list of these options. The relevant
options are:

.. code-block:: shell
-NSTACK integer size of stack (default 1000)
-NFRAME integer depth of function call nesting (default 200)
-nogui do not send any gui info to screen
and all InterViews and X11 options
Note that the special option ``-print-options`` will cause the neuron module
to print its options (except for that one) on first import. Also,
``h.nrnversion(7)`` will return the effective command line indicating the
options.

If an arg appears multiple times, only the first will take effect.

Example:

.. code-block:: shell
export NEURON_MODULE_OPTIONS="-nogui -NFRAME 1000 -NSTACK 10000"
python -c '
from neuron import h
h("""
func add_recurse() {
if ($1 == 0) { return 0 }
return $1 + add_recurse($1 - 1)
}
""")
i = 900
assert(h.add_recurse(i) == i*(i+1)/2)
assert("-nogui -NFRAME 1000 -NSTACK 10000" in h.nrnversion(7))
'
As the environment variable is only used on the first import of NEURON,
one can set it from within python by using
``os.environ[NEURON_MODULE_OPTIONS] = "..."`` as in

.. code-block:: python
import sys
assert('neuron' not in sys.modules)
import os
nrn_options = "-nogui -NSTACK 3000 -NFRAME 525"
os.environ["NEURON_MODULE_OPTIONS"] = nrn_options
from neuron import h
assert(nrn_options in h.nrnversion(7))
NRNUNIT_USE_LEGACY
------------------
When set to 1, legacy unit values for FARADAY, R, and a few other constants
Expand Down
2 changes: 1 addition & 1 deletion share/lib/nrn.defaults.in
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
@nrndef_unix@*jvmdll: libjvm.so
@nrndef_mac@*jvmdll: libjvm.dylib

// The default stack size is 1000 and the frame (recursion depth) size is 100 unless explicitly set nonzero below
// The default stack size is 1000 and the frame (recursion depth) size is 512 unless explicitly set nonzero below
// One may also use the -NSTACK stacksize -NFRAME framesize options at launch.
*NSTACK: 0
*NFRAME: 0
Expand Down
215 changes: 185 additions & 30 deletions src/nrnpython/inithoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
#if defined(__MINGW32__)
#define _hypot hypot
#endif
#include <Python.h>
#include "nrnpy_utils.h"
#include <stdlib.h>
#include <ctype.h>

#if defined(NRNPYTHON_DYNAMICLOAD) && NRNPYTHON_DYNAMICLOAD > 0
// when compiled with different Python.h, force correct value
Expand Down Expand Up @@ -49,22 +50,170 @@ extern int nrn_nobanner_;
extern int ivocmain(int, char**, char**);
extern int nrn_main_launch;

#ifdef NRNMPI

static const char* argv_mpi[] = {"NEURON", "-mpi", "-dll", 0};
static int argc_mpi = 2;

#endif

static const char* argv_nompi[] = {"NEURON", "-dll", 0};
static int argc_nompi = 1;

#if USE_PTHREAD
#include <pthread.h>
static pthread_t main_thread_;
#endif

static void nrnpython_finalize() {
/**
* Manage argc,argv for calling ivocmain
* add_arg(...) will only add if name is not already in the arg list
* returns 1 if added, 0 if not
*/
static size_t arg_size;
static int argc;
static char** argv;
static int add_arg(const char* name, const char* value) {
if (size_t(argc + 2) >= arg_size) {
arg_size += 10;
argv = (char**)realloc(argv, arg_size*sizeof(char*));
}
// Don't add if already in argv
for (int i=1; i < argc; ++i) {
if (strcmp(name, argv[i]) == 0) {
return 0;
}
}
argv[argc++] = strdup(name);
if (value) {
argv[argc++] = strdup(value);
}
return 1;
}

/**
* Return 1 if string, 0 otherwise.
*/
static int is_string(PyObject* po) {
if (PyUnicode_Check(po) || PyBytes_Check(po)) {
return 1;
}
return 0;
}

/**
* Add all name:value from __main__.neuron_options dict if exists
* to the argc,argv for calling ivocmain
* Note: if value is None then only name is added to argv.
* The special "-print-options" name is not added to argv but
* causes a return value of 1. Otherwise the return value is 0.
*/
static int add_neuron_options() {
PyObject* modules = PyImport_GetModuleDict();
PyObject* module = PyDict_GetItemString(modules, "__main__");
int rval = 0;
if (!module) {
PyErr_Clear();
PySys_WriteStdout("No __main__ module\n");
return rval;
}
PyObject* neuron_options = PyObject_GetAttrString(module, "neuron_options");
if (!neuron_options) {
PyErr_Clear();
return rval;
}
if (!PyDict_Check(neuron_options)) {
PySys_WriteStdout("__main__.neuron_options is not a dict\n");
return rval;
}
PyObject *key, *value;
Py_ssize_t pos = 0;
while(PyDict_Next(neuron_options, &pos, &key, &value)) {
if (!is_string(key) || (!is_string(value) && value != Py_None)) {
PySys_WriteStdout("A neuron_options key:value is not a string:string or string:None\n");
continue;
}
Py2NRNString skey(key);
Py2NRNString sval(value);
if (strcmp(skey.c_str(), "-print-options") == 0) {
rval = 1;
continue;
}
add_arg(skey.c_str(), sval.c_str());
}
return rval;
}

/**
* Space separated options. Must handle escaped space, '...' and "...".
* Return 1 if contains a -print-options (not added to options)
*/

static int add_space_separated_options(const char* str) {
int rval = 0;
if (!str) { return rval; }
char* s = strdup(str);
//int state = 0; // 1 means in "...", 2 means in '...'
for (char* cp = s; *cp; cp++) {
while (isspace(*cp)) { // skip spaces
++cp;
if (*cp == '\0') {
free(s);
return rval;
}
}
// start processing a token
char* cpbegin = cp;
char* cp1 = cpbegin; // in token pointer, escapes cause to lag behind
while (!isspace(*cp) && *cp != '\0') { // to next space delimiter
*cp1++ = *cp++;
if (cp1[-1] == '\\' && (isspace(*cp) || *cp == '"' || *cp == '\'')) {
// escaped space, ", or '
cp1[-1] = *cp++;
}else if (cp1[-1] == '"') { // consume to next (unescaped) "
cp1--; // backup over the "
while (*cp != '"' && *cp != '\0') {
*cp1++ = *cp++;
if (cp1[-1] == '\\' && *cp == '"') { // escaped " inside "..."
cp1[-1] = *cp++;
}
}
if (*cp == '"') {
cp++; // skip over the closing "
}
}else if (cp1[-1] == '\'') { // consume to next (unescaped) '
cp1--; // backup over the '
while (*cp != '\'' && *cp != '\0') {
*cp1++ = *cp++;
if (cp1[-1] == '\\' && *cp == '\'') { // escaped ' inside '...'
cp1[-1] = *cp++;
}
}
if (*cp == '\'') {
cp++; // skip over the closing '
}
}
}
if (*cp == '\0') { // at end of s. 'for' will return after it increments.
--cp;
}
if (cp1 > cpbegin) {
*cp1 = '\0';
}
if (strcmp(cpbegin, "-print-options") == 0) {
rval = 1;
}else{
add_arg(cpbegin, NULL);
}
}
free(s);
return rval;
}

/**
* Return 1 if the option exists in argv[]
*/
static int have_opt(const char* arg) {
if (!arg) { return 0; }
for (int i=0; i < argc; ++i) {
if (strcmp(arg, argv[i]) == 0) {
return 1;
}
}
return 0;
}

void nrnpython_finalize() {
#if USE_PTHREAD
pthread_t now = pthread_self();
if (pthread_equal(main_thread_, now)) {
Expand Down Expand Up @@ -110,8 +259,6 @@ void inithoc() {

char buf[200];

int argc = argc_nompi;
char** argv = (char**)argv_nompi;
#if USE_PTHREAD
main_thread_ = pthread_self();
#endif
Expand All @@ -124,6 +271,11 @@ void inithoc() {
return;
#endif //!PY_MAJOR_VERSION >= 3
}

add_arg("NEURON", NULL);
int print_options = add_neuron_options();
print_options += add_space_separated_options(getenv("NEURON_MODULE_OPTIONS"));

#ifdef NRNMPI

int flag = 0; // true if MPI_Initialized is called
Expand All @@ -136,11 +288,12 @@ void inithoc() {
nrnmpi_stubs();
/**
* In case of dynamic mpi build we load MPI unless NEURON_INIT_MPI is explicitly set to 0.
* and there is no '-mpi' arg.
* We call nrnmpi_load to load MPI library which returns:
* - nil if loading is successfull
* - error message in case of loading error
*/
if(env_mpi != NULL && strcmp(env_mpi, "0") == 0) {
if(env_mpi != NULL && strcmp(env_mpi, "0") == 0 && !have_opt("-mpi")) {
libnrnmpi_is_loaded = 0;
}
if (libnrnmpi_is_loaded) {
Expand All @@ -152,12 +305,14 @@ void inithoc() {
}
if (pmes && libnrnmpi_is_loaded) {
printf(
"NEURON_INIT_MPI nonzero in env but NEURON cannot initialize MPI "
"NEURON_INIT_MPI nonzero in env (or -mpi arg) but NEURON cannot initialize MPI "
"because:\n%s\n",
pmes);
exit(1);
}
}
#else
have_opt(NULL); // avoid 'defined but not used' warning
#endif

/**
Expand All @@ -170,12 +325,10 @@ void inithoc() {
nrnmpi_wrap_mpi_init(&flag);
if (flag) {
mpi_mes = 1;
argc = argc_mpi;
argv = (char**)argv_mpi;
add_arg("-mpi", NULL);
} else if(env_mpi != NULL && strcmp(env_mpi, "1") == 0) {
mpi_mes = 2;
argc = argc_mpi;
argv = (char**)argv_mpi;
add_arg("-mpi", NULL);
}else{
mpi_mes = 3;
}
Expand All @@ -197,9 +350,7 @@ void inithoc() {
FILE* f;
if ((f = fopen(buf, "r")) != 0) {
fclose(f);
argc += 2;
argv[argc - 1] = new char[strlen(buf) + 1];
strcpy(argv[argc - 1], buf);
add_arg("-dll", buf);
}
#endif // !defined(__CYGWIN__)
nrn_is_python_extension = 1;
Expand Down Expand Up @@ -239,22 +390,26 @@ void inithoc() {
const int nframe_env_value = strtol(env_nframe, &endptr, 10);
if (*endptr == '\0') {
if(nframe_env_value > 0) {
argc += 3;
argv[argc - 2] = new char[strlen("-NFRAME") + 1];
strcpy(argv[argc - 2], "-NFRAME");
argv[argc - 1] = new char[strlen(env_nframe) + 1];
strcpy(argv[argc - 1], env_nframe);
add_arg("-NFRAME", env_nframe);
}else{
printf(
PySys_WriteStdout(
"NEURON_NFRAME env value must be positive\n");
}
}else{
printf(
PySys_WriteStdout(
"NEURON_NFRAME env value is invalid!\n");
}

}

if (print_options) {
PySys_WriteStdout("ivocmain options:");
for (int i=1; i < argc; ++i ) {
PySys_WriteStdout(" '%s'", argv[i]);
}
PySys_WriteStdout("\n");
}

nrn_main_launch = 2;
ivocmain(argc, argv, env);
// nrnpy_augment_path();
Expand Down
2 changes: 1 addition & 1 deletion src/nrnpython/setup.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ if using_pgi:
#################################

#include_dirs for hoc module
include_dirs = [nrn_srcdir+'/src/oc', '../oc', nrn_srcdir+'/src/nrnmpi']
include_dirs = [nrn_srcdir+'/src/nrnpython', nrn_srcdir+'/src/oc', '../oc', nrn_srcdir+'/src/nrnmpi']
#include dirs for all modules

include_dirs_common = []
Expand Down
Loading

0 comments on commit d294f25

Please sign in to comment.