diff --git a/doc/graphviz/lammps-invoke-python.dot b/doc/graphviz/lammps-invoke-python.dot new file mode 100644 index 0000000000..f880eecfd7 --- /dev/null +++ b/doc/graphviz/lammps-invoke-python.dot @@ -0,0 +1,27 @@ +// LAMMPS -> Python +digraph api { + rankdir="LR"; + edge [constraint=false]; + input [shape=box label="LAMMPS\nInput Script" height=1.5]; + subgraph cluster0 { + style=filled; + color="#e5e5e5"; + rank=same; + capi [shape=box style=filled height=1 color="#666666" fontcolor=white label="LAMMPS\nC Library API"]; + instance [shape=box style=filled height=1 color="#3465a4" fontcolor=white label="LAMMPS\ninstance\n\n0x01abcdef"]; + capi -> instance [dir=both]; + label="LAMMPS Shared Library\nor LAMMPS Executable"; + } + python [shape=box style=filled color="#4e9a06" fontcolor=white label="Python\nScript" height=1.5]; + subgraph cluster1 { + style=filled; + color="#e5e5e5"; + lammps [shape=box style=filled height=1 color="#729fcf" label="lammps\n\nptr: 0x01abcdef"]; + label="LAMMPS Python Module"; + } + input -> instance [constraint=true]; + instance -> python [dir=both constraint=true]; + python:e -> lammps:w [dir=both constraint=true]; + lammps:s -> capi:e [dir=both label=ctypes constraint=true]; +} + diff --git a/doc/graphviz/pylammps-invoke-lammps.dot b/doc/graphviz/pylammps-invoke-lammps.dot new file mode 100644 index 0000000000..0d9e65a5fe --- /dev/null +++ b/doc/graphviz/pylammps-invoke-lammps.dot @@ -0,0 +1,30 @@ +// PyLammps -> LAMMPS +digraph api { + rankdir="LR"; + edge [constraint=false]; + python [shape=box style=filled color="#4e9a06" fontcolor=white label="Python\nScript" height=1.5]; + subgraph cluster0 { + style=filled; + color="#e5e5e5"; + height=1.5; + rank=same; + pylammps [shape=box style=filled height=1 color="#729fcf" label="(I)PyLammps"]; + lammps [shape=box style=filled height=1 color="#729fcf" label="lammps\n\nptr: 0x01abcdef"]; + pylammps -> lammps [dir=both]; + label="LAMMPS Python Module"; + } + subgraph cluster1 { + style=filled; + color="#e5e5e5"; + height=1.5; + capi [shape=box style=filled height=1 color="#666666" fontcolor=white label="LAMMPS\nC Library API"]; + instance [shape=box style=filled height=1 color="#3465a4" fontcolor=white label="LAMMPS\ninstance\n\n0x01abcdef"]; + capi -> instance [dir=both constraint=true]; + label="LAMMPS Shared Library"; + } + python -> pylammps [dir=both constraint=true]; + lammps -> capi [dir=both label=ctypes constraint=true]; + + pylammps:e -> instance:ne [dir=back, style=dashed label="captured standard output"]; +} + diff --git a/doc/graphviz/python-invoke-lammps.dot b/doc/graphviz/python-invoke-lammps.dot new file mode 100644 index 0000000000..6b9766fc7d --- /dev/null +++ b/doc/graphviz/python-invoke-lammps.dot @@ -0,0 +1,24 @@ +// Python -> LAMMPS +digraph api { + rankdir="LR"; + python [shape=box style=filled color="#4e9a06" fontcolor=white label="Python\nScript" height=1.5]; + subgraph cluster0 { + style=filled; + color="#e5e5e5"; + height=1.5; + lammps [shape=box style=filled height=1 color="#729fcf" label="lammps\n\nptr: 0x01abcdef"]; + label="LAMMPS Python Module"; + } + subgraph cluster1 { + style=filled; + color="#e5e5e5"; + height=1.5; + capi [shape=box style=filled height=1 color="#666666" fontcolor=white label="LAMMPS\nC Library API"]; + instance [shape=box style=filled height=1 color="#3465a4" fontcolor=white label="LAMMPS\ninstance\n\n0x01abcdef"]; + capi -> instance [dir=both]; + label="LAMMPS Shared Library"; + } + python -> lammps [dir=both]; + lammps -> capi [dir=both,label=ctypes]; +} + diff --git a/doc/src/Build_basics.rst b/doc/src/Build_basics.rst index 74b6023c3e..8a9370e5a7 100644 --- a/doc/src/Build_basics.rst +++ b/doc/src/Build_basics.rst @@ -329,6 +329,7 @@ LAMMPS. ---------- .. _exe: +.. _library: Build the LAMMPS executable and library --------------------------------------- diff --git a/doc/src/Fortran.rst b/doc/src/Fortran.rst index d411d56d42..608ad48424 100644 --- a/doc/src/Fortran.rst +++ b/doc/src/Fortran.rst @@ -33,6 +33,13 @@ Fortran code using the interface. cover the entire range of functionality available in the C and Python library interfaces. +.. note:: + + A contributed (and complete!) Fortran interface is available + in the ``examples/COUPLE/fortran2`` folder. Please see the + README file in that folder for more information about that + Fortran interface and how to contact its author and maintainer. + ---------- Creating or deleting a LAMMPS object diff --git a/doc/src/Howto_pylammps.rst b/doc/src/Howto_pylammps.rst index 96d9acd994..89119e89af 100644 --- a/doc/src/Howto_pylammps.rst +++ b/doc/src/Howto_pylammps.rst @@ -11,7 +11,7 @@ on its own or use an existing lammps Python object. It creates a simpler, more "pythonic" interface to common LAMMPS functionality, in contrast to the ``lammps.py`` wrapper for the C-style LAMMPS library interface which is written using `Python ctypes `_. The ``lammps.py`` wrapper -is discussed on the :doc:`Python library ` doc page. +is discussed on the :doc:`Python_head` doc page. Unlike the flat ``ctypes`` interface, PyLammps exposes a discoverable API. It no longer requires knowledge of the underlying C++ code diff --git a/doc/src/Install_patch.rst b/doc/src/Install_patch.rst index 0bbd7bbf00..3ccb9fef03 100644 --- a/doc/src/Install_patch.rst +++ b/doc/src/Install_patch.rst @@ -10,7 +10,7 @@ If you prefer to download a tarball, as described on the :doc:`tarball download ` page, you can stay current by downloading "patch files" when new patch releases are made. A link to a patch file is posted on the -`bugf fixes and new feature page `_ +`bug fixes and new feature page `_ of the LAMMPS website, along with a list of changed files and details about what is in the new patch release. This page explains how to apply the patch file to your local diff --git a/doc/src/JPG/lammps-classes.png b/doc/src/JPG/lammps-classes.png index e673299e9d..6d8a20aecd 100644 Binary files a/doc/src/JPG/lammps-classes.png and b/doc/src/JPG/lammps-classes.png differ diff --git a/doc/src/JPG/lammps-invoke-python.png b/doc/src/JPG/lammps-invoke-python.png new file mode 100644 index 0000000000..d1e25f52ea Binary files /dev/null and b/doc/src/JPG/lammps-invoke-python.png differ diff --git a/doc/src/JPG/pylammps-invoke-lammps.png b/doc/src/JPG/pylammps-invoke-lammps.png new file mode 100644 index 0000000000..39296ee272 Binary files /dev/null and b/doc/src/JPG/pylammps-invoke-lammps.png differ diff --git a/doc/src/JPG/python-invoke-lammps.png b/doc/src/JPG/python-invoke-lammps.png new file mode 100644 index 0000000000..0c456028db Binary files /dev/null and b/doc/src/JPG/python-invoke-lammps.png differ diff --git a/doc/src/Library_add.rst b/doc/src/Library_add.rst index e58b6c2b73..ef91c98ab0 100644 --- a/doc/src/Library_add.rst +++ b/doc/src/Library_add.rst @@ -2,11 +2,11 @@ Adding code to the Library interface ==================================== The functionality of the LAMMPS library interface has historically -always been motivated by the needs of its users and functions were -added or expanded as they were needed and used. Contributions to -the interface are always welcome. However with a refactoring of -the library interface and its documentation that started in 2020, -there are now a few requirements for inclusion of changes. +been motivated by the needs of its users. Functions have been added +or expanded as they were needed and used. Contributions to the +interface are always welcome. However with a refactoring of the +library interface and its documentation that started in 2020, there +are now a few requirements for including new changes or extensions. - New functions should be orthogonal to existing ones and not implement functionality that can already be achieved with the @@ -17,17 +17,18 @@ there are now a few requirements for inclusion of changes. ``doc/src`` folder. - If possible, new unit tests to test those new features should be added. - - The new feature should also be implemented and documented for - the Python and Fortran modules. + - The new feature should also be implemented and documented not + just for the C interface, but also the Python and Fortran interfaces. - All additions should work and be compatible with ``-DLAMMPS_BIGBIG``, - ``-DLAMMPS_SMALLBIG``, ``-DLAMMPS_SMALLSMALL`` and compiling + ``-DLAMMPS_SMALLBIG``, ``-DLAMMPS_SMALLSMALL`` and when compiling with and without MPI support. - The ``library.h`` file should be kept compatible to C code at a level similar to C89. Its interfaces may not reference any - custom data types (e.g. ``bigint``, ``tagint``, and so on) only - known inside of LAMMPS. - - only C style comments, not C++ style + custom data types (e.g. ``bigint``, ``tagint``, and so on) that + are only known inside of LAMMPS. + - only use C style comments, not C++ style + +Please note that these are *not* *strict* requirements, but the LAMMPS +developers appreciate if they are followed and can assist with +implementing what is missing. -Please note, that these are *not* *strict* requirements, but the -LAMMPS developers appreciate if they are followed closely and will -assist with implementing what is missing. diff --git a/doc/src/Library_config.rst b/doc/src/Library_config.rst index 0c1e254537..230c908ad6 100644 --- a/doc/src/Library_config.rst +++ b/doc/src/Library_config.rst @@ -21,18 +21,18 @@ This section documents the following functions: -------------------- -The following library functions can be used to query the LAMMPS library -about compile time settings and included packages and styles. This -enables programs that use the library interface to run LAMMPS -simulations to determine, whether the linked LAMMPS library is compatible -with the requirements of the application without crashing during the -LAMMPS functions (e.g. due to missing pair styles from packages) or to -choose between different options (e.g. whether to use ``lj/cut``, -``lj/cut/opt``, ``lj/cut/omp`` or ``lj/cut/intel``). Most of the -functions can be called directly without first creating a LAMMPS -instance. While crashes within LAMMPS may be recovered from through -enabling :ref:`exceptions `, avoiding them proactively is -a safer approach. +These library functions can be used to query the LAMMPS library for +compile time settings and included packages and styles. This enables +programs that use the library interface to determine whether the +linked LAMMPS library is compatible with the requirements of the +application without crashing during the LAMMPS functions (e.g. due to +missing pair styles from packages) or to choose between different +options (e.g. whether to use ``lj/cut``, ``lj/cut/opt``, +``lj/cut/omp`` or ``lj/cut/intel``). Most of the functions can be +called directly without first creating a LAMMPS instance. While +crashes within LAMMPS may be recovered from by enabling +:ref:`exceptions `, avoiding them proactively is a safer +approach. .. code-block:: C :caption: Example for using configuration settings functions diff --git a/doc/src/Library_neighbor.rst b/doc/src/Library_neighbor.rst index 3179b669f1..f50d28b81e 100644 --- a/doc/src/Library_neighbor.rst +++ b/doc/src/Library_neighbor.rst @@ -1,8 +1,8 @@ Accessing LAMMPS Neighbor lists =============================== -The following functions allow to access neighbor lists -generated by LAMMPS or query their properties: +The following functions enable access to neighbor lists generated by +LAMMPS or querying of their properties: - :cpp:func:`lammps_find_compute_neighlist` - :cpp:func:`lammps_find_fix_neighlist` diff --git a/doc/src/Library_objects.rst b/doc/src/Library_objects.rst index 3b87842169..5ce03f84a3 100644 --- a/doc/src/Library_objects.rst +++ b/doc/src/Library_objects.rst @@ -1,8 +1,8 @@ Retrieving or setting properties of LAMMPS objects ================================================== -This section documents accessing or modifying data from objects like -computes, fixes, or variables in LAMMPS using following functions: +This section documents accessing or modifying data stored by computes, +fixes, or variables in LAMMPS using the following functions: - :cpp:func:`lammps_extract_compute` - :cpp:func:`lammps_extract_fix` diff --git a/doc/src/Library_scatter.rst b/doc/src/Library_scatter.rst index e3ca34e999..4fb02ec3e3 100644 --- a/doc/src/Library_scatter.rst +++ b/doc/src/Library_scatter.rst @@ -1,7 +1,16 @@ Library functions for scatter/gather operations ================================================ -This section documents the following functions: +This section has functions which gather per-atom data from one or more +processors into a contiguous global list ordered by atom ID. The same +list is returned to all calling processors. It also contains +functions which scatter per-atom data from a contiguous global list +across the processors that own those atom IDs. It also has a +create_atoms() function which can create new atoms by scattering them +appropriately to owning processors in the LAMMPS spatial +decomposition. + +It documents the following functions: - :cpp:func:`lammps_gather_atoms` - :cpp:func:`lammps_gather_atoms_concat` @@ -14,8 +23,6 @@ This section documents the following functions: - :cpp:func:`lammps_scatter` - :cpp:func:`lammps_scatter_subset` -.. TODO add description - ----------------------- .. doxygenfunction:: lammps_gather_atoms diff --git a/doc/src/Library_utility.rst b/doc/src/Library_utility.rst index 5fbb3efd89..3ca56789c3 100644 --- a/doc/src/Library_utility.rst +++ b/doc/src/Library_utility.rst @@ -1,8 +1,8 @@ Library interface utility functions =================================== -To simplify some of the tasks, the library interface contains -some utility functions that are not directly calling LAMMPS: +To simplify some tasks, the library interface contains these utility +functions. They do not directly call the LAMMPS library. - :cpp:func:`lammps_encode_image_flags` - :cpp:func:`lammps_decode_image_flags` diff --git a/doc/src/Python_call.rst b/doc/src/Python_call.rst index e0bcf70b9a..3b8b33b341 100644 --- a/doc/src/Python_call.rst +++ b/doc/src/Python_call.rst @@ -1,5 +1,5 @@ -Call Python from a LAMMPS input script -====================================== +Calling Python from a LAMMPS input script +***************************************** LAMMPS has several commands which can be used to invoke Python code directly from an input script: @@ -47,32 +47,3 @@ See the :doc:`python ` doc page and the :doc:`variable ` doc page for its python-style variables for more info, including examples of Python code you can write for both pure Python operations and callbacks to LAMMPS. - -The :doc:`fix python/invoke ` command can execute -Python code at selected timesteps during a simulation run. - -The :doc:`pair_style python ` command allows you to define -pairwise potentials as python code which encodes a single pairwise -interaction. This is useful for rapid development and debugging of a -new potential. - -To use any of these commands, you only need to build LAMMPS with the -PYTHON package installed: - -.. code-block:: bash - - make yes-python - make machine - -Note that this will link LAMMPS with the Python library on your -system, which typically requires several auxiliary system libraries to -also be linked. The list of these libraries and the paths to find -them are specified in the lib/python/Makefile.lammps file. You need -to insure that file contains the correct information for your version -of Python and your machine to successfully build LAMMPS. See the -lib/python/README file for more info. - -If you want to write Python code with callbacks to LAMMPS, then you -must also follow the steps summarized in the :doc:`Python run ` doc page. I.e. you must build LAMMPS as a shared -library and insure that Python can find the python/lammps.py file and -the shared library. diff --git a/doc/src/Python_config.rst b/doc/src/Python_config.rst new file mode 100644 index 0000000000..bb44ae5630 --- /dev/null +++ b/doc/src/Python_config.rst @@ -0,0 +1,45 @@ +Retrieving LAMMPS configuration information +******************************************* + +The following methods can be used to query the LAMMPS library +about compile time settings and included packages and styles. + +.. code-block:: Python + :caption: Example for using configuration settings functions + + from lammps import lammps + + lmp = lammps() + + try: + lmp.file("in.missing") + except Exception as e: + print("LAMMPS failed with error:", e) + + # write compressed dump file depending on available of options + + if lmp.has_style("dump", "atom/zstd"): + lmp.command("dump d1 all atom/zstd 100 dump.zst") + elif lmp.has_style("dump", "atom/gz"): + lmp.command("dump d1 all atom/gz 100 dump.gz") + elif lmp.has_gzip_support(): + lmp.command("dump d1 all atom 100 dump.gz") + else: + lmp.command("dump d1 all atom 100 dump") + + +----------------------- + +**Methods:** + +* :py:attr:`lammps.has_mpi_support ` +* :py:attr:`lammps.has_exceptions ` +* :py:attr:`lammps.has_gzip_support ` +* :py:attr:`lammps.has_png_support ` +* :py:attr:`lammps.has_jpeg_support ` +* :py:attr:`lammps.has_ffmpeg_support ` + +* :py:attr:`lammps.installed_packages ` + +* :py:meth:`lammps.has_style() ` +* :py:meth:`lammps.available_styles() ` diff --git a/doc/src/Python_error.rst b/doc/src/Python_error.rst new file mode 100644 index 0000000000..f57ec06576 --- /dev/null +++ b/doc/src/Python_error.rst @@ -0,0 +1,36 @@ +LAMMPS error handling in Python +******************************* + +Compiling the shared library with :ref:`C++ exception support ` provides a better error +handling experience. Without exceptions the LAMMPS code will terminate the +current Python process with an error message. C++ exceptions allow capturing +them on the C++ side and rethrowing them on the Python side. This way +LAMMPS errors can be handled through the Python exception handling mechanism. + +.. code-block:: Python + + from lammps import lammps, MPIAbortException + + lmp = lammps() + + try: + # LAMMPS will normally terminate itself and the running process if an error + # occurs. This would kill the Python interpreter. To avoid this, make sure to + # compile with LAMMPS_EXCEPTIONS enabled. This ensures the library API calls + # will not terminate the parent process. Instead, the library wrapper will + # detect that an error has occured and throw a Python exception + + lmp.command('unknown') + except MPIAbortException as ae: + # Single MPI process got killed. This would normally be handled by an MPI abort + pass + except Exception as e: + # All (MPI) processes have reached this error + pass + +.. warning:: + + Capturing a LAMMPS exception in Python can still mean that the + current LAMMPS process is in an illegal state and must be terminated. It is + advised to save your data and terminate the Python instance as quickly as + possible. diff --git a/doc/src/Python_examples.rst b/doc/src/Python_examples.rst index 6b444e40ed..c63691a004 100644 --- a/doc/src/Python_examples.rst +++ b/doc/src/Python_examples.rst @@ -1,6 +1,9 @@ Example Python scripts that use LAMMPS ====================================== +The python/examples directory has Python scripts which show how Python +can run LAMMPS, grab data, change it, and put it back into LAMMPS. + These are the Python scripts included as demos in the python/examples directory of the LAMMPS distribution, to illustrate the kinds of things that are possible when Python wraps LAMMPS. If you create your diff --git a/doc/src/Python_ext.rst b/doc/src/Python_ext.rst new file mode 100644 index 0000000000..40f6e10609 --- /dev/null +++ b/doc/src/Python_ext.rst @@ -0,0 +1,16 @@ +Extending the library and Python interface +****************************************** + +As noted above, these Python class methods correspond one-to-one with +the functions in the LAMMPS library interface in ``src/library.cpp`` and +``library.h``. This means you can extend the Python wrapper via the +following steps: + +* Add a new interface function to ``src/library.cpp`` and + ``src/library.h``. +* Rebuild LAMMPS as a shared library. +* Add a wrapper method to ``python/lammps.py`` for this interface + function. +* You should now be able to invoke the new interface function from a + Python script. + diff --git a/doc/src/Python_head.rst b/doc/src/Python_head.rst index 611e6424fe..99d005dd97 100644 --- a/doc/src/Python_head.rst +++ b/doc/src/Python_head.rst @@ -1,44 +1,50 @@ Use Python with LAMMPS ********************** -These doc pages describe various ways that LAMMPS and Python can be -used together. +These pages describe various ways that LAMMPS and Python can be used +together. .. toctree:: :maxdepth: 1 Python_overview - Python_run - Python_shlib Python_install - Python_mpi - Python_test - Python_library - Python_module - Python_pylammps - Python_examples + Python_run + Python_usage Python_call + Python_config + Python_neighbor + Python_module + Python_examples + Python_error + Python_ext + Python_trouble -If you're not familiar with `Python `_, it's a -powerful scripting and programming language which can do most -everything that lower-level languages like C or C++ can do in fewer -lines of code. The only drawback is slower execution speed. Python -is also easy to use as a "glue" language to drive a program through -its library interface, or to hook multiple pieces of software -together, such as a simulation code plus a visualization tool, or to -run a coupled multiscale or multiphysics model. +If you are not familiar with `Python `_, it is a +powerful scripting and programming language which can do almost +everything that compiled languages like C, C++, or Fortran can do in +fewer lines of code. It also comes with a large collection of add-on +modules for many purposes (either bundled or easily installed from +Python code repositories). The major drawback is slower execution speed +of the script code compared to compiled programming languages. But when +the script code is interfaced to optimized compiled code, performance can +be on par with a standalone executable, for as long as the scripting is +restricted to high-level operations. Thus Python is also convenient to +use as a "glue" language to "drive" a program through its library +interface, or to hook multiple pieces of software together, such as a +simulation code and a visualization tool, or to run a coupled +multi-scale or multi-physics model. -See the :doc:`Howto_couple ` doc page for more ideas about -coupling LAMMPS to other codes. See the :doc:`Howto library ` doc page for a description of the LAMMPS -library interface provided in src/library.h and src/library.h. That -interface is exposed to Python either when calling LAMMPS from Python -or when calling Python from a LAMMPS input script and then calling -back to LAMMPS from Python code. The library interface is designed to -be easy to add functionality to. Thus the Python interface to LAMMPS -is also easy to extend as well. +See the :doc:`Howto_couple` page for more ideas about coupling LAMMPS +to other codes. See the :doc:`Library` page for a description of the +LAMMPS library interfaces. That interface is exposed to Python either +when calling LAMMPS from Python or when calling Python from a LAMMPS +input script and then calling back to LAMMPS from Python code. The +C-library interface is designed to be easy to add functionality to, +thus the Python interface to LAMMPS is easy to extend as well. If you create interesting Python scripts that run LAMMPS or interesting Python functions that can be called from a LAMMPS input script, that you think would be generally useful, please post them as a pull request to our `GitHub site `_, -and they can be added to the LAMMPS distribution or webpage. +and they can be added to the LAMMPS distribution or web page. diff --git a/doc/src/Python_install.rst b/doc/src/Python_install.rst index 8b2fe9c367..6927d0745f 100644 --- a/doc/src/Python_install.rst +++ b/doc/src/Python_install.rst @@ -1,68 +1,468 @@ -Installing LAMMPS in Python -=========================== +Installation +************ -For Python to invoke LAMMPS, there are 2 files it needs to know about: +The LAMMPS Python module enables calling the :ref:`LAMMPS C library API +` from Python by dynamically loading functions in the +LAMMPS shared library through the Python `ctypes `_ +module. Because of the dynamic loading, it is required that LAMMPS is +compiled in :ref:`"shared" mode `. It is also recommended to +compile LAMMPS with :ref:`C++ exceptions ` enabled. -* python/lammps.py -* liblammps.so or liblammps.dylib +Two files are necessary for Python to be able to invoke LAMMPS code: -The python source code in lammps.py is the Python wrapper on the -LAMMPS library interface. The liblammps.so or liblammps.dylib file -is the shared LAMMPS library that Python loads dynamically. +* The LAMMPS Python Module (``lammps.py``) from the ``python`` folder +* The LAMMPS Shared Library (``liblammps.so``, ``liblammps.dylib`` or + ``liblammps.dll``) from the folder where you compiled LAMMPS. -You can achieve that Python can find these files in one of two ways: +.. _ctypes: https://docs.python.org/3/library/ctypes.html +.. _python_virtualenv: https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/#creating-a-virtual-environment +.. _python_venv: https://docs.python.org/3/library/venv.html +.. _python_pep405: https://www.python.org/dev/peps/pep-0405 -* set two environment variables pointing to the location in the source tree -* run "make install-python" or run the python/install.py script explicitly +.. _python_install_guides: -When calling "make install-python" LAMMPS will try to install the -python module and the shared library into the python site-packages folders; -either the system-wide ones, or the local users ones (in case of insufficient -permissions for the global install). Python will then find the module -and shared library file automatically. The exact location of these folders -depends on your python version and your operating system. When using -the CMake build system, you can set the python executable to use during -the CMake configuration process. Details are given in the build instructions -for the :ref:`PYTHON ` package. When using the conventional make -system, you can override the python version to version x.y when calling -make with PYTHON=pythonx.y. +Installing the LAMMPS Python Module and Shared Library +====================================================== -If you set the paths to these files as environment variables, you only -have to do it once. For the csh or tcsh shells, add something like -this to your ~/.cshrc file, one line for each of the two files: +Making LAMMPS usable within Python and vice versa requires putting the +LAMMPS Python module file (``lammps.py``) into a location where the +Python interpreter can find it and installing the LAMMPS shared library +into a folder that the dynamic loader searches or into the same folder +where the ``lammps.py`` file is. There are multiple ways to achieve +this. -.. code-block:: csh +#. Do a full LAMMPS installation of libraries, executables, selected + headers, documentation (if enabled), and supporting files (only + available via CMake), which can also be either system-wide or into + user specific folders. - setenv PYTHONPATH ${PYTHONPATH}:/home/sjplimp/lammps/python - setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:/home/sjplimp/lammps/src +#. Install both files into a Python ``site-packages`` folder, either + system-wide or in the corresponding user-specific folder. This way no + additional environment variables need to be set, but the shared + library is otherwise not accessible. -On MacOSX you may also need to set DYLD_LIBRARY_PATH accordingly. -For Bourne/Korn shells accordingly into the corresponding files using -the "export" shell builtin. +#. Do an installation into a virtual environment. This can either be + an installation of the python module only or a full installation. -If you use "make install-python" or the python/install.py script, you need -to invoke it every time you rebuild LAMMPS (as a shared library) or -make changes to the python/lammps.py file, so that the site-packages -files are updated with the new version. +#. Leave the files where they are in the source/development tree and + adjust some environment variables. -If the default settings of "make install-python" are not what you want, -you can invoke install.py from the python directory manually as +.. tabs:: -.. parsed-literal:: + .. tab:: Full install (CMake-only) - % python install.py -m \ -l -v [-d \] + :ref:`Build the LAMMPS executable and library ` with + ``-DBUILD_SHARED_LIBS=on``, ``-DLAMMPS_EXCEPTIONS=on`` and + ``-DPKG_PYTHON=on`` (The first option is required, the other two + are optional by recommended). The exact file name of the shared + library depends on the platform (Unix/Linux, MacOS, Windows) and + the build configuration being used. The installation base folder + is already set by default to the ``$HOME/.local`` directory, but + it can be changed to a custom location defined by the + ``CMAKE_INSTALL_PREFIX`` CMake variable. This uses a folder + called ``build`` to store files generated during compilation. -* The -m flag points to the lammps.py python module file to be installed, -* the -l flag points to the LAMMPS shared library file to be installed, -* the -v flag points to the version.h file in the LAMMPS source -* and the optional -d flag to a custom (legacy) installation folder + .. code-block:: bash -If you use a legacy installation folder, you will need to set your -PYTHONPATH and LD_LIBRARY_PATH (and/or DYLD_LIBRARY_PATH) environment -variables accordingly, as described above. + # create build folder + mkdir build + cd build + + # configure LAMMPS compilation + cmake -C cmake/presets/minimal.cmake -D BUILD_SHARED_LIBS=on \ + -D LAMMPS_EXCEPTIONS=on -D PKG_PYTHON=on cmake + + # compile LAMMPS + cmake --build . + + # install LAMMPS into $HOME/.local + cmake --install . + + + This leads to an installation to the following locations: + + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===========================================================+=============================================================+ + | LAMMPS Python Module | * ``$HOME/.local/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$HOME/.local/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``$HOME/.local/lib/`` (32bit) | | + | | * ``$HOME/.local/lib64/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS executable | * ``$HOME/.local/bin/`` | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS potential files | * ``$HOME/.local/share/lammps/potentials/`` | Set ``LAMMPS_POTENTIALS`` environment variable to this path | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + + For a system-wide installation you need to set + ``CMAKE_INSTALL_PREFIX`` to a system folder like ``/usr`` (or + ``/usr/local``). The installation step (**not** the + configuration/compilation) needs to be done with superuser + privilege, e.g. by using ``sudo cmake --install .``. The + installation folders will then by changed to: + + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===================================================+=============================================================+ + | LAMMPS Python Module | * ``/usr/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``/usr/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``/usr/lib/`` (32bit) | | + | | * ``/usr/lib64/`` (64bit) | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS executable | * ``/usr/bin/`` | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS potential files | * ``/usr/share/lammps/potentials/`` | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + + To be able to use the "user" installation you have to ensure that + the folder containing the LAMMPS shared library is either included + in a path searched by the shared linker (e.g. like + ``/usr/lib64/``) or part of the ``LD_LIBRARY_PATH`` environment + variable (or ``DYLD_LIBRARY_PATH`` on MacOS). Otherwise you will + get an error when trying to create a LAMMPS object through the + Python module. + + .. code-block:: bash + + # Unix/Linux + export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH + + # MacOS + export DYLD_LIBRARY_PATH=$HOME/.local/lib:$DYLD_LIBRARY_PATH + + If you plan to use the LAMMPS executable (e.g., ``lmp``), you may + also need to adjust the ``PATH`` environment variable (but many + newer Linux distributions already have ``$HOME/.local/bin`` + included). Example: + + .. code-block:: bash + + export PATH=$HOME/.local/bin:$PATH + + To make those changes permanent, you can add the commands to your + ``$HOME/.bashrc`` file. For a system-wide installation is is not + necessary due to files installed in system folders that are loaded + automatically when a login shell is started. + + .. tab:: Python module only + + Compile LAMMPS with either :doc:`CMake ` or the + :doc:`traditional make ` procedure in :ref:`shared + mode `. After compilation has finished type (in the + compilation folder): + + .. code-block:: bash + + make install-python + + This will try to install (only) the shared library and the python + module into a system folder and if that fails (due to missing + write permissions) will instead do the installation to a user + folder under ``$HOME/.local``. For a system-wide installation you + would have to gain superuser privilege, e.g. though ``sudo`` + + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===========================================================+=============================================================+ + | LAMMPS Python Module | * ``$HOME/.local/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$HOME/.local/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``$HOME/.local/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$HOME/.local/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + + For a system-wide installation those folders would then become. + + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===================================================+=============================================================+ + | LAMMPS Python Module | * ``/usr/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``/usr/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``/usr/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``/usr/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+---------------------------------------------------+-------------------------------------------------------------+ + + No environment variables need to be set for those, as those + folders are searched by default by Python or the LAMMPS Python + module. + + For the traditional make process you can override the python + version to version x.y when calling ``make`` with + ``PYTHON=pythonX.Y``. For a CMake based compilation this choice + has to be made during the CMake configuration step. + + If the default settings of ``make install-python`` are not what you want, + you can invoke ``install.py`` from the python directory manually as + + .. code-block:: bash + + $ python install.py -m -l -v [-d ] + + * The ``-m`` flag points to the ``lammps.py`` python module file to be installed, + * the ``-l`` flag points to the LAMMPS shared library file to be installed, + * the ``-v`` flag points to the ``version.h`` file in the LAMMPS source + * and the optional ``-d`` flag to a custom (legacy) installation folder + + If you use a legacy installation folder, you will need to set your + ``PYTHONPATH`` and ``LD_LIBRARY_PATH`` (and/or ``DYLD_LIBRARY_PATH``) environment + variables accordingly as explained in the description for "In place use". + + .. tab:: Virtual environment + + A virtual environment is a minimal Python installation inside of a + folder. It allows isolating and customizing a Python environment + that is mostly independent from a user or system installation. + For the core Python environment, it uses symbolic links to the + system installation and thus it can be set up quickly and will not + take up much disk space. This gives you the flexibility to + install (newer/different) versions of Python packages that would + potentially conflict with already installed system packages. It + also does not requite any superuser privileges. See `PEP 405: + Python Virtual Environments `_ for more + information. + + To create a virtual environment in the folder ``$HOME/myenv``, + use the `venv `_ module as follows. + + .. code-block:: bash + + # create virtual environment in folder $HOME/myenv + python3 -m venv $HOME/myenv + + For Python versions prior 3.3 you can use `virtualenv + `_ command instead of "python3 -m venv". This + step has to be done only once. + + To activate the virtual environment type: + + .. code-block:: bash + + source $HOME/myenv/bin/activate + + This has to be done every time you log in or open a new terminal + window and after you turn off the virtual environment with the + ``deactivate`` command. + + When using CMake to build LAMMPS, you need to set + ``CMAKE_INSTALL_PREFIX`` to the value of the ``$VIRTUAL_ENV`` + environment variable during the configuration step. For the + traditional make procedure, not additional steps are needed. + After compiling LAMMPS you can do a "Python module only" + installation with ``make install-python`` and the LAMMPS Python + module and the shared library file are installed into the + following locations: + + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===========================================================+=============================================================+ + | LAMMPS Python Module | * ``$VIRTUAL_ENV/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$VIRTUAL_ENV/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``$VIRTUAL_ENV/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$VIRTUAL_ENV/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + + If you do a full installation (CMake only) with "install", this + leads to the following installation locations: + + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | File | Location | Notes | + +========================+===========================================================+=============================================================+ + | LAMMPS Python Module | * ``$VIRTUAL_ENV/lib/pythonX.Y/site-packages/`` (32bit) | ``X.Y`` depends on the installed Python version | + | | * ``$VIRTUAL_ENV/lib64/pythonX.Y/site-packages/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS shared library | * ``$VIRTUAL_ENV/lib/`` (32bit) | | + | | * ``$VIRTUAL_ENV/lib64/`` (64bit) | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS executable | * ``$VIRTUAL_ENV/bin/`` | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + | LAMMPS potential files | * ``$VIRTUAL_ENV/share/lammps/potentials/`` | | + +------------------------+-----------------------------------------------------------+-------------------------------------------------------------+ + + In that case you need to modify the ``$HOME/myenv/bin/activate`` + script in a similar fashion you need to update your + ``$HOME/.bashrc`` file to include the shared library and + executable locations in ``LD_LIBRARY_PATH`` (or + ``DYLD_LIBRARY_PATH`` on MacOS) and ``PATH``, respectively. + + For example with: + + .. code-block:: bash + + # Unix/Linux + echo 'export LD_LIBRARY_PATH=$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH' >> $HOME/myenv/bin/activate + + # MacOS + echo 'export DYLD_LIBRARY_PATH=$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH' >> $HOME/myenv/bin/activate + + .. tab:: In place usage + + You can also :doc:`compile LAMMPS ` as usual in + :ref:`"shared" mode ` leave the shared library and Python + module files inside the source/compilation folders. Instead of + copying the files where they can be found, you need to set the environment + variables ``PYTHONPATH`` (for the Python module) and + ``LD_LIBRARY_PATH`` (or ``DYLD_LIBRARY_PATH`` on MacOS + + For Bourne shells (bash, ksh and similar) the commands are: + + .. code-block:: bash + + export PYTHONPATH=${PYTHONPATH}:${HOME}/lammps/python + export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:${HOME}/lammps/src + + For the C-shells like csh or tcsh the commands are: + + .. code-block:: csh + + setenv PYTHONPATH ${PYTHONPATH}:${HOME}/lammps/python + setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:${HOME}/lammps/src + + On MacOS you may also need to set ``DYLD_LIBRARY_PATH`` accordingly. + You can make those changes permanent by editing your ``$HOME/.bashrc`` + or ``$HOME/.login`` files, respectively. + + +To verify if LAMMPS can be successfully started from Python, start the +Python interpreter, load the ``lammps`` Python module and create a +LAMMPS instance. This should not generate an error message and produce +output similar to the following: + + .. code-block:: bash + + $ python + Python 3.8.5 (default, Sep 5 2020, 10:50:12) + [GCC 10.2.0] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> import lammps + >>> lmp = lammps.lammps() + LAMMPS (18 Sep 2020) + using 1 OpenMP thread(s) per MPI task + >>> + +.. note:: + + Unless you opted for "In place use", you will have to rerun the installation + any time you recompile LAMMPS to ensure the latest Python module and shared + library are installed and used. + +.. note:: + + If you want Python to be able to load different versions of the + LAMMPS shared library with different settings, you will need to + manually copy the files under different names + (e.g. ``liblammps_mpi.so`` or ``liblammps_gpu.so``) into the + appropriate folder as indicated above. You can then select the + desired library through the *name* argument of the LAMMPS object + constructor (see :ref:`python_create_lammps`). + +.. _python_install_mpi4py: + +Extending Python to run in parallel +=================================== + +If you wish to run LAMMPS in parallel from Python, you need to extend +your Python with an interface to MPI. This also allows you to +make MPI calls directly from Python in your script, if you desire. + +We have tested this with `MPI for Python `_ +(aka mpi4py) and you will find installation instruction for it below. + +.. note:: + + Older LAMMPS versions were also tested with `PyPar `_ + but we can no longer test it, since it does not work with the Python + (3.x) versions on our test servers. Since there have been no updates + to PyPar visible in its repository since November 2016 we have to assume + it is no longer maintained. + +Installation of mpi4py (version 3.0.3 as of Sep 2020) can be done as +follows: + +- Via ``pip`` into a local user folder with: + + .. code-block:: bash + + pip install --user mpi4py + +- Via ``dnf`` into a system folder for RedHat/Fedora systems: + + .. code-block:: bash + + # for use with OpenMPI + sudo dnf install python3-mpi4py-openmpi + # for use with MPICH + sudo dnf install python3-mpi4py-openmpi + +- Via ``pip`` into a virtual environment (see above): + + .. code-block:: bash + + $ source $HOME/myenv/activate + (myenv)$ pip install mpi4py + +- Via ``pip`` into a system folder (not recommended): + + .. code-block:: bash + + sudo pip install mpi4py + +.. _mpi4py_install: https://mpi4py.readthedocs.io/en/stable/install.html + +For more detailed installation instructions and additional options, +please see the `mpi4py installation `_ page. + + +To use ``mpi4py`` and LAMMPS in parallel from Python, you **must** make +certain that **both** are using the **same** implementation and version +of MPI library. If you only have one MPI library installed on your +system this is not an issue, but it can be if you have multiple MPI +installations (e.g. on an HPC cluster to be selected through environment +modules). Your LAMMPS build is explicit about which MPI it is using, +since it is either detected during CMake configuration or in the +traditional make build system you specify the details in your low-level +``src/MAKE/Makefile.foo`` file. The installation process of ``mpi4py`` +uses the ``mpicc`` command to find information about the MPI it uses to +build against. And it tries to load "libmpi.so" from the +``LD_LIBRARY_PATH``. This may or may not find the MPI library that +LAMMPS is using. If you have problems running both mpi4py and LAMMPS +together, this is an issue you may need to address, e.g. by loading the +module for different MPI installation so that mpi4py finds the right +one. + +If you have successfully installed mpi4py, you should be able to run +Python and type + +.. code-block:: python + + from mpi4py import MPI + +without error. You should also be able to run Python in parallel +on a simple test script + +.. code-block:: bash + + $ mpirun -np 4 python3 test.py + +where ``test.py`` contains the lines + +.. code-block:: python + + from mpi4py import MPI + comm = MPI.COMM_WORLD + print("Proc %d out of %d procs" % (comm.Get_rank(),comm.Get_size())) + +and see one line of output for each processor you run on. + +.. code-block:: bash + + # NOTE: the line order is not deterministic + $ mpirun -np 4 python3 test.py + Proc 0 out of 4 procs + Proc 1 out of 4 procs + Proc 2 out of 4 procs + Proc 3 out of 4 procs -Note that if you want Python to be able to load different versions of -the LAMMPS shared library (see :doc:`this section `), you will -need to manually copy files like liblammps_g++.so into the appropriate -system directory. This is not needed if you set the LD_LIBRARY_PATH -environment variable as described above. diff --git a/doc/src/Python_library.rst b/doc/src/Python_library.rst deleted file mode 100644 index 370c67b3f8..0000000000 --- a/doc/src/Python_library.rst +++ /dev/null @@ -1,256 +0,0 @@ -Python library interface -======================== - -As described previously, the Python interface to LAMMPS consists of a -Python "lammps" module, the source code for which is in -python/lammps.py, which creates a "lammps" object, with a set of -methods that can be invoked on that object. The sample Python code -below assumes you have first imported the "lammps" module in your -Python script, as follows: - -.. code-block:: Python - - from lammps import lammps - -These are the methods defined by the lammps module. If you look at -the files src/library.cpp and src/library.h you will see they -correspond one-to-one with calls you can make to the LAMMPS library -from a C++ or C or Fortran program, and which are described on the -:doc:`Howto library ` doc page. - -The python/examples directory has Python scripts which show how Python -can run LAMMPS, grab data, change it, and put it back into LAMMPS. - -.. code-block:: Python - - lmp = lammps() # create a LAMMPS object using the default liblammps.so library - # 4 optional args are allowed: name, cmdargs, ptr, comm - lmp = lammps(ptr=lmpptr) # use lmpptr as previously created LAMMPS object - lmp = lammps(comm=split) # create a LAMMPS object with a custom communicator, requires mpi4py 2.0.0 or later - lmp = lammps(name="g++") # create a LAMMPS object using the liblammps_g++.so library - lmp = lammps(name="g++",cmdargs=list) # add LAMMPS command-line args, e.g. list = ["-echo","screen"] - - lmp.close() # destroy a LAMMPS object - - version = lmp.version() # return the numerical version id, e.g. LAMMPS 2 Sep 2015 -> 20150902 - - lmp.file(file) # run an entire input script, file = "in.lj" - lmp.command(cmd) # invoke a single LAMMPS command, cmd = "run 100" - lmp.commands_list(cmdlist) # invoke commands in cmdlist = **"run 10", "run 20"** - lmp.commands_string(multicmd) # invoke commands in multicmd = "run 10\nrun 20" - - size = lmp.extract_setting(name) # return data type info - - xlo = lmp.extract_global(name,type) # extract a global quantity - # name = "boxxlo", "nlocal", etc - # type = 0 = int - # 1 = double - - boxlo,boxhi,xy,yz,xz,periodicity,box_change = lmp.extract_box() # extract box info - - coords = lmp.extract_atom(name,type) # extract a per-atom quantity - # name = "x", "type", etc - # type = 0 = vector of ints - # 1 = array of ints - # 2 = vector of doubles - # 3 = array of doubles - - eng = lmp.extract_compute(id,style,type) # extract value(s) from a compute - v3 = lmp.extract_fix(id,style,type,i,j) # extract value(s) from a fix - # id = ID of compute or fix - # style = 0 = global data - # 1 = per-atom data - # 2 = local data - # type = 0 = scalar - # 1 = vector - # 2 = array - # i,j = indices of value in global vector or array - - var = lmp.extract_variable(name,group,flag) # extract value(s) from a variable - # name = name of variable - # group = group ID (ignored for equal-style variables) - # flag = 0 = equal-style variable - # 1 = atom-style variable - - value = lmp.get_thermo(name) # return current value of a thermo keyword - natoms = lmp.get_natoms() # total # of atoms as int - - flag = lmp.set_variable(name,value) # set existing named string-style variable to value, flag = 0 if successful - lmp.reset_box(boxlo,boxhi,xy,yz,xz) # reset the simulation box size - - data = lmp.gather_atoms(name,type,count) # return per-atom property of all atoms gathered into data, ordered by atom ID - # name = "x", "charge", "type", etc - data = lmp.gather_atoms_concat(name,type,count) # ditto, but concatenated atom values from each proc (unordered) - data = lmp.gather_atoms_subset(name,type,count,ndata,ids) # ditto, but for subset of Ndata atoms with IDs - - lmp.scatter_atoms(name,type,count,data) # scatter per-atom property to all atoms from data, ordered by atom ID - # name = "x", "charge", "type", etc - # count = # of per-atom values, 1 or 3, etc - - lmp.scatter_atoms_subset(name,type,count,ndata,ids,data) # ditto, but for subset of Ndata atoms with IDs - - lmp.create_atoms(n,ids,types,x,v,image,shrinkexceed) # create N atoms with IDs, types, x, v, and image flags - ----------- - -The lines - -.. code-block:: Python - - from lammps import lammps - lmp = lammps() - -create an instance of LAMMPS, wrapped in a Python class by the lammps -Python module, and return an instance of the Python class as lmp. It -is used to make all subsequent calls to the LAMMPS library. - -Additional arguments to lammps() can be used to tell Python the name -of the shared library to load or to pass arguments to the LAMMPS -instance, the same as if LAMMPS were launched from a command-line -prompt. - -If the ptr argument is set like this: - -.. code-block:: Python - - lmp = lammps(ptr=lmpptr) - -then lmpptr must be an argument passed to Python via the LAMMPS -:doc:`python ` command, when it is used to define a Python -function that is invoked by the LAMMPS input script. This mode of -calling Python from LAMMPS is described in the :doc:`Python call ` doc page. The variable lmpptr refers to the -instance of LAMMPS that called the embedded Python interpreter. Using -it as an argument to lammps() allows the returned Python class -instance "lmp" to make calls to that instance of LAMMPS. See the -:doc:`python ` command doc page for examples using this syntax. - -Note that you can create multiple LAMMPS objects in your Python -script, and coordinate and run multiple simulations, e.g. - -.. code-block:: Python - - from lammps import lammps - lmp1 = lammps() - lmp2 = lammps() - lmp1.file("in.file1") - lmp2.file("in.file2") - -The file(), command(), commands_list(), commands_string() methods -allow an input script, a single command, or multiple commands to be -invoked. - -The extract_setting(), extract_global(), extract_box(), -extract_atom(), extract_compute(), extract_fix(), and -extract_variable() methods return values or pointers to data -structures internal to LAMMPS. - -For extract_global() see the src/library.cpp file for the list of -valid names. New names could easily be added. A double or integer is -returned. You need to specify the appropriate data type via the type -argument. - -For extract_atom(), a pointer to internal LAMMPS atom-based data is -returned, which you can use via normal Python subscripting. See the -extract() method in the src/atom.cpp file for a list of valid names. -Again, new names could easily be added if the property you want is not -listed. A pointer to a vector of doubles or integers, or a pointer to -an array of doubles (double \*\*) or integers (int \*\*) is returned. You -need to specify the appropriate data type via the type argument. - -For extract_compute() and extract_fix(), the global, per-atom, or -local data calculated by the compute or fix can be accessed. What is -returned depends on whether the compute or fix calculates a scalar or -vector or array. For a scalar, a single double value is returned. If -the compute or fix calculates a vector or array, a pointer to the -internal LAMMPS data is returned, which you can use via normal Python -subscripting. The one exception is that for a fix that calculates a -global vector or array, a single double value from the vector or array -is returned, indexed by I (vector) or I and J (array). I,J are -zero-based indices. The I,J arguments can be left out if not needed. -See the :doc:`Howto output ` doc page for a discussion of -global, per-atom, and local data, and of scalar, vector, and array -data types. See the doc pages for individual :doc:`computes ` -and :doc:`fixes ` for a description of what they calculate and -store. - -For extract_variable(), an :doc:`equal-style or atom-style variable ` is evaluated and its result returned. - -For equal-style variables a single double value is returned and the -group argument is ignored. For atom-style variables, a vector of -doubles is returned, one value per atom, which you can use via normal -Python subscripting. The values will be zero for atoms not in the -specified group. - -The get_thermo() method returns the current value of a thermo -keyword as a float. - -The get_natoms() method returns the total number of atoms in the -simulation, as an int. - -The set_variable() method sets an existing string-style variable to a -new string value, so that subsequent LAMMPS commands can access the -variable. - -The reset_box() method resets the size and shape of the simulation -box, e.g. as part of restoring a previously extracted and saved state -of a simulation. - -The gather methods collect peratom info of the requested type (atom -coords, atom types, forces, etc) from all processors, and returns the -same vector of values to each calling processor. The scatter -functions do the inverse. They distribute a vector of peratom values, -passed by all calling processors, to individual atoms, which may be -owned by different processors. - -Note that the data returned by the gather methods, -e.g. gather_atoms("x"), is different from the data structure returned -by extract_atom("x") in four ways. (1) Gather_atoms() returns a -vector which you index as x[i]; extract_atom() returns an array -which you index as x[i][j]. (2) Gather_atoms() orders the atoms -by atom ID while extract_atom() does not. (3) Gather_atoms() returns -a list of all atoms in the simulation; extract_atoms() returns just -the atoms local to each processor. (4) Finally, the gather_atoms() -data structure is a copy of the atom coords stored internally in -LAMMPS, whereas extract_atom() returns an array that effectively -points directly to the internal data. This means you can change -values inside LAMMPS from Python by assigning a new values to the -extract_atom() array. To do this with the gather_atoms() vector, you -need to change values in the vector, then invoke the scatter_atoms() -method. - -For the scatter methods, the array of coordinates passed to must be a -ctypes vector of ints or doubles, allocated and initialized something -like this: - -.. code-block:: Python - - from ctypes import \* - natoms = lmp.get_natoms() - n3 = 3\*natoms - x = (n3\*c_double)() - x[0] = x coord of atom with ID 1 - x[1] = y coord of atom with ID 1 - x[2] = z coord of atom with ID 1 - x[3] = x coord of atom with ID 2 - ... - x[n3-1] = z coord of atom with ID natoms - lmp.scatter_atoms("x",1,3,x) - -Alternatively, you can just change values in the vector returned by -the gather methods, since they are also ctypes vectors. - ----------- - -As noted above, these Python class methods correspond one-to-one with -the functions in the LAMMPS library interface in src/library.cpp and -library.h. This means you can extend the Python wrapper via the -following steps: - -* Add a new interface function to src/library.cpp and - src/library.h. -* Rebuild LAMMPS as a shared library. -* Add a wrapper method to python/lammps.py for this interface - function. -* You should now be able to invoke the new interface function from a - Python script. - diff --git a/doc/src/Python_module.rst b/doc/src/Python_module.rst index e9a48dd797..04bc3f2c5b 100644 --- a/doc/src/Python_module.rst +++ b/doc/src/Python_module.rst @@ -28,278 +28,8 @@ There are multiple Python interface classes in the :py:mod:`lammps` module: ---------- -Setting up a Python virtual environment -*************************************** - -LAMMPS and its Python module can be installed together into a Python virtual -environment. This lets you isolate your customized Python environment from -your user or system installation. The following is a minimal working example: - -.. code-block:: bash - - # create and change into build directory - mkdir build - cd build - - # create virtual environment - virtualenv myenv - - # Add venv lib folder to LD_LIBRARY_PATH when activating it - echo 'export LD_LIBRARY_PATH=$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH' >> myenv/bin/activate - - # Add LAMMPS_POTENTIALS path when activating venv - echo 'export LAMMPS_POTENTIALS=$VIRTUAL_ENV/share/lammps/potentials' >> myenv/bin/activate - - # activate environment - source myenv/bin/activate - - # configure LAMMPS compilation - # compiles as shared library with PYTHON package and C++ exceptions - # and installs into myvenv - (myenv)$ cmake -C ../cmake/presets/minimal.cmake \ - -D BUILD_SHARED_LIBS=on \ - -D PKG_PYTHON=on \ - -D LAMMPS_EXCEPTIONS=on \ - -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV \ - ../cmake - - # compile LAMMPS - (myenv)$ cmake --build . --parallel - - # install LAMMPS into myvenv - (myenv)$ cmake --install . - -Creating or deleting a LAMMPS object -************************************ - -With the Python interface the creation of a :cpp:class:`LAMMPS -` instance is included in the constructors for the -:py:meth:`lammps `, :py:meth:`PyLammps `, -and :py:meth:`PyLammps ` classes. -Internally it will call either :cpp:func:`lammps_open` or :cpp:func:`lammps_open_no_mpi` from the C -library API to create the class instance. - -All arguments are optional. The *name* argument allows loading a -LAMMPS shared library that is named ``liblammps_machine.so`` instead of -the default name of ``liblammps.so``. In most cases the latter will be -installed or used. The *ptr* argument is for use of the -:py:mod:`lammps` module from inside a LAMMPS instance, e.g. with the -:doc:`python ` command, where a pointer to the already existing -:cpp:class:`LAMMPS ` class instance can be passed -to the Python class and used instead of creating a new instance. The -*comm* argument may be used in combination with the `mpi4py `_ -module to pass an MPI communicator to LAMMPS and thus it is possible -to run the Python module like the library interface on a subset of the -MPI ranks after splitting the communicator. - - -Here are simple examples using all three Python interfaces: - -.. tabs:: - - .. tab:: lammps API - - .. code-block:: python - - from lammps import lammps - - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] - # create LAMMPS instance - lmp = lammps(cmdargs=args) - # get and print numerical version code - print("LAMMPS Version: ", lmp.version()) - # explicitly close and delete LAMMPS instance (optional) - lmp.close() - - .. tab:: PyLammps API - - The :py:class:`PyLammps` class is a wrapper around the - :py:class:`lammps` class and all of its lower level functions. - By default, it will create a new instance of :py:class:`lammps` passing - along all arguments to the constructor of :py:class:`lammps`. - - .. code-block:: python - - from lammps import PyLammps - - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] - # create LAMMPS instance - L = PyLammps(cmdargs=args) - # get and print numerical version code - print("LAMMPS Version: ", L.version()) - # explicitly close and delete LAMMPS instance (optional) - L.close() - - :py:class:`PyLammps` objects can also be created on top of an existing :py:class:`lammps` object: - - .. code-block:: Python - - from lammps import lammps, PyLammps - ... - # create LAMMPS instance - lmp = lammps(cmdargs=args) - # create PyLammps instance using previously created LAMMPS instance - L = PyLammps(ptr=lmp) - - This is useful if you have to create the :py:class:`lammps ` - instance is a specific way, but want to take advantage of the - :py:class:`PyLammps ` interface. - - .. tab:: IPyLammps API - - The :py:class:`IPyLammps` class is an extension of the - :py:class:`PyLammps` class. It has the same construction behavior. By - default, it will create a new instance of :py:class:`lammps` passing - along all arguments to the constructor of :py:class:`lammps`. - - .. code-block:: python - - from lammps import IPyLammps - - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] - # create LAMMPS instance - L = IPyLammps(cmdargs=args) - # get and print numerical version code - print("LAMMPS Version: ", L.version()) - # explicitly close and delete LAMMPS instance (optional) - L.close() - - You can also initialize IPyLammps on top of an existing :py:class:`lammps` or :py:class:`PyLammps` object: - - .. code-block:: Python - - from lammps import lammps, IPyLammps - ... - # create LAMMPS instance - lmp = lammps(cmdargs=args) - # create PyLammps instance using previously created LAMMPS instance - L = PyLammps(ptr=lmp) - - This is useful if you have to create the :py:class:`lammps ` - instance is a specific way, but want to take advantage of the - :py:class:`IPyLammps ` interface. - -In all of the above cases, same as with the :ref:`C library API `, this will use the -``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was -compiled with. The :py:func:`lmp.close() ` call is -optional since the LAMMPS class instance will also be deleted -automatically during the :py:class:`lammps ` class -destructor. - -Executing LAMMPS commands -************************* - -Once an instance of the :py:class:`lammps`, :py:class:`PyLammps`, or -:py:class:`IPyLammps` class is created, there are multiple ways to "feed" it -commands. In a way that is not very different from running a LAMMPS input -script, except that Python has many more facilities for structured -programming than the LAMMPS input script syntax. Furthermore it is possible -to "compute" what the next LAMMPS command should be. - -.. tabs:: - - .. tab:: lammps API - - Same as in the equivalent - :doc:`C library functions `, commands can be read from a file, a - single string, a list of strings and a block of commands in a single - multi-line string. They are processed under the same boundary conditions - as the C library counterparts. The example below demonstrates the use - of :py:func:`lammps.file`, :py:func:`lammps.command`, - :py:func:`lammps.commands_list`, and :py:func:`lammps.commands_string`: - - .. code-block:: python - - from lammps import lammps - lmp = lammps() - # read commands from file 'in.melt' - lmp.file('in.melt') - # issue a single command - lmp.command('variable zpos index 1.0') - # create 10 groups with 10 atoms each - cmds = ["group g{} id {}:{}".format(i,10*i+1,10*(i+1)) for i in range(10)] - lmp.commands_list(cmds) - # run commands from a multi-line string - block = """ - clear - region box block 0 2 0 2 0 2 - create_box 1 box - create_atoms 1 single 1.0 1.0 ${zpos} - """ - lmp.commands_string(block) - - .. tab:: PyLammps/IPyLammps API - - Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS - commands by calling equivalent member functions. - - For instance, the following LAMMPS command - - .. code-block:: LAMMPS - - region box block 0 10 0 5 -0.5 0.5 - - can be executed using the following Python code if *L* is a :py:class:`lammps` instance: - - .. code-block:: Python - - L.command("region box block 0 10 0 5 -0.5 0.5") - - With the PyLammps interface, any LAMMPS command can be split up into arbitrary parts. - These parts are then passed to a member function with the name of the command. - For the ``region`` command that means the :code:`region` method can be called. - The arguments of the command can be passed as one string, or - individually. - - .. code-block:: Python - - L.region("box block", 0, 10, 0, 5, -0.5, 0.5) - - In this example all parameters except the first are Python floating-point literals. The - PyLammps interface takes the entire parameter list and transparently - merges it to a single command string. - - The benefit of this approach is avoiding redundant command calls and easier - parameterization. In the original interface parameterization this needed to be done - manually by creating formatted strings. - - .. code-block:: Python - - L.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) - - In contrast, methods of PyLammps accept parameters directly and will convert - them automatically to a final command string. - - .. code-block:: Python - - L.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) - - Using these facilities, the example shown for the lammps API can be rewritten as follows: - - .. code-block:: python - - from lammps import PyLammps - L = PyLammps() - # read commands from file 'in.melt' - L.file('in.melt') - # issue a single command - L.variable('zpos', 'index', 1.0) - # create 10 groups with 10 atoms each - for i in range(10): - L.group(f"g{i}", "id", f"{10*i+1}:{10*(i+1)}") - - L.clear() - L.region("box block", 0, 2, 0, 2, 0, 2) - L.create_box(1, "box") - L.create_atoms(1, "single", 1.0, 1.0, "${zpos}") - ----------- - The ``lammps`` class API -************************ +======================== The :py:class:`lammps ` class is the core of the LAMMPS Python interfaces. It is a wrapper around the :ref:`LAMMPS C library @@ -314,10 +44,13 @@ functions. Below is a detailed documentation of the API. .. autoclass:: lammps.lammps :members: +.. autoclass:: lammps.numpy_wrapper + :members: + ---------- The ``PyLammps`` class API -************************** +========================== The :py:class:`PyLammps ` class is a wrapper that creates a simpler, more "Pythonic" interface to common LAMMPS functionality. LAMMPS @@ -340,7 +73,7 @@ scripts shorter and more concise. See the :doc:`PyLammps Tutorial ---------- The ``IPyLammps`` class API -*************************** +=========================== The :py:class:`IPyLammps ` class is an extension of :py:class:`PyLammps `, adding additional functions to @@ -353,12 +86,12 @@ See the :doc:`PyLammps Tutorial ` for examples. ---------- Additional components of the ``lammps`` module -********************************************** +============================================== The :py:mod:`lammps` module additionally contains several constants and the :py:class:`NeighList ` class: -.. _py_data_constants: +.. _py_datatype_constants: Data Types ---------- @@ -383,7 +116,9 @@ Style Constants Constants in the :py:mod:`lammps` module to select what style of data to request from computes or fixes. See :cpp:enum:`_LMP_STYLE_CONST` for the equivalent constants in the C library interface. Used in - :py:func:`lammps.extract_compute` and :py:func:`lammps.extract_fix`. + :py:func:`lammps.extract_compute`, :py:func:`lammps.extract_fix`, and their NumPy variants + :py:func:`lammps.numpy.extract_compute() ` and + :py:func:`lammps.numpy.extract_fix() `. .. _py_type_constants: @@ -396,18 +131,20 @@ Type Constants Constants in the :py:mod:`lammps` module to select what type of data to request from computes or fixes. See :cpp:enum:`_LMP_TYPE_CONST` for the equivalent constants in the C library interface. Used in - :py:func:`lammps.extract_compute` and :py:func:`lammps.extract_fix`. + :py:func:`lammps.extract_compute`, :py:func:`lammps.extract_fix`, and their NumPy variants + :py:func:`lammps.numpy.extract_compute() ` and + :py:func:`lammps.numpy.extract_fix() `. -.. _py_var_constants: +.. _py_vartype_constants: -Variable Style Constants +Variable Type Constants ------------------------ .. py:data:: LMP_VAR_EQUAL, LMP_VAR_ATOM :type: int - Constants in the :py:mod:`lammps` module to select what style of - variable to query when calling :py:func:`lammps.extract_variable`. + Constants in the :py:mod:`lammps` module to select what type of + variable to query when calling :py:func:`lammps.extract_variable`. See also: :doc:`variable command `. Classes representing internal objects ------------------------------------- @@ -416,19 +153,6 @@ Classes representing internal objects :members: :no-undoc-members: - -LAMMPS error handling in Python -******************************* - -Compiling the shared library with :ref:`C++ exception support ` provides a better error -handling experience. Without exceptions the LAMMPS code will terminate the -current Python process with an error message. C++ exceptions allow capturing -them on the C++ side and rethrowing them on the Python side. This way -LAMMPS errors can be handled through the Python exception handling mechanism. - -.. warning:: - - Capturing a LAMMPS exception in Python can still mean that the - current LAMMPS process is in an illegal state and must be terminated. It is - advised to save your data and terminate the Python instance as quickly as - possible. +.. autoclass:: lammps.NumPyNeighList + :members: + :no-undoc-members: diff --git a/doc/src/Python_mpi.rst b/doc/src/Python_mpi.rst deleted file mode 100644 index 02a62c89d0..0000000000 --- a/doc/src/Python_mpi.rst +++ /dev/null @@ -1,71 +0,0 @@ -Extending Python to run in parallel -=================================== - -If you wish to run LAMMPS in parallel from Python, you need to extend -your Python with an interface to MPI. This also allows you to -make MPI calls directly from Python in your script, if you desire. - -We have tested this with mpi4py and pypar: - -* `MPI for Python `_ -* `pypar `_ - -We recommend the use of mpi4py as it is the more complete MPI interface, -and as of version 2.0.0 mpi4py allows passing a custom MPI communicator -to the LAMMPS constructor, which means one can easily run one or more -LAMMPS instances on subsets of the total MPI ranks. - -To install mpi4py (version mpi4py-3.0.3 as of Nov 2019), unpack it -and from its main directory, type - -.. code-block:: bash - - python setup.py build - sudo python setup.py install - -Again, the "sudo" is only needed if required to copy mpi4py files into -your Python distribution's site-packages directory. To install with -user privilege into the user local directory type - -.. code-block:: bash - - python setup.py install --user - -If you have successfully installed mpi4py, you should be able to run -Python and type - -.. code-block:: python - - from mpi4py import MPI - -without error. You should also be able to run python in parallel -on a simple test script - -.. code-block:: bash - - % mpirun -np 4 python test.py - -where test.py contains the lines - -.. code-block:: python - - from mpi4py import MPI - comm = MPI.COMM_WORLD - print "Proc %d out of %d procs" % (comm.Get_rank(),comm.Get_size()) - -and see one line of output for each processor you run on. - -.. note:: - - To use mpi4py and LAMMPS in parallel from Python, you must - insure both are using the same version of MPI. If you only have one - MPI installed on your system, this is not an issue, but it can be if - you have multiple MPIs. Your LAMMPS build is explicit about which MPI - it is using, since you specify the details in your low-level - src/MAKE/Makefile.foo file. Mpi4py uses the "mpicc" command to find - information about the MPI it uses to build against. And it tries to - load "libmpi.so" from the LD_LIBRARY_PATH. This may or may not find - the MPI library that LAMMPS is using. If you have problems running - both mpi4py and LAMMPS together, this is an issue you may need to - address, e.g. by moving other MPI installations so that mpi4py finds - the right one. diff --git a/doc/src/Python_neighbor.rst b/doc/src/Python_neighbor.rst new file mode 100644 index 0000000000..2e8f84050c --- /dev/null +++ b/doc/src/Python_neighbor.rst @@ -0,0 +1,18 @@ +Accessing LAMMPS Neighbor lists +******************************* + +**Methods:** + +* :py:meth:`lammps.get_neighlist() `: Get neighbor list for given index +* :py:meth:`lammps.get_neighlist_size()`: Get number of elements in neighbor list +* :py:meth:`lammps.get_neighlist_element_neighbors()`: Get element in neighbor list and its neighbors + +* :py:meth:`lammps.find_pair_neighlist() `: Find neighbor list of pair style +* :py:meth:`lammps.find_fix_neighlist() `: Find neighbor list of pair style +* :py:meth:`lammps.find_compute_neighlist() `: Find neighbor list of pair style + + +**NumPy Methods:** + +* :py:meth:`lammps.numpy.get_neighlist() `: Get neighbor list for given index, which uses NumPy arrays for its element neighbor arrays +* :py:meth:`lammps.numpy.get_neighlist_element_neighbors() `: Get element in neighbor list and its neighbors (as numpy array) diff --git a/doc/src/Python_overview.rst b/doc/src/Python_overview.rst index a41de5d444..cbc656b602 100644 --- a/doc/src/Python_overview.rst +++ b/doc/src/Python_overview.rst @@ -1,25 +1,92 @@ -Overview of Python and LAMMPS -============================= +Overview +======== + +The LAMMPS distribution includes a python directory with all you need to +run LAMMPS from Python. The ``python/lammps.py`` contains :doc:`the +"lammps" Python ` that wraps the LAMMPS C-library +interface. This file makes it is possible to do the following either +from a Python script, or interactively from a Python prompt: + +- create one or more instances of LAMMPS +- invoke LAMMPS commands or read them from an input script +- run LAMMPS incrementally +- extract LAMMPS results +- and modify internal LAMMPS data structures. + +From a Python script you can do this in serial or parallel. Running +Python interactively in parallel does not generally work, unless you +have a version of Python that extends Python to enable multiple +instances of Python to read what you type. + +To do all of this, you must build LAMMPS in :ref:`"shared" mode ` +and make certain that your Python interpreter can find the ``lammps.py`` +file and the LAMMPS shared library file. + +.. _ctypes: https://docs.python.org/3/library/ctypes.html + +The Python wrapper for LAMMPS uses the `ctypes `_ package in +Python, which auto-generates the interface code needed between Python +and a set of C-style library functions. Ctypes has been part of the +standard Python distribution since version 2.5. You can check which +version of Python you have by simply typing "python" at a shell prompt. +Below is an example output for Python version 3.8.5. + +.. code-block:: + + $ python + Python 3.8.5 (default, Aug 12 2020, 00:00:00) + [GCC 10.2.1 20200723 (Red Hat 10.2.1-1)] on linux + Type "help", "copyright", "credits" or "license" for more information. + >>> + + +.. warning:: Python 2 support is deprecated + + While the LAMMPS Python module was originally developed to support + both, Python 2 and 3, any new code is only tested with Python 3. + Please note that Python 2 is no longer maintained as of `January 1, + 2020 `_. Therefore, we + highly recommend using Python version 3.6 or later. Compatibility to + Python 2 will be removed eventually. + +--------- LAMMPS can work together with Python in three ways. First, Python can -wrap LAMMPS through the its :doc:`library interface `, so +wrap LAMMPS through the its :doc:`library interface `, so that a Python script can create one or more instances of LAMMPS and -launch one or more simulations. In Python lingo, this is called +launch one or more simulations. In Python terms, this is referred to as "extending" Python with a LAMMPS module. -Second, a lower-level Python interface can be used indirectly through -the provided PyLammps and IPyLammps wrapper classes, written in Python. -These wrappers try to simplify the usage of LAMMPS in Python by -providing an object-based interface to common LAMMPS functionality. -They also reduces the amount of code necessary to parameterize LAMMPS -scripts through Python and make variables and computes directly -accessible. +.. figure:: JPG/python-invoke-lammps.png + :figclass: align-center -Third, LAMMPS can use the Python interpreter, so that a LAMMPS -input script or styles can invoke Python code directly, and pass -information back-and-forth between the input script and Python -functions you write. This Python code can also callback to LAMMPS -to query or change its attributes through the LAMMPS Python module -mentioned above. In Python lingo, this is "embedding" Python in -LAMMPS. When used in this mode, Python can perform script operations -that the simple LAMMPS input script syntax can not. + Launching LAMMPS via Python + + +Second, the lower-level Python interface in the :py:class:`lammps Python +class ` can be used indirectly through the provided +:py:class:`PyLammps ` and :py:class:`IPyLammps +` wrapper classes, also written in Python. These +wrappers try to simplify the usage of LAMMPS in Python by providing a +more object-based interface to common LAMMPS functionality. They also +reduce the amount of code necessary to parameterize LAMMPS scripts +through Python and make variables and computes directly accessible. + +.. figure:: JPG/pylammps-invoke-lammps.png + :figclass: align-center + + Using the PyLammps / IPyLammps wrappers + +Third, LAMMPS can use the Python interpreter, so that a LAMMPS input +script or styles can invoke Python code directly, and pass information +back-and-forth between the input script and Python functions you write. +This Python code can also call back to LAMMPS to query or change its +attributes through the LAMMPS Python module mentioned above. In Python +terms, this is called "embedding" Python into LAMMPS. When used in this +mode, Python can perform script operations that the simple LAMMPS input +script syntax can not. + +.. figure:: JPG/lammps-invoke-python.png + :figclass: align-center + + Calling Python code from LAMMPS diff --git a/doc/src/Python_pylammps.rst b/doc/src/Python_pylammps.rst deleted file mode 100644 index ad34706f50..0000000000 --- a/doc/src/Python_pylammps.rst +++ /dev/null @@ -1,5 +0,0 @@ -PyLammps interface -================== - -PyLammps is a Python wrapper class which can be created on its own or -use an existing lammps Python object. It has its own :doc:`Howto pylammps ` doc page. diff --git a/doc/src/Python_run.rst b/doc/src/Python_run.rst index 9aab6da9c6..41086a63d6 100644 --- a/doc/src/Python_run.rst +++ b/doc/src/Python_run.rst @@ -1,32 +1,110 @@ Run LAMMPS from Python ====================== -The LAMMPS distribution includes a python directory with all you need -to run LAMMPS from Python. The python/lammps.py file wraps the LAMMPS -library interface, with one wrapper function per LAMMPS library -function. This file makes it is possible to do the following either -from a Python script, or interactively from a Python prompt: create -one or more instances of LAMMPS, invoke LAMMPS commands or give it an -input script, run LAMMPS incrementally, extract LAMMPS results, an -modify internal LAMMPS variables. From a Python script you can do -this in serial or parallel. Running Python interactively in parallel -does not generally work, unless you have a version of Python that -extends Python to enable multiple instances of Python to read what you -type. +Running LAMMPS and Python in serial: +------------------------------------- -To do all of this, you must first build LAMMPS as a shared library, -then insure that your Python can find the python/lammps.py file and -the shared library. +To run a LAMMPS in serial, type these lines into Python +interactively from the ``bench`` directory: -Two advantages of using Python to run LAMMPS are how concise the -language is, and that it can be run interactively, enabling rapid -development and debugging. If you use it to mostly invoke costly -operations within LAMMPS, such as running a simulation for a -reasonable number of timesteps, then the overhead cost of invoking -LAMMPS through Python will be negligible. +.. code-block:: python -The Python wrapper for LAMMPS uses the "ctypes" package in Python, -which auto-generates the interface code needed between Python and a -set of C-style library functions. Ctypes is part of standard Python -for versions 2.5 and later. You can check which version of Python you -have by simply typing "python" at a shell prompt. + >>> from lammps import lammps + >>> lmp = lammps() + >>> lmp.file("in.lj") + +Or put the same lines in the file ``test.py`` and run it as + +.. code-block:: bash + + $ python3 test.py + +Either way, you should see the results of running the ``in.lj`` benchmark +on a single processor appear on the screen, the same as if you had +typed something like: + +.. code-block:: bash + + lmp_serial -in in.lj + +Running LAMMPS and Python in parallel with MPI (mpi4py) +------------------------------------------------------- + +To run LAMMPS in parallel, assuming you have installed the +`mpi4py `_ package as discussed +:ref:`python_install_mpi4py`, create a ``test.py`` file containing these lines: + +.. code-block:: python + + from mpi4py import MPI + from lammps import lammps + lmp = lammps() + lmp.file("in.lj") + me = MPI.COMM_WORLD.Get_rank() + nprocs = MPI.COMM_WORLD.Get_size() + print("Proc %d out of %d procs has" % (me,nprocs),lmp) + MPI.Finalize() + +You can run the script in parallel as: + +.. code-block:: bash + + $ mpirun -np 4 python3 test.py + +and you should see the same output as if you had typed + +.. code-block:: bash + + $ mpirun -np 4 lmp_mpi -in in.lj + +Note that without the mpi4py specific lines from ``test.py`` + +.. code-block:: + + from lammps import lammps + lmp = lammps() + lmp.file("in.lj") + +running the script with ``mpirun`` on :math:`P` processors would lead to +:math:`P` independent simulations to run parallel, each with a single +processor. Therefore, if you use the mpi4py lines and you see multiple LAMMPS +single processor outputs, mpi4py is not working correctly. + +Also note that once you import the mpi4py module, mpi4py initializes MPI +for you, and you can use MPI calls directly in your Python script, as +described in the mpi4py documentation. The last line of your Python +script should be ``MPI.finalize()``, to insure MPI is shut down +correctly. + +Running Python scripts +---------------------- + +Note that any Python script (not just for LAMMPS) can be invoked in +one of several ways: + +.. code-block:: bash + + $ python script.py + $ python -i script.py + $ ./script.py + +The last command requires that the first line of the script be +something like this: + +.. code-block:: bash + + #!/usr/bin/python + #!/usr/bin/python -i + +where the path points to where you have Python installed, and that you +have made the script file executable: + +.. code-block:: bash + + $ chmod +x script.py + +Without the ``-i`` flag, Python will exit when the script finishes. +With the ``-i`` flag, you will be left in the Python interpreter when +the script finishes, so you can type subsequent commands. As +mentioned above, you can only run Python interactively when running +Python on a single processor, not in parallel. diff --git a/doc/src/Python_shlib.rst b/doc/src/Python_shlib.rst deleted file mode 100644 index b02fcd7bb8..0000000000 --- a/doc/src/Python_shlib.rst +++ /dev/null @@ -1,78 +0,0 @@ -Build LAMMPS as a shared library -================================ - -.. TODO this is mostly redundant and should be addressed in the 'progguide' branch if it has not already - -Build LAMMPS as a shared library using make -------------------------------------------- - -Instructions on how to build LAMMPS as a shared library are given on -the :doc:`Build_basics ` doc page. A shared library is -one that is dynamically loadable, which is what Python requires to -wrap LAMMPS. On Linux this is a library file that ends in ".so", not -".a". - -From the src directory, type - -.. code-block:: bash - - make foo mode=shared - -where foo is the machine target name, such as mpi or serial. -This should create the file liblammps_foo.so in the src directory, as -well as a soft link liblammps.so, which is what the Python wrapper will -load by default. Note that if you are building multiple machine -versions of the shared library, the soft link is always set to the -most recently built version. - -.. note:: - - If you are building LAMMPS with an MPI or FFT library or other - auxiliary libraries (used by various packages), then all of these - extra libraries must also be shared libraries. If the LAMMPS - shared-library build fails with an error complaining about this, see - the :doc:`Build_basics ` doc page. - -Build LAMMPS as a shared library using CMake --------------------------------------------- - -When using CMake the following two options are necessary to generate the LAMMPS -shared library: - -.. code-block:: bash - - -D BUILD_SHARED_LIBS=on # enable building of LAMMPS shared library (both options are needed!) - -What this does is create a liblammps.so which contains the majority of LAMMPS -code. The generated lmp binary also dynamically links to this library. This -means that either this liblammps.so file has to be in the same directory, a system -library path (e.g. /usr/lib64/) or in the LD_LIBRARY_PATH. - -If you want to use the shared library with Python the recommended way is to create a virtualenv and use it as -CMAKE_INSTALL_PREFIX. - -.. code-block:: bash - - # create virtualenv - virtualenv --python=$(which python3) myenv3 - source myenv3/bin/activate - - # build library - mkdir build - cd build - cmake -D PKG_PYTHON=on -D BUILD_SHARED_LIBS=on -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV ../cmake - make -j 4 - - # install into prefix - make install - -This will also install the Python module into your virtualenv. Since virtualenv -does not change your LD_LIBRARY_PATH, you still need to add its lib64 folder to -it, which contains the installed liblammps.so. - -.. code-block:: bash - - export LD_LIBRARY_PATH=$VIRTUAL_ENV/lib64:$LD_LIBRARY_PATH - -Starting Python outside (!) of your build directory, but with the virtualenv -enabled and with the LD_LIBRARY_PATH set gives you access to LAMMPS via Python. diff --git a/doc/src/Python_test.rst b/doc/src/Python_test.rst deleted file mode 100644 index 323feec2d8..0000000000 --- a/doc/src/Python_test.rst +++ /dev/null @@ -1,152 +0,0 @@ -Test the Python/LAMMPS interface -================================ - -To test if LAMMPS is callable from Python, launch Python interactively -and type: - -.. parsed-literal:: - - >>> from lammps import lammps - >>> lmp = lammps() - -If you get no errors, you're ready to use LAMMPS from Python. If the -second command fails, the most common error to see is - -.. parsed-literal:: - - OSError: Could not load LAMMPS dynamic library - -which means Python was unable to load the LAMMPS shared library. This -typically occurs if the system can't find the LAMMPS shared library or -one of the auxiliary shared libraries it depends on, or if something -about the library is incompatible with your Python. The error message -should give you an indication of what went wrong. - -You can also test the load directly in Python as follows, without -first importing from the lammps.py file: - -.. parsed-literal:: - - >>> from ctypes import CDLL - >>> CDLL("liblammps.so") - -If an error occurs, carefully go through the steps on the -:doc:`Build_basics ` doc page about building a shared -library and the :doc:`Python_install ` doc page about -insuring Python can find the necessary two files it needs. - -Test LAMMPS and Python in serial: -------------------------------------- - -To run a LAMMPS test in serial, type these lines into Python -interactively from the bench directory: - -.. parsed-literal:: - - >>> from lammps import lammps - >>> lmp = lammps() - >>> lmp.file("in.lj") - -Or put the same lines in the file test.py and run it as - -.. code-block:: bash - - % python test.py - -Either way, you should see the results of running the in.lj benchmark -on a single processor appear on the screen, the same as if you had -typed something like: - -.. parsed-literal:: - - lmp_g++ -in in.lj - -Test LAMMPS and Python in parallel: ---------------------------------------- - -To run LAMMPS in parallel, assuming you have installed the -`PyPar `_ package as discussed -above, create a test.py file containing these lines: - -.. code-block:: python - - import pypar - from lammps import lammps - lmp = lammps() - lmp.file("in.lj") - print "Proc %d out of %d procs has" % (pypar.rank(),pypar.size()),lmp - pypar.finalize() - -To run LAMMPS in parallel, assuming you have installed the -`mpi4py `_ package as discussed -above, create a test.py file containing these lines: - -.. code-block:: python - - from mpi4py import MPI - from lammps import lammps - lmp = lammps() - lmp.file("in.lj") - me = MPI.COMM_WORLD.Get_rank() - nprocs = MPI.COMM_WORLD.Get_size() - print "Proc %d out of %d procs has" % (me,nprocs),lmp - MPI.Finalize() - -You can either script in parallel as: - -.. code-block:: bash - - % mpirun -np 4 python test.py - -and you should see the same output as if you had typed - -.. code-block:: bash - - % mpirun -np 4 lmp_g++ -in in.lj - -Note that if you leave out the 3 lines from test.py that specify PyPar -commands you will instantiate and run LAMMPS independently on each of -the P processors specified in the mpirun command. In this case you -should get 4 sets of output, each showing that a LAMMPS run was made -on a single processor, instead of one set of output showing that -LAMMPS ran on 4 processors. If the 1-processor outputs occur, it -means that PyPar is not working correctly. - -Also note that once you import the PyPar module, PyPar initializes MPI -for you, and you can use MPI calls directly in your Python script, as -described in the PyPar documentation. The last line of your Python -script should be pypar.finalize(), to insure MPI is shut down -correctly. - -Running Python scripts: ---------------------------- - -Note that any Python script (not just for LAMMPS) can be invoked in -one of several ways: - -.. code-block:: bash - - % python foo.script - % python -i foo.script - % foo.script - -The last command requires that the first line of the script be -something like this: - -.. code-block:: bash - - #!/usr/local/bin/python - #!/usr/local/bin/python -i - -where the path points to where you have Python installed, and that you -have made the script file executable: - -.. code-block:: bash - - % chmod +x foo.script - -Without the "-i" flag, Python will exit when the script finishes. -With the "-i" flag, you will be left in the Python interpreter when -the script finishes, so you can type subsequent commands. As -mentioned above, you can only run Python interactively when running -Python on a single processor, not in parallel. diff --git a/doc/src/Python_trouble.rst b/doc/src/Python_trouble.rst new file mode 100644 index 0000000000..3ef7dacf34 --- /dev/null +++ b/doc/src/Python_trouble.rst @@ -0,0 +1,44 @@ +Troubleshooting +*************** + +Testing if Python can launch LAMMPS +=================================== + +To test if LAMMPS is callable from Python, launch Python interactively +and type: + +.. code-block:: python + + >>> from lammps import lammps + >>> lmp = lammps() + +If you get no errors, you're ready to use LAMMPS from Python. If the +second command fails, the most common error to see is + +.. code-block:: bash + + OSError: Could not load LAMMPS dynamic library + +which means Python was unable to load the LAMMPS shared library. This +typically occurs if the system can't find the LAMMPS shared library or +one of the auxiliary shared libraries it depends on, or if something +about the library is incompatible with your Python. The error message +should give you an indication of what went wrong. + +If your shared library uses a suffix, such as ``liblammps_mpi.so``, change +the constructor call as follows (see :ref:`python_create_lammps` for more details): + +.. code-block:: python + + >>> lmp = lammps(name='mpi') + +You can also test the load directly in Python as follows, without +first importing from the lammps.py file: + +.. code-block:: python + + >>> from ctypes import CDLL + >>> CDLL("liblammps.so") + +If an error occurs, carefully go through the steps in :ref:`python_install_guides` and on the +:doc:`Build_basics ` doc page about building a shared library. diff --git a/doc/src/Python_usage.rst b/doc/src/Python_usage.rst new file mode 100644 index 0000000000..777f226e68 --- /dev/null +++ b/doc/src/Python_usage.rst @@ -0,0 +1,569 @@ +.. _mpi4py_url: https://mpi4py.readthedocs.io/ + +.. _python_create_lammps: + +Creating or deleting a LAMMPS object +************************************ + +With the Python interface the creation of a :cpp:class:`LAMMPS +` instance is included in the constructors for the +:py:class:`lammps `, :py:class:`PyLammps `, +and :py:class:`IPyLammps ` classes. +Internally it will call either :cpp:func:`lammps_open` or :cpp:func:`lammps_open_no_mpi` from the C +library API to create the class instance. + +All arguments are optional. The *name* argument allows loading a +LAMMPS shared library that is named ``liblammps_machine.so`` instead of +the default name of ``liblammps.so``. In most cases the latter will be +installed or used. The *ptr* argument is for use of the +:py:mod:`lammps` module from inside a LAMMPS instance, e.g. with the +:doc:`python ` command, where a pointer to the already existing +:cpp:class:`LAMMPS ` class instance can be passed +to the Python class and used instead of creating a new instance. The +*comm* argument may be used in combination with the `mpi4py `_ +module to pass an MPI communicator to LAMMPS and thus it is possible +to run the Python module like the library interface on a subset of the +MPI ranks after splitting the communicator. + + +Here are simple examples using all three Python interfaces: + +.. tabs:: + + .. tab:: lammps API + + .. code-block:: python + + from lammps import lammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + + # create LAMMPS instance + lmp = lammps(cmdargs=args) + + # get and print numerical version code + print("LAMMPS Version: ", lmp.version()) + + # explicitly close and delete LAMMPS instance (optional) + lmp.close() + + .. tab:: PyLammps API + + The :py:class:`PyLammps ` class is a wrapper around the + :py:class:`lammps ` class and all of its lower level functions. + By default, it will create a new instance of :py:class:`lammps ` passing + along all arguments to the constructor of :py:class:`lammps `. + + .. code-block:: python + + from lammps import PyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + + # create LAMMPS instance + L = PyLammps(cmdargs=args) + + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + + # explicitly close and delete LAMMPS instance (optional) + L.close() + + :py:class:`PyLammps ` objects can also be created on top of an existing + :py:class:`lammps ` object: + + .. code-block:: Python + + from lammps import lammps, PyLammps + ... + # create LAMMPS instance + lmp = lammps(cmdargs=args) + + # create PyLammps instance using previously created LAMMPS instance + L = PyLammps(ptr=lmp) + + This is useful if you have to create the :py:class:`lammps ` + instance is a specific way, but want to take advantage of the + :py:class:`PyLammps ` interface. + + .. tab:: IPyLammps API + + The :py:class:`IPyLammps ` class is an extension of the + :py:class:`PyLammps ` class. It has the same construction behavior. By + default, it will create a new instance of :py:class:`lammps` passing + along all arguments to the constructor of :py:class:`lammps`. + + .. code-block:: python + + from lammps import IPyLammps + + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] + + # create LAMMPS instance + L = IPyLammps(cmdargs=args) + + # get and print numerical version code + print("LAMMPS Version: ", L.version()) + + # explicitly close and delete LAMMPS instance (optional) + L.close() + + You can also initialize IPyLammps on top of an existing :py:class:`lammps` or :py:class:`PyLammps` object: + + .. code-block:: Python + + from lammps import lammps, IPyLammps + ... + # create LAMMPS instance + lmp = lammps(cmdargs=args) + + # create PyLammps instance using previously created LAMMPS instance + L = PyLammps(ptr=lmp) + + This is useful if you have to create the :py:class:`lammps ` + instance is a specific way, but want to take advantage of the + :py:class:`IPyLammps ` interface. + +In all of the above cases, same as with the :ref:`C library API `, this will use the +``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was +compiled with. + +The :py:func:`lmp.close() ` call is +optional since the LAMMPS class instance will also be deleted +automatically during the :py:class:`lammps ` class +destructor. + +Note that you can create multiple LAMMPS objects in your Python +script, and coordinate and run multiple simulations, e.g. + +.. code-block:: Python + + from lammps import lammps + lmp1 = lammps() + lmp2 = lammps() + lmp1.file("in.file1") + lmp2.file("in.file2") + +Executing LAMMPS commands +************************* + +Once an instance of the :py:class:`lammps `, +:py:class:`PyLammps `, or +:py:class:`IPyLammps ` class is created, there are +multiple ways to "feed" it commands. In a way that is not very different from +running a LAMMPS input script, except that Python has many more facilities +for structured programming than the LAMMPS input script syntax. Furthermore +it is possible to "compute" what the next LAMMPS command should be. + +.. tabs:: + + .. tab:: lammps API + + Same as in the equivalent + :doc:`C library functions `, commands can be read from a file, a + single string, a list of strings and a block of commands in a single + multi-line string. They are processed under the same boundary conditions + as the C library counterparts. The example below demonstrates the use + of :py:func:`lammps.file()`, :py:func:`lammps.command()`, + :py:func:`lammps.commands_list()`, and :py:func:`lammps.commands_string()`: + + .. code-block:: python + + from lammps import lammps + lmp = lammps() + + # read commands from file 'in.melt' + lmp.file('in.melt') + + # issue a single command + lmp.command('variable zpos index 1.0') + + # create 10 groups with 10 atoms each + cmds = ["group g{} id {}:{}".format(i,10*i+1,10*(i+1)) for i in range(10)] + lmp.commands_list(cmds) + + # run commands from a multi-line string + block = """ + clear + region box block 0 2 0 2 0 2 + create_box 1 box + create_atoms 1 single 1.0 1.0 ${zpos} + """ + lmp.commands_string(block) + + .. tab:: PyLammps/IPyLammps API + + Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS + commands by calling equivalent member functions of :py:class:`PyLammps ` + and :py:class:`IPyLammps ` instances. + + For instance, the following LAMMPS command + + .. code-block:: LAMMPS + + region box block 0 10 0 5 -0.5 0.5 + + can be executed using with the lammps AI with the following Python code if *L* is an + instance of :py:class:`lammps `: + + .. code-block:: Python + + L.command("region box block 0 10 0 5 -0.5 0.5") + + With the PyLammps interface, any LAMMPS command can be split up into arbitrary parts. + These parts are then passed to a member function with the name of the command. + For the ``region`` command that means the :code:`region()` method can be called. + The arguments of the command can be passed as one string, or + individually. + + .. code-block:: Python + + L.region("box block", 0, 10, 0, 5, -0.5, 0.5) + + In this example all parameters except the first are Python floating-point literals. The + PyLammps interface takes the entire parameter list and transparently + merges it to a single command string. + + The benefit of this approach is avoiding redundant command calls and easier + parameterization. In the original interface parameterization this needed to be done + manually by creating formatted strings. + + .. code-block:: Python + + L.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) + + In contrast, methods of PyLammps accept parameters directly and will convert + them automatically to a final command string. + + .. code-block:: Python + + L.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) + + Using these facilities, the example shown for the lammps API can be rewritten as follows: + + .. code-block:: python + + from lammps import PyLammps + L = PyLammps() + + # read commands from file 'in.melt' + L.file('in.melt') + + # issue a single command + L.variable('zpos', 'index', 1.0) + + # create 10 groups with 10 atoms each + for i in range(10): + L.group(f"g{i}", "id", f"{10*i+1}:{10*(i+1)}") + + L.clear() + L.region("box block", 0, 2, 0, 2, 0, 2) + L.create_box(1, "box") + L.create_atoms(1, "single", 1.0, 1.0, "${zpos}") + + +Retrieving or setting LAMMPS system properties +********************************************** + +Similar to what is described in :doc:`Library_properties`, the instances of +:py:class:`lammps `, :py:class:`PyLammps `, or +:py:class:`IPyLammps ` can be used to extract different kinds +of information about the active LAMMPS instance and also to modify some of it. The +main difference between the interfaces is how the information is exposed. + +While the :py:class:`lammps ` is just a thin layer that wraps C API calls, +:py:class:`PyLammps ` and :py:class:`IPyLammps ` expose +information as objects and properties. + +In some cases the data returned is a direct reference to the original data +inside LAMMPS cast to ``ctypes`` pointers. Where possible, the wrappers will +determine the ``ctypes`` data type and cast pointers accordingly. If +``numpy`` is installed arrays can also be extracted as numpy arrays, which +will access the C arrays directly and have the correct dimensions to protect +against invalid accesses. + +.. warning:: + + When accessing per-atom data, + please note that this data is the per-processor local data and indexed + accordingly. These arrays can change sizes and order at every neighbor list + rebuild and atom sort event as atoms are migrating between sub-domains. + +.. tabs:: + + .. tab:: lammps API + + .. code-block:: python + + from lammps import lammps + + lmp = lammps() + lmp.file("in.sysinit") + + natoms = lmp.get_natoms() + print(f"running simulation with {natoms} atoms") + + lmp.command("run 1000 post no"); + + for i in range(10): + lmp.command("run 100 pre no post no") + pe = lmp.get_thermo("pe") + ke = lmp.get_thermo("ke") + print(f"PE = {pe}\nKE = {ke}") + + lmp.close() + + **Methods**: + + * :py:meth:`version() `: return the numerical version id, e.g. LAMMPS 2 Sep 2015 -> 20150902 + * :py:meth:`get_thermo() `: return current value of a thermo keyword + * :py:meth:`get_natoms() `: total # of atoms as int + * :py:meth:`reset_box() `: reset the simulation box size + * :py:meth:`extract_setting() `: return a global setting + * :py:meth:`extract_global() `: extract a global quantity + * :py:meth:`extract_atom() `: extract a per-atom quantity + * :py:meth:`extract_box() `: extract box info + * :py:meth:`create_atoms() `: create N atoms with IDs, types, x, v, and image flags + + **Numpy Methods**: + + * :py:meth:`numpy.extract_atom() `: extract a per-atom quantity as numpy array + + .. tab:: PyLammps/IPyLammps API + + In addition to the functions provided by :py:class:`lammps `, :py:class:`PyLammps ` objects + have several properties which allow you to query the system state: + + L.system + Is a dictionary describing the system such as the bounding box or number of atoms + + L.system.xlo, L.system.xhi + bounding box limits along x-axis + + L.system.ylo, L.system.yhi + bounding box limits along y-axis + + L.system.zlo, L.system.zhi + bounding box limits along z-axis + + L.communication + configuration of communication subsystem, such as the number of threads or processors + + L.communication.nthreads + number of threads used by each LAMMPS process + + L.communication.nprocs + number of MPI processes used by LAMMPS + + L.fixes + List of fixes in the current system + + L.computes + List of active computes in the current system + + L.dump + List of active dumps in the current system + + L.groups + List of groups present in the current system + + **Retrieving the value of an arbitrary LAMMPS expressions** + + LAMMPS expressions can be immediately evaluated by using the ``eval`` method. The + passed string parameter can be any expression containing global :doc:`thermo` values, + variables, compute or fix data (see :doc:`Howto_output`): + + + .. code-block:: Python + + result = L.eval("ke") # kinetic energy + result = L.eval("pe") # potential energy + + result = L.eval("v_t/2.0") + + **Example** + + .. code-block:: python + + from lammps import PyLammps + + L = PyLammps() + L.file("in.sysinit") + + print(f"running simulation with {L.system.natoms} atoms") + + L.run(1000, "post no"); + + for i in range(10): + L.run(100, "pre no post no") + pe = L.eval("pe") + ke = L.eval("ke") + print(f"PE = {pe}\nKE = {ke}") + + + +Retrieving or setting properties of LAMMPS objects +************************************************** + +This section documents accessing or modifying data from objects like +computes, fixes, or variables in LAMMPS using the :py:mod:`lammps` module. + +.. tabs:: + + .. tab:: lammps API + + For :py:meth:`lammps.extract_compute() ` and + :py:meth:`lammps.extract_fix() `, the global, per-atom, + or local data calculated by the compute or fix can be accessed. What is returned + depends on whether the compute or fix calculates a scalar or vector or array. + For a scalar, a single double value is returned. If the compute or fix calculates + a vector or array, a pointer to the internal LAMMPS data is returned, which you can + use via normal Python subscripting. + + The one exception is that for a fix that calculates a + global vector or array, a single double value from the vector or array + is returned, indexed by I (vector) or I and J (array). I,J are + zero-based indices. + See the :doc:`Howto output ` doc page for a discussion of + global, per-atom, and local data, and of scalar, vector, and array + data types. See the doc pages for individual :doc:`computes ` + and :doc:`fixes ` for a description of what they calculate and + store. + + For :py:meth:`lammps.extract_variable() `, + an :doc:`equal-style or atom-style variable ` is evaluated and + its result returned. + + For equal-style variables a single ``c_double`` value is returned and the + group argument is ignored. For atom-style variables, a vector of + ``c_double`` is returned, one value per atom, which you can use via normal + Python subscripting. The values will be zero for atoms not in the + specified group. + + :py:meth:`lammps.numpy.extract_compute() `, + :py:meth:`lammps.numpy.extract_fix() `, and + :py:meth:`lammps.numpy.extract_variable() ` are + equivalent NumPy implementations that return NumPy arrays instead of ``ctypes`` pointers. + + The :py:meth:`lammps.set_variable() ` method sets an + existing string-style variable to a new string value, so that subsequent LAMMPS + commands can access the variable. + + **Methods**: + + * :py:meth:`lammps.extract_compute() `: extract value(s) from a compute + * :py:meth:`lammps.extract_fix() `: extract value(s) from a fix + * :py:meth:`lammps.extract_variable() `: extract value(s) from a variable + * :py:meth:`lammps.set_variable() `: set existing named string-style variable to value + + **NumPy Methods**: + + * :py:meth:`lammps.numpy.extract_compute() `: extract value(s) from a compute, return arrays as numpy arrays + * :py:meth:`lammps.numpy.extract_fix() `: extract value(s) from a fix, return arrays as numpy arrays + * :py:meth:`lammps.numpy.extract_variable() `: extract value(s) from a variable, return arrays as numpy arrays + + + .. tab:: PyLammps/IPyLammps API + + PyLammps and IPyLammps classes currently do not add any additional ways of + retrieving information out of computes and fixes. This information can still be accessed by using the lammps API: + + .. code-block:: python + + L.lmp.extract_compute(...) + L.lmp.extract_fix(...) + # OR + L.lmp.numpy.extract_compute(...) + L.lmp.numpy.extract_fix(...) + + LAMMPS variables can be both defined and accessed via the :py:class:`PyLammps ` interface. + + To define a variable you can use the :doc:`variable ` command: + + .. code-block:: Python + + L.variable("a index 2") + + A dictionary of all variables is returned by the :py:attr:`PyLammps.variables ` property: + + you can access an individual variable by retrieving a variable object from the + ``L.variables`` dictionary by name + + .. code-block:: Python + + a = L.variables['a'] + + The variable value can then be easily read and written by accessing the value + property of this object. + + .. code-block:: Python + + print(a.value) + a.value = 4 + + + +Gather and Scatter Data between MPI processors +********************************************** + +.. code-block:: Python + + data = lmp.gather_atoms(name,type,count) # return per-atom property of all atoms gathered into data, ordered by atom ID + # name = "x", "charge", "type", etc + data = lmp.gather_atoms_concat(name,type,count) # ditto, but concatenated atom values from each proc (unordered) + data = lmp.gather_atoms_subset(name,type,count,ndata,ids) # ditto, but for subset of Ndata atoms with IDs + + lmp.scatter_atoms(name,type,count,data) # scatter per-atom property to all atoms from data, ordered by atom ID + # name = "x", "charge", "type", etc + # count = # of per-atom values, 1 or 3, etc + + lmp.scatter_atoms_subset(name,type,count,ndata,ids,data) # ditto, but for subset of Ndata atoms with IDs + + +The gather methods collect peratom info of the requested type (atom +coords, atom types, forces, etc) from all processors, and returns the +same vector of values to each calling processor. The scatter +functions do the inverse. They distribute a vector of peratom values, +passed by all calling processors, to individual atoms, which may be +owned by different processors. + +Note that the data returned by the gather methods, +e.g. gather_atoms("x"), is different from the data structure returned +by extract_atom("x") in four ways. (1) Gather_atoms() returns a +vector which you index as x[i]; extract_atom() returns an array +which you index as x[i][j]. (2) Gather_atoms() orders the atoms +by atom ID while extract_atom() does not. (3) Gather_atoms() returns +a list of all atoms in the simulation; extract_atoms() returns just +the atoms local to each processor. (4) Finally, the gather_atoms() +data structure is a copy of the atom coords stored internally in +LAMMPS, whereas extract_atom() returns an array that effectively +points directly to the internal data. This means you can change +values inside LAMMPS from Python by assigning a new values to the +extract_atom() array. To do this with the gather_atoms() vector, you +need to change values in the vector, then invoke the scatter_atoms() +method. + +For the scatter methods, the array of coordinates passed to must be a +ctypes vector of ints or doubles, allocated and initialized something +like this: + +.. code-block:: Python + + from ctypes import c_double + natoms = lmp.get_natoms() + n3 = 3*natoms + x = (n3*c_double)() + x[0] = x coord of atom with ID 1 + x[1] = y coord of atom with ID 1 + x[2] = z coord of atom with ID 1 + x[3] = x coord of atom with ID 2 + ... + x[n3-1] = z coord of atom with ID natoms + lmp.scatter_atoms("x",1,3,x) + +Alternatively, you can just change values in the vector returned by +the gather methods, since they are also ctypes vectors. + diff --git a/doc/src/compute_tally.rst b/doc/src/compute_tally.rst index 6487618516..0e2856ea5e 100644 --- a/doc/src/compute_tally.rst +++ b/doc/src/compute_tally.rst @@ -107,8 +107,8 @@ The computes in this package are not compatible with dynamic groups. Related commands """""""""""""""" -*compute group/group*\ _compute_group_group.html, *compute -heat/flux*\ _compute_heat_flux.html +* :doc:`compute group/group ` +* :doc:`compute heat/flux ` Default """"""" diff --git a/doc/src/python.rst b/doc/src/python.rst index 8451fe476e..f38e756232 100644 --- a/doc/src/python.rst +++ b/doc/src/python.rst @@ -340,7 +340,7 @@ to the screen and log file. Note that since the LAMMPS print command itself takes a string in quotes as its argument, the Python string must be delimited with a different style of quotes. -The :doc:`Python library ` doc page describes the syntax +The :doc:`Python_head` doc page describes the syntax for how Python wraps the various functions included in the LAMMPS library interface. @@ -350,7 +350,7 @@ which loads and runs the following function from examples/python/funcs.py: .. code-block:: python def loop(N,cut0,thresh,lmpptr): - print "LOOP ARGS",N,cut0,thresh,lmpptr + print("LOOP ARGS", N, cut0, thresh, lmpptr) from lammps import lammps lmp = lammps(ptr=lmpptr) natoms = lmp.get_natoms() @@ -365,12 +365,12 @@ which loads and runs the following function from examples/python/funcs.py: lmp.command("pair_coeff * * 1.0 1.0") # ditto lmp.command("run 10") # ditto pe = lmp.extract_compute("thermo_pe",0,0) # extract total PE from LAMMPS - print "PE",pe/natoms,thresh + print("PE", pe/natoms, thresh) if pe/natoms < thresh: return with these input script commands: -.. parsed-literal:: +.. code-block:: LAMMPS python loop input 4 10 1.0 -4.0 SELF format iffp file funcs.py python loop invoke @@ -473,11 +473,11 @@ like this: .. code-block:: python import exceptions - print "Inside simple function" + print("Inside simple function") try: foo += 1 # one or more statements here - except Exception, e: - print "FOO error:",e + except Exception as e: + print("FOO error:", e) then you will get this message printed to the screen: diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index 669bb8fd64..8d88417291 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -3289,6 +3289,7 @@ vectorized Vegt vel Velázquez +venv Verlag verlet Verlet diff --git a/examples/python/in.fix_python_invoke_neighlist b/examples/python/in.fix_python_invoke_neighlist index e5445227b1..af0399ae1f 100644 --- a/examples/python/in.fix_python_invoke_neighlist +++ b/examples/python/in.fix_python_invoke_neighlist @@ -32,8 +32,9 @@ def post_force_callback(lmp, v): t = L.extract_global("ntimestep", 0) print(pid_prefix, "### POST_FORCE ###", t) - #mylist = L.get_neighlist(0) - mylist = L.find_pair_neighlist("lj/cut", request=0) + #mylist = L.numpy.get_neighlist(0) + idx = L.find_pair_neighlist("lj/cut", request=0) + mylist = L.numpy.get_neighlist(idx) print(pid_prefix, mylist) nlocal = L.extract_global("nlocal") nghost = L.extract_global("nghost") @@ -43,8 +44,8 @@ def post_force_callback(lmp, v): v = L.numpy.extract_atom("v", nelem=nlocal+nghost, dim=3) f = L.numpy.extract_atom("f", nelem=nlocal+nghost, dim=3) - for iatom, numneigh, neighs in mylist: - print(pid_prefix, "- {}".format(iatom), x[iatom], v[iatom], f[iatom], " : ", numneigh, "Neighbors") + for iatom, neighs in mylist: + print(pid_prefix, "- {}".format(iatom), x[iatom], v[iatom], f[iatom], " : ", len(neighs), "Neighbors") for jatom in neighs: if jatom < nlocal: print(pid_prefix, " * ", jatom, x[jatom], v[jatom], f[jatom]) diff --git a/python/lammps.py b/python/lammps.py index 59f2dce012..4982c11b39 100644 --- a/python/lammps.py +++ b/python/lammps.py @@ -81,7 +81,12 @@ class MPIAbortException(Exception): class NeighList: """This is a wrapper class that exposes the contents of a neighbor list. - It can be used like a regular Python list. + It can be used like a regular Python list. Each element is a tuple of: + + * the atom local index + * its number of neighbors + * and a pointer to an c_int array containing local atom indices of its + neighbors Internally it uses the lower-level LAMMPS C-library interface. @@ -109,8 +114,8 @@ class NeighList: def get(self, element): """ - :return: tuple with atom local index, number of neighbors and array of neighbor local atom indices - :rtype: (int, int, numpy.array) + :return: tuple with atom local index, numpy array of neighbor local atom indices + :rtype: (int, int, ctypes.POINTER(c_int)) """ iatom, numneigh, neighbors = self.lmp.get_neighlist_element_neighbors(self.idx, element) return iatom, numneigh, neighbors @@ -129,6 +134,35 @@ class NeighList: for ii in range(inum): yield self.get(ii) +# ------------------------------------------------------------------------- + +class NumPyNeighList(NeighList): + """This is a wrapper class that exposes the contents of a neighbor list. + + It can be used like a regular Python list. Each element is a tuple of: + + * the atom local index + * a NumPy array containing the local atom indices of its neighbors + + Internally it uses the lower-level LAMMPS C-library interface. + + :param lmp: reference to instance of :py:class:`lammps` + :type lmp: lammps + :param idx: neighbor list index + :type idx: int + """ + def __init__(self, lmp, idx): + super(NumPyNeighList, self).__init__(lmp, idx) + + def get(self, element): + """ + :return: tuple with atom local index, numpy array of neighbor local atom indices + :rtype: (int, numpy.array) + """ + iatom, neighbors = self.lmp.numpy.get_neighlist_element_neighbors(self.idx, element) + return iatom, neighbors + + # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- @@ -471,181 +505,17 @@ class lammps(object): @property def numpy(self): - "Convert between ctypes arrays and numpy arrays" + """ Return object to access numpy versions of API + + It provides alternative implementations of API functions that + return numpy arrays instead of ctypes pointers. If numpy is not installed, + accessing this property will lead to an ImportError. + + :return: instance of numpy wrapper object + :rtype: numpy_wrapper + """ if not self._numpy: - import numpy as np - class LammpsNumpyWrapper: - def __init__(self, lmp): - self.lmp = lmp - - def _ctype_to_numpy_int(self, ctype_int): - if ctype_int == c_int32: - return np.int32 - elif ctype_int == c_int64: - return np.int64 - return np.intc - - def extract_atom(self, name, dtype=LAMMPS_AUTODETECT, nelem=LAMMPS_AUTODETECT, dim=LAMMPS_AUTODETECT): - """Retrieve per-atom properties from LAMMPS as NumPy arrays - - This is a wrapper around the :cpp:func:`lammps_extract_atom` - function of the C-library interface. Its documentation includes a - list of the supported keywords and their data types. - Since Python needs to know the data type to be able to interpret - the result, by default, this function will try to auto-detect the data - type by asking the library. You can also force a specific data type. - For that purpose the :py:mod:`lammps` module contains the constants - ``LAMMPS_INT``, ``LAMMPS_INT_2D``, ``LAMMPS_DOUBLE``, - ``LAMMPS_DOUBLE_2D``, ``LAMMPS_INT64``, ``LAMMPS_INT64_2D``, and - ``LAMMPS_STRING``. - This function returns ``None`` if either the keyword is not - recognized, or an invalid data type constant is used. - - .. note:: - - While the returned arrays of per-atom data are dimensioned - for the range [0:nmax] - as is the underlying storage - - the data is usually only valid for the range of [0:nlocal], - unless the property of interest is also updated for ghost - atoms. In some cases, this depends on a LAMMPS setting, see - for example :doc:`comm_modify vel yes `. - - :param name: name of the property - :type name: string - :param dtype: type of the returned data (see :ref:`py_data_constants`) - :type dtype: int, optional - :param nelem: number of elements in array - :type nelem: int, optional - :param dim: dimension of each element - :type dim: int, optional - :return: requested data as NumPy array with direct access to C data - :rtype: numpy.array - """ - if dtype == LAMMPS_AUTODETECT: - dtype = self.lmp.extract_atom_datatype(name) - - if nelem == LAMMPS_AUTODETECT: - if name == "mass": - nelem = self.lmp.extract_global("ntypes") + 1 - else: - nelem = self.lmp.extract_global("nlocal") - if dim == LAMMPS_AUTODETECT: - if dtype in (LAMMPS_INT_2D, LAMMPS_DOUBLE_2D, LAMMPS_INT64_2D): - # TODO add other fields - if name in ("x", "v", "f", "angmom", "torque", "csforce", "vforce"): - dim = 3 - else: - dim = 2 - else: - dim = 1 - - raw_ptr = self.lmp.extract_atom(name, dtype) - - if dtype in (LAMMPS_DOUBLE, LAMMPS_DOUBLE_2D): - return self.darray(raw_ptr, nelem, dim) - elif dtype in (LAMMPS_INT, LAMMPS_INT_2D): - return self.iarray(c_int32, raw_ptr, nelem, dim) - elif dtype in (LAMMPS_INT64, LAMMPS_INT64_2D): - return self.iarray(c_int64, raw_ptr, nelem, dim) - return raw_ptr - - def extract_atom_iarray(self, name, nelem, dim=1): - warnings.warn("deprecated, use extract_atom instead", DeprecationWarning) - - if name in ['id', 'molecule']: - c_int_type = self.lmp.c_tagint - elif name in ['image']: - c_int_type = self.lmp.c_imageint - else: - c_int_type = c_int - - if dim == 1: - raw_ptr = self.lmp.extract_atom(name, LAMMPS_INT) - else: - raw_ptr = self.lmp.extract_atom(name, LAMMPS_INT_2D) - - return self.iarray(c_int_type, raw_ptr, nelem, dim) - - def extract_atom_darray(self, name, nelem, dim=1): - warnings.warn("deprecated, use extract_atom instead", DeprecationWarning) - - if dim == 1: - raw_ptr = self.lmp.extract_atom(name, LAMMPS_DOUBLE) - else: - raw_ptr = self.lmp.extract_atom(name, LAMMPS_DOUBLE_2D) - - return self.darray(raw_ptr, nelem, dim) - - def extract_compute(self, cid, style, datatype): - value = self.lmp.extract_compute(cid, style, datatype) - - if style in (LMP_STYLE_GLOBAL, LMP_STYLE_LOCAL): - if datatype == LMP_TYPE_VECTOR: - nrows = self.lmp.extract_compute(cid, style, LMP_SIZE_VECTOR) - return self.darray(value, nrows) - elif datatype == LMP_TYPE_ARRAY: - nrows = self.lmp.extract_compute(cid, style, LMP_SIZE_ROWS) - ncols = self.lmp.extract_compute(cid, style, LMP_SIZE_COLS) - return self.darray(value, nrows, ncols) - elif style == LMP_STYLE_ATOM: - if datatype == LMP_TYPE_VECTOR: - nlocal = self.lmp.extract_global("nlocal", LAMMPS_INT) - return self.darray(value, nlocal) - elif datatype == LMP_TYPE_ARRAY: - nlocal = self.lmp.extract_global("nlocal", LAMMPS_INT) - ncols = self.lmp.extract_compute(cid, style, LMP_SIZE_COLS) - return self.darray(value, nlocal, ncols) - return value - - def extract_fix(self, fid, style, datatype, nrow=0, ncol=0): - value = self.lmp.extract_fix(fid, style, datatype, nrow, ncol) - if style == LMP_STYLE_ATOM: - if datatype == LMP_TYPE_VECTOR: - nlocal = self.lmp.extract_global("nlocal", LAMMPS_INT) - return self.darray(value, nlocal) - elif datatype == LMP_TYPE_ARRAY: - nlocal = self.lmp.extract_global("nlocal", LAMMPS_INT) - ncols = self.lmp.extract_fix(fid, style, LMP_SIZE_COLS, 0, 0) - return self.darray(value, nlocal, ncols) - elif style == LMP_STYLE_LOCAL: - if datatype == LMP_TYPE_VECTOR: - nrows = self.lmp.extract_fix(fid, style, LMP_SIZE_ROWS, 0, 0) - return self.darray(value, nrows) - elif datatype == LMP_TYPE_ARRAY: - nrows = self.lmp.extract_fix(fid, style, LMP_SIZE_ROWS, 0, 0) - ncols = self.lmp.extract_fix(fid, style, LMP_SIZE_COLS, 0, 0) - return self.darray(value, nrows, ncols) - return value - - def extract_variable(self, name, group=None, datatype=LMP_VAR_EQUAL): - value = self.lmp.extract_variable(name, group, datatype) - if datatype == LMP_VAR_ATOM: - return np.ctypeslib.as_array(value) - return value - - def iarray(self, c_int_type, raw_ptr, nelem, dim=1): - np_int_type = self._ctype_to_numpy_int(c_int_type) - - if dim == 1: - ptr = cast(raw_ptr, POINTER(c_int_type * nelem)) - else: - ptr = cast(raw_ptr[0], POINTER(c_int_type * nelem * dim)) - - a = np.frombuffer(ptr.contents, dtype=np_int_type) - a.shape = (nelem, dim) - return a - - def darray(self, raw_ptr, nelem, dim=1): - if dim == 1: - ptr = cast(raw_ptr, POINTER(c_double * nelem)) - else: - ptr = cast(raw_ptr[0], POINTER(c_double * nelem * dim)) - - a = np.frombuffer(ptr.contents) - a.shape = (nelem, dim) - return a - - self._numpy = LammpsNumpyWrapper(self) + self._numpy = numpy_wrapper(self) return self._numpy # ------------------------------------------------------------------------- @@ -705,6 +575,18 @@ class lammps(object): # ------------------------------------------------------------------------- + @property + def _lammps_exception(self): + sb = create_string_buffer(100) + error_type = self.lib.lammps_get_last_error_message(self.lmp, sb, 100) + error_msg = sb.value.decode().strip() + + if error_type == 2: + return MPIAbortException(error_msg) + return Exception(error_msg) + + # ------------------------------------------------------------------------- + def file(self, path): """Read LAMMPS commands from a file. @@ -719,6 +601,9 @@ class lammps(object): else: return self.lib.lammps_file(self.lmp, path) + if self.has_exceptions and self.lib.lammps_has_error(self.lmp): + raise self._lammps_exception + # ------------------------------------------------------------------------- def command(self,cmd): @@ -735,13 +620,7 @@ class lammps(object): self.lib.lammps_command(self.lmp,cmd) if self.has_exceptions and self.lib.lammps_has_error(self.lmp): - sb = create_string_buffer(100) - error_type = self.lib.lammps_get_last_error_message(self.lmp, sb, 100) - error_msg = sb.value.decode().strip() - - if error_type == 2: - raise MPIAbortException(error_msg) - raise Exception(error_msg) + raise self._lammps_exception # ------------------------------------------------------------------------- @@ -761,6 +640,9 @@ class lammps(object): self.lib.lammps_commands_list.argtypes = [c_void_p, c_int, c_char_p * narg] self.lib.lammps_commands_list(self.lmp,narg,args) + if self.has_exceptions and self.lib.lammps_has_error(self.lmp): + raise self._lammps_exception + # ------------------------------------------------------------------------- def commands_string(self,multicmd): @@ -776,6 +658,9 @@ class lammps(object): if type(multicmd) is str: multicmd = multicmd.encode() self.lib.lammps_commands_string(self.lmp,c_char_p(multicmd)) + if self.has_exceptions and self.lib.lammps_has_error(self.lmp): + raise self._lammps_exception + # ------------------------------------------------------------------------- def get_natoms(self): @@ -892,14 +777,12 @@ class lammps(object): list of the supported keywords. This function returns ``None`` if the keyword is not recognized. Otherwise it will return a positive integer value that - corresponds to one of the constants define in the :py:mod:`lammps` module: - ``LAMMPS_INT``, ``LAMMPS_INT_2D``, ``LAMMPS_DOUBLE``, ``LAMMPS_DOUBLE_2D``, - ``LAMMPS_INT64``, ``LAMMPS_INT64_2D``, and ``LAMMPS_STRING``. These values - are equivalent to the ones defined in :cpp:enum:`_LMP_DATATYPE_CONST`. + corresponds to one of the :ref:`data type ` + constants define in the :py:mod:`lammps` module. :param name: name of the property :type name: string - :return: datatype of global property + :return: data type of global property, see :ref:`py_datatype_constants` :rtype: int """ if name: name = name.encode() @@ -921,15 +804,13 @@ class lammps(object): Since Python needs to know the data type to be able to interpret the result, by default, this function will try to auto-detect the data type by asking the library. You can also force a specific data type. For that - purpose the :py:mod:`lammps` module contains the constants ``LAMMPS_INT``, - ``LAMMPS_DOUBLE``, ``LAMMPS_INT64``, and ``LAMMPS_STRING``. These values - are equivalent to the ones defined in :cpp:enum:`_LMP_DATATYPE_CONST`. - This function returns ``None`` if either the keyword is not recognized, + purpose the :py:mod:`lammps` module contains :ref:`data type ` + constants. This function returns ``None`` if either the keyword is not recognized, or an invalid data type constant is used. :param name: name of the property :type name: string - :param dtype: data type of the returned data (see :ref:`py_data_constants`) + :param dtype: data type of the returned data (see :ref:`py_datatype_constants`) :type dtype: int, optional :return: value of the property or None :rtype: int, float, or NoneType @@ -970,14 +851,12 @@ class lammps(object): list of the supported keywords. This function returns ``None`` if the keyword is not recognized. Otherwise it will return an integer value that - corresponds to one of the constants define in the :py:mod:`lammps` module: - ``LAMMPS_INT``, ``LAMMPS_INT_2D``, ``LAMMPS_DOUBLE``, ``LAMMPS_DOUBLE_2D``, - ``LAMMPS_INT64``, ``LAMMPS_INT64_2D``, and ``LAMMPS_STRING``. These values - are equivalent to the ones defined in :cpp:enum:`_LMP_DATATYPE_CONST`. + corresponds to one of the :ref:`data type ` constants + defined in the :py:mod:`lammps` module. :param name: name of the property :type name: string - :return: data type of per-atom property (see :ref:`py_data_constants`) + :return: data type of per-atom property (see :ref:`py_datatype_constants`) :rtype: int """ if name: name = name.encode() @@ -995,11 +874,9 @@ class lammps(object): list of the supported keywords and their data types. Since Python needs to know the data type to be able to interpret the result, by default, this function will try to auto-detect the data type - by asking the library. You can also force a specific data type. For - that purpose the :py:mod:`lammps` module contains the constants - ``LAMMPS_INT``, ``LAMMPS_INT_2D``, ``LAMMPS_DOUBLE``, ``LAMMPS_DOUBLE_2D``, - ``LAMMPS_INT64``, ``LAMMPS_INT64_2D``, and ``LAMMPS_STRING``. These values - are equivalent to the ones defined in :cpp:enum:`_LMP_DATATYPE_CONST`. + by asking the library. You can also force a specific data type by setting ``dtype`` + to one of the :ref:`data type ` constants defined in the + :py:mod:`lammps` module. This function returns ``None`` if either the keyword is not recognized, or an invalid data type constant is used. @@ -1014,7 +891,7 @@ class lammps(object): :param name: name of the property :type name: string - :param dtype: data type of the returned data (see :ref:`py_data_constants`) + :param dtype: data type of the returned data (see :ref:`py_datatype_constants`) :type dtype: int, optional :return: requested data or ``None`` :rtype: ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.POINTER(ctypes.c_int32)), @@ -1062,12 +939,12 @@ class lammps(object): :param id: compute ID :type id: string - :param style: style of the data retrieve (global, atom, or local) + :param style: style of the data retrieve (global, atom, or local), see :ref:`py_style_constants` :type style: int - :param type: type or size of the returned data (scalar, vector, or array) + :param type: type or size of the returned data (scalar, vector, or array), see :ref:`py_type_constants` :type type: int - :return: requested data - :rtype: integer or double or pointer to 1d or 2d double array or None + :return: requested data as scalar, pointer to 1d or 2d double array, or None + :rtype: c_double, ctypes.POINTER(c_double), ctypes.POINTER(ctypes.POINTER(c_double)), or NoneType """ if id: id = id.encode() else: return None @@ -1132,33 +1009,29 @@ class lammps(object): :param id: fix ID :type id: string - :param style: style of the data retrieve (global, atom, or local) + :param style: style of the data retrieve (global, atom, or local), see :ref:`py_style_constants` :type style: int - :param type: type or size of the returned data (scalar, vector, or array) + :param type: type or size of the returned data (scalar, vector, or array), see :ref:`py_type_constants` :type type: int :param nrow: index of global vector element or row index of global array element :type nrow: int :param ncol: column index of global array element :type ncol: int - :return: requested data - :rtype: integer or double value, pointer to 1d or 2d double array or None + :return: requested data or None + :rtype: c_double, ctypes.POINTER(c_double), ctypes.POINTER(ctypes.POINTER(c_double)), or NoneType """ if id: id = id.encode() else: return None if style == LMP_STYLE_GLOBAL: - if type == LMP_TYPE_SCALAR \ - or type == LMP_TYPE_VECTOR \ - or type == LMP_TYPE_ARRAY: + if type in (LMP_TYPE_SCALAR, LMP_TYPE_VECTOR, LMP_TYPE_ARRAY): self.lib.lammps_extract_fix.restype = POINTER(c_double) ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,nrow,ncol) result = ptr[0] self.lib.lammps_free(ptr) return result - elif type == LMP_SIZE_VECTOR \ - or type == LMP_SIZE_ROWS \ - or type == LMP_SIZE_COLS: + elif type in (LMP_SIZE_VECTOR, LMP_SIZE_ROWS, LMP_SIZE_COLS): self.lib.lammps_extract_fix.restype = POINTER(c_int) ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,nrow,ncol) return ptr[0] @@ -1185,15 +1058,12 @@ class lammps(object): self.lib.lammps_extract_fix.restype = POINTER(c_double) elif type == LMP_TYPE_ARRAY: self.lib.lammps_extract_fix.restype = POINTER(POINTER(c_double)) - elif type == LMP_TYPE_SCALAR \ - or type == LMP_SIZE_VECTOR \ - or type == LMP_SIZE_ROWS \ - or type == LMP_SIZE_COLS: + elif type in (LMP_TYPE_SCALAR, LMP_SIZE_VECTOR, LMP_SIZE_ROWS, LMP_SIZE_COLS): self.lib.lammps_extract_fix.restype = POINTER(c_int) else: return None ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,nrow,ncol) - if type == LMP_TYPE_VECTOR or type == LMP_TYPE_ARRAY: + if type in (LMP_TYPE_VECTOR, LMP_TYPE_ARRAY): return ptr else: return ptr[0] @@ -1206,17 +1076,17 @@ class lammps(object): # for vector, must copy nlocal returned values to local c_double vector # memory was allocated by library interface function - def extract_variable(self,name,group=None,type=LMP_VAR_EQUAL): + def extract_variable(self, name, group=None, vartype=LMP_VAR_EQUAL): """ Evaluate a LAMMPS variable and return its data This function is a wrapper around the function :cpp:func:`lammps_extract_variable` of the C-library interface, evaluates variable name and returns a copy of the computed data. The memory temporarily allocated by the C-interface is deleted - after the data is copied to a python variable or list. + after the data is copied to a Python variable or list. The variable must be either an equal-style (or equivalent) variable or an atom-style variable. The variable type has to - provided as type parameter which may be two constants: + provided as ``vartype`` parameter which may be two constants: ``LMP_VAR_EQUAL`` or ``LMP_VAR_STRING``; it defaults to equal-style variables. The group parameter is only used for atom-style variables and @@ -1224,26 +1094,24 @@ class lammps(object): :param name: name of the variable to execute :type name: string - :param group: name of group for atom style variable - :type group: string - :param type: type of variable - :type type: int + :param group: name of group for atom-style variable + :type group: string, only for atom-style variables + :param vartype: type of variable, see :ref:`py_vartype_constants` + :type vartype: int :return: the requested data - :rtype: double, array of doubles, or None + :rtype: c_double, (c_double), or NoneType """ if name: name = name.encode() else: return None if group: group = group.encode() - if type == LMP_VAR_EQUAL: + if vartype == LMP_VAR_EQUAL: self.lib.lammps_extract_variable.restype = POINTER(c_double) ptr = self.lib.lammps_extract_variable(self.lmp,name,group) result = ptr[0] self.lib.lammps_free(ptr) return result - if type == LMP_VAR_ATOM: - self.lib.lammps_extract_global.restype = POINTER(c_int) - nlocalptr = self.lib.lammps_extract_global(self.lmp,"nlocal".encode()) - nlocal = nlocalptr[0] + elif vartype == LMP_VAR_ATOM: + nlocal = self.extract_global("nlocal") result = (c_double*nlocal)() self.lib.lammps_extract_variable.restype = POINTER(c_double) ptr = self.lib.lammps_extract_variable(self.lmp,name,group) @@ -1697,13 +1565,6 @@ class lammps(object): def set_fix_external_callback(self, fix_name, callback, caller=None): import numpy as np - def _ctype_to_numpy_int(ctype_int): - if ctype_int == c_int32: - return np.int32 - elif ctype_int == c_int64: - return np.int64 - return np.intc - def callback_wrapper(caller, ntimestep, nlocal, tag_ptr, x_ptr, fext_ptr): tag = self.numpy.iarray(self.c_tagint, tag_ptr, nlocal, 1) x = self.numpy.darray(x_ptr, nlocal, 3) @@ -1716,11 +1577,15 @@ class lammps(object): self.callback[fix_name] = { 'function': cFunc, 'caller': caller } self.lib.lammps_set_fix_external_callback(self.lmp, fix_name.encode(), cFunc, cCaller) + # ------------------------------------------------------------------------- def get_neighlist(self, idx): """Returns an instance of :class:`NeighList` which wraps access to the neighbor list with the given index + See :py:meth:`lammps.numpy.get_neighlist() ` if you want to use + NumPy arrays instead of ``c_int`` pointers. + :param idx: index of neighbor list :type idx: int :return: an instance of :class:`NeighList` wrapping access to neighbor list data @@ -1732,6 +1597,36 @@ class lammps(object): # ------------------------------------------------------------------------- + def get_neighlist_size(self, idx): + """Return the number of elements in neighbor list with the given index + + :param idx: neighbor list index + :type idx: int + :return: number of elements in neighbor list with index idx + :rtype: int + """ + return self.lib.lammps_neighlist_num_elements(self.lmp, idx) + + # ------------------------------------------------------------------------- + + def get_neighlist_element_neighbors(self, idx, element): + """Return data of neighbor list entry + + :param element: neighbor list index + :type element: int + :param element: neighbor list element index + :type element: int + :return: tuple with atom local index, number of neighbors and array of neighbor local atom indices + :rtype: (int, int, POINTER(c_int)) + """ + c_iatom = c_int() + c_numneigh = c_int() + c_neighbors = POINTER(c_int)() + self.lib.lammps_neighlist_element_neighbors(self.lmp, idx, element, byref(c_iatom), byref(c_numneigh), byref(c_neighbors)) + return c_iatom.value, c_numneigh.value, c_neighbors + + # ------------------------------------------------------------------------- + def find_pair_neighlist(self, style, exact=True, nsub=0, request=0): """Find neighbor list index of pair style neighbor list @@ -1758,7 +1653,7 @@ class lammps(object): style = style.encode() exact = int(exact) idx = self.lib.lammps_find_pair_neighlist(self.lmp, style, exact, nsub, request) - return self.get_neighlist(idx) + return idx # ------------------------------------------------------------------------- @@ -1774,7 +1669,7 @@ class lammps(object): """ fixid = fixid.encode() idx = self.lib.lammps_find_fix_neighlist(self.lmp, fixid, request) - return self.get_neighlist(idx) + return idx # ------------------------------------------------------------------------- @@ -1790,38 +1685,295 @@ class lammps(object): """ computeid = computeid.encode() idx = self.lib.lammps_find_compute_neighlist(self.lmp, computeid, request) - return self.get_neighlist(idx) + return idx + +# ------------------------------------------------------------------------- + +class numpy_wrapper: + """lammps API NumPy Wrapper + + This is a wrapper class that provides additional methods on top of an + existing :py:class:`lammps` instance. The methods transform raw ctypes + pointers into NumPy arrays, which give direct access to the + original data while protecting against out-of-bounds accesses. + + There is no need to explicitly instantiate this class. Each instance + of :py:class:`lammps` has a :py:attr:`numpy ` property + that returns an instance. + + :param lmp: instance of the :py:class:`lammps` class + :type lmp: lammps + """ + def __init__(self, lmp): + self.lmp = lmp # ------------------------------------------------------------------------- - def get_neighlist_size(self, idx): - """Return the number of elements in neighbor list with the given index + def _ctype_to_numpy_int(self, ctype_int): + import numpy as np + if ctype_int == c_int32: + return np.int32 + elif ctype_int == c_int64: + return np.int64 + return np.intc - :param idx: neighbor list index + # ------------------------------------------------------------------------- + + def extract_atom(self, name, dtype=LAMMPS_AUTODETECT, nelem=LAMMPS_AUTODETECT, dim=LAMMPS_AUTODETECT): + """Retrieve per-atom properties from LAMMPS as NumPy arrays + + This is a wrapper around the :py:meth:`lammps.extract_atom()` method. + It behaves the same as the original method, but returns NumPy arrays + instead of ``ctypes`` pointers. + + .. note:: + + While the returned arrays of per-atom data are dimensioned + for the range [0:nmax] - as is the underlying storage - + the data is usually only valid for the range of [0:nlocal], + unless the property of interest is also updated for ghost + atoms. In some cases, this depends on a LAMMPS setting, see + for example :doc:`comm_modify vel yes `. + + :param name: name of the property + :type name: string + :param dtype: type of the returned data (see :ref:`py_datatype_constants`) + :type dtype: int, optional + :param nelem: number of elements in array + :type nelem: int, optional + :param dim: dimension of each element + :type dim: int, optional + :return: requested data as NumPy array with direct access to C data or None + :rtype: numpy.array or NoneType + """ + if dtype == LAMMPS_AUTODETECT: + dtype = self.lmp.extract_atom_datatype(name) + + if nelem == LAMMPS_AUTODETECT: + if name == "mass": + nelem = self.lmp.extract_global("ntypes") + 1 + else: + nelem = self.lmp.extract_global("nlocal") + if dim == LAMMPS_AUTODETECT: + if dtype in (LAMMPS_INT_2D, LAMMPS_DOUBLE_2D, LAMMPS_INT64_2D): + # TODO add other fields + if name in ("x", "v", "f", "angmom", "torque", "csforce", "vforce"): + dim = 3 + else: + dim = 2 + else: + dim = 1 + + raw_ptr = self.lmp.extract_atom(name, dtype) + + if dtype in (LAMMPS_DOUBLE, LAMMPS_DOUBLE_2D): + return self.darray(raw_ptr, nelem, dim) + elif dtype in (LAMMPS_INT, LAMMPS_INT_2D): + return self.iarray(c_int32, raw_ptr, nelem, dim) + elif dtype in (LAMMPS_INT64, LAMMPS_INT64_2D): + return self.iarray(c_int64, raw_ptr, nelem, dim) + return raw_ptr + + # ------------------------------------------------------------------------- + + def extract_atom_iarray(self, name, nelem, dim=1): + warnings.warn("deprecated, use extract_atom instead", DeprecationWarning) + + if name in ['id', 'molecule']: + c_int_type = self.lmp.c_tagint + elif name in ['image']: + c_int_type = self.lmp.c_imageint + else: + c_int_type = c_int + + if dim == 1: + raw_ptr = self.lmp.extract_atom(name, LAMMPS_INT) + else: + raw_ptr = self.lmp.extract_atom(name, LAMMPS_INT_2D) + + return self.iarray(c_int_type, raw_ptr, nelem, dim) + + # ------------------------------------------------------------------------- + + def extract_atom_darray(self, name, nelem, dim=1): + warnings.warn("deprecated, use extract_atom instead", DeprecationWarning) + + if dim == 1: + raw_ptr = self.lmp.extract_atom(name, LAMMPS_DOUBLE) + else: + raw_ptr = self.lmp.extract_atom(name, LAMMPS_DOUBLE_2D) + + return self.darray(raw_ptr, nelem, dim) + + # ------------------------------------------------------------------------- + + def extract_compute(self, cid, style, type): + """Retrieve data from a LAMMPS compute + + This is a wrapper around the + :py:meth:`lammps.extract_compute() ` method. + It behaves the same as the original method, but returns NumPy arrays + instead of ``ctypes`` pointers. + + :param id: compute ID + :type id: string + :param style: style of the data retrieve (global, atom, or local), see :ref:`py_style_constants` + :type style: int + :param type: type of the returned data (scalar, vector, or array), see :ref:`py_type_constants` + :type type: int + :return: requested data either as float, as NumPy array with direct access to C data, or None + :rtype: float, numpy.array, or NoneType + """ + value = self.lmp.extract_compute(cid, style, type) + + if style in (LMP_STYLE_GLOBAL, LMP_STYLE_LOCAL): + if type == LMP_TYPE_VECTOR: + nrows = self.lmp.extract_compute(cid, style, LMP_SIZE_VECTOR) + return self.darray(value, nrows) + elif type == LMP_TYPE_ARRAY: + nrows = self.lmp.extract_compute(cid, style, LMP_SIZE_ROWS) + ncols = self.lmp.extract_compute(cid, style, LMP_SIZE_COLS) + return self.darray(value, nrows, ncols) + elif style == LMP_STYLE_ATOM: + if type == LMP_TYPE_VECTOR: + nlocal = self.lmp.extract_global("nlocal") + return self.darray(value, nlocal) + elif type == LMP_TYPE_ARRAY: + nlocal = self.lmp.extract_global("nlocal") + ncols = self.lmp.extract_compute(cid, style, LMP_SIZE_COLS) + return self.darray(value, nlocal, ncols) + return value + + # ------------------------------------------------------------------------- + + def extract_fix(self, fid, style, type, nrow=0, ncol=0): + """Retrieve data from a LAMMPS fix + + This is a wrapper around the :py:meth:`lammps.extract_fix() ` method. + It behaves the same as the original method, but returns NumPy arrays + instead of ``ctypes`` pointers. + + :param id: fix ID + :type id: string + :param style: style of the data retrieve (global, atom, or local), see :ref:`py_style_constants` + :type style: int + :param type: type or size of the returned data (scalar, vector, or array), see :ref:`py_type_constants` + :type type: int + :param nrow: index of global vector element or row index of global array element + :type nrow: int + :param ncol: column index of global array element + :type ncol: int + :return: requested data + :rtype: integer or double value, pointer to 1d or 2d double array or None + + """ + value = self.lmp.extract_fix(fid, style, type, nrow, ncol) + if style == LMP_STYLE_ATOM: + if type == LMP_TYPE_VECTOR: + nlocal = self.lmp.extract_global("nlocal") + return self.darray(value, nlocal) + elif type == LMP_TYPE_ARRAY: + nlocal = self.lmp.extract_global("nlocal") + ncols = self.lmp.extract_fix(fid, style, LMP_SIZE_COLS, 0, 0) + return self.darray(value, nlocal, ncols) + elif style == LMP_STYLE_LOCAL: + if type == LMP_TYPE_VECTOR: + nrows = self.lmp.extract_fix(fid, style, LMP_SIZE_ROWS, 0, 0) + return self.darray(value, nrows) + elif type == LMP_TYPE_ARRAY: + nrows = self.lmp.extract_fix(fid, style, LMP_SIZE_ROWS, 0, 0) + ncols = self.lmp.extract_fix(fid, style, LMP_SIZE_COLS, 0, 0) + return self.darray(value, nrows, ncols) + return value + + # ------------------------------------------------------------------------- + + def extract_variable(self, name, group=None, vartype=LMP_VAR_EQUAL): + """ Evaluate a LAMMPS variable and return its data + + This function is a wrapper around the function + :py:meth:`lammps.extract_variable() ` + method. It behaves the same as the original method, but returns NumPy arrays + instead of ``ctypes`` pointers. + + :param name: name of the variable to execute + :type name: string + :param group: name of group for atom-style variable (ignored for equal-style variables) + :type group: string + :param vartype: type of variable, see :ref:`py_vartype_constants` + :type vartype: int + :return: the requested data or None + :rtype: c_double, numpy.array, or NoneType + """ + import numpy as np + value = self.lmp.extract_variable(name, group, vartype) + if vartype == LMP_VAR_ATOM: + return np.ctypeslib.as_array(value) + return value + + # ------------------------------------------------------------------------- + + def get_neighlist(self, idx): + """Returns an instance of :class:`NumPyNeighList` which wraps access to the neighbor list with the given index + + :param idx: index of neighbor list :type idx: int - :return: number of elements in neighbor list with index idx - :rtype: int - """ - return self.lib.lammps_neighlist_num_elements(self.lmp, idx) + :return: an instance of :class:`NumPyNeighList` wrapping access to neighbor list data + :rtype: NumPyNeighList + """ + if idx < 0: + return None + return NumPyNeighList(self.lmp, idx) # ------------------------------------------------------------------------- def get_neighlist_element_neighbors(self, idx, element): """Return data of neighbor list entry + This function is a wrapper around the function + :py:meth:`lammps.get_neighlist_element_neighbors() ` + method. It behaves the same as the original method, but returns a NumPy array containing the neighbors + instead of a ``ctypes`` pointer. + :param element: neighbor list index :type element: int :param element: neighbor list element index :type element: int - :return: tuple with atom local index, number of neighbors and array of neighbor local atom indices - :rtype: (int, int, numpy.array) + :return: tuple with atom local index and numpy array of neighbor local atom indices + :rtype: (int, numpy.array) """ - c_iatom = c_int() - c_numneigh = c_int() - c_neighbors = POINTER(c_int)() - self.lib.lammps_neighlist_element_neighbors(self.lmp, idx, element, byref(c_iatom), byref(c_numneigh), byref(c_neighbors)) - neighbors = self.numpy.iarray(c_int, c_neighbors, c_numneigh.value, 1) - return c_iatom.value, c_numneigh.value, neighbors + iatom, numneigh, c_neighbors = self.lmp.get_neighlist_element_neighbors(idx, element) + neighbors = self.iarray(c_int, c_neighbors, numneigh, 1) + return iatom, neighbors + + # ------------------------------------------------------------------------- + + def iarray(self, c_int_type, raw_ptr, nelem, dim=1): + import numpy as np + np_int_type = self._ctype_to_numpy_int(c_int_type) + + if dim == 1: + ptr = cast(raw_ptr, POINTER(c_int_type * nelem)) + else: + ptr = cast(raw_ptr[0], POINTER(c_int_type * nelem * dim)) + + a = np.frombuffer(ptr.contents, dtype=np_int_type) + a.shape = (nelem, dim) + return a + + # ------------------------------------------------------------------------- + + def darray(self, raw_ptr, nelem, dim=1): + import numpy as np + if dim == 1: + ptr = cast(raw_ptr, POINTER(c_double * nelem)) + else: + ptr = cast(raw_ptr[0], POINTER(c_double * nelem * dim)) + + a = np.frombuffer(ptr.contents) + a.shape = (nelem, dim) + return a + # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- diff --git a/src/library.cpp b/src/library.cpp index a9f7f9daba..404789d315 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -4641,7 +4641,7 @@ the failing MPI ranks to send messages. * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. * \param buffer string buffer to copy the error message to * \param buf_size size of the provided string buffer - * \return 1 when all ranks had the error, 1 on a single rank error. + * \return 1 when all ranks had the error, 2 on a single rank error. */ int lammps_get_last_error_message(void *handle, char *buffer, int buf_size) { #ifdef LAMMPS_EXCEPTIONS diff --git a/unittest/python/python-commands.py b/unittest/python/python-commands.py index 0b853a207e..6bd5a2a247 100644 --- a/unittest/python/python-commands.py +++ b/unittest/python/python-commands.py @@ -85,6 +85,49 @@ create_atoms 1 single & natoms = self.lmp.get_natoms() self.assertEqual(natoms,2) + def testNeighborList(self): + self.lmp.command("units lj") + self.lmp.command("atom_style atomic") + self.lmp.command("atom_modify map array") + self.lmp.command("boundary f f f") + self.lmp.command("region box block 0 2 0 2 0 2") + self.lmp.command("create_box 1 box") + + x = [ + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.5 + ] + + types = [1, 1] + + self.assertEqual(self.lmp.create_atoms(2, id=None, type=types, x=x), 2) + nlocal = self.lmp.extract_global("nlocal") + self.assertEqual(nlocal, 2) + + self.lmp.command("mass 1 1.0") + self.lmp.command("velocity all create 3.0 87287") + self.lmp.command("pair_style lj/cut 2.5") + self.lmp.command("pair_coeff 1 1 1.0 1.0 2.5") + self.lmp.command("neighbor 0.1 bin") + self.lmp.command("neigh_modify every 20 delay 0 check no") + + self.lmp.command("run 0") + + self.assertEqual(self.lmp.find_pair_neighlist("lj/cut"), 0) + nlist = self.lmp.get_neighlist(0) + self.assertEqual(len(nlist), 2) + atom_i, numneigh_i, neighbors_i = nlist[0] + atom_j, numneigh_j, _ = nlist[1] + + self.assertEqual(atom_i, 0) + self.assertEqual(atom_j, 1) + + self.assertEqual(numneigh_i, 1) + self.assertEqual(numneigh_j, 0) + + self.assertEqual(1, neighbors_i[0]) + + ############################## if __name__ == "__main__": unittest.main() diff --git a/unittest/python/python-numpy.py b/unittest/python/python-numpy.py index 3c8ff9f512..46794590f4 100644 --- a/unittest/python/python-numpy.py +++ b/unittest/python/python-numpy.py @@ -135,5 +135,48 @@ class PythonNumpy(unittest.TestCase): self.assertTrue((x[1] == (1.0, 1.0, 1.5)).all()) self.assertEqual(len(v), 2) + def testNeighborList(self): + self.lmp.command("units lj") + self.lmp.command("atom_style atomic") + self.lmp.command("atom_modify map array") + self.lmp.command("boundary f f f") + self.lmp.command("region box block 0 2 0 2 0 2") + self.lmp.command("create_box 1 box") + + x = [ + 1.0, 1.0, 1.0, + 1.0, 1.0, 1.5 + ] + + types = [1, 1] + + self.assertEqual(self.lmp.create_atoms(2, id=None, type=types, x=x), 2) + nlocal = self.lmp.extract_global("nlocal") + self.assertEqual(nlocal, 2) + + self.lmp.command("mass 1 1.0") + self.lmp.command("velocity all create 3.0 87287") + self.lmp.command("pair_style lj/cut 2.5") + self.lmp.command("pair_coeff 1 1 1.0 1.0 2.5") + self.lmp.command("neighbor 0.1 bin") + self.lmp.command("neigh_modify every 20 delay 0 check no") + + self.lmp.command("run 0") + + self.assertEqual(self.lmp.find_pair_neighlist("lj/cut"), 0) + nlist = self.lmp.numpy.get_neighlist(0) + self.assertEqual(len(nlist), 2) + atom_i, neighbors_i = nlist[0] + atom_j, neighbors_j = nlist[1] + + self.assertEqual(atom_i, 0) + self.assertEqual(atom_j, 1) + + self.assertEqual(len(neighbors_i), 1) + self.assertEqual(len(neighbors_j), 0) + + self.assertIn(1, neighbors_i) + self.assertNotIn(0, neighbors_j) + if __name__ == "__main__": unittest.main() diff --git a/unittest/python/python-open.py b/unittest/python/python-open.py index 6ad7d335d5..6153e032e3 100644 --- a/unittest/python/python-open.py +++ b/unittest/python/python-open.py @@ -18,6 +18,7 @@ try: machine = "" lmp = lammps(name=machine) has_mpi = lmp.has_mpi_support + has_exceptions = lmp.has_exceptions lmp.close() except: pass @@ -57,5 +58,32 @@ class PythonOpen(unittest.TestCase): self.assertEqual(lmp.opened,1) lmp.close() + @unittest.skipIf(not has_exceptions,"Skipping death test since LAMMPS isn't compiled with exception support") + def testUnknownCommand(self): + lmp = lammps(name=self.machine) + + with self.assertRaisesRegex(Exception, "ERROR: Unknown command: write_paper"): + lmp.command("write_paper") + + lmp.close() + + @unittest.skipIf(not has_exceptions,"Skipping death test since LAMMPS isn't compiled with exception support") + def testUnknownCommandInList(self): + lmp = lammps(name=self.machine) + + with self.assertRaisesRegex(Exception, "ERROR: Unknown command: write_paper"): + lmp.commands_list(["write_paper"]) + + lmp.close() + + @unittest.skipIf(not has_exceptions,"Skipping death test since LAMMPS isn't compiled with exception support") + def testUnknownCommandInList(self): + lmp = lammps(name=self.machine) + + with self.assertRaisesRegex(Exception, "ERROR: Unknown command: write_paper"): + lmp.commands_string("write_paper") + + lmp.close() + if __name__ == "__main__": unittest.main()