From 19d25a3654ae56fc59db803a0077251fbdf71609 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 1 May 2025 07:59:41 -0600 Subject: [PATCH 01/23] initial implementation of python functions in variable formulas, including for atom-style vars --- doc/src/variable.rst | 103 +++++++++++++++++++++++++++++-------------- src/variable.cpp | 98 ++++++++++++++++++++++++++++++++++++---- src/variable.h | 6 ++- 3 files changed, 163 insertions(+), 44 deletions(-) diff --git a/doc/src/variable.rst b/doc/src/variable.rst index 75a13e47e1..ba3a5db35a 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -45,7 +45,8 @@ Syntax *universe* args = one or more strings *world* args = one string for each partition of processors - *equal* or *vector* or *atom* args = one formula containing numbers, thermo keywords, math operations, built-in functions, atom values and vectors, compute/fix/variable references + *equal* or *vector* or *atom* args = one formula containing numbers, thermo keywords, + math operations, built-in functions, atom values and vectors, compute/fix/variable references numbers = 0.0, 100, -5.4, 2.8e-4, etc constants = PI, version, on, off, true, false, yes, no thermo keywords = vol, ke, press, etc from :doc:`thermo_style ` @@ -67,8 +68,12 @@ Syntax bound(group,dir,region), gyration(group,region), ke(group,reigon), angmom(group,dim,region), torque(group,dim,region), inertia(group,dimdim,region), omega(group,dim,region) - special functions = sum(x), min(x), max(x), ave(x), trap(x), slope(x), sort(x), rsort(x), gmask(x), rmask(x), grmask(x,y), next(x), is_file(name), is_os(name), extract_setting(name), label2type(kind,label), is_typelabel(kind,label), is_timeout() - feature functions = is_available(category,feature), is_active(category,feature), is_defined(category,id) + special functions = sum(x), min(x), max(x), ave(x), trap(x), slope(x), sort(x), rsort(x), \ gmask(x), rmask(x), grmask(x,y), next(x), is_file(name), is_os(name), + extract_setting(name), label2type(kind,label), + is_typelabel(kind,label), is_timeout() + feature functions = is_available(category,feature), is_active(category,feature), + is_defined(category,id) + python function = py_varname(x,y,z,...) atom value = id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] atom vector = id, mass, type, mol, radius, q, x, y, z, vx, vy, vz, fx, fy, fz custom atom property = i_name, d_name, i_name[i], d_name[i], i2_name[i], d2_name[i], i2_name[i][j], d2_name[i][j] @@ -127,18 +132,21 @@ command), or used as input to an averaging fix (see the :doc:`fix ave/time ` command). Variables of style *vector* store a formula which produces a vector of such values which can be used as input to various averaging fixes, or elements of which can be part of -thermodynamic output. Variables of style *atom* store a formula which -when evaluated produces one numeric value per atom which can be output -to a dump file (see the :doc:`dump custom ` command) or used as -input to an averaging fix (see the :doc:`fix ave/chunk -` and :doc:`fix ave/atom ` commands). -Variables of style *atomfile* can be used anywhere in an input script -that atom-style variables are used; they get their per-atom values -from a file rather than from a formula. Variables of style *python* -can be hooked to Python functions using code you provide, so that the -variable gets its value from the evaluation of the Python code. -Variables of style *internal* are used by a few commands which set -their value directly. +thermodynamic output. + +Variables of style *atom* store a formula which when evaluated +produces one numeric value per atom which can be output to a dump file +(see the :doc:`dump custom ` command) or used as input to an +averaging fix (see the :doc:`fix ave/chunk ` and +:doc:`fix ave/atom ` commands). Variables of style +*atomfile* can be used anywhere in an input script that atom-style +variables are used; they get their per-atom values from a file rather +than from a formula. + +Variables of style *python* can be hooked to Python functions using +Python code you provide, so that the variable gets its value from the +evaluation of the Python code. Variables of style *internal* are used +by a few commands which set their value directly. .. note:: @@ -166,15 +174,16 @@ simulation. .. note:: - When an input script line is encountered that defines a variable - of style *equal* or *vector* or *atom* or *python* that contains a - formula or Python code, the formula is NOT immediately evaluated. It - will be evaluated every time when the variable is **used** instead. If - you simply want to evaluate a formula in place you can use as - so-called. See the section below about "Immediate Evaluation of - Variables" for more details on the topic. This is also true of a - *format* style variable since it evaluates another variable when it is - invoked. + When an input script line is encountered that defines a variable of + style *equal* or *vector* or *atom* or *python* that contains a + formula or links to Python code, the formula or Python code is NOT + immediately evaluated. Instead, it is evaulated aech time the + variable is **used**. If you simply want to evaluate a formula in + place you can use a so-called immediate variable. as described in + the preceding note. Or see the section below about "Immediate + Evaluation of Variables" for more details on the topic. This is + also true of a *format* style variable since it evaluates another + variable when it is invoked. Variables of style *equal* and *vector* and *atom* can be used as inputs to various other commands which evaluate their formulas as @@ -183,12 +192,12 @@ this context, variables of style *timer* or *internal* or *python* can be used in place of an equal-style variable, with the following two caveats. -First, internal-style variables can be used except by commands that -set the value stored by the internal variable. When the LAMMPS -command evaluates the internal-style variable, it will use the value -set (internally) by another command. Second, python-style variables -can be used so long as the associated Python function, as defined by -the :doc:`python ` command, returns a numeric value. When the +First, internal-style variables require their values be set by code +elsewhere in LAMMPS. When a LAMMPS input script or command evaluates +an internal-style variable, it must have a current value set +(internally) via that mechanism. Second, python-style variables can +be used so long as the associated Python function, as defined by the +:doc:`python ` command, returns a numeric value. When the LAMMPS command evaluates the python-style variable, the Python function will be executed. @@ -439,6 +448,14 @@ python-style variable can be used in place of an equal-style variable anywhere in an input script, e.g. as an argument to another command that allows for equal-style variables. +A python-style variable can also be used within the formula for an +equal-style or atom-style formula with the syntax +py_varname(arg1,arg2,...) as explained below for variable formulas. +When used in an atom-style formula, it can the variable can be invoked +once per atom using arguments specific to each atom. The resulting +values in the atom-style variable can thus be calculated by Python +code. + ---------- For the *string* style, a single string is assigned to the variable. @@ -528,9 +545,9 @@ is a valid (though strange) variable formula: Specifically, a formula can contain numbers, constants, thermo keywords, math operators, math functions, group functions, region -functions, special functions, feature functions, atom values, atom -vectors, custom atom properties, compute references, fix references, and references to other -variables. +functions, special functions, feature functions, the python function, +atom values, atom vectors, custom atom properties, compute references, +fix references, and references to other variables. +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Number | 0.2, 100, 1.0e20, -15.4, etc | @@ -541,7 +558,7 @@ variables. +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Math operators | (), -x, x+y, x-y, x\*y, x/y, x\^y, x%y, x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x \|\| y, x \|\^ y, !x | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Math functions | sqrt(x), exp(x), ln(x), log(x), abs(x), sign(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ternary(x,y,z), ramp(x,y), stagger(x,y), logfreq(x,y,z), logfreq2(x,y,z), logfreq3(x,y,z), stride(x,y,z), stride2(x,y,z,a,b,c), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) | +| Math functions | sqrt(x), exp(x), ln(x), log(x), abs(x), sign(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ternary(x,y,z), ramp(x,y), stagger(x,y), logfreq(x,y,z), logfreq2(x,y,z), logfreq3(x,y,z), stride(x,y,z), stride2(x,y,z,a,b,c), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Group functions | count(ID), mass(ID), charge(ID), xcm(ID,dim), vcm(ID,dim), fcm(ID,dim), bound(ID,dir), gyration(ID), ke(ID), angmom(ID,dim), torque(ID,dim), inertia(ID,dimdim), omega(ID,dim) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -550,6 +567,7 @@ variables. | Special functions | sum(x), min(x), max(x), ave(x), trap(x), slope(x), sort(x), rsort(x), gmask(x), rmask(x), grmask(x,y), next(x), is_file(name), is_os(name), extract_setting(name), label2type(kind,label), is_typelabel(kind,label), is_timeout() | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Feature functions | is_available(category,feature), is_active(category,feature), is_defined(category,id) | ++------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| Python function | py_varname(x,y,z,...) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Atom values | id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -1161,6 +1179,23 @@ variable name. ---------- +Python Function +----------------- + +NOTE: this needs work +explain why use this versus just reference a python variable +(b/c can pass args) +(b/c can use it in an atom-style varible) + +Math operators are written in the usual way, where the "x" and "y" in +the examples can themselves be arbitrarily complex formulas, as in the +examples above. In this syntax, "x" and "y" can be scalar values or +per-atom vectors. For example, "ke/natoms" is the division of two +scalars, where "vy+vz" is the element-by-element sum of two per-atom +vectors of y and z velocities. + +---------- + Atom Values and Vectors ----------------------- diff --git a/src/variable.cpp b/src/variable.cpp index ee2114998c..7b8ec6012a 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -81,6 +81,7 @@ enum{DONE,ADD,SUBTRACT,MULTIPLY,DIVIDE,CARAT,MODULO,UNARY, RAMP,STAGGER,LOGFREQ,LOGFREQ2,LOGFREQ3,STRIDE,STRIDE2, VDISPLACE,SWIGGLE,CWIGGLE,SIGN,GMASK,RMASK, GRMASK,IS_ACTIVE,IS_DEFINED,IS_AVAILABLE,IS_FILE,EXTRACT_SETTING, + PYFUNCTION, VALUE,ATOMARRAY,TYPEARRAY,INTARRAY,BIGINTARRAY,VECTORARRAY}; // customize by adding a special function @@ -1006,7 +1007,7 @@ char *Variable::pythonstyle(char *name, char *funcname) /* ---------------------------------------------------------------------- return 1 if variable is INTERNAL style, 0 if not - this is checked before call to set_internal() to assure it can be set + this is checked before call to internal_set() to assure it can be set ------------------------------------------------------------------------- */ int Variable::internalstyle(int ivar) @@ -2361,13 +2362,13 @@ double Variable::evaluate(char *str, Tree **tree, int ivar) } // ---------------- - // math/group/special/labelmap function or atom value/vector or constant or thermo keyword + // math/group/region/special/feature function or atom value/vector or constant or thermo keyword // ---------------- } else { // ---------------- - // math or group or special function + // math or group/region or special or feature function // ---------------- if (str[i] == '(') { @@ -3183,6 +3184,20 @@ double Variable::collapse_tree(Tree *tree) return tree->value; } + if (tree->type == PYFUNCTION) { + int narg = tree->argcount; + int *argvars = tree->argvars; + double arg; + for (int iarg = 0; iarg < narg; iarg++) { + if (iarg == 0) arg = collapse_tree(tree->first); + else if (iarg == 1) arg = collapse_tree(tree->second); + else arg = collapse_tree(tree->extra[iarg-2]); + internal_set(argvars[iarg],arg); + } + tree->value = compute_equal(tree->pyvar); + return tree->value; + } + // mask functions do not become a single collapsed value if (tree->type == GMASK) return 0.0; @@ -3518,8 +3533,20 @@ double Variable::eval_tree(Tree *tree, int i) } if (tree->type == SIGN) - return (eval_tree(tree->first,i) >= 0.0) ? 1.0 : -1.0; // sign(eval_tree(tree->first,i)); - + return (eval_tree(tree->first,i) >= 0.0) ? 1.0 : -1.0; // sign(eval_tree(tree->first,i)); + + if (tree->type == PYFUNCTION) { + int narg = tree->argcount; + for (int iarg = 0; iarg < narg; iarg++) { + if (iarg == 0) arg = eval_tree(tree->first,i); + else if (iarg == 1) arg = eval_tree(tree->second,i); + else arg = eval_tree(tree->extra[iarg-2],i); + internal_set(tree->argvars[iarg],arg); + } + arg = compute_equal(tree->pyvar); + return arg; + } + if (tree->type == GMASK) { if (atom->mask[i] & tree->ivalue) return 1.0; else return 0.0; @@ -3583,6 +3610,7 @@ void Variable::free_tree(Tree *tree) for (int i = 0; i < tree->nextra; i++) free_tree(tree->extra[i]); delete[] tree->extra; } + if (tree->argvars) delete[] tree->argvars; if (tree->selfalloc) memory->destroy(tree->array); delete tree; @@ -3684,8 +3712,9 @@ tagint Variable::int_between_brackets(char *&ptr, int varallow) /* ---------------------------------------------------------------------- process a math function in formula + includes a Python function with syntax py_varname(arg1,arg2,...) push result onto tree or arg stack - word = math function + word = math function name contents = str between parentheses with comma-separated args return 0 if not a match, 1 if successfully processed customize by adding a math function: @@ -3693,7 +3722,8 @@ tagint Variable::int_between_brackets(char *&ptr, int varallow) atan2(y,x),random(x,y,z),normal(x,y,z),ceil(),floor(),round(),ternary(), ramp(x,y),stagger(x,y),logfreq(x,y,z),logfreq2(x,y,z), logfreq3(x,y,z),stride(x,y,z),stride2(x,y,z,a,b,c),vdisplace(x,y), - swiggle(x,y,z),cwiggle(x,y,z),sign(x) + swiggle(x,y,z),cwiggle(x,y,z),sign(x), + py_varname(arg1,arg2,...) (up to MAXFUNCARG) ------------------------------------------------------------------------- */ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **treestack, @@ -3711,7 +3741,8 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree strcmp(word,"logfreq") != 0 && strcmp(word,"logfreq2") != 0 && strcmp(word,"logfreq3") != 0 && strcmp(word,"stride") != 0 && strcmp(word,"stride2") != 0 && strcmp(word,"vdisplace") != 0 && - strcmp(word,"swiggle") != 0 && strcmp(word,"cwiggle") != 0 && strcmp(word,"sign") != 0) + strcmp(word,"swiggle") != 0 && strcmp(word,"cwiggle") != 0 && strcmp(word,"sign") != 0 && + strstr(word,"py_") != word) return 0; // parse contents for comma-separated args @@ -4106,11 +4137,60 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree double value = value1 + value2*(1.0-cos(omega*delta*update->dt)); argstack[nargstack++] = value; } + } else if (strcmp(word,"sign") == 0) { if (narg != 1) print_var_error(FLERR,"Invalid math function in variable formula",ivar); if (tree) newtree->type = SIGN; else argstack[nargstack++] = (value1 >= 0.0) ? 1.0 : -1.0; // sign(value1); + + // Python function tied to varname + // narg arguments tied to internal variables pyarg1, pyarg2, ... pyargN + + } else if (strstr(word,"py_") == word) { + + // text following py_ = python-style variable name + + int pyvar = find(&word[3]); + if (style[pyvar] != PYTHON) + print_var_error(FLERR,"Invalid python function variable name",ivar); + + // check for existence of narg internal variables with names pyarg1 to pyargN + // store their indices in jvars + + int *jvars = new int[narg]; + char *internal_varname; + + for (int iarg = 0; iarg < narg; iarg++) { + internal_varname = utils::strdup(fmt::format("pyarg{}", iarg+1)); + jvars[iarg] = find(internal_varname); + if (jvars[iarg] < 0) + print_var_error(FLERR,"Invalid python function arg in variable formula",ivar); + if (!internalstyle(jvars[iarg])) + print_var_error(FLERR,"Invalid python function arg in variable formula",ivar); + delete[] internal_varname; + } + + // if tree: store python variable and arg info in tree for later eval + // else: one-time eval of python function now + + if (tree) { + newtree->type = PYFUNCTION; + newtree->pyvar = pyvar; + newtree->argcount = narg; + newtree->argvars = new int[narg]; + for (int iarg = 0; iarg < narg; iarg++) + newtree->argvars[iarg] = jvars[iarg]; + } else { + for (int iarg = 0; iarg < narg; iarg++) { + if (iarg == 0) internal_set(jvars[iarg],value1); + else if (iarg == 1) internal_set(jvars[iarg],value2); + else internal_set(jvars[iarg],values[iarg-2]); + } + argstack[nargstack++] = compute_equal(pyvar); + } + + delete[] jvars; } // delete stored args @@ -4377,7 +4457,7 @@ Region *Variable::region_function(char *id, int ivar) customize by adding a special function: sum(x),min(x),max(x),ave(x),trap(x),slope(x), gmask(x),rmask(x),grmask(x,y),next(x),is_file(x),is_os(x), - extract_setting(x),label2type(x,y),is_tpelabel(x,y) + extract_setting(x),label2type(x,y),is_typelabel(x,y) is_timeout() ------------------------------------------------------------------------- */ diff --git a/src/variable.h b/src/variable.h index c71d21ca63..1fe75de7de 100644 --- a/src/variable.h +++ b/src/variable.h @@ -123,9 +123,13 @@ class Variable : protected Pointers { Tree *first, *second; // ptrs further down tree for first 2 args Tree **extra; // ptrs further down tree for nextra args + int pyvar; // index of Python variable invoked as py_name() + int argcount; // # of args to associated Python function + int *argvars; // indices of internal variables for each arg + Tree() : array(nullptr), iarray(nullptr), barray(nullptr), selfalloc(0), ivalue(0), nextra(0), - region(nullptr), first(nullptr), second(nullptr), extra(nullptr) + region(nullptr), first(nullptr), second(nullptr), extra(nullptr), argvars(nullptr) { } }; From 76d0ead2455fc307c4677291ef7154a98c1a4a95 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 1 May 2025 12:41:48 -0600 Subject: [PATCH 02/23] doc pages for new Python function wrappers --- doc/src/Python_call.rst | 20 +++- doc/src/variable.rst | 91 ++++++++++++++---- examples/python/in.python.wrap | 46 +++++++++ examples/python/log.1May25.python.wrap.g++.1 | 99 ++++++++++++++++++++ examples/python/log.1May25.python.wrap.g++.4 | 99 ++++++++++++++++++++ 5 files changed, 333 insertions(+), 22 deletions(-) create mode 100644 examples/python/in.python.wrap create mode 100644 examples/python/log.1May25.python.wrap.g++.1 create mode 100644 examples/python/log.1May25.python.wrap.g++.4 diff --git a/doc/src/Python_call.rst b/doc/src/Python_call.rst index 796adfe1c0..338b7f85bb 100644 --- a/doc/src/Python_call.rst +++ b/doc/src/Python_call.rst @@ -5,18 +5,28 @@ LAMMPS has several commands which can be used to invoke Python code directly from an input script: * :doc:`python ` -* :doc:`variable python ` +* :doc:`python-style variables ` +* :doc:`equal-style and atom-style variables with formulas containing Python function wrappers ` * :doc:`fix python/invoke ` * :doc:`pair_style python ` -The :doc:`python ` command which can be used to define and -execute a Python function that you write the code for. The Python -function can also be assigned to a LAMMPS python-style variable via -the :doc:`variable ` command. Each time the variable is +The :doc:`python ` command can be used to define and execute a +Python function that you write the code for. The Python function can +also be assigned to a LAMMPS python-style variable via the +:doc:`variable ` command. Each time the variable is evaluated, either in the LAMMPS input script itself, or by another LAMMPS command that uses the variable, this will trigger the Python function to be invoked. +The Python function can also be referenced in the formula used to +define an :doc:`equal-style or atom-style variable `, using +the syntax for a :doc:`Python function wrapper `. This make +it easy to pass LAMMPS-related arguments to the Python function, as +well as to invoke it whenever the equal- or atom-style variable is +evaluated. For an atom-style variable it means the Python function +can be invoked once per atom, using per-atom properties as arguments +to the function. + The Python code for the function can be included directly in the input script or in an auxiliary file. The function can have arguments which are mapped to LAMMPS variables (also defined in the input script) and diff --git a/doc/src/variable.rst b/doc/src/variable.rst index ba3a5db35a..245755a371 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -73,7 +73,7 @@ Syntax is_typelabel(kind,label), is_timeout() feature functions = is_available(category,feature), is_active(category,feature), is_defined(category,id) - python function = py_varname(x,y,z,...) + python function wrapper = py_varname(x,y,z,...) atom value = id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] atom vector = id, mass, type, mol, radius, q, x, y, z, vx, vy, vz, fx, fy, fz custom atom property = i_name, d_name, i_name[i], d_name[i], i2_name[i], d2_name[i], i2_name[i][j], d2_name[i][j] @@ -545,9 +545,9 @@ is a valid (though strange) variable formula: Specifically, a formula can contain numbers, constants, thermo keywords, math operators, math functions, group functions, region -functions, special functions, feature functions, the python function, -atom values, atom vectors, custom atom properties, compute references, -fix references, and references to other variables. +functions, special functions, feature functions, python function +wrappers, atom values, atom vectors, custom atom properties, compute +references, fix references, and references to other variables. +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Number | 0.2, 100, 1.0e20, -15.4, etc | @@ -567,7 +567,8 @@ fix references, and references to other variables. | Special functions | sum(x), min(x), max(x), ave(x), trap(x), slope(x), sort(x), rsort(x), gmask(x), rmask(x), grmask(x,y), next(x), is_file(name), is_os(name), extract_setting(name), label2type(kind,label), is_typelabel(kind,label), is_timeout() | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Feature functions | is_available(category,feature), is_active(category,feature), is_defined(category,id) | -+------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| Python function | py_varname(x,y,z,...) | ++------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +Python function wrappers | py_varname(x,y,z,...) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Atom values | id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -1179,20 +1180,76 @@ variable name. ---------- -Python Function ------------------ +Python Function wrappers +------------------------ -NOTE: this needs work -explain why use this versus just reference a python variable -(b/c can pass args) -(b/c can use it in an atom-style varible) +A Python function wrapper enables the formula for an equal-style or +atom-style variable to invoke functions coded in Python. In the case +of an equal-style variable, the Python-coded function will be invoked +once. In the case of an atom-style variable, it can be invoked once +per atom, if one or more of its arguments include a per-atom quantity, +e.g. the position of an atom. As illustrated below, the reason to use +a Python function wrapper is to make it easy to pass LAMMPS-related +arguments to the Python-coded function associated with a python-style +variable. -Math operators are written in the usual way, where the "x" and "y" in -the examples can themselves be arbitrarily complex formulas, as in the -examples above. In this syntax, "x" and "y" can be scalar values or -per-atom vectors. For example, "ke/natoms" is the division of two -scalars, where "vy+vz" is the element-by-element sum of two per-atom -vectors of y and z velocities. +The syntax for defining a Python function wrapper is + +.. code-block:: LAMMPS + + py_varname(arg1,arg2,...argN) + +where *varname* is the name of a python-style variable which couples +to a Python-coded function. The function will be passed the zero or +more arguments listed in parentheses: *arg1*, *arg2*, ... *argN*. As +with Math Functions, each argument can itself be an arbitrarily +complex formula. + +A Python function wrapper can be used in the following manner by an +input script: + +.. code-block:: LAMMPS + + variable foo python truncate + python truncate return v_foo input 1 v_pyarg1 format fi here """ +def truncate(x): + return int(x) +""" + variable pyarg1 internal 0.0 + variable xtrunc atom py_foo(x) + variable ytrunc atom py_foo(y) + variable ztrunc atom py_foo(z) + dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + +The first two commands define a python-style variable *foo* and couple +it to the Python-coded function *truncate()* which takes a single +floating point argument, and returns its truncated integer value. In +this case, the Python code for truncate() is included in the *python* +command; it could also be contained in a file. See the :doc:`python +` command doc page for details. + +The *variable pyarg1* command defines an internal-style variable. It +MUST have the name pyarg1. If the Python function has *N* arguments, +*N* internal-style variables MUST be defined with names *pyarg1*, +*pyarg2*, ... *pyargN*. Note that multiple Python function wrappers +can use the same internal-style variables. + +The next three commands define atom-style variables *xtrunc*, +*ytrunc*, and *ztrunc*. Each of them include the same Python function +wrapper in their formula, with a different argument. The atom-style +variable *xtrunc* will invoke the python-style variable *foo*, which +will in turn invoke the Python-coded *truncate()* method. Because +*xtrunc* is an atom-style variable, and the argument *x* in the Python +function wrapper is a per-atom quantity (the x-coord of each atom), +each processor will invoke the *truncate()* method once per atom, for +the atoms it owns. When invoked for the Ith atom, the x-coord of the +Ith atom becomes the value of the *pyarg1* internal-style variable. +The call to the *truncate()* function uses the value of the *pyarg1* +variable as its first (and only) argument. + +The resulting per-atom vector for *xtrunc* will thus contain the +truncated x-coord of every atom in the system. The dump command +includes the truncated xyz coords for each atom in its output. ---------- diff --git a/examples/python/in.python.wrap b/examples/python/in.python.wrap new file mode 100644 index 0000000000..19ecec8f66 --- /dev/null +++ b/examples/python/in.python.wrap @@ -0,0 +1,46 @@ +# 3d Lennard-Jones melt with equal- and atom-style variables which +# use a Python function wrapper in their formulas + +variable x index 5 +variable y index 5 +variable z index 5 + +units lj +atom_style atomic + +lattice fcc 0.8442 +region box block 0 $x 0 $y 0 $z +create_box 1 box +create_atoms 1 box +mass 1 1.0 + +velocity all create 1.44 87287 loop geom + +pair_style lj/cut 2.5 +pair_coeff 1 1 1.0 1.0 2.5 + +neighbor 0.3 bin +neigh_modify delay 0 every 20 check no + +fix 1 all nve + +variable foo python truncate +python truncate return v_foo input 1 v_pyarg1 format fi here """ +def truncate(x): + return int(x) +""" +variable pyarg1 internal 0.0 + +variable scalar equal py_foo(4.5) +print "TRUNCATE ${scalar}" + +variable xtrunc atom py_foo(x) +variable ytrunc atom py_foo(y) +variable ztrunc atom py_foo(z) + +# examine dump file to see truncated xyz coords of each atom + +#dump 1 all custom 100 tmp.dump id x y z +dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + +run 100 diff --git a/examples/python/log.1May25.python.wrap.g++.1 b/examples/python/log.1May25.python.wrap.g++.1 new file mode 100644 index 0000000000..8038b600f3 --- /dev/null +++ b/examples/python/log.1May25.python.wrap.g++.1 @@ -0,0 +1,99 @@ +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-125-g7ca493917a-modified) +# 3d Lennard-Jones melt with equal- and atom-style variables which +# use a Python function wrapper in their formulas + +variable x index 5 +variable y index 5 +variable z index 5 + +units lj +atom_style atomic + +lattice fcc 0.8442 +Lattice spacing in x,y,z = 1.6795962 1.6795962 1.6795962 +region box block 0 $x 0 $y 0 $z +region box block 0 5 0 $y 0 $z +region box block 0 5 0 5 0 $z +region box block 0 5 0 5 0 5 +create_box 1 box +Created orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) + 1 by 1 by 1 MPI processor grid +create_atoms 1 box +Created 500 atoms + using lattice units in orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) + create_atoms CPU = 0.000 seconds +mass 1 1.0 + +velocity all create 1.44 87287 loop geom + +pair_style lj/cut 2.5 +pair_coeff 1 1 1.0 1.0 2.5 + +neighbor 0.3 bin +neigh_modify delay 0 every 20 check no + +fix 1 all nve + +variable foo python truncate +python truncate return v_foo input 1 v_pyarg1 format fi here """ +def truncate(x): + return int(x) +""" +variable pyarg1 internal 0.0 + +variable scalar equal py_foo(4.5) +print "TRUNCATE ${scalar}" +TRUNCATE 4 + +variable xtrunc atom py_foo(x) +variable ytrunc atom py_foo(y) +variable ztrunc atom py_foo(z) + +#dump 1 all custom 100 tmp.dump id x y z +dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + +run 100 +Generated 0 of 0 mixed pair_coeff terms from geometric mixing rule +Neighbor list info ... + update: every = 20 steps, delay = 0 steps, check = no + max neighbors/atom: 2000, page size: 100000 + master list distance cutoff = 2.8 + ghost atom cutoff = 2.8 + binsize = 1.4, bins = 6 6 6 + 1 neighbor lists, perpetual/occasional/extra = 1 0 0 + (1) pair lj/cut, perpetual + attributes: half, newton on + pair build: half/bin/atomonly/newton + stencil: half/bin/3d + bin: standard +Per MPI rank memory allocation (min/avg/max) = 2.644 | 2.644 | 2.644 Mbytes + Step Temp E_pair E_mol TotEng Press + 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 + 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 +Loop time of 0.0160255 on 1 procs for 100 steps with 500 atoms + +Performance: 2695709.610 tau/day, 6240.069 timesteps/s, 3.120 Matom-step/s +100.0% CPU use with 1 MPI tasks x no OpenMP threads + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Pair | 0.011326 | 0.011326 | 0.011326 | 0.0 | 70.67 +Neigh | 0.002924 | 0.002924 | 0.002924 | 0.0 | 18.25 +Comm | 0.00046255 | 0.00046255 | 0.00046255 | 0.0 | 2.89 +Output | 0.0010398 | 0.0010398 | 0.0010398 | 0.0 | 6.49 +Modify | 0.00020589 | 0.00020589 | 0.00020589 | 0.0 | 1.28 +Other | | 6.725e-05 | | | 0.42 + +Nlocal: 500 ave 500 max 500 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +Nghost: 1941 ave 1941 max 1941 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +Neighs: 18766 ave 18766 max 18766 min +Histogram: 1 0 0 0 0 0 0 0 0 0 + +Total # of neighbors = 18766 +Ave neighs/atom = 37.532 +Neighbor list builds = 5 +Dangerous builds not checked +Total wall time: 0:00:00 diff --git a/examples/python/log.1May25.python.wrap.g++.4 b/examples/python/log.1May25.python.wrap.g++.4 new file mode 100644 index 0000000000..3abf6d27fa --- /dev/null +++ b/examples/python/log.1May25.python.wrap.g++.4 @@ -0,0 +1,99 @@ +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-125-g7ca493917a-modified) +# 3d Lennard-Jones melt with equal- and atom-style variables which +# use a Python function wrapper in their formulas + +variable x index 5 +variable y index 5 +variable z index 5 + +units lj +atom_style atomic + +lattice fcc 0.8442 +Lattice spacing in x,y,z = 1.6795962 1.6795962 1.6795962 +region box block 0 $x 0 $y 0 $z +region box block 0 5 0 $y 0 $z +region box block 0 5 0 5 0 $z +region box block 0 5 0 5 0 5 +create_box 1 box +Created orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) + 1 by 2 by 2 MPI processor grid +create_atoms 1 box +Created 500 atoms + using lattice units in orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) + create_atoms CPU = 0.000 seconds +mass 1 1.0 + +velocity all create 1.44 87287 loop geom + +pair_style lj/cut 2.5 +pair_coeff 1 1 1.0 1.0 2.5 + +neighbor 0.3 bin +neigh_modify delay 0 every 20 check no + +fix 1 all nve + +variable foo python truncate +python truncate return v_foo input 1 v_pyarg1 format fi here """ +def truncate(x): + return int(x) +""" +variable pyarg1 internal 0.0 + +variable scalar equal py_foo(4.5) +print "TRUNCATE ${scalar}" +TRUNCATE 4 + +variable xtrunc atom py_foo(x) +variable ytrunc atom py_foo(y) +variable ztrunc atom py_foo(z) + +#dump 1 all custom 100 tmp.dump id x y z +dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + +run 100 +Generated 0 of 0 mixed pair_coeff terms from geometric mixing rule +Neighbor list info ... + update: every = 20 steps, delay = 0 steps, check = no + max neighbors/atom: 2000, page size: 100000 + master list distance cutoff = 2.8 + ghost atom cutoff = 2.8 + binsize = 1.4, bins = 6 6 6 + 1 neighbor lists, perpetual/occasional/extra = 1 0 0 + (1) pair lj/cut, perpetual + attributes: half, newton on + pair build: half/bin/atomonly/newton + stencil: half/bin/3d + bin: standard +Per MPI rank memory allocation (min/avg/max) = 2.609 | 2.609 | 2.609 Mbytes + Step Temp E_pair E_mol TotEng Press + 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 + 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 +Loop time of 0.00641075 on 4 procs for 100 steps with 500 atoms + +Performance: 6738684.275 tau/day, 15598.806 timesteps/s, 7.799 Matom-step/s +100.0% CPU use with 4 MPI tasks x no OpenMP threads + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Pair | 0.0028061 | 0.0028831 | 0.0029657 | 0.1 | 44.97 +Neigh | 0.00086635 | 0.00088279 | 0.00089739 | 0.0 | 13.77 +Comm | 0.0020095 | 0.0020768 | 0.0021521 | 0.1 | 32.40 +Output | 0.00041634 | 0.00042457 | 0.00043221 | 0.0 | 6.62 +Modify | 6.2967e-05 | 6.4188e-05 | 6.5205e-05 | 0.0 | 1.00 +Other | | 7.934e-05 | | | 1.24 + +Nlocal: 125 ave 126 max 123 min +Histogram: 1 0 0 0 0 0 1 0 0 2 +Nghost: 1085.75 ave 1090 max 1082 min +Histogram: 1 0 1 0 0 0 1 0 0 1 +Neighs: 4691.5 ave 4969 max 4368 min +Histogram: 1 0 0 0 1 0 0 1 0 1 + +Total # of neighbors = 18766 +Ave neighs/atom = 37.532 +Neighbor list builds = 5 +Dangerous builds not checked +Total wall time: 0:00:00 From a2a2c541b5ae299951e5985f778dfec682169c4d Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 1 May 2025 12:57:27 -0600 Subject: [PATCH 03/23] tweaks to code and doc page --- doc/src/variable.rst | 20 ++++++++++---------- src/variable.cpp | 12 ++++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/doc/src/variable.rst b/doc/src/variable.rst index 245755a371..96d3999787 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -177,7 +177,7 @@ simulation. When an input script line is encountered that defines a variable of style *equal* or *vector* or *atom* or *python* that contains a formula or links to Python code, the formula or Python code is NOT - immediately evaluated. Instead, it is evaulated aech time the + immediately evaluated. Instead, it is evaluated each time the variable is **used**. If you simply want to evaluate a formula in place you can use a so-called immediate variable. as described in the preceding note. Or see the section below about "Immediate @@ -568,7 +568,7 @@ references, fix references, and references to other variables. +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Feature functions | is_available(category,feature), is_active(category,feature), is_defined(category,id) | +------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -Python function wrappers | py_varname(x,y,z,...) | +| Python func wrappers | py_varname(x,y,z,...) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Atom values | id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -1212,14 +1212,14 @@ input script: variable foo python truncate python truncate return v_foo input 1 v_pyarg1 format fi here """ -def truncate(x): - return int(x) -""" - variable pyarg1 internal 0.0 - variable xtrunc atom py_foo(x) - variable ytrunc atom py_foo(y) - variable ztrunc atom py_foo(z) - dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + def truncate(x): + return int(x) + """ + variable pyarg1 internal 0.0 + variable xtrunc atom py_foo(x) + variable ytrunc atom py_foo(y) + variable ztrunc atom py_foo(z) + dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc The first two commands define a python-style variable *foo* and couple it to the Python-coded function *truncate()* which takes a single diff --git a/src/variable.cpp b/src/variable.cpp index 7b8ec6012a..36e50d5af5 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -3534,7 +3534,7 @@ double Variable::eval_tree(Tree *tree, int i) if (tree->type == SIGN) return (eval_tree(tree->first,i) >= 0.0) ? 1.0 : -1.0; // sign(eval_tree(tree->first,i)); - + if (tree->type == PYFUNCTION) { int narg = tree->argcount; for (int iarg = 0; iarg < narg; iarg++) { @@ -3546,7 +3546,7 @@ double Variable::eval_tree(Tree *tree, int i) arg = compute_equal(tree->pyvar); return arg; } - + if (tree->type == GMASK) { if (atom->mask[i] & tree->ivalue) return 1.0; else return 0.0; @@ -4137,7 +4137,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree double value = value1 + value2*(1.0-cos(omega*delta*update->dt)); argstack[nargstack++] = value; } - + } else if (strcmp(word,"sign") == 0) { if (narg != 1) print_var_error(FLERR,"Invalid math function in variable formula",ivar); @@ -4150,7 +4150,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree } else if (strstr(word,"py_") == word) { // text following py_ = python-style variable name - + int pyvar = find(&word[3]); if (style[pyvar] != PYTHON) print_var_error(FLERR,"Invalid python function variable name",ivar); @@ -4160,7 +4160,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree int *jvars = new int[narg]; char *internal_varname; - + for (int iarg = 0; iarg < narg; iarg++) { internal_varname = utils::strdup(fmt::format("pyarg{}", iarg+1)); jvars[iarg] = find(internal_varname); @@ -4173,7 +4173,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree // if tree: store python variable and arg info in tree for later eval // else: one-time eval of python function now - + if (tree) { newtree->type = PYFUNCTION; newtree->pyvar = pyvar; From 1fc13c491c69da12c5df76d3edba9fe432574a55 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 1 May 2025 13:16:50 -0600 Subject: [PATCH 04/23] change PYFUNCTION to PYWRAPPER in code --- src/variable.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/variable.cpp b/src/variable.cpp index 36e50d5af5..bbd976ad6b 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -81,7 +81,7 @@ enum{DONE,ADD,SUBTRACT,MULTIPLY,DIVIDE,CARAT,MODULO,UNARY, RAMP,STAGGER,LOGFREQ,LOGFREQ2,LOGFREQ3,STRIDE,STRIDE2, VDISPLACE,SWIGGLE,CWIGGLE,SIGN,GMASK,RMASK, GRMASK,IS_ACTIVE,IS_DEFINED,IS_AVAILABLE,IS_FILE,EXTRACT_SETTING, - PYFUNCTION, + PYWRAPPER, VALUE,ATOMARRAY,TYPEARRAY,INTARRAY,BIGINTARRAY,VECTORARRAY}; // customize by adding a special function @@ -2369,6 +2369,7 @@ double Variable::evaluate(char *str, Tree **tree, int ivar) // ---------------- // math or group/region or special or feature function + // math_function() includes Python function wrapper // ---------------- if (str[i] == '(') { @@ -3184,7 +3185,7 @@ double Variable::collapse_tree(Tree *tree) return tree->value; } - if (tree->type == PYFUNCTION) { + if (tree->type == PYWRAPPER) { int narg = tree->argcount; int *argvars = tree->argvars; double arg; @@ -3535,7 +3536,7 @@ double Variable::eval_tree(Tree *tree, int i) if (tree->type == SIGN) return (eval_tree(tree->first,i) >= 0.0) ? 1.0 : -1.0; // sign(eval_tree(tree->first,i)); - if (tree->type == PYFUNCTION) { + if (tree->type == PYWRAPPER) { int narg = tree->argcount; for (int iarg = 0; iarg < narg; iarg++) { if (iarg == 0) arg = eval_tree(tree->first,i); @@ -4172,10 +4173,10 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree } // if tree: store python variable and arg info in tree for later eval - // else: one-time eval of python function now + // else: one-time eval of python-coded function now via python variable if (tree) { - newtree->type = PYFUNCTION; + newtree->type = PYWRAPPER; newtree->pyvar = pyvar; newtree->argcount = narg; newtree->argvars = new int[narg]; From 9b36c58eb2e527576d162cb10c6fc803e9d5070f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 1 May 2025 16:03:11 -0400 Subject: [PATCH 05/23] fix spelling and docbuild issues --- doc/src/variable.rst | 4 ++-- doc/utils/sphinx-config/false_positives.txt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/src/variable.rst b/doc/src/variable.rst index 96d3999787..a51901fbce 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -558,7 +558,7 @@ references, fix references, and references to other variables. +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Math operators | (), -x, x+y, x-y, x\*y, x/y, x\^y, x%y, x == y, x != y, x < y, x <= y, x > y, x >= y, x && y, x \|\| y, x \|\^ y, !x | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -| Math functions | sqrt(x), exp(x), ln(x), log(x), abs(x), sign(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ternary(x,y,z), ramp(x,y), stagger(x,y), logfreq(x,y,z), logfreq2(x,y,z), logfreq3(x,y,z), stride(x,y,z), stride2(x,y,z,a,b,c), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) | +| Math functions | sqrt(x), exp(x), ln(x), log(x), abs(x), sign(x), sin(x), cos(x), tan(x), asin(x), acos(x), atan(x), atan2(y,x), random(x,y,z), normal(x,y,z), ceil(x), floor(x), round(x), ternary(x,y,z), ramp(x,y), stagger(x,y), logfreq(x,y,z), logfreq2(x,y,z), logfreq3(x,y,z), stride(x,y,z), stride2(x,y,z,a,b,c), vdisplace(x,y), swiggle(x,y,z), cwiggle(x,y,z) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Group functions | count(ID), mass(ID), charge(ID), xcm(ID,dim), vcm(ID,dim), fcm(ID,dim), bound(ID,dir), gyration(ID), ke(ID), angmom(ID,dim), torque(ID,dim), inertia(ID,dimdim), omega(ID,dim) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ @@ -567,7 +567,7 @@ references, fix references, and references to other variables. | Special functions | sum(x), min(x), max(x), ave(x), trap(x), slope(x), sort(x), rsort(x), gmask(x), rmask(x), grmask(x,y), next(x), is_file(name), is_os(name), extract_setting(name), label2type(kind,label), is_typelabel(kind,label), is_timeout() | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Feature functions | is_available(category,feature), is_active(category,feature), is_defined(category,id) | -+------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ ++------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Python func wrappers | py_varname(x,y,z,...) | +------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Atom values | id[i], mass[i], type[i], mol[i], x[i], y[i], z[i], vx[i], vy[i], vz[i], fx[i], fy[i], fz[i], q[i] | diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index 577d2c23de..bb6b960a0d 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -3097,6 +3097,7 @@ Pxy pxz py Py +pyargs pydir pylammps PyLammps @@ -4043,6 +4044,7 @@ Vanduyfhuys varargs varavg variational +varname Varshalovich Varshney vashishta From 1f85dd40930f486f111c1936f3d9da9f42ea82ec Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Tue, 6 May 2025 16:28:10 -0600 Subject: [PATCH 06/23] update comment in variable.cpp --- src/variable.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/variable.cpp b/src/variable.cpp index bbd976ad6b..7843b15293 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -1425,6 +1425,7 @@ void Variable::copy(int narg, char **from, char **to) sin(x),cos(x),tan(x),asin(x),atan2(y,x),... group function = count(group), mass(group), xcm(group,x), ... special function = sum(x),min(x), ... + python function wrapper = py_varname(x,y,z,...) atom value = x[i], y[i], vx[i], ... atom vector = x, y, vx, ... custom atom property = i/d_name, i/d_name[i], i/d2_name[i], i/d2_name[i][j] From 06616c5ff30332c1c08db509ca3476c9354b202c Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 15 May 2025 16:37:41 -0600 Subject: [PATCH 07/23] change how internal vars are defined by various commands, enable Python class to return a numeric value directly (for speed) --- src/PYTHON/python_impl.cpp | 146 ++++++++++++++++++++++++++++----- src/PYTHON/python_impl.h | 6 +- src/compute_angle_local.cpp | 5 +- src/compute_bond_local.cpp | 5 +- src/compute_dihedral_local.cpp | 5 +- src/create_atoms.cpp | 18 ++-- src/fix_deposit.cpp | 15 +++- src/lmppython.cpp | 17 +++- src/lmppython.h | 10 ++- src/variable.cpp | 129 ++++++++++++++++++++--------- src/variable.h | 2 + src/write_dump.cpp | 2 +- 12 files changed, 278 insertions(+), 82 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index e4ec8d74d8..885d578fe1 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -62,6 +62,7 @@ using namespace LAMMPS_NS; enum { NONE, INT, DOUBLE, STRING, PTR }; +enum { VALUE, VARIABLE, INTERNALVAR }; /* ---------------------------------------------------------------------- */ @@ -174,7 +175,17 @@ void PythonImpl::command(int narg, char **arg) arg[0], pfuncs[ifunc].ovarname, pfuncs[ifunc].name); } - invoke_function(ifunc, str); + // NOTE to Richard - if this function returns a value, + // I don't believe it will be accessible to the input script + // b/c that only occurs if Variable::retreive() or Variable::compute_equal() is called + // so if a Python function with a return is invoked this way, + // it might be better to issue an error or warning ? + // i.e. it only make sense to call a setup-style kind of Python function this way + // one with no return value + // it also means there is no need to call Variable::pythonstyle() here + // or even define the pythonstyle() method in Variable ? + + invoke_function(ifunc, str, nullptr); return; } @@ -317,7 +328,7 @@ void PythonImpl::command(int narg, char **arg) /* ------------------------------------------------------------------ */ -void PythonImpl::invoke_function(int ifunc, char *result) +void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) { PyUtils::GIL lock; PyObject *pValue; @@ -336,27 +347,33 @@ void PythonImpl::invoke_function(int ifunc, char *result) for (int i = 0; i < ninput; i++) { int itype = pfuncs[ifunc].itype[i]; if (itype == INT) { - if (pfuncs[ifunc].ivarflag[i]) { + if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) error->all(FLERR, "Could not evaluate Python function {} input variable: {}", pfuncs[ifunc].name, pfuncs[ifunc].svalue[i]); pValue = PY_INT_FROM_LONG(PY_LONG_FROM_STRING(str)); + } else if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { + double value = input->variable->compute_equal(pfuncs[ifunc].internal_var[i]); + pValue = PyLong_FromDouble(value); } else { pValue = PY_INT_FROM_LONG(pfuncs[ifunc].ivalue[i]); } } else if (itype == DOUBLE) { - if (pfuncs[ifunc].ivarflag[i]) { + if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) error->all(FLERR, "Could not evaluate Python function {} input variable: {}", pfuncs[ifunc].name, pfuncs[ifunc].svalue[i]); pValue = PyFloat_FromDouble(std::stod(str)); + } else if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { + double value = input->variable->compute_equal(pfuncs[ifunc].internal_var[i]); + pValue = PyFloat_FromDouble(value); } else { pValue = PyFloat_FromDouble(pfuncs[ifunc].dvalue[i]); } } else if (itype == STRING) { - if (pfuncs[ifunc].ivarflag[i]) { + if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) error->all(FLERR, "Could not evaluate Python function {} input variable: {}", @@ -385,17 +402,24 @@ void PythonImpl::invoke_function(int ifunc, char *result) } // function returned a value - // assign it to result string stored by python-style variable - // or if user specified a length, assign it to longstr + // if result is non-NULL, assign to result string stored by python-style variable + // or if value is string and user specified a length, assign it to longstr + // if dvalue is non-NULL, assign numeric value directly to dvalue if (pfuncs[ifunc].noutput) { int otype = pfuncs[ifunc].otype; if (otype == INT) { - auto value = fmt::format("{}", PY_INT_AS_LONG(pValue)); - strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); + if (dvalue) *dvalue = (double) PY_INT_AS_LONG(pValue); + else { + auto value = fmt::format("{}", PY_INT_AS_LONG(pValue)); + strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); + } } else if (otype == DOUBLE) { - auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); - strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); + if (*dvalue) *dvalue = PyFloat_AsDouble(pValue); + else { + auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); + strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); + } } else if (otype == STRING) { const char *pystr = PyUnicode_AsUTF8(pValue); if (pfuncs[ifunc].longstr) @@ -416,10 +440,22 @@ int PythonImpl::find(const char *name) return -1; } -/* ------------------------------------------------------------------ */ +/* --------------------------------------------------------------------- + called by Variable class when a python-style variable is evaluated + this will call invoke_function() in this class + either via Variable::retrieve() or Variable::equalstyle + retrieve calls with numeric = 0, equalstyle with numeric = 1 + ensure name matches a Python function + ensure the Python function produces an output + ensure the Python function outputs to the matching python-style variable + ensure a string is returned only if retrieve() is the caller +--------------------------------------------------------------------- */ -int PythonImpl::variable_match(const char *name, const char *varname, int numeric) +int PythonImpl::function_match(const char *name, const char *varname, int numeric) { + // NOTE to Richard - any reason not to put error messages here instead of in Variable class ? + // error messages appear 3x in Variable + int ifunc = find(name); if (ifunc < 0) return -1; if (pfuncs[ifunc].noutput == 0) return -2; @@ -428,6 +464,50 @@ int PythonImpl::variable_match(const char *name, const char *varname, int numeri return ifunc; } +/* --------------------------------------------------------------------- + called by Variable class when evaluating a Python wrapper function + which will call invoke_function() + either via equal-style or atom-style variable formula + the latter calls invoke_function() once per atom + same error checks as function_match() plus 2 new ones + ensure match of number of Python function args mapped to internal-style variables + ensure each internal-style variable still exists + must check now in case user input script deleted variables between runs + which could invalidate indices set in create_event() + other classes avoid this issue by setting variable indices in their init() method +--------------------------------------------------------------------- */ + +int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, int *argvars) +{ + // NOTE to Richard - any reason not to put 2 extra error messages here instead of in Variable class ? + // only this class knows the name of the missing internal var, so can generate better error message + + int ifunc = function_match(name, varname, 1); + if (ifunc < 0) return ifunc; + + int internal_count = 0; + for (int i = 0; i < pfuncs[ifunc].ninput; i++) + if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) internal_count++; + if (internal_count != narg) return -5; + + // set argvars of internal-style variables for use by Variable class + // in Python wrapper functions + // also set internal_var for use by invoke_function() + // so that invoke_function() is as fast as possible for args which are internal-style vars + + int j = 0; + for (int i = 0; i < pfuncs[ifunc].ninput; i++) + if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { + int ivar = input->variable->find(pfuncs[ifunc].svalue[i]); + if (ivar < 0) return -6; + pfuncs[ifunc].internal_var[i] = ivar; + argvars[j++] = ivar; + } + + return ifunc; +} + + /* ------------------------------------------------------------------ */ char *PythonImpl::long_string(int ifunc) @@ -469,6 +549,7 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon pfuncs[ifunc].ivalue = new int[ninput]; pfuncs[ifunc].dvalue = new double[ninput]; pfuncs[ifunc].svalue = new char *[ninput]; + pfuncs[ifunc].internal_var = new int[ninput]; for (int i = 0; i < ninput; i++) { pfuncs[ifunc].svalue[i] = nullptr; @@ -476,32 +557,56 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon if (type == 'i') { pfuncs[ifunc].itype[i] = INT; if (utils::strmatch(istr[i], "^v_")) { - pfuncs[ifunc].ivarflag[i] = 1; + pfuncs[ifunc].ivarflag[i] = VARIABLE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 2); + } else if (utils::strmatch(istr[i], "^iv_")) { + pfuncs[ifunc].ivarflag[i] = INTERNALVAR; + pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 3); + char *vname = pfuncs[ifunc].svalue[i]; + int ivar = input->variable->find(vname); + if (ivar < 0) { // create internal variable if does not exist + input->variable->internal_create(vname,0.0); + ivar = input->variable->find(vname); + } + if (!input->variable->internalstyle(ivar)) + error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", vname); } else { - pfuncs[ifunc].ivarflag[i] = 0; + pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].ivalue[i] = utils::inumeric(FLERR, istr[i], false, lmp); } } else if (type == 'f') { pfuncs[ifunc].itype[i] = DOUBLE; if (utils::strmatch(istr[i], "^v_")) { - pfuncs[ifunc].ivarflag[i] = 1; + pfuncs[ifunc].ivarflag[i] = VARIABLE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 2); + } else if (utils::strmatch(istr[i], "^iv_")) { + pfuncs[ifunc].ivarflag[i] = INTERNALVAR; + pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 3); + char *vname = pfuncs[ifunc].svalue[i]; + int ivar = input->variable->find(vname); + if (ivar < 0) { // create internal variable if does not exist + input->variable->internal_create(vname,0.0); + ivar = input->variable->find(vname); + } + if (!input->variable->internalstyle(ivar)) + error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", vname); } else { - pfuncs[ifunc].ivarflag[i] = 0; + pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].dvalue[i] = utils::numeric(FLERR, istr[i], false, lmp); } } else if (type == 's') { pfuncs[ifunc].itype[i] = STRING; if (utils::strmatch(istr[i], "^v_")) { - pfuncs[ifunc].ivarflag[i] = 1; + pfuncs[ifunc].ivarflag[i] = VARIABLE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 2); + } else if (utils::strmatch(istr[i], "^iv_")) { + error->all(FLERR, "Input argument {} cannot be internal variable with string format", istr[i]); } else { - pfuncs[ifunc].ivarflag[i] = 0; + pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i]); } } else if (type == 'p') { - pfuncs[ifunc].ivarflag[i] = 0; + pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].itype[i] = PTR; if (strcmp(istr[i], "SELF") != 0) error->all(FLERR, "Invalid python command"); @@ -574,6 +679,7 @@ void PythonImpl::deallocate(int i) delete[] pfuncs[i].dvalue; for (int j = 0; j < pfuncs[i].ninput; j++) delete[] pfuncs[i].svalue[j]; delete[] pfuncs[i].svalue; + delete[] pfuncs[i].internal_var; delete[] pfuncs[i].ovarname; delete[] pfuncs[i].longstr; } diff --git a/src/PYTHON/python_impl.h b/src/PYTHON/python_impl.h index 667980f8aa..3ead92f6f2 100644 --- a/src/PYTHON/python_impl.h +++ b/src/PYTHON/python_impl.h @@ -25,9 +25,10 @@ class PythonImpl : protected Pointers, public PythonInterface { PythonImpl(class LAMMPS *); ~PythonImpl() override; void command(int, char **) override; - void invoke_function(int, char *) override; + void invoke_function(int, char *, double *) override; int find(const char *) override; - int variable_match(const char *, const char *, int) override; + int function_match(const char *, const char *, int) override; + int wrapper_match(const char *, const char *, int, int *) override; char *long_string(int) override; int execute_string(char *) override; int execute_file(char *) override; @@ -44,6 +45,7 @@ class PythonImpl : protected Pointers, public PythonInterface { int *ivalue; double *dvalue; char **svalue; + int *internal_var; // stores per-arg index of internal variable int otype; char *ovarname; char *longstr; diff --git a/src/compute_angle_local.cpp b/src/compute_angle_local.cpp index b4a9334416..af20bb5a9e 100644 --- a/src/compute_angle_local.cpp +++ b/src/compute_angle_local.cpp @@ -108,7 +108,10 @@ ComputeAngleLocal::ComputeAngleLocal(LAMMPS *lmp, int narg, char **arg) : if (tstr) { tvar = input->variable->find(tstr); - if (tvar < 0) error->all(FLERR, "Variable name for compute angle/local does not exist"); + if (tvar < 0) { + input->variable->internal_create(tstr,0.0); + tvar = input->variable->find(tstr); + } if (!input->variable->internalstyle(tvar)) error->all(FLERR, "Variable for compute angle/local is invalid style"); } diff --git a/src/compute_bond_local.cpp b/src/compute_bond_local.cpp index e9632d254f..4adf5d9ff8 100644 --- a/src/compute_bond_local.cpp +++ b/src/compute_bond_local.cpp @@ -154,7 +154,10 @@ ComputeBondLocal::ComputeBondLocal(LAMMPS *lmp, int narg, char **arg) : if (dstr) { dvar = input->variable->find(dstr); - if (dvar < 0) error->all(FLERR, "Variable name for compute bond/local does not exist"); + if (dvar < 0) { + input->variable->internal_create(dstr,0.0); + dvar = input->variable->find(dstr); + } if (!input->variable->internalstyle(dvar)) error->all(FLERR, "Variable for compute bond/local is invalid style"); } diff --git a/src/compute_dihedral_local.cpp b/src/compute_dihedral_local.cpp index bd1e389e85..f19a7b48be 100644 --- a/src/compute_dihedral_local.cpp +++ b/src/compute_dihedral_local.cpp @@ -102,7 +102,10 @@ ComputeDihedralLocal::ComputeDihedralLocal(LAMMPS *lmp, int narg, char **arg) : if (pstr) { pvar = input->variable->find(pstr); - if (pvar < 0) error->all(FLERR, "Variable name for compute dihedral/local does not exist"); + if (pvar < 0) { + input->variable->internal_create(pstr,0.0); + pvar = input->variable->find(pstr); + } if (!input->variable->internalstyle(pvar)) error->all(FLERR, "Variable for compute dihedral/local is invalid style"); } diff --git a/src/create_atoms.cpp b/src/create_atoms.cpp index 93f4519d3c..d9a32dceed 100644 --- a/src/create_atoms.cpp +++ b/src/create_atoms.cpp @@ -392,22 +392,28 @@ void CreateAtoms::command(int narg, char **arg) if (xstr) { xvar = input->variable->find(xstr); - if (xvar < 0) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms does not exist", xstr); + if (xvar < 0) { + input->variable->internal_create(xstr,0.0); + xvar = input->variable->find(xstr); + } if (!input->variable->internalstyle(xvar)) error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", xstr); } if (ystr) { yvar = input->variable->find(ystr); - if (yvar < 0) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms does not exist", ystr); + if (yvar < 0) { + input->variable->internal_create(ystr,0.0); + yvar = input->variable->find(ystr); + } if (!input->variable->internalstyle(yvar)) error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", ystr); } if (zstr) { zvar = input->variable->find(zstr); - if (zvar < 0) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms does not exist", zstr); + if (zvar < 0) { + input->variable->internal_create(zstr,0.0); + zvar = input->variable->find(zstr); + } if (!input->variable->internalstyle(zvar)) error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", zstr); } diff --git a/src/fix_deposit.cpp b/src/fix_deposit.cpp index c9d09fa8b7..593ae4a76e 100644 --- a/src/fix_deposit.cpp +++ b/src/fix_deposit.cpp @@ -868,19 +868,28 @@ void FixDeposit::options(int narg, char **arg) if (xstr) { xvar = input->variable->find(xstr); - if (xvar < 0) error->all(FLERR, "Variable {} for fix deposit does not exist", xstr); + if (xvar < 0) { + input->variable->internal_create(xstr,0.0); + xvar = input->variable->find(xstr); + } if (!input->variable->internalstyle(xvar)) error->all(FLERR, "Variable for fix deposit is invalid style"); } if (ystr) { yvar = input->variable->find(ystr); - if (yvar < 0) error->all(FLERR, "Variable {} for fix deposit does not exist", ystr); + if (yvar < 0) { + input->variable->internal_create(ystr,0.0); + yvar = input->variable->find(ystr); + } if (!input->variable->internalstyle(yvar)) error->all(FLERR, "Variable for fix deposit is invalid style"); } if (zstr) { zvar = input->variable->find(zstr); - if (zvar < 0) error->all(FLERR, "Variable {} for fix deposit does not exist", zstr); + if (zvar < 0) { + input->variable->internal_create(zstr,0.0); + zvar = input->variable->find(zstr); + } if (!input->variable->internalstyle(zvar)) error->all(FLERR, "Variable for fix deposit is invalid style"); } diff --git a/src/lmppython.cpp b/src/lmppython.cpp index b3c52111fe..6a8bc1f5a2 100644 --- a/src/lmppython.cpp +++ b/src/lmppython.cpp @@ -67,10 +67,10 @@ void Python::command(int narg, char **arg) /* ------------------------------------------------------------------ */ -void Python::invoke_function(int ifunc, char *result) +void Python::invoke_function(int ifunc, char *result, double *dvalue) { init(); - impl->invoke_function(ifunc, result); + impl->invoke_function(ifunc, result, dvalue); } /* ------------------------------------------------------------------ */ @@ -83,10 +83,19 @@ int Python::find(const char *name) /* ------------------------------------------------------------------ */ -int Python::variable_match(const char *name, const char *varname, int numeric) +int Python::function_match(const char *name, const char *varname, int numeric) { init(); - return impl->variable_match(name, varname, numeric); + return impl->function_match(name, varname, numeric); +} + +/* ------------------------------------------------------------------ */ + +int Python::wrapper_match(const char *name, const char *varname, + int narg, int *argvars) +{ + init(); + return impl->wrapper_match(name, varname, narg, argvars); } /* ------------------------------------------------------------------ */ diff --git a/src/lmppython.h b/src/lmppython.h index d961e1b046..23137b7ecb 100644 --- a/src/lmppython.h +++ b/src/lmppython.h @@ -22,9 +22,10 @@ class PythonInterface { public: virtual ~PythonInterface() noexcept(false) {} virtual void command(int, char **) = 0; - virtual void invoke_function(int, char *) = 0; + virtual void invoke_function(int, char *, double *) = 0; virtual int find(const char *) = 0; - virtual int variable_match(const char *, const char *, int) = 0; + virtual int function_match(const char *, const char *, int) = 0; + virtual int wrapper_match(const char *, const char *, int, int *) = 0; virtual char *long_string(int ifunc) = 0; virtual int execute_string(char *) = 0; virtual int execute_file(char *) = 0; @@ -37,9 +38,10 @@ class Python : protected Pointers { ~Python() override; void command(int, char **); - void invoke_function(int, char *); + void invoke_function(int, char *, double *); int find(const char *); - int variable_match(const char *, const char *, int); + int function_match(const char *, const char *, int); + int wrapper_match(const char *, const char *, int, int *); char *long_string(int ifunc); int execute_string(char *); int execute_file(char *); diff --git a/src/variable.cpp b/src/variable.cpp index 7843b15293..c8df2e0266 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -117,6 +117,7 @@ Variable::Variable(LAMMPS *lmp) : Pointers(lmp) num = nullptr; which = nullptr; pad = nullptr; + pyindex = nullptr; reader = nullptr; data = nullptr; dvalue = nullptr; @@ -163,6 +164,7 @@ Variable::~Variable() memory->destroy(num); memory->destroy(which); memory->destroy(pad); + memory->destroy(pyindex); memory->sfree(reader); memory->sfree(data); memory->sfree(dvalue); @@ -960,9 +962,27 @@ int Variable::equalstyle(int ivar) if (style[ivar] == EQUAL || style[ivar] == TIMER || style[ivar] == INTERNAL) return 1; if (style[ivar] == PYTHON) { - int ifunc = python->variable_match(data[ivar][0],names[ivar],1); - if (ifunc < 0) return 0; - else return 1; + pyindex[ivar] = python->function_match(data[ivar][0],names[ivar],1); + if (pyindex[ivar] < 0) { + int ierror = pyindex[ivar]; + if (ierror == -1) { + error->all(FLERR, "Python function {} specified by variable {} not found", + data[ivar][0], names[ivar]); + } else if (ierror == -2) { + error->all(FLERR, "Python function {} for variable {} does not return a value", + data[ivar][0], names[ivar]); + } else if (ierror == -3) { + error->all(FLERR, "Python function {} and variable {} do not not link to each other", + data[ivar][0], names[ivar]); + } else if (ierror == -4) { + error->all(FLERR, "Python function {} for variable {} returns a string", + data[ivar][0], names[ivar]); + } else { + error->all(FLERR, "Unknown error linking Python function {} to variable {}", + data[ivar][0],names[ivar]); + } + return 0; + } else return 1; } return 0; } @@ -990,7 +1010,7 @@ int Variable::vectorstyle(int ivar) } /* ---------------------------------------------------------------------- - check if variable with name is PYTHON and matches funcname + check if variable with name is PYTHON style and matches funcname called by Python class before it invokes a Python function return data storage so Python function can return a value for this variable return nullptr if not a match @@ -1007,7 +1027,7 @@ char *Variable::pythonstyle(char *name, char *funcname) /* ---------------------------------------------------------------------- return 1 if variable is INTERNAL style, 0 if not - this is checked before call to internal_set() to assure it can be set + this is checked before call to internal_set() to ensure it can be set ------------------------------------------------------------------------- */ int Variable::internalstyle(int ivar) @@ -1083,23 +1103,23 @@ char *Variable::retrieve(const char *name) str = data[ivar][1] = utils::strdup(result); } else if (style[ivar] == PYTHON) { - int ifunc = python->variable_match(data[ivar][0],name,0); + int ifunc = python->function_match(data[ivar][0],name,0); if (ifunc < 0) { if (ifunc == -1) { - error->all(FLERR, "Could not find Python function {} linked to variable {}", + error->all(FLERR, "Python function {} specified by variable {} not found", data[ivar][0], name); } else if (ifunc == -2) { - error->all(FLERR, "Python function {} for variable {} does not have a return value", + error->all(FLERR, "Python function {} for variable {} does not return a value", data[ivar][0], name); } else if (ifunc == -3) { - error->all(FLERR,"Python variable {} does not match variable name registered with " - "Python function {}", name, data[ivar][0]); + error->all(FLERR, "Python function {} and variable {} do not not link to each other", + data[ivar][0], name); } else { - error->all(FLERR, "Unknown error verifying function {} linked to python style variable {}", + error->all(FLERR, "Unknown error linking Python function {} to variable {}", data[ivar][0],name); } } - python->invoke_function(ifunc,data[ivar][1]); + python->invoke_function(ifunc,data[ivar][1],nullptr); str = data[ivar][1]; // if Python func returns a string longer than VALUELENGTH @@ -1158,17 +1178,7 @@ double Variable::compute_equal(int ivar) if (style[ivar] == EQUAL) value = evaluate(data[ivar][0],nullptr,ivar); else if (style[ivar] == TIMER) value = dvalue[ivar]; else if (style[ivar] == INTERNAL) value = dvalue[ivar]; - else if (style[ivar] == PYTHON) { - int ifunc = python->find(data[ivar][0]); - if (ifunc < 0) - print_var_error(FLERR,fmt::format("cannot find python function {}",data[ivar][0]),ivar); - python->invoke_function(ifunc,data[ivar][1]); - try { - value = std::stod(data[ivar][1]); - } catch (std::exception &e) { - print_var_error(FLERR,"has an invalid value", ivar); - } - } + else if (style[ivar] == PYTHON) python->invoke_function(pyindex[ivar],nullptr,&value); // round to zero on underflow if (fabs(value) < std::numeric_limits::min()) value = 0.0; @@ -1335,6 +1345,30 @@ void Variable::internal_set(int ivar, double value) dvalue[ivar] = value; } +/* ---------------------------------------------------------------------- + create an INTERNAL style variable with name, set to value +------------------------------------------------------------------------- */ + +void Variable::internal_create(char *name, double value) +{ + if (find(name) >= 0) + error->all(FLERR,"Creation of internal-style variable {} which already exists", name); + + if (nvar == maxvar) grow(); + style[nvar] = INTERNAL; + num[nvar] = 1; + which[nvar] = 0; + pad[nvar] = 0; + data[nvar] = new char *[num[nvar]]; + data[nvar][0] = new char[VALUELENGTH]; + dvalue[nvar] = value; + + if (!utils::is_id(name)) + error->all(FLERR, "Variable name '{}' must have only letters, numbers, or underscores", name); + names[nvar] = utils::strdup(name); + nvar++; +} + /* ---------------------------------------------------------------------- remove Nth variable from list and compact list delete reader explicitly if it exists @@ -1382,6 +1416,7 @@ void Variable::grow() memory->grow(num,maxvar,"var:num"); memory->grow(which,maxvar,"var:which"); memory->grow(pad,maxvar,"var:pad"); + memory->grow(pyindex,maxvar,"var:pyindex"); reader = (VarReader **) memory->srealloc(reader,maxvar*sizeof(VarReader *),"var:reader"); @@ -4146,33 +4181,49 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree if (tree) newtree->type = SIGN; else argstack[nargstack++] = (value1 >= 0.0) ? 1.0 : -1.0; // sign(value1); - // Python function tied to varname - // narg arguments tied to internal variables pyarg1, pyarg2, ... pyargN + // Python wrapper function tied to python-style variable + // text following py_ = python-style variable name tied to Python function + // narg arguments are tied to internal variables defined by python command } else if (strstr(word,"py_") == word) { - // text following py_ = python-style variable name + // pyvar = index of python-style variable which invokes Python function int pyvar = find(&word[3]); if (style[pyvar] != PYTHON) print_var_error(FLERR,"Invalid python function variable name",ivar); - // check for existence of narg internal variables with names pyarg1 to pyargN - // store their indices in jvars + // check that wrapper matches Python function + // jvars = returned indices of narg internal variables used by Python function int *jvars = new int[narg]; - char *internal_varname; - - for (int iarg = 0; iarg < narg; iarg++) { - internal_varname = utils::strdup(fmt::format("pyarg{}", iarg+1)); - jvars[iarg] = find(internal_varname); - if (jvars[iarg] < 0) - print_var_error(FLERR,"Invalid python function arg in variable formula",ivar); - if (!internalstyle(jvars[iarg])) - print_var_error(FLERR,"Invalid python function arg in variable formula",ivar); - delete[] internal_varname; + pyindex[pyvar] = python->wrapper_match(data[pyvar][0],names[pyvar],narg,jvars); + if (pyindex[pyvar] < 0) { + int ierror = pyindex[pyvar]; + if (ierror == -1) { + error->all(FLERR, "Python function {} specified by variable {} not found", + data[ivar][0], names[ivar]); + } else if (ierror == -2) { + error->all(FLERR, "Python function {} for variable {} does not return a value", + data[ivar][0], names[ivar]); + } else if (ierror == -3) { + error->all(FLERR, "Python function {} and variable {} do not not link to each other", + data[ivar][0], names[ivar]); + } else if (ierror == -4) { + error->all(FLERR, "Python function {} for variable {} returns a string", + data[ivar][0], names[ivar]); + } else if (ierror == -5) { + error->all(FLERR, "Python function {} does not use {} internal variable args", + data[ivar][0], narg); + } else if (ierror == -6) { + error->all(FLERR,"Python function {} cannot find an internal variable", + data[ivar][0]); + } else { + error->all(FLERR, "Unknown error linking Python function {} to variable {}", + data[ivar][0],names[ivar]); + } } - + // if tree: store python variable and arg info in tree for later eval // else: one-time eval of python-coded function now via python variable diff --git a/src/variable.h b/src/variable.h index 1fe75de7de..25afe55e1b 100644 --- a/src/variable.h +++ b/src/variable.h @@ -49,6 +49,7 @@ class Variable : protected Pointers { void compute_atom(int, int, double *, int, int); int compute_vector(int, double **); void internal_set(int, double); + void internal_create(char *, double); tagint int_between_brackets(char *&, int); double evaluate_boolean(char *); @@ -87,6 +88,7 @@ class Variable : protected Pointers { int *num; // # of values for each variable int *which; // next available value for each variable int *pad; // 1 = pad loop/uloop variables with 0s, 0 = no pad + int *pyindex; // indices to Python funcs for python-style vars class VarReader **reader; // variable that reads from file char ***data; // str value of each variable's values double *dvalue; // single numeric value for internal variables diff --git a/src/write_dump.cpp b/src/write_dump.cpp index 71d955dc1f..d0918ec39f 100644 --- a/src/write_dump.cpp +++ b/src/write_dump.cpp @@ -85,7 +85,7 @@ void WriteDump::command(int narg, char **arg) if (strcmp(arg[1], "image") == 0) (dynamic_cast(dump))->multifile_override = 1; if (strcmp(arg[1], "cfg") == 0) (dynamic_cast(dump))->multifile_override = 1; if ((update->first_update == 0) && (comm->me == 0) && (noinitwarn == 0)) - error->warning(FLERR, "Calling write_dump before a full system init."); + error->warning(FLERR, "Calling write_dump before a full system init"); dump->init(); dump->write(); From ebfb94a71746445279bc52fe0ee9be7502cd42e8 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Thu, 15 May 2025 17:04:18 -0600 Subject: [PATCH 08/23] fix whitespace --- src/PYTHON/python_impl.cpp | 14 +++++++------- src/compute_angle_local.cpp | 2 +- src/compute_bond_local.cpp | 2 +- src/compute_dihedral_local.cpp | 2 +- src/create_atoms.cpp | 6 +++--- src/fix_deposit.cpp | 6 +++--- src/variable.cpp | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index 885d578fe1..c3c30fe677 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -184,7 +184,7 @@ void PythonImpl::command(int narg, char **arg) // one with no return value // it also means there is no need to call Variable::pythonstyle() here // or even define the pythonstyle() method in Variable ? - + invoke_function(ifunc, str, nullptr); return; } @@ -455,7 +455,7 @@ int PythonImpl::function_match(const char *name, const char *varname, int numeri { // NOTE to Richard - any reason not to put error messages here instead of in Variable class ? // error messages appear 3x in Variable - + int ifunc = find(name); if (ifunc < 0) return -1; if (pfuncs[ifunc].noutput == 0) return -2; @@ -481,7 +481,7 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i { // NOTE to Richard - any reason not to put 2 extra error messages here instead of in Variable class ? // only this class knows the name of the missing internal var, so can generate better error message - + int ifunc = function_match(name, varname, 1); if (ifunc < 0) return ifunc; @@ -494,7 +494,7 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i // in Python wrapper functions // also set internal_var for use by invoke_function() // so that invoke_function() is as fast as possible for args which are internal-style vars - + int j = 0; for (int i = 0; i < pfuncs[ifunc].ninput; i++) if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { @@ -503,7 +503,7 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i pfuncs[ifunc].internal_var[i] = ivar; argvars[j++] = ivar; } - + return ifunc; } @@ -565,7 +565,7 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon char *vname = pfuncs[ifunc].svalue[i]; int ivar = input->variable->find(vname); if (ivar < 0) { // create internal variable if does not exist - input->variable->internal_create(vname,0.0); + input->variable->internal_create(vname,0.0); ivar = input->variable->find(vname); } if (!input->variable->internalstyle(ivar)) @@ -585,7 +585,7 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon char *vname = pfuncs[ifunc].svalue[i]; int ivar = input->variable->find(vname); if (ivar < 0) { // create internal variable if does not exist - input->variable->internal_create(vname,0.0); + input->variable->internal_create(vname,0.0); ivar = input->variable->find(vname); } if (!input->variable->internalstyle(ivar)) diff --git a/src/compute_angle_local.cpp b/src/compute_angle_local.cpp index af20bb5a9e..53266b831f 100644 --- a/src/compute_angle_local.cpp +++ b/src/compute_angle_local.cpp @@ -109,7 +109,7 @@ ComputeAngleLocal::ComputeAngleLocal(LAMMPS *lmp, int narg, char **arg) : if (tstr) { tvar = input->variable->find(tstr); if (tvar < 0) { - input->variable->internal_create(tstr,0.0); + input->variable->internal_create(tstr,0.0); tvar = input->variable->find(tstr); } if (!input->variable->internalstyle(tvar)) diff --git a/src/compute_bond_local.cpp b/src/compute_bond_local.cpp index 4adf5d9ff8..8dafaccad4 100644 --- a/src/compute_bond_local.cpp +++ b/src/compute_bond_local.cpp @@ -155,7 +155,7 @@ ComputeBondLocal::ComputeBondLocal(LAMMPS *lmp, int narg, char **arg) : if (dstr) { dvar = input->variable->find(dstr); if (dvar < 0) { - input->variable->internal_create(dstr,0.0); + input->variable->internal_create(dstr,0.0); dvar = input->variable->find(dstr); } if (!input->variable->internalstyle(dvar)) diff --git a/src/compute_dihedral_local.cpp b/src/compute_dihedral_local.cpp index f19a7b48be..3f84360913 100644 --- a/src/compute_dihedral_local.cpp +++ b/src/compute_dihedral_local.cpp @@ -103,7 +103,7 @@ ComputeDihedralLocal::ComputeDihedralLocal(LAMMPS *lmp, int narg, char **arg) : if (pstr) { pvar = input->variable->find(pstr); if (pvar < 0) { - input->variable->internal_create(pstr,0.0); + input->variable->internal_create(pstr,0.0); pvar = input->variable->find(pstr); } if (!input->variable->internalstyle(pvar)) diff --git a/src/create_atoms.cpp b/src/create_atoms.cpp index d9a32dceed..b055e3827a 100644 --- a/src/create_atoms.cpp +++ b/src/create_atoms.cpp @@ -393,7 +393,7 @@ void CreateAtoms::command(int narg, char **arg) if (xstr) { xvar = input->variable->find(xstr); if (xvar < 0) { - input->variable->internal_create(xstr,0.0); + input->variable->internal_create(xstr,0.0); xvar = input->variable->find(xstr); } if (!input->variable->internalstyle(xvar)) @@ -402,7 +402,7 @@ void CreateAtoms::command(int narg, char **arg) if (ystr) { yvar = input->variable->find(ystr); if (yvar < 0) { - input->variable->internal_create(ystr,0.0); + input->variable->internal_create(ystr,0.0); yvar = input->variable->find(ystr); } if (!input->variable->internalstyle(yvar)) @@ -411,7 +411,7 @@ void CreateAtoms::command(int narg, char **arg) if (zstr) { zvar = input->variable->find(zstr); if (zvar < 0) { - input->variable->internal_create(zstr,0.0); + input->variable->internal_create(zstr,0.0); zvar = input->variable->find(zstr); } if (!input->variable->internalstyle(zvar)) diff --git a/src/fix_deposit.cpp b/src/fix_deposit.cpp index 593ae4a76e..66d03a76cf 100644 --- a/src/fix_deposit.cpp +++ b/src/fix_deposit.cpp @@ -869,7 +869,7 @@ void FixDeposit::options(int narg, char **arg) if (xstr) { xvar = input->variable->find(xstr); if (xvar < 0) { - input->variable->internal_create(xstr,0.0); + input->variable->internal_create(xstr,0.0); xvar = input->variable->find(xstr); } if (!input->variable->internalstyle(xvar)) @@ -878,7 +878,7 @@ void FixDeposit::options(int narg, char **arg) if (ystr) { yvar = input->variable->find(ystr); if (yvar < 0) { - input->variable->internal_create(ystr,0.0); + input->variable->internal_create(ystr,0.0); yvar = input->variable->find(ystr); } if (!input->variable->internalstyle(yvar)) @@ -887,7 +887,7 @@ void FixDeposit::options(int narg, char **arg) if (zstr) { zvar = input->variable->find(zstr); if (zvar < 0) { - input->variable->internal_create(zstr,0.0); + input->variable->internal_create(zstr,0.0); zvar = input->variable->find(zstr); } if (!input->variable->internalstyle(zvar)) diff --git a/src/variable.cpp b/src/variable.cpp index c8df2e0266..1b14a78ab7 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -4223,7 +4223,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree data[ivar][0],names[ivar]); } } - + // if tree: store python variable and arg info in tree for later eval // else: one-time eval of python-coded function now via python variable From c3b25c8c2775df638af70bdfad5141a1a78402d5 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Mon, 19 May 2025 14:34:22 -0600 Subject: [PATCH 09/23] updated doc pages and code --- doc/src/compute_angle_local.rst | 17 +- doc/src/compute_bond_local.rst | 17 +- doc/src/compute_dihedral_local.rst | 43 +- doc/src/create_atoms.rst | 25 +- doc/src/fix_controller.rst | 100 ++-- doc/src/fix_deposit.rst | 48 +- doc/src/python.rst | 471 ++++++++++++------- doc/src/variable.rst | 73 +-- examples/python/in.python.wrap | 4 +- examples/python/log.1May25.python.wrap.g++.1 | 24 +- examples/python/log.1May25.python.wrap.g++.4 | 26 +- src/PYTHON/python_impl.cpp | 2 +- 12 files changed, 512 insertions(+), 338 deletions(-) diff --git a/doc/src/compute_angle_local.rst b/doc/src/compute_angle_local.rst index d4491c6945..5bd1692355 100644 --- a/doc/src/compute_angle_local.rst +++ b/doc/src/compute_angle_local.rst @@ -53,15 +53,17 @@ The value *eng* is the interaction energy for the angle. The value *v_name* can be used together with the *set* keyword to compute a user-specified function of the angle theta. The *name* -specified for the *v_name* value is the name of an :doc:`equal-style variable ` which should evaluate a formula based on a +specified for the *v_name* value is the name of an :doc:`equal-style +variable ` which should evaluate a formula based on a variable which will store the angle theta. This other variable must -be an :doc:`internal-style variable ` defined in the input -script; its initial numeric value can be anything. It must be an -internal-style variable, because this command resets its value -directly. The *set* keyword is used to identify the name of this -other variable associated with theta. +be an :doc:`internal-style variable ` specified by the *set* +keyword. It is an internal-style variable, because this command +resets its value directly. The internal-style variable does not need +to be defined in the input script (though it can be); if it is not +defined, then the *set* option creates an :doc:`internal-style +variable ` with the specified name. -Note that the value of theta for each angle which stored in the +Note that the value of theta for each angle which is stored in the internal variable is in radians, not degrees. As an example, these commands can be added to the bench/in.rhodo @@ -70,7 +72,6 @@ system and output the statistics in various ways: .. code-block:: LAMMPS - variable t internal 0.0 variable cos equal cos(v_t) variable cossq equal cos(v_t)*cos(v_t) diff --git a/doc/src/compute_bond_local.rst b/doc/src/compute_bond_local.rst index e070d507b1..27bf14d407 100644 --- a/doc/src/compute_bond_local.rst +++ b/doc/src/compute_bond_local.rst @@ -118,13 +118,15 @@ moving apart. The value *v_name* can be used together with the *set* keyword to compute a user-specified function of the bond distance. The *name* -specified for the *v_name* value is the name of an :doc:`equal-style variable ` which should evaluate a formula based on a -variable which will store the bond distance. This other variable must -be an :doc:`internal-style variable ` defined in the input -script; its initial numeric value can be anything. It must be an -internal-style variable, because this command resets its value -directly. The *set* keyword is used to identify the name of this -other variable associated with theta. +specified for the *v_name* value is the name of an :doc:`equal-style +variable ` which should evaluate a formula based on a +variable which stores the bond distance. This other variable must be +the :doc:`internal-style variable ` specified by the *set* +keyword. It is an internal-style variable, because this command +resets its value directly. The internal-style variable does not need +to be defined in the input script (though it can be); if it is not +defined, then the *set* option creates an :doc:`internal-style +variable ` with the specified name. As an example, these commands can be added to the bench/in.rhodo script to compute the length\ :math:`^2` of every bond in the system and @@ -132,7 +134,6 @@ output the statistics in various ways: .. code-block:: LAMMPS - variable d internal 0.0 variable dsq equal v_d*v_d compute 1 all property/local batom1 batom2 btype diff --git a/doc/src/compute_dihedral_local.rst b/doc/src/compute_dihedral_local.rst index d809cd39ce..77f467721d 100644 --- a/doc/src/compute_dihedral_local.rst +++ b/doc/src/compute_dihedral_local.rst @@ -45,30 +45,31 @@ interactions. The number of datums generated, aggregated across all processors, equals the number of dihedral angles in the system, modified by the group parameter as explained below. -The value *phi* (:math:`\phi`) is the dihedral angle, as defined in the diagram -on the :doc:`dihedral_style ` doc page. +The value *phi* (:math:`\phi`) is the dihedral angle, as defined in +the diagram on the :doc:`dihedral_style ` doc page. -The value *v_name* can be used together with the *set* keyword to compute a -user-specified function of the dihedral angle :math:`\phi`. The *name* -specified for the *v_name* value is the name of an -:doc:`equal-style variable ` which should evaluate a formula based on -a variable which will store the angle :math:`\phi`. This other variable must -be an :doc:`internal-style variable ` defined in the input -script; its initial numeric value can be anything. It must be an -internal-style variable, because this command resets its value -directly. The *set* keyword is used to identify the name of this -other variable associated with :math:`\phi`. +The value *v_name* can be used together with the *set* keyword to +compute a user-specified function of the dihedral angle :math:`\phi`. +The *name* specified for the *v_name* value is the name of an +:doc:`equal-style variable ` which should evaluate a formula +based on a variable which will store the angle :math:`\phi`. This +other variable must be an :doc:`internal-style variable ` +specified by the *set* keyword. It is an internal-style variable, +because this command resets its value directly. The internal-style +variable does not need to be defined in the input script (though it +can be); if it is not defined, then the *set* option creates an +:doc:`internal-style variable ` with the specified name. -Note that the value of :math:`\phi` for each angle which stored in the internal -variable is in radians, not degrees. +Note that the value of :math:`\phi` for each angle which stored in the +internal variable is in radians, not degrees. As an example, these commands can be added to the bench/in.rhodo -script to compute the :math:`\cos\phi` and :math:`\cos^2\phi` of every dihedral -angle in the system and output the statistics in various ways: +script to compute the :math:`\cos\phi` and :math:`\cos^2\phi` of every +dihedral angle in the system and output the statistics in various +ways: .. code-block:: LAMMPS - variable p internal 0.0 variable cos equal cos(v_p) variable cossq equal cos(v_p)*cos(v_p) @@ -100,10 +101,10 @@ no consistent ordering of the entries within the local vector or array from one timestep to the next. The only consistency that is guaranteed is that the ordering on a particular timestep will be the same for local vectors or arrays generated by other compute commands. -For example, dihedral output from the -:doc:`compute property/local ` command can be combined -with data from this command and output by the :doc:`dump local ` -command in a consistent way. +For example, dihedral output from the :doc:`compute property/local +` command can be combined with data from this +command and output by the :doc:`dump local ` command in a +consistent way. Here is an example of how to do this: diff --git a/doc/src/create_atoms.rst b/doc/src/create_atoms.rst index 6d3604215c..8601f02a01 100644 --- a/doc/src/create_atoms.rst +++ b/doc/src/create_atoms.rst @@ -416,24 +416,23 @@ atom, based on its coordinates. They apply to all styles except *single*. The *name* specified for the *var* keyword is the name of an :doc:`equal-style variable ` that should evaluate to a zero or non-zero value based on one or two or three variables that -will store the *x*, *y*, or *z* coordinates of an atom (one variable per -coordinate). If used, these other variables must be -:doc:`internal-style variables ` defined in the input -script; their initial numeric value can be anything. They must be -internal-style variables, because this command resets their values -directly. The *set* keyword is used to identify the names of these -other variables, one variable for the *x*-coordinate of a created atom, -one for *y*, and one for *z*. +will store the *x*, *y*, or *z* coordinates of an atom (one variable +per coordinate). If used, these other variables must be specified by +the *set* keyword. They are internal-style variable, because this +command resets their values directly. The internal-style variables do +not need to be defined in the input script (though they can be); if +one (or more) is not defined, then the *set* option creates an +:doc:`internal-style variable ` with the specified name. .. figure:: img/sinusoid.jpg :figwidth: 50% :align: right :target: _images/sinusoid.jpg -When an atom is created, its :math:`(x,y,z)` coordinates become the values for -any *set* variable that is defined. The *var* variable is then -evaluated. If the returned value is 0.0, the atom is not created. If -it is non-zero, the atom is created. +When an atom is about to be created, its :math:`(x,y,z)` coordinates +become the values for any *set* variable that is defined. The *var* +variable is then evaluated. If the returned value is 0.0, the atom is +not created. If it is non-zero, the atom is created. As an example, these commands can be used in a 2d simulation, to create a sinusoidal surface. Note that the surface is "rough" due to @@ -456,8 +455,6 @@ converts lattice spacings to distance. region box block 0 $x 0 $y -0.5 0.5 create_box 1 box - variable xx internal 0.0 - variable yy internal 0.0 variable v equal "(0.2*v_y*ylat * cos(v_xx/xlat * 2.0*PI*4.0/v_x) + 0.5*v_y*ylat - v_yy) > 0.0" create_atoms 1 box var v set x xx set y yy write_dump all atom sinusoid.lammpstrj diff --git a/doc/src/fix_controller.rst b/doc/src/fix_controller.rst index fc8186ef29..4e0414ca92 100644 --- a/doc/src/fix_controller.rst +++ b/doc/src/fix_controller.rst @@ -98,52 +98,53 @@ the following dynamic equation: \frac{dc}{dt} = -\alpha (K_p e + K_i \int_0^t e \, dt + K_d \frac{de}{dt} ) -where *c* is the continuous time analog of the control variable, -*e* =\ *pvar*\ -\ *setpoint* is the error in the process variable, and -:math:`\alpha`, :math:`K_p`, :math:`K_i` , and :math:`K_d` are constants -set by the corresponding -keywords described above. The discretized version of this equation is: +where *c* is the continuous time analog of the control variable, *e* +=\ *pvar*\ -\ *setpoint* is the error in the process variable, and +:math:`\alpha`, :math:`K_p`, :math:`K_i` , and :math:`K_d` are +constants set by the corresponding keywords described above. The +discretized version of this equation is: .. math:: c_n = c_{n-1} -\alpha \left( K_p \tau e_n + K_i \tau^2 \sum_{i=1}^n e_i + K_d (e_n - e_{n-1}) \right) -where :math:`\tau = \mathtt{Nevery} \cdot \mathtt{timestep}` is the time -interval between updates, -and the subscripted variables indicate the values of *c* and *e* at -successive updates. +where :math:`\tau = \mathtt{Nevery} \cdot \mathtt{timestep}` is the +time interval between updates, and the subscripted variables indicate +the values of *c* and *e* at successive updates. From the first equation, it is clear that if the three gain values :math:`K_p`, :math:`K_i`, :math:`K_d` are dimensionless constants, -then :math:`\alpha` must have -units of [unit *cvar*\ ]/[unit *pvar*\ ]/[unit time] e.g. [ eV/K/ps -]. The advantage of this unit scheme is that the value of the -constants should be invariant under a change of either the MD timestep -size or the value of *Nevery*\ . Similarly, if the LAMMPS :doc:`unit style ` is changed, it should only be necessary to change -the value of :math:`\alpha` to reflect this, while leaving :math:`K_p`, -:math:`K_i`, and :math:`K_d` unaltered. +then :math:`\alpha` must have units of [unit *cvar*\ ]/[unit *pvar*\ +]/[unit time] e.g. [ eV/K/ps ]. The advantage of this unit scheme is +that the value of the constants should be invariant under a change of +either the MD timestep size or the value of *Nevery*\ . Similarly, if +the LAMMPS :doc:`unit style ` is changed, it should only be +necessary to change the value of :math:`\alpha` to reflect this, while +leaving :math:`K_p`, :math:`K_i`, and :math:`K_d` unaltered. When choosing the values of the four constants, it is best to first pick a value and sign for :math:`\alpha` that is consistent with the -magnitudes and signs of *pvar* and *cvar*\ . The magnitude of :math:`K_p` -should then be tested over a large positive range keeping :math:`K_i = K_d =0`. -A good value for :math:`K_p` will produce a fast response in *pvar*, -without overshooting the *setpoint*\ . For many applications, proportional -feedback is sufficient, and so :math:`K_i = K_d =0` can be used. In cases -where there is a substantial lag time in the response of *pvar* to a change -in *cvar*, this can be counteracted by increasing :math:`K_d`. In situations +magnitudes and signs of *pvar* and *cvar*\ . The magnitude of +:math:`K_p` should then be tested over a large positive range keeping +:math:`K_i = K_d =0`. A good value for :math:`K_p` will produce a +fast response in *pvar*, without overshooting the *setpoint*\ . For +many applications, proportional feedback is sufficient, and so +:math:`K_i = K_d =0` can be used. In cases where there is a +substantial lag time in the response of *pvar* to a change in *cvar*, +this can be counteracted by increasing :math:`K_d`. In situations where *pvar* plateaus without reaching *setpoint*, this can be -counteracted by increasing :math:`K_i`. In the language of Charles Dickens, -:math:`K_p` represents the error of the present, :math:`K_i` the error of -the past, and :math:`K_d` the error yet to come. +counteracted by increasing :math:`K_i`. In the language of Charles +Dickens, :math:`K_p` represents the error of the present, :math:`K_i` +the error of the past, and :math:`K_d` the error yet to come. Because this fix updates *cvar*, but does not initialize its value, -the initial value :math:`c_0` is that assigned by the user in the input script via -the :doc:`internal-style variable ` command. This value is -used (by every other LAMMPS command that uses the variable) until this -fix performs its first update of *cvar* after *Nevery* timesteps. On -the first update, the value of the derivative term is set to zero, -because the value of :math:`e_{n-1}` is not yet defined. +the initial value :math:`c_0` is that assigned by the user in the +input script via the :doc:`internal-style variable ` +command. This value is used (by every other LAMMPS command that uses +the variable) until this fix performs its first update of *cvar* after +*Nevery* timesteps. On the first update, the value of the derivative +term is set to zero, because the value of :math:`e_{n-1}` is not yet +defined. ---------- @@ -154,21 +155,23 @@ must produce a global quantity, not a per-atom or local quantity. If *pvar* begins with "c\_", a compute ID must follow which has been previously defined in the input script and which generates a global -scalar or vector. See the individual :doc:`compute ` doc page -for details. If no bracketed integer is appended, the scalar +scalar or vector. See the individual :doc:`compute ` doc +page for details. If no bracketed integer is appended, the scalar calculated by the compute is used. If a bracketed integer is appended, the Ith value of the vector calculated by the compute is -used. Users can also write code for their own compute styles and :doc:`add them to LAMMPS `. +used. Users can also write code for their own compute styles and +:doc:`add them to LAMMPS `. If *pvar* begins with "f\_", a fix ID must follow which has been previously defined in the input script and which generates a global scalar or vector. See the individual :doc:`fix ` page for details. Note that some fixes only produce their values on certain timesteps, which must be compatible with when fix controller -references the values, or else an error results. If no bracketed integer -is appended, the scalar calculated by the fix is used. If a bracketed -integer is appended, the Ith value of the vector calculated by the fix -is used. Users can also write code for their own fix style and :doc:`add them to LAMMPS `. +references the values, or else an error results. If no bracketed +integer is appended, the scalar calculated by the fix is used. If a +bracketed integer is appended, the Ith value of the vector calculated +by the fix is used. Users can also write code for their own fix style +and :doc:`add them to LAMMPS `. If *pvar* begins with "v\_", a variable name must follow which has been previously defined in the input script. Only equal-style variables @@ -182,19 +185,21 @@ variable. The target value *setpoint* for the process variable must be a numeric value, in whatever units *pvar* is defined for. -The control variable *cvar* must be the name of an :doc:`internal-style variable ` previously defined in the input script. Note -that it is not specified with a "v\_" prefix, just the name of the -variable. It must be an internal-style variable, because this fix -updates its value directly. Note that other commands can use an -equal-style versus internal-style variable interchangeably. +The control variable *cvar* must be the name of an +:doc:`internal-style variable ` previously defined in the +input script. Note that it is not specified with a "v\_" prefix, just +the name of the variable. It must be an internal-style variable, +because this fix updates its value directly. Note that other commands +can use an equal-style versus internal-style variable interchangeably. ---------- Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" -Currently, no information about this fix is written to :doc:`binary restart files `. None of the :doc:`fix_modify ` options -are relevant to this fix. +Currently, no information about this fix is written to :doc:`binary +restart files `. None of the :doc:`fix_modify ` +options are relevant to this fix. This fix produces a global vector with 3 values which can be accessed by various :doc:`output commands `. The values can be @@ -211,7 +216,8 @@ variable is in. The vector values calculated by this fix are "extensive". No parameter of this fix can be used with the *start/stop* keywords of -the :doc:`run ` command. This fix is not invoked during :doc:`energy minimization `. +the :doc:`run ` command. This fix is not invoked during +:doc:`energy minimization `. Restrictions """""""""""" diff --git a/doc/src/fix_deposit.rst b/doc/src/fix_deposit.rst index 8f88717a00..09cf328fec 100644 --- a/doc/src/fix_deposit.rst +++ b/doc/src/fix_deposit.rst @@ -225,22 +225,25 @@ rotated configuration of the molecule. .. versionadded:: 21Nov2023 -The *var* and *set* keywords can be used together to provide a criterion -for accepting or rejecting the addition of an individual atom, based on its -coordinates. The *name* specified for the *var* keyword is the name of an -:doc:`equal-style variable ` that should evaluate to a zero or -non-zero value based on one or two or three variables that will store the -*x*, *y*, or *z* coordinates of an atom (one variable per coordinate). If -used, these other variables must be :doc:`internal-style variables -` defined in the input script; their initial numeric value can be -anything. They must be internal-style variables, because this command -resets their values directly. The *set* keyword is used to identify the -names of these other variables, one variable for the *x*-coordinate of a -created atom, one for *y*, and one for *z*. When an atom is created, its -:math:`(x,y,z)` coordinates become the values for any *set* variable that -is defined. The *var* variable is then evaluated. If the returned value -is 0.0, the atom is not created. If it is non-zero, the atom is created. -For an example of how to use these keywords, see the +The *var* and *set* keywords can be used together to provide a +criterion for accepting or rejecting the addition of an individual +atom, based on its coordinates. The *name* specified for the *var* +keyword is the name of an :doc:`equal-style variable ` that +should evaluate to a zero or non-zero value based on one or two or +three variables that will store the *x*, *y*, or *z* coordinates of an +atom (one variable per coordinate). If used, these other variables +must be :doc:`internal-style variables ` specified by the +*set* keyword. They must be internal-style variables, because this +command resets their values directly. The internal-style variables do +not need to be defined in the input script (though they can be); if +one (or more) is not defined, then the *set* option creates an +:doc:`internal-style variable ` with the specified name. + +When an atom is about to be created, its :math:`(x,y,z)` coordinates +become the values for any *set* variable that is defined. The *var* +variable is then evaluated. If the returned value is 0.0, the atom is +not created. If it is non-zero, the atom is created. For an example +of how to use the set/var keywords in a similar context, see the :doc:`create_atoms ` command. The *rate* option moves the insertion volume in the z direction (3d) @@ -304,12 +307,13 @@ units of distance or velocity. Restart, fix_modify, output, run start/stop, minimize info """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" -This fix writes the state of the deposition to :doc:`binary restart files `. This includes information about how many -particles have been deposited, the random number generator seed, the -next timestep for deposition, etc. See the -:doc:`read_restart ` command for info on how to re-specify -a fix in an input script that reads a restart file, so that the -operation of the fix continues in an uninterrupted fashion. +This fix writes the state of the deposition to :doc:`binary restart +files `. This includes information about how many particles +have been deposited, the random number generator seed, the next +timestep for deposition, etc. See the :doc:`read_restart +` command for info on how to re-specify a fix in an +input script that reads a restart file, so that the operation of the +fix continues in an uninterrupted fashion. .. note:: diff --git a/doc/src/python.rst b/doc/src/python.rst index 99f32e7c80..38e36147c0 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -10,43 +10,45 @@ Syntax python mode keyword args ... -* mode = *source* or name of Python function +* mode = *source* or *name* of Python function if mode is *source*: .. parsed-literal:: keyword = *here* or name of a *Python file* - *here* arg = inline - inline = one or more lines of Python code which defines func - must be a single argument, typically enclosed between triple quotes + *here* arg = one or more lines of Python code + must be a single argument, typically enclosed between triple quotes + the in-lined Python code will be executed immediately *Python file* = name of a file with Python code which will be executed immediately -* if *mode* is the name of a Python function, one or more keywords with/without arguments must be appended +* if *mode* is *name* of a Python function: .. parsed-literal:: + one or more keywords with/without arguments must be appended keyword = *invoke* or *input* or *return* or *format* or *length* or *file* or *here* or *exists* - *invoke* arg = none = invoke the previously defined Python function + *invoke* arg = none = invoke the previously-defined Python function *input* args = N i1 i2 ... iN N = # of inputs to function i1,...,iN = value, SELF, or LAMMPS variable name value = integer number, floating point number, or string - SELF = reference to LAMMPS itself which can be accessed by Python function - variable = v_name, where name = name of LAMMPS variable, e.g. v_abc + SELF = reference to LAMMPS itself which can then be accessed by Python function + variable = v_name, where name = name of a LAMMPS variable, e.g. v_abc + internal variable = iv_name, where name = name of a LAMMPS internal-style variable, e.g. iv_xyz *return* arg = varReturn varReturn = v_name = LAMMPS variable name which the return value of the Python function will be assigned to *format* arg = fstring with M characters M = N if no return value, where N = # of inputs M = N+1 if there is a return value - fstring = each character (i,f,s,p) corresponds in order to an input or return value - 'i' = integer, 'f' = floating point, 's' = string, 'p' = SELF + fstring = each character (i,f,s,p) corresponds (in order) to an input or return value + 'i' = integer, 'f' = floating point, 's' = string, 'p' = SELF *length* arg = Nlen Nlen = max length of string returned from Python function *file* arg = filename - filename = file of Python code, which defines func + filename = file of Python code, which defines the Python function *here* arg = inline - inline = one or more lines of Python code which defines func + inline = one or more lines of Python code which defines the Python function must be a single argument, typically enclosed between triple quotes *exists* arg = none = Python code has been loaded by previous python command @@ -87,37 +89,43 @@ Examples Description """"""""""" -The *python* command allows interfacing LAMMPS with an embedded Python -interpreter and enables either executing arbitrary python code in that -interpreter, registering a Python function for future execution (as a -python style variable, from a fix interfaced with python, or for direct -invocation), or invoking such a previously registered function. +The *python* command interfaces LAMMPS with an embedded Python +interpreter and enables executing arbitrary python code in that +interpreter. This can be done immediately, by using *mode* = +*source*. Or execution can be deferred, by registering a Python +function for later execution, by using *mode* = *name* of a Python +function. -Arguments, including LAMMPS variables, can be passed to the function -from the LAMMPS input script and a value returned by the Python function -assigned to a LAMMPS variable. The Python code for the function can be included -directly in the input script or in a separate Python file. The function -can be standard Python code or it can make "callbacks" to LAMMPS through -its library interface to query or set internal values within LAMMPS. -This is a powerful mechanism for performing complex operations in a -LAMMPS input script that are not possible with the simple input script -and variable syntax which LAMMPS defines. Thus your input script can -operate more like a true programming language. +Later execution can be triggered in one of two ways. One is to use +the python command again with its *invoke* keyword. The other is to +trigger the evaluation of a python-style, equal-style, or atom-style +variable. A python-style variable invokes its associated Python +function; its return value becomes the value of the python-style +variable. Equal- and atom-style variables can use a Python function +wrapper in their formulas which encodes the Python function name, and +specifies arguments to pass to the function. + +Note python-style, equal-style, and atom-style variables can be used +in many different ways within LAMMPS. They can be evaulated directly +in an input script, effectively replacing the variable with its value. +Or they can be passed to various commands as arguments, so that the +variable is evaluated multiple times during a simulation run. See the +:doc:`variable ` command doc page for more details on +variable styles which enable Python function evaluation. + +The Python code for the function can be included directly in the input +script or in a separate Python file. The function can be standard +Python code or it can make "callbacks" to LAMMPS through its library +interface to query or set internal values within LAMMPS. This is a +powerful mechanism for performing complex operations in a LAMMPS input +script that are not possible with the simple input script and variable +syntax which LAMMPS defines. Thus your input script can operate more +like a true programming language. Use of this command requires building LAMMPS with the PYTHON package which links to the Python library so that the Python interpreter is embedded in LAMMPS. More details about this process are given below. -There are two ways to invoke a Python function once it has been -registered. One is using the *invoke* keyword. The other is to assign -the function to a :doc:`python-style variable ` defined in -your input script. Whenever the variable is evaluated, it will execute -the Python function to assign a value to the variable. Note that -variables can be evaluated in many different ways within LAMMPS. They -can be substituted with their result directly in an input script, or -they can be passed to various commands as arguments, so that the -variable is evaluated during a simulation run. - A broader overview of how Python can be used with LAMMPS is given in the :doc:`Use Python with LAMMPS ` section of the documentation. There also is an ``examples/python`` directory which @@ -125,25 +133,31 @@ illustrates use of the python command. ---------- -The first argument of the *python* command is either the *source* -keyword or the name of a Python function. This defines the mode -of the python command. +The first argument is the *mode* setting, which is either *source* or +the *name* of a Python function. .. versionchanged:: 22Dec2022 -If the *source* keyword is used, it is followed by either a file name or -the *here* keyword. No other keywords can be used. The *here* keyword -is followed by a string with python commands, either on a single line -enclosed in quotes, or as multiple lines enclosed in triple quotes. -These Python commands will be passed to the python interpreter and -executed immediately without registering a Python function for future -execution. The code will be loaded into and run in the "main" module of -the Python interpreter. This allows running arbitrary Python code at -any time while processing the LAMMPS input file. This can be used to -pre-load Python modules, initialize global variables, define functions -or classes, or perform operations using the python programming language. -The Python code will be executed in parallel on all MPI processes. No -arguments can be passed. +If *source* is used, it is followed by either the *here* keyword or a +file name containing Python code. The *here* keyword is followed by a +string containing python commands, either on a single line enclosed in +quotes, or as multiple lines enclosed in triple quotes. In either +case, the in-line code or file contents are passed to the python +interpreter and executed immediately. The code will be loaded into +and run in the "main" module of the Python interpreter. This allows +running arbitrary Python code at any time while processing the SPARTA +input file. This can be used to pre-load Python modules, initialize +global variables, define functions or classes, or perform operations +using the Python programming language. The Python code will be +executed in parallel on all the MPI processes being used to run +LAMMPS. Note that no arguments can be passed to the executed Python +code. + +If the *mode* setting is the *name* of a Python function, then it will +be registered with SPARTA for future execution (or already be defined, +see the *exists* keyword). One or more keywords must follow the +*mode* function name. One of the keywords must be *invoke*, *file*, +*here*, or *exists*. In all other cases, the first argument is the name of a Python function that will be registered with LAMMPS for future execution. The function @@ -154,40 +168,79 @@ If the *invoke* keyword is used, no other keywords can be used, and a previous *python* command must have registered the Python function referenced by this command. This invokes the Python function with the previously defined arguments and the return value is processed as -explained below. You can invoke the function as many times as you wish -in your input script. +explained below. You can invoke a registered function as many times +as you wish in your input script. + +NOTE: As indicated with a NOTE in python_impl.cpp, I don't think there +is any access to a value returned by invoking a Py function in this way. +If that is correct, I think this should be clarified in the doc page, +with a better explanation of the utility of using the *invoke* keyword. The *input* keyword defines how many arguments *N* the Python function -expects. If it takes no arguments, then the *input* keyword should not -be used. Each argument can be specified directly as a value, e.g. '6' -or '3.14159' or 'abc' (a string of characters). The type of each -argument is specified by the *format* keyword as explained below, so -that Python will know how to interpret the value. If the word SELF is -used for an argument it has a special meaning. A pointer is passed to -the Python function which it can convert into a reference to LAMMPS +expects. If it takes no arguments, then the *input* keyword should +not be used. Each argument can be specified directly as a value, +e.g. '6' or '3.14159' or 'abc' (a string of characters). The type of +each argument is specified by the *format* keyword as explained below, +so that Python will know how to interpret the value. If the word SELF +is used for an argument it has a special meaning. A pointer is passed +to the Python function which it can convert into a reference to LAMMPS itself using the :doc:`LAMMPS Python module `. This enables the function to call back to LAMMPS through its library -interface as explained below. This allows the Python function to query -or set values internal to LAMMPS which can affect the subsequent -execution of the input script. A LAMMPS variable can also be used as an -argument, specified as v_name, where "name" is the name of the variable. -Any style of LAMMPS variable returning a scalar or a string can be used, -as defined by the :doc:`variable ` command. The *format* -keyword must be used to set the type of data that is passed to Python. +interface as explained below. This allows the Python function to +query or set values internal to LAMMPS which can affect the subsequent +execution of the input script. + +A LAMMPS variable can also be used as an *input* argument, specified +as v_name, where "name" is the name of the variable defined in the +input script. Any style of LAMMPS variable returning a scalar or a +string can be used, as defined by the :doc:`variable ` +command. The style of variable must be consistent with the *format* +keyword specification for the type of data that is passed to Python. Each time the Python function is invoked, the LAMMPS variable is -evaluated and its value is passed to the Python function. +evaluated and its value is passed as an argument to the Python +function. + +A LAMMPS internal-style variable can also be used as an *input* +argument, specified as iv_name, where "name" is the name of the +internal-style variable. The internal-style variable does not have to +be defined in the input script (though it can be); if it is not +defined, this command creates an :doc:`internal-style variable +` with the specified name. + +An internal-style variable must be used when an equal-style or +atom-style variable triggers the invocation of the Python function +defined by this command, by including a Python function wrapper in its +formula, with one or more arguments also included in the formula. + +In brief, the syntax for a Python function wrapper in a variable +formula is py_varname(arg1,arg2,...argN), where "varname" is the name +of a python-style variable associated with a Python function defined +by this command. One or more arguments to the function wrapper can +themselves be formulas which the variable command will evaluate and +pass as arguments to the Python function. This is done by assigning +the numeric result for each argument to an internal-style variable; +this the *input* keyword must specify the arguments as internal-style +variables and their format (see below) as "f" for floating point. +This is because LAMMPS variable formulas are calculated with floating +point arithmetic (any integer values are converted to floating point). + +See the :doc:`variable ` command doc page for full details +on formula syntax including for Python function wrappers. Examples +using Python function wrappers are shown below. The *return* keyword is only needed if the Python function returns a -value. The specified *varReturn* must be of the form v_name, where -"name" is the name of a python-style LAMMPS variable, defined by the +value. The specified *varReturn* is of the form v_name, where "name" +is the name of a python-style LAMMPS variable, defined by the :doc:`variable ` command. The Python function can return a numeric or string value, as specified by the *format* keyword. -As explained on the :doc:`variable ` doc page, the definition -of a python-style variable associates a Python function name with the -variable. This must match the *Python function name* first argument of -the *python* command. For example these two commands would be -consistent: +---------- + +As explained on the :doc:`variable ` doc page, the +definition of a python-style variable associates a Python function +name with the variable. Its specification must match the *mode* +argument of the *python* command for the Python function name. For +example these two commands would be consistent: .. code-block:: LAMMPS @@ -196,43 +249,43 @@ consistent: The two commands can appear in either order in the input script so long as both are specified before the Python function is invoked for -the first time. Afterwards, the variable 'foo' is associated with -the Python function 'myMultiply'. +the first time. The *format* keyword must be used if the *input* or *return* keywords are used. It defines an *fstring* with M characters, where M = sum of number of inputs and outputs. The order of characters corresponds to the N inputs, followed by the return value (if it exists). Each character must be one of the following: "i" for integer, "f" for -floating point, "s" for string, or "p" for SELF. Each character defines -the type of the corresponding input or output value of the Python -function and affects the type conversion that is performed internally as -data is passed back and forth between LAMMPS and Python. Note that it -is permissible to use a :doc:`python-style variable ` in a -LAMMPS command that allows for an equal-style variable as an argument, -but only if the output of the Python function is flagged as a numeric -value ("i" or "f") via the *format* keyword. +floating point, "s" for string, or "p" for SELF. Each character +defines the type of the corresponding input or output value of the +Python function and affects the type conversion that is performed +internally as data is passed back and forth between LAMMPS and Python. +Note that it is permissible to use a :doc:`python-style variable +` in a LAMMPS command that allows for an equal-style +variable as an argument, but only if the output of the Python function +is flagged as a numeric value ("i" or "f") via the *format* keyword. If the *return* keyword is used and the *format* keyword specifies the output as a string, then the default maximum length of that string is 63 characters (64-1 for the string terminator). If you want to return a longer string, the *length* keyword can be specified with its *Nlen* -value set to a larger number (the code allocates space for Nlen+1 to -include the string terminator). If the Python function generates a +value set to a larger number. LAMMPS will then allocate Nlen+1 space +to include the string terminator. If the Python function generates a string longer than the default 63 or the specified *Nlen*, it will be truncated. ---------- -Either the *file*, *here*, or *exists* keyword must be used, but only -one of them. These keywords specify what Python code to load into the -Python interpreter. The *file* keyword gives the name of a file -containing Python code, which should end with a ".py" suffix. The code -will be immediately loaded into and run in the "main" module of the -Python interpreter. The Python code will be executed in parallel on all -MPI processes. Note that Python code which contains a function -definition does not "execute" the function when it is run; it simply -defines the function so that it can be invoked later. +As noted above, either the *invoke*, *file*, *here*, or *exists* +keyword must be used, but only one of them. These keywords specify +what Python code to load into the Python interpreter. The *file* +keyword gives the name of a file containing Python code, which should +end with a ".py" suffix. The code will be immediately loaded into and +run in the "main" module of the Python interpreter. The Python code +will be executed in parallel on all MPI processes. Note that Python +code which contains a function definition does not "execute" the +function when it is run; it simply defines the function so that it can +be invoked later. The *here* keyword does the same thing, except that the Python code follows as a single argument to the *here* keyword. This can be done @@ -243,15 +296,18 @@ proper indentation, blank lines, and comments, as desired. See the how triple quotes can be used as part of input script syntax. The *exists* keyword takes no argument. It means that Python code -containing the required Python function with the given name has already -been executed, for example by a *python source* command or in the same -file that was used previously with the *file* keyword. +containing the required Python function with the given name has +already been executed, for example by a *python source* command or in +the same file that was used previously with the *file* keyword. This +allows use of a single file of Python code which contains multiple +functions, any of which can be used in the same (or different) input +scripts (see below). -Note that the Python code that is loaded and run must contain a function -with the specified function name. To operate properly when later -invoked, the function code must match the *input* and *return* and -*format* keywords specified by the python command. Otherwise Python -will generate an error. +Note that the Python code that is loaded and run by the *file* or +*here* keyword must contain a function with the specified function +name. To operate properly when later invoked, the function code must +match the *input* and *return* and *format* keywords specified by the +python command. Otherwise Python will generate an error. ---------- @@ -308,13 +364,13 @@ previous value is simply returned, without re-computing it. The "global" statement inside the Python function allows it to overwrite the global variables from within the local context of the function. -Note that if you load Python code multiple times (via multiple python -commands), you can overwrite previously loaded variables and functions -if you are not careful. E.g. if the code above were loaded twice, the -global variables would be re-initialized, which might not be what you -want. Likewise, if a function with the same name exists in two chunks -of Python code you load, the function loaded second will override the -function loaded first. +Also note that if you load Python code multiple times (via multiple +python commands), you can overwrite previously loaded variables and +functions if you are not careful. E.g. if the code above were loaded +twice, the global variables would be re-initialized, which might not +be what you want. Likewise, if a function with the same name exists +in two chunks of Python code you load, the function loaded second will +override the function loaded first. It's important to realize that if you are running LAMMPS in parallel, each MPI task will load the Python interpreter and execute a local @@ -325,15 +381,16 @@ This implies three important things. First, if you put a print or other statement creating output to the screen in your Python function, you will see P copies of the output, when running on P processors. If the prints occur at (nearly) the same -time, the P copies of the output may be mixed together. When loading -the LAMMPS Python module into the embedded Python interpreter, it is -possible to pass the pointer to the current LAMMPS class instance and -via the Python interface to the LAMMPS library interface, it is possible -to determine the MPI rank of the current process and thus adapt the -Python code so that output will only appear on MPI rank 0. The -following LAMMPS input demonstrates how this could be done. The text -'Hello, LAMMPS!' should be printed only once, even when running LAMMPS -in parallel. +time, the P copies of the output may be mixed together. + +It is possible to avoid this issue, by passing the pointer to the +current LAMMPS class instance to the Python function via the {input} +SELF argument described above. The Python function can then use the +Python interface to the LAMMPS library interface, and determine the +MPI rank of the current process. The Python code can then ensure +output will only appear on MPI rank 0. The following LAMMPS input +demonstrates how this could be done. The text 'Hello, LAMPS!' should +be printed only once, even when running LAMMPS in parallel. .. code-block:: LAMMPS @@ -348,13 +405,13 @@ in parallel. python python_hello invoke -If your Python code loads Python modules that are not pre-loaded by the -Python library, then it will load the module from disk. This may be a -bottleneck if 1000s of processors try to load a module at the same time. -On some large supercomputers, loading of modules from disk by Python may -be disabled. In this case you would need to pre-build a Python library -that has the required modules pre-loaded and link LAMMPS with that -library. +Second, if your Python code loads Python modules that are not +pre-loaded by the Python library, then it will load the module from +disk. This may be a bottleneck if 1000s of processors try to load a +module at the same time. On some large supercomputers, loading of +modules from disk by Python may be disabled. In this case you would +need to pre-build a Python library that has the required modules +pre-loaded and link LAMMPS with that library. Third, if your Python code calls back to LAMMPS (discussed in the next section) and causes LAMMPS to perform an MPI operation requires @@ -365,10 +422,10 @@ LAMMPS. Otherwise the code may hang. ---------- -Your Python function can "call back" to LAMMPS through its -library interface, if you use the SELF input to pass Python -a pointer to LAMMPS. The mechanism for doing this in your -Python function is as follows: +As mentioned above, a Python function can "call back" to LAMMPS +through its library interface, if the SELF input is used to pass +Python a pointer to LAMMPS. The mechanism for doing this is as +follows: .. code-block:: python @@ -416,7 +473,7 @@ which loads and runs the following function from ``examples/python/funcs.py``: lmp.set_variable("cut",cut) # set a variable in LAMMPS lmp.command("pair_style lj/cut ${cut}") # LAMMPS command - #lmp.command("pair_style lj/cut %d" % cut) # LAMMPS command option + #lmp.command("pair_style lj/cut %d" % cut) # alternate form of LAMMPS command lmp.command("pair_coeff * * 1.0 1.0") # ditto lmp.command("run 10") # ditto @@ -449,9 +506,9 @@ is a useful way for a Python function to return multiple values to LAMMPS, more than the single value that can be passed back via a return statement. This cutoff value in the "cut" variable is then substituted (by LAMMPS) in the pair_style command that is executed -next. Alternatively, the "LAMMPS command option" line could be used -in place of the 2 preceding lines, to have Python insert the value -into the LAMMPS command string. +next. Alternatively, the "alternate form of LAMMPS command" line +could be used in place of the 2 preceding lines, to have Python insert +the value into the LAMMPS command string. .. note:: @@ -463,20 +520,109 @@ into the LAMMPS command string. file() functions, so long as the command would work if it were executed in the LAMMPS input script directly at the same point. -However, a Python function can also be invoked during a run, whenever -an associated LAMMPS variable it is assigned to is evaluated. If the -variable is an input argument to another LAMMPS command (e.g. :doc:`fix setforce `), then the Python function will be invoked -inside the class for that command, in one of its methods that is -invoked in the middle of a timestep. You cannot execute arbitrary -input script commands from the Python function (again, via the -command() or file() functions) at that point in the run and expect it -to work. Other library functions such as those that invoke computes -or other variables may have hidden side effects as well. In these -cases, LAMMPS has no simple way to check that something illogical is -being attempted. -The same applies to Python functions called during a simulation run at -each time step using :doc:`fix python/invoke `. +---------- + +A Python function can also be invoked during a run, whenever +an associated python-style variable it is assigned to is evaluated. + +If the variable is an input argument to another LAMMPS command +(e.g. :doc:`fix setforce `), then the Python function +will be invoked inside the class for that command, possibly in one of +its methods that is invoked in the middle of a timestep. You cannot +execute arbitrary input script commands from the Python function +(again, via the command() or file() functions) at that point in the +run and expect it to work. Other library functions such as those that +invoke computes or other variables may have hidden side effects as +well. In these cases, LAMMPS has no simple way to check that +something illogical is being attempted. + +The same constraints apply to Python functions called during a +simulation run at each time step using the :doc:`fix python/invoke +` command. + +---------- + +A Python function can also be invoked within the formula for an +equal-style or atom-style varaible. This means the Python function +will be invoked whenever the variable is invoked. In the case of an +atom-style varaible, the Python function can be invoked once per atom. + +Here are two simple examples using equal- and atom-style variables to +trigger execution of a Python function: + +.. code-block:: LAMMPS + + variable foo python truncate + python truncate return v_foo input 1 iv_arg format fi here """ +def truncate(x): + return int(x) +""" + variable ptrunc equal py_foo(press) + print "TRUNCATED pressure = ${ptrunc}" + +The Python "truncate" function simply converts a floating-point value +to an integer value. When the LAMMPS print command evaluates the +equal-style "ptrunc" variable, the current thermodynamic pressure is +passed to the Python function. The truncated value is output to the +screen and logfile by the print command. Note that the *input* +keyword for the *python* command, specifies an internal-style variable +named "arg" as iv_arg which is required to invoke the Python function +from a Python function wrapper. + +The last 2 lines can be replaced by these to define atom-style +varaibles which invoke the same Python "truncate" function: + +.. code-block:: LAMMPS + + variable xtrunc atom py_foo(x) + variable ytrunc atom py_foo(y) + variable ztrunc atom py_foo(z) + dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc + +Now when the dump command invokes the 3 atom-style variables, their +arguments x,y,z to the Python function wrapper are the current +per-atom coordinates of each atom. The Python "truncate" function is +thus invoked 3 times for each atom, and the truncated coordinate +values for each atom are written to the dump file. + +Note that when using a Python function wrapper in a variable, +arguments can be passed to the Python function either from the +varaible formula or by *input* keyword to the *python command. For +example, consider these (made up) commands: + +.. code-block:: LAMMPS + + variable foo python mixedargs + python mixedargs return v_foo input 6 7.5 v_myValue iv_arg1 iv_argy iv_argz v_flags & + format fffffsf here """ +def mixedargs(a,b,x,y,z,flags): + ... + return result +""" + variable flags string optionABC + variable myValue equal "2.0*temp*c_pe" + compute pe all pe + compute peatom all pe/atom + variable field atom py_foo(x+3.0,sqrt(y),(z-zlo)*c_peatom) + +They define a Python "mixedargs" function with 6 arguments. Three of +them are internal-style variables, which the variable formula +calculates as numeric values for each atom and passes to the function. +In this example, these arguments are themselves small formulas +containing the x,y,z coordinates of each atom as well as a per-atom +compute (c_peratom) and thermodynamic keyword (zlo). + +The other three arguements (7.5,v_myValue,v_flags) are defined by the +*python* command. The first and last are constant values (7.5 and the +optionABC string). The second argument (myValue) is the result of an +equal-style variable formula which accesses the system temperature and +potential energy. + +The "result" returned by teach invocation of the Python "mixedargs" +function becomes the per-atom value in the atom-style "field" +variable, which could be output to a dump file or used elsewhere in +the input script. ---------- @@ -563,27 +709,30 @@ If you use Python code which calls back to LAMMPS, via the SELF input argument explained above, there is an extra step required when building LAMMPS. LAMMPS must also be built as a shared library and your Python function must be able to load the :doc:`"lammps" Python module -` that wraps the LAMMPS library interface. These are the -same steps required to use Python by itself to wrap LAMMPS. Details on -these steps are explained on the :doc:`Python ` doc page. -Note that it is important that the stand-alone LAMMPS executable and the -LAMMPS shared library be consistent (built from the same source code -files) in order for this to work. If the two have been built at -different times using different source files, problems may occur. +` that wraps the LAMMPS library interface. + +These are the same steps required to use Python by itself to wrap +LAMMPS. Details on these steps are explained on the :doc:`Python +` doc page. Note that it is important that the +stand-alone LAMMPS executable and the LAMMPS shared library be +consistent (built from the same source code files) in order for this +to work. If the two have been built at different times using +different source files, problems may occur. Another limitation of calling back to Python from the LAMMPS module using the *python* command in a LAMMPS input is that both, the Python interpreter and LAMMPS, must be linked to the same Python runtime as a shared library. If the Python interpreter is linked to Python statically (which seems to happen with Conda) then loading the shared -LAMMPS library will create a second python "main" module that hides the -one from the Python interpreter and all previous defined function and -global variables will become invisible. +LAMMPS library will create a second python "main" module that hides +the one from the Python interpreter and all previous defined function +and global variables will become invisible. Related commands """""""""""""""" -:doc:`shell `, :doc:`variable `, :doc:`fix python/invoke ` +:doc:`shell `, :doc:`variable `, :doc:`fix + python/invoke ` Default """"""" diff --git a/doc/src/variable.rst b/doc/src/variable.rst index a51901fbce..08afb64762 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -397,13 +397,24 @@ using the :doc:`command-line switch -var `. For the *internal* style a numeric value is provided. This value will be assigned to the variable until a LAMMPS command sets it to a new -value. There are currently only two LAMMPS commands that require -*internal* variables as inputs, because they reset them: -:doc:`create_atoms ` and :doc:`fix controller -`. As mentioned above, an internal-style variable can -be used in place of an equal-style variable anywhere else in an input -script, e.g. as an argument to another command that allows for -equal-style variables. +value. + +Note however, that most commands which use internal-style variables do +not require them to be defined in the input script. They create one +or more internal-style variables if they do not already exist. +Examples are these commands: + +* :doc:`create_atoms ` +* :doc:`fix deposit ` +* :doc:`compute bond/local ` +* :doc:`compute angle/local ` +* :doc:`compute dihedral/local ` +* :doc:`python ` command in conjunction with Python function wrappers used in equal- and atom-style variable formulas + +A command which does require an internal-style variable to be defined +in the input script is the :doc:`fix controller ` command doc page for details. -The *variable pyarg1* command defines an internal-style variable. It -MUST have the name pyarg1. If the Python function has *N* arguments, -*N* internal-style variables MUST be defined with names *pyarg1*, -*pyarg2*, ... *pyargN*. Note that multiple Python function wrappers -can use the same internal-style variables. - The next three commands define atom-style variables *xtrunc*, *ytrunc*, and *ztrunc*. Each of them include the same Python function wrapper in their formula, with a different argument. The atom-style @@ -1242,15 +1247,27 @@ will in turn invoke the Python-coded *truncate()* method. Because *xtrunc* is an atom-style variable, and the argument *x* in the Python function wrapper is a per-atom quantity (the x-coord of each atom), each processor will invoke the *truncate()* method once per atom, for -the atoms it owns. When invoked for the Ith atom, the x-coord of the -Ith atom becomes the value of the *pyarg1* internal-style variable. -The call to the *truncate()* function uses the value of the *pyarg1* -variable as its first (and only) argument. +the atoms it owns. + +When invoked for the Ith atom, the value of the *arg* internal-style +variable, defined by the *python* command, is set to the x-coord of +the Ith atom. The call via python-style variable *foo* to the Python +*truncate()* function passes the value of the *arg* variable as its +first (and only) argument. Likewise, the return value of the Python +function becomes is stored by the python-style variable *foo* and used +in the *xtrunc* atom-style variable formula for the Ith atom. The resulting per-atom vector for *xtrunc* will thus contain the truncated x-coord of every atom in the system. The dump command includes the truncated xyz coords for each atom in its output. +See the :doc:`python ' command for more details on options the +*python* command can specify as well as examples of more complex +Python functions which can be wrapped in this manner. In particular, +the Python function can take a variety of arguments, some generated by +the *python* command, and others by the arguments of the Python +function wrapper. + ---------- Atom Values and Vectors diff --git a/examples/python/in.python.wrap b/examples/python/in.python.wrap index 19ecec8f66..35c080f11b 100644 --- a/examples/python/in.python.wrap +++ b/examples/python/in.python.wrap @@ -25,11 +25,10 @@ neigh_modify delay 0 every 20 check no fix 1 all nve variable foo python truncate -python truncate return v_foo input 1 v_pyarg1 format fi here """ +python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ -variable pyarg1 internal 0.0 variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" @@ -40,7 +39,6 @@ variable ztrunc atom py_foo(z) # examine dump file to see truncated xyz coords of each atom -#dump 1 all custom 100 tmp.dump id x y z dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc run 100 diff --git a/examples/python/log.1May25.python.wrap.g++.1 b/examples/python/log.1May25.python.wrap.g++.1 index 8038b600f3..a5edfe079e 100644 --- a/examples/python/log.1May25.python.wrap.g++.1 +++ b/examples/python/log.1May25.python.wrap.g++.1 @@ -1,4 +1,4 @@ -LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-125-g7ca493917a-modified) +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-266-gebfb94a717-modified) # 3d Lennard-Jones melt with equal- and atom-style variables which # use a Python function wrapper in their formulas @@ -35,11 +35,10 @@ neigh_modify delay 0 every 20 check no fix 1 all nve variable foo python truncate -python truncate return v_foo input 1 v_pyarg1 format fi here """ +python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ -variable pyarg1 internal 0.0 variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" @@ -49,7 +48,8 @@ variable xtrunc atom py_foo(x) variable ytrunc atom py_foo(y) variable ztrunc atom py_foo(z) -#dump 1 all custom 100 tmp.dump id x y z +# examine dump file to see truncated xyz coords of each atom + dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc run 100 @@ -70,20 +70,20 @@ Per MPI rank memory allocation (min/avg/max) = 2.644 | 2.644 | 2.644 Mbytes Step Temp E_pair E_mol TotEng Press 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 -Loop time of 0.0160255 on 1 procs for 100 steps with 500 atoms +Loop time of 0.014627 on 1 procs for 100 steps with 500 atoms -Performance: 2695709.610 tau/day, 6240.069 timesteps/s, 3.120 Matom-step/s +Performance: 2953445.899 tau/day, 6836.680 timesteps/s, 3.418 Matom-step/s 100.0% CPU use with 1 MPI tasks x no OpenMP threads MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Pair | 0.011326 | 0.011326 | 0.011326 | 0.0 | 70.67 -Neigh | 0.002924 | 0.002924 | 0.002924 | 0.0 | 18.25 -Comm | 0.00046255 | 0.00046255 | 0.00046255 | 0.0 | 2.89 -Output | 0.0010398 | 0.0010398 | 0.0010398 | 0.0 | 6.49 -Modify | 0.00020589 | 0.00020589 | 0.00020589 | 0.0 | 1.28 -Other | | 6.725e-05 | | | 0.42 +Pair | 0.010546 | 0.010546 | 0.010546 | 0.0 | 72.10 +Neigh | 0.0027775 | 0.0027775 | 0.0027775 | 0.0 | 18.99 +Comm | 0.00044818 | 0.00044818 | 0.00044818 | 0.0 | 3.06 +Output | 0.00060601 | 0.00060601 | 0.00060601 | 0.0 | 4.14 +Modify | 0.00018516 | 0.00018516 | 0.00018516 | 0.0 | 1.27 +Other | | 6.39e-05 | | | 0.44 Nlocal: 500 ave 500 max 500 min Histogram: 1 0 0 0 0 0 0 0 0 0 diff --git a/examples/python/log.1May25.python.wrap.g++.4 b/examples/python/log.1May25.python.wrap.g++.4 index 3abf6d27fa..ff3e94ddf7 100644 --- a/examples/python/log.1May25.python.wrap.g++.4 +++ b/examples/python/log.1May25.python.wrap.g++.4 @@ -1,4 +1,4 @@ -LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-125-g7ca493917a-modified) +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-266-gebfb94a717-modified) # 3d Lennard-Jones melt with equal- and atom-style variables which # use a Python function wrapper in their formulas @@ -35,11 +35,10 @@ neigh_modify delay 0 every 20 check no fix 1 all nve variable foo python truncate -python truncate return v_foo input 1 v_pyarg1 format fi here """ +python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ -variable pyarg1 internal 0.0 variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" @@ -49,7 +48,8 @@ variable xtrunc atom py_foo(x) variable ytrunc atom py_foo(y) variable ztrunc atom py_foo(z) -#dump 1 all custom 100 tmp.dump id x y z +# examine dump file to see truncated xyz coords of each atom + dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc run 100 @@ -70,20 +70,20 @@ Per MPI rank memory allocation (min/avg/max) = 2.609 | 2.609 | 2.609 Mbytes Step Temp E_pair E_mol TotEng Press 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 -Loop time of 0.00641075 on 4 procs for 100 steps with 500 atoms +Loop time of 0.0062374 on 4 procs for 100 steps with 500 atoms -Performance: 6738684.275 tau/day, 15598.806 timesteps/s, 7.799 Matom-step/s -100.0% CPU use with 4 MPI tasks x no OpenMP threads +Performance: 6925957.189 tau/day, 16032.308 timesteps/s, 8.016 Matom-step/s +74.7% CPU use with 4 MPI tasks x no OpenMP threads MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Pair | 0.0028061 | 0.0028831 | 0.0029657 | 0.1 | 44.97 -Neigh | 0.00086635 | 0.00088279 | 0.00089739 | 0.0 | 13.77 -Comm | 0.0020095 | 0.0020768 | 0.0021521 | 0.1 | 32.40 -Output | 0.00041634 | 0.00042457 | 0.00043221 | 0.0 | 6.62 -Modify | 6.2967e-05 | 6.4188e-05 | 6.5205e-05 | 0.0 | 1.00 -Other | | 7.934e-05 | | | 1.24 +Pair | 0.0027648 | 0.0028431 | 0.0029465 | 0.1 | 45.58 +Neigh | 0.00084567 | 0.00086563 | 0.00088168 | 0.0 | 13.88 +Comm | 0.0020822 | 0.0021609 | 0.0022418 | 0.1 | 34.64 +Output | 0.00021567 | 0.00022125 | 0.00022624 | 0.0 | 3.55 +Modify | 6.2567e-05 | 6.4105e-05 | 6.63e-05 | 0.0 | 1.03 +Other | | 8.241e-05 | | | 1.32 Nlocal: 125 ave 126 max 123 min Histogram: 1 0 0 0 0 0 1 0 0 2 diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index c3c30fe677..e556dc2f18 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -415,7 +415,7 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); } } else if (otype == DOUBLE) { - if (*dvalue) *dvalue = PyFloat_AsDouble(pValue); + if (dvalue) *dvalue = PyFloat_AsDouble(pValue); else { auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); From ac059a15b035e6715bedcd3b55beaaf66d63e4cb Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Mon, 19 May 2025 14:39:30 -0600 Subject: [PATCH 10/23] added a NOTE --- doc/src/python.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/src/python.rst b/doc/src/python.rst index 38e36147c0..b7706135df 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -171,10 +171,11 @@ previously defined arguments and the return value is processed as explained below. You can invoke a registered function as many times as you wish in your input script. -NOTE: As indicated with a NOTE in python_impl.cpp, I don't think there -is any access to a value returned by invoking a Py function in this way. -If that is correct, I think this should be clarified in the doc page, -with a better explanation of the utility of using the *invoke* keyword. +NOTE for Richard: As indicated with a NOTE in python_impl.cpp, I don't +think there is any access to a value returned by invoking a Py +function in this way. If that is correct, I think this should be +clarified in the doc page, with a better explanation of the utility of +using the *invoke* keyword. The *input* keyword defines how many arguments *N* the Python function expects. If it takes no arguments, then the *input* keyword should From 8fa4c0974cae9c681235ff904d96c59077859187 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Mon, 19 May 2025 16:41:00 -0600 Subject: [PATCH 11/23] remove blank line --- src/PYTHON/python_impl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index e556dc2f18..556a79261a 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -507,7 +507,6 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i return ifunc; } - /* ------------------------------------------------------------------ */ char *PythonImpl::long_string(int ifunc) From 2351418c9421a2f2a6f5b8907f36637c828356f6 Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Mon, 19 May 2025 21:42:08 -0600 Subject: [PATCH 12/23] made collapse_tree() method work correctly in Variable class for Python function wrappers, also added support for vector-style variables to use Python function wrappers --- doc/src/python.rst | 98 ++++++++++++++++++++++++++++++---------------- src/variable.cpp | 31 +++++++++++---- 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/doc/src/python.rst b/doc/src/python.rst index b7706135df..f39e7a505d 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -98,20 +98,21 @@ function. Later execution can be triggered in one of two ways. One is to use the python command again with its *invoke* keyword. The other is to -trigger the evaluation of a python-style, equal-style, or atom-style -variable. A python-style variable invokes its associated Python -function; its return value becomes the value of the python-style -variable. Equal- and atom-style variables can use a Python function -wrapper in their formulas which encodes the Python function name, and -specifies arguments to pass to the function. +trigger the evaluation of a python-style, equal-style, vector-style, +or atom-style variable. A python-style variable invokes its +associated Python function; its return value becomes the value of the +python-style variable. Equal-, vector-, and atom-style variables can +use a Python function wrapper in their formulas which encodes the +Python function name, and specifies arguments to pass to the function. -Note python-style, equal-style, and atom-style variables can be used -in many different ways within LAMMPS. They can be evaulated directly -in an input script, effectively replacing the variable with its value. -Or they can be passed to various commands as arguments, so that the -variable is evaluated multiple times during a simulation run. See the -:doc:`variable ` command doc page for more details on -variable styles which enable Python function evaluation. +Note that python-style, equal-style, vectir-style, and atom-style +variables can be used in many different ways within LAMMPS. They can +be evaulated directly in an input script, effectively replacing the +variable with its value. Or they can be passed to various commands as +arguments, so that the variable is evaluated multiple times during a +simulation run. See the :doc:`variable ` command doc page +for more details on variable styles which enable Python function +evaluation. The Python code for the function can be included directly in the input script or in a separate Python file. The function can be standard @@ -199,7 +200,9 @@ command. The style of variable must be consistent with the *format* keyword specification for the type of data that is passed to Python. Each time the Python function is invoked, the LAMMPS variable is evaluated and its value is passed as an argument to the Python -function. +function. Note that a python-style variable can be used as an +argument, which means that the a Python function can use arguments +which invoke other Python functions. A LAMMPS internal-style variable can also be used as an *input* argument, specified as iv_name, where "name" is the name of the @@ -208,26 +211,35 @@ be defined in the input script (though it can be); if it is not defined, this command creates an :doc:`internal-style variable ` with the specified name. -An internal-style variable must be used when an equal-style or -atom-style variable triggers the invocation of the Python function -defined by this command, by including a Python function wrapper in its -formula, with one or more arguments also included in the formula. +An internal-style variable must be used when an equal-style, +vector-style, or atom-style variable triggers the invocation of the +Python function defined by this command, by including a Python +function wrapper with arguments in its formula. Each of the arguments +must be specified as an internal-style variable via the *input* +keyword. In brief, the syntax for a Python function wrapper in a variable formula is py_varname(arg1,arg2,...argN), where "varname" is the name of a python-style variable associated with a Python function defined by this command. One or more arguments to the function wrapper can -themselves be formulas which the variable command will evaluate and -pass as arguments to the Python function. This is done by assigning -the numeric result for each argument to an internal-style variable; -this the *input* keyword must specify the arguments as internal-style -variables and their format (see below) as "f" for floating point. -This is because LAMMPS variable formulas are calculated with floating -point arithmetic (any integer values are converted to floating point). +themselves be sub-formulas which the variable command will evaluate +and pass as arguments to the Python function. This is done by +assigning the numeric result for each argument to an internal-style +variable; thus the *input* keyword must specify the arguments as +internal-style variables and their format (see below) as "f" for +floating point. This is because LAMMPS variable formulas are +calculated with floating point arithmetic (any integer values are +converted to floating point). Note that the Python function can also +have additional inputs, also specified by the *input* keyword, which +are NOT arguments in the Python function wrapper. See the example +below for the "mixedargs" Python function. See the :doc:`variable ` command doc page for full details on formula syntax including for Python function wrappers. Examples -using Python function wrappers are shown below. +using Python function wrappers are shown below. Note that as +explained above with python-style variables, Python function wrappers +can be nested; a sub-formula for an argument can contain its own +Python function wrapper which invokes another Python function. The *return* keyword is only needed if the Python function returns a value. The specified *varReturn* is of the form v_name, where "name" @@ -545,12 +557,14 @@ simulation run at each time step using the :doc:`fix python/invoke ---------- A Python function can also be invoked within the formula for an -equal-style or atom-style varaible. This means the Python function -will be invoked whenever the variable is invoked. In the case of an -atom-style varaible, the Python function can be invoked once per atom. +equal-style, vector-style, or atom-style varaible. This means the +Python function will be invoked whenever the variable is invoked. In +the case of a vector-style variable, the Python function can be +invoked once per element of the global vector. In the case of an +atom-style variable, the Python function can be invoked once per atom. -Here are two simple examples using equal- and atom-style variables to -trigger execution of a Python function: +Here are three simple examples using equal-, vector-, and atom-style +variables to trigger execution of a Python function: .. code-block:: LAMMPS @@ -571,8 +585,24 @@ keyword for the *python* command, specifies an internal-style variable named "arg" as iv_arg which is required to invoke the Python function from a Python function wrapper. -The last 2 lines can be replaced by these to define atom-style -varaibles which invoke the same Python "truncate" function: +The last 2 lines can be replaced by these to define a vector-style +variable which invokes the same Python "truncate" function: + +.. code-block:: LAMMPS + + compute ke all temp + variable ke vector c_ke + variable ketrunc vector py_foo(v_ke) + thermo_style custom step temp epair v_ketrunc[*] + +The vector-style variable "ketrunc" invokes the Python "truncate" +function on each of the 6 components of the global kinetic energy +tensor calculated by the :doc:`compute ke ` command. The +6 truncated values will be printed with thermo output to the screen +and log file. + +Alternatively, the last 2 lines can be replaced by these to define +atom-style variables which invoke the same Python "truncate" function: .. code-block:: LAMMPS @@ -581,7 +611,7 @@ varaibles which invoke the same Python "truncate" function: variable ztrunc atom py_foo(z) dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc -Now when the dump command invokes the 3 atom-style variables, their +When the dump command invokes the 3 atom-style variables, their arguments x,y,z to the Python function wrapper are the current per-atom coordinates of each atom. The Python "truncate" function is thus invoked 3 times for each atom, and the truncated coordinate diff --git a/src/variable.cpp b/src/variable.cpp index 1b14a78ab7..f8d9caa2e3 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -1460,7 +1460,7 @@ void Variable::copy(int narg, char **from, char **to) sin(x),cos(x),tan(x),asin(x),atan2(y,x),... group function = count(group), mass(group), xcm(group,x), ... special function = sum(x),min(x), ... - python function wrapper = py_varname(x,y,z,...) + python function wrapper = py_varname(x,y,z,...) (up to MAXFUNCARG) atom value = x[i], y[i], vx[i], ... atom vector = x, y, vx, ... custom atom property = i/d_name, i/d_name[i], i/d2_name[i], i/d2_name[i][j] @@ -2663,7 +2663,8 @@ double Variable::evaluate(char *str, Tree **tree, int ivar) atan2(y,x),random(x,y,z),normal(x,y,z),ceil(),floor(),round(),ternary(x,y,z), ramp(x,y),stagger(x,y),logfreq(x,y,z),logfreq2(x,y,z), logfreq3(x,y,z),stride(x,y,z),stride2(x,y,z,a,b,c),vdisplace(x,y),swiggle(x,y,z), - cwiggle(x,y,z),sign(x),gmask(x),rmask(x),grmask(x,y) + cwiggle(x,y,z),sign(x),py_varname(x,y,z,...), + gmask(x),rmask(x),grmask(x,y) ---------------------------------------------------------------------- */ double Variable::collapse_tree(Tree *tree) @@ -3220,7 +3221,7 @@ double Variable::collapse_tree(Tree *tree) tree->value = (arg1 >= 0.0) ? 1.0 : -1.0; // sign(arg1); return tree->value; } - + if (tree->type == PYWRAPPER) { int narg = tree->argcount; int *argvars = tree->argvars; @@ -3231,6 +3232,16 @@ double Variable::collapse_tree(Tree *tree) else arg = collapse_tree(tree->extra[iarg-2]); internal_set(argvars[iarg],arg); } + for (int iarg = 0; iarg < narg; iarg++) { + if (iarg == 0) { + if (tree->first->type != VALUE) return 0.0; + } else if (iarg == 1) { + if (tree->second->type != VALUE) return 0.0; + } else { + if (tree->extra[iarg-2]->type != VALUE) return 0.0; + } + } + tree->type = VALUE; tree->value = compute_equal(tree->pyvar); return tree->value; } @@ -3248,12 +3259,14 @@ double Variable::collapse_tree(Tree *tree) evaluate an atom-style or vector-style variable parse tree index I = atom I or vector index I tree was created by one-time parsing of formula string via evaluate() + followed by collapse_tree() operation to streamline tree as much as possible customize by adding a function: sqrt(),exp(),ln(),log(),sin(),cos(),tan(),asin(),acos(),atan(), atan2(y,x),random(x,y,z),normal(x,y,z),ceil(),floor(),round(),ternary(x,y,z), ramp(x,y),stagger(x,y),logfreq(x,y,z),logfreq2(x,y,z), logfreq3(x,y,z),stride(x,y,z),stride2(x,y,z,a,b,c),vdisplace(x,y), - swiggle(x,y,z),cwiggle(x,y,z),sign(x),gmask(x),rmask(x),grmask(x,y) + swiggle(x,y,z),cwiggle(x,y,z),sign(x),py_varname(x,y,z,...), + gmask(x),rmask(x),grmask(x,y) ---------------------------------------------------------------------- */ double Variable::eval_tree(Tree *tree, int i) @@ -3749,7 +3762,6 @@ tagint Variable::int_between_brackets(char *&ptr, int varallow) /* ---------------------------------------------------------------------- process a math function in formula - includes a Python function with syntax py_varname(arg1,arg2,...) push result onto tree or arg stack word = math function name contents = str between parentheses with comma-separated args @@ -3759,8 +3771,7 @@ tagint Variable::int_between_brackets(char *&ptr, int varallow) atan2(y,x),random(x,y,z),normal(x,y,z),ceil(),floor(),round(),ternary(), ramp(x,y),stagger(x,y),logfreq(x,y,z),logfreq2(x,y,z), logfreq3(x,y,z),stride(x,y,z),stride2(x,y,z,a,b,c),vdisplace(x,y), - swiggle(x,y,z),cwiggle(x,y,z),sign(x), - py_varname(arg1,arg2,...) (up to MAXFUNCARG) + swiggle(x,y,z),cwiggle(x,y,z),sign(x),py_varname(x,y,z,...) ------------------------------------------------------------------------- */ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **treestack, @@ -5429,7 +5440,11 @@ void Variable::print_var_error(const std::string &srcfile, const int lineno, void Variable::print_tree(Tree *tree, int level) { - printf("TREE %d: %d %g\n",level,tree->type,tree->value); + if (tree->type == VALUE) { + printf("TREE %d: %d %g\n",level,tree->type,tree->value); + return; + } + printf("TREE %d: %d\n",level,tree->type); if (tree->first) print_tree(tree->first,level+1); if (tree->second) print_tree(tree->second,level+1); if (tree->nextra) From 37a344a2ade49a5259ff19e1aa472eca71ee6a4b Mon Sep 17 00:00:00 2001 From: Steve Plimpton Date: Tue, 20 May 2025 13:33:36 -0600 Subject: [PATCH 13/23] add example for Python with vector-style variable --- doc/src/python.rst | 17 +- examples/python/dump.1May25.python.wrap.g++.1 | 234 ++++++++++++++++++ examples/python/dump.1May25.python.wrap.g++.4 | 234 ++++++++++++++++++ examples/python/in.python.wrap | 26 +- examples/python/log.1May25.python.wrap.g++.1 | 77 +++--- examples/python/log.1May25.python.wrap.g++.4 | 85 ++++--- 6 files changed, 596 insertions(+), 77 deletions(-) create mode 100644 examples/python/dump.1May25.python.wrap.g++.1 create mode 100644 examples/python/dump.1May25.python.wrap.g++.4 diff --git a/doc/src/python.rst b/doc/src/python.rst index f39e7a505d..c23b44d5d8 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -593,7 +593,7 @@ variable which invokes the same Python "truncate" function: compute ke all temp variable ke vector c_ke variable ketrunc vector py_foo(v_ke) - thermo_style custom step temp epair v_ketrunc[*] + thermo_style custom step temp epair v_ketrunc[*6] The vector-style variable "ketrunc" invokes the Python "truncate" function on each of the 6 components of the global kinetic energy @@ -601,8 +601,9 @@ tensor calculated by the :doc:`compute ke ` command. The 6 truncated values will be printed with thermo output to the screen and log file. -Alternatively, the last 2 lines can be replaced by these to define -atom-style variables which invoke the same Python "truncate" function: +Or the last 2 lines of the equal-style variable example can be +replaced by these to define atom-style variables which invoke the same +Python "truncate" function: .. code-block:: LAMMPS @@ -625,13 +626,13 @@ example, consider these (made up) commands: .. code-block:: LAMMPS variable foo python mixedargs - python mixedargs return v_foo input 6 7.5 v_myValue iv_arg1 iv_argy iv_argz v_flags & + python mixedargs return v_foo input 6 7.5 v_myValue iv_arg1 iv_argy iv_argz v_flag & format fffffsf here """ -def mixedargs(a,b,x,y,z,flags): +def mixedargs(a,b,x,y,z,flag): ... return result """ - variable flags string optionABC + variable flag string optionABC variable myValue equal "2.0*temp*c_pe" compute pe all pe compute peatom all pe/atom @@ -644,13 +645,13 @@ In this example, these arguments are themselves small formulas containing the x,y,z coordinates of each atom as well as a per-atom compute (c_peratom) and thermodynamic keyword (zlo). -The other three arguements (7.5,v_myValue,v_flags) are defined by the +The other three arguements (7.5,v_myValue,v_flag) are defined by the *python* command. The first and last are constant values (7.5 and the optionABC string). The second argument (myValue) is the result of an equal-style variable formula which accesses the system temperature and potential energy. -The "result" returned by teach invocation of the Python "mixedargs" +The "result" returned by each invocation of the Python "mixedargs" function becomes the per-atom value in the atom-style "field" variable, which could be output to a dump file or used elsewhere in the input script. diff --git a/examples/python/dump.1May25.python.wrap.g++.1 b/examples/python/dump.1May25.python.wrap.g++.1 new file mode 100644 index 0000000000..b294b70d67 --- /dev/null +++ b/examples/python/dump.1May25.python.wrap.g++.1 @@ -0,0 +1,234 @@ +ITEM: TIMESTEP +0 +ITEM: NUMBER OF ATOMS +108 +ITEM: BOX BOUNDS pp pp pp +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +ITEM: ATOMS id x y z v_xtrunc v_ytrunc v_ztrunc +1 0 0 0 0 0 0 +2 0.839798 0.839798 0 0 0 0 +3 0.839798 0 0.839798 0 0 0 +4 0 0.839798 0.839798 0 0 0 +5 1.6796 0 0 1 0 0 +6 2.51939 0.839798 0 2 0 0 +7 2.51939 0 0.839798 2 0 0 +8 1.6796 0.839798 0.839798 1 0 0 +9 3.35919 0 0 3 0 0 +10 4.19899 0.839798 0 4 0 0 +11 4.19899 0 0.839798 4 0 0 +12 3.35919 0.839798 0.839798 3 0 0 +13 0 1.6796 0 0 1 0 +14 0.839798 2.51939 0 0 2 0 +15 0.839798 1.6796 0.839798 0 1 0 +16 0 2.51939 0.839798 0 2 0 +17 1.6796 1.6796 0 1 1 0 +18 2.51939 2.51939 0 2 2 0 +19 2.51939 1.6796 0.839798 2 1 0 +20 1.6796 2.51939 0.839798 1 2 0 +21 3.35919 1.6796 0 3 1 0 +22 4.19899 2.51939 0 4 2 0 +23 4.19899 1.6796 0.839798 4 1 0 +24 3.35919 2.51939 0.839798 3 2 0 +25 0 3.35919 0 0 3 0 +26 0.839798 4.19899 0 0 4 0 +27 0.839798 3.35919 0.839798 0 3 0 +28 0 4.19899 0.839798 0 4 0 +29 1.6796 3.35919 0 1 3 0 +30 2.51939 4.19899 0 2 4 0 +31 2.51939 3.35919 0.839798 2 3 0 +32 1.6796 4.19899 0.839798 1 4 0 +33 3.35919 3.35919 0 3 3 0 +34 4.19899 4.19899 0 4 4 0 +35 4.19899 3.35919 0.839798 4 3 0 +36 3.35919 4.19899 0.839798 3 4 0 +37 0 0 1.6796 0 0 1 +38 0.839798 0.839798 1.6796 0 0 1 +39 0.839798 0 2.51939 0 0 2 +40 0 0.839798 2.51939 0 0 2 +41 1.6796 0 1.6796 1 0 1 +42 2.51939 0.839798 1.6796 2 0 1 +43 2.51939 0 2.51939 2 0 2 +44 1.6796 0.839798 2.51939 1 0 2 +45 3.35919 0 1.6796 3 0 1 +46 4.19899 0.839798 1.6796 4 0 1 +47 4.19899 0 2.51939 4 0 2 +48 3.35919 0.839798 2.51939 3 0 2 +49 0 1.6796 1.6796 0 1 1 +50 0.839798 2.51939 1.6796 0 2 1 +51 0.839798 1.6796 2.51939 0 1 2 +52 0 2.51939 2.51939 0 2 2 +53 1.6796 1.6796 1.6796 1 1 1 +54 2.51939 2.51939 1.6796 2 2 1 +55 2.51939 1.6796 2.51939 2 1 2 +56 1.6796 2.51939 2.51939 1 2 2 +57 3.35919 1.6796 1.6796 3 1 1 +58 4.19899 2.51939 1.6796 4 2 1 +59 4.19899 1.6796 2.51939 4 1 2 +60 3.35919 2.51939 2.51939 3 2 2 +61 0 3.35919 1.6796 0 3 1 +62 0.839798 4.19899 1.6796 0 4 1 +63 0.839798 3.35919 2.51939 0 3 2 +64 0 4.19899 2.51939 0 4 2 +65 1.6796 3.35919 1.6796 1 3 1 +66 2.51939 4.19899 1.6796 2 4 1 +67 2.51939 3.35919 2.51939 2 3 2 +68 1.6796 4.19899 2.51939 1 4 2 +69 3.35919 3.35919 1.6796 3 3 1 +70 4.19899 4.19899 1.6796 4 4 1 +71 4.19899 3.35919 2.51939 4 3 2 +72 3.35919 4.19899 2.51939 3 4 2 +73 0 0 3.35919 0 0 3 +74 0.839798 0.839798 3.35919 0 0 3 +75 0.839798 0 4.19899 0 0 4 +76 0 0.839798 4.19899 0 0 4 +77 1.6796 0 3.35919 1 0 3 +78 2.51939 0.839798 3.35919 2 0 3 +79 2.51939 0 4.19899 2 0 4 +80 1.6796 0.839798 4.19899 1 0 4 +81 3.35919 0 3.35919 3 0 3 +82 4.19899 0.839798 3.35919 4 0 3 +83 4.19899 0 4.19899 4 0 4 +84 3.35919 0.839798 4.19899 3 0 4 +85 0 1.6796 3.35919 0 1 3 +86 0.839798 2.51939 3.35919 0 2 3 +87 0.839798 1.6796 4.19899 0 1 4 +88 0 2.51939 4.19899 0 2 4 +89 1.6796 1.6796 3.35919 1 1 3 +90 2.51939 2.51939 3.35919 2 2 3 +91 2.51939 1.6796 4.19899 2 1 4 +92 1.6796 2.51939 4.19899 1 2 4 +93 3.35919 1.6796 3.35919 3 1 3 +94 4.19899 2.51939 3.35919 4 2 3 +95 4.19899 1.6796 4.19899 4 1 4 +96 3.35919 2.51939 4.19899 3 2 4 +97 0 3.35919 3.35919 0 3 3 +98 0.839798 4.19899 3.35919 0 4 3 +99 0.839798 3.35919 4.19899 0 3 4 +100 0 4.19899 4.19899 0 4 4 +101 1.6796 3.35919 3.35919 1 3 3 +102 2.51939 4.19899 3.35919 2 4 3 +103 2.51939 3.35919 4.19899 2 3 4 +104 1.6796 4.19899 4.19899 1 4 4 +105 3.35919 3.35919 3.35919 3 3 3 +106 4.19899 4.19899 3.35919 4 4 3 +107 4.19899 3.35919 4.19899 4 3 4 +108 3.35919 4.19899 4.19899 3 4 4 +ITEM: TIMESTEP +100 +ITEM: NUMBER OF ATOMS +108 +ITEM: BOX BOUNDS pp pp pp +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +ITEM: ATOMS id x y z v_xtrunc v_ytrunc v_ztrunc +1 4.97801 0.0605812 4.88474 4 0 4 +2 0.800305 0.813213 0.0576603 0 0 0 +3 0.6485 5.00484 0.836947 0 5 0 +4 5.01236 0.786108 0.77507 5 0 0 +5 1.50261 4.9277 4.9755 1 4 4 +6 2.46756 0.653742 5.0003 2 0 5 +7 2.55699 4.96491 0.926671 2 4 0 +8 1.62464 0.676882 0.777822 1 0 0 +9 3.57018 0.00324557 4.76943 3 0 4 +10 4.24909 0.985667 5.01613 4 0 5 +11 4.13124 0.178555 0.700886 4 0 0 +12 3.22537 0.963341 0.639686 3 0 0 +13 4.90815 1.75715 0.0826318 4 1 0 +14 1.00239 2.45421 0.0111309 1 2 0 +15 0.88902 1.62097 0.874547 0 1 0 +16 0.269512 2.41383 0.844638 0 2 0 +17 1.54513 1.51174 0.0869798 1 1 0 +18 2.0637 2.5991 0.0826392 2 2 0 +19 2.44634 1.71869 0.918144 2 1 0 +20 1.57997 2.52559 1.04361 1 2 1 +21 3.26063 2.10027 0.0160498 3 2 0 +22 4.21774 2.61172 0.0367453 4 2 0 +23 4.32215 1.8333 0.925663 4 1 0 +24 3.38151 2.6788 0.906258 3 2 0 +25 0.275459 3.26838 0.21932 0 3 0 +26 0.675354 4.14887 4.94054 0 4 4 +27 0.867201 3.35405 1.17655 0 3 1 +28 4.78788 4.34273 0.478103 4 4 0 +29 1.42014 3.50181 0.191388 1 3 0 +30 2.39935 4.14279 0.0726977 2 4 0 +31 2.68157 3.57418 0.921633 2 3 0 +32 1.77965 4.13041 0.925765 1 4 0 +33 3.10555 3.22911 5.00703 3 3 5 +34 4.24908 3.67507 4.83053 4 3 4 +35 4.2444 3.59428 0.837193 4 3 0 +36 3.52155 4.21032 0.423884 3 4 0 +37 0.00881412 4.90616 1.74242 0 4 1 +38 0.800033 0.812556 1.72553 0 0 1 +39 0.929754 0.0251285 2.53734 0 0 2 +40 0.0189933 0.897729 2.4272 0 0 2 +41 1.77025 0.0864618 1.69637 1 0 1 +42 2.6077 0.984882 1.7897 2 0 1 +43 2.55819 4.95626 2.45373 2 4 2 +44 1.77963 0.875522 2.5056 1 0 2 +45 3.50499 0.0189748 1.5186 3 0 1 +46 4.47856 0.849521 1.59138 4 0 1 +47 3.99 0.309367 2.42507 3 0 2 +48 3.06373 0.748905 2.87477 3 0 2 +49 0.149175 1.72252 1.81302 0 1 1 +50 0.963279 2.42412 1.76899 0 2 1 +51 0.879984 1.74899 2.55389 0 1 2 +52 0.211736 2.57456 2.48446 0 2 2 +53 1.75238 1.7433 1.79028 1 1 1 +54 2.61023 2.6802 1.55816 2 2 1 +55 2.55259 1.72904 2.60295 2 1 2 +56 1.7903 2.56204 2.41895 1 2 2 +57 3.43857 1.71819 1.54054 3 1 1 +58 4.20455 2.5331 1.87607 4 2 1 +59 4.12961 1.48438 2.40107 4 1 2 +60 3.36606 2.52455 2.67203 3 2 2 +61 0.0138563 3.23504 1.76147 0 3 1 +62 0.968224 4.42746 1.67526 0 4 1 +63 0.965745 3.43479 2.52674 0 3 2 +64 0.144542 4.27264 2.61793 0 4 2 +65 1.7741 3.32184 1.66819 1 3 1 +66 2.4878 4.29352 1.69069 2 4 1 +67 2.56992 3.44579 2.4534 2 3 2 +68 1.72356 4.2344 2.49376 1 4 2 +69 3.43363 3.36417 1.87451 3 3 1 +70 4.32623 4.1046 1.69102 4 4 1 +71 4.22927 3.23505 2.64208 4 3 2 +72 3.41086 4.28362 2.42553 3 4 2 +73 4.85349 0.0953155 3.14546 4 0 3 +74 0.833593 0.840282 3.27238 0 0 3 +75 0.578712 4.99093 3.85234 0 4 3 +76 0.119704 1.00404 4.09942 0 1 4 +77 1.78347 4.95586 3.31184 1 4 3 +78 2.34318 0.736887 3.54475 2 0 3 +79 2.34858 4.82316 4.28272 2 4 4 +80 1.55893 0.765128 4.31112 1 0 4 +81 3.13128 0.0257347 3.70936 3 0 3 +82 4.10851 0.877375 3.36388 4 0 3 +83 4.30691 4.69041 4.14081 4 4 4 +84 3.48471 1.07347 4.35888 3 1 4 +85 0.0668209 1.77975 3.29313 0 1 3 +86 0.894798 2.5043 3.34172 0 2 3 +87 0.743432 1.71961 4.3483 0 1 4 +88 0.135168 2.69084 4.12098 0 2 4 +89 1.73749 1.5259 3.46692 1 1 3 +90 2.47391 2.57508 3.27574 2 2 3 +91 2.4825 1.46064 4.38907 2 1 4 +92 1.67786 2.47309 4.03082 1 2 4 +93 3.34694 1.70248 3.44784 3 1 3 +94 4.20676 2.29697 3.23228 4 2 3 +95 4.31748 1.76411 4.20898 4 1 4 +96 3.1135 2.59262 4.08996 3 2 4 +97 0.0727406 3.44797 3.30247 0 3 3 +98 1.0752 4.05296 3.38036 1 4 3 +99 1.0085 3.35058 4.3598 1 3 4 +100 0.054939 4.00087 4.11831 0 4 4 +101 1.72259 3.24691 3.19685 1 3 3 +102 2.54324 4.21582 3.29642 2 4 3 +103 2.33008 3.38603 4.27885 2 3 4 +104 1.65617 4.10852 4.3491 1 4 4 +105 3.14646 3.43756 3.28105 3 3 3 +106 4.09781 4.10691 3.24439 4 4 3 +107 4.1529 3.22538 3.79605 4 3 3 +108 3.16394 3.96553 4.31023 3 3 4 diff --git a/examples/python/dump.1May25.python.wrap.g++.4 b/examples/python/dump.1May25.python.wrap.g++.4 new file mode 100644 index 0000000000..4f1ad1f2c8 --- /dev/null +++ b/examples/python/dump.1May25.python.wrap.g++.4 @@ -0,0 +1,234 @@ +ITEM: TIMESTEP +0 +ITEM: NUMBER OF ATOMS +108 +ITEM: BOX BOUNDS pp pp pp +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +ITEM: ATOMS id x y z v_xtrunc v_ytrunc v_ztrunc +1 0 0 0 0 0 0 +2 0.839798 0.839798 0 0 0 0 +3 0.839798 0 0.839798 0 0 0 +4 0 0.839798 0.839798 0 0 0 +13 0 1.6796 0 0 1 0 +14 0.839798 1.6796 0.839798 0 1 0 +19 0 0 1.6796 0 0 1 +20 0.839798 0.839798 1.6796 0 0 1 +25 0 1.6796 1.6796 0 1 1 +5 1.6796 0 0 1 0 0 +6 2.51939 0.839798 0 2 0 0 +7 2.51939 0 0.839798 2 0 0 +8 1.6796 0.839798 0.839798 1 0 0 +15 1.6796 1.6796 0 1 1 0 +16 2.51939 1.6796 0.839798 2 1 0 +21 1.6796 0 1.6796 1 0 1 +22 2.51939 0.839798 1.6796 2 0 1 +26 1.6796 1.6796 1.6796 1 1 1 +9 3.35919 0 0 3 0 0 +10 4.19899 0.839798 0 4 0 0 +11 4.19899 0 0.839798 4 0 0 +12 3.35919 0.839798 0.839798 3 0 0 +17 3.35919 1.6796 0 3 1 0 +18 4.19899 1.6796 0.839798 4 1 0 +23 3.35919 0 1.6796 3 0 1 +24 4.19899 0.839798 1.6796 4 0 1 +27 3.35919 1.6796 1.6796 3 1 1 +28 0.839798 0 2.51939 0 0 2 +29 0 0.839798 2.51939 0 0 2 +34 0.839798 1.6796 2.51939 0 1 2 +37 0 0 3.35919 0 0 3 +38 0.839798 0.839798 3.35919 0 0 3 +39 0.839798 0 4.19899 0 0 4 +40 0 0.839798 4.19899 0 0 4 +49 0 1.6796 3.35919 0 1 3 +50 0.839798 1.6796 4.19899 0 1 4 +30 2.51939 0 2.51939 2 0 2 +31 1.6796 0.839798 2.51939 1 0 2 +35 2.51939 1.6796 2.51939 2 1 2 +41 1.6796 0 3.35919 1 0 3 +42 2.51939 0.839798 3.35919 2 0 3 +43 2.51939 0 4.19899 2 0 4 +44 1.6796 0.839798 4.19899 1 0 4 +51 1.6796 1.6796 3.35919 1 1 3 +52 2.51939 1.6796 4.19899 2 1 4 +32 4.19899 0 2.51939 4 0 2 +33 3.35919 0.839798 2.51939 3 0 2 +36 4.19899 1.6796 2.51939 4 1 2 +45 3.35919 0 3.35919 3 0 3 +46 4.19899 0.839798 3.35919 4 0 3 +47 4.19899 0 4.19899 4 0 4 +48 3.35919 0.839798 4.19899 3 0 4 +53 3.35919 1.6796 3.35919 3 1 3 +54 4.19899 1.6796 4.19899 4 1 4 +55 0.839798 2.51939 0 0 2 0 +56 0 2.51939 0.839798 0 2 0 +61 0 3.35919 0 0 3 0 +62 0.839798 4.19899 0 0 4 0 +63 0.839798 3.35919 0.839798 0 3 0 +64 0 4.19899 0.839798 0 4 0 +73 0.839798 2.51939 1.6796 0 2 1 +76 0 3.35919 1.6796 0 3 1 +77 0.839798 4.19899 1.6796 0 4 1 +57 2.51939 2.51939 0 2 2 0 +58 1.6796 2.51939 0.839798 1 2 0 +65 1.6796 3.35919 0 1 3 0 +66 2.51939 4.19899 0 2 4 0 +67 2.51939 3.35919 0.839798 2 3 0 +68 1.6796 4.19899 0.839798 1 4 0 +74 2.51939 2.51939 1.6796 2 2 1 +78 1.6796 3.35919 1.6796 1 3 1 +79 2.51939 4.19899 1.6796 2 4 1 +59 4.19899 2.51939 0 4 2 0 +60 3.35919 2.51939 0.839798 3 2 0 +69 3.35919 3.35919 0 3 3 0 +70 4.19899 4.19899 0 4 4 0 +71 4.19899 3.35919 0.839798 4 3 0 +72 3.35919 4.19899 0.839798 3 4 0 +75 4.19899 2.51939 1.6796 4 2 1 +80 3.35919 3.35919 1.6796 3 3 1 +81 4.19899 4.19899 1.6796 4 4 1 +82 0 2.51939 2.51939 0 2 2 +85 0.839798 3.35919 2.51939 0 3 2 +86 0 4.19899 2.51939 0 4 2 +91 0.839798 2.51939 3.35919 0 2 3 +92 0 2.51939 4.19899 0 2 4 +97 0 3.35919 3.35919 0 3 3 +98 0.839798 4.19899 3.35919 0 4 3 +99 0.839798 3.35919 4.19899 0 3 4 +100 0 4.19899 4.19899 0 4 4 +83 1.6796 2.51939 2.51939 1 2 2 +87 2.51939 3.35919 2.51939 2 3 2 +88 1.6796 4.19899 2.51939 1 4 2 +93 2.51939 2.51939 3.35919 2 2 3 +94 1.6796 2.51939 4.19899 1 2 4 +101 1.6796 3.35919 3.35919 1 3 3 +102 2.51939 4.19899 3.35919 2 4 3 +103 2.51939 3.35919 4.19899 2 3 4 +104 1.6796 4.19899 4.19899 1 4 4 +84 3.35919 2.51939 2.51939 3 2 2 +89 4.19899 3.35919 2.51939 4 3 2 +90 3.35919 4.19899 2.51939 3 4 2 +95 4.19899 2.51939 3.35919 4 2 3 +96 3.35919 2.51939 4.19899 3 2 4 +105 3.35919 3.35919 3.35919 3 3 3 +106 4.19899 4.19899 3.35919 4 4 3 +107 4.19899 3.35919 4.19899 4 3 4 +108 3.35919 4.19899 4.19899 3 4 4 +ITEM: TIMESTEP +100 +ITEM: NUMBER OF ATOMS +108 +ITEM: BOX BOUNDS pp pp pp +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +0.0000000000000000e+00 5.0387885741475218e+00 +ITEM: ATOMS id x y z v_xtrunc v_ytrunc v_ztrunc +27 3.43857 1.71819 1.54054 3 1 1 +55 1.00239 2.45421 0.0111309 1 2 0 +24 4.47856 0.849521 1.59138 4 0 1 +4 5.01236 0.786108 0.77507 5 0 0 +13 4.90815 1.75715 0.0826318 4 1 0 +14 0.88902 1.62097 0.874547 0 1 0 +23 3.50499 0.0189748 1.5186 3 0 1 +20 0.800033 0.812556 1.72553 0 0 1 +25 0.149175 1.72252 1.81302 0 1 1 +18 4.32215 1.8333 0.925663 4 1 0 +2 0.800305 0.813213 0.0576603 0 0 0 +11 4.13124 0.178555 0.700886 4 0 0 +8 1.62464 0.676882 0.777822 1 0 0 +15 1.54513 1.51174 0.0869798 1 1 0 +16 2.44634 1.71869 0.918144 2 1 0 +21 1.77025 0.0864618 1.69637 1 0 1 +22 2.6077 0.984882 1.7897 2 0 1 +26 1.75238 1.7433 1.79028 1 1 1 +56 0.269512 2.41383 0.844638 0 2 0 +73 0.963279 2.42412 1.76899 0 2 1 +12 3.22537 0.963341 0.639686 3 0 0 +29 0.0189933 0.897729 2.4272 0 0 2 +36 4.12961 1.48438 2.40107 4 1 2 +31 1.77963 0.875522 2.5056 1 0 2 +17 3.26063 2.10027 0.0160498 3 2 0 +32 3.99 0.309367 2.42507 3 0 2 +10 4.24909 0.985667 5.01613 4 0 5 +9 3.57018 0.00324557 4.76943 3 0 4 +34 0.879984 1.74899 2.55389 0 1 2 +54 4.31748 1.76411 4.20898 4 1 4 +38 0.833593 0.840282 3.27238 0 0 3 +53 3.34694 1.70248 3.44784 3 1 3 +40 0.119704 1.00404 4.09942 0 1 4 +49 0.0668209 1.77975 3.29313 0 1 3 +50 0.743432 1.71961 4.3483 0 1 4 +1 4.97801 0.0605812 4.88474 4 0 4 +28 0.929754 0.0251285 2.53734 0 0 2 +94 1.67786 2.47309 4.03082 1 2 4 +48 3.48471 1.07347 4.35888 3 1 4 +42 2.34318 0.736887 3.54475 2 0 3 +46 4.10851 0.877375 3.36388 4 0 3 +44 1.55893 0.765128 4.31112 1 0 4 +51 1.73749 1.5259 3.46692 1 1 3 +52 2.4825 1.46064 4.38907 2 1 4 +45 3.13128 0.0257347 3.70936 3 0 3 +33 3.06373 0.748905 2.87477 3 0 2 +37 4.85349 0.0953155 3.14546 4 0 3 +95 4.20676 2.29697 3.23228 4 2 3 +91 0.894798 2.5043 3.34172 0 2 3 +6 2.46756 0.653742 5.0003 2 0 5 +35 2.55259 1.72904 2.60295 2 1 2 +75 4.20455 2.5331 1.87607 4 2 1 +83 1.7903 2.56204 2.41895 1 2 2 +90 3.41086 4.28362 2.42553 3 4 2 +7 2.55699 4.96491 0.926671 2 4 0 +63 0.867201 3.35405 1.17655 0 3 1 +64 4.78788 4.34273 0.478103 4 4 0 +81 4.32623 4.1046 1.69102 4 4 1 +76 0.0138563 3.23504 1.76147 0 3 1 +77 0.968224 4.42746 1.67526 0 4 1 +57 2.0637 2.5991 0.0826392 2 2 0 +80 3.43363 3.36417 1.87451 3 3 1 +65 1.42014 3.50181 0.191388 1 3 0 +58 1.57997 2.52559 1.04361 1 2 1 +67 2.68157 3.57418 0.921633 2 3 0 +68 1.77965 4.13041 0.925765 1 4 0 +74 2.61023 2.6802 1.55816 2 2 1 +78 1.7741 3.32184 1.66819 1 3 1 +79 2.4878 4.29352 1.69069 2 4 1 +59 4.21774 2.61172 0.0367453 4 2 0 +72 3.52155 4.21032 0.423884 3 4 0 +19 0.00881412 4.90616 1.74242 0 4 1 +71 4.2444 3.59428 0.837193 4 3 0 +60 3.38151 2.6788 0.906258 3 2 0 +88 1.72356 4.2344 2.49376 1 4 2 +87 2.56992 3.44579 2.4534 2 3 2 +61 0.275459 3.26838 0.21932 0 3 0 +3 0.6485 5.00484 0.836947 0 5 0 +66 2.39935 4.14279 0.0726977 2 4 0 +82 0.211736 2.57456 2.48446 0 2 2 +30 2.55819 4.95626 2.45373 2 4 2 +108 3.16394 3.96553 4.31023 3 3 4 +62 0.675354 4.14887 4.94054 0 4 4 +47 4.30691 4.69041 4.14081 4 4 4 +107 4.1529 3.22538 3.79605 4 3 3 +92 0.135168 2.69084 4.12098 0 2 4 +97 0.0727406 3.44797 3.30247 0 3 3 +98 1.0752 4.05296 3.38036 1 4 3 +99 1.0085 3.35058 4.3598 1 3 4 +100 0.054939 4.00087 4.11831 0 4 4 +106 4.09781 4.10691 3.24439 4 4 3 +70 4.24908 3.67507 4.83053 4 3 4 +43 2.34858 4.82316 4.28272 2 4 4 +105 3.14646 3.43756 3.28105 3 3 3 +96 3.1135 2.59262 4.08996 3 2 4 +101 1.72259 3.24691 3.19685 1 3 3 +102 2.54324 4.21582 3.29642 2 4 3 +103 2.33008 3.38603 4.27885 2 3 4 +104 1.65617 4.10852 4.3491 1 4 4 +84 3.36606 2.52455 2.67203 3 2 2 +89 4.22927 3.23505 2.64208 4 3 2 +41 1.78347 4.95586 3.31184 1 4 3 +93 2.47391 2.57508 3.27574 2 2 3 +39 0.578712 4.99093 3.85234 0 4 3 +86 0.144542 4.27264 2.61793 0 4 2 +5 1.50261 4.9277 4.9755 1 4 4 +69 3.10555 3.22911 5.00703 3 3 5 +85 0.965745 3.43479 2.52674 0 3 2 diff --git a/examples/python/in.python.wrap b/examples/python/in.python.wrap index 35c080f11b..47a2d3a572 100644 --- a/examples/python/in.python.wrap +++ b/examples/python/in.python.wrap @@ -1,9 +1,9 @@ # 3d Lennard-Jones melt with equal- and atom-style variables which # use a Python function wrapper in their formulas -variable x index 5 -variable y index 5 -variable z index 5 +variable x index 3 +variable y index 3 +variable z index 3 units lj atom_style atomic @@ -24,21 +24,37 @@ neigh_modify delay 0 every 20 check no fix 1 all nve +# define Python truncate() function + variable foo python truncate python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ +# use in equal-style variable + variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" +# use in atom-style variable +# examine dump file to see truncated xyz coords of each atom + variable xtrunc atom py_foo(x) variable ytrunc atom py_foo(y) variable ztrunc atom py_foo(z) -# examine dump file to see truncated xyz coords of each atom - dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc +# use in vector-style variable + +compute ke all temp +variable ke vector c_ke +variable ketrunc vector py_foo(v_ke) +thermo_style custom step temp epair v_ketrunc[*6] + run 100 + +print "KE TENSOR ${ketrunc}" + + diff --git a/examples/python/log.1May25.python.wrap.g++.1 b/examples/python/log.1May25.python.wrap.g++.1 index a5edfe079e..3a7dd1c7b2 100644 --- a/examples/python/log.1May25.python.wrap.g++.1 +++ b/examples/python/log.1May25.python.wrap.g++.1 @@ -1,10 +1,10 @@ -LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-266-gebfb94a717-modified) +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-270-g2351418c94-modified) # 3d Lennard-Jones melt with equal- and atom-style variables which # use a Python function wrapper in their formulas -variable x index 5 -variable y index 5 -variable z index 5 +variable x index 3 +variable y index 3 +variable z index 3 units lj atom_style atomic @@ -12,15 +12,15 @@ atom_style atomic lattice fcc 0.8442 Lattice spacing in x,y,z = 1.6795962 1.6795962 1.6795962 region box block 0 $x 0 $y 0 $z -region box block 0 5 0 $y 0 $z -region box block 0 5 0 5 0 $z -region box block 0 5 0 5 0 5 +region box block 0 3 0 $y 0 $z +region box block 0 3 0 3 0 $z +region box block 0 3 0 3 0 3 create_box 1 box -Created orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) +Created orthogonal box = (0 0 0) to (5.0387886 5.0387886 5.0387886) 1 by 1 by 1 MPI processor grid create_atoms 1 box -Created 500 atoms - using lattice units in orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) +Created 108 atoms + using lattice units in orthogonal box = (0 0 0) to (5.0387886 5.0387886 5.0387886) create_atoms CPU = 0.000 seconds mass 1 1.0 @@ -34,24 +34,36 @@ neigh_modify delay 0 every 20 check no fix 1 all nve +# define Python truncate() function + variable foo python truncate python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ +# use in equal-style variable + variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" TRUNCATE 4 +# use in atom-style variable +# examine dump file to see truncated xyz coords of each atom + variable xtrunc atom py_foo(x) variable ytrunc atom py_foo(y) variable ztrunc atom py_foo(z) -# examine dump file to see truncated xyz coords of each atom - dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc +# use in vector-style variable + +compute ke all temp +variable ke vector c_ke +variable ketrunc vector py_foo(v_ke) +thermo_style custom step temp epair v_ketrunc[*6] + run 100 Generated 0 of 0 mixed pair_coeff terms from geometric mixing rule Neighbor list info ... @@ -59,41 +71,46 @@ Neighbor list info ... max neighbors/atom: 2000, page size: 100000 master list distance cutoff = 2.8 ghost atom cutoff = 2.8 - binsize = 1.4, bins = 6 6 6 + binsize = 1.4, bins = 4 4 4 1 neighbor lists, perpetual/occasional/extra = 1 0 0 (1) pair lj/cut, perpetual attributes: half, newton on pair build: half/bin/atomonly/newton stencil: half/bin/3d bin: standard -Per MPI rank memory allocation (min/avg/max) = 2.644 | 2.644 | 2.644 Mbytes - Step Temp E_pair E_mol TotEng Press - 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 - 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 -Loop time of 0.014627 on 1 procs for 100 steps with 500 atoms +Per MPI rank memory allocation (min/avg/max) = 2.598 | 2.598 | 2.598 Mbytes + Step Temp E_pair v_ketrunc[1] v_ketrunc[2] v_ketrunc[3] v_ketrunc[4] v_ketrunc[5] v_ketrunc[6] + 0 1.44 -6.7733681 155 152 154 -10 -4 -6 + 100 0.82217015 -5.8614684 113 65 84 7 -1 -12 +Loop time of 0.00278186 on 1 procs for 100 steps with 108 atoms -Performance: 2953445.899 tau/day, 6836.680 timesteps/s, 3.418 Matom-step/s +Performance: 15529161.573 tau/day, 35947.133 timesteps/s, 3.882 Matom-step/s 100.0% CPU use with 1 MPI tasks x no OpenMP threads MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Pair | 0.010546 | 0.010546 | 0.010546 | 0.0 | 72.10 -Neigh | 0.0027775 | 0.0027775 | 0.0027775 | 0.0 | 18.99 -Comm | 0.00044818 | 0.00044818 | 0.00044818 | 0.0 | 3.06 -Output | 0.00060601 | 0.00060601 | 0.00060601 | 0.0 | 4.14 -Modify | 0.00018516 | 0.00018516 | 0.00018516 | 0.0 | 1.27 -Other | | 6.39e-05 | | | 0.44 +Pair | 0.0018161 | 0.0018161 | 0.0018161 | 0.0 | 65.29 +Neigh | 0.00057543 | 0.00057543 | 0.00057543 | 0.0 | 20.68 +Comm | 0.00019634 | 0.00019634 | 0.00019634 | 0.0 | 7.06 +Output | 0.00012056 | 0.00012056 | 0.00012056 | 0.0 | 4.33 +Modify | 4.8221e-05 | 4.8221e-05 | 4.8221e-05 | 0.0 | 1.73 +Other | | 2.516e-05 | | | 0.90 -Nlocal: 500 ave 500 max 500 min +Nlocal: 108 ave 108 max 108 min Histogram: 1 0 0 0 0 0 0 0 0 0 -Nghost: 1941 ave 1941 max 1941 min +Nghost: 980 ave 980 max 980 min Histogram: 1 0 0 0 0 0 0 0 0 0 -Neighs: 18766 ave 18766 max 18766 min +Neighs: 4071 ave 4071 max 4071 min Histogram: 1 0 0 0 0 0 0 0 0 0 -Total # of neighbors = 18766 -Ave neighs/atom = 37.532 +Total # of neighbors = 4071 +Ave neighs/atom = 37.694444 Neighbor list builds = 5 Dangerous builds not checked + +print "KE TENSOR ${ketrunc}" +KE TENSOR [113,65,84,7,-1,-12] + + Total wall time: 0:00:00 diff --git a/examples/python/log.1May25.python.wrap.g++.4 b/examples/python/log.1May25.python.wrap.g++.4 index ff3e94ddf7..5f28f5bc34 100644 --- a/examples/python/log.1May25.python.wrap.g++.4 +++ b/examples/python/log.1May25.python.wrap.g++.4 @@ -1,10 +1,10 @@ -LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-266-gebfb94a717-modified) +LAMMPS (2 Apr 2025 - Development - patch_2Apr2025-270-g2351418c94-modified) # 3d Lennard-Jones melt with equal- and atom-style variables which # use a Python function wrapper in their formulas -variable x index 5 -variable y index 5 -variable z index 5 +variable x index 3 +variable y index 3 +variable z index 3 units lj atom_style atomic @@ -12,15 +12,15 @@ atom_style atomic lattice fcc 0.8442 Lattice spacing in x,y,z = 1.6795962 1.6795962 1.6795962 region box block 0 $x 0 $y 0 $z -region box block 0 5 0 $y 0 $z -region box block 0 5 0 5 0 $z -region box block 0 5 0 5 0 5 +region box block 0 3 0 $y 0 $z +region box block 0 3 0 3 0 $z +region box block 0 3 0 3 0 3 create_box 1 box -Created orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) +Created orthogonal box = (0 0 0) to (5.0387886 5.0387886 5.0387886) 1 by 2 by 2 MPI processor grid create_atoms 1 box -Created 500 atoms - using lattice units in orthogonal box = (0 0 0) to (8.397981 8.397981 8.397981) +Created 108 atoms + using lattice units in orthogonal box = (0 0 0) to (5.0387886 5.0387886 5.0387886) create_atoms CPU = 0.000 seconds mass 1 1.0 @@ -34,24 +34,36 @@ neigh_modify delay 0 every 20 check no fix 1 all nve +# define Python truncate() function + variable foo python truncate python truncate return v_foo input 1 iv_arg format fi here """ def truncate(x): return int(x) """ +# use in equal-style variable + variable scalar equal py_foo(4.5) print "TRUNCATE ${scalar}" TRUNCATE 4 +# use in atom-style variable +# examine dump file to see truncated xyz coords of each atom + variable xtrunc atom py_foo(x) variable ytrunc atom py_foo(y) variable ztrunc atom py_foo(z) -# examine dump file to see truncated xyz coords of each atom - dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc +# use in vector-style variable + +compute ke all temp +variable ke vector c_ke +variable ketrunc vector py_foo(v_ke) +thermo_style custom step temp epair v_ketrunc[*6] + run 100 Generated 0 of 0 mixed pair_coeff terms from geometric mixing rule Neighbor list info ... @@ -59,41 +71,46 @@ Neighbor list info ... max neighbors/atom: 2000, page size: 100000 master list distance cutoff = 2.8 ghost atom cutoff = 2.8 - binsize = 1.4, bins = 6 6 6 + binsize = 1.4, bins = 4 4 4 1 neighbor lists, perpetual/occasional/extra = 1 0 0 (1) pair lj/cut, perpetual attributes: half, newton on pair build: half/bin/atomonly/newton stencil: half/bin/3d bin: standard -Per MPI rank memory allocation (min/avg/max) = 2.609 | 2.609 | 2.609 Mbytes - Step Temp E_pair E_mol TotEng Press - 0 1.44 -6.7733681 0 -4.6176881 -5.0221006 - 100 0.75627408 -5.7580082 0 -4.6258659 0.21870071 -Loop time of 0.0062374 on 4 procs for 100 steps with 500 atoms +Per MPI rank memory allocation (min/avg/max) = 2.59 | 2.59 | 2.59 Mbytes + Step Temp E_pair v_ketrunc[1] v_ketrunc[2] v_ketrunc[3] v_ketrunc[4] v_ketrunc[5] v_ketrunc[6] + 0 1.44 -6.7733681 155 152 154 -10 -4 -6 + 100 0.82217015 -5.8614684 113 65 84 7 -1 -12 +Loop time of 0.00268845 on 4 procs for 100 steps with 108 atoms -Performance: 6925957.189 tau/day, 16032.308 timesteps/s, 8.016 Matom-step/s -74.7% CPU use with 4 MPI tasks x no OpenMP threads +Performance: 16068745.964 tau/day, 37196.171 timesteps/s, 4.017 Matom-step/s +66.7% CPU use with 4 MPI tasks x no OpenMP threads MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Pair | 0.0027648 | 0.0028431 | 0.0029465 | 0.1 | 45.58 -Neigh | 0.00084567 | 0.00086563 | 0.00088168 | 0.0 | 13.88 -Comm | 0.0020822 | 0.0021609 | 0.0022418 | 0.1 | 34.64 -Output | 0.00021567 | 0.00022125 | 0.00022624 | 0.0 | 3.55 -Modify | 6.2567e-05 | 6.4105e-05 | 6.63e-05 | 0.0 | 1.03 -Other | | 8.241e-05 | | | 1.32 +Pair | 0.00043389 | 0.00051695 | 0.00061255 | 0.0 | 19.23 +Neigh | 0.00017121 | 0.00018976 | 0.00019891 | 0.0 | 7.06 +Comm | 0.0017423 | 0.0018487 | 0.0019509 | 0.2 | 68.76 +Output | 6.7449e-05 | 6.9998e-05 | 7.5195e-05 | 0.0 | 2.60 +Modify | 2.1329e-05 | 2.2855e-05 | 2.4821e-05 | 0.0 | 0.85 +Other | | 4.018e-05 | | | 1.49 -Nlocal: 125 ave 126 max 123 min -Histogram: 1 0 0 0 0 0 1 0 0 2 -Nghost: 1085.75 ave 1090 max 1082 min -Histogram: 1 0 1 0 0 0 1 0 0 1 -Neighs: 4691.5 ave 4969 max 4368 min -Histogram: 1 0 0 0 1 0 0 1 0 1 +Nlocal: 27 ave 30 max 25 min +Histogram: 1 0 1 0 1 0 0 0 0 1 +Nghost: 614 ave 616 max 612 min +Histogram: 1 0 1 0 0 0 0 1 0 1 +Neighs: 1017.75 ave 1149 max 894 min +Histogram: 1 1 0 0 0 0 0 1 0 1 -Total # of neighbors = 18766 -Ave neighs/atom = 37.532 +Total # of neighbors = 4071 +Ave neighs/atom = 37.694444 Neighbor list builds = 5 Dangerous builds not checked + +print "KE TENSOR ${ketrunc}" +KE TENSOR [113,65,84,7,-1,-12] + + Total wall time: 0:00:00 From a246619ecf98583de36d0678ebbb1f4a5265c554 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 28 May 2025 21:54:17 -0400 Subject: [PATCH 14/23] whitespace --- src/variable.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/variable.cpp b/src/variable.cpp index f8d9caa2e3..07f331c72a 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -3221,7 +3221,7 @@ double Variable::collapse_tree(Tree *tree) tree->value = (arg1 >= 0.0) ? 1.0 : -1.0; // sign(arg1); return tree->value; } - + if (tree->type == PYWRAPPER) { int narg = tree->argcount; int *argvars = tree->argvars; From f6f3eb1c3103e5813f94cdfed42550b20819124f Mon Sep 17 00:00:00 2001 From: Jacob Gissinger Date: Fri, 6 Jun 2025 00:15:30 -0400 Subject: [PATCH 15/23] bug-fix bug fix --- src/REACTION/fix_bond_react.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/REACTION/fix_bond_react.cpp b/src/REACTION/fix_bond_react.cpp index 6ba6954541..970418f49b 100644 --- a/src/REACTION/fix_bond_react.cpp +++ b/src/REACTION/fix_bond_react.cpp @@ -3109,7 +3109,7 @@ void FixBondReact::update_everything() update_num_mega++; } MPI_Allreduce(MPI_IN_PLACE, &noccur[0], nreacts, MPI_INT, MPI_SUM, world); - reaction_count_total[rxnID] += noccur[rxnID]; + for (rxnID = 0; rxnID < nreacts; rxnID++) reaction_count_total[rxnID] += noccur[rxnID]; } else if (pass == 1) { for (int i = 0; i < global_megasize; i++) { rxnID = (int) global_mega_glove[0][i]; From e1aa3cf7eccd36db3bf7914c08cc249e2aa159ef Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 00:19:33 -0400 Subject: [PATCH 16/23] move throwing Python variable errors to PythonImpl class and change API accordingly. --- src/PYTHON/python_impl.cpp | 41 ++++++++++++--------- src/PYTHON/python_impl.h | 4 +-- src/lmppython.cpp | 15 ++++---- src/lmppython.h | 8 ++--- src/variable.cpp | 73 ++++---------------------------------- 5 files changed, 44 insertions(+), 97 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index 556a79261a..483038cc1c 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -451,16 +451,23 @@ int PythonImpl::find(const char *name) ensure a string is returned only if retrieve() is the caller --------------------------------------------------------------------- */ -int PythonImpl::function_match(const char *name, const char *varname, int numeric) +int PythonImpl::function_match(const char *name, const char *varname, int numeric, Error *error) { - // NOTE to Richard - any reason not to put error messages here instead of in Variable class ? - // error messages appear 3x in Variable - int ifunc = find(name); - if (ifunc < 0) return -1; - if (pfuncs[ifunc].noutput == 0) return -2; - if (strcmp(pfuncs[ifunc].ovarname, varname) != 0) return -3; - if (numeric && pfuncs[ifunc].otype == STRING) return -4; + + if (ifunc < 0) + error->all(FLERR, Error::NOLASTLINE, "Python function {} specified by variable {} not found", + name, varname); + if (pfuncs[ifunc].noutput == 0) + error->all(FLERR, Error::NOLASTLINE, + "Python function {} for variable {} does not return a value", name, varname); + if (strcmp(pfuncs[ifunc].ovarname, varname) != 0) + error->all(FLERR, Error::NOLASTLINE, + "Python function {} and variable {} do not not link to each other", name, varname); + if (numeric && pfuncs[ifunc].otype == STRING) + error->all(FLERR, Error::NOLASTLINE, "Python function {} for variable {} returns a string", + name, varname); + return ifunc; } @@ -477,18 +484,18 @@ int PythonImpl::function_match(const char *name, const char *varname, int numeri other classes avoid this issue by setting variable indices in their init() method --------------------------------------------------------------------- */ -int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, int *argvars) +int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, int *argvars, + Error *error) { - // NOTE to Richard - any reason not to put 2 extra error messages here instead of in Variable class ? - // only this class knows the name of the missing internal var, so can generate better error message - - int ifunc = function_match(name, varname, 1); + int ifunc = function_match(name, varname, 1, error); if (ifunc < 0) return ifunc; int internal_count = 0; for (int i = 0; i < pfuncs[ifunc].ninput; i++) if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) internal_count++; - if (internal_count != narg) return -5; + if (internal_count != narg) + error->all(FLERR, Error::NOLASTLINE, + "Python function {} does not use {} internal variable args", name, narg); // set argvars of internal-style variables for use by Variable class // in Python wrapper functions @@ -496,13 +503,15 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i // so that invoke_function() is as fast as possible for args which are internal-style vars int j = 0; - for (int i = 0; i < pfuncs[ifunc].ninput; i++) + for (int i = 0; i < pfuncs[ifunc].ninput; i++) { if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { int ivar = input->variable->find(pfuncs[ifunc].svalue[i]); - if (ivar < 0) return -6; + error->all(FLERR, Error::NOLASTLINE, "Python function {} cannot find internal variable {}", + name, pfuncs[ifunc].svalue[i]); pfuncs[ifunc].internal_var[i] = ivar; argvars[j++] = ivar; } + } return ifunc; } diff --git a/src/PYTHON/python_impl.h b/src/PYTHON/python_impl.h index 3ead92f6f2..5c3955afc6 100644 --- a/src/PYTHON/python_impl.h +++ b/src/PYTHON/python_impl.h @@ -27,8 +27,8 @@ class PythonImpl : protected Pointers, public PythonInterface { void command(int, char **) override; void invoke_function(int, char *, double *) override; int find(const char *) override; - int function_match(const char *, const char *, int) override; - int wrapper_match(const char *, const char *, int, int *) override; + int function_match(const char *, const char *, int, Error *) override; + int wrapper_match(const char *, const char *, int, int *, Error *) override; char *long_string(int) override; int execute_string(char *) override; int execute_file(char *) override; diff --git a/src/lmppython.cpp b/src/lmppython.cpp index 6a8bc1f5a2..f7a213302a 100644 --- a/src/lmppython.cpp +++ b/src/lmppython.cpp @@ -14,9 +14,8 @@ #include "lmppython.h" #if defined(LMP_PYTHON) #include "python_impl.h" -#else -#include "error.h" #endif +#include "error.h" using namespace LAMMPS_NS; @@ -43,7 +42,7 @@ void Python::init() #if defined(LMP_PYTHON) if (!impl) impl = new PythonImpl(lmp); #else - error->all(FLERR, "Python support missing! Compile with PYTHON package installed!"); + error->all(FLERR, Error::NOLASTLINE, "Python support missing! Compile with PYTHON package installed!"); #endif } @@ -83,19 +82,19 @@ int Python::find(const char *name) /* ------------------------------------------------------------------ */ -int Python::function_match(const char *name, const char *varname, int numeric) +int Python::function_match(const char *name, const char *varname, int numeric, Error *errptr) { init(); - return impl->function_match(name, varname, numeric); + return impl->function_match(name, varname, numeric, errptr); } /* ------------------------------------------------------------------ */ -int Python::wrapper_match(const char *name, const char *varname, - int narg, int *argvars) +int Python::wrapper_match(const char *name, const char *varname, int narg, int *argvars, + Error *errptr) { init(); - return impl->wrapper_match(name, varname, narg, argvars); + return impl->wrapper_match(name, varname, narg, argvars, errptr); } /* ------------------------------------------------------------------ */ diff --git a/src/lmppython.h b/src/lmppython.h index 23137b7ecb..f7db8a4c06 100644 --- a/src/lmppython.h +++ b/src/lmppython.h @@ -24,8 +24,8 @@ class PythonInterface { virtual void command(int, char **) = 0; virtual void invoke_function(int, char *, double *) = 0; virtual int find(const char *) = 0; - virtual int function_match(const char *, const char *, int) = 0; - virtual int wrapper_match(const char *, const char *, int, int *) = 0; + virtual int function_match(const char *, const char *, int, Error *) = 0; + virtual int wrapper_match(const char *, const char *, int, int *, Error *) = 0; virtual char *long_string(int ifunc) = 0; virtual int execute_string(char *) = 0; virtual int execute_file(char *) = 0; @@ -40,8 +40,8 @@ class Python : protected Pointers { void command(int, char **); void invoke_function(int, char *, double *); int find(const char *); - int function_match(const char *, const char *, int); - int wrapper_match(const char *, const char *, int, int *); + int function_match(const char *, const char *, int, Error *); + int wrapper_match(const char *, const char *, int, int *, Error *); char *long_string(int ifunc); int execute_string(char *); int execute_file(char *); diff --git a/src/variable.cpp b/src/variable.cpp index 07f331c72a..5f848913c8 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -959,31 +959,10 @@ void Variable::python_command(int narg, char **arg) int Variable::equalstyle(int ivar) { - if (style[ivar] == EQUAL || style[ivar] == TIMER || - style[ivar] == INTERNAL) return 1; - if (style[ivar] == PYTHON) { - pyindex[ivar] = python->function_match(data[ivar][0],names[ivar],1); - if (pyindex[ivar] < 0) { - int ierror = pyindex[ivar]; - if (ierror == -1) { - error->all(FLERR, "Python function {} specified by variable {} not found", - data[ivar][0], names[ivar]); - } else if (ierror == -2) { - error->all(FLERR, "Python function {} for variable {} does not return a value", - data[ivar][0], names[ivar]); - } else if (ierror == -3) { - error->all(FLERR, "Python function {} and variable {} do not not link to each other", - data[ivar][0], names[ivar]); - } else if (ierror == -4) { - error->all(FLERR, "Python function {} for variable {} returns a string", - data[ivar][0], names[ivar]); - } else { - error->all(FLERR, "Unknown error linking Python function {} to variable {}", - data[ivar][0],names[ivar]); - } - return 0; - } else return 1; - } + if (style[ivar] == EQUAL || style[ivar] == TIMER || style[ivar] == INTERNAL) return 1; + if ((style[ivar] == PYTHON) && (python->function_match(data[ivar][0], names[ivar], 1, error) >= 0)) + return 1; + return 0; } @@ -1103,22 +1082,7 @@ char *Variable::retrieve(const char *name) str = data[ivar][1] = utils::strdup(result); } else if (style[ivar] == PYTHON) { - int ifunc = python->function_match(data[ivar][0],name,0); - if (ifunc < 0) { - if (ifunc == -1) { - error->all(FLERR, "Python function {} specified by variable {} not found", - data[ivar][0], name); - } else if (ifunc == -2) { - error->all(FLERR, "Python function {} for variable {} does not return a value", - data[ivar][0], name); - } else if (ifunc == -3) { - error->all(FLERR, "Python function {} and variable {} do not not link to each other", - data[ivar][0], name); - } else { - error->all(FLERR, "Unknown error linking Python function {} to variable {}", - data[ivar][0],name); - } - } + int ifunc = python->function_match(data[ivar][0],name,0,error); python->invoke_function(ifunc,data[ivar][1],nullptr); str = data[ivar][1]; @@ -4208,32 +4172,7 @@ int Variable::math_function(char *word, char *contents, Tree **tree, Tree **tree // jvars = returned indices of narg internal variables used by Python function int *jvars = new int[narg]; - pyindex[pyvar] = python->wrapper_match(data[pyvar][0],names[pyvar],narg,jvars); - if (pyindex[pyvar] < 0) { - int ierror = pyindex[pyvar]; - if (ierror == -1) { - error->all(FLERR, "Python function {} specified by variable {} not found", - data[ivar][0], names[ivar]); - } else if (ierror == -2) { - error->all(FLERR, "Python function {} for variable {} does not return a value", - data[ivar][0], names[ivar]); - } else if (ierror == -3) { - error->all(FLERR, "Python function {} and variable {} do not not link to each other", - data[ivar][0], names[ivar]); - } else if (ierror == -4) { - error->all(FLERR, "Python function {} for variable {} returns a string", - data[ivar][0], names[ivar]); - } else if (ierror == -5) { - error->all(FLERR, "Python function {} does not use {} internal variable args", - data[ivar][0], narg); - } else if (ierror == -6) { - error->all(FLERR,"Python function {} cannot find an internal variable", - data[ivar][0]); - } else { - error->all(FLERR, "Unknown error linking Python function {} to variable {}", - data[ivar][0],names[ivar]); - } - } + pyindex[pyvar] = python->wrapper_match(data[pyvar][0],names[pyvar],narg,jvars,error); // if tree: store python variable and arg info in tree for later eval // else: one-time eval of python-coded function now via python variable From 1b12f3b47eb679debb004a436d5c817927c19cc1 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 00:19:56 -0400 Subject: [PATCH 17/23] improve error messages for python command --- src/PYTHON/python_impl.cpp | 120 ++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 47 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index 483038cc1c..e94734cbf2 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -95,20 +95,27 @@ PythonImpl::PythonImpl(LAMMPS *lmp) : Pointers(lmp) // Inform python intialization scheme of the mliappy module. // This -must- happen before python is initialized. int err = PyImport_AppendInittab("mliap_model_python_couple", PyInit_mliap_model_python_couple); - if (err) error->all(FLERR, "Could not register MLIAPPY embedded python module."); + if (err) + error->all(FLERR, Error::NOLASTLINE, "Could not register MLIAPPY embedded python module."); err = PyImport_AppendInittab("mliap_unified_couple", PyInit_mliap_unified_couple); - if (err) error->all(FLERR, "Could not register MLIAPPY unified embedded python module."); + if (err) + error->all(FLERR, Error::NOLASTLINE, + "Could not register MLIAPPY unified embedded python module."); #ifdef LMP_KOKKOS // Inform python intialization scheme of the mliappy module. // This -must- happen before python is initialized. err = PyImport_AppendInittab("mliap_model_python_couple_kokkos", PyInit_mliap_model_python_couple_kokkos); - if (err) error->all(FLERR, "Could not register MLIAPPY embedded python KOKKOS module."); + if (err) + error->all(FLERR, Error::NOLASTLINE, + "Could not register MLIAPPY embedded python KOKKOS module."); err = PyImport_AppendInittab("mliap_unified_couple_kokkos", PyInit_mliap_unified_couple_kokkos); - if (err) error->all(FLERR, "Could not register MLIAPPY unified embedded python KOKKOS module."); + if (err) + error->all(FLERR, Error::NOLASTLINE, + "Could not register MLIAPPY unified embedded python KOKKOS module."); #endif } #endif @@ -130,7 +137,7 @@ PythonImpl::PythonImpl(LAMMPS *lmp) : Pointers(lmp) PyUtils::GIL lock; PyObject *pModule = PyImport_AddModule("__main__"); - if (!pModule) error->all(FLERR, "Could not initialize embedded Python"); + if (!pModule) error->all(FLERR, Error::NOLASTLINE, "Could not initialize embedded Python"); pyMain = (void *) pModule; } @@ -163,16 +170,17 @@ void PythonImpl::command(int narg, char **arg) if (narg == 2 && strcmp(arg[1], "invoke") == 0) { int ifunc = find(arg[0]); - if (ifunc < 0) error->all(FLERR, "Python invoke of unknown function: {}", arg[0]); + if (ifunc < 0) + error->all(FLERR, Error::ARGZERO, "Python invoke of unknown function: {}", arg[0]); char *str = nullptr; if (pfuncs[ifunc].noutput) { str = input->variable->pythonstyle(pfuncs[ifunc].ovarname, pfuncs[ifunc].name); if (!str) - error->all(FLERR, - "Python variable {} does not match variable {} " - "registered with Python function {}", - arg[0], pfuncs[ifunc].ovarname, pfuncs[ifunc].name); + error->all( + FLERR, Error::ARGZERO, + "Python variable {} does not match variable {} registered with Python function {}", + arg[0], pfuncs[ifunc].ovarname, pfuncs[ifunc].name); } // NOTE to Richard - if this function returns a value, @@ -200,7 +208,7 @@ void PythonImpl::command(int narg, char **arg) if (platform::file_is_readable(arg[1])) err = execute_file(arg[1]); else - error->all(FLERR, "Could not open python source file {} for processing", arg[1]); + error->all(FLERR, 1, "Could not open python source file {} for processing", arg[1]); } if (err) error->all(FLERR, "Failure in python source command"); @@ -224,7 +232,8 @@ void PythonImpl::command(int narg, char **arg) if (strcmp(arg[iarg], "input") == 0) { if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "python input", error); ninput = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); - if (ninput < 0) error->all(FLERR, "Invalid number of python input arguments: {}", ninput); + if (ninput < 0) + error->all(FLERR, iarg + 1, "Invalid number of python input arguments: {}", ninput); iarg += 2; delete[] istr; istr = new char *[ninput]; @@ -243,7 +252,8 @@ void PythonImpl::command(int narg, char **arg) } else if (strcmp(arg[iarg], "length") == 0) { if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "python length", error); length_longstr = utils::inumeric(FLERR, arg[iarg + 1], false, lmp); - if (length_longstr <= 0) error->all(FLERR, "Invalid python return value length"); + if (length_longstr <= 0) + error->all(FLERR, iarg + 1, "Invalid python return value length {}", length_longstr); iarg += 2; } else if (strcmp(arg[iarg], "file") == 0) { if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "python file", error); @@ -258,15 +268,18 @@ void PythonImpl::command(int narg, char **arg) existflag = 1; iarg++; } else - error->all(FLERR, "Unknown python command keyword: {}", arg[iarg]); + error->all(FLERR, iarg, "Unknown python command keyword: {}", arg[iarg]); } if (pyfile && herestr) - error->all(FLERR, "Must not use python 'file' and 'here' keywords at the same time"); + error->all(FLERR, Error::NOLASTLINE, + "Must not use python 'file' and 'here' keywords at the same time"); if (pyfile && existflag) - error->all(FLERR, "Must not use python 'file' and 'exists' keywords at the same time"); + error->all(FLERR, Error::NOLASTLINE, + "Must not use python 'file' and 'exists' keywords at the same time"); if (herestr && existflag) - error->all(FLERR, "Must not use python 'here' and 'exists' keywords at the same time"); + error->all(FLERR, Error::NOLASTLINE, + "Must not use python 'here' and 'exists' keywords at the same time"); // create or overwrite entry in pfuncs vector with name = arg[0] @@ -284,13 +297,13 @@ void PythonImpl::command(int narg, char **arg) if (fp == nullptr) { PyUtils::Print_Errors(); - error->all(FLERR, "Could not open Python file: {}", pyfile); + error->all(FLERR, Error::NOLASTLINE, "Could not open Python file: {}", pyfile); } int err = PyRun_SimpleFile(fp, pyfile); if (err) { PyUtils::Print_Errors(); - error->all(FLERR, "Could not process Python file: {}", pyfile); + error->all(FLERR, Error::NOLASTLINE, "Could not process Python file: {}", pyfile); } fclose(fp); @@ -298,7 +311,7 @@ void PythonImpl::command(int narg, char **arg) int err = PyRun_SimpleString(herestr); if (err) { PyUtils::Print_Errors(); - error->all(FLERR, "Could not process Python string: {}", herestr); + error->all(FLERR, Error::NOLASTLINE, "Could not process Python string: {}", herestr); } } @@ -309,12 +322,12 @@ void PythonImpl::command(int narg, char **arg) if (!pFunc) { PyUtils::Print_Errors(); - error->all(FLERR, "Could not find Python function {}", pfuncs[ifunc].name); + error->all(FLERR, Error::NOLASTLINE, "Could not find Python function {}", pfuncs[ifunc].name); } if (!PyCallable_Check(pFunc)) { PyUtils::Print_Errors(); - error->all(FLERR, "Python function {} is not callable", pfuncs[ifunc].name); + error->all(FLERR, Error::NOLASTLINE, "Python function {} is not callable", pfuncs[ifunc].name); } pfuncs[ifunc].pFunc = (void *) pFunc; @@ -342,7 +355,8 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) PyObject *pArgs = PyTuple_New(ninput); if (!pArgs) - error->all(FLERR, "Could not prepare arguments for Python function {}", pfuncs[ifunc].name); + error->all(FLERR, Error::NOLASTLINE, "Could not prepare arguments for Python function {}", + pfuncs[ifunc].name); for (int i = 0; i < ninput; i++) { int itype = pfuncs[ifunc].itype[i]; @@ -350,8 +364,9 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) - error->all(FLERR, "Could not evaluate Python function {} input variable: {}", - pfuncs[ifunc].name, pfuncs[ifunc].svalue[i]); + error->all(FLERR, Error::NOLASTLINE, + "Could not evaluate Python function {} input variable: {}", pfuncs[ifunc].name, + pfuncs[ifunc].svalue[i]); pValue = PY_INT_FROM_LONG(PY_LONG_FROM_STRING(str)); } else if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { double value = input->variable->compute_equal(pfuncs[ifunc].internal_var[i]); @@ -363,8 +378,9 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) - error->all(FLERR, "Could not evaluate Python function {} input variable: {}", - pfuncs[ifunc].name, pfuncs[ifunc].svalue[i]); + error->all(FLERR, Error::NOLASTLINE, + "Could not evaluate Python function {} input variable: {}", pfuncs[ifunc].name, + pfuncs[ifunc].svalue[i]); pValue = PyFloat_FromDouble(std::stod(str)); } else if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { double value = input->variable->compute_equal(pfuncs[ifunc].internal_var[i]); @@ -376,16 +392,17 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (pfuncs[ifunc].ivarflag[i] == VARIABLE) { str = input->variable->retrieve(pfuncs[ifunc].svalue[i]); if (!str) - error->all(FLERR, "Could not evaluate Python function {} input variable: {}", - pfuncs[ifunc].name, pfuncs[ifunc].svalue[i]); + error->all(FLERR, Error::NOLASTLINE, + "Could not evaluate Python function {} input variable: {}", pfuncs[ifunc].name, + pfuncs[ifunc].svalue[i]); pValue = PyUnicode_FromString(str); } else { pValue = PyUnicode_FromString(pfuncs[ifunc].svalue[i]); } } else if (itype == PTR) { - pValue = PyCapsule_New((void *)lmp, nullptr, nullptr); + pValue = PyCapsule_New((void *) lmp, nullptr, nullptr); } else { - error->all(FLERR, "Unsupported variable type: {}", itype); + error->all(FLERR, Error::NOLASTLINE, "Unsupported variable type: {}", itype); } PyTuple_SetItem(pArgs, i, pValue); } @@ -398,7 +415,8 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (!pValue) { PyUtils::Print_Errors(); - error->one(FLERR, "Python evaluation of function {} failed", pfuncs[ifunc].name); + error->one(FLERR, Error::NOLASTLINE, "Python evaluation of function {} failed", + pfuncs[ifunc].name); } // function returned a value @@ -409,13 +427,15 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (pfuncs[ifunc].noutput) { int otype = pfuncs[ifunc].otype; if (otype == INT) { - if (dvalue) *dvalue = (double) PY_INT_AS_LONG(pValue); + if (dvalue) + *dvalue = (double) PY_INT_AS_LONG(pValue); else { auto value = fmt::format("{}", PY_INT_AS_LONG(pValue)); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); } } else if (otype == DOUBLE) { - if (dvalue) *dvalue = PyFloat_AsDouble(pValue); + if (dvalue) + *dvalue = PyFloat_AsDouble(pValue); else { auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); @@ -545,9 +565,10 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon pfuncs[ifunc].noutput = noutput; if (!format && ninput + noutput) - error->all(FLERR, "Missing python format keyword"); + error->all(FLERR, Error::NOLASTLINE, "Missing python format keyword"); else if (format && ((int) strlen(format) != ninput + noutput)) - error->all(FLERR, "Input/output arguments ({}) and format characters ({}) are inconsistent", + error->all(FLERR, Error::NOLASTLINE, + "Input/output arguments ({}) and format characters ({}) are inconsistent", (ninput + noutput), strlen(format)); // process inputs as values or variables @@ -572,12 +593,13 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 3); char *vname = pfuncs[ifunc].svalue[i]; int ivar = input->variable->find(vname); - if (ivar < 0) { // create internal variable if does not exist - input->variable->internal_create(vname,0.0); + if (ivar < 0) { // create internal variable if does not exist + input->variable->internal_create(vname, 0.0); ivar = input->variable->find(vname); } if (!input->variable->internalstyle(ivar)) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", vname); + error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", + vname); } else { pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].ivalue[i] = utils::inumeric(FLERR, istr[i], false, lmp); @@ -592,12 +614,13 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 3); char *vname = pfuncs[ifunc].svalue[i]; int ivar = input->variable->find(vname); - if (ivar < 0) { // create internal variable if does not exist - input->variable->internal_create(vname,0.0); + if (ivar < 0) { // create internal variable if does not exist + input->variable->internal_create(vname, 0.0); ivar = input->variable->find(vname); } if (!input->variable->internalstyle(ivar)) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", vname); + error->all(FLERR, Error::NOLASTLINE, "Variable {} for python command is invalid style", + vname); } else { pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].dvalue[i] = utils::numeric(FLERR, istr[i], false, lmp); @@ -608,7 +631,8 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon pfuncs[ifunc].ivarflag[i] = VARIABLE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i] + 2); } else if (utils::strmatch(istr[i], "^iv_")) { - error->all(FLERR, "Input argument {} cannot be internal variable with string format", istr[i]); + error->all(FLERR, Error::NOLASTLINE, + "Input argument {} cannot be internal variable with string format", istr[i]); } else { pfuncs[ifunc].ivarflag[i] = VALUE; pfuncs[ifunc].svalue[i] = utils::strdup(istr[i]); @@ -619,7 +643,7 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon if (strcmp(istr[i], "SELF") != 0) error->all(FLERR, "Invalid python command"); } else - error->all(FLERR, "Invalid python format character: {}", type); + error->all(FLERR, Error::NOLASTLINE, "Invalid python format character: {}", type); } // process output as value or variable @@ -636,17 +660,19 @@ int PythonImpl::create_entry(char *name, int ninput, int noutput, int length_lon else if (type == 's') pfuncs[ifunc].otype = STRING; else - error->all(FLERR, "Invalid python return format character: {}", type); + error->all(FLERR, Error::NOLASTLINE, "Invalid python return format character: {}", type); if (length_longstr) { if (pfuncs[ifunc].otype != STRING) - error->all(FLERR, "Python command length keyword cannot be used unless output is a string"); + error->all(FLERR, Error::NOLASTLINE, + "Python command length keyword cannot be used unless output is a string"); pfuncs[ifunc].length_longstr = length_longstr; pfuncs[ifunc].longstr = new char[length_longstr + 1]; pfuncs[ifunc].longstr[length_longstr] = '\0'; } - if (strstr(ostr, "v_") != ostr) error->all(FLERR, "Invalid python command"); + if (strstr(ostr, "v_") != ostr) + error->all(FLERR, Error::NOLASTLINE, "Invalid python output variable name {}", ostr); pfuncs[ifunc].ovarname = utils::strdup(ostr + 2); return ifunc; From 001fa6a0249665bfd7d5a5ddbb3dc9f1c541269f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 00:28:18 -0400 Subject: [PATCH 18/23] corrections from GitHub copilot --- src/PYTHON/python_impl.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index e94734cbf2..c4dd1cf9b8 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -483,7 +483,7 @@ int PythonImpl::function_match(const char *name, const char *varname, int numeri "Python function {} for variable {} does not return a value", name, varname); if (strcmp(pfuncs[ifunc].ovarname, varname) != 0) error->all(FLERR, Error::NOLASTLINE, - "Python function {} and variable {} do not not link to each other", name, varname); + "Python function {} and variable {} do not link to each other", name, varname); if (numeric && pfuncs[ifunc].otype == STRING) error->all(FLERR, Error::NOLASTLINE, "Python function {} for variable {} returns a string", name, varname); @@ -526,8 +526,9 @@ int PythonImpl::wrapper_match(const char *name, const char *varname, int narg, i for (int i = 0; i < pfuncs[ifunc].ninput; i++) { if (pfuncs[ifunc].ivarflag[i] == INTERNALVAR) { int ivar = input->variable->find(pfuncs[ifunc].svalue[i]); - error->all(FLERR, Error::NOLASTLINE, "Python function {} cannot find internal variable {}", - name, pfuncs[ifunc].svalue[i]); + if (ivar < 0) + error->all(FLERR, Error::NOLASTLINE, "Python function {} cannot find internal variable {}", + name, pfuncs[ifunc].svalue[i]); pfuncs[ifunc].internal_var[i] = ivar; argvars[j++] = ivar; } From 65debaf191a8e3afed04744623a5458eae29b170 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 02:02:04 -0400 Subject: [PATCH 19/23] resolve NOTES and add option to print return value to log with python invoke --- doc/src/python.rst | 40 +++++++++++++++++++++----------------- src/PYTHON/python_impl.cpp | 37 +++++++++++++++++------------------ 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/doc/src/python.rst b/doc/src/python.rst index c23b44d5d8..cc9be0c125 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -28,7 +28,8 @@ Syntax one or more keywords with/without arguments must be appended keyword = *invoke* or *input* or *return* or *format* or *length* or *file* or *here* or *exists* - *invoke* arg = none = invoke the previously-defined Python function + *invoke* arg = invoke the previously-defined Python function + logreturn = log return value of the invoked python function, if defined (optional) *input* args = N i1 i2 ... iN N = # of inputs to function i1,...,iN = value, SELF, or LAMMPS variable name @@ -58,7 +59,7 @@ Examples .. code-block:: LAMMPS python pForce input 2 v_x 20.0 return v_f format fff file force.py - python pForce invoke + python pForce invoke logreturn python factorial input 1 myN return v_fac format ii here """ def factorial(n): @@ -165,18 +166,19 @@ that will be registered with LAMMPS for future execution. The function may already be defined (see *exists* keyword) or must be defined using the *file* or *here* keywords as explained below. -If the *invoke* keyword is used, no other keywords can be used, and a -previous *python* command must have registered the Python function -referenced by this command. This invokes the Python function with the -previously defined arguments and the return value is processed as -explained below. You can invoke a registered function as many times -as you wish in your input script. - -NOTE for Richard: As indicated with a NOTE in python_impl.cpp, I don't -think there is any access to a value returned by invoking a Py -function in this way. If that is correct, I think this should be -clarified in the doc page, with a better explanation of the utility of -using the *invoke* keyword. +If the *invoke* keyword is used, only the optional *logreturn* keyword +can be used. A previous *python* command must have registered the +Python function referenced by this command. The command invokes the +Python function with the previously defined arguments. A return value +of the Python function will be ignored unless the Python function is +linked to a :doc:`python style variable ` with the *return* +keyword. This return value can be logged to the screen and logfile by +adding the *logreturn* keyword to the *invoke* command. In that case a +message with the name of the python command and the return value is +printed. Return values of python functions are otherwise only +accessible when the function is invoked indirectly by expanding a +:doc:`python style variable `. You can invoke a registered +function as many times as you wish in your input script. The *input* keyword defines how many arguments *N* the Python function expects. If it takes no arguments, then the *input* keyword should @@ -213,10 +215,9 @@ defined, this command creates an :doc:`internal-style variable An internal-style variable must be used when an equal-style, vector-style, or atom-style variable triggers the invocation of the -Python function defined by this command, by including a Python -function wrapper with arguments in its formula. Each of the arguments -must be specified as an internal-style variable via the *input* -keyword. +Python function defined by this command, by including a Python function +wrapper with arguments in its formula. Each of the arguments must be +specified as an internal-style variable via the *input* keyword. In brief, the syntax for a Python function wrapper in a variable formula is py_varname(arg1,arg2,...argN), where "varname" is the name @@ -246,6 +247,9 @@ value. The specified *varReturn* is of the form v_name, where "name" is the name of a python-style LAMMPS variable, defined by the :doc:`variable ` command. The Python function can return a numeric or string value, as specified by the *format* keyword. +This return value is *only* accessible when expanding the python-style +variable. When the *invoke* keyword is used, the return value of +the python function is ignored. ---------- diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index c4dd1cf9b8..694a79f06a 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -17,6 +17,7 @@ #include "python_impl.h" +#include "comm.h" #include "error.h" #include "input.h" #include "memory.h" @@ -166,9 +167,9 @@ void PythonImpl::command(int narg, char **arg) { if (narg < 2) utils::missing_cmd_args(FLERR, "python", error); - // if invoke is only keyword, invoke the previously defined function + // if invoke keyword is used, invoke the previously defined function - if (narg == 2 && strcmp(arg[1], "invoke") == 0) { + if (strcmp(arg[1], "invoke") == 0) { int ifunc = find(arg[0]); if (ifunc < 0) error->all(FLERR, Error::ARGZERO, "Python invoke of unknown function: {}", arg[0]); @@ -183,17 +184,17 @@ void PythonImpl::command(int narg, char **arg) arg[0], pfuncs[ifunc].ovarname, pfuncs[ifunc].name); } - // NOTE to Richard - if this function returns a value, - // I don't believe it will be accessible to the input script - // b/c that only occurs if Variable::retreive() or Variable::compute_equal() is called - // so if a Python function with a return is invoked this way, - // it might be better to issue an error or warning ? - // i.e. it only make sense to call a setup-style kind of Python function this way - // one with no return value - // it also means there is no need to call Variable::pythonstyle() here - // or even define the pythonstyle() method in Variable ? + bool logreturn = false; + if (narg == 3 && strcmp(arg[2], "logreturn") == 0) logreturn = true; + + char *str = nullptr; + if (logreturn && pfuncs[ifunc].noutput) str = new char[Variable::VALUELENGTH]; invoke_function(ifunc, str, nullptr); + if (logreturn && str && (comm->me == 0)) + utils::logmesg(lmp, "Invoked python function {} returned {}\n", arg[0], str); + + delete[] str; return; } @@ -427,16 +428,14 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) if (pfuncs[ifunc].noutput) { int otype = pfuncs[ifunc].otype; if (otype == INT) { - if (dvalue) - *dvalue = (double) PY_INT_AS_LONG(pValue); - else { + if (dvalue) *dvalue = (double) PY_INT_AS_LONG(pValue); + if (result) { auto value = fmt::format("{}", PY_INT_AS_LONG(pValue)); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); } } else if (otype == DOUBLE) { - if (dvalue) - *dvalue = PyFloat_AsDouble(pValue); - else { + if (dvalue) *dvalue = PyFloat_AsDouble(pValue); + if (result) { auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); } @@ -444,10 +443,10 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue) const char *pystr = PyUnicode_AsUTF8(pValue); if (pfuncs[ifunc].longstr) strncpy(pfuncs[ifunc].longstr, pystr, pfuncs[ifunc].length_longstr); - else - strncpy(result, pystr, Variable::VALUELENGTH - 1); + if (result) strncpy(result, pystr, Variable::VALUELENGTH - 1); } } + Py_CLEAR(pValue); } From 4cb936c3c694a8905fa408d0e2285876efb661f4 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 02:17:39 -0400 Subject: [PATCH 20/23] no need to allocate since we use the space of the python style variable, which is required --- src/PYTHON/python_impl.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/PYTHON/python_impl.cpp b/src/PYTHON/python_impl.cpp index 694a79f06a..4ed208fb2f 100644 --- a/src/PYTHON/python_impl.cpp +++ b/src/PYTHON/python_impl.cpp @@ -187,14 +187,11 @@ void PythonImpl::command(int narg, char **arg) bool logreturn = false; if (narg == 3 && strcmp(arg[2], "logreturn") == 0) logreturn = true; - char *str = nullptr; - if (logreturn && pfuncs[ifunc].noutput) str = new char[Variable::VALUELENGTH]; - invoke_function(ifunc, str, nullptr); + if (logreturn && str && (comm->me == 0)) utils::logmesg(lmp, "Invoked python function {} returned {}\n", arg[0], str); - delete[] str; return; } From 84f20fe5164f9880c4016df9c1db36a74f9d015d Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 05:59:03 -0400 Subject: [PATCH 21/23] must initialize pyindex[ivar] in equalstyle check --- src/variable.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/variable.cpp b/src/variable.cpp index 5f848913c8..b6cf99d363 100644 --- a/src/variable.cpp +++ b/src/variable.cpp @@ -597,6 +597,7 @@ void Variable::set(int narg, char **arg) num[nvar] = 2; which[nvar] = 1; pad[nvar] = 0; + pyindex[nvar] = -1; data[nvar] = new char *[num[nvar]]; data[nvar][0] = utils::strdup(arg[2]); data[nvar][1] = new char[VALUELENGTH]; @@ -960,8 +961,10 @@ void Variable::python_command(int narg, char **arg) int Variable::equalstyle(int ivar) { if (style[ivar] == EQUAL || style[ivar] == TIMER || style[ivar] == INTERNAL) return 1; - if ((style[ivar] == PYTHON) && (python->function_match(data[ivar][0], names[ivar], 1, error) >= 0)) - return 1; + if (style[ivar] == PYTHON) { + pyindex[ivar] = python->function_match(data[ivar][0], names[ivar], 1, error); + if (pyindex[ivar] >= 0) return 1; + } return 0; } From 3f685e9a446f622e50c2f6a970ca3f4210992d7a Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 6 Jun 2025 12:13:02 -0400 Subject: [PATCH 22/23] fix several spelling and formatting issues in added/modified documentation --- doc/src/python.rst | 157 +++++++++++++++++++++---------------------- doc/src/variable.rst | 28 ++++---- 2 files changed, 91 insertions(+), 94 deletions(-) diff --git a/doc/src/python.rst b/doc/src/python.rst index cc9be0c125..0652420fe5 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -92,28 +92,26 @@ Description The *python* command interfaces LAMMPS with an embedded Python interpreter and enables executing arbitrary python code in that -interpreter. This can be done immediately, by using *mode* = -*source*. Or execution can be deferred, by registering a Python -function for later execution, by using *mode* = *name* of a Python -function. +interpreter. This can be done immediately, by using *mode* = *source*. +Or execution can be deferred, by registering a Python function for later +execution, by using *mode* = *name* of a Python function. -Later execution can be triggered in one of two ways. One is to use -the python command again with its *invoke* keyword. The other is to -trigger the evaluation of a python-style, equal-style, vector-style, -or atom-style variable. A python-style variable invokes its -associated Python function; its return value becomes the value of the -python-style variable. Equal-, vector-, and atom-style variables can -use a Python function wrapper in their formulas which encodes the -Python function name, and specifies arguments to pass to the function. +Later execution can be triggered in one of two ways. One is to use the +python command again with its *invoke* keyword. The other is to trigger +the evaluation of a python-style, equal-style, vector-style, or +atom-style variable. A python-style variable invokes its associated +Python function; its return value becomes the value of the python-style +variable. Equal-, vector-, and atom-style variables can use a Python +function wrapper in their formulas which encodes the Python function +name, and specifies arguments to pass to the function. -Note that python-style, equal-style, vectir-style, and atom-style -variables can be used in many different ways within LAMMPS. They can -be evaulated directly in an input script, effectively replacing the +Note that python-style, equal-style, vector-style, and atom-style +variables can be used in many different ways within LAMMPS. They can be +evaluated directly in an input script, effectively replacing the variable with its value. Or they can be passed to various commands as arguments, so that the variable is evaluated multiple times during a -simulation run. See the :doc:`variable ` command doc page -for more details on variable styles which enable Python function -evaluation. +simulation run. See the :doc:`variable ` command doc page for +more details on variable styles which enable Python function evaluation. The Python code for the function can be included directly in the input script or in a separate Python file. The function can be standard @@ -219,21 +217,20 @@ Python function defined by this command, by including a Python function wrapper with arguments in its formula. Each of the arguments must be specified as an internal-style variable via the *input* keyword. -In brief, the syntax for a Python function wrapper in a variable -formula is py_varname(arg1,arg2,...argN), where "varname" is the name -of a python-style variable associated with a Python function defined -by this command. One or more arguments to the function wrapper can -themselves be sub-formulas which the variable command will evaluate -and pass as arguments to the Python function. This is done by -assigning the numeric result for each argument to an internal-style -variable; thus the *input* keyword must specify the arguments as -internal-style variables and their format (see below) as "f" for -floating point. This is because LAMMPS variable formulas are -calculated with floating point arithmetic (any integer values are -converted to floating point). Note that the Python function can also -have additional inputs, also specified by the *input* keyword, which -are NOT arguments in the Python function wrapper. See the example -below for the "mixedargs" Python function. +In brief, the syntax for a Python function wrapper in a variable formula +is ``py_varname(arg1,arg2,...argN)``, where "varname" is the name of a +python-style variable associated with a Python function defined by this +command. One or more arguments to the function wrapper can themselves +be sub-formulas which the variable command will evaluate and pass as +arguments to the Python function. This is done by assigning the numeric +result for each argument to an internal-style variable; thus the *input* +keyword must specify the arguments as internal-style variables and their +format (see below) as "f" for floating point. This is because LAMMPS +variable formulas are calculated with floating point arithmetic (any +integer values are converted to floating point). Note that the Python +function can also have additional inputs, also specified by the *input* +keyword, which are NOT arguments in the Python function wrapper. See +the example below for the ``mixedargs`` Python function. See the :doc:`variable ` command doc page for full details on formula syntax including for Python function wrappers. Examples @@ -561,11 +558,11 @@ simulation run at each time step using the :doc:`fix python/invoke ---------- A Python function can also be invoked within the formula for an -equal-style, vector-style, or atom-style varaible. This means the +equal-style, vector-style, or atom-style variable. This means the Python function will be invoked whenever the variable is invoked. In -the case of a vector-style variable, the Python function can be -invoked once per element of the global vector. In the case of an -atom-style variable, the Python function can be invoked once per atom. +the case of a vector-style variable, the Python function can be invoked +once per element of the global vector. In the case of an atom-style +variable, the Python function can be invoked once per atom. Here are three simple examples using equal-, vector-, and atom-style variables to trigger execution of a Python function: @@ -574,15 +571,15 @@ variables to trigger execution of a Python function: variable foo python truncate python truncate return v_foo input 1 iv_arg format fi here """ -def truncate(x): - return int(x) -""" + def truncate(x): + return int(x) + """ variable ptrunc equal py_foo(press) print "TRUNCATED pressure = ${ptrunc}" -The Python "truncate" function simply converts a floating-point value +The Python ``truncate`` function simply converts a floating-point value to an integer value. When the LAMMPS print command evaluates the -equal-style "ptrunc" variable, the current thermodynamic pressure is +equal-style ``ptrunc`` variable, the current thermodynamic pressure is passed to the Python function. The truncated value is output to the screen and logfile by the print command. Note that the *input* keyword for the *python* command, specifies an internal-style variable @@ -590,7 +587,7 @@ named "arg" as iv_arg which is required to invoke the Python function from a Python function wrapper. The last 2 lines can be replaced by these to define a vector-style -variable which invokes the same Python "truncate" function: +variable which invokes the same Python ``truncate`` function: .. code-block:: LAMMPS @@ -599,15 +596,15 @@ variable which invokes the same Python "truncate" function: variable ketrunc vector py_foo(v_ke) thermo_style custom step temp epair v_ketrunc[*6] -The vector-style variable "ketrunc" invokes the Python "truncate" -function on each of the 6 components of the global kinetic energy -tensor calculated by the :doc:`compute ke ` command. The -6 truncated values will be printed with thermo output to the screen -and log file. +The vector-style variable ``ketrunc`` invokes the Python ``truncate`` +function on each of the 6 components of the global kinetic energy tensor +calculated by the :doc:`compute ke ` command. The 6 +truncated values will be printed with thermo output to the screen and +log file. -Or the last 2 lines of the equal-style variable example can be -replaced by these to define atom-style variables which invoke the same -Python "truncate" function: +Or the last 2 lines of the equal-style variable example can be replaced +by these to define atom-style variables which invoke the same Python +``truncate`` function: .. code-block:: LAMMPS @@ -617,48 +614,48 @@ Python "truncate" function: dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc When the dump command invokes the 3 atom-style variables, their -arguments x,y,z to the Python function wrapper are the current -per-atom coordinates of each atom. The Python "truncate" function is -thus invoked 3 times for each atom, and the truncated coordinate -values for each atom are written to the dump file. +arguments x,y,z to the Python function wrapper are the current per-atom +coordinates of each atom. The Python ``truncate`` function is thus +invoked 3 times for each atom, and the truncated coordinate values for +each atom are written to the dump file. -Note that when using a Python function wrapper in a variable, -arguments can be passed to the Python function either from the -varaible formula or by *input* keyword to the *python command. For -example, consider these (made up) commands: +Note that when using a Python function wrapper in a variable, arguments +can be passed to the Python function either from the variable formula or +by *input* keyword to the :doc:`python command `. For example, +consider these (made up) commands: .. code-block:: LAMMPS variable foo python mixedargs python mixedargs return v_foo input 6 7.5 v_myValue iv_arg1 iv_argy iv_argz v_flag & format fffffsf here """ -def mixedargs(a,b,x,y,z,flag): - ... - return result -""" + def mixedargs(a,b,x,y,z,flag): + ... + return result + """ variable flag string optionABC variable myValue equal "2.0*temp*c_pe" compute pe all pe compute peatom all pe/atom variable field atom py_foo(x+3.0,sqrt(y),(z-zlo)*c_peatom) -They define a Python "mixedargs" function with 6 arguments. Three of -them are internal-style variables, which the variable formula -calculates as numeric values for each atom and passes to the function. -In this example, these arguments are themselves small formulas -containing the x,y,z coordinates of each atom as well as a per-atom -compute (c_peratom) and thermodynamic keyword (zlo). +They define a Python ``mixedargs`` function with 6 arguments. Three of +them are internal-style variables, which the variable formula calculates +as numeric values for each atom and passes to the function. In this +example, these arguments are themselves small formulas containing the +x,y,z coordinates of each atom as well as a per-atom compute (c_peratom) +and thermodynamic keyword (zlo). -The other three arguements (7.5,v_myValue,v_flag) are defined by the -*python* command. The first and last are constant values (7.5 and the -optionABC string). The second argument (myValue) is the result of an -equal-style variable formula which accesses the system temperature and -potential energy. +The other three arguments ``(7.5,v_myValue,v_flag)`` are defined by the +*python* command. The first and last are constant values ("7.5" and the +``optionABC`` string). The second argument (``myValue``) is the result +of an equal-style variable formula which accesses the system temperature +and potential energy. -The "result" returned by each invocation of the Python "mixedargs" -function becomes the per-atom value in the atom-style "field" -variable, which could be output to a dump file or used elsewhere in -the input script. +The "result" returned by each invocation of the Python ``mixedargs`` +function becomes the per-atom value in the atom-style "field" variable, +which could be output to a dump file or used elsewhere in the input +script. ---------- @@ -767,8 +764,8 @@ and global variables will become invisible. Related commands """""""""""""""" -:doc:`shell `, :doc:`variable `, :doc:`fix - python/invoke ` +:doc:`shell `, :doc:`variable `, +:doc:`fix python/invoke ` Default """"""" diff --git a/doc/src/variable.rst b/doc/src/variable.rst index 08afb64762..47ebdbce67 100644 --- a/doc/src/variable.rst +++ b/doc/src/variable.rst @@ -400,21 +400,21 @@ be assigned to the variable until a LAMMPS command sets it to a new value. Note however, that most commands which use internal-style variables do -not require them to be defined in the input script. They create one -or more internal-style variables if they do not already exist. -Examples are these commands: +not require them to be defined in the input script. They create one or +more internal-style variables if they do not already exist. Examples +are these commands: * :doc:`create_atoms ` * :doc:`fix deposit ` * :doc:`compute bond/local ` -* :doc:`compute angle/local ` +* :doc:`compute angle/local ` * :doc:`compute dihedral/local ` * :doc:`python ` command in conjunction with Python function wrappers used in equal- and atom-style variable formulas -A command which does require an internal-style variable to be defined -in the input script is the :doc:`fix controller ` command, +because another (arbitrary) command typically also references the +variable. ---------- @@ -1261,12 +1261,12 @@ The resulting per-atom vector for *xtrunc* will thus contain the truncated x-coord of every atom in the system. The dump command includes the truncated xyz coords for each atom in its output. -See the :doc:`python ' command for more details on options the -*python* command can specify as well as examples of more complex -Python functions which can be wrapped in this manner. In particular, -the Python function can take a variety of arguments, some generated by -the *python* command, and others by the arguments of the Python -function wrapper. +See the :doc:`python ` command for more details on options the +*python* command can specify as well as examples of more complex Python +functions which can be wrapped in this manner. In particular, the +Python function can take a variety of arguments, some generated by the +*python* command, and others by the arguments of the Python function +wrapper. ---------- From 72080d78a796aa5d5ee3aea150e3b06d1c08453a Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 8 Jun 2025 08:52:45 -0400 Subject: [PATCH 23/23] hide repetitive code use a macro --- src/create_atoms.cpp | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/create_atoms.cpp b/src/create_atoms.cpp index ebbe2f9035..5eb7e47a84 100644 --- a/src/create_atoms.cpp +++ b/src/create_atoms.cpp @@ -390,34 +390,23 @@ void CreateAtoms::command(int narg, char **arg) if (!input->variable->equalstyle(vvar)) error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", vstr); - if (xstr) { - xvar = input->variable->find(xstr); - if (xvar < 0) { - input->variable->internal_create(xstr,0.0); - xvar = input->variable->find(xstr); - } - if (!input->variable->internalstyle(xvar)) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", xstr); - } - if (ystr) { - yvar = input->variable->find(ystr); - if (yvar < 0) { - input->variable->internal_create(ystr,0.0); - yvar = input->variable->find(ystr); - } - if (!input->variable->internalstyle(yvar)) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", ystr); - } - if (zstr) { - zvar = input->variable->find(zstr); - if (zvar < 0) { - input->variable->internal_create(zstr,0.0); - zvar = input->variable->find(zstr); - } - if (!input->variable->internalstyle(zvar)) - error->all(FLERR, Error::NOLASTLINE, "Variable {} for create_atoms is invalid style", zstr); +#define SETUP_XYZ_VAR(str,var) \ + if (str) { \ + var = input->variable->find(str); \ + if (var < 0) { \ + input->variable->internal_create(str, 0.0); \ + var = input->variable->find(str); \ + } \ + if (!input->variable->internalstyle(var)) \ + error->all(FLERR, Error::NOLASTLINE, \ + "Variable {} for create_atoms is invalid style", str); \ } + + SETUP_XYZ_VAR(xstr, xvar); + SETUP_XYZ_VAR(ystr, yvar); + SETUP_XYZ_VAR(zstr, zvar); } +#undef SETUP_XYZ_VAR // require non-none lattice be defined for BOX or REGION styles