resolve NOTES and add option to print return value to log with python invoke

This commit is contained in:
Axel Kohlmeyer
2025-06-06 02:02:04 -04:00
parent 001fa6a024
commit 65debaf191
2 changed files with 40 additions and 37 deletions

View File

@ -28,7 +28,8 @@ Syntax
one or more keywords with/without arguments must be appended 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* 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 *input* args = N i1 i2 ... iN
N = # of inputs to function N = # of inputs to function
i1,...,iN = value, SELF, or LAMMPS variable name i1,...,iN = value, SELF, or LAMMPS variable name
@ -58,7 +59,7 @@ Examples
.. code-block:: LAMMPS .. code-block:: LAMMPS
python pForce input 2 v_x 20.0 return v_f format fff file force.py 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 """ python factorial input 1 myN return v_fac format ii here """
def factorial(n): 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 may already be defined (see *exists* keyword) or must be defined using
the *file* or *here* keywords as explained below. the *file* or *here* keywords as explained below.
If the *invoke* keyword is used, no other keywords can be used, and a If the *invoke* keyword is used, only the optional *logreturn* keyword
previous *python* command must have registered the Python function can be used. A previous *python* command must have registered the
referenced by this command. This invokes the Python function with the Python function referenced by this command. The command invokes the
previously defined arguments and the return value is processed as Python function with the previously defined arguments. A return value
explained below. You can invoke a registered function as many times of the Python function will be ignored unless the Python function is
as you wish in your input script. linked to a :doc:`python style variable <variable>` with the *return*
keyword. This return value can be logged to the screen and logfile by
NOTE for Richard: As indicated with a NOTE in python_impl.cpp, I don't adding the *logreturn* keyword to the *invoke* command. In that case a
think there is any access to a value returned by invoking a Py message with the name of the python command and the return value is
function in this way. If that is correct, I think this should be printed. Return values of python functions are otherwise only
clarified in the doc page, with a better explanation of the utility of accessible when the function is invoked indirectly by expanding a
using the *invoke* keyword. :doc:`python style variable <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 The *input* keyword defines how many arguments *N* the Python function
expects. If it takes no arguments, then the *input* keyword should 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, An internal-style variable must be used when an equal-style,
vector-style, or atom-style variable triggers the invocation of the vector-style, or atom-style variable triggers the invocation of the
Python function defined by this command, by including a Python Python function defined by this command, by including a Python function
function wrapper with arguments in its formula. Each of the arguments wrapper with arguments in its formula. Each of the arguments must be
must be specified as an internal-style variable via the *input* specified as an internal-style variable via the *input* keyword.
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
@ -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 is the name of a python-style LAMMPS variable, defined by the
:doc:`variable <variable>` command. The Python function can return a :doc:`variable <variable>` command. The Python function can return a
numeric or string value, as specified by the *format* keyword. 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.
---------- ----------

View File

@ -17,6 +17,7 @@
#include "python_impl.h" #include "python_impl.h"
#include "comm.h"
#include "error.h" #include "error.h"
#include "input.h" #include "input.h"
#include "memory.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 (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]); int ifunc = find(arg[0]);
if (ifunc < 0) if (ifunc < 0)
error->all(FLERR, Error::ARGZERO, "Python invoke of unknown function: {}", arg[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); arg[0], pfuncs[ifunc].ovarname, pfuncs[ifunc].name);
} }
// NOTE to Richard - if this function returns a value, bool logreturn = false;
// I don't believe it will be accessible to the input script if (narg == 3 && strcmp(arg[2], "logreturn") == 0) logreturn = true;
// 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, char *str = nullptr;
// it might be better to issue an error or warning ? if (logreturn && pfuncs[ifunc].noutput) str = new char[Variable::VALUELENGTH];
// 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); 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; return;
} }
@ -427,16 +428,14 @@ void PythonImpl::invoke_function(int ifunc, char *result, double *dvalue)
if (pfuncs[ifunc].noutput) { if (pfuncs[ifunc].noutput) {
int otype = pfuncs[ifunc].otype; int otype = pfuncs[ifunc].otype;
if (otype == INT) { if (otype == INT) {
if (dvalue) if (dvalue) *dvalue = (double) PY_INT_AS_LONG(pValue);
*dvalue = (double) PY_INT_AS_LONG(pValue); if (result) {
else {
auto value = fmt::format("{}", PY_INT_AS_LONG(pValue)); auto value = fmt::format("{}", PY_INT_AS_LONG(pValue));
strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); strncpy(result, value.c_str(), Variable::VALUELENGTH - 1);
} }
} else if (otype == DOUBLE) { } else if (otype == DOUBLE) {
if (dvalue) if (dvalue) *dvalue = PyFloat_AsDouble(pValue);
*dvalue = PyFloat_AsDouble(pValue); if (result) {
else {
auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue)); auto value = fmt::format("{:.15g}", PyFloat_AsDouble(pValue));
strncpy(result, value.c_str(), Variable::VALUELENGTH - 1); 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); const char *pystr = PyUnicode_AsUTF8(pValue);
if (pfuncs[ifunc].longstr) if (pfuncs[ifunc].longstr)
strncpy(pfuncs[ifunc].longstr, pystr, pfuncs[ifunc].length_longstr); strncpy(pfuncs[ifunc].longstr, pystr, pfuncs[ifunc].length_longstr);
else if (result) strncpy(result, pystr, Variable::VALUELENGTH - 1);
strncpy(result, pystr, Variable::VALUELENGTH - 1);
} }
} }
Py_CLEAR(pValue); Py_CLEAR(pValue);
} }