From 48d2a48a1f682bed928a8ff2974e17f58c860f00 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 26 Aug 2020 09:01:59 -0400 Subject: [PATCH] import updated python module from progguide branch --- python/lammps.py | 983 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 763 insertions(+), 220 deletions(-) diff --git a/python/lammps.py b/python/lammps.py index 8bd1fc693b..2eba2a45c9 100644 --- a/python/lammps.py +++ b/python/lammps.py @@ -10,10 +10,9 @@ # # See the README file in the top-level LAMMPS directory. # ------------------------------------------------------------------------- +# Python wrappers for the LAMMPS library via ctypes -# Python wrappers on LAMMPS library via ctypes - -# for python3 compatibility +# for python2/3 compatibility from __future__ import print_function @@ -32,10 +31,30 @@ import select import re import sys +# various symbolic constants to be used +# in certain calls to select data formats LAMMPS_INT = 0 -LAMMPS_DOUBLE = 1 -LAMMPS_BIGINT = 2 -LAMMPS_TAGINT = 3 +LAMMPS_INT2D = 1 +LAMMPS_DOUBLE = 2 +LAMMPS_DBLE2D = 3 +LAMMPS_BIGINT = 4 +LAMMPS_TAGINT = 5 +LAMMPS_STRING = 6 + +# these must be kept in sync with the enums in library.h +LMP_STYLE_GLOBAL = 0 +LMP_STYLE_ATOM = 1 +LMP_STYLE_LOCAL = 2 + +LMP_TYPE_SCALAR = 0 +LMP_TYPE_VECTOR = 1 +LMP_TYPE_ARRAY = 2 +LMP_SIZE_VECTOR = 3 +LMP_SIZE_ROWS = 4 +LMP_SIZE_COLS = 5 + +LMP_VAR_EQUAL = 0 +LMP_VAR_ATOM = 1 def get_ctypes_int(size): if size == 4: @@ -52,13 +71,13 @@ class MPIAbortException(Exception): return repr(self.message) class NeighList: - """This is a wrapper class that exposes the contents of a neighbor list + """This is a wrapper class that exposes the contents of a neighbor list. It can be used like a regular Python list. Internally it uses the lower-level LAMMPS C-library interface. - :param lmp: reference to instance of :class:`lammps` + :param lmp: reference to instance of :py:class:`lammps` :type lmp: lammps :param idx: neighbor list index :type idx: int @@ -103,31 +122,49 @@ class NeighList: yield self.get(ii) class lammps(object): + """Create an instance of the LAMMPS Python class. - # detect if Python is using version of mpi4py that can pass a communicator + .. _mpi4py_docs: https://mpi4py.readthedocs.io/ - has_mpi4py = False - try: - from mpi4py import MPI - from mpi4py import __version__ as mpi4py_version - if mpi4py_version.split('.')[0] in ['2','3']: has_mpi4py = True - except: - pass + This is a Python wrapper class that exposes the LAMMPS C-library + interface to Python. It either requires that LAMMPS has been compiled + as shared library which is then dynamically loaded via the ctypes + Python module or that this module called from a Python function that + is called from a Python interpreter embedded into a LAMMPS executable, + for example through the :doc:`python invoke ` command. + When the class is instantiated it calls the :cpp:func:`lammps_open` + function of the LAMMPS C-library interface, which in + turn will create an instance of the :cpp:class:`LAMMPS ` + C++ class. The handle to this C++ class is stored internally + and automatically passed to the calls to the C library interface. + + :param name: "machine" name of the shared LAMMPS library ("mpi" loads ``liblammps_mpi.so``, "" loads ``liblammps.so``) + :type name: string + :param cmdargs: list of command line arguments to be passed to the :cpp:func:`lammps_open` function. The executable name is automatically added. + :type cmdargs: list + :param ptr: pointer to a LAMMPS C++ class instance when called from an embedded Python interpreter. None means load symbols from shared library. + :type ptr: pointer + :param comm: MPI communicator (as provided by `mpi4py `_). ``None`` means use ``MPI_COMM_WORLD`` implicitly. + :type comm: MPI_Comm + """ # create instance of LAMMPS - def __init__(self,name="",cmdargs=None,ptr=None,comm=None): + def __init__(self,name='',cmdargs=None,ptr=None,comm=None): self.comm = comm self.opened = 0 - # determine module location + # determine module file location modpath = dirname(abspath(getsourcefile(lambda:0))) self.lib = None self.lmp = None - # if a pointer to a LAMMPS object is handed in, - # all symbols should already be available + # if a pointer to a LAMMPS object is handed in + # when being called from a Python interpreter + # embedded into a LAMMPS executable, all library + # symbols should already be available so we do not + # load a shared object. try: if ptr: self.lib = CDLL("",RTLD_GLOBAL) @@ -142,23 +179,47 @@ class lammps(object): # fall back to loading with a relative path, # typically requires LD_LIBRARY_PATH to be set appropriately - if any([f.startswith('liblammps') and f.endswith('.dylib') for f in os.listdir(modpath)]): + if any([f.startswith('liblammps') and f.endswith('.dylib') + for f in os.listdir(modpath)]): lib_ext = ".dylib" + elif any([f.startswith('liblammps') and f.endswith('.dll') + for f in os.listdir(modpath)]): + lib_ext = ".dll" else: lib_ext = ".so" if not self.lib: try: - if not name: self.lib = CDLL(join(modpath,"liblammps" + lib_ext),RTLD_GLOBAL) - else: self.lib = CDLL(join(modpath,"liblammps_%s" % name + lib_ext), - RTLD_GLOBAL) + if not name: + self.lib = CDLL(join(modpath,"liblammps" + lib_ext),RTLD_GLOBAL) + else: + self.lib = CDLL(join(modpath,"liblammps_%s" % name + lib_ext), + RTLD_GLOBAL) except: - if not name: self.lib = CDLL("liblammps" + lib_ext,RTLD_GLOBAL) - else: self.lib = CDLL("liblammps_%s" % name + lib_ext,RTLD_GLOBAL) + if not name: + self.lib = CDLL("liblammps" + lib_ext,RTLD_GLOBAL) + else: + self.lib = CDLL("liblammps_%s" % name + lib_ext,RTLD_GLOBAL) - # define ctypes API for each library method - # NOTE: should add one of these for each lib function + # declare all argument and return types for all library methods here. + # exceptions are where the arguments depend on certain conditions and + # then are defined where the functions are used. + self.lib.lammps_open.restype = c_void_p + self.lib.lammps_open_no_mpi.restype = c_void_p + self.lib.lammps_close.argtypes = [c_void_p] + self.lib.lammps_free.argtypes = [c_void_p] + self.lib.lammps_file.argtypes = [c_void_p, c_char_p] + self.lib.lammps_file.restype = None + + self.lib.lammps_command.argtypes = [c_void_p, c_char_p] + self.lib.lammps_command.restype = c_char_p + self.lib.lammps_commands_list.restype = None + self.lib.lammps_commands_string.argtypes = [c_void_p, c_char_p] + self.lib.lammps_commands_string.restype = None + + self.lib.lammps_get_natoms.argtypes = [c_void_p] + self.lib.lammps_get_natoms.restype = c_double self.lib.lammps_extract_box.argtypes = \ [c_void_p,POINTER(c_double),POINTER(c_double), POINTER(c_double),POINTER(c_double),POINTER(c_double), @@ -210,6 +271,16 @@ class lammps(object): self.lib.lammps_get_last_error_message.argtypes = [c_void_p, c_char_p, c_int] self.lib.lammps_get_last_error_message.restype = c_int + # detect if Python is using version of mpi4py that can pass a communicator + + self.has_mpi4py = False + try: + from mpi4py import __version__ as mpi4py_version + # tested to work with mpi4py versions 2 and 3 + self.has_mpi4py = mpi4py_version.split('.')[0] in ['2','3'] + except: + pass + # if no ptr provided, create an instance of LAMMPS # don't know how to pass an MPI communicator from PyPar # but we can pass an MPI communicator from mpi4py v2.0.0 and later @@ -224,17 +295,22 @@ class lammps(object): # with mpi4py v2, can pass MPI communicator to LAMMPS # need to adjust for type of MPI communicator object # allow for int (like MPICH) or void* (like OpenMPI) + if self.has_mpi4py and self.has_mpi_support: + from mpi4py import MPI + self.MPI = MPI if comm: - if not lammps.has_mpi4py: + if not self.has_mpi4py: raise Exception('Python mpi4py version is not 2 or 3') - if lammps.MPI._sizeof(lammps.MPI.Comm) == sizeof(c_int): + if not self.has_mpi_support: + raise Exception('LAMMPS not compiled with real MPI library') + if self.MPI._sizeof(self.MPI.Comm) == sizeof(c_int): MPI_Comm = c_int else: MPI_Comm = c_void_p narg = 0 - cargs = 0 + cargs = None if cmdargs: cmdargs.insert(0,"lammps.py") narg = len(cmdargs) @@ -243,22 +319,19 @@ class lammps(object): cmdargs[i] = cmdargs[i].encode() cargs = (c_char_p*narg)(*cmdargs) self.lib.lammps_open.argtypes = [c_int, c_char_p*narg, \ - MPI_Comm, c_void_p()] + MPI_Comm, c_void_p] else: - self.lib.lammps_open.argtypes = [c_int, c_int, \ - MPI_Comm, c_void_p()] + self.lib.lammps_open.argtypes = [c_int, c_char_p, \ + MPI_Comm, c_void_p] - self.lib.lammps_open.restype = None self.opened = 1 - self.lmp = c_void_p() - comm_ptr = lammps.MPI._addressof(comm) + comm_ptr = self.MPI._addressof(comm) comm_val = MPI_Comm.from_address(comm_ptr) - self.lib.lammps_open(narg,cargs,comm_val,byref(self.lmp)) + self.lmp = self.lib.lammps_open(narg,cargs,comm_val,None) else: - if lammps.has_mpi4py: - from mpi4py import MPI - self.comm = MPI.COMM_WORLD + if self.has_mpi4py and self.has_mpi_support: + self.comm = self.MPI.COMM_WORLD self.opened = 1 if cmdargs: cmdargs.insert(0,"lammps.py") @@ -267,13 +340,12 @@ class lammps(object): if type(cmdargs[i]) is str: cmdargs[i] = cmdargs[i].encode() cargs = (c_char_p*narg)(*cmdargs) - self.lmp = c_void_p() - self.lib.lammps_open_no_mpi(narg,cargs,byref(self.lmp)) + self.lib.lammps_open_no_mpi.argtypes = [c_int, c_char_p*narg, \ + c_void_p] + self.lmp = self.lib.lammps_open_no_mpi(narg,cargs,None) else: - self.lmp = c_void_p() - self.lib.lammps_open_no_mpi(0,None,byref(self.lmp)) - # could use just this if LAMMPS lib interface supported it - # self.lmp = self.lib.lammps_open_no_mpi(0,None) + self.lib.lammps_open_no_mpi.argtypes = [c_int, c_char_p, c_void_p] + self.lmp = self.lib.lammps_open_no_mpi(0,None,None) else: # magic to convert ptr to ctypes ptr @@ -311,114 +383,9 @@ class lammps(object): self.lib.lammps_close(self.lmp) self.opened = 0 - def close(self): - if self.opened: self.lib.lammps_close(self.lmp) - self.lmp = None - self.opened = 0 - - def version(self): - return self.lib.lammps_version(self.lmp) - - def file(self,file): - if file: file = file.encode() - self.lib.lammps_file(self.lmp,file) - - # send a single command - - def command(self,cmd): - if cmd: cmd = cmd.encode() - 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) - - # send a list of commands - - def commands_list(self,cmdlist): - cmds = [x.encode() for x in cmdlist if type(x) is str] - args = (c_char_p * len(cmdlist))(*cmds) - self.lib.lammps_commands_list(self.lmp,len(cmdlist),args) - - # send a string of commands - - def commands_string(self,multicmd): - if type(multicmd) is str: multicmd = multicmd.encode() - self.lib.lammps_commands_string(self.lmp,c_char_p(multicmd)) - - # extract lammps type byte sizes - - def extract_setting(self, name): - if name: name = name.encode() - self.lib.lammps_extract_setting.restype = c_int - return int(self.lib.lammps_extract_setting(self.lmp,name)) - - # extract global info - - def extract_global(self,name,type): - if name: name = name.encode() - if type == LAMMPS_INT: - self.lib.lammps_extract_global.restype = POINTER(c_int) - elif type == LAMMPS_DOUBLE: - self.lib.lammps_extract_global.restype = POINTER(c_double) - elif type == LAMMPS_BIGINT: - self.lib.lammps_extract_global.restype = POINTER(self.c_bigint) - elif type == LAMMPS_TAGINT: - self.lib.lammps_extract_global.restype = POINTER(self.c_tagint) - else: return None - ptr = self.lib.lammps_extract_global(self.lmp,name) - return ptr[0] - - # extract global info - - def extract_box(self): - boxlo = (3*c_double)() - boxhi = (3*c_double)() - xy = c_double() - yz = c_double() - xz = c_double() - periodicity = (3*c_int)() - box_change = c_int() - - self.lib.lammps_extract_box(self.lmp,boxlo,boxhi, - byref(xy),byref(yz),byref(xz), - periodicity,byref(box_change)) - - boxlo = boxlo[:3] - boxhi = boxhi[:3] - xy = xy.value - yz = yz.value - xz = xz.value - periodicity = periodicity[:3] - box_change = box_change.value - - return boxlo,boxhi,xy,yz,xz,periodicity,box_change - - # extract per-atom info - # NOTE: need to insure are converting to/from correct Python type - # e.g. for Python list or NumPy or ctypes - - def extract_atom(self,name,type): - if name: name = name.encode() - if type == 0: - self.lib.lammps_extract_atom.restype = POINTER(c_int) - elif type == 1: - self.lib.lammps_extract_atom.restype = POINTER(POINTER(c_int)) - elif type == 2: - self.lib.lammps_extract_atom.restype = POINTER(c_double) - elif type == 3: - self.lib.lammps_extract_atom.restype = POINTER(POINTER(c_double)) - else: return None - ptr = self.lib.lammps_extract_atom(self.lmp,name) - return ptr - @property def numpy(self): + "Convert between ctypes arrays and numpy arrays" if not self._numpy: import numpy as np class LammpsNumpyWrapper: @@ -480,56 +447,440 @@ class lammps(object): self._numpy = LammpsNumpyWrapper(self) return self._numpy - # extract compute info + def close(self): + """Explicitly delete a LAMMPS instance through the C-library interface. + + This is a wrapper around the :cpp:func:`lammps_close` function of the C-library interface. + """ + if self.opened: self.lib.lammps_close(self.lmp) + self.lmp = None + self.opened = 0 + + def finalize(self): + """Shut down the MPI communication through the library interface by calling :cpp:func:`lammps_finalize`. + """ + if self.opened: self.lib.lammps_close(self.lmp) + self.lmp = None + self.opened = 0 + self.lib.lammps_finalize() + + def version(self): + """Return a numerical representation of the LAMMPS version in use. + + This is a wrapper around the :cpp:func:`lammps_close` function of the C-library interface. + + :return: version number + :rtype: int + """ + return self.lib.lammps_version(self.lmp) + + def file(self,file): + """Read LAMMPS commands from a file. + + This is a wrapper around the :cpp:func:`lammps_file` function of the C-library interface. + It will open the file with the name/path `file` and process the LAMMPS commands line by line until + the end. The function will return when the end of the file is reached. + + :param file: Name of the file/path with LAMMPS commands + :type file: string + """ + if file: file = file.encode() + else: return + self.lib.lammps_file(self.lmp,file) + + def command(self,cmd): + """Process a single LAMMPS input command from a string. + + This is a wrapper around the :cpp:func:`lammps_command` + function of the C-library interface. + + :param cmd: a single lammps command + :type cmd: string + """ + if cmd: cmd = cmd.encode() + else: return + 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) + + def commands_list(self,cmdlist): + """Process multiple LAMMPS input commands from a list of strings. + + This is a wrapper around the + :cpp:func:`lammps_commands_list` function of + the C-library interface. + + :param cmdlist: a single lammps command + :type cmdlist: list of strings + """ + cmds = [x.encode() for x in cmdlist if type(x) is str] + narg = len(cmdlist) + args = (c_char_p * narg)(*cmds) + self.lib.lammps_commands_list.argtypes = [c_void_p, c_int, c_char_p * narg] + self.lib.lammps_commands_list(self.lmp,narg,args) + + def commands_string(self,multicmd): + """Process a block of LAMMPS input commands from a string. + + This is a wrapper around the + :cpp:func:`lammps_commands_string` + function of the C-library interface. + + :param multicmd: text block of lammps commands + :type multicmd: string + """ + if type(multicmd) is str: multicmd = multicmd.encode() + self.lib.lammps_commands_string(self.lmp,c_char_p(multicmd)) + + def get_natoms(self): + """Get the total number of atoms in the LAMMPS instance. + + Will be precise up to 53-bit signed integer due to the + underlying :cpp:func:`lammps_get_natoms` function returning a double. + + :return: number of atoms + :rtype: float + """ + return self.lib.lammps_get_natoms(self.lmp) + + def extract_box(self): + """Extract simulation box parameters + + This is a wrapper around the :cpp:func:`lammps_extract_box` function + of the C-library interface. Unlike in the C function, the result is + returned as a list. + + :return: list of the extracted data: boxlo, boxhi, xy, yz, xz, periodicity, box_change + :rtype: [ 3*double, 3*double, double, double, 3*int, int] + """ + boxlo = (3*c_double)() + boxhi = (3*c_double)() + xy = c_double() + yz = c_double() + xz = c_double() + periodicity = (3*c_int)() + box_change = c_int() + + self.lib.lammps_extract_box(self.lmp,boxlo,boxhi, + byref(xy),byref(yz),byref(xz), + periodicity,byref(box_change)) + + boxlo = boxlo[:3] + boxhi = boxhi[:3] + xy = xy.value + yz = yz.value + xz = xz.value + periodicity = periodicity[:3] + box_change = box_change.value + + return boxlo,boxhi,xy,yz,xz,periodicity,box_change + + def reset_box(self,boxlo,boxhi,xy,yz,xz): + """Reset simulation box parameters + + This is a wrapper around the :cpp:func:`lammps_reset_box` function + of the C-library interface. + + :param boxlo: new lower box boundaries + :type boxlo: list of 3 floating point numbers + :param boxhi: new upper box boundaries + :type boxhi: list of 3 floating point numbers + :param xy: xy tilt factor + :type xy: float + :param yz: yz tilt factor + :type yz: float + :param xz: xz tilt factor + :type xz: float + """ + cboxlo = (3*c_double)(*boxlo) + cboxhi = (3*c_double)(*boxhi) + self.lib.lammps_reset_box(self.lmp,cboxlo,cboxhi,xy,yz,xz) + + def get_thermo(self,name): + """Get current value of a thermo keyword + + This is a wrapper around the :cpp:func:`lammps_get_thermo` + function of the C-library interface. + + :param name: name of thermo keyword + :type name: string + :return: value of thermo keyword + :rtype: double or None + """ + if name: name = name.encode() + else: return None + self.lib.lammps_get_thermo.restype = c_double + return self.lib.lammps_get_thermo(self.lmp,name) + + def extract_setting(self, name): + """Query LAMMPS about global settings that can be expressed as an integer. + + This is a wrapper around the :cpp:func:`lammps_extract_setting` + function of the C-library interface. Its documentation includes + a list of the supported keywords. + + :param name: name of the setting + :type name: string + :return: value of the setting + :rtype: int + """ + if name: name = name.encode() + else: return None + self.lib.lammps_extract_setting.restype = c_int + return int(self.lib.lammps_extract_setting(self.lmp,name)) + + # extract global info + + def extract_global(self, name, type): + """Query LAMMPS about global settings of different types. + + This is a wrapper around the :cpp:func:`lammps_extract_global` + function of the C-library interface. Unlike the C function + this method returns the value and not a pointer and thus can + only return the first value for keywords representing a list + of values. The :cpp:func:`lammps_extract_global` 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, the type has to be provided as an argument. For + that purpose the :py:mod:`lammps` module contains the constants + ``LAMMPS_INT``, ``LAMMPS_DOUBLE``, ``LAMMPS_BIGINT``, + ``LAMMPS_TAGINT``, and ``LAMMPS_STRING``. + This function returns ``None`` if either the keyword is not + recognized, or an invalid data type constant is used. + + :param name: name of the setting + :type name: string + :param type: type of the returned data + :type type: int + :return: value of the setting + :rtype: integer or double or string or None + """ + if name: name = name.encode() + else: return None + if type == LAMMPS_INT: + self.lib.lammps_extract_global.restype = POINTER(c_int) + elif type == LAMMPS_DOUBLE: + self.lib.lammps_extract_global.restype = POINTER(c_double) + elif type == LAMMPS_BIGINT: + self.lib.lammps_extract_global.restype = POINTER(self.c_bigint) + elif type == LAMMPS_TAGINT: + self.lib.lammps_extract_global.restype = POINTER(self.c_tagint) + elif type == LAMMPS_STRING: + self.lib.lammps_extract_global.restype = c_char_p + ptr = self.lib.lammps_extract_global(self.lmp,name) + return str(ptr,'ascii') + else: return None + ptr = self.lib.lammps_extract_global(self.lmp,name) + if ptr: return ptr[0] + else: return None + + # extract per-atom info + # NOTE: need to insure are converting to/from correct Python type + # e.g. for Python list or NumPy or ctypes + + def extract_atom(self,name,type): + """Retrieve per-atom properties from LAMMPS + + 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, the type has to be provided as an argument. For + that purpose the :py:mod:`lammps` module contains the constants + ``LAMMPS_INT``, ``LAMMPS_INT2D``, ``LAMMPS_DOUBLE``, + and ``LAMMPS_DBLE2D``. + This function returns ``None`` if either the keyword is not + recognized, or an invalid data type constant is used. + + .. note:: + + While the returned arrays of per-atom data are dimensioned + for the range [0:nmax] - as is the underlying storage - + the data is usually only valid for the range of [0:nlocal], + unless the property of interest is also updated for ghost + atoms. In some cases, this depends on a LAMMPS setting, see + for example :doc:`comm_modify vel yes `. + + :param name: name of the setting + :type name: string + :param type: type of the returned data + :type type: int + :return: requested data + :rtype: pointer to integer or double or None + """ + ntypes = int(self.extract_setting('ntypes')) + nmax = int(self.extract_setting('nmax')) + if name: name = name.encode() + else: return None + if type == LAMMPS_INT: + self.lib.lammps_extract_atom.restype = POINTER(c_int) + elif type == LAMMPS_INT2D: + self.lib.lammps_extract_atom.restype = POINTER(POINTER(c_int)) + elif type == LAMMPS_DOUBLE: + self.lib.lammps_extract_atom.restype = POINTER(c_double) + elif type == LAMMPS_DBLE2D: + self.lib.lammps_extract_atom.restype = POINTER(POINTER(c_double)) + else: return None + ptr = self.lib.lammps_extract_atom(self.lmp,name) + if ptr: return ptr + else: return None + def extract_compute(self,id,style,type): + """Retrieve data from a LAMMPS compute + + This is a wrapper around the :cpp:func:`lammps_extract_compute` + function of the C-library interface. + This function returns ``None`` if either the compute id is not + recognized, or an invalid combination of :ref:`style ` + and :ref:`type ` constants is used. The + names and functionality of the constants are the same as for + the corresponding C-library function. For requests to return + a scalar or a size, the value is returned, otherwise a pointer. + + :param id: compute ID + :type id: string + :param style: style of the data retrieve (global, atom, or local) + :type style: int + :param type: type or size of the returned data (scalar, vector, or array) + :type type: int + :return: requested data + :rtype: integer or double or pointer to 1d or 2d double array or None + """ if id: id = id.encode() - if type == 0: - if style == 0: + else: return None + + if type == LMP_TYPE_SCALAR: + if style == LMP_STYLE_GLOBAL: self.lib.lammps_extract_compute.restype = POINTER(c_double) ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) return ptr[0] - elif style == 1: + elif style == LMP_STYLE_ATOM: return None - elif style == 2: + elif style == LMP_STYLE_LOCAL: self.lib.lammps_extract_compute.restype = POINTER(c_int) ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) return ptr[0] - if type == 1: + + if type == LMP_TYPE_VECTOR: self.lib.lammps_extract_compute.restype = POINTER(c_double) ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) return ptr - if type == 2: + + if type == LMP_TYPE_ARRAY: self.lib.lammps_extract_compute.restype = POINTER(POINTER(c_double)) ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) return ptr + + if type == LMP_SIZE_COLS: + if style == LMP_STYLE_GLOBAL \ + or style == LMP_STYLE_ATOM \ + or style == LMP_STYLE_LOCAL: + self.lib.lammps_extract_compute.restype = POINTER(c_int) + ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) + return ptr[0] + + if type == LMP_SIZE_VECTOR \ + or type == LMP_SIZE_ROWS: + if style == LMP_STYLE_GLOBAL \ + or style == LMP_STYLE_LOCAL: + self.lib.lammps_extract_compute.restype = POINTER(c_int) + ptr = self.lib.lammps_extract_compute(self.lmp,id,style,type) + return ptr[0] + return None # extract fix info - # in case of global datum, free memory for 1 double via lammps_free() + # in case of global data, free memory for 1 double via lammps_free() # double was allocated by library interface function - def extract_fix(self,id,style,type,i=0,j=0): + def extract_fix(self,id,style,type,nrow=0,ncol=0): + """Retrieve data from a LAMMPS fix + + This is a wrapper around the :cpp:func:`lammps_extract_fix` + function of the C-library interface. + This function returns ``None`` if either the fix id is not + recognized, or an invalid combination of :ref:`style ` + and :ref:`type ` constants is used. The + names and functionality of the constants are the same as for + the corresponding C-library function. For requests to return + a scalar or a size, the value is returned, also when accessing + global vectors or arrays, otherwise a pointer. + + :param id: fix ID + :type id: string + :param style: style of the data retrieve (global, atom, or local) + :type style: int + :param type: type or size of the returned data (scalar, vector, or array) + :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 + + """ if id: id = id.encode() - if style == 0: - self.lib.lammps_extract_fix.restype = POINTER(c_double) - ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,i,j) - result = ptr[0] - self.lib.lammps_free(ptr) - return result - elif (style == 2) and (type == 0): - self.lib.lammps_extract_fix.restype = POINTER(c_int) - ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,i,j) - return ptr[0] - elif (style == 1) or (style == 2): - if type == 1: + else: return None + + if style == LMP_STYLE_GLOBAL: + if type == LMP_TYPE_SCALAR \ + or type == LMP_TYPE_VECTOR \ + or type == LMP_TYPE_ARRAY: self.lib.lammps_extract_fix.restype = POINTER(c_double) - elif type == 2: - self.lib.lammps_extract_fix.restype = POINTER(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: + 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] else: return None - ptr = self.lib.lammps_extract_fix(self.lmp,id,style,type,i,j) - return ptr + + elif style == LMP_STYLE_ATOM: + if type == LMP_TYPE_VECTOR: + 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_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_SIZE_COLS: + return ptr[0] + else: + return ptr + + elif style == LMP_STYLE_LOCAL: + if type == LMP_TYPE_VECTOR: + 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: + 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: + return ptr + else: + return ptr[0] else: return None @@ -538,16 +889,41 @@ 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,type): + def extract_variable(self,name,group=None,type=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. + 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: + ``LMP_VAR_EQUAL`` or ``LMP_VAR_STRING``; it defaults to + equal-style variables. + The group parameter is only used for atom-style variables and + defaults to the group "all" if set to ``None``, which is the default. + + :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 + :return: the requested data + :rtype: double, array of doubles, or None + """ if name: name = name.encode() + else: return None if group: group = group.encode() - if type == 0: + if type == 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 == 1: + 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] @@ -559,34 +935,25 @@ class lammps(object): return result return None - # return current value of thermo keyword - - def get_thermo(self,name): - if name: name = name.encode() - self.lib.lammps_get_thermo.restype = c_double - return self.lib.lammps_get_thermo(self.lmp,name) - - # return total number of atoms in system - - def get_natoms(self): - return self.lib.lammps_get_natoms(self.lmp) - - # set variable value - # value is converted to string - # returns 0 for success, -1 if failed - def set_variable(self,name,value): + """Set a new value for a LAMMPS string style variable + + This is a wrapper around the :cpp:func:`lammps_set_variable` + function of the C-library interface. + + :param name: name of the variable + :type name: string + :param value: new variable value + :type value: any. will be converted to a string + :return: either 0 on success or -1 on failure + :rtype: int + """ if name: name = name.encode() + else: return -1 if value: value = str(value).encode() + else: return -1 return self.lib.lammps_set_variable(self.lmp,name,value) - # reset simulation box size - - def reset_box(self,boxlo,boxhi,xy,yz,xz): - cboxlo = (3*c_double)(*boxlo) - cboxhi = (3*c_double)(*boxhi) - self.lib.lammps_reset_box(self.lmp,cboxlo,cboxhi,xy,yz,xz) - # return vector of atom properties gathered across procs # 3 variants to match src/library.cpp # name = atom property recognized by LAMMPS in atom->extract() @@ -648,6 +1015,44 @@ class lammps(object): if name: name = name.encode() self.lib.lammps_scatter_atoms_subset(self.lmp,name,type,count,ndata,ids,data) + + def encode_image_flags(self,ix,iy,iz): + """ convert 3 integers with image flags for x-, y-, and z-direction + into a single integer like it is used internally in LAMMPS + + This method is a wrapper around the :cpp:func:`lammps_encode_image_flags` + function of library interface. + + :param ix: x-direction image flag + :type ix: int + :param iy: y-direction image flag + :type iy: int + :param iz: z-direction image flag + :type iz: int + :return: encoded image flags + :rtype: lammps.c_imageint + """ + self.lib.lammps_encode_image_flags.restype = self.c_imageint + return self.lib.lammps_encode_image_flags(ix,iy,iz) + + def decode_image_flags(self,image): + """ Convert encoded image flag integer into list of three regular integers. + + This method is a wrapper around the :cpp:func:`lammps_decode_image_flags` + function of library interface. + + :param image: encoded image flags + :type image: lammps.c_imageint + :return: list of three image flags in x-, y-, and z- direction + :rtype: list of 3 int + """ + + flags = (c_int*3)() + self.lib.lammps_decode_image_flags.argtypes = [self.c_imageint, POINTER(c_int*3)] + self.lib.lammps_decode_image_flags(image,byref(flags)) + + return [int(i) for i in flags] + # create N atoms on all procs # N = global number of atoms # id = ID of each atom (optional, can be None) @@ -658,47 +1063,178 @@ class lammps(object): # e.g. for Python list or NumPy, etc # ditto for gather_atoms() above - def create_atoms(self,n,id,type,x,v,image=None,shrinkexceed=False): + def create_atoms(self,n,id,type,x,v=None,image=None,shrinkexceed=False): + """ + Create N atoms from list of coordinates and properties + + This function is a wrapper around the :cpp:func:`lammps_create_atoms` + function of the C-library interface, and the behavior is similar except + that the *v*, *image*, and *shrinkexceed* arguments are optional and + default to *None*, *None*, and *False*, respectively. With none being + equivalent to a ``NULL`` pointer in C. + + The lists of coordinates, types, atom IDs, velocities, image flags can + be provided in any format that may be converted into the required + internal data types. Also the list may contain more than *N* entries, + but not fewer. In the latter case, the function will return without + attempting to create atoms. You may use the :py:func:`encode_image_flags + ` method to properly combine three integers + with image flags into a single integer. + + :param n: number of atoms for which data is provided + :type n: int + :param id: list of atom IDs with at least n elements or None + :type id: list of lammps.tagint + :param type: list of atom types + :type type: list of int + :param x: list of coordinates for x-, y-, and z (flat list of 3n entries) + :type x: list of float + :param v: list of velocities for x-, y-, and z (flat list of 3n entries) or None (optional) + :type v: list of float + :param image: list of encoded image flags (optional) + :type image: list of lammps.imageint + :param shrinkexceed: whether to expand shrink-wrap boundaries if atoms are outside the box (optional) + :type shrinkexceed: bool + :return: number of atoms created. 0 if insufficient or invalid data + :rtype: int + """ if id: - id_lmp = (c_int * n)() - id_lmp[:] = id + id_lmp = (self.c_tagint*n)() + try: + id_lmp[:] = id[0:n] + except: + return 0 else: - id_lmp = id + id_lmp = None + + type_lmp = (c_int*n)() + try: + type_lmp[:] = type[0:n] + except: + return 0 + + three_n = 3*n + x_lmp = (c_double*three_n)() + try: + x_lmp[:] = x[0:three_n] + except: + return 0 + + if v: + v_lmp = (c_double*(three_n))() + try: + v_lmp[:] = v[0:three_n] + except: + return 0 + else: + v_lmp = None if image: - image_lmp = (c_int * n)() - image_lmp[:] = image + img_lmp = (self.c_imageint*n)() + try: + img_lmp[:] = image[0:n] + except: + return 0 else: - image_lmp = image + img_lmp = None - type_lmp = (c_int * n)() - type_lmp[:] = type - self.lib.lammps_create_atoms(self.lmp,n,id_lmp,type_lmp,x,v,image_lmp, - shrinkexceed) + if shrinkexceed: + se_lmp = 1 + else: + se_lmp = 0 + + self.lib.lammps_file.argtypes = [c_void_p, c_int, POINTER(self.c_tagint*n), + POINTER(c_int*n), POINTER(c_double*three_n), + POINTER(c_double*three_n), + POINTER(self.c_imageint*n), c_int] + return self.lib.lammps_create_atoms(self.lmp,n,id_lmp,type_lmp,x_lmp,v_lmp,img_lmp,se_lmp) + + @property + def has_mpi_support(self): + """ Report whether the LAMMPS shared library was compiled with a + real MPI library or in serial. + + This is a wrapper around the :cpp:func:`lammps_config_has_mpi_support` + function of the library interface. + + :return: False when compiled with MPI STUBS, otherwise True + :rtype: bool + """ + return self.lib.lammps_config_has_mpi_support() != 0 @property def has_exceptions(self): - """ Return whether the LAMMPS shared library was compiled with C++ exceptions handling enabled """ + """ Report whether the LAMMPS shared library was compiled with C++ + exceptions handling enabled + + This is a wrapper around the :cpp:func:`lammps_config_has_exceptions` + function of the library interface. + + :return: state of C++ exception support + :rtype: bool + """ return self.lib.lammps_config_has_exceptions() != 0 @property def has_gzip_support(self): + """ Report whether the LAMMPS shared library was compiled with support + for reading and writing compressed files through ``gzip``. + + This is a wrapper around the :cpp:func:`lammps_config_has_gzip_support` + function of the library interface. + + :return: state of gzip support + :rtype: bool + """ return self.lib.lammps_config_has_gzip_support() != 0 @property def has_png_support(self): + """ Report whether the LAMMPS shared library was compiled with support + for writing images in PNG format. + + This is a wrapper around the :cpp:func:`lammps_config_has_png_support` + function of the library interface. + + :return: state of PNG support + :rtype: bool + """ return self.lib.lammps_config_has_png_support() != 0 @property def has_jpeg_support(self): + """ Report whether the LAMMPS shared library was compiled with support + for writing images in JPEG format. + + This is a wrapper around the :cpp:func:`lammps_config_has_jpeg_support` + function of the library interface. + + :return: state of JPEG support + :rtype: bool + """ return self.lib.lammps_config_has_jpeg_support() != 0 @property def has_ffmpeg_support(self): + """ State of support for writing movies with ``ffmpeg`` in the LAMMPS shared library + + This is a wrapper around the :cpp:func:`lammps_config_has_ffmpeg_support` + function of the library interface. + + :return: state of ffmpeg support + :rtype: bool + """ return self.lib.lammps_config_has_ffmpeg_support() != 0 @property def installed_packages(self): + """ List of the names of enabled packages in the LAMMPS shared library + + This is a wrapper around the functions :cpp:func:`lammps_config_package_count` + and :cpp:func`lammps_config_package_name` of the library interface. + + :return + """ if self._installed_packages is None: self._installed_packages = [] npackages = self.lib.lammps_config_package_count() @@ -711,10 +1247,14 @@ class lammps(object): def has_style(self, category, name): """Returns whether a given style name is available in a given category + This is a wrapper around the function :cpp:func:`lammps_has_style` + of the library interface. + :param category: name of category :type category: string :param name: name of the style :type name: string + :return: true if style is available in given category :rtype: bool """ @@ -723,8 +1263,12 @@ class lammps(object): def available_styles(self, category): """Returns a list of styles available for a given category + This is a wrapper around the functions :cpp:func:`lammps_style_count` + and :cpp:func`lammps_style_name` of the library interface. + :param category: name of category :type category: string + :return: list of style names in given category :rtype: list """ @@ -759,7 +1303,6 @@ class lammps(object): cCaller = caller 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): @@ -1090,7 +1633,7 @@ def get_thermo_data(output): class PyLammps(object): """ - More Python-like wrapper for LAMMPS (e.g., for iPython) + More Python-like wrapper for LAMMPS (e.g., for IPython) See examples/ipython for usage """ @@ -1135,7 +1678,7 @@ class PyLammps(object): def run(self, *args, **kwargs): output = self.__getattr__('run')(*args, **kwargs) - if(lammps.has_mpi4py): + if(self.has_mpi4py): output = self.lmp.comm.bcast(output, root=0) self.runs += get_thermo_data(output) @@ -1350,7 +1893,7 @@ class PyLammps(object): class IPyLammps(PyLammps): """ - iPython wrapper for LAMMPS which adds embedded graphics capabilities + IPython wrapper for LAMMPS which adds embedded graphics capabilities """ def __init__(self,name="",cmdargs=None,ptr=None,comm=None):