refactor handling of the python source command. document it and more limits.

This commit is contained in:
Axel Kohlmeyer
2022-11-10 16:03:06 -05:00
parent dd8c1df9c2
commit 34a5093229
3 changed files with 141 additions and 117 deletions

View File

@ -8,14 +8,24 @@ Syntax
.. parsed-literal:: .. parsed-literal::
python func keyword args ... python function keyword args ...
* func = name of Python function * function = *source* or name of Python function
* one or more keyword/args pairs must be appended
if function is *source*:
.. parsed-literal:: .. parsed-literal::
keyword = *invoke* or *input* or *return* or *format* or *length* or *file* or *here* or *exists* or *source* keyword = *inline* or name of a Python file
inline = one or more lines of Python code which will be executed immediately
must be a single argument, typically enclosed in triple quotes
Python file = name of a file with Python code which will be executed immediately
* if function is the name of a Python function, one or more keyword/args pairs must be appended
.. parsed-literal::
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 *input* args = N i1 i2 ... iN
N = # of inputs to function N = # of inputs to function
@ -38,10 +48,6 @@ Syntax
inline = one or more lines of Python code which defines func inline = one or more lines of Python code which defines func
must be a single argument, typically enclosed between triple quotes must be a single argument, typically enclosed between triple quotes
*exists* arg = none = Python code has been loaded by previous python command *exists* arg = none = Python code has been loaded by previous python command
*source* arg = *filename* or *inline*
filename = file of Python code which will be executed immediately
inline = one or more lines of Python code which will be executed immediately
must be a single argument, typically enclosed between triple quotes
Examples Examples
"""""""" """"""""
@ -70,80 +76,89 @@ Examples
lmp.command("pair_style lj/cut ${cut}") # LAMMPS commands lmp.command("pair_style lj/cut ${cut}") # LAMMPS commands
lmp.command("pair_coeff * * 1.0 1.0") lmp.command("pair_coeff * * 1.0 1.0")
lmp.command("run 100") lmp.command("run 100")
""" """
python source funcdef.py
python source inline "from lammps import lammps"
Description Description
""""""""""" """""""""""
Define a Python function or execute a previously defined function or Define a Python function or execute a previously defined function or
execute some arbitrary python code. execute some arbitrary python code. Arguments, including LAMMPS
Arguments, including LAMMPS variables, can be passed to the function variables, can be passed to the function from the LAMMPS input script
from the LAMMPS input script and a value returned by the Python and a value returned by the Python function to a LAMMPS variable. The
function to a LAMMPS variable. The Python code for the function can Python code for the function can be included directly in the input
be included directly in the input script or in a separate Python file. script or in a separate Python file. The function can be standard
The function can be standard Python code or it can make "callbacks" to Python code or it can make "callbacks" to LAMMPS through its library
LAMMPS through its library interface to query or set internal values interface to query or set internal values within LAMMPS. This is a
within LAMMPS. This is a powerful mechanism for performing complex powerful mechanism for performing complex operations in a LAMMPS input
operations in a LAMMPS input script that are not possible with the script that are not possible with the simple input script and variable
simple input script and variable syntax which LAMMPS defines. Thus syntax which LAMMPS defines. Thus your input script can operate more
your input script can operate more like a true programming language. like a true programming language.
Use of this command requires building LAMMPS with the PYTHON package Use of this command requires building LAMMPS with the PYTHON package
which links to the Python library so that the Python interpreter is which links to the Python library so that the Python interpreter is
embedded in LAMMPS. More details about this process are given below. embedded in LAMMPS. More details about this process are given below.
There are two ways to invoke a Python function once it has been There are two ways to invoke a Python function once it has been defined.
defined. One is using the *invoke* keyword. The other is to assign One is using the *invoke* keyword. The other is to assign the function
the function to a :doc:`python-style variable <variable>` defined in to a :doc:`python-style variable <variable>` defined in your input
your input script. Whenever the variable is evaluated, it will script. Whenever the variable is evaluated, it will execute the Python
execute the Python function to assign a value to the variable. Note function to assign a value to the variable. Note that variables can be
that variables can be evaluated in many different ways within LAMMPS. evaluated in many different ways within LAMMPS. They can be substituted
They can be substituted for directly in an input script. Or they can with their result directly in an input script, or they can be passed to
be passed to various commands as arguments, so that the variable is various commands as arguments, so that the variable is evaluated during
evaluated during a simulation run. a simulation run.
A broader overview of how Python can be used with LAMMPS is given on A broader overview of how Python can be used with LAMMPS is given in the
the :doc:`Python <Python_head>` doc page. There is an examples/python :doc:`Use Python with LAMMPS <Python_head>` section of the
directory which illustrates use of the python command. documentation. There is an ``examples/python`` directory which
illustrates use of the python command.
---------- ----------
The *func* setting specifies the name of the Python function. The The first argument of the *python* command is either the *source*
code for the function is defined using the *file* or *here* keywords keyword or the name of a Python function.
as explained below. In case of the *source* keyword, the name of
the function is ignored. If the *source* keyword is used, no other keywords can be used. The
argument either can be a filename or the keyword *inline* 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.
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
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 If the *invoke* keyword is used, no other keywords can be used, and a
previous python command must have defined the Python function previous python command must have registered the Python function
referenced by this command. This invokes the Python function with the referenced by this command. This invokes the Python function with the
previously defined arguments and return value processed as explained previously defined arguments and the return value is processed as
below. You can invoke the function as many times as you wish in your explained below. You can invoke the function as many times as you wish
input script. in your input script.
If the *source* keyword is used, no other keywords can be used.
The argument can be a filename or 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 *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 not
not be used. Each argument can be specified directly as a value, be used. Each argument can be specified directly as a value, e.g. 6 or
e.g. 6 or 3.14159 or abc (a string of characters). The type of each 3.14159 or abc (a string of characters). The type of each argument is
argument is specified by the *format* keyword as explained below, so specified by the *format* keyword as explained below, so that Python
that Python will know how to interpret the value. If the word SELF is will know how to interpret the value. If the word SELF is used for an
used for an argument it has a special meaning. A pointer is passed to argument it has a special meaning. A pointer is passed to the Python
the Python function which it converts into a reference to LAMMPS function which it converts into a reference to LAMMPS itself. This
itself. This enables the function to call back to LAMMPS through its enables the function to call back to LAMMPS through its library
library interface as explained below. This allows the Python function interface as explained below. This allows the Python function to query
to query or set values internal to LAMMPS which can affect the or set values internal to LAMMPS which can affect the subsequent
subsequent execution of the input script. A LAMMPS variable can also execution of the input script. A LAMMPS variable can also be used as an
be used as an argument, specified as v_name, where "name" is the name argument, specified as v_name, where "name" is the name of the variable.
of the variable. Any style of LAMMPS variable can be used, as defined Any style of LAMMPS variable can be used, as defined by the
by the :doc:`variable <variable>` command. Each time the Python :doc:`variable <variable>` command. Each time the Python function is
function is invoked, the LAMMPS variable is evaluated and its value is invoked, the LAMMPS variable is evaluated and its value is passed to the
passed to the Python function. 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* must be of the form v_name, where value. The specified *varReturn* must be of the form v_name, where
@ -165,19 +180,19 @@ 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 long as both are specified before the Python function is invoked for
the first time. the first time.
The *format* keyword must be used if the *input* or *return* keyword The *format* keyword must be used if the *input* or *return* keyword is
is used. It defines an *fstring* with M characters, where M = sum of used. It defines an *fstring* with M characters, where M = sum of
number of inputs and outputs. The order of characters corresponds to number of inputs and outputs. The order of characters corresponds to
the N inputs, followed by the return value (if it exists). Each the N inputs, followed by the return value (if it exists). Each
character must be one of the following: "i" for integer, "f" for character must be one of the following: "i" for integer, "f" for
floating point, "s" for string, or "p" for SELF. Each character floating point, "s" for string, or "p" for SELF. Each character defines
defines the type of the corresponding input or output value of the the type of the corresponding input or output value of the Python
Python function and affects the type conversion that is performed function and affects the type conversion that is performed internally as
internally as data is passed back and forth between LAMMPS and Python. data is passed back and forth between LAMMPS and Python. Note that it
Note that it is permissible to use a :doc:`python-style variable <variable>` in a LAMMPS command that allows for an is permissible to use a :doc:`python-style variable <variable>` in a
equal-style variable as an argument, but only if the output of the LAMMPS command that allows for an equal-style variable as an argument,
Python function is flagged as a numeric value ("i" or "f") via the but only if the output of the Python function is flagged as a numeric
*format* keyword. value ("i" or "f") via the *format* keyword.
If the *return* keyword is used and the *format* keyword specifies the 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 output as a string, then the default maximum length of that string is
@ -192,12 +207,12 @@ truncated.
Either the *file*, *here*, or *exists* keyword must be used, but only 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 one of them. These keywords specify what Python code to load into the
Python interpreter. The *file* keyword gives the name of a file, Python interpreter. The *file* keyword gives the name of a file
which should end with a ".py" suffix, which contains Python code. The containing Python code, which should end with a ".py" suffix. The code
code will be immediately loaded into and run in the "main" module of will be immediately loaded into and run in the "main" module of the
the Python interpreter. Note that Python code which contains a Python interpreter. Note that Python code which contains a function
function definition does not "execute" the function when it is run; it definition does not "execute" the function when it is run; it simply
simply defines the function so that it can be invoked later. defines the function so that it can be invoked later.
The *here* keyword does the same thing, except that the Python code 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 follows as a single argument to the *here* keyword. This can be done
@ -208,14 +223,15 @@ proper indentation, blank lines, and comments, as desired. See the
how triple quotes can be used as part of input script syntax. how triple quotes can be used as part of input script syntax.
The *exists* keyword takes no argument. It means that Python code The *exists* keyword takes no argument. It means that Python code
containing the required Python function defined by the *func* setting, containing the required Python function with the given name has already
is assumed to have been previously loaded by another python command. been executed, for example by a *python source* command or in the same
file that was used previously with the *file* keyword.
Note that the Python code that is loaded and run must contain a Note that the Python code that is loaded and run must contain a function
function with the specified *func* name. To operate properly when with the specified function name. To operate properly when later
later invoked, the function code must match the *input* and invoked, the function code must match the *input* and *return* and
*return* and *format* keywords specified by the python command. *format* keywords specified by the python command. Otherwise Python
Otherwise Python will generate an error. will generate an error.
---------- ----------
@ -225,19 +241,18 @@ LAMMPS.
Whether you load Python code from a file or directly from your input Whether you load Python code from a file or directly from your input
script, via the *file* and *here* keywords, the code can be identical. script, via the *file* and *here* keywords, the code can be identical.
It must be indented properly as Python requires. It can contain It must be indented properly as Python requires. It can contain
comments or blank lines. If the code is in your input script, it comments or blank lines. If the code is in your input script, it cannot
cannot however contain triple-quoted Python strings, since that will however contain triple-quoted Python strings, since that will conflict
conflict with the triple-quote parsing that the LAMMPS input script with the triple-quote parsing that the LAMMPS input script performs.
performs.
All the Python code you specify via one or more python commands is All the Python code you specify via one or more python commands is
loaded into the Python "main" module, i.e. __main__. The code can loaded into the Python "main" module, i.e. __main__. The code can
define global variables or statements that are outside of function define global variables or statements that are outside of function
definitions. It can contain multiple functions, only one of which definitions. It can contain multiple functions, only one of which
matches the *func* setting in the python command. This means you can matches the *func* setting in the python command. This means you can
use the *file* keyword once to load several functions, and the use the *file* keyword once to load several functions, and the *exists*
*exists* keyword thereafter in subsequent python commands to access keyword thereafter in subsequent python commands to access the other
the other functions previously loaded. functions previously loaded.
A Python function you define (or more generally, the code you load) A Python function you define (or more generally, the code you load)
can import other Python modules or classes, it can make calls to other can import other Python modules or classes, it can make calls to other
@ -495,24 +510,35 @@ Restrictions
"""""""""""" """"""""""""
This command is part of the PYTHON package. It is only enabled if This command is part of the PYTHON package. It is only enabled if
LAMMPS was built with that package. See the :doc:`Build package <Build_package>` page for more info. LAMMPS was built with that package. See the :doc:`Build package
<Build_package>` page for more info.
Building LAMMPS with the PYTHON package will link LAMMPS with the Building LAMMPS with the PYTHON package will link LAMMPS with the Python
Python library on your system. Settings to enable this are in the library on your system. Settings to enable this are in the
lib/python/Makefile.lammps file. See the lib/python/README file for lib/python/Makefile.lammps file. See the lib/python/README file for
information on those settings. information on those settings.
If you use Python code which calls back to LAMMPS, via the SELF input argument If you use Python code which calls back to LAMMPS, via the SELF input
explained above, there is an extra step required when building LAMMPS. LAMMPS argument explained above, there is an extra step required when building
must also be built as a shared library and your Python function must be able to LAMMPS. LAMMPS must also be built as a shared library and your Python
load the :doc:`"lammps" Python module <Python_module>` that wraps the LAMMPS function must be able to load the :doc:`"lammps" Python module
library interface. These are the same steps required to use Python by itself <Python_module>` that wraps the LAMMPS library interface. These are the
to wrap LAMMPS. Details on these steps are explained on the :doc:`Python same steps required to use Python by itself to wrap LAMMPS. Details on
<Python_head>` doc page. Note that it is important that the stand-alone LAMMPS these steps are explained on the :doc:`Python <Python_head>` doc page.
executable and the LAMMPS shared library be consistent (built from the same Note that it is important that the stand-alone LAMMPS executable and the
source code files) in order for this to work. If the two have been built at 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. 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.
Related commands Related commands
"""""""""""""""" """"""""""""""""

View File

@ -130,19 +130,17 @@ void PythonImpl::command(int narg, char **arg)
return; return;
} }
// if source is only keyword, execute the python code // if source is only keyword, execute the python code in file
if (narg == 3 && strcmp(arg[1], "source") == 0) { if ((narg > 1) && (strcmp(arg[0], "source") == 0)) {
int err; int err = -1;
FILE *fp = fopen(arg[2], "r"); if ((narg > 2) && (strcmp(arg[1], "inline") == 0)) {
if (fp == nullptr)
err = execute_string(arg[2]); err = execute_string(arg[2]);
else } else {
err = execute_file(arg[2]); if (platform::file_is_readable(arg[1])) err = execute_file(arg[1]);
}
if (fp) fclose(fp); if (err) error->warning(FLERR, "Could not process Python source command. Error code: {}", err);
if (err) error->all(FLERR, "Could not process Python source command");
return; return;
} }

View File

@ -276,7 +276,7 @@ TEST_F(PythonPackageTest, RunSource)
{ {
// execute python script from file // execute python script from file
auto output = CAPTURE_OUTPUT([&] { auto output = CAPTURE_OUTPUT([&] {
command("python xyz source ${input_dir}/run.py"); command("python source ${input_dir}/run.py");
}); });
ASSERT_THAT(output, HasSubstr(LOREM_IPSUM)); ASSERT_THAT(output, HasSubstr(LOREM_IPSUM));
@ -286,7 +286,7 @@ TEST_F(PythonPackageTest, RunSourceInline)
{ {
// execute inline python script // execute inline python script
auto output = CAPTURE_OUTPUT([&] { auto output = CAPTURE_OUTPUT([&] {
command("python xyz source \"\"\"\n" command("python source inline \"\"\"\n"
"from __future__ import print_function\n" "from __future__ import print_function\n"
"print(2+2)\n" "print(2+2)\n"
"\"\"\""); "\"\"\"");