diff --git a/.github/workflows/compile-msvc.yml b/.github/workflows/compile-msvc.yml index e71a2950e6..75d531d9af 100644 --- a/.github/workflows/compile-msvc.yml +++ b/.github/workflows/compile-msvc.yml @@ -21,6 +21,7 @@ jobs: shell: bash run: | cmake -C cmake/presets/windows.cmake \ + -D PKG_PYTHON=on \ -S cmake -B build \ -D BUILD_SHARED_LIBS=on \ -D LAMMPS_EXCEPTIONS=on \ diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 7ee4563b6e..56a07dc94b 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -359,11 +359,13 @@ if(BUILD_OMP) ((CMAKE_CXX_COMPILER_ID STREQUAL "Intel") AND (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 19.0))) # GCC 9.x and later plus Clang 10.x and later implement strict OpenMP 4.0 semantics for consts. # Intel 18.0 was tested to support both, so we switch to OpenMP 4+ from 19.x onward to be safe. - target_compile_definitions(lammps PRIVATE -DLAMMPS_OMP_COMPAT=4) + set(LAMMPS_OMP_COMPAT_LEVEL 4) else() - target_compile_definitions(lammps PRIVATE -DLAMMPS_OMP_COMPAT=3) + set(LAMMPS_OMP_COMPAT_LEVEL 3) endif() + target_compile_definitions(lammps PRIVATE -DLAMMPS_OMP_COMPAT=${LAMMPS_OMP_COMPAT_LEVEL}) target_link_libraries(lammps PRIVATE OpenMP::OpenMP_CXX) + target_link_libraries(lmp PRIVATE OpenMP::OpenMP_CXX) endif() if(PKG_MSCG OR PKG_ATC OR PKG_AWPMD OR PKG_ML-QUIP OR PKG_LATTE) diff --git a/cmake/CMakeSettings.json b/cmake/CMakeSettings.json index ee4b3c46d5..9320341ec4 100644 --- a/cmake/CMakeSettings.json +++ b/cmake/CMakeSettings.json @@ -6,7 +6,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -25,6 +25,54 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" + } + ] + }, + { + "name": "x64-Release-MSVC", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${workspaceRoot}\\build\\${name}", + "installRoot": "${workspaceRoot}\\install\\${name}", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [ + { + "name": "BUILD_SHARED_LIBS", + "value": "True", + "type": "BOOL" + }, + { + "name": "BUILD_TOOLS", + "value": "True", + "type": "BOOL" + }, + { + "name": "LAMMPS_EXCEPTIONS", + "value": "True", + "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" } ] }, @@ -34,11 +82,16 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "clang_cl_x64" ], "variables": [ + { + "name": "BUILD_SHARED_LIBS", + "value": "True", + "type": "BOOL" + }, { "name": "BUILD_TOOLS", "value": "True", @@ -48,6 +101,16 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" } ] }, @@ -57,7 +120,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=on -DCMAKE_CXX_COMPILER=icx -DCMAKE_C_COMPILER=icx -DBUILD_MPI=off", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DCMAKE_CXX_COMPILER=icx -DCMAKE_C_COMPILER=icx", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -76,6 +139,21 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "True", + "type": "BOOL" + }, + { + "name": "BUILD_MPI", + "value": "False", + "type": "BOOL" } ] }, @@ -85,7 +163,7 @@ "configurationType": "Debug", "buildRoot": "${workspaceRoot}\\build\\${name}", "installRoot": "${workspaceRoot}\\install\\${name}", - "cmakeCommandArgs": "-S ${workspaceRoot}\\cmake -C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DENABLE_TESTING=off -DCMAKE_CXX_COMPILER=icl -DCMAKE_C_COMPILER=icl -DCMAKE_Fortran_COMPILER=ifort -DBUILD_MPI=off", + "cmakeCommandArgs": "-C ${workspaceRoot}\\cmake\\presets\\windows.cmake -DCMAKE_CXX_COMPILER=icl -DCMAKE_C_COMPILER=icl -DCMAKE_Fortran_COMPILER=ifort", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], @@ -104,8 +182,23 @@ "name": "LAMMPS_EXCEPTIONS", "value": "True", "type": "BOOL" + }, + { + "name": "PKG_PYTHON", + "value": "True", + "type": "BOOL" + }, + { + "name": "ENABLE_TESTING", + "value": "False", + "type": "BOOL" + }, + { + "name": "BUILD_MPI", + "value": "False", + "type": "BOOL" } ] } ] -} \ No newline at end of file +} diff --git a/cmake/Modules/Packages/KOKKOS.cmake b/cmake/Modules/Packages/KOKKOS.cmake index ebc5adff5b..bccfdf7243 100644 --- a/cmake/Modules/Packages/KOKKOS.cmake +++ b/cmake/Modules/Packages/KOKKOS.cmake @@ -11,8 +11,14 @@ if(Kokkos_ENABLE_CUDA) endif() # Adding OpenMP compiler flags without the checks done for # BUILD_OMP can result in compile failures. Enforce consistency. -if(Kokkos_ENABLE_OPENMP AND NOT BUILD_OMP) - message(FATAL_ERROR "Must enable BUILD_OMP with Kokkos_ENABLE_OPENMP") +if(Kokkos_ENABLE_OPENMP) + if(NOT BUILD_OMP) + message(FATAL_ERROR "Must enable BUILD_OMP with Kokkos_ENABLE_OPENMP") + else() + if(LAMMPS_OMP_COMPAT_LEVEL LESS 4) + message(FATAL_ERROR "Compiler must support OpenMP 4.0 or later with Kokkos_ENABLE_OPENMP") + endif() + endif() endif() ######################################################################## @@ -27,6 +33,8 @@ if(DOWNLOAD_KOKKOS) endforeach() message(STATUS "KOKKOS download requested - we will build our own") list(APPEND KOKKOS_LIB_BUILD_ARGS "-DCMAKE_INSTALL_PREFIX=") + # build KOKKOS downloaded libraries as static libraries but with PIC, if needed + list(APPEND KOKKOS_LIB_BUILD_ARGS "-DBUILD_SHARED_LIBS=OFF") if(CMAKE_REQUEST_PIC) list(APPEND KOKKOS_LIB_BUILD_ARGS ${CMAKE_REQUEST_PIC}) endif() @@ -47,18 +55,22 @@ if(DOWNLOAD_KOKKOS) URL ${KOKKOS_URL} URL_MD5 ${KOKKOS_MD5} CMAKE_ARGS ${KOKKOS_LIB_BUILD_ARGS} - BUILD_BYPRODUCTS /lib/libkokkoscore.a + BUILD_BYPRODUCTS /lib/libkokkoscore.a /lib/libkokkoscontainers.a ) ExternalProject_get_property(kokkos_build INSTALL_DIR) file(MAKE_DIRECTORY ${INSTALL_DIR}/include) - add_library(LAMMPS::KOKKOS UNKNOWN IMPORTED) - set_target_properties(LAMMPS::KOKKOS PROPERTIES + add_library(LAMMPS::KOKKOSCORE UNKNOWN IMPORTED) + add_library(LAMMPS::KOKKOSCONTAINERS UNKNOWN IMPORTED) + set_target_properties(LAMMPS::KOKKOSCORE PROPERTIES IMPORTED_LOCATION "${INSTALL_DIR}/lib/libkokkoscore.a" INTERFACE_INCLUDE_DIRECTORIES "${INSTALL_DIR}/include" INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS}) - target_link_libraries(lammps PRIVATE LAMMPS::KOKKOS) - target_link_libraries(lmp PRIVATE LAMMPS::KOKKOS) - add_dependencies(LAMMPS::KOKKOS kokkos_build) + set_target_properties(LAMMPS::KOKKOSCONTAINERS PROPERTIES + IMPORTED_LOCATION "${INSTALL_DIR}/lib/libkokkoscontainers.a") + target_link_libraries(lammps PRIVATE LAMMPS::KOKKOSCORE LAMMPS::KOKKOSCONTAINERS) + target_link_libraries(lmp PRIVATE LAMMPS::KOKKOSCORE LAMMPS::KOKKOSCONTAINERS) + add_dependencies(LAMMPS::KOKKOSCORE kokkos_build) + add_dependencies(LAMMPS::KOKKOSCONTAINERS kokkos_build) elseif(EXTERNAL_KOKKOS) find_package(Kokkos 3.5.00 REQUIRED CONFIG) target_link_libraries(lammps PRIVATE Kokkos::kokkos) @@ -66,8 +78,14 @@ elseif(EXTERNAL_KOKKOS) else() set(LAMMPS_LIB_KOKKOS_SRC_DIR ${LAMMPS_LIB_SOURCE_DIR}/kokkos) set(LAMMPS_LIB_KOKKOS_BIN_DIR ${LAMMPS_LIB_BINARY_DIR}/kokkos) + # build KOKKOS internal libraries as static libraries but with PIC, if needed + set(BUILD_SHARED_LIBS OFF) + if(CMAKE_REQUEST_PIC) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() add_subdirectory(${LAMMPS_LIB_KOKKOS_SRC_DIR} ${LAMMPS_LIB_KOKKOS_BIN_DIR}) + set(Kokkos_INCLUDE_DIRS ${LAMMPS_LIB_KOKKOS_SRC_DIR}/core/src ${LAMMPS_LIB_KOKKOS_SRC_DIR}/containers/src ${LAMMPS_LIB_KOKKOS_SRC_DIR}/algorithms/src diff --git a/cmake/Modules/Packages/ML-HDNNP.cmake b/cmake/Modules/Packages/ML-HDNNP.cmake index e27b3a1410..5a4c287fa2 100644 --- a/cmake/Modules/Packages/ML-HDNNP.cmake +++ b/cmake/Modules/Packages/ML-HDNNP.cmake @@ -46,12 +46,10 @@ if(DOWNLOAD_N2P2) if((CMAKE_SYSTEM_NAME STREQUAL Windows) AND CMAKE_CROSSCOMPILING) get_target_property(N2P2_MPI_INCLUDE MPI::MPI_CXX INTERFACE_INCLUDE_DIRECTORIES) set(N2P2_PROJECT_OPTIONS "-I${N2P2_MPI_INCLUDE}") - set(MPI_CXX_COMPILER ${CMAKE_CXX_COMPILER}) endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "Intel") get_target_property(N2P2_MPI_INCLUDE MPI::MPI_CXX INTERFACE_INCLUDE_DIRECTORIES) set(N2P2_PROJECT_OPTIONS "-I${N2P2_MPI_INCLUDE}") - set(MPI_CXX_COMPILER ${CMAKE_CXX_COMPILER}) endif() endif() @@ -64,8 +62,8 @@ if(DOWNLOAD_N2P2) string(TOUPPER "${CMAKE_BUILD_TYPE}" BTYPE) set(N2P2_BUILD_FLAGS "${CMAKE_SHARED_LIBRARY_CXX_FLAGS} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BTYPE}} ${N2P2_CXX_STD}") set(N2P2_BUILD_OPTIONS INTERFACES=LAMMPS COMP=${N2P2_COMP} "PROJECT_OPTIONS=${N2P2_PROJECT_OPTIONS}" "PROJECT_DEBUG=" - "PROJECT_CC=${CMAKE_CXX_COMPILER}" "PROJECT_MPICC=${MPI_CXX_COMPILER}" "PROJECT_CFLAGS=${N2P2_BUILD_FLAGS}" - "PROJECT_AR=${N2P2_AR}") + "PROJECT_CC=${CMAKE_CXX_COMPILER}" "PROJECT_MPICC=${CMAKE_CXX_COMPILER}" "PROJECT_CFLAGS=${N2P2_BUILD_FLAGS}" + "PROJECT_AR=${N2P2_AR}" "APP_CORE=nnp-convert" "APP_TRAIN=nnp-train" "APP=nnp-convert") # echo final flag for debugging message(STATUS "N2P2 BUILD OPTIONS: ${N2P2_BUILD_OPTIONS}") diff --git a/doc/src/Developer_utils.rst b/doc/src/Developer_utils.rst index a9969b7543..7172f81eb7 100644 --- a/doc/src/Developer_utils.rst +++ b/doc/src/Developer_utils.rst @@ -205,6 +205,9 @@ Convenience functions .. doxygenfunction:: logmesg(LAMMPS *lmp, const std::string &mesg) :project: progguide +.. doxygenfunction:: flush_buffers(LAMMPS *lmp) + :project: progguide + .. doxygenfunction:: getsyserror :project: progguide diff --git a/doc/src/Library_utility.rst b/doc/src/Library_utility.rst index 32fac6bcc8..da64e3b8f0 100644 --- a/doc/src/Library_utility.rst +++ b/doc/src/Library_utility.rst @@ -13,6 +13,7 @@ functions. They do not directly call the LAMMPS library. - :cpp:func:`lammps_fix_external_set_virial_peratom` - :cpp:func:`lammps_fix_external_set_vector_length` - :cpp:func:`lammps_fix_external_set_vector` +- :cpp:func:`lammps_flush_buffers` - :cpp:func:`lammps_free` - :cpp:func:`lammps_is_running` - :cpp:func:`lammps_force_timeout` @@ -72,6 +73,11 @@ where such memory buffers were allocated that require the use of ----------------------- +.. doxygenfunction:: lammps_flush_buffers + :project: progguide + +----------------------- + .. doxygenfunction:: lammps_free :project: progguide diff --git a/lib/kokkos/core/src/desul/atomics/Generic.hpp b/lib/kokkos/core/src/desul/atomics/Generic.hpp index 9d5e87ece2..19d7a453fa 100644 --- a/lib/kokkos/core/src/desul/atomics/Generic.hpp +++ b/lib/kokkos/core/src/desul/atomics/Generic.hpp @@ -10,8 +10,10 @@ SPDX-License-Identifier: (BSD-3-Clause) #define DESUL_ATOMICS_GENERIC_HPP_ #include +#if defined(__GNUC__) && (!defined(__clang__)) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif #include "desul/atomics/Common.hpp" #include "desul/atomics/Compare_Exchange.hpp" #include "desul/atomics/Lock_Array.hpp" @@ -686,5 +688,7 @@ DESUL_INLINE_FUNCTION bool atomic_compare_exchange_weak(T* const dest, #include #include #include +#if defined(__GNUC__) && (!defined(__clang__)) #pragma GCC diagnostic pop #endif +#endif diff --git a/lib/kokkos/core/src/impl/Kokkos_Core.cpp b/lib/kokkos/core/src/impl/Kokkos_Core.cpp index a1f9d33632..00d6b5850b 100644 --- a/lib/kokkos/core/src/impl/Kokkos_Core.cpp +++ b/lib/kokkos/core/src/impl/Kokkos_Core.cpp @@ -58,7 +58,7 @@ #ifndef _WIN32 #include #else -#include +#include #endif //---------------------------------------------------------------------------- diff --git a/python/lammps/core.py b/python/lammps/core.py index 62b0f5d8b6..d934ee1baa 100644 --- a/python/lammps/core.py +++ b/python/lammps/core.py @@ -164,6 +164,7 @@ class lammps(object): 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_flush_buffers.argtypes = [c_void_p] self.lib.lammps_free.argtypes = [c_void_p] self.lib.lammps_file.argtypes = [c_void_p, c_char_p] @@ -1118,6 +1119,16 @@ class lammps(object): # ------------------------------------------------------------------------- + def flush_buffers(self): + """Flush output buffers + + This is a wrapper around the :cpp:func:`lammps_flush_buffers` + function of the C-library interface. + """ + self.lib.lammps_flush_buffers(self.lmp) + + # ------------------------------------------------------------------------- + def set_variable(self,name,value): """Set a new value for a LAMMPS string style variable diff --git a/python/lammps/numpy_wrapper.py b/python/lammps/numpy_wrapper.py index 20ec85d001..3619728081 100644 --- a/python/lammps/numpy_wrapper.py +++ b/python/lammps/numpy_wrapper.py @@ -92,7 +92,7 @@ class numpy_wrapper: 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", "x0", "omega", "angmom", "torque", "vforce", "vest"): + if name in ("x", "v", "f", "x0","omega", "angmom", "torque", "csforce", "vforce", "vest"): dim = 3 elif name == "smd_data_9": dim = 9 diff --git a/python/lammps/pylammps.py b/python/lammps/pylammps.py index abd4d6da98..cdb6620c27 100644 --- a/python/lammps/pylammps.py +++ b/python/lammps/pylammps.py @@ -20,47 +20,47 @@ from __future__ import print_function +import io import os import re -import select +import sys +import tempfile from collections import namedtuple from .core import lammps +# ------------------------------------------------------------------------- class OutputCapture(object): """ Utility class to capture LAMMPS library output """ - def __init__(self): - self.stdout_pipe_read, self.stdout_pipe_write = os.pipe() self.stdout_fd = 1 + self.captured_output = "" def __enter__(self): - self.stdout = os.dup(self.stdout_fd) - os.dup2(self.stdout_pipe_write, self.stdout_fd) + self.tmpfile = tempfile.TemporaryFile(mode='w+b') + + sys.stdout.flush() + + # make copy of original stdout + self.stdout_orig = os.dup(self.stdout_fd) + + # replace stdout and redirect to temp file + os.dup2(self.tmpfile.fileno(), self.stdout_fd) return self def __exit__(self, exc_type, exc_value, traceback): - os.dup2(self.stdout, self.stdout_fd) - os.close(self.stdout) - os.close(self.stdout_pipe_read) - os.close(self.stdout_pipe_write) - - # check if we have more to read from the pipe - def more_data(self, pipe): - r, _, _ = select.select([pipe], [], [], 0) - return bool(r) - - # read the whole pipe - def read_pipe(self, pipe): - out = "" - while self.more_data(pipe): - out += os.read(pipe, 1024).decode() - return out + os.dup2(self.stdout_orig, self.stdout_fd) + os.close(self.stdout_orig) + self.tmpfile.close() @property def output(self): - return self.read_pipe(self.stdout_pipe_read) + sys.stdout.flush() + self.tmpfile.flush() + self.tmpfile.seek(0, io.SEEK_SET) + self.captured_output = self.tmpfile.read().decode('utf-8') + return self.captured_output # ------------------------------------------------------------------------- @@ -109,9 +109,9 @@ class AtomList(object): """ if index not in self._loaded: if self.dimensions == 2: - atom = Atom2D(self._pylmp, index + 1) + atom = Atom2D(self._pylmp, index) else: - atom = Atom(self._pylmp, index + 1) + atom = Atom(self._pylmp, index) self._loaded[index] = atom return self._loaded[index] @@ -134,6 +134,12 @@ class Atom(object): def __dir__(self): return [k for k in super().__dir__() if not k.startswith('_')] + def get(self, name, index): + prop = self._pylmp.lmp.numpy.extract_atom(name) + if prop is not None: + return prop[index] + return None + @property def id(self): """ @@ -141,7 +147,7 @@ class Atom(object): :type: int """ - return int(self._pylmp.eval("id[%d]" % self.index)) + return self.get("id", self.index) @property def type(self): @@ -150,7 +156,7 @@ class Atom(object): :type: int """ - return int(self._pylmp.eval("type[%d]" % self.index)) + return self.get("type", self.index) @property def mol(self): @@ -159,7 +165,7 @@ class Atom(object): :type: int """ - return self._pylmp.eval("mol[%d]" % self.index) + return self.get("mol", self.index) @property def mass(self): @@ -168,52 +174,114 @@ class Atom(object): :type: float """ - return self._pylmp.eval("mass[%d]" % self.index) + return self.get("mass", self.index) + + @property + def radius(self): + """ + Return the particle radius + + :type: float + """ + return self.get("radius", self.index) @property def position(self): """ :getter: Return position of atom :setter: Set position of atom - :type: tuple (float, float, float) + :type: numpy.array (float, float, float) """ - return (self._pylmp.eval("x[%d]" % self.index), - self._pylmp.eval("y[%d]" % self.index), - self._pylmp.eval("z[%d]" % self.index)) + return self.get("x", self.index) @position.setter def position(self, value): - """ - :getter: Return velocity of atom - :setter: Set velocity of atom - :type: tuple (float, float, float) - """ - self._pylmp.set("atom", self.index, "x", value[0]) - self._pylmp.set("atom", self.index, "y", value[1]) - self._pylmp.set("atom", self.index, "z", value[2]) + current = self.position + current[:] = value @property def velocity(self): - return (self._pylmp.eval("vx[%d]" % self.index), - self._pylmp.eval("vy[%d]" % self.index), - self._pylmp.eval("vz[%d]" % self.index)) + """ + :getter: Return velocity of atom + :setter: Set velocity of atom + :type: numpy.array (float, float, float) + """ + return self.get("v", self.index) @velocity.setter def velocity(self, value): - self._pylmp.set("atom", self.index, "vx", value[0]) - self._pylmp.set("atom", self.index, "vy", value[1]) - self._pylmp.set("atom", self.index, "vz", value[2]) + current = self.velocity + current[:] = value @property def force(self): """ Return the total force acting on the atom - :type: tuple (float, float, float) + :type: numpy.array (float, float, float) """ - return (self._pylmp.eval("fx[%d]" % self.index), - self._pylmp.eval("fy[%d]" % self.index), - self._pylmp.eval("fz[%d]" % self.index)) + return self.get("f", self.index) + + @force.setter + def force(self, value): + current = self.force + current[:] = value + + @property + def torque(self): + """ + Return the total torque acting on the atom + + :type: numpy.array (float, float, float) + """ + return self.get("torque", self.index) + + @force.setter + def torque(self, value): + current = self.torque + current[:] = value + + @property + def omega(self): + """ + Return the rotational velocity of the particle + + :type: numpy.array (float, float, float) + """ + return self.get("torque", self.index) + + @omega.setter + def omega(self, value): + current = self.torque + current[:] = value + + @property + def torque(self): + """ + Return the total torque acting on the particle + + :type: numpy.array (float, float, float) + """ + return self.get("torque", self.index) + + @torque.setter + def torque(self, value): + current = self.torque + current[:] = value + + @property + def angular_momentum(self): + """ + Return the angular momentum of the particle + + :type: numpy.array (float, float, float) + """ + return self.get("angmom", self.index) + + @angular_momentum.setter + def angular_momentum(self, value): + current = self.angular_momentum + current[:] = value @property def charge(self): @@ -222,7 +290,7 @@ class Atom(object): :type: float """ - return self._pylmp.eval("q[%d]" % self.index) + return self.get("q", self.index) # ------------------------------------------------------------------------- @@ -244,39 +312,42 @@ class Atom2D(Atom): :getter: Return position of atom :setter: Set position of atom - :type: tuple (float, float) + :type: numpy.array (float, float) """ - return (self._pylmp.eval("x[%d]" % self.index), - self._pylmp.eval("y[%d]" % self.index)) + return super(Atom2D, self).position[0:2] @position.setter def position(self, value): - self._pylmp.set("atom", self.index, "x", value[0]) - self._pylmp.set("atom", self.index, "y", value[1]) + current = self.position + current[:] = value @property def velocity(self): """Access to velocity of an atom :getter: Return velocity of atom :setter: Set velocity of atom - :type: tuple (float, float) + :type: numpy.array (float, float) """ - return (self._pylmp.eval("vx[%d]" % self.index), - self._pylmp.eval("vy[%d]" % self.index)) + return super(Atom2D, self).velocity[0:2] @velocity.setter def velocity(self, value): - self._pylmp.set("atom", self.index, "vx", value[0]) - self._pylmp.set("atom", self.index, "vy", value[1]) + current = self.velocity + current[:] = value @property def force(self): """Access to force of an atom - - :type: tuple (float, float) + :getter: Return force of atom + :setter: Set force of atom + :type: numpy.array (float, float) """ - return (self._pylmp.eval("fx[%d]" % self.index), - self._pylmp.eval("fy[%d]" % self.index)) + return super(Atom2D, self).force[0:2] + + @force.setter + def force(self, value): + current = self.force + current[:] = value # ------------------------------------------------------------------------- @@ -541,7 +612,8 @@ class PyLammps(object): :getter: Returns an object with properties storing the current system state :type: namedtuple """ - output = self.info("system") + output = self.lmp_info("system") + output = output[output.index("System information:")+1:] d = self._parse_info_system(output) return namedtuple('System', d.keys())(*d.values()) @@ -553,7 +625,8 @@ class PyLammps(object): :getter: Returns an object with properties storing the current communication state :type: namedtuple """ - output = self.info("communication") + output = self.lmp_info("communication") + output = output[output.index("Communication information:")+1:] d = self._parse_info_communication(output) return namedtuple('Communication', d.keys())(*d.values()) @@ -565,7 +638,8 @@ class PyLammps(object): :getter: Returns a list of computes that are currently active in this LAMMPS instance :type: list """ - output = self.info("computes") + output = self.lmp_info("computes") + output = output[output.index("Compute information:")+1:] return self._parse_element_list(output) @property @@ -576,7 +650,8 @@ class PyLammps(object): :getter: Returns a list of dumps that are currently active in this LAMMPS instance :type: list """ - output = self.info("dumps") + output = self.lmp_info("dumps") + output = output[output.index("Dump information:")+1:] return self._parse_element_list(output) @property @@ -587,7 +662,8 @@ class PyLammps(object): :getter: Returns a list of fixes that are currently active in this LAMMPS instance :type: list """ - output = self.info("fixes") + output = self.lmp_info("fixes") + output = output[output.index("Fix information:")+1:] return self._parse_element_list(output) @property @@ -598,7 +674,8 @@ class PyLammps(object): :getter: Returns a list of atom groups that are currently active in this LAMMPS instance :type: list """ - output = self.info("groups") + output = self.lmp_info("groups") + output = output[output.index("Group information:")+1:] return self._parse_groups(output) @property @@ -609,11 +686,12 @@ class PyLammps(object): :getter: Returns a dictionary of all variables that are defined in this LAMMPS instance :type: dict """ - output = self.info("variables") - vars = {} + output = self.lmp_info("variables") + output = output[output.index("Variable information:")+1:] + variables = {} for v in self._parse_element_list(output): - vars[v['name']] = Variable(self, v['name'], v['style'], v['def']) - return vars + variables[v['name']] = Variable(self, v['name'], v['style'], v['def']) + return variables def eval(self, expr): """ @@ -638,10 +716,9 @@ class PyLammps(object): return [x.strip() for x in value.split('=')] def _parse_info_system(self, output): - lines = output[5:-2] system = {} - for line in lines: + for line in output: if line.startswith("Units"): system['units'] = self._get_pair(line)[1] elif line.startswith("Atom style"): @@ -699,10 +776,9 @@ class PyLammps(object): return system def _parse_info_communication(self, output): - lines = output[5:-3] comm = {} - for line in lines: + for line in output: if line.startswith("MPI library"): comm['mpi_version'] = line.split(':')[1].strip() elif line.startswith("Comm style"): @@ -720,10 +796,10 @@ class PyLammps(object): return comm def _parse_element_list(self, output): - lines = output[5:-3] elements = [] - for line in lines: + for line in output: + if not line or (":" not in line): continue element_info = self._split_values(line.split(':')[1].strip()) element = {'name': element_info[0]} for key, value in [self._get_pair(x) for x in element_info[1:]]: @@ -732,11 +808,10 @@ class PyLammps(object): return elements def _parse_groups(self, output): - lines = output[5:-3] groups = [] group_pattern = re.compile(r"(?P.+) \((?P.+)\)") - for line in lines: + for line in output: m = group_pattern.match(line.split(':')[1].strip()) group = {'name': m.group('name'), 'type': m.group('type')} groups.append(group) @@ -747,7 +822,7 @@ class PyLammps(object): return self.__getattr__("print")(s) def __dir__(self): - return ['angle_coeff', 'angle_style', 'atom_modify', 'atom_style', 'atom_style', + return sorted(set(['angle_coeff', 'angle_style', 'atom_modify', 'atom_style', 'atom_style', 'bond_coeff', 'bond_style', 'boundary', 'change_box', 'communicate', 'compute', 'create_atoms', 'create_box', 'delete_atoms', 'delete_bonds', 'dielectric', 'dihedral_coeff', 'dihedral_style', 'dimension', 'dump', 'fix', 'fix_modify', @@ -757,7 +832,16 @@ class PyLammps(object): 'pair_style', 'processors', 'read', 'read_data', 'read_restart', 'region', 'replicate', 'reset_timestep', 'restart', 'run', 'run_style', 'thermo', 'thermo_modify', 'thermo_style', 'timestep', 'undump', 'unfix', 'units', - 'variable', 'velocity', 'write_restart'] + 'variable', 'velocity', 'write_restart'] + self.lmp.available_styles("command"))) + + def lmp_info(self, s): + # skip anything before and after Info-Info-Info + # also skip timestamp line + output = self.__getattr__("info")(s) + indices = [index for index, line in enumerate(output) if line.startswith("Info-Info-Info-Info")] + start = indices[0] + end = indices[1] + return [line for line in output[start+2:end] if line] def __getattr__(self, name): """ @@ -773,10 +857,12 @@ class PyLammps(object): """ def handler(*args, **kwargs): cmd_args = [name] + [str(x) for x in args] + self.lmp.flush_buffers() with OutputCapture() as capture: cmd = ' '.join(cmd_args) self.command(cmd) + self.lmp.flush_buffers() output = capture.output comm = self.lmp.get_mpi_comm() diff --git a/src/KOKKOS/kokkos.cpp b/src/KOKKOS/kokkos.cpp index e26513a122..0c730fda65 100644 --- a/src/KOKKOS/kokkos.cpp +++ b/src/KOKKOS/kokkos.cpp @@ -600,7 +600,8 @@ void KokkosLMP::my_signal_handler(int sig) { if (sig == SIGSEGV) { #if defined(_WIN32) - kill(_getpid(),SIGABRT); + // there is no kill() function on Windows + exit(1); #else kill(getpid(),SIGABRT); #endif diff --git a/src/KOKKOS/pair_exp6_rx_kokkos.cpp b/src/KOKKOS/pair_exp6_rx_kokkos.cpp index 0988cb4910..d96e9f3f65 100644 --- a/src/KOKKOS/pair_exp6_rx_kokkos.cpp +++ b/src/KOKKOS/pair_exp6_rx_kokkos.cpp @@ -17,9 +17,7 @@ ------------------------------------------------------------------------- */ #include "pair_exp6_rx_kokkos.h" -#include -#include #include "atom.h" #include "comm.h" #include "force.h" @@ -28,12 +26,14 @@ #include "memory_kokkos.h" #include "error.h" #include "fix.h" -#include #include "atom_masks.h" #include "neigh_request.h" #include "atom_kokkos.h" #include "kokkos.h" +#include +#include +#include #ifdef _OPENMP #include @@ -57,22 +57,6 @@ using namespace MathSpecialKokkos; #define exp6PotentialType (1) #define isExp6PotentialType(_type) ( (_type) == exp6PotentialType ) -namespace /* anonymous */ -{ - -//typedef double TimerType; -//TimerType getTimeStamp(void) { return platform::walltime(); } -//double getElapsedTime( const TimerType &t0, const TimerType &t1) { return t1-t0; } - -typedef struct timespec TimerType; -TimerType getTimeStamp(void) { TimerType tick; clock_gettime( CLOCK_MONOTONIC, &tick); return tick; } -double getElapsedTime( const TimerType &t0, const TimerType &t1) -{ - return (t1.tv_sec - t0.tv_sec) + 1e-9*(t1.tv_nsec - t0.tv_nsec); -} - -} // end namespace - /* ---------------------------------------------------------------------- */ template @@ -142,8 +126,6 @@ void PairExp6rxKokkos::init_style() template void PairExp6rxKokkos::compute(int eflag_in, int vflag_in) { - //TimerType t_start = getTimeStamp(); - copymode = 1; eflag = eflag_in; @@ -187,7 +169,6 @@ void PairExp6rxKokkos::compute(int eflag_in, int vflag_in) // and ghost atoms. Make the parameter data persistent // and exchange like any other atom property later. - //TimerType t_mix_start = getTimeStamp(); { const int np_total = nlocal + atom->nghost; @@ -260,7 +241,6 @@ void PairExp6rxKokkos::compute(int eflag_in, int vflag_in) error->all(FLERR,"Computed fraction less than -10*DBL_EPSILON"); #endif } - //TimerType t_mix_stop = getTimeStamp(); k_error_flag.template modify(); k_error_flag.template sync(); @@ -377,9 +357,6 @@ void PairExp6rxKokkos::compute(int eflag_in, int vflag_in) } copymode = 0; - - //TimerType t_stop = getTimeStamp(); - //printf("PairExp6rxKokkos::compute %f %f\n", getElapsedTime(t_start, t_stop), getElapsedTime(t_mix_start, t_mix_stop)); } template diff --git a/src/KOKKOS/pppm_kokkos.cpp b/src/KOKKOS/pppm_kokkos.cpp index da18bba001..a5073129e8 100644 --- a/src/KOKKOS/pppm_kokkos.cpp +++ b/src/KOKKOS/pppm_kokkos.cpp @@ -2711,10 +2711,9 @@ void PPPMKokkos::compute_rho_coeff() { int j,k,l,m; FFT_SCALAR s; - - //FFT_SCALAR **a; - //memory->create2d_offset(a,order,-order,order,"pppm:a"); - FFT_SCALAR a[order][2*order+1]; + FFT_SCALAR **a = new FFT_SCALAR *[order]; + for (int i = 0; i < order; ++i) + a[i] = new FFT_SCALAR[2*order+1]; for (k = 0; k <= 2*order; k++) for (l = 0; l < order; l++) @@ -2744,7 +2743,9 @@ void PPPMKokkos::compute_rho_coeff() h_rho_coeff(l,m-(1-order)/2) = a[l][k+order]; m++; } - //memory->destroy2d_offset(a,-order); + for (int i = 0; i < order; ++i) + delete[] a[i]; + delete[] a; } /* ---------------------------------------------------------------------- diff --git a/src/dump.cpp b/src/dump.cpp index 46622c2efa..f11f21f71e 100644 --- a/src/dump.cpp +++ b/src/dump.cpp @@ -952,76 +952,67 @@ void Dump::balance() int nswap = 0; MPI_Request *request = new MPI_Request[nprocs]; - int procstart = 0; - int iproc = me; - int iproc_prev; - for (int i = 0; i < nme_balance; i++) { + // find which proc starting atom belongs to - // find which proc this atom belongs to + int startproc = me; + while (proc_new_offsets[me] < proc_offsets[startproc]) startproc--; + while (proc_new_offsets[me] > proc_offsets[startproc+1]-1) startproc++; - while (proc_new_offsets[me] + i < proc_offsets[iproc]) iproc--; - while (proc_new_offsets[me] + i > proc_offsets[iproc+1]-1) iproc++; + // find which proc ending atom belongs to - if (i != 0 && (iproc != iproc_prev || i == nme_balance - 1)) { + int endproc = me; + while (proc_new_offsets[me] + nme_balance-1 < proc_offsets[endproc]) endproc--; + while (proc_new_offsets[me] + nme_balance-1 > proc_offsets[endproc+1]-1) endproc++; - // finished with proc + // loop over procs - int procrecv = iproc; - if (iproc != iproc_prev) procrecv = iproc_prev; + for (int iproc = startproc; iproc <= endproc; iproc++) { + int istart = MAX(0, proc_offsets[iproc] - proc_new_offsets[me]); + int iend = MIN(nme_balance-1, proc_offsets[iproc+1]-1 - proc_new_offsets[me]); + int nrecv = iend - istart + 1; + if (nrecv == 0) continue; - int procnrecv = i - procstart + 1; - if (iproc != iproc_prev) procnrecv--; + // post receive for this proc - // post receive for this proc - - if (iproc_prev != me) - MPI_Irecv(&buf_balance[procstart*size_one],procnrecv*size_one,MPI_DOUBLE, - procrecv,0,world,&request[nswap++]); - - procstart = i; - } - - iproc_prev = iproc; + if (iproc != me) + MPI_Irecv(&buf_balance[istart*size_one],nrecv*size_one,MPI_DOUBLE, + iproc,0,world,&request[nswap++]); } // compute which atoms I am sending and to which procs - procstart = 0; - iproc = me; - for (int i = 0; i < nme; i++) { + // find which proc starting atom belongs to - // find which proc this atom should belong to + startproc = me; + while (proc_offsets[me] < proc_new_offsets[startproc]) startproc--; + while (proc_offsets[me] > proc_new_offsets[startproc+1]-1) startproc++; - while (proc_offsets[me] + i < proc_new_offsets[iproc]) iproc--; - while (proc_offsets[me] + i > proc_new_offsets[iproc+1] - 1) iproc++; + // find which proc ending atom belongs to - if (i != 0 && (iproc != iproc_prev || i == nme - 1)) { + endproc = me; + while (proc_offsets[me] + nme-1 < proc_new_offsets[endproc]) endproc--; + while (proc_offsets[me] + nme-1 > proc_new_offsets[endproc+1]-1) endproc++; - // finished with proc + // loop over procs - int procsend = iproc; - if (iproc != iproc_prev) procsend = iproc_prev; + for (int iproc = startproc; iproc <= endproc; iproc++) { + int istart = MAX(0,proc_new_offsets[iproc] - proc_offsets[me]); + int iend = MIN(nme-1,proc_new_offsets[iproc+1]-1 - proc_offsets[me]); + int nsend = iend - istart + 1; + if (nsend == 0) continue; - int procnsend = i - procstart + 1; - if (iproc != iproc_prev) procnsend--; + // send for this proc - // send for this proc + if (iproc != me) { + MPI_Send(&buf[istart*size_one],nsend*size_one,MPI_DOUBLE,iproc,0,world); + } else { - if (iproc_prev != me) { - MPI_Send(&buf[procstart*size_one],procnsend*size_one,MPI_DOUBLE,procsend,0,world); - } else { + // sending to self, copy buffers - // sending to self, copy buffers - - int offset_me = proc_offsets[me] - proc_new_offsets[me]; - memcpy(&buf_balance[(offset_me + procstart)*size_one],&buf[procstart*size_one],sizeof(double)*procnsend*size_one); - } - - procstart = i; + int offset_me = proc_offsets[me] - proc_new_offsets[me]; + memcpy(&buf_balance[(offset_me + istart)*size_one],&buf[istart*size_one],sizeof(double)*nsend*size_one); } - - iproc_prev = iproc; } // wait for all recvs diff --git a/src/error.cpp b/src/error.cpp index e2162cf661..912093c865 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -213,8 +213,7 @@ void Error::one(const std::string &file, int line, const std::string &str) throw LAMMPSAbortException(mesg, world); #else - if (screen) fflush(screen); - if (logfile) fflush(logfile); + utils::flush_buffers(lmp); KokkosLMP::finalize(); MPI_Abort(world,1); exit(1); // to trick "smart" compilers into believing this does not return diff --git a/src/library.cpp b/src/library.cpp index a27da0d478..8c6ee5e774 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -5439,6 +5439,21 @@ void lammps_fix_external_set_vector(void *handle, const char *id, int idx, doubl /* ---------------------------------------------------------------------- */ +/** Flush output buffers + +\verbatim embed:rst +This function can be used to force output to be written to screen and logfiles +to simplify capturing output from LAMMPS library calls. +\endverbatim + * + * \param handle pointer to a previously created LAMMPS instance cast to ``void *``. + */ +void lammps_flush_buffers(void *handle) { + utils::flush_buffers((LAMMPS *) handle); +} + +/* ---------------------------------------------------------------------- */ + /** Free memory buffer allocated by LAMMPS. * \verbatim embed:rst diff --git a/src/library.h b/src/library.h index 1605267818..94fd7f7380 100644 --- a/src/library.h +++ b/src/library.h @@ -246,6 +246,8 @@ void lammps_fix_external_set_virial_peratom(void *handle, const char *id, double void lammps_fix_external_set_vector_length(void *handle, const char *id, int len); void lammps_fix_external_set_vector(void *handle, const char *id, int idx, double val); +void lammps_flush_buffers(void *ptr); + void lammps_free(void *ptr); int lammps_is_running(void *handle); diff --git a/src/thermo.cpp b/src/thermo.cpp index e39d7d7c57..27d74c58b6 100644 --- a/src/thermo.cpp +++ b/src/thermo.cpp @@ -375,8 +375,7 @@ void Thermo::compute(int flag) if (me == 0) { utils::logmesg(lmp,line); - if (screen && flushflag) fflush(screen); - if (logfile && flushflag) fflush(logfile); + if (flushflag) utils::flush_buffers(lmp); } // set to 1, so that subsequent invocations of CPU time will be non-zero diff --git a/src/utils.cpp b/src/utils.cpp index ca2a0c4f5b..c60908a2f2 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -24,6 +24,7 @@ #include "text_file_reader.h" #include "tokenizer.h" #include "update.h" +#include "universe.h" #include #include @@ -138,6 +139,14 @@ void utils::fmtargs_logmesg(LAMMPS *lmp, fmt::string_view format, fmt::format_ar } } +void utils::flush_buffers(LAMMPS *lmp) +{ + if (lmp->screen) fflush(lmp->screen); + if (lmp->logfile) fflush(lmp->logfile); + if (lmp->universe->uscreen) fflush(lmp->universe->uscreen); + if (lmp->universe->ulogfile) fflush(lmp->universe->ulogfile); +} + /* define this here, so we won't have to include the headers everywhere and utils.h will more likely be included anyway. */ diff --git a/src/utils.h b/src/utils.h index 47a4ace5f9..425fbfe0c1 100644 --- a/src/utils.h +++ b/src/utils.h @@ -74,6 +74,14 @@ namespace utils { void logmesg(LAMMPS *lmp, const std::string &mesg); + /*! Flush output buffers + * + * This function calls fflush on screen and logfile FILE pointers + * if available + */ + + void flush_buffers(LAMMPS *lmp); + /*! Return a string representing the current system error status * * This is a wrapper around calling strerror(errno). diff --git a/tools/swig/lammps.i b/tools/swig/lammps.i index 4d0d52f779..fb4322af34 100644 --- a/tools/swig/lammps.i +++ b/tools/swig/lammps.i @@ -156,6 +156,8 @@ extern int lammps_is_running(void *handle); extern void lammps_force_timeout(void *handle); extern int lammps_has_error(void *handle); extern int lammps_get_last_error_message(void *handle, char *buffer, int buf_size); + +extern void lammps_flush_buffers(void *ptr); %} enum _LMP_DATATYPE_CONST { @@ -287,4 +289,6 @@ extern void lammps_force_timeout(void *handle); extern int lammps_has_error(void *handle); extern int lammps_get_last_error_message(void *handle, char *buffer, int buf_size); -/* last revised on 21 July 2021 */ +extern void lammps_flush_buffers(void *ptr); + +/* last revised on 4 February 2022 */ diff --git a/unittest/cplusplus/test_lammps_class.cpp b/unittest/cplusplus/test_lammps_class.cpp index 6c733a31e4..4885cffbe2 100644 --- a/unittest/cplusplus/test_lammps_class.cpp +++ b/unittest/cplusplus/test_lammps_class.cpp @@ -261,13 +261,13 @@ protected: int argc = sizeof(args) / sizeof(char *); // only run this test fixture with kk suffix if KOKKOS package is installed - // also need to figure out a way to find which parallelizations are enabled if (LAMMPS::is_installed_pkg("KOKKOS")) { ::testing::internal::CaptureStdout(); lmp = new LAMMPS(argc, argv, MPI_COMM_WORLD); std::string output = ::testing::internal::GetCapturedStdout(); - EXPECT_THAT(output, StartsWith("Kokkos::OpenMP::")); + if (Info::has_accelerator_feature("KOKKOS", "api", "openmp")) + EXPECT_THAT(output, StartsWith("Kokkos::OpenMP::")); } else GTEST_SKIP(); } diff --git a/unittest/force-styles/tests/atomic-pair-reaxff_tabulate.yaml b/unittest/force-styles/tests/atomic-pair-reaxff_tabulate.yaml index 5acd407191..b8d662829b 100644 --- a/unittest/force-styles/tests/atomic-pair-reaxff_tabulate.yaml +++ b/unittest/force-styles/tests/atomic-pair-reaxff_tabulate.yaml @@ -1,6 +1,6 @@ --- lammps_version: 30 Jul 2021 -tags: slow, unstable +tags: slow, unstable, noWindows date_generated: Mon Aug 23 20:32:05 2021 epsilon: 1e-12 skip_tests: diff --git a/unittest/python/CMakeLists.txt b/unittest/python/CMakeLists.txt index 7f9b5e71b2..3fc0f6ba58 100644 --- a/unittest/python/CMakeLists.txt +++ b/unittest/python/CMakeLists.txt @@ -98,14 +98,10 @@ if(Python_EXECUTABLE) WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set_tests_properties(PythonCapabilities PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") -if(CMAKE_SYSTEM_NAME STREQUAL "Windows") - message(STATUS "Skipping Tests for PyLammps Module: not yet ported to Windows") -else() add_test(NAME PythonPyLammps COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-pylammps.py -v WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set_tests_properties(PythonPyLammps PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") -endif() add_test(NAME PythonFormats COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-formats.py -v diff --git a/unittest/python/python-pylammps.py b/unittest/python/python-pylammps.py index 6294972ab4..2b92f82248 100644 --- a/unittest/python/python-pylammps.py +++ b/unittest/python/python-pylammps.py @@ -1,6 +1,13 @@ -import sys,os,unittest +import os,unittest from lammps import PyLammps +try: + import numpy + NUMPY_INSTALLED = True +except ImportError: + NUMPY_INSTALLED = False + +@unittest.skipIf(not NUMPY_INSTALLED, "numpy is not available") class PythonPyLammps(unittest.TestCase): def setUp(self): machine = None @@ -49,8 +56,8 @@ class PythonPyLammps(unittest.TestCase): self.assertEqual(self.pylmp.lmp.create_atoms(2, id=None, type=types, x=x), 2) self.assertEqual(self.pylmp.system.natoms, 2) self.assertEqual(len(self.pylmp.atoms), 2) - self.assertEqual(self.pylmp.atoms[0].position, tuple(x[0:3])) - self.assertEqual(self.pylmp.atoms[1].position, tuple(x[3:6])) + numpy.testing.assert_array_equal(self.pylmp.atoms[0].position, tuple(x[0:3])) + numpy.testing.assert_array_equal(self.pylmp.atoms[1].position, tuple(x[3:6])) self.assertEqual(self.pylmp.last_run, None)