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

This commit is contained in:
Steve Plimpton
2025-05-19 21:42:08 -06:00
parent 8fa4c0974c
commit 2351418c94
2 changed files with 87 additions and 42 deletions

View File

@ -98,20 +98,21 @@ function.
Later execution can be triggered in one of two ways. One is to use 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 the python command again with its *invoke* keyword. The other is to
trigger the evaluation of a python-style, equal-style, or atom-style trigger the evaluation of a python-style, equal-style, vector-style,
variable. A python-style variable invokes its associated Python or atom-style variable. A python-style variable invokes its
function; its return value becomes the value of the python-style associated Python function; its return value becomes the value of the
variable. Equal- and atom-style variables can use a Python function python-style variable. Equal-, vector-, and atom-style variables can
wrapper in their formulas which encodes the Python function name, and use a Python function wrapper in their formulas which encodes the
specifies arguments to pass to the function. Python function name, and specifies arguments to pass to the function.
Note python-style, equal-style, and atom-style variables can be used Note that python-style, equal-style, vectir-style, and atom-style
in many different ways within LAMMPS. They can be evaulated directly variables can be used in many different ways within LAMMPS. They can
in an input script, effectively replacing the variable with its value. be evaulated directly in an input script, effectively replacing the
Or they can be passed to various commands as arguments, so that the variable with its value. Or they can be passed to various commands as
variable is evaluated multiple times during a simulation run. See the arguments, so that the variable is evaluated multiple times during a
:doc:`variable <variable>` command doc page for more details on simulation run. See the :doc:`variable <variable>` command doc page
variable styles which enable Python function evaluation. for more details on variable styles which enable Python function
evaluation.
The Python code for the function can be included directly in the input 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 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. keyword specification for the type of data that is passed to Python.
Each time the Python function is invoked, the LAMMPS variable is Each time the Python function is invoked, the LAMMPS variable is
evaluated and its value is passed as an argument to the Python 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* A LAMMPS internal-style variable can also be used as an *input*
argument, specified as iv_name, where "name" is the name of the 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 defined, this command creates an :doc:`internal-style variable
<variable>` with the specified name. <variable>` with the specified name.
An internal-style variable must be used when an equal-style or An internal-style variable must be used when an equal-style,
atom-style variable triggers the invocation of the Python function vector-style, or atom-style variable triggers the invocation of the
defined by this command, by including a Python function wrapper in its Python function defined by this command, by including a Python
formula, with one or more arguments also included in the formula. 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 In brief, the syntax for a Python function wrapper in a variable
formula is py_varname(arg1,arg2,...argN), where "varname" is the name formula is py_varname(arg1,arg2,...argN), where "varname" is the name
of a python-style variable associated with a Python function defined of a python-style variable associated with a Python function defined
by this command. One or more arguments to the function wrapper can by this command. One or more arguments to the function wrapper can
themselves be formulas which the variable command will evaluate and themselves be sub-formulas which the variable command will evaluate
pass as arguments to the Python function. This is done by assigning and pass as arguments to the Python function. This is done by
the numeric result for each argument to an internal-style variable; assigning the numeric result for each argument to an internal-style
this the *input* keyword must specify the arguments as internal-style variable; thus the *input* keyword must specify the arguments as
variables and their format (see below) as "f" for floating point. internal-style variables and their format (see below) as "f" for
This is because LAMMPS variable formulas are calculated with floating floating point. This is because LAMMPS variable formulas are
point arithmetic (any integer values are converted to floating point). 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 <variable>` command doc page for full details See the :doc:`variable <variable>` command doc page for full details
on formula syntax including for Python function wrappers. Examples 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 The *return* keyword is only needed if the Python function returns a
value. The specified *varReturn* is of the form v_name, where "name" 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 A Python function can also be invoked within the formula for an
equal-style or atom-style varaible. This means the Python function equal-style, vector-style, or atom-style varaible. This means the
will be invoked whenever the variable is invoked. In the case of an Python function will be invoked whenever the variable is invoked. In
atom-style varaible, 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 two simple examples using equal- and atom-style variables to Here are three simple examples using equal-, vector-, and atom-style
trigger execution of a Python function: variables to trigger execution of a Python function:
.. code-block:: LAMMPS .. 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 named "arg" as iv_arg which is required to invoke the Python function
from a Python function wrapper. from a Python function wrapper.
The last 2 lines can be replaced by these to define atom-style The last 2 lines can be replaced by these to define a vector-style
varaibles which invoke the same Python "truncate" function: 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 <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 .. code-block:: LAMMPS
@ -581,7 +611,7 @@ varaibles which invoke the same Python "truncate" function:
variable ztrunc atom py_foo(z) variable ztrunc atom py_foo(z)
dump 1 all custom 100 tmp.dump id x y z v_xtrunc v_ytrunc v_ztrunc 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 arguments x,y,z to the Python function wrapper are the current
per-atom coordinates of each atom. The Python "truncate" function is per-atom coordinates of each atom. The Python "truncate" function is
thus invoked 3 times for each atom, and the truncated coordinate thus invoked 3 times for each atom, and the truncated coordinate

View File

@ -1460,7 +1460,7 @@ void Variable::copy(int narg, char **from, char **to)
sin(x),cos(x),tan(x),asin(x),atan2(y,x),... sin(x),cos(x),tan(x),asin(x),atan2(y,x),...
group function = count(group), mass(group), xcm(group,x), ... group function = count(group), mass(group), xcm(group,x), ...
special function = sum(x),min(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 value = x[i], y[i], vx[i], ...
atom vector = x, y, vx, ... atom vector = x, y, vx, ...
custom atom property = i/d_name, i/d_name[i], i/d2_name[i], i/d2_name[i][j] 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), 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), 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), 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) 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); tree->value = (arg1 >= 0.0) ? 1.0 : -1.0; // sign(arg1);
return tree->value; return tree->value;
} }
if (tree->type == PYWRAPPER) { if (tree->type == PYWRAPPER) {
int narg = tree->argcount; int narg = tree->argcount;
int *argvars = tree->argvars; int *argvars = tree->argvars;
@ -3231,6 +3232,16 @@ double Variable::collapse_tree(Tree *tree)
else arg = collapse_tree(tree->extra[iarg-2]); else arg = collapse_tree(tree->extra[iarg-2]);
internal_set(argvars[iarg],arg); 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); tree->value = compute_equal(tree->pyvar);
return tree->value; return tree->value;
} }
@ -3248,12 +3259,14 @@ double Variable::collapse_tree(Tree *tree)
evaluate an atom-style or vector-style variable parse tree evaluate an atom-style or vector-style variable parse tree
index I = atom I or vector index I index I = atom I or vector index I
tree was created by one-time parsing of formula string via evaluate() 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: customize by adding a function:
sqrt(),exp(),ln(),log(),sin(),cos(),tan(),asin(),acos(),atan(), 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), 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), 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), 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) 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 process a math function in formula
includes a Python function with syntax py_varname(arg1,arg2,...)
push result onto tree or arg stack push result onto tree or arg stack
word = math function name word = math function name
contents = str between parentheses with comma-separated args 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(), 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), 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), 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(x,y,z,...)
py_varname(arg1,arg2,...) (up to MAXFUNCARG)
------------------------------------------------------------------------- */ ------------------------------------------------------------------------- */
int Variable::math_function(char *word, char *contents, Tree **tree, Tree **treestack, 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) 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->first) print_tree(tree->first,level+1);
if (tree->second) print_tree(tree->second,level+1); if (tree->second) print_tree(tree->second,level+1);
if (tree->nextra) if (tree->nextra)