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