initial implementation of python functions in variable formulas, including for atom-style vars
This commit is contained in:
@ -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 <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 <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 <dump>` command) or used as
|
||||
input to an averaging fix (see the :doc:`fix ave/chunk
|
||||
<fix_ave_chunk>` and :doc:`fix ave/atom <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 <dump>` command) or used as input to an
|
||||
averaging fix (see the :doc:`fix ave/chunk <fix_ave_chunk>` and
|
||||
:doc:`fix ave/atom <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 <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 <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 |
|
||||
@ -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
|
||||
-----------------------
|
||||
|
||||
|
||||
@ -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;
|
||||
@ -3520,6 +3535,18 @@ 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++) {
|
||||
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()
|
||||
------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
@ -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)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user