Merge pull request #2412 from lammps/progguide-updates
Updates to the Programmer Guide part of the Manual
This commit is contained in:
27
doc/graphviz/lammps-invoke-python.dot
Normal file
27
doc/graphviz/lammps-invoke-python.dot
Normal file
@ -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];
|
||||
}
|
||||
|
||||
30
doc/graphviz/pylammps-invoke-lammps.dot
Normal file
30
doc/graphviz/pylammps-invoke-lammps.dot
Normal file
@ -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"];
|
||||
}
|
||||
|
||||
24
doc/graphviz/python-invoke-lammps.dot
Normal file
24
doc/graphviz/python-invoke-lammps.dot
Normal file
@ -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];
|
||||
}
|
||||
|
||||
@ -329,6 +329,7 @@ LAMMPS.
|
||||
----------
|
||||
|
||||
.. _exe:
|
||||
.. _library:
|
||||
|
||||
Build the LAMMPS executable and library
|
||||
---------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <ctypes_>`_. The ``lammps.py`` wrapper
|
||||
is discussed on the :doc:`Python library <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
|
||||
|
||||
@ -10,7 +10,7 @@ If you prefer to download a tarball, as described on the
|
||||
:doc:`tarball download <Install_tarball>` 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 <https://lammps.sandia.gov/bug.html>`_
|
||||
`bug fixes and new feature page <https://lammps.sandia.gov/bug.html>`_
|
||||
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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 266 KiB |
BIN
doc/src/JPG/lammps-invoke-python.png
Normal file
BIN
doc/src/JPG/lammps-invoke-python.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
doc/src/JPG/pylammps-invoke-lammps.png
Normal file
BIN
doc/src/JPG/pylammps-invoke-lammps.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
BIN
doc/src/JPG/python-invoke-lammps.png
Normal file
BIN
doc/src/JPG/python-invoke-lammps.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
@ -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.
|
||||
|
||||
@ -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 <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 <exceptions>`, avoiding them proactively is a safer
|
||||
approach.
|
||||
|
||||
.. code-block:: C
|
||||
:caption: Example for using configuration settings functions
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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 <python>` doc page and the :doc:`variable <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 <fix_python_invoke>` command can execute
|
||||
Python code at selected timesteps during a simulation run.
|
||||
|
||||
The :doc:`pair_style python <pair_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 <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.
|
||||
|
||||
45
doc/src/Python_config.rst
Normal file
45
doc/src/Python_config.rst
Normal file
@ -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 <lammps.lammps.has_mpi_support>`
|
||||
* :py:attr:`lammps.has_exceptions <lammps.lammps.has_exceptions>`
|
||||
* :py:attr:`lammps.has_gzip_support <lammps.lammps.has_gzip_support>`
|
||||
* :py:attr:`lammps.has_png_support <lammps.lammps.has_png_support>`
|
||||
* :py:attr:`lammps.has_jpeg_support <lammps.lammps.has_jpeg_support>`
|
||||
* :py:attr:`lammps.has_ffmpeg_support <lammps.lammps.has_ffmpeg_support>`
|
||||
|
||||
* :py:attr:`lammps.installed_packages <lammps.lammps.installed_pages>`
|
||||
|
||||
* :py:meth:`lammps.has_style() <lammps.lammps.has_style()>`
|
||||
* :py:meth:`lammps.available_styles() <lammps.lammps.available_styles()>`
|
||||
36
doc/src/Python_error.rst
Normal file
36
doc/src/Python_error.rst
Normal file
@ -0,0 +1,36 @@
|
||||
LAMMPS error handling in Python
|
||||
*******************************
|
||||
|
||||
Compiling the shared library with :ref:`C++ exception support <exceptions>` 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.
|
||||
@ -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
|
||||
|
||||
16
doc/src/Python_ext.rst
Normal file
16
doc/src/Python_ext.rst
Normal file
@ -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.
|
||||
|
||||
@ -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 <http://www.python.org>`_, 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 <http://www.python.org>`_, 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 <Howto_couple>` doc page for more ideas about
|
||||
coupling LAMMPS to other codes. See the :doc:`Howto library <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 <https://github.com/lammps/lammps>`_,
|
||||
and they can be added to the LAMMPS distribution or webpage.
|
||||
and they can be added to the LAMMPS distribution or web page.
|
||||
|
||||
@ -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
|
||||
<lammps_c_api>` from Python by dynamically loading functions in the
|
||||
LAMMPS shared library through the Python `ctypes <ctypes_>`_
|
||||
module. Because of the dynamic loading, it is required that LAMMPS is
|
||||
compiled in :ref:`"shared" mode <exe>`. It is also recommended to
|
||||
compile LAMMPS with :ref:`C++ exceptions <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 <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 \<python module\> -l <shared library> -v <version.h file> [-d \<pydir\>]
|
||||
:ref:`Build the LAMMPS executable and library <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 <Build_cmake>` or the
|
||||
:doc:`traditional make <Build_make>` procedure in :ref:`shared
|
||||
mode <exe>`. 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 <python module> -l <shared library> -v <version.h file> [-d <pydir>]
|
||||
|
||||
* 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 <python_pep405>`_ for more
|
||||
information.
|
||||
|
||||
To create a virtual environment in the folder ``$HOME/myenv``,
|
||||
use the `venv <python_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
|
||||
<python_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 <Build>` as usual in
|
||||
:ref:`"shared" mode <exe>` 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 <https://mpi4py.readthedocs.io/>`_
|
||||
(aka mpi4py) and you will find installation instruction for it below.
|
||||
|
||||
.. note::
|
||||
|
||||
Older LAMMPS versions were also tested with `PyPar <https://github.com/daleroberts/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 <mpi4py_install>`_ 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 <Python_shlib>`), 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.
|
||||
|
||||
@ -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 <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 <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 <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 <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 <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 <compute>`
|
||||
and :doc:`fixes <fix>` for a description of what they calculate and
|
||||
store.
|
||||
|
||||
For extract_variable(), an :doc:`equal-style or atom-style variable <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.
|
||||
|
||||
@ -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
|
||||
<LAMMPS_NS::LAMMPS>` instance is included in the constructors for the
|
||||
:py:meth:`lammps <lammps.lammps.__init__()>`, :py:meth:`PyLammps <lammps.PyLammps.__init__()>`,
|
||||
and :py:meth:`PyLammps <lammps.IPyLammps.__init__()>` 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 <python>` command, where a pointer to the already existing
|
||||
:cpp:class:`LAMMPS <LAMMPS_NS::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 <mpi4py_url_>`_
|
||||
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 <lammps.lammps>`
|
||||
instance is a specific way, but want to take advantage of the
|
||||
:py:class:`PyLammps <lammps.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 <lammps.lammps>`
|
||||
instance is a specific way, but want to take advantage of the
|
||||
:py:class:`IPyLammps <lammps.IPyLammps>` interface.
|
||||
|
||||
In all of the above cases, same as with the :ref:`C library API <lammps_c_api>`, this will use the
|
||||
``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was
|
||||
compiled with. The :py:func:`lmp.close() <lammps.lammps.close>` call is
|
||||
optional since the LAMMPS class instance will also be deleted
|
||||
automatically during the :py:class:`lammps <lammps.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 <Library_execute>`, 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 <lammps.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 <lammps.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 <lammps.PyLammps>` class is an extension of
|
||||
:py:class:`PyLammps <lammps.PyLammps>`, adding additional functions to
|
||||
@ -353,12 +86,12 @@ See the :doc:`PyLammps Tutorial <Howto_pylammps>` for examples.
|
||||
----------
|
||||
|
||||
Additional components of the ``lammps`` module
|
||||
**********************************************
|
||||
==============================================
|
||||
|
||||
The :py:mod:`lammps` module additionally contains several constants
|
||||
and the :py:class:`NeighList <lammps.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() <numpy_wrapper.extract_compute>` and
|
||||
:py:func:`lammps.numpy.extract_fix() <numpy_wrapper.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() <numpy_wrapper.extract_compute>` and
|
||||
:py:func:`lammps.numpy.extract_fix() <numpy_wrapper.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 <variable>`.
|
||||
|
||||
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 <exceptions>` 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:
|
||||
|
||||
@ -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 <https://mpi4py.readthedocs.io/>`_
|
||||
* `pypar <https://github.com/daleroberts/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.
|
||||
18
doc/src/Python_neighbor.rst
Normal file
18
doc/src/Python_neighbor.rst
Normal file
@ -0,0 +1,18 @@
|
||||
Accessing LAMMPS Neighbor lists
|
||||
*******************************
|
||||
|
||||
**Methods:**
|
||||
|
||||
* :py:meth:`lammps.get_neighlist() <lammps.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() <lammps.lammps.find_pair_neighlist()>`: Find neighbor list of pair style
|
||||
* :py:meth:`lammps.find_fix_neighlist() <lammps.lammps.find_pair_neighlist()>`: Find neighbor list of pair style
|
||||
* :py:meth:`lammps.find_compute_neighlist() <lammps.lammps.find_pair_neighlist()>`: Find neighbor list of pair style
|
||||
|
||||
|
||||
**NumPy Methods:**
|
||||
|
||||
* :py:meth:`lammps.numpy.get_neighlist() <lammps.numpy_wrapper.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() <lammps.numpy_wrapper.get_neighlist_element_neighbors()>`: Get element in neighbor list and its neighbors (as numpy array)
|
||||
@ -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 <Python_module>` 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 <exe>`
|
||||
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 <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 <https://www.python.org/doc/sunset-python-2/>`_. 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 <Howto_library>`, so
|
||||
wrap LAMMPS through the its :doc:`library interface <Library>`, 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 <lammps.lammps>` can be used indirectly through the provided
|
||||
:py:class:`PyLammps <lammps.PyLammps>` and :py:class:`IPyLammps
|
||||
<lammps.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
|
||||
|
||||
@ -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 <Howto_pylammps>` doc page.
|
||||
@ -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 <https://mpi4py.readthedocs.io>`_ 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.
|
||||
|
||||
@ -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 <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 <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.
|
||||
@ -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 <Build_basics>` doc page about building a shared
|
||||
library and the :doc:`Python_install <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 <https://github.com/daleroberts/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 <https://mpi4py.readthedocs.io>`_ 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.
|
||||
44
doc/src/Python_trouble.rst
Normal file
44
doc/src/Python_trouble.rst
Normal file
@ -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 <Build_basics>` doc page about building a shared library.
|
||||
569
doc/src/Python_usage.rst
Normal file
569
doc/src/Python_usage.rst
Normal file
@ -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
|
||||
<LAMMPS_NS::LAMMPS>` instance is included in the constructors for the
|
||||
:py:class:`lammps <lammps.lammps>`, :py:class:`PyLammps <lammps.PyLammps>`,
|
||||
and :py:class:`IPyLammps <lammps.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 <python>` command, where a pointer to the already existing
|
||||
:cpp:class:`LAMMPS <LAMMPS_NS::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 <mpi4py_url_>`_
|
||||
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 <lammps.PyLammps>` class is a wrapper around the
|
||||
:py:class:`lammps <lammps.lammps>` class and all of its lower level functions.
|
||||
By default, it will create a new instance of :py:class:`lammps <lammps.lammps>` passing
|
||||
along all arguments to the constructor of :py:class:`lammps <lammps.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 <lammps.PyLammps>` objects can also be created on top of an existing
|
||||
:py:class:`lammps <lammps.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 <lammps.lammps>`
|
||||
instance is a specific way, but want to take advantage of the
|
||||
:py:class:`PyLammps <lammps.PyLammps>` interface.
|
||||
|
||||
.. tab:: IPyLammps API
|
||||
|
||||
The :py:class:`IPyLammps <lammps.IPyLammps>` class is an extension of the
|
||||
:py:class:`PyLammps <lammps.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 <lammps.lammps>`
|
||||
instance is a specific way, but want to take advantage of the
|
||||
:py:class:`IPyLammps <lammps.IPyLammps>` interface.
|
||||
|
||||
In all of the above cases, same as with the :ref:`C library API <lammps_c_api>`, this will use the
|
||||
``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was
|
||||
compiled with.
|
||||
|
||||
The :py:func:`lmp.close() <lammps.lammps.close()>` call is
|
||||
optional since the LAMMPS class instance will also be deleted
|
||||
automatically during the :py:class:`lammps <lammps.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 <lammps.lammps>`,
|
||||
:py:class:`PyLammps <lammps.PyLammps>`, or
|
||||
:py:class:`IPyLammps <lammps.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 <Library_execute>`, 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 <lammps.PyLammps>`
|
||||
and :py:class:`IPyLammps <lammps.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 <lammps.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 <lammps.lammps>`, :py:class:`PyLammps <lammps.PyLammps>`, or
|
||||
:py:class:`IPyLammps <lammps.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 <lammps.lammps>` is just a thin layer that wraps C API calls,
|
||||
:py:class:`PyLammps <lammps.PyLammps>` and :py:class:`IPyLammps <lammps.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() <lammps.lammps.version()>`: return the numerical version id, e.g. LAMMPS 2 Sep 2015 -> 20150902
|
||||
* :py:meth:`get_thermo() <lammps.lammps.get_thermo()>`: return current value of a thermo keyword
|
||||
* :py:meth:`get_natoms() <lammps.lammps.get_natoms()>`: total # of atoms as int
|
||||
* :py:meth:`reset_box() <lammps.lammps.reset_box()>`: reset the simulation box size
|
||||
* :py:meth:`extract_setting() <lammps.lammps.extract_setting()>`: return a global setting
|
||||
* :py:meth:`extract_global() <lammps.lammps.extract_global()>`: extract a global quantity
|
||||
* :py:meth:`extract_atom() <lammps.lammps.extract_atom()>`: extract a per-atom quantity
|
||||
* :py:meth:`extract_box() <lammps.lammps.extract_box()>`: extract box info
|
||||
* :py:meth:`create_atoms() <lammps.lammps.create_atoms()>`: create N atoms with IDs, types, x, v, and image flags
|
||||
|
||||
**Numpy Methods**:
|
||||
|
||||
* :py:meth:`numpy.extract_atom() <lammps.numpy_wrapper.extract_atom()>`: extract a per-atom quantity as numpy array
|
||||
|
||||
.. tab:: PyLammps/IPyLammps API
|
||||
|
||||
In addition to the functions provided by :py:class:`lammps <lammps.lammps>`, :py:class:`PyLammps <lammps.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() <lammps.lammps.extract_compute()>` and
|
||||
:py:meth:`lammps.extract_fix() <lammps.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 <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 <compute>`
|
||||
and :doc:`fixes <fix>` for a description of what they calculate and
|
||||
store.
|
||||
|
||||
For :py:meth:`lammps.extract_variable() <lammps.lammps.extract_variable()>`,
|
||||
an :doc:`equal-style or atom-style variable <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() <lammps.numpy_wrapper.extract_compute()>`,
|
||||
:py:meth:`lammps.numpy.extract_fix() <lammps.numpy_wrapper.extract_fix()>`, and
|
||||
:py:meth:`lammps.numpy.extract_variable() <lammps.numpy_wrapper.extract_variable()>` are
|
||||
equivalent NumPy implementations that return NumPy arrays instead of ``ctypes`` pointers.
|
||||
|
||||
The :py:meth:`lammps.set_variable() <lammps.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() <lammps.lammps.extract_compute()>`: extract value(s) from a compute
|
||||
* :py:meth:`lammps.extract_fix() <lammps.lammps.extract_fix()>`: extract value(s) from a fix
|
||||
* :py:meth:`lammps.extract_variable() <lammps.lammps.extract_variable()>`: extract value(s) from a variable
|
||||
* :py:meth:`lammps.set_variable() <lammps.lammps.set_variable()>`: set existing named string-style variable to value
|
||||
|
||||
**NumPy Methods**:
|
||||
|
||||
* :py:meth:`lammps.numpy.extract_compute() <lammps.numpy_wrapper.extract_compute()>`: extract value(s) from a compute, return arrays as numpy arrays
|
||||
* :py:meth:`lammps.numpy.extract_fix() <lammps.numpy_wrapper.extract_fix()>`: extract value(s) from a fix, return arrays as numpy arrays
|
||||
* :py:meth:`lammps.numpy.extract_variable() <lammps.numpy_wrapper.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 <lammps.PyLammps>` interface.
|
||||
|
||||
To define a variable you can use the :doc:`variable <variable>` command:
|
||||
|
||||
.. code-block:: Python
|
||||
|
||||
L.variable("a index 2")
|
||||
|
||||
A dictionary of all variables is returned by the :py:attr:`PyLammps.variables <lammps.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.
|
||||
|
||||
@ -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 <compute_group_group>`
|
||||
* :doc:`compute heat/flux <compute_heat_flux>`
|
||||
|
||||
Default
|
||||
"""""""
|
||||
|
||||
@ -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 <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:
|
||||
|
||||
|
||||
@ -3289,6 +3289,7 @@ vectorized
|
||||
Vegt
|
||||
vel
|
||||
Velázquez
|
||||
venv
|
||||
Verlag
|
||||
verlet
|
||||
Verlet
|
||||
|
||||
@ -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])
|
||||
|
||||
676
python/lammps.py
676
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 <comm_modify>`.
|
||||
|
||||
: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 <py_datatype_constants>`
|
||||
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 <py_datatype_constants>`
|
||||
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 <py_datatype_constants>` 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 <py_datatype_constants>` 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() <lammps.numpy_wrapper.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 <lammps.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 <comm_modify>`.
|
||||
|
||||
: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() <lammps.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() <lammps.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() <lammps.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() <lammps.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
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
|
||||
Reference in New Issue
Block a user