From f93281d86862613d7694ca280b4b9af59652b1a6 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 12 Sep 2024 23:49:48 -0400 Subject: [PATCH 01/26] Implement CMake upgrade and C++ standard deprecation as we did with C++11 --- cmake/CMakeLists.txt | 18 +++++++++++++++--- src/MAKE/Makefile.mpi | 6 +++--- src/MAKE/Makefile.serial | 6 +++--- src/lmptype.h | 7 +++++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index c68a925324..b10823aba4 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -2,7 +2,7 @@ ######################################## # CMake build system # This file is part of LAMMPS -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.20) ######################################## # set policy to silence warnings about ignoring _ROOT but use it if(POLICY CMP0074) @@ -144,16 +144,28 @@ if((PKG_KOKKOS) AND (Kokkos_ENABLE_CUDA) AND NOT (CMAKE_CXX_COMPILER_ID STREQUAL set(CMAKE_TUNE_DEFAULT "${CMAKE_TUNE_DEFAULT} -Xcudafe --diag_suppress=unrecognized_pragma") endif() -# we require C++11 without extensions. Kokkos requires at least C++17 (currently) +# we *require* C++11 without extensions but prefer C++17. +# Kokkos requires at least C++17 (currently) if(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) + if(cxx_std_17 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + set(CMAKE_CXX_STANDARD 17) + else() + set(CMAKE_CXX_STANDARD 11) + endif() endif() if(CMAKE_CXX_STANDARD LESS 11) message(FATAL_ERROR "C++ standard must be set to at least 11") endif() +if(CMAKE_CXX_STANDARD LESS 17) + message(WARNING "Selecting C++17 standard is preferred over C++${CMAKE_CXX_STANDARD}") +endif() if(PKG_KOKKOS AND (CMAKE_CXX_STANDARD LESS 17)) set(CMAKE_CXX_STANDARD 17) endif() +# turn off C++17 check in lmptype.h +if(LAMMPS_CXX11) + add_compile_definitions(LAMMPS_CXX11) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL "Use compiler extensions") # ugly hacks for MSVC which by default always reports an old C++ standard in the __cplusplus macro diff --git a/src/MAKE/Makefile.mpi b/src/MAKE/Makefile.mpi index 9cd451a32c..8b21412e46 100644 --- a/src/MAKE/Makefile.mpi +++ b/src/MAKE/Makefile.mpi @@ -7,12 +7,12 @@ SHELL = /bin/sh # specify flags and libraries needed for your compiler CC = mpicxx -CCFLAGS = -g -O3 -std=c++11 +CCFLAGS = -g -O3 # -std=c++17 SHFLAGS = -fPIC DEPFLAGS = -M LINK = mpicxx -LINKFLAGS = -g -O3 -std=c++11 +LINKFLAGS = -g -O3 # -std=c++17 LIB = SIZE = size @@ -28,7 +28,7 @@ SHLIBFLAGS = -shared -rdynamic # LAMMPS ifdef settings # see possible settings in Section 3.5 of the manual -LMP_INC = -DLAMMPS_GZIP -DLAMMPS_MEMALIGN=64 # -DLAMMPS_CXX98 +LMP_INC = -DLAMMPS_GZIP -DLAMMPS_MEMALIGN=64 # -DLAMMPS_CXX11 # MPI library # see discussion in Section 3.4 of the manual diff --git a/src/MAKE/Makefile.serial b/src/MAKE/Makefile.serial index f588922a1f..a7406b7a6d 100644 --- a/src/MAKE/Makefile.serial +++ b/src/MAKE/Makefile.serial @@ -7,12 +7,12 @@ SHELL = /bin/sh # specify flags and libraries needed for your compiler CC = g++ -CCFLAGS = -g -O3 -std=c++11 +CCFLAGS = -g -O3 # -std=c++17 SHFLAGS = -fPIC DEPFLAGS = -M LINK = g++ -LINKFLAGS = -g -O -std=c++11 +LINKFLAGS = -g -O # -std=c++17 LIB = SIZE = size @@ -28,7 +28,7 @@ SHLIBFLAGS = -shared -rdynamic # LAMMPS ifdef settings # see possible settings in Section 3.5 of the manual -LMP_INC = -DLAMMPS_GZIP -DLAMMPS_MEMALIGN=64 # -DLAMMPS_CXX98 +LMP_INC = -DLAMMPS_GZIP -DLAMMPS_MEMALIGN=64 # -DLAMMPS_CXX11 # MPI library # see discussion in Section 3.4 of the manual diff --git a/src/lmptype.h b/src/lmptype.h index d2181c9898..d7ed016c8f 100644 --- a/src/lmptype.h +++ b/src/lmptype.h @@ -34,6 +34,13 @@ #error LAMMPS requires a C++11 (or later) compliant compiler. Enable C++11 compatibility or upgrade the compiler. #endif +// C++17 check +#ifndef LAMMPS_CXX11 +#if __cplusplus < 201703L +#error LAMMPS is planning to transition to C++17. To disable this error please use a C++17 compliant compiler, enable C++17 support, or define -DLAMMPS_CXX11 in your makefile or when running cmake +#endif +#endif + #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS #endif From 0729c04dc14fd009b46d67bedacd5c205706f176 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 13 Sep 2024 22:38:20 -0400 Subject: [PATCH 02/26] document that GNU make build support is no longer required for new contributions --- doc/src/Modify_requirements.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/src/Modify_requirements.rst b/doc/src/Modify_requirements.rst index cbcb3eca13..c3e514a423 100644 --- a/doc/src/Modify_requirements.rst +++ b/doc/src/Modify_requirements.rst @@ -208,20 +208,21 @@ Build system (strict) LAMMPS currently supports two build systems: one that is based on :doc:`traditional Makefiles ` and one that is based on -:doc:`CMake `. Therefore, your contribution must be -compatible with and support both build systems. +:doc:`CMake `. As of fall 2024, it is no longer required +to support the traditional make build system. New packages may choose +to only support building with CMake. Additions to existing packages +must follow the requirements set by that package. For a single pair of header and implementation files that are an independent feature, it is usually only required to add them to ``src/.gitignore``. For traditional make, if your contributed files or package depend on -other LAMMPS style files or packages also being installed -(e.g. because your file is a derived class from the other LAMMPS -class), then an ``Install.sh`` file is also needed to check for those -dependencies and modifications to ``src/Depend.sh`` to trigger the checks. -See other README and Install.sh files in other directories as -examples. +other LAMMPS style files or packages also being installed (e.g. because +your file is a derived class from the other LAMMPS class), then an +``Install.sh`` file is also needed to check for those dependencies and +modifications to ``src/Depend.sh`` to trigger the checks. See other +README and Install.sh files in other directories as examples. Similarly, for CMake support, changes may need to be made to ``cmake/CMakeLists.txt``, some of the files in ``cmake/presets``, and From 3bed50c1c3192569e7be1d0390ed8c3ea1bb33a9 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 18 Sep 2024 15:18:41 -0400 Subject: [PATCH 03/26] Add text that we favor now CMake based builds --- doc/src/Build.rst | 12 ++++++++---- doc/src/Build_cmake.rst | 14 +++++++------- doc/src/Build_make.rst | 4 ++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/doc/src/Build.rst b/doc/src/Build.rst index 7cf2a01992..7ca8cd428e 100644 --- a/doc/src/Build.rst +++ b/doc/src/Build.rst @@ -1,10 +1,14 @@ Build LAMMPS ============ -LAMMPS is built as a library and an executable from source code using -either traditional makefiles for use with GNU make (which may require -manual editing), or using a build environment generated by CMake (Unix -Makefiles, Ninja, Xcode, Visual Studio, KDevelop, CodeBlocks and more). +LAMMPS is built as a library and an executable from source code using a +build environment generated by CMake (Unix Makefiles, Ninja, Xcode, +Visual Studio, KDevelop, CodeBlocks and more depending on the platform). +Using CMake is the preferred way to build LAMMPS. In addition, LAMMPS +can be compiled using the legacy build system based on traditional +makefiles for use with GNU make (which may require manual editing). +Support for the legacy build system is slowly being phased out and may +not be available for all optional features. As an alternative, you can download a package with pre-built executables or automated build trees, as described in the :doc:`Install ` diff --git a/doc/src/Build_cmake.rst b/doc/src/Build_cmake.rst index 1b2bef936e..5169f1039e 100644 --- a/doc/src/Build_cmake.rst +++ b/doc/src/Build_cmake.rst @@ -16,7 +16,7 @@ environments is on a :doc:`separate page `. .. note:: - LAMMPS currently requires that CMake version 3.16 or later is available. + LAMMPS currently requires that CMake version 3.20 or later is available. .. warning:: @@ -32,11 +32,11 @@ environments is on a :doc:`separate page `. Advantages of using CMake ^^^^^^^^^^^^^^^^^^^^^^^^^ -CMake is an alternative to compiling LAMMPS in the traditional way -through :doc:`(manually customized) makefiles `. Using -CMake has multiple advantages that are specifically helpful for -people with limited experience in compiling software or for people -that want to modify or extend LAMMPS. +CMake is the preferred way of compiling LAMMPS in contrast to the legacy +build system based on GNU make and through :doc:`(manually customized) +makefiles `. Using CMake has multiple advantages that are +specifically helpful for people with limited experience in compiling +software or for people that want to modify or extend LAMMPS. - CMake can detect available hardware, tools, features, and libraries and adapt the LAMMPS default build configuration accordingly. @@ -47,7 +47,7 @@ that want to modify or extend LAMMPS. knowledge of file formats or complex command line syntax is required. - All enabled components are compiled in a single build operation. - Automated dependency tracking for all files and configuration options. -- Support for true out-of-source compilation. Multiple configurations +- Support for true out-of-source compilation. Multiple configurations and settings with different choices of LAMMPS packages, settings, or compilers can be configured and built concurrently from the same source tree. diff --git a/doc/src/Build_make.rst b/doc/src/Build_make.rst index 932050d410..00f2f0b24d 100644 --- a/doc/src/Build_make.rst +++ b/doc/src/Build_make.rst @@ -8,6 +8,10 @@ Building LAMMPS with traditional makefiles requires that you have a for customizing your LAMMPS build with a number of global compilation options and features. +This build system is slowly being phased out and may not support all +optional features and packages in LAMMPS. It is recommended to switch +to the :doc:`CMake based build system `. + Requirements ^^^^^^^^^^^^ From 906ae818dacf8e581370eb253e4c2353c8f54d26 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Wed, 18 Sep 2024 15:43:56 -0400 Subject: [PATCH 04/26] add package removal warnings and GNU make deprecation warnings --- cmake/CMakeLists.txt | 9 +++++++++ src/ATC/Install.sh | 13 +++++++++++++ src/AWPMD/Install.sh | 13 +++++++++++++ src/COLVARS/Install.sh | 13 +++++++++++++ src/GPU/Install.sh | 13 +++++++++++++ src/LEPTON/Install.sh | 13 +++++++++++++ src/PLUMED/Install.sh | 13 +++++++++++++ src/POEMS/Install.sh | 13 +++++++++++++ 8 files changed, 100 insertions(+) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index b10823aba4..9ec3996c64 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -358,6 +358,15 @@ foreach(PKG ${STANDARD_PACKAGES} ${SUFFIX_PACKAGES}) option(PKG_${PKG} "Build ${PKG} Package" OFF) endforeach() +set(DEPRECATED_PACKAGES AWPMD ATC POEMS) +foreach(PKG ${DEPRECATED_PACKAGES}) + message(WARNING + "The ${PKG} package will be removed from LAMMPS in Summer 2025 due to lack of " + "maintenance and use of code constructs that conflict with modern C++ compilers " + "and standards. Please contact developers@lammps.org if you have any concerns " + "about this step.") +endforeach() + ###################################################### # packages with special compiler needs or external libs ###################################################### diff --git a/src/ATC/Install.sh b/src/ATC/Install.sh index 2194685f92..04f4f7c8ac 100755 --- a/src/ATC/Install.sh +++ b/src/ATC/Install.sh @@ -9,6 +9,19 @@ mode=$1 LC_ALL=C export LC_ALL +cat < Date: Wed, 18 Sep 2024 16:22:34 -0400 Subject: [PATCH 05/26] deprecate COMPRESS and VTK package from GNU build --- src/COMPRESS/Install.sh | 13 +++++++++++++ src/VTK/Install.sh | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/COMPRESS/Install.sh b/src/COMPRESS/Install.sh index c0d926cba4..3c5d6d121a 100755 --- a/src/COMPRESS/Install.sh +++ b/src/COMPRESS/Install.sh @@ -7,6 +7,19 @@ mode=$1 LC_ALL=C export LC_ALL +cat < Date: Wed, 18 Sep 2024 16:38:16 -0400 Subject: [PATCH 06/26] deprecate ML-POD from using GNU make build --- src/ML-POD/Install.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ML-POD/Install.sh b/src/ML-POD/Install.sh index a62887e1b3..ffc25b1420 100755 --- a/src/ML-POD/Install.sh +++ b/src/ML-POD/Install.sh @@ -7,6 +7,19 @@ mode=$1 LC_ALL=C export LC_ALL +cat < Date: Thu, 19 Sep 2024 09:59:53 -0400 Subject: [PATCH 07/26] ELECTRODE is going CMake-only as well --- src/ELECTRODE/Install.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/ELECTRODE/Install.sh b/src/ELECTRODE/Install.sh index 561df0dce0..6ece8467aa 100755 --- a/src/ELECTRODE/Install.sh +++ b/src/ELECTRODE/Install.sh @@ -11,6 +11,19 @@ mode=$1 LC_ALL=C export LC_ALL +cat < Date: Thu, 3 Oct 2024 15:39:33 -0400 Subject: [PATCH 08/26] propagate new c++ standard handling from main CMakeLists.txt to plugin version --- examples/plugins/CMakeLists.txt | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/plugins/CMakeLists.txt b/examples/plugins/CMakeLists.txt index 8771b29121..60cbd01d73 100644 --- a/examples/plugins/CMakeLists.txt +++ b/examples/plugins/CMakeLists.txt @@ -42,8 +42,28 @@ else() endif() endif() -# C++11 is required -set(CMAKE_CXX_STANDARD 11) +# we *require* C++11 without extensions but prefer C++17. +# Kokkos requires at least C++17 (currently) +if(NOT CMAKE_CXX_STANDARD) + if(cxx_std_17 IN_LIST CMAKE_CXX_COMPILE_FEATURES) + set(CMAKE_CXX_STANDARD 17) + else() + set(CMAKE_CXX_STANDARD 11) + endif() +endif() +if(CMAKE_CXX_STANDARD LESS 11) + message(FATAL_ERROR "C++ standard must be set to at least 11") +endif() +if(CMAKE_CXX_STANDARD LESS 17) + message(WARNING "Selecting C++17 standard is preferred over C++${CMAKE_CXX_STANDARD}") +endif() +if(PKG_KOKKOS AND (CMAKE_CXX_STANDARD LESS 17)) + set(CMAKE_CXX_STANDARD 17) +endif() +# turn off C++17 check in lmptype.h +if(LAMMPS_CXX11) + add_compile_definitions(LAMMPS_CXX11) +endif() set(CMAKE_CXX_STANDARD_REQUIRED ON) # Need -restrict with Intel compilers From 0a3d213ed9073ae6f2694e596d1ffa99fdc7f1d0 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 4 Oct 2024 16:02:53 -0400 Subject: [PATCH 09/26] turn hard requirement for CMake 3.20 into a warning for now --- cmake/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index a8a447e2d8..61bab2bee2 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -2,7 +2,10 @@ ######################################## # CMake build system # This file is part of LAMMPS -cmake_minimum_required(VERSION 3.20) +cmake_minimum_required(VERSION 3.16) +if(CMAKE_VERSION VERSION_LESS 3.20) + message(WARNING "LAMMPS is planning require at least CMake version 3.20 by Summer 2025. Please upgrade!") +endif() ######################################## # set policy to silence warnings about ignoring _ROOT but use it if(POLICY CMP0074) From 166f0cb5eaa0834c2a2f7eb72e707da0b845ff7b Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 5 Oct 2024 01:04:26 -0400 Subject: [PATCH 10/26] print warning only when package selected --- cmake/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 7ff8eb6abf..cbc8a100fb 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -364,11 +364,13 @@ endforeach() set(DEPRECATED_PACKAGES AWPMD ATC POEMS) foreach(PKG ${DEPRECATED_PACKAGES}) - message(WARNING + if(PKG_${PKG}) + message(WARNING "The ${PKG} package will be removed from LAMMPS in Summer 2025 due to lack of " "maintenance and use of code constructs that conflict with modern C++ compilers " "and standards. Please contact developers@lammps.org if you have any concerns " "about this step.") + endif() endforeach() ###################################################### From a73baf81b1decaeec47955035e0aea40870c201c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 5 Oct 2024 01:04:39 -0400 Subject: [PATCH 11/26] update settings --- cmake/presets/oneapi.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmake/presets/oneapi.cmake b/cmake/presets/oneapi.cmake index 393d1d9b68..403494c409 100644 --- a/cmake/presets/oneapi.cmake +++ b/cmake/presets/oneapi.cmake @@ -18,11 +18,11 @@ set(MPI_CXX_COMPILER "mpicxx" CACHE STRING "" FORCE) unset(HAVE_OMP_H_INCLUDE CACHE) set(OpenMP_C "icx" CACHE STRING "" FORCE) -set(OpenMP_C_FLAGS "-qopenmp;-qopenmp-simd" CACHE STRING "" FORCE) +set(OpenMP_C_FLAGS "-qopenmp" CACHE STRING "" FORCE) set(OpenMP_C_LIB_NAMES "omp" CACHE STRING "" FORCE) set(OpenMP_CXX "icpx" CACHE STRING "" FORCE) -set(OpenMP_CXX_FLAGS "-qopenmp;-qopenmp-simd" CACHE STRING "" FORCE) +set(OpenMP_CXX_FLAGS "-qopenmp" CACHE STRING "" FORCE) set(OpenMP_CXX_LIB_NAMES "omp" CACHE STRING "" FORCE) -set(OpenMP_Fortran_FLAGS "-qopenmp;-qopenmp-simd" CACHE STRING "" FORCE) +set(OpenMP_Fortran_FLAGS "-qopenmp" CACHE STRING "" FORCE) set(OpenMP_omp_LIBRARY "libiomp5.so" CACHE PATH "" FORCE) From 74e449605a161780e80a4d709ca2cfa53322d713 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 15 Oct 2024 14:48:30 -0400 Subject: [PATCH 12/26] add warning to PyLammps that it will be removed soon --- python/lammps/pylammps.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/lammps/pylammps.py b/python/lammps/pylammps.py index 1f5a1a0db9..3f1ac2e2b3 100644 --- a/python/lammps/pylammps.py +++ b/python/lammps/pylammps.py @@ -468,6 +468,9 @@ class PyLammps(object): self.comm_nprocs = self.lmp.extract_setting("world_size") self.comm_me = self.lmp.extract_setting("world_rank") if self.comm_me == 0: + print("\nWARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") + print("WARNING: The PyLammps class is obsolete and will be removed from LAMMPS soon.") + print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING\n") print("LAMMPS output is captured by PyLammps wrapper") if self.comm_nprocs > 1: print("WARNING: Using PyLammps with multiple MPI ranks is experimental. Not all functionality is supported.") From cfb8b25c6ef6accc628c04b9fe6cb3d020634d79 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Thu, 24 Oct 2024 13:33:42 -0400 Subject: [PATCH 13/26] fix grammar --- cmake/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 99048778ae..cda833944e 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -4,7 +4,7 @@ # This file is part of LAMMPS cmake_minimum_required(VERSION 3.16) if(CMAKE_VERSION VERSION_LESS 3.20) - message(WARNING "LAMMPS is planning require at least CMake version 3.20 by Summer 2025. Please upgrade!") + message(WARNING "LAMMPS is planning to require at least CMake version 3.20 by Summer 2025. Please upgrade!") endif() ######################################## # set policy to silence warnings about ignoring _ROOT but use it From 9da58b3ffcc0d9133a06508ab724f230544d0af1 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Mon, 4 Nov 2024 08:39:11 -0700 Subject: [PATCH 14/26] python: deprecated pylammps interface --- python/lammps/pylammps.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/lammps/pylammps.py b/python/lammps/pylammps.py index 1f5a1a0db9..cf5a2dc054 100644 --- a/python/lammps/pylammps.py +++ b/python/lammps/pylammps.py @@ -428,6 +428,8 @@ class PyLammps(object): lower-level interface. The original interface can still be accessed via :py:attr:`PyLammps.lmp`. + .. deprecated:: TBA + :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. @@ -447,6 +449,12 @@ class PyLammps(object): """ def __init__(self, name="", cmdargs=None, ptr=None, comm=None, verbose=False): + print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") + print() + print("The PyLammps interface is deprecated and will be removed in future versions.") + print("Please use the lammps Python class instead.") + print() + print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") self.has_echo = False self.verbose = verbose From 24a4ff78b6ba5264236aa5f58d978498dd921340 Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Mon, 4 Nov 2024 09:19:38 -0700 Subject: [PATCH 15/26] python: update examples and docs --- doc/src/Howto.rst | 1 + doc/src/Howto_pylammps.rst | 564 +----------------- doc/src/Howto_python.rst | 488 +++++++++++++++ doc/src/Library.rst | 7 +- doc/src/Python_atoms.rst | 68 +-- doc/src/Python_create.rst | 107 +--- doc/src/Python_execute.rst | 162 +++-- doc/src/Python_module.rst | 55 +- doc/src/Python_overview.rst | 19 +- doc/utils/requirements.txt | 1 + .../examples/{pylammps => ipython}/.gitignore | 0 .../examples/{pylammps => ipython}/README.md | 16 +- .../atoms.ipynb} | 108 ++-- .../dihedrals/data.dihedral | 0 .../dihedrals/dihedral.ipynb | 73 +-- .../{pylammps => ipython}/elastic/Au.data | 0 .../{pylammps => ipython}/elastic/README | 0 .../{pylammps => ipython}/elastic/elastic.py | 3 +- python/examples/ipython/index.ipynb | 61 ++ .../{pylammps => ipython}/montecarlo/mc.ipynb | 93 ++- .../{pylammps => ipython}/mpi4py/hello.py | 0 .../{pylammps => ipython}/mpi4py/in.melt | 0 .../{pylammps => ipython}/mpi4py/melt.py | 6 +- .../{pylammps => ipython}/simple.ipynb | 304 +++++----- python/examples/ipython/thermo.ipynb | 305 ++++++++++ .../examples/pylammps/interface_usage.ipynb | 546 ----------------- python/lammps/core.py | 138 +++++ python/lammps/ipython/__init__.py | 23 + python/lammps/ipython/magics.py | 75 +++ python/lammps/ipython/wrapper.py | 113 ++++ python/setup.py | 2 +- 31 files changed, 1608 insertions(+), 1730 deletions(-) create mode 100644 doc/src/Howto_python.rst rename python/examples/{pylammps => ipython}/.gitignore (100%) rename python/examples/{pylammps => ipython}/README.md (80%) rename python/examples/{pylammps/interface_usage_bonds.ipynb => ipython/atoms.ipynb} (74%) rename python/examples/{pylammps => ipython}/dihedrals/data.dihedral (100%) rename python/examples/{pylammps => ipython}/dihedrals/dihedral.ipynb (75%) rename python/examples/{pylammps => ipython}/elastic/Au.data (100%) rename python/examples/{pylammps => ipython}/elastic/README (100%) rename python/examples/{pylammps => ipython}/elastic/elastic.py (99%) create mode 100644 python/examples/ipython/index.ipynb rename python/examples/{pylammps => ipython}/montecarlo/mc.ipynb (75%) rename python/examples/{pylammps => ipython}/mpi4py/hello.py (100%) rename python/examples/{pylammps => ipython}/mpi4py/in.melt (100%) rename python/examples/{pylammps => ipython}/mpi4py/melt.py (61%) rename python/examples/{pylammps => ipython}/simple.ipynb (53%) create mode 100644 python/examples/ipython/thermo.ipynb delete mode 100644 python/examples/pylammps/interface_usage.ipynb create mode 100644 python/lammps/ipython/__init__.py create mode 100644 python/lammps/ipython/magics.py create mode 100644 python/lammps/ipython/wrapper.py diff --git a/doc/src/Howto.rst b/doc/src/Howto.rst index 5a63e2b1c4..16620bf47a 100644 --- a/doc/src/Howto.rst +++ b/doc/src/Howto.rst @@ -104,5 +104,6 @@ Tutorials howto Howto_lammps_gui Howto_moltemplate Howto_pylammps + Howto_python Howto_wsl diff --git a/doc/src/Howto_pylammps.rst b/doc/src/Howto_pylammps.rst index 645434bbab..a8371f1366 100644 --- a/doc/src/Howto_pylammps.rst +++ b/doc/src/Howto_pylammps.rst @@ -1,564 +1,6 @@ PyLammps Tutorial ================= -.. contents:: - -Overview --------- - -:py:class:`PyLammps ` is a Python wrapper class for -LAMMPS which can be created on its own or use an existing -:py:class:`lammps Python ` object. It creates a simpler, -more "pythonic" interface to common LAMMPS functionality, in contrast to -the :py:class:`lammps ` wrapper for the LAMMPS :ref:`C -language library interface API ` which is written using -`Python ctypes `_. The :py:class:`lammps ` -wrapper 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 implementation. Finally, the :py:class:`IPyLammps -` wrapper builds on top of :py:class:`PyLammps -` and adds some additional features for `IPython -integration `_ into `Jupyter notebooks `_, e.g. for -embedded visualization output from :doc:`dump style image `. - -.. _ctypes: https://docs.python.org/3/library/ctypes.html -.. _ipython: https://ipython.org/ -.. _jupyter: https://jupyter.org/ - -Comparison of lammps and PyLammps interfaces -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -lammps.lammps -""""""""""""" - -* uses `ctypes `_ -* direct memory access to native C++ data with optional support for NumPy arrays -* provides functions to send and receive data to LAMMPS -* interface modeled after the LAMMPS :ref:`C language library interface API ` -* requires knowledge of how LAMMPS internally works (C pointers, etc) -* full support for running Python with MPI using `mpi4py `_ -* no overhead from creating a more Python-like interface - -lammps.PyLammps -""""""""""""""" - -* higher-level abstraction built on *top* of the original :py:class:`ctypes based interface ` -* manipulation of Python objects -* communication with LAMMPS is hidden from API user -* shorter, more concise Python -* better IPython integration, designed for quick prototyping -* designed for serial execution -* additional overhead from capturing and parsing the LAMMPS screen output - -Quick Start ------------ - -System-wide Installation -^^^^^^^^^^^^^^^^^^^^^^^^ - -Step 1: Building LAMMPS as a shared library -""""""""""""""""""""""""""""""""""""""""""" - -To use LAMMPS inside of Python it has to be compiled as shared -library. This library is then loaded by the Python interface. In this -example we enable the MOLECULE package and compile LAMMPS with PNG, JPEG -and FFMPEG output support enabled. - -Step 1a: For the CMake based build system, the steps are: - -.. code-block:: bash - - mkdir $LAMMPS_DIR/build-shared - cd $LAMMPS_DIR/build-shared - - # MPI, PNG, Jpeg, FFMPEG are auto-detected - cmake ../cmake -DPKG_MOLECULE=yes -DBUILD_LIB=yes -DBUILD_SHARED_LIBS=yes - make - -Step 1b: For the legacy, make based build system, the steps are: - -.. code-block:: bash - - cd $LAMMPS_DIR/src - - # add packages if necessary - make yes-MOLECULE - - # compile shared library using Makefile - make mpi mode=shlib LMP_INC="-DLAMMPS_PNG -DLAMMPS_JPEG -DLAMMPS_FFMPEG" JPG_LIB="-lpng -ljpeg" - -Step 2: Installing the LAMMPS Python package -"""""""""""""""""""""""""""""""""""""""""""" - -PyLammps is part of the lammps Python package. To install it simply install -that package into your current Python installation with: - -.. code-block:: bash - - make install-python - -.. note:: - - Recompiling the shared library requires re-installing the Python package - -Installation inside of a virtualenv -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -You can use virtualenv to create a custom Python environment specifically tuned -for your workflow. - -Benefits of using a virtualenv -"""""""""""""""""""""""""""""" - -* isolation of your system Python installation from your development installation -* installation can happen in your user directory without root access (useful for HPC clusters) -* installing packages through pip allows you to get newer versions of packages than e.g., through apt-get or yum package managers (and without root access) -* you can even install specific old versions of a package if necessary - -**Prerequisite (e.g. on Ubuntu)** - -.. code-block:: bash - - apt-get install python-virtualenv - -Creating a virtualenv with lammps installed -""""""""""""""""""""""""""""""""""""""""""" - -.. code-block:: bash - - # create virtualenv named 'testing' - virtualenv $HOME/python/testing - - # activate 'testing' environment - source $HOME/python/testing/bin/activate - -Now configure and compile the LAMMPS shared library as outlined above. -When using CMake and the shared library has already been build, you -need to re-run CMake to update the location of the python executable -to the location in the virtual environment with: - -.. code-block:: bash - - cmake . -DPython_EXECUTABLE=$(which python) - - # install LAMMPS package in virtualenv - (testing) make install-python - - # install other useful packages - (testing) pip install matplotlib jupyter mpi4py - - ... - - # return to original shell - (testing) deactivate - -Creating a new instance of PyLammps ------------------------------------ - -To create a PyLammps object you need to first import the class from the lammps -module. By using the default constructor, a new *lammps* instance is created. - -.. code-block:: python - - from lammps import PyLammps - L = PyLammps() - -You can also initialize PyLammps on top of this existing *lammps* object: - -.. code-block:: python - - from lammps import lammps, PyLammps - lmp = lammps() - L = PyLammps(ptr=lmp) - -Commands --------- - -Sending a LAMMPS command with the existing library interfaces is done using -the command method of the lammps object instance. - -For instance, let's take the following LAMMPS command: - -.. code-block:: LAMMPS - - region box block 0 10 0 5 -0.5 0.5 - -In the original interface this command can be executed with the following -Python code if *L* was a lammps instance: - -.. code-block:: python - - L.command("region box block 0 10 0 5 -0.5 0.5") - -With the PyLammps interface, any command can be split up into arbitrary parts -separated by white-space, passed as individual arguments to a region method. - -.. code-block:: python - - L.region("box block", 0, 10, 0, 5, -0.5, 0.5) - -Note that each parameter is set as Python literal floating-point number. In the -PyLammps interface, each command takes an arbitrary parameter list and transparently -merges it to a single command string, separating individual parameters by white-space. - -The benefit of this approach is avoiding redundant command calls and easier -parameterization. In the original interface parameterization 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) - -System state ------------- - -In addition to dispatching commands directly through the PyLammps object, it -also provides 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 - -Working with LAMMPS variables ------------------------------ - -LAMMPS variables can be both defined and accessed via the PyLammps interface. - -To define a variable you can use the :doc:`variable ` command: - -.. code-block:: python - - L.variable("a index 2") - -A dictionary of all variables is returned by L.variables - -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 - -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 thermo values, -variables, compute or fix data. - -.. code-block:: python - - result = L.eval("ke") # kinetic energy - result = L.eval("pe") # potential energy - - result = L.eval("v_t/2.0") - -Accessing atom data -------------------- - -All atoms in the current simulation can be accessed by using the L.atoms list. -Each element of this list is an object which exposes its properties (id, type, -position, velocity, force, etc.). - -.. code-block:: python - - # access first atom - L.atoms[0].id - L.atoms[0].type - - # access second atom - L.atoms[1].position - L.atoms[1].velocity - L.atoms[1].force - -Some properties can also be used to set: - -.. code-block:: python - - # set position in 2D simulation - L.atoms[0].position = (1.0, 0.0) - - # set position in 3D simulation - L.atoms[0].position = (1.0, 0.0, 1.) - -Evaluating thermo data ----------------------- - -Each simulation run usually produces thermo output based on system state, -computes, fixes or variables. The trajectories of these values can be queried -after a run via the L.runs list. This list contains a growing list of run data. -The first element is the output of the first run, the second element that of -the second run. - -.. code-block:: python - - L.run(1000) - L.runs[0] # data of first 1000 time steps - - L.run(1000) - L.runs[1] # data of second 1000 time steps - -Each run contains a dictionary of all trajectories. Each trajectory is -accessible through its thermo name: - -.. code-block:: python - - L.runs[0].thermo.Step # list of time steps in first run - L.runs[0].thermo.Ke # list of kinetic energy values in first run - -Together with matplotlib plotting data out of LAMMPS becomes simple: - -.. code-block:: python - - import matplotlib.plot as plt - steps = L.runs[0].thermo.Step - ke = L.runs[0].thermo.Ke - plt.plot(steps, ke) - -Error handling with PyLammps ----------------------------- - -Using C++ exceptions in LAMMPS for errors allows capturing them on the -C++ side and rethrowing them on the Python side. This way you can handle -LAMMPS errors 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. - -Using PyLammps in IPython notebooks and Jupyter ------------------------------------------------ - -If the LAMMPS Python package is installed for the same Python interpreter as -IPython, you can use PyLammps directly inside of an IPython notebook inside of -Jupyter. Jupyter is a powerful integrated development environment (IDE) for -many dynamic languages like Python, Julia and others, which operates inside of -any web browser. Besides auto-completion and syntax highlighting it allows you -to create formatted documents using Markup, mathematical formulas, graphics and -animations intermixed with executable Python code. It is a great format for -tutorials and showcasing your latest research. - -To launch an instance of Jupyter simply run the following command inside your -Python environment (this assumes you followed the Quick Start instructions): - -.. code-block:: bash - - jupyter notebook - -IPyLammps Examples ------------------- - -Examples of IPython notebooks can be found in the python/examples/pylammps -subdirectory. To open these notebooks launch *jupyter notebook* inside this -directory and navigate to one of them. If you compiled and installed -a LAMMPS shared library with exceptions, PNG, JPEG and FFMPEG support -you should be able to rerun all of these notebooks. - -Validating a dihedral potential -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This example showcases how an IPython Notebook can be used to compare a simple -LAMMPS simulation of a harmonic dihedral potential to its analytical solution. -Four atoms are placed in the simulation and the dihedral potential is applied on -them using a datafile. Then one of the atoms is rotated along the central axis by -setting its position from Python, which changes the dihedral angle. - -.. code-block:: python - - phi = [d \* math.pi / 180 for d in range(360)] - - pos = [(1.0, math.cos(p), math.sin(p)) for p in phi] - - pe = [] - for p in pos: - L.atoms[3].position = p - L.run(0) - pe.append(L.eval("pe")) - -By evaluating the potential energy for each position we can verify that -trajectory with the analytical formula. To compare both solutions, we plot -both trajectories over each other using matplotlib, which embeds the generated -plot inside the IPython notebook. - -.. image:: JPG/pylammps_dihedral.jpg - :align: center - -Running a Monte Carlo relaxation -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This second example shows how to use PyLammps to create a 2D Monte Carlo Relaxation -simulation, computing and plotting energy terms and even embedding video output. - -Initially, a 2D system is created in a state with minimal energy. - -.. image:: JPG/pylammps_mc_minimum.jpg - :align: center - -It is then disordered by moving each atom by a random delta. - -.. code-block:: python - - random.seed(27848) - deltaperturb = 0.2 - - for i in range(L.system.natoms): - x, y = L.atoms[i].position - dx = deltaperturb \* random.uniform(-1, 1) - dy = deltaperturb \* random.uniform(-1, 1) - L.atoms[i].position = (x+dx, y+dy) - - L.run(0) - -.. image:: JPG/pylammps_mc_disordered.jpg - :align: center - -Finally, the Monte Carlo algorithm is implemented in Python. It continuously -moves random atoms by a random delta and only accepts certain moves. - -.. code-block:: python - - estart = L.eval("pe") - elast = estart - - naccept = 0 - energies = [estart] - - niterations = 3000 - deltamove = 0.1 - kT = 0.05 - - natoms = L.system.natoms - - for i in range(niterations): - iatom = random.randrange(0, natoms) - current_atom = L.atoms[iatom] - - x0, y0 = current_atom.position - - dx = deltamove \* random.uniform(-1, 1) - dy = deltamove \* random.uniform(-1, 1) - - current_atom.position = (x0+dx, y0+dy) - - L.run(1, "pre no post no") - - e = L.eval("pe") - energies.append(e) - - if e <= elast: - naccept += 1 - elast = e - elif random.random() <= math.exp(natoms\*(elast-e)/kT): - naccept += 1 - elast = e - else: - current_atom.position = (x0, y0) - -The energies of each iteration are collected in a Python list and finally plotted using matplotlib. - -.. image:: JPG/pylammps_mc_energies_plot.jpg - :align: center - -The IPython notebook also shows how to use dump commands and embed video files -inside of the IPython notebook. - -Using PyLammps and mpi4py (Experimental) ----------------------------------------- - -PyLammps can be run in parallel using `mpi4py -`_. This python package can be installed -using - -.. code-block:: bash - - pip install mpi4py - -.. warning:: - - Usually, any :py:class:`PyLammps ` command must be - executed by *all* MPI processes. However, evaluations and querying - the system state is only available on MPI rank 0. Using these - functions from other MPI ranks will raise an exception. - -The following is a short example which reads in an existing LAMMPS input -file and executes it in parallel. You can find in.melt in the -examples/melt folder. Please take note that the -:py:meth:`PyLammps.eval() ` is called only from -MPI rank 0. - -.. code-block:: python - - from mpi4py import MPI - from lammps import PyLammps - - L = PyLammps() - L.file("in.melt") - - if MPI.COMM_WORLD.rank == 0: - print("Potential energy: ", L.eval("pe")) - - MPI.Finalize() - -To run this script (melt.py) in parallel using 4 MPI processes we invoke the -following mpirun command: - -.. code-block:: bash - - mpirun -np 4 python melt.py - -Feedback and Contributing -------------------------- - -If you find this Python interface useful, please feel free to provide feedback -and ideas on how to improve it to Richard Berger (richard.berger@outlook.com). We also -want to encourage people to write tutorial style IPython notebooks showcasing LAMMPS usage -and maybe their latest research results. +The PyLammps interface is deprecated and will be removed in a future release of +LAMMPS. As such, the PyLammps version of this tutorial has been removed and is +replaced by the :doc:`Howto_python`. diff --git a/doc/src/Howto_python.rst b/doc/src/Howto_python.rst new file mode 100644 index 0000000000..f668532f44 --- /dev/null +++ b/doc/src/Howto_python.rst @@ -0,0 +1,488 @@ +LAMMPS Python Tutorial +====================== + +.. contents:: + +Overview +-------- + +:py:class:`lammps ` is a Python wrapper class for the +LAMMPS :ref:`C language library interface API ` which is written using +`Python ctypes `_. + +In addition to the flat `ctypes `_ interface, this class exposes a +discoverable API that doesn't require knowledge of the underlying C++ +code implementation. + +Finally, the API exposes some additional features for `IPython integration +`_ into `Jupyter notebooks `_, e.g. for embedded +visualization output from :doc:`dump style image `. + +.. _ctypes: https://docs.python.org/3/library/ctypes.html +.. _ipython: https://ipython.org/ +.. _jupyter: https://jupyter.org/ + +Quick Start +----------- + +System-wide Installation +^^^^^^^^^^^^^^^^^^^^^^^^ + +Step 1: Building LAMMPS as a shared library +""""""""""""""""""""""""""""""""""""""""""" + +To use LAMMPS inside of Python it has to be compiled as shared +library. This library is then loaded by the Python interface. In this +example we enable the MOLECULE package and compile LAMMPS with PNG, JPEG +and FFMPEG output support enabled. + +Step 1a: For the CMake based build system, the steps are: + +.. code-block:: bash + + mkdir $LAMMPS_DIR/build-shared + cd $LAMMPS_DIR/build-shared + + # MPI, PNG, Jpeg, FFMPEG are auto-detected + cmake ../cmake -DPKG_MOLECULE=yes -DPKG_PYTHON=on -DBUILD_SHARED_LIBS=yes + make + +Step 1b: For the legacy, make based build system, the steps are: + +.. code-block:: bash + + cd $LAMMPS_DIR/src + + # add packages if necessary + make yes-MOLECULE + make yes-PYTHON + + # compile shared library using Makefile + make mpi mode=shlib LMP_INC="-DLAMMPS_PNG -DLAMMPS_JPEG -DLAMMPS_FFMPEG" JPG_LIB="-lpng -ljpeg" + +Step 2: Installing the LAMMPS Python package +"""""""""""""""""""""""""""""""""""""""""""" + +Next install the LAMMPS Python package into your current Python installation with: + +.. code-block:: bash + + make install-python + +.. note:: + + Recompiling the shared library requires re-installing the Python package + +Installation inside of a virtual environment +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can use virtual environemnts to create a custom Python environment +specifically tuned for your workflow. + +Benefits of using a virtualenv +"""""""""""""""""""""""""""""" + +* isolation of your system Python installation from your development installation +* installation can happen in your user directory without root access (useful for HPC clusters) +* installing packages through pip allows you to get newer versions of packages than e.g., through apt-get or yum package managers (and without root access) +* you can even install specific old versions of a package if necessary + +**Prerequisite (e.g. on Ubuntu)** + +.. code-block:: bash + + apt-get install python-venv + +Creating a virtualenv with lammps installed +""""""""""""""""""""""""""""""""""""""""""" + +.. code-block:: bash + + # create virtual envrionment named 'testing' + python3 -m venv $HOME/python/testing + + # activate 'testing' environment + source $HOME/python/testing/bin/activate + +Now configure and compile the LAMMPS shared library as outlined above. +When using CMake and the shared library has already been build, you +need to re-run CMake to update the location of the python executable +to the location in the virtual environment with: + +.. code-block:: bash + + cmake . -DPython_EXECUTABLE=$(which python) + + # install LAMMPS package in virtualenv + (testing) make install-python + + # install other useful packages + (testing) pip install matplotlib jupyter mpi4py + + ... + + # return to original shell + (testing) deactivate + +Creating a new lammps instance +------------------------------ + +To create a lammps object you need to first import the class from the lammps +module. By using the default constructor, a new :py:class:`lammps +` instance is created. + +.. code-block:: python + + from lammps import lammps + L = lammps() + +Commands +-------- + +Sending a LAMMPS command with the library interface is done using +the ``command`` method of the lammps object. + +For instance, let's take the following LAMMPS command: + +.. code-block:: LAMMPS + + region box block 0 10 0 5 -0.5 0.5 + +This command can be executed with the following Python code if ``L`` is a ``lammps`` +instance: + +.. code-block:: python + + L.command("region box block 0 10 0 5 -0.5 0.5") + +For convenience, the ``lammps`` class also provides a command wrapper ``cmd`` +that turns any LAMMPS command into a regular function call: + +.. code-block:: python + + L.cmd.region("box block", 0, 10, 0, 5, -0.5, 0.5) + +Note that each parameter is set as Python number literal. With +the wrapper each command takes an arbitrary parameter list and transparently +merges it to a single command string, separating individual parameters by +white-space. + +The benefit of this approach is avoiding redundant command calls and easier +parameterization. With the ``command`` function each call needs to be assembled +manually using formatted strings. + +.. code-block:: python + + L.command(f"region box block {xlo} {xhi} {ylo} {yhi} {zlo} {zhi}") + +The wrapper accepts parameters directly and will convert +them automatically to a final command string. + +.. code-block:: python + + L.cmd.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) + +.. note:: + + When running in IPython you can use Tab-completion after ``L.cmd.`` to see + all available LAMMPS commands. + +System state +------------ + +In addition to dispatching commands directly through the PyLammps object, it +also provides 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 + +Working with LAMMPS variables +----------------------------- + +LAMMPS variables can be both defined and accessed via the PyLammps interface. + +To define a variable you can use the :doc:`variable ` command: + +.. code-block:: python + + L.variable("a index 2") + +A dictionary of all variables is returned by L.variables + +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 + +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 thermo values, +variables, compute or fix data. + +.. code-block:: python + + result = L.get_thermo("ke") # kinetic energy + result = L.get_thermo("pe") # potential energy + + result = L.extract_variable("t") / 2.0 + +Accessing atom data +------------------- + +All atoms in the current simulation can be accessed by using the L.atoms list. +Each element of this list is an object which exposes its properties (id, type, +position, velocity, force, etc.). + +.. code-block:: python + + # access first atom + atom_id = L.numpy.extract_atom("id") + atom_type = L.numpy.extract_atom("type") + + x = L.numpy.extract_atom("x") + v = L.numpy.extract_atom("v") + f = L.numpy.extract_atom("f") + +Some properties can also be used to set: + +.. code-block:: python + + # set position in 2D simulation + x[0] = (1.0, 0.0) + + # set position in 3D simulation + x[0] = (1.0, 0.0, 1.) + +Evaluating thermo data +---------------------- + +Each simulation run usually produces thermo output based on system state, +computes, fixes or variables. The trajectories of these values can be queried +after a run via the L.runs list. This list contains a growing list of run data. +The first element is the output of the first run, the second element that of +the second run. + +.. code-block:: python + + L.run(1000) + L.runs[0] # data of first 1000 time steps + + L.run(1000) + L.runs[1] # data of second 1000 time steps + +Each run contains a dictionary of all trajectories. Each trajectory is +accessible through its thermo name: + +.. code-block:: python + + L.runs[0].thermo.Step # list of time steps in first run + L.runs[0].thermo.Ke # list of kinetic energy values in first run + +Together with matplotlib plotting data out of LAMMPS becomes simple: + +.. code-block:: python + + import matplotlib.plot as plt + steps = L.runs[0].thermo.Step + ke = L.runs[0].thermo.Ke + plt.plot(steps, ke) + +Error handling with PyLammps +---------------------------- + +Using C++ exceptions in LAMMPS for errors allows capturing them on the +C++ side and rethrowing them on the Python side. This way you can handle +LAMMPS errors 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. + +Using LAMMPS in IPython notebooks and Jupyter +--------------------------------------------- + +If the LAMMPS Python package is installed for the same Python interpreter as +IPython, you can use LAMMPS directly inside of an IPython notebook inside of +Jupyter. Jupyter is a powerful integrated development environment (IDE) for +many dynamic languages like Python, Julia and others, which operates inside of +any web browser. Besides auto-completion and syntax highlighting it allows you +to create formatted documents using Markup, mathematical formulas, graphics and +animations intermixed with executable Python code. It is a great format for +tutorials and showcasing your latest research. + +To launch an instance of Jupyter simply run the following command inside your +Python environment (this assumes you followed the Quick Start instructions): + +.. code-block:: bash + + jupyter notebook + +Interactive Python Examples +--------------------------- + +Examples of IPython notebooks can be found in the ``python/examples/juypter`` +subdirectory. To open these notebooks launch ``jupyter notebook`` inside this +directory and navigate to one of them. If you compiled and installed +a LAMMPS shared library with PNG, JPEG and FFMPEG support +you should be able to rerun all of these notebooks. + +Validating a dihedral potential +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This example showcases how an IPython Notebook can be used to compare a simple +LAMMPS simulation of a harmonic dihedral potential to its analytical solution. +Four atoms are placed in the simulation and the dihedral potential is applied on +them using a datafile. Then one of the atoms is rotated along the central axis by +setting its position from Python, which changes the dihedral angle. + +.. code-block:: python + + phi = [d \* math.pi / 180 for d in range(360)] + + pos = [(1.0, math.cos(p), math.sin(p)) for p in phi] + + x = L.numpy.extract_atom("x") + + pe = [] + for p in pos: + x[3] = p + L.cmd.run(0) + pe.append(L.get_thermo("pe")) + +By evaluating the potential energy for each position we can verify that +trajectory with the analytical formula. To compare both solutions, we plot +both trajectories over each other using matplotlib, which embeds the generated +plot inside the IPython notebook. + +.. image:: JPG/pylammps_dihedral.jpg + :align: center + +Running a Monte Carlo relaxation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This second example shows how to use the `lammps` Python interface to create a +2D Monte Carlo Relaxation simulation, computing and plotting energy terms and +even embedding video output. + +Initially, a 2D system is created in a state with minimal energy. + +.. image:: JPG/pylammps_mc_minimum.jpg + :align: center + +It is then disordered by moving each atom by a random delta. + +.. code-block:: python + + random.seed(27848) + deltaperturb = 0.2 + x = L.numpy.extract_atom("x") + natoms = x.shape[0] + + for i in range(natoms): + dx = deltaperturb \* random.uniform(-1, 1) + dy = deltaperturb \* random.uniform(-1, 1) + x[i][0] += dx + x[i][1] += dy + + L.cmd.run(0) + +.. image:: JPG/pylammps_mc_disordered.jpg + :align: center + +Finally, the Monte Carlo algorithm is implemented in Python. It continuously +moves random atoms by a random delta and only accepts certain moves. + +.. code-block:: python + + estart = L.get_thermo("pe") + elast = estart + + naccept = 0 + energies = [estart] + + niterations = 3000 + deltamove = 0.1 + kT = 0.05 + + for i in range(niterations): + x = L.numpy.extract_atom("x") + natoms = x.shape[0] + iatom = random.randrange(0, natoms) + current_atom = x[iatom] + + x0 = current_atom[0] + y0 = current_atom[1] + + dx = deltamove \* random.uniform(-1, 1) + dy = deltamove \* random.uniform(-1, 1) + + current_atom[0] = x0 + dx + current_atom[1] = y0 + dy + + L.cmd.run(1, "pre no post no") + + e = L.get_thermo("pe") + energies.append(e) + + if e <= elast: + naccept += 1 + elast = e + elif random.random() <= math.exp(natoms\*(elast-e)/kT): + naccept += 1 + elast = e + else: + current_atom[0] = x0 + current_atom[1] = y0 + +The energies of each iteration are collected in a Python list and finally plotted using matplotlib. + +.. image:: JPG/pylammps_mc_energies_plot.jpg + :align: center + +The IPython notebook also shows how to use dump commands and embed video files +inside of the IPython notebook. diff --git a/doc/src/Library.rst b/doc/src/Library.rst index 50c28b7fcd..e2021187c2 100644 --- a/doc/src/Library.rst +++ b/doc/src/Library.rst @@ -131,16 +131,15 @@ run LAMMPS in serial mode. .. _lammps_python_api: -LAMMPS Python APIs -================== +LAMMPS Python API +================= The LAMMPS Python module enables calling the LAMMPS C library API from Python by dynamically loading functions in the LAMMPS shared library through the `Python ctypes module `_. Because of the dynamic loading, it is **required** that LAMMPS is compiled in :ref:`"shared" mode `. The Python interface is object-oriented, but -otherwise tries to be very similar to the C library API. Three different -Python classes to run LAMMPS are available and they build on each other. +otherwise tries to be very similar to the C library API. More information on this is in the :doc:`Python_head` section of the manual. Use of the LAMMPS Python module is described in :doc:`Python_module`. diff --git a/doc/src/Python_atoms.rst b/doc/src/Python_atoms.rst index f01559a524..0a445f9b6b 100644 --- a/doc/src/Python_atoms.rst +++ b/doc/src/Python_atoms.rst @@ -2,14 +2,8 @@ Per-atom properties =================== Similar to what is described in :doc:`Library_atoms`, the instances of -:py:class:`lammps `, :py:class:`PyLammps `, or -:py:class:`IPyLammps ` can be used to extract atom quantities -and modify some of them. The main difference between the interfaces is how the information -is exposed. - -While the :py:class:`lammps ` is just a thin layer that wraps C API calls, -:py:class:`PyLammps ` and :py:class:`IPyLammps ` expose -information as objects and properties. +:py:class:`lammps ` can be used to extract atom quantities +and modify some of them. 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 @@ -25,57 +19,25 @@ against invalid accesses. accordingly. These arrays can change sizes and order at every neighbor list rebuild and atom sort event as atoms are migrating between subdomains. -.. tabs:: +.. code-block:: python - .. tab:: lammps API + from lammps import lammps - .. code-block:: python + lmp = lammps() + lmp.file("in.sysinit") - from lammps import lammps + nlocal = lmp.extract_global("nlocal") + x = lmp.extract_atom("x") - lmp = lammps() - lmp.file("in.sysinit") + for i in range(nlocal): + print("(x,y,z) = (", x[i][0], x[i][1], x[i][2], ")") - nlocal = lmp.extract_global("nlocal") - x = lmp.extract_atom("x") + lmp.close() - for i in range(nlocal): - print("(x,y,z) = (", x[i][0], x[i][1], x[i][2], ")") +**Methods**: - lmp.close() +* :py:meth:`extract_atom() `: extract a per-atom quantity - **Methods**: - - * :py:meth:`extract_atom() `: extract a per-atom quantity - - **Numpy Methods**: - - * :py:meth:`numpy.extract_atom() `: extract a per-atom quantity as numpy array - - .. tab:: PyLammps/IPyLammps API - - All atoms in the current simulation can be accessed by using the :py:attr:`PyLammps.atoms ` property. - Each element of this list is a :py:class:`Atom ` or :py:class:`Atom2D ` object. The attributes of - these objects provide access to their data (id, type, position, velocity, force, etc.): - - .. code-block:: python - - # access first atom - L.atoms[0].id - L.atoms[0].type - - # access second atom - L.atoms[1].position - L.atoms[1].velocity - L.atoms[1].force - - Some attributes can be changed: - - .. code-block:: python - - # set position in 2D simulation - L.atoms[0].position = (1.0, 0.0) - - # set position in 3D simulation - L.atoms[0].position = (1.0, 0.0, 1.0) +**Numpy Methods**: +* :py:meth:`numpy.extract_atom() `: extract a per-atom quantity as numpy array diff --git a/doc/src/Python_create.rst b/doc/src/Python_create.rst index 939aad2f32..c1444c400e 100644 --- a/doc/src/Python_create.rst +++ b/doc/src/Python_create.rst @@ -26,108 +26,25 @@ 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: +Here is a simple example using the LAMMPS Python interface: -.. tabs:: +.. code-block:: python - .. tab:: lammps API + from lammps import lammps - .. code-block:: python + # NOTE: argv[0] is set by the lammps class constructor + args = ["-log", "none"] - from lammps import lammps + # create LAMMPS instance + lmp = lammps(cmdargs=args) - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] + # get and print numerical version code + print("LAMMPS Version: ", lmp.version()) - # create LAMMPS instance - lmp = lammps(cmdargs=args) + # explicitly close and delete LAMMPS instance (optional) + lmp.close() - # get and print numerical version code - print("LAMMPS Version: ", lmp.version()) - - # explicitly close and delete LAMMPS instance (optional) - lmp.close() - - .. tab:: PyLammps API - - The :py:class:`PyLammps ` class is a wrapper around the - :py:class:`lammps ` class and all of its lower level functions. - By default, it will create a new instance of :py:class:`lammps ` passing - along all arguments to the constructor of :py:class:`lammps `. - - .. code-block:: python - - from lammps import PyLammps - - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] - - # create LAMMPS instance - L = PyLammps(cmdargs=args) - - # get and print numerical version code - print("LAMMPS Version: ", L.version()) - - # explicitly close and delete LAMMPS instance (optional) - L.close() - - :py:class:`PyLammps ` objects can also be created on top of an existing - :py:class:`lammps ` object: - - .. code-block:: python - - from lammps import lammps, PyLammps - ... - # create LAMMPS instance - lmp = lammps(cmdargs=args) - - # create PyLammps instance using previously created LAMMPS instance - L = PyLammps(ptr=lmp) - - This is useful if you have to create the :py:class:`lammps ` - instance is a specific way, but want to take advantage of the - :py:class:`PyLammps ` interface. - - .. tab:: IPyLammps API - - The :py:class:`IPyLammps ` class is an extension of the - :py:class:`PyLammps ` class. It has the same construction behavior. By - default, it will create a new instance of :py:class:`lammps` passing - along all arguments to the constructor of :py:class:`lammps`. - - .. code-block:: python - - from lammps import IPyLammps - - # NOTE: argv[0] is set by the lammps class constructor - args = ["-log", "none"] - - # create LAMMPS instance - L = IPyLammps(cmdargs=args) - - # get and print numerical version code - print("LAMMPS Version: ", L.version()) - - # explicitly close and delete LAMMPS instance (optional) - L.close() - - You can also initialize IPyLammps on top of an existing :py:class:`lammps` or :py:class:`PyLammps` object: - - .. code-block:: python - - from lammps import lammps, IPyLammps - ... - # create LAMMPS instance - lmp = lammps(cmdargs=args) - - # create PyLammps instance using previously created LAMMPS instance - L = PyLammps(ptr=lmp) - - This is useful if you have to create the :py:class:`lammps ` - instance is a specific way, but want to take advantage of the - :py:class:`IPyLammps ` interface. - -In all of the above cases, same as with the :ref:`C library API `, this will use the +Same as with the :ref:`C library API `, this will use the ``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was compiled with. diff --git a/doc/src/Python_execute.rst b/doc/src/Python_execute.rst index 01cf0e920f..28c3ff5575 100644 --- a/doc/src/Python_execute.rst +++ b/doc/src/Python_execute.rst @@ -1,127 +1,119 @@ Executing commands ================== -Once an instance of the :py:class:`lammps `, -:py:class:`PyLammps `, or -:py:class:`IPyLammps ` class is created, there are +Once an instance of the :py:class:`lammps ` 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:: +Same as in the equivalent :doc:`C library functions `, +commands can be read from a file, a single string, a list of strings and a +block of commands in a single multi-line string. They are processed under the +same boundary conditions as the C library counterparts. The example below +demonstrates the use of :py:func:`lammps.file()`, :py:func:`lammps.command()`, +:py:func:`lammps.commands_list()`, and :py:func:`lammps.commands_string()`: - .. tab:: lammps API +.. code-block:: python - Same as in the equivalent - :doc:`C library functions `, commands can be read from a file, a - single string, a list of strings and a block of commands in a single - multi-line string. They are processed under the same boundary conditions - as the C library counterparts. The example below demonstrates the use - of :py:func:`lammps.file()`, :py:func:`lammps.command()`, - :py:func:`lammps.commands_list()`, and :py:func:`lammps.commands_string()`: + from lammps import lammps + lmp = lammps() - .. code-block:: python + # read commands from file 'in.melt' + lmp.file('in.melt') - from lammps import lammps - lmp = lammps() + # issue a single command + lmp.command('variable zpos index 1.0') - # read commands from file 'in.melt' - lmp.file('in.melt') + # 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) - # issue a single command - lmp.command('variable zpos index 1.0') + # 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) - # 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) + +Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS +commands by calling equivalent member functions of :py:class:`PyLammps ` +and :py:class:`IPyLammps ` instances. - # 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) +For instance, the following LAMMPS command - .. tab:: PyLammps/IPyLammps API +.. code-block:: LAMMPS - Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS - commands by calling equivalent member functions of :py:class:`PyLammps ` - and :py:class:`IPyLammps ` instances. + region box block 0 10 0 5 -0.5 0.5 - For instance, the following LAMMPS command +can be executed using with the lammps API with the following Python code if ``lmp`` is an +instance of :py:class:`lammps `: - .. code-block:: LAMMPS +.. code-block:: python - region box block 0 10 0 5 -0.5 0.5 + from lammps import lammps - can be executed using with the lammps API with the following Python code if ``lmp`` is an - instance of :py:class:`lammps `: + lmp = lammps() + lmp.command("region box block 0 10 0 5 -0.5 0.5") - .. code-block:: python +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 :doc:`command `. +For the :doc:`region ` command that means the :code:`region()` method can be called. +The arguments of the command can be passed as one string, or +individually. - from lammps import lammps +.. code-block:: python - lmp = lammps() - lmp.command("region box block 0 10 0 5 -0.5 0.5") + from lammps import lammps - 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 :doc:`command `. - For the :doc:`region ` command that means the :code:`region()` method can be called. - The arguments of the command can be passed as one string, or - individually. + L = lammps() - .. code-block:: python + # pass command parameters as one string + L.cmd.region("box block 0 10 0 5 -0.5 0.5") - from lammps import PyLammps + # OR pass them individually + L.cmd.region("box block", 0, 10, 0, 5, -0.5, 0.5) - L = PyLammps() +In the latter example, all parameters except the first are Python floating-point literals. The +member function takes the entire parameter list and transparently merges it to a single command +string. - # pass command parameters as one string - L.region("box block 0 10 0 5 -0.5 0.5") +The benefit of this approach is avoiding redundant command calls and easier +parameterization. In the lammps API parameterization needed to be done +manually by creating formatted command strings. - # OR pass them individually - L.region("box block", 0, 10, 0, 5, -0.5, 0.5) +.. code-block:: python - In the latter example, all parameters except the first are Python floating-point literals. The - member function takes the entire parameter list and transparently merges it to a single command - string. + lmp.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) - The benefit of this approach is avoiding redundant command calls and easier - parameterization. In the lammps API parameterization needed to be done - manually by creating formatted command strings. +In contrast, methods of PyLammps accept parameters directly and will convert +them automatically to a final command string. - .. code-block:: python +.. code-block:: python - lmp.command("region box block %f %f %f %f %f %f" % (xlo, xhi, ylo, yhi, zlo, zhi)) + L.cmd.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) - In contrast, methods of PyLammps accept parameters directly and will convert - them automatically to a final command string. +Using these facilities, the previous example shown above can be rewritten as follows: - .. code-block:: python +.. code-block:: python - L.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) + from lammps import PyLammps + L = lammps() - Using these facilities, the example shown for the lammps API can be rewritten as follows: + # read commands from file 'in.melt' + L.file('in.melt') - .. code-block:: python + # issue a single command + L.cmd.variable('zpos', 'index', 1.0) - from lammps import PyLammps - L = PyLammps() + # create 10 groups with 10 atoms each + for i in range(10): + L.cmd.group(f"g{i}", "id", f"{10*i+1}:{10*(i+1)}") - # 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}") + L.cmd.clear() + L.cmd.region("box block", 0, 2, 0, 2, 0, 2) + L.cmd.create_box(1, "box") + L.cmd.create_atoms(1, "single", 1.0, 1.0, "${zpos}") diff --git a/doc/src/Python_module.rst b/doc/src/Python_module.rst index c19d4b0345..9c60982e1b 100644 --- a/doc/src/Python_module.rst +++ b/doc/src/Python_module.rst @@ -10,19 +10,13 @@ be installed into a Python system folder or a user folder with ``make install-python``. Components of the module can then loaded into a Python session with the ``import`` command. -There are multiple Python interface classes in the :py:mod:`lammps` module: +.. warning:: -- the :py:class:`lammps ` class. This is a wrapper around - the C-library interface and its member functions try to replicate the - :ref:`C-library API ` closely. This is the most - feature-complete Python API. -- the :py:class:`PyLammps ` class. This is a more high-level - and more Python style class implemented on top of the - :py:class:`lammps ` class. -- the :py:class:`IPyLammps ` class is derived from - :py:class:`PyLammps ` and adds embedded graphics - features to conveniently include LAMMPS into `Jupyter - `_ notebooks. + Alternative interfaces such as :py:class:`PyLammps ` and + :py:class:`IPyLammps ` classes have been deprecated and + will be removed in a future version of LAMMPS. The :doc:`Howto_pylammps` has + also been replaced by a reworked :doc:`Howto_python` that showcases how to + use the modern Python API facilities instead. .. _mpi4py_url: https://mpi4py.readthedocs.io @@ -49,7 +43,7 @@ The ``lammps`` class API ======================== The :py:class:`lammps ` class is the core of the LAMMPS -Python interfaces. It is a wrapper around the :ref:`LAMMPS C library +Python interface. It is a wrapper around the :ref:`LAMMPS C library API ` using the `Python ctypes module `_ and a shared library compiled from the LAMMPS sources code. The individual methods in this @@ -64,40 +58,7 @@ functions. Below is a detailed documentation of the API. .. autoclass:: lammps.numpy_wrapper::numpy_wrapper :members: ----------- - -The ``PyLammps`` class API -========================== - -The :py:class:`PyLammps ` class is a wrapper that creates a -simpler, more "Pythonic" interface to common LAMMPS functionality. LAMMPS -data structures are exposed through objects and properties. This makes Python -scripts shorter and more concise. See the :doc:`PyLammps Tutorial -` for an introduction on how to use this interface. - -.. autoclass:: lammps.PyLammps - :members: - -.. autoclass:: lammps.AtomList - :members: - -.. autoclass:: lammps.Atom - :members: - -.. autoclass:: lammps.Atom2D - :members: - ----------- - -The ``IPyLammps`` class API -=========================== - -The :py:class:`IPyLammps ` class is an extension of -:py:class:`PyLammps `, adding additional functions to -quickly display visualizations such as images and videos inside of IPython. -See the :doc:`PyLammps Tutorial ` for examples. - -.. autoclass:: lammps.IPyLammps +.. autoclass:: lammps.ipython::wrapper :members: ---------- diff --git a/doc/src/Python_overview.rst b/doc/src/Python_overview.rst index a13da0d512..85bc0d3bfa 100644 --- a/doc/src/Python_overview.rst +++ b/doc/src/Python_overview.rst @@ -56,7 +56,7 @@ Below is an example output for Python version 3.8.5. --------- -LAMMPS can work together with Python in three ways. First, Python can +LAMMPS can work together with Python in two ways. First, Python can wrap LAMMPS through the its :doc:`library interface `, so that a Python script can create one or more instances of LAMMPS and launch one or more simulations. In Python terms, this is referred to as @@ -67,22 +67,7 @@ launch one or more simulations. In Python terms, this is referred to as Launching LAMMPS via Python - -Second, the lower-level Python interface in the :py:class:`lammps Python -class ` can be used indirectly through the provided -:py:class:`PyLammps ` and :py:class:`IPyLammps -` wrapper classes, also written in Python. These -wrappers try to simplify the usage of LAMMPS in Python by providing a -more object-based interface to common LAMMPS functionality. They also -reduce the amount of code necessary to parameterize LAMMPS scripts -through Python and make variables and computes directly accessible. - -.. figure:: JPG/pylammps-invoke-lammps.png - :figclass: align-center - - Using the PyLammps / IPyLammps wrappers - -Third, LAMMPS can use the Python interpreter, so that a LAMMPS input +Second, 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 diff --git a/doc/utils/requirements.txt b/doc/utils/requirements.txt index acf575fe58..d842f47c11 100644 --- a/doc/utils/requirements.txt +++ b/doc/utils/requirements.txt @@ -9,3 +9,4 @@ Pygments six pyyaml linkchecker +ipython diff --git a/python/examples/pylammps/.gitignore b/python/examples/ipython/.gitignore similarity index 100% rename from python/examples/pylammps/.gitignore rename to python/examples/ipython/.gitignore diff --git a/python/examples/pylammps/README.md b/python/examples/ipython/README.md similarity index 80% rename from python/examples/pylammps/README.md rename to python/examples/ipython/README.md index e66f5a2a8e..ef8356fc6a 100644 --- a/python/examples/pylammps/README.md +++ b/python/examples/ipython/README.md @@ -1,10 +1,10 @@ -# PyLammps and Jupyter Notebooks +# IPython and Jupyter Notebooks -This folder contains examples showcasing the usage of the PyLammps Python +This folder contains examples showcasing the usage of the LAMMPS Python interface and Jupyter notebooks. To use this you will need LAMMPS compiled as a shared library and the LAMMPS Python package installed. -An extensive guide on how to achieve this is documented in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a [PyLammps tutorial](https://docs.lammps.org/Howto_pylammps.html). +An extensive guide on how to achieve this is documented in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a [LAMMPS Python tutorial](https://docs.lammps.org/Howto_python.html). The following will show one way of creating a Python virtual environment which has both LAMMPS and its Python package installed: @@ -53,7 +53,7 @@ which has both LAMMPS and its Python package installed: ```shell (myenv)$ cmake -C ../cmake/presets/basic.cmake \ -D BUILD_SHARED_LIBS=on \ - -D LAMMPS_EXCEPTIONS=on -D PKG_PYTHON=on \ + -D PKG_PYTHON=on \ -D CMAKE_INSTALL_PREFIX=$VIRTUAL_ENV \ ../cmake ``` @@ -67,19 +67,19 @@ which has both LAMMPS and its Python package installed: 8. Install LAMMPS and Python package into virtual environment ```shell - (myenv)$ cmake --install . + (myenv)$ make install-python ``` 9. Install other Python packages into virtual environment ```shell - (myenv)$ pip install jupyter matplotlib mpi4py + (myenv)$ pip install jupyter matplotlib pandas mpi4py ``` -10. Navigate to pylammps examples folder +10. Navigate to ipython examples folder ```shell - (myenv)$ cd ../python/examples/pylammmps + (myenv)$ cd ../python/examples/ipython ``` 11. Launch Jupyter and work inside browser diff --git a/python/examples/pylammps/interface_usage_bonds.ipynb b/python/examples/ipython/atoms.ipynb similarity index 74% rename from python/examples/pylammps/interface_usage_bonds.ipynb rename to python/examples/ipython/atoms.ipynb index 0203ceb5c4..14b60d4e28 100644 --- a/python/examples/pylammps/interface_usage_bonds.ipynb +++ b/python/examples/ipython/atoms.ipynb @@ -4,16 +4,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Example 3: 2D circle of particles inside of box with LJ walls" + "# Example 3: Example 3: Using Atom Data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Prerequisites\n", - "\n", - "Before running this example, make sure your Python environment can find the LAMMPS shared library (`liblammps.so`) and the LAMMPS Python package is installed. If you followed the [README](README.md) in this folder, this should already be the case. You can also find more information about how to compile LAMMPS and install the LAMMPS Python package in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a dedicated [PyLammps HowTo](https://docs.lammps.org/Howto_pylammps.html)." + "Author: [Richard Berger](mailto:richard.berger@outlook.com)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "2D circle of particles inside of box with LJ walls" ] }, { @@ -29,7 +34,7 @@ "metadata": {}, "outputs": [], "source": [ - "from lammps import IPyLammps" + "from lammps import lammps" ] }, { @@ -38,7 +43,8 @@ "metadata": {}, "outputs": [], "source": [ - "L = IPyLammps()" + "L = lammps()\n", + "cmd = L.cmd" ] }, { @@ -60,36 +66,36 @@ "v = 0.3\n", "w = 0.08\n", " \n", - "L.units(\"lj\")\n", - "L.dimension(2)\n", - "L.atom_style(\"bond\")\n", - "L.boundary(\"f f p\")\n", + "cmd.units(\"lj\")\n", + "cmd.dimension(2)\n", + "cmd.atom_style(\"bond\")\n", + "cmd.boundary(\"f f p\")\n", "\n", - "L.lattice(\"hex\", 0.85)\n", - "L.region(\"box\", \"block\", 0, x, 0, y, -0.5, 0.5)\n", - "L.create_box(1, \"box\", \"bond/types\", 1, \"extra/bond/per/atom\", 6)\n", - "L.region(\"circle\", \"sphere\", d/2.0+1.0, d/2.0/math.sqrt(3.0)+1, 0.0, d/2.0)\n", - "L.create_atoms(1, \"region\", \"circle\")\n", - "L.mass(1, 1.0)\n", + "cmd.lattice(\"hex\", 0.85)\n", + "cmd.region(\"box\", \"block\", 0, x, 0, y, -0.5, 0.5)\n", + "cmd.create_box(1, \"box\", \"bond/types\", 1, \"extra/bond/per/atom\", 6)\n", + "cmd.region(\"circle\", \"sphere\", d/2.0+1.0, d/2.0/math.sqrt(3.0)+1, 0.0, d/2.0)\n", + "cmd.create_atoms(1, \"region\", \"circle\")\n", + "cmd.mass(1, 1.0)\n", "\n", - "L.velocity(\"all create 0.5 87287 loop geom\")\n", - "L.velocity(\"all set\", v, w, 0, \"sum yes\")\n", + "cmd.velocity(\"all create 0.5 87287 loop geom\")\n", + "cmd.velocity(\"all set\", v, w, 0, \"sum yes\")\n", "\n", - "L.pair_style(\"lj/cut\", 2.5)\n", - "L.pair_coeff(1, 1, 10.0, 1.0, 2.5)\n", + "cmd.pair_style(\"lj/cut\", 2.5)\n", + "cmd.pair_coeff(1, 1, 10.0, 1.0, 2.5)\n", "\n", - "L.bond_style(\"harmonic\")\n", - "L.bond_coeff(1, 10.0, 1.2)\n", + "cmd.bond_style(\"harmonic\")\n", + "cmd.bond_coeff(1, 10.0, 1.2)\n", "\n", - "L.create_bonds(\"many\", \"all\", \"all\", 1, 1.0, 1.5)\n", + "cmd.create_bonds(\"many\", \"all\", \"all\", 1, 1.0, 1.5)\n", "\n", - "L.neighbor(0.3, \"bin\")\n", - "L.neigh_modify(\"delay\", 0, \"every\", 1, \"check yes\")\n", + "cmd.neighbor(0.3, \"bin\")\n", + "cmd.neigh_modify(\"delay\", 0, \"every\", 1, \"check yes\")\n", "\n", - "L.fix(1, \"all\", \"nve\")\n", + "cmd.fix(1, \"all\", \"nve\")\n", "\n", - "L.fix(2, \"all wall/lj93 xlo 0.0 1 1 2.5 xhi\", x, \"1 1 2.5\")\n", - "L.fix(3, \"all wall/lj93 ylo 0.0 1 1 2.5 yhi\", y, \"1 1 2.5\")" + "cmd.fix(2, \"all wall/lj93 xlo 0.0 1 1 2.5 xhi\", x, \"1 1 2.5\")\n", + "cmd.fix(3, \"all wall/lj93 ylo 0.0 1 1 2.5 yhi\", y, \"1 1 2.5\")" ] }, { @@ -105,7 +111,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.8)" + "L.ipython.image(zoom=1.8)" ] }, { @@ -121,10 +127,10 @@ "metadata": {}, "outputs": [], "source": [ - "L.thermo_style(\"custom step temp epair press\")\n", - "L.thermo(100)\n", - "output = L.run(40000)\n", - "L.image(zoom=1.8)" + "cmd.thermo_style(\"custom step temp epair press\")\n", + "cmd.thermo(100)\n", + "output = cmd.run(40000)\n", + "L.ipython.image(zoom=1.8)" ] }, { @@ -366,7 +372,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.eval(\"ke\")" + "L.expand(\"ke\")" ] }, { @@ -382,7 +388,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[0]" + "L.numpy.extract_atom(\"x\")" ] }, { @@ -391,7 +397,7 @@ "metadata": {}, "outputs": [], "source": [ - "dir(L.atoms[0])" + "L.numpy.extract_atom(\"id\")" ] }, { @@ -400,7 +406,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[0].position" + "L.numpy.extract_atom(\"v\")" ] }, { @@ -409,7 +415,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[0].id" + "L.numpy.extract_atom(\"f\")" ] }, { @@ -418,25 +424,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[0].velocity" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].force" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].type" + "L.numpy.extract_atom(\"type\")" ] }, { @@ -449,7 +437,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -463,9 +451,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" + "version": "3.9.6" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/python/examples/pylammps/dihedrals/data.dihedral b/python/examples/ipython/dihedrals/data.dihedral similarity index 100% rename from python/examples/pylammps/dihedrals/data.dihedral rename to python/examples/ipython/dihedrals/data.dihedral diff --git a/python/examples/pylammps/dihedrals/dihedral.ipynb b/python/examples/ipython/dihedrals/dihedral.ipynb similarity index 75% rename from python/examples/pylammps/dihedrals/dihedral.ipynb rename to python/examples/ipython/dihedrals/dihedral.ipynb index 240e3e8bd6..4fece8aa58 100644 --- a/python/examples/pylammps/dihedrals/dihedral.ipynb +++ b/python/examples/ipython/dihedrals/dihedral.ipynb @@ -13,7 +13,8 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib notebook" + "import matplotlib.pyplot as plt\n", + "from lammps import lammps" ] }, { @@ -22,25 +23,8 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from lammps import IPyLammps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L = IPyLammps()" + "L = lammps()\n", + "cmd = L.cmd" ] }, { @@ -51,13 +35,13 @@ "source": [ "import math\n", "\n", - "L.units(\"real\")\n", - "L.atom_style(\"molecular\")\n", + "cmd.units(\"real\")\n", + "cmd.atom_style(\"molecular\")\n", "\n", - "L.boundary(\"f f f\")\n", - "L.neighbor(0.3, \"bin\")\n", + "cmd.boundary(\"f f f\")\n", + "cmd.neighbor(0.3, \"bin\")\n", "\n", - "L.dihedral_style(\"harmonic\")" + "cmd.dihedral_style(\"harmonic\")" ] }, { @@ -66,7 +50,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.read_data(\"data.dihedral\")" + "cmd.read_data(\"data.dihedral\")" ] }, { @@ -75,8 +59,8 @@ "metadata": {}, "outputs": [], "source": [ - "L.pair_style(\"zero\", 5)\n", - "L.pair_coeff(\"*\", \"*\")" + "cmd.pair_style(\"zero\", 5)\n", + "cmd.pair_coeff(\"*\", \"*\")" ] }, { @@ -85,7 +69,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.mass(1, 1.0)" + "cmd.mass(1, 1.0)" ] }, { @@ -94,7 +78,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.velocity(\"all\", \"set\", 0.0, 0.0, 0.0)" + "cmd.velocity(\"all\", \"set\", 0.0, 0.0, 0.0)" ] }, { @@ -103,7 +87,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.run(0);" + "cmd.run(0);" ] }, { @@ -112,7 +96,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.0)" + "L.ipython.image(zoom=1.0,size=[320,320])" ] }, { @@ -121,7 +105,8 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[3].position" + "x = L.numpy.extract_atom(\"x\")\n", + "print(x[3])" ] }, { @@ -130,7 +115,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[3].position = (1.0, 0.0, 1.0)" + "x[3] = (1.0, 0.0, 1.0)" ] }, { @@ -139,7 +124,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.0)" + "L.ipython.image(zoom=1.0,size=[320,320])" ] }, { @@ -148,7 +133,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.eval(\"pe\")" + "L.get_thermo(\"pe\")" ] }, { @@ -157,7 +142,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.atoms[3].position = (1.0, 0.0, -1.0)" + "x[3] = (1.0, 0.0, -1.0)" ] }, { @@ -166,7 +151,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.run(0);" + "cmd.run(0)" ] }, { @@ -207,9 +192,9 @@ "source": [ "pe = []\n", "for p in pos:\n", - " L.atoms[3].position = p\n", - " L.run(0);\n", - " pe.append(L.eval(\"pe\"))" + " x[3] = p\n", + " cmd.run(0);\n", + " pe.append(L.get_thermo(\"pe\"))" ] }, { @@ -233,7 +218,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -247,9 +232,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" + "version": "3.9.6" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/python/examples/pylammps/elastic/Au.data b/python/examples/ipython/elastic/Au.data similarity index 100% rename from python/examples/pylammps/elastic/Au.data rename to python/examples/ipython/elastic/Au.data diff --git a/python/examples/pylammps/elastic/README b/python/examples/ipython/elastic/README similarity index 100% rename from python/examples/pylammps/elastic/README rename to python/examples/ipython/elastic/README diff --git a/python/examples/pylammps/elastic/elastic.py b/python/examples/ipython/elastic/elastic.py similarity index 99% rename from python/examples/pylammps/elastic/elastic.py rename to python/examples/ipython/elastic/elastic.py index 48f97925da..e69b5394bf 100644 --- a/python/examples/pylammps/elastic/elastic.py +++ b/python/examples/ipython/elastic/elastic.py @@ -158,7 +158,8 @@ def elastic(): parser.add_argument("--up", type=float, default=1.0e-6, help="the deformation magnitude (in strain units)") args = parser.parse_args() - L = PyLammps() + lmp = lammps() + L = lmp.cmd L.units("metal") diff --git a/python/examples/ipython/index.ipynb b/python/examples/ipython/index.ipynb new file mode 100644 index 0000000000..9fdac385fd --- /dev/null +++ b/python/examples/ipython/index.ipynb @@ -0,0 +1,61 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "666d3036-47d5-44d2-bc1a-ca4b00a9e9b8", + "metadata": {}, + "source": [ + "# LAMMPS IPython Tutorial" + ] + }, + { + "cell_type": "markdown", + "id": "f1422a43-f76b-456b-bf76-61ad92bd4ff0", + "metadata": {}, + "source": [ + "Author: [Richard Berger](mailto:richard.berger@outlook.com)" + ] + }, + { + "cell_type": "markdown", + "id": "8f2ea92d-8cc3-4999-81a0-79aa55bb66ab", + "metadata": {}, + "source": [ + "## Contents\n", + "\n", + "- [Example 1: Using LAMMPS with Python](simple.ipynb)\n", + "- [Example 2: Analyzing LAMMPS thermodynamic data](thermo.ipynb)\n", + "- [Example 3: Using Atom Data](atom.ipynb)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b41dc533-be6d-4450-8ad7-7345e9f44ea3", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/python/examples/pylammps/montecarlo/mc.ipynb b/python/examples/ipython/montecarlo/mc.ipynb similarity index 75% rename from python/examples/pylammps/montecarlo/mc.ipynb rename to python/examples/ipython/montecarlo/mc.ipynb index 8058a9eb41..b1cfa488eb 100644 --- a/python/examples/pylammps/montecarlo/mc.ipynb +++ b/python/examples/ipython/montecarlo/mc.ipynb @@ -13,16 +13,6 @@ "metadata": {}, "outputs": [], "source": [ - "from __future__ import print_function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", "import matplotlib.pyplot as plt" ] }, @@ -48,7 +38,7 @@ "metadata": {}, "outputs": [], "source": [ - "from lammps import IPyLammps" + "from lammps import lammps" ] }, { @@ -57,7 +47,8 @@ "metadata": {}, "outputs": [], "source": [ - "L = IPyLammps()" + "L = lammps()\n", + "cmd = L.cmd" ] }, { @@ -66,25 +57,25 @@ "metadata": {}, "outputs": [], "source": [ - "L.units(\"lj\")\n", - "L.atom_style(\"atomic\")\n", - "L.atom_modify(\"map array sort\", 0, 0.0)\n", + "cmd.units(\"lj\")\n", + "cmd.atom_style(\"atomic\")\n", + "cmd.atom_modify(\"map array sort\", 0, 0.0)\n", "\n", - "L.dimension(2)\n", + "cmd.dimension(2)\n", "\n", - "L.lattice(\"hex\", 1.0)\n", - "L.region(\"box block\", 0, 10, 0, 5, -0.5, 0.5)\n", + "cmd.lattice(\"hex\", 1.0)\n", + "cmd.region(\"box block\", 0, 10, 0, 5, -0.5, 0.5)\n", "\n", - "L.create_box(1, \"box\")\n", - "L.create_atoms(1, \"box\")\n", - "L.mass(1, 1.0)\n", + "cmd.create_box(1, \"box\")\n", + "cmd.create_atoms(1, \"box\")\n", + "cmd.mass(1, 1.0)\n", "\n", - "L.pair_style(\"lj/cut\", 2.5)\n", - "L.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", - "L.pair_modify(\"shift\", \"yes\")\n", + "cmd.pair_style(\"lj/cut\", 2.5)\n", + "cmd.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", + "cmd.pair_modify(\"shift\", \"yes\")\n", "\n", - "L.neighbor(0.3, \"bin\")\n", - "L.neigh_modify(\"delay\", 0, \"every\", 1, \"check\", \"yes\")" + "cmd.neighbor(0.3, \"bin\")\n", + "cmd.neigh_modify(\"delay\", 0, \"every\", 1, \"check\", \"yes\")" ] }, { @@ -93,7 +84,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.6)" + "L.ipython.image(zoom=1.6,size=[320,320])" ] }, { @@ -102,7 +93,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.run(0);" + "cmd.run(0)" ] }, { @@ -111,7 +102,7 @@ "metadata": {}, "outputs": [], "source": [ - "emin = L.eval(\"pe\")" + "emin = L.get_thermo(\"pe\")" ] }, { @@ -120,7 +111,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.dump(\"3 all movie 25 movie.mp4 type type zoom 1.6 adiam 1.0\")" + "cmd.dump(\"3 all movie 25 movie.mp4 type type zoom 1.6 adiam 1.0\")" ] }, { @@ -146,11 +137,12 @@ "metadata": {}, "outputs": [], "source": [ - "for i in range(L.system.natoms):\n", - " x, y = L.atoms[i].position\n", + "pos = L.numpy.extract_atom(\"x\")\n", + "for i in range(len(pos)):\n", + " x, y = pos[i][0], pos[i][1]\n", " dx = deltaperturb * random.uniform(-1, 1)\n", " dy = deltaperturb * random.uniform(-1, 1)\n", - " L.atoms[i].position = (x+dx, y+dy)" + " pos[i] = (x+dx, y+dy, 0)" ] }, { @@ -159,7 +151,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.run(0);" + "cmd.run(0)" ] }, { @@ -168,7 +160,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.6)" + "L.ipython.image(zoom=1.6,size=[320,320])" ] }, { @@ -184,7 +176,7 @@ "metadata": {}, "outputs": [], "source": [ - "estart = L.eval(\"pe\")\n", + "estart = L.get_thermo(\"pe\")\n", "elast = estart" ] }, @@ -223,22 +215,23 @@ "metadata": {}, "outputs": [], "source": [ - "natoms = L.system.natoms\n", + "natoms = L.extract_global(\"natoms\")\n", "\n", "for i in range(niterations):\n", + " pos = L.numpy.extract_atom(\"x\")\n", " iatom = random.randrange(0, natoms)\n", - " current_atom = L.atoms[iatom]\n", + " current_atom = pos[iatom]\n", " \n", - " x0, y0 = current_atom.position\n", + " x0, y0 = current_atom[0], current_atom[1]\n", " \n", " dx = deltamove * random.uniform(-1, 1)\n", " dy = deltamove * random.uniform(-1, 1)\n", " \n", - " current_atom.position = (x0+dx, y0+dy)\n", + " pos[iatom] = (x0+dx, y0+dy, 0)\n", " \n", - " L.run(1, \"pre no post no\")\n", + " cmd.run(1, \"pre no post no\")\n", " \n", - " e = L.eval(\"pe\")\n", + " e = L.get_thermo(\"pe\")\n", " energies.append(e)\n", " \n", " if e <= elast:\n", @@ -248,7 +241,7 @@ " naccept += 1\n", " elast = e\n", " else:\n", - " current_atom.position = (x0, y0)" + " pos[iatom] = (x0, y0, 0)" ] }, { @@ -268,7 +261,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.eval(\"pe\")" + "L.get_thermo(\"pe\")" ] }, { @@ -304,7 +297,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.6)" + "L.ipython.image(zoom=1.6, size=[320,320])" ] }, { @@ -314,7 +307,7 @@ "outputs": [], "source": [ "# close dump file to access it\n", - "L.undump(3)" + "cmd.undump(3)" ] }, { @@ -323,7 +316,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.video(\"movie.mp4\")" + "L.ipython.video(\"movie.mp4\")" ] }, { @@ -336,7 +329,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -350,9 +343,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" + "version": "3.9.6" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/python/examples/pylammps/mpi4py/hello.py b/python/examples/ipython/mpi4py/hello.py similarity index 100% rename from python/examples/pylammps/mpi4py/hello.py rename to python/examples/ipython/mpi4py/hello.py diff --git a/python/examples/pylammps/mpi4py/in.melt b/python/examples/ipython/mpi4py/in.melt similarity index 100% rename from python/examples/pylammps/mpi4py/in.melt rename to python/examples/ipython/mpi4py/in.melt diff --git a/python/examples/pylammps/mpi4py/melt.py b/python/examples/ipython/mpi4py/melt.py similarity index 61% rename from python/examples/pylammps/mpi4py/melt.py rename to python/examples/ipython/mpi4py/melt.py index ad9c54c0b5..e51a914775 100644 --- a/python/examples/pylammps/mpi4py/melt.py +++ b/python/examples/ipython/mpi4py/melt.py @@ -1,10 +1,10 @@ from mpi4py import MPI -from lammps import PyLammps +from lammps import lammps -L = PyLammps() +L = lammps() L.file('in.melt') if MPI.COMM_WORLD.rank == 0: - pe = L.eval("pe") + pe = L.get_thermo("pe") print("Potential Energy:", pe) diff --git a/python/examples/pylammps/simple.ipynb b/python/examples/ipython/simple.ipynb similarity index 53% rename from python/examples/pylammps/simple.ipynb rename to python/examples/ipython/simple.ipynb index 170a33ebbc..d45c56db18 100644 --- a/python/examples/pylammps/simple.ipynb +++ b/python/examples/ipython/simple.ipynb @@ -4,14 +4,28 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Example 1: Using LAMMPS with PyLammps" + "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The LAMMPS Python package provides multiple interfaces. The `PyLammps` interface is a high-level abstration of the low-level `lammps` interface. `IPyLammps` further extends this interface with functions that are useful for Jupyter notebooks to enable embedding generated graphics and videos." + "# Example 1: Using LAMMPS with Python" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Author: [Richard Berger](mailto:richard.berger@outlook.com)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The LAMMPS Python package enables calling the LAMMPS C library API." ] }, { @@ -20,7 +34,7 @@ "source": [ "## Prerequisites\n", "\n", - "Before Running this example, make sure your Python environment can find the LAMMPS shared library (`liblammps.so`) and the LAMMPS Python package is installed. If you followed the [README](README.md) in this folder, this should already be the case. You can also find more information about how to compile LAMMPS and install the LAMMPS Python package in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a dedicated [PyLammps HowTo](https://docs.lammps.org/Howto_pylammps.html)." + "Before running this example, make sure your Python environment can find the LAMMPS shared library (`liblammps.so`) and the LAMMPS Python package is installed. If you followed the [README](README.md) in this folder, this should already be the case. You can also find more information about how to compile LAMMPS and install the LAMMPS Python package in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a dedicated [LAMMPS Python HowTo](https://docs.lammps.org/Howto_python.html)." ] }, { @@ -38,17 +52,17 @@ "metadata": {}, "outputs": [], "source": [ - "from lammps import IPyLammps\n", - "L = IPyLammps()" + "from lammps import lammps\n", + "L = lammps()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "With `PyLammps`/`IPyLammps` you can write LAMMPS simulations similar to the input script language. Take the following LAMMPS input script:\n", + "With the `lammps` class you can write LAMMPS simulations similar to the input script language. Take the following LAMMPS input script:\n", "\n", - "```bash\n", + "```lammps\n", "# 3d Lennard-Jones melt\n", "\n", "units lj\n", @@ -72,7 +86,7 @@ "\n", "thermo 50\n", "```\n", - "The equivalent can be written with `PyLammps`/`IPyLammps`:" + "The equivalent can be written in Python:" ] }, { @@ -83,35 +97,33 @@ "source": [ "# 3d Lennard-Jones melt\n", "\n", - "L.units(\"lj\")\n", - "L.atom_style(\"atomic\")\n", + "L.cmd.units(\"lj\")\n", + "L.cmd.atom_style(\"atomic\")\n", "\n", - "L.lattice(\"fcc\", 0.8442)\n", - "L.region(\"box\", \"block\", 0, 4, 0, 4, 0, 4)\n", - "L.create_box(1, \"box\")\n", - "L.create_atoms(1, \"box\")\n", - "L.mass(1, 1.0)\n", + "L.cmd.lattice(\"fcc\", 0.8442)\n", + "L.cmd.region(\"box\", \"block\", 0, 4, 0, 4, 0, 4)\n", + "L.cmd.create_box(1, \"box\")\n", + "L.cmd.create_atoms(1, \"box\")\n", + "L.cmd.mass(1, 1.0)\n", "\n", - "L.velocity(\"all\", \"create\", 1.44, 87287, \"loop geom\")\n", + "L.cmd.velocity(\"all\", \"create\", 1.44, 87287, \"loop geom\")\n", "\n", - "L.pair_style(\"lj/cut\", 2.5)\n", - "L.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", + "L.cmd.pair_style(\"lj/cut\", 2.5)\n", + "L.cmd.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", "\n", - "L.neighbor(0.3, \"bin\")\n", - "L.neigh_modify(\"delay\", 0, \"every\", 20, \"check no\")\n", + "L.cmd.neighbor(0.3, \"bin\")\n", + "L.cmd.neigh_modify(\"delay\", 0, \"every\", 20, \"check no\")\n", "\n", - "L.fix(\"1\", \"all\", \"nve\")\n", + "L.cmd.fix(\"1\", \"all\", \"nve\")\n", "\n", - "L.thermo(50)" + "L.cmd.thermo(50)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Visualizing the initial state\n", - "\n", - "`IPyLammps` allows you to visualize the current simulation state with the [image](https://docs.lammps.org/Python_module.html#lammps.IPyLammps.image) command. Here we use it to create an image of the initial state of the system." + "Some LAMMPS commands will produce output that will be visible in the notebook. However, due to buffering, it might not be shown right away. Use the `flush_buffers` method to see all the output that has been written so far." ] }, { @@ -120,7 +132,109 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.0)" + "L.flush_buffers()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "An alternative to this is to enable auto flushing after each command by setting `cmd.auto_flush` to `True`. Each command will then call `flush_buffers()` automatically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "L.cmd.auto_flush = True\n", + "L.cmd.info(\"system\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In many cases the LAMMPS output will become excessive, which is why you may want to suppress it. For this purpose we provide a IPython extension in the `lammps.ipython` package. To load the extension, add a code cell with the following content:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext lammps.ipython" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the extension is loaded you have access to the `%%capture_lammps_output` magic. In its simplest form it can be used to supress LAMMPS output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture_lammps_output\n", + "L.cmd.info(\"system\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also use the same `%%capture_lammps_output` magic to store the output in a variable by providing a variable name:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture_lammps_output out\n", + "L.cmd.info(\"system\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In this case we are storing the output in a `out` variable. Note the output is only available after the cell has been executed, not within the same cell." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(out)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Visualizing the initial state\n", + "\n", + "The `lammps` class also has an `ipython` attribute which provides some basic visualization capabilities in IPython Jupyter notebooks. E.g., you can visualize the current simulation state with the [image](https://docs.lammps.org/Python_module.html#lammps.ipython_wrapper.image) command. Here we use it to create an image of the initial state of the system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "L.ipython.image()" ] }, { @@ -129,7 +243,7 @@ "source": [ "## Running simulations\n", "\n", - "Use the `run` command to start the simulation. In Jupyter the return value of the last command will be displayed. The `run` command will return the output of the simulation." + "Use the `run` command to start the simulation. It will print the output of the simulation." ] }, { @@ -138,23 +252,7 @@ "metadata": {}, "outputs": [], "source": [ - "L.run(150)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can suppress it by adding a semicolon `;`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.run(100);" + "L.cmd.run(250)" ] }, { @@ -170,127 +268,23 @@ "metadata": {}, "outputs": [], "source": [ - "L.image(zoom=1.0)" + "L.ipython.image(zoom=1.0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Post-processing thermo output\n", + "## Conclusion\n", + "This covered the basics of creating an instance of LAMMPS from Python, passing commands to LAMMPS and potentially supressing or capturing its output, and visualizing the system. In the [following tutorial](thermo.ipynb) we will look at how to process thermodynamic output from LAMMPS.\n", "\n", - "Independent of whether or not you suppress or show the output of the `run` command, `PyLammps` will record the output. Each `run` command creates a new entry in the `L.runs` list. So far our PyLammps instance `L` executed two `run` commands:" + "
Next" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "len(L.runs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Each entry contains information about the simulation run, including the thermo output for the printed out time steps.\n", - "\n", - "```bash\n", - "# thermo output of a LAMMPS simulation run\n", - "Step Temp E_pair E_mol TotEng Press\n", - " 0 1.44 -6.7733681 0 -4.6218056 -5.0244179\n", - " 50 0.70303849 -5.6796164 0 -4.629178 0.50453907\n", - " 100 0.72628044 -5.7150774 0 -4.6299123 0.29765862\n", - " 150 0.78441711 -5.805142 0 -4.6331125 -0.086709661\n", - "```\n", - "\n", - "`PyLammps` already parses this information and makes it available as dictionaries and arrays." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[1]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For example, the first run was 150 time steps, with printing out a line every 50 steps. You can access the list of time steps using `{entry}.thermo.Step`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0].thermo.Step" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The corresponding values of each thermo quantity are also accessed this way:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0].thermo.TotEng" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Together you can use this information to run post-processing on these values or even plot it using `matplotlib`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "\n", - "plt.xlabel('time step')\n", - "plt.ylabel('Total Energy')\n", - "plt.plot(L.runs[0].thermo.Step, L.runs[0].thermo.TotEng)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -304,9 +298,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.2" + "version": "3.9.6" } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 4 } diff --git a/python/examples/ipython/thermo.ipynb b/python/examples/ipython/thermo.ipynb new file mode 100644 index 0000000000..ea465f5f79 --- /dev/null +++ b/python/examples/ipython/thermo.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 2: Analyzing LAMMPS thermodynamic data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Author: [Richard Berger](mailto:richard.berger@outlook.com)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This tutorial assumes you've completed the [first example](simple.ipynb) and understand the basics of running LAMMPS through Python. In this tutorial we will build on top of that example and look at how to extract thermodynamic data produced by LAMMPS into Python and visualize it. Let's first start by recreating our simple melt example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext lammps.ipython" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from lammps import lammps\n", + "L = lammps()\n", + "L.cmd.auto_flush = True\n", + "\n", + "def init_melt_system(L):\n", + " # 3d Lennard-Jones melt\n", + " L.cmd.clear()\n", + " L.cmd.units(\"lj\")\n", + " L.cmd.atom_style(\"atomic\")\n", + " \n", + " L.cmd.lattice(\"fcc\", 0.8442)\n", + " L.cmd.region(\"box\", \"block\", 0, 4, 0, 4, 0, 4)\n", + " L.cmd.create_box(1, \"box\")\n", + " L.cmd.create_atoms(1, \"box\")\n", + " L.cmd.mass(1, 1.0)\n", + " \n", + " L.cmd.velocity(\"all\", \"create\", 1.44, 87287, \"loop geom\")\n", + " \n", + " L.cmd.pair_style(\"lj/cut\", 2.5)\n", + " L.cmd.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", + " \n", + " L.cmd.neighbor(0.3, \"bin\")\n", + " L.cmd.neigh_modify(\"delay\", 0, \"every\", 20, \"check no\")\n", + " \n", + " L.cmd.fix(\"1\", \"all\", \"nve\")\n", + " \n", + " L.cmd.thermo(50)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we take advantage of the fact that we can write regular Python functions to organize our LAMMPS simulation. This allows us to clear and initialize a new system by calling the `init_melt_system()` function. With this we can now go ahead an run this simulation for 100 steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "init_melt_system(L)\n", + "L.cmd.run(100)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Extracting thermodynamic data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Looking at the above output we see that LAMMPS prints out thermodynamic data for steps 0, 50 and 100.\n", + "\n", + "```\n", + " Step Temp E_pair E_mol TotEng Press \n", + " 0 1.44 -6.7733681 0 -4.6218056 -5.0244179 \n", + " 50 0.70303849 -5.6796164 0 -4.629178 0.50453907 \n", + " 100 0.72628044 -5.7150774 0 -4.6299123 0.29765862\n", + "```\n", + "\n", + "We could parse the text output and extract the necessary information, but this has proven to be error-prone and clunky, especially in cases where other output gets interleaved with thermo output lines. Instead, we can make use of the Python integration within LAMMPS to execute arbitrary Python code during time steps using `fix python/invoke`. We can extract the thermodynamic data directly using the LAMMPS Python interface and process it in any way we want.\n", + "\n", + "For this we first define the data structure we want to use to store the data. For each column of the thermodynamic data we want to store a list of values for each time step. Let's use a Python `dict` with the following structure:\n", + "\n", + "```python\n", + "{'Step': [0, 50, 100, ...], 'Temp': [...], 'E_pair': [...], 'E_mol': [...], 'TotEng': [...], 'Press': [...]}\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To start, let's define an empty `dict` and call it `current_run`. As the simulation progresses, we append new data into this dictionary:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "current_run = {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's define a function that should be executed every time step a thermodynamic output line would be written. This function takes a `lammps` class instance and through it can access LAMMPS state and data. We can use the [`last_thermo()`](https://docs.lammps.org/Python_module.html#lammps.lammps.last_thermo) function of the `lammps` class to get the latest thermodynamic data as a dictionary. This data is all we need to populate our `current_run` data structure." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def append_thermo_data(lmp):\n", + " for k, v in lmp.last_thermo().items():\n", + " current_run.setdefault(k, []).append(v)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With these two pieces in place, it is now time to tell LAMMPS about how we want to call this function.\n", + "\n", + "First, let's suppress any LAMMPS output via `%%capture_lammps_output` and reinitialize our system with `init_melt_system()` so our system is back in its initial state and the time step is back to 0.\n", + "\n", + "Next, we add a new fix `python/invoke` that should execute every 50 time steps, the same as our `thermo 50` command above. At the end of every 50 time steps (including the first one), it should call the `append_thermo_data` function we just defined. Notice we can just pass the function as parameter. Finally, we tell LAMMPS to run for 250 steps." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture_lammps_output\n", + "init_melt_system(L)\n", + "L.cmd.fix(\"myfix\", \"all\", \"python/invoke\", 50, \"end_of_step\", append_thermo_data)\n", + "L.cmd.run(250)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's inspect our `current_run` dictionary after the run has completed:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "current_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see, the time steps 0, 50, 100, 150, and 200 were added to dictionary. However, the last time step 250 is still missing. For this we need to manually add a final call to our `append_thermo_data()` helper function." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "append_thermo_data(L)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With this our `current_run` dictionary now has all the data of the completed run:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "current_run" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Plotting thermodynamic data with matplotlib\n", + "\n", + "Now that we have our data available as Python variables, we can easily use other libraries for visualization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "\n", + "plt.xlabel('time step')\n", + "plt.ylabel('Total Energy')\n", + "plt.plot(current_run['Step'], current_run['TotEng'])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Using Pandas library\n", + "\n", + "Since we can call any Python code from LAMMPS, the above example can also be rewritten using the Pandas library:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture_lammps_output\n", + "import pandas as pd\n", + "\n", + "current_run = pd.DataFrame()\n", + "\n", + "def append_thermo_data(lmp):\n", + " global current_run\n", + " current_time_step = pd.DataFrame.from_records([lmp.last_thermo()])\n", + " current_run = pd.concat([current_run, current_time_step], ignore_index=True)\n", + "\n", + "init_melt_system(L)\n", + "L.cmd.fix(\"myfix\", \"all\", \"python/invoke\", 50, \"end_of_step\", append_thermo_data)\n", + "L.cmd.run(250)\n", + "append_thermo_data(L)\n", + "current_run" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "current_run.plot(x='Step', y='TotEng')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/python/examples/pylammps/interface_usage.ipynb b/python/examples/pylammps/interface_usage.ipynb deleted file mode 100644 index 18902caec9..0000000000 --- a/python/examples/pylammps/interface_usage.ipynb +++ /dev/null @@ -1,546 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Example 2: Using the PyLammps interface" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Prerequisites\n", - "\n", - "Before running this example, make sure your Python environment can find the LAMMPS shared library (`liblammps.so`) and the LAMMPS Python package is installed. If you followed the [README](README.md) in this folder, this should already be the case. You can also find more information about how to compile LAMMPS and install the LAMMPS Python package in the [LAMMPS manual](https://docs.lammps.org/Python_install.html). There is also a dedicated [PyLammps HowTo](https://docs.lammps.org/Howto_pylammps.html)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup system" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from lammps import IPyLammps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L = IPyLammps()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# 3d Lennard-Jones melt\n", - "L.units(\"lj\")\n", - "L.atom_style(\"atomic\")\n", - "L.atom_modify(\"map array\")\n", - "\n", - "L.lattice(\"fcc\", 0.8442)\n", - "L.region(\"box block\", 0, 4, 0, 4, 0, 4)\n", - "L.create_box(1, \"box\")\n", - "L.create_atoms(1, \"box\")\n", - "L.mass(1, 1.0)\n", - "\n", - "L.velocity(\"all create\", 1.44, 87287, \"loop geom\")\n", - "\n", - "L.pair_style(\"lj/cut\", 2.5)\n", - "L.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", - "\n", - "L.neighbor(0.3, \"bin\")\n", - "L.neigh_modify(\"delay 0 every 20 check no\")\n", - "\n", - "L.fix(\"1 all nve\")\n", - "\n", - "L.variable(\"fx atom fx\")\n", - "\n", - "L.run(10)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Visualize the initial state" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.image(zoom=1)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Queries about LAMMPS simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system.natoms" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.communication" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.fixes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.computes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.dumps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.groups" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Working with LAMMPS Variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"a index 2\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"t equal temp\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "\n", - "if sys.version_info < (3, 0):\n", - " # In Python 2 'print' is a restricted keyword, which is why you have to use the lmp_print function instead.\n", - " x = float(L.lmp_print('\"${a}\"'))\n", - "else:\n", - " # In Python 3 the print function can be redefined.\n", - " # x = float(L.print('\"${a}\"')\")\n", - " \n", - " # To avoid a syntax error in Python 2 executions of this notebook, this line is packed into an eval statement\n", - " x = float(eval(\"L.print('\\\"${a}\\\"')\"))\n", - "x" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['t'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.eval(\"v_t/2.0\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"b index a b c\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['b'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.eval(\"v_b\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['b'].definition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.lmp.command('variable i loop 10')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"i loop 10\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['i'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.next(\"i\")\n", - "L.variables['i'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.eval(\"ke\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Accessing Atom data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dir(L.atoms[0])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].position" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].id" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].velocity" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].force" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.atoms[0].type" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['fx'].value" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Accessing thermo data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0].thermo" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.runs[0].thermo" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "dir(L.runs[0].thermo)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Saving session to as LAMMPS input file" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "PyLammps can keep track of all LAMMPS commands that are executed. This allows you to prototype a script and then later on save it as a regular input script:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L = IPyLammps()\n", - "\n", - "# enable command history\n", - "L.enable_cmd_history = True\n", - "\n", - "# 3d Lennard-Jones melt\n", - "L.units(\"lj\")\n", - "L.atom_style(\"atomic\")\n", - "L.atom_modify(\"map array\")\n", - "\n", - "L.lattice(\"fcc\", 0.8442)\n", - "L.region(\"box block\", 0, 4, 0, 4, 0, 4)\n", - "L.create_box(1, \"box\")\n", - "L.create_atoms(1, \"box\")\n", - "L.mass(1, 1.0)\n", - "\n", - "L.velocity(\"all create\", 1.44, 87287, \"loop geom\")\n", - "\n", - "L.pair_style(\"lj/cut\", 2.5)\n", - "L.pair_coeff(1, 1, 1.0, 1.0, 2.5)\n", - "\n", - "L.neighbor(0.3, \"bin\")\n", - "L.neigh_modify(\"delay 0 every 20 check no\")\n", - "\n", - "L.fix(\"1 all nve\")\n", - "\n", - "L.run(10)\n", - "\n", - "# write LAMMPS input script with all commands executed so far (including implicit ones)\n", - "L.write_script(\"in.output\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!cat in.output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.2" - } - }, - "nbformat": 4, - "nbformat_minor": 1 -} diff --git a/python/lammps/core.py b/python/lammps/core.py index 30df44f050..510385c175 100644 --- a/python/lammps/core.py +++ b/python/lammps/core.py @@ -59,6 +59,87 @@ class ExceptionCheck: # ------------------------------------------------------------------------- +class command_wrapper(object): + def __init__(self, lmp): + self.lmp = lmp + self.auto_flush = False + + def lmp_print(self, s): + """ needed for Python2 compatibility, since print is a reserved keyword """ + return self.__getattr__("print")(s) + + def __dir__(self): + 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', + 'group', 'improper_coeff', 'improper_style', 'include', 'kspace_modify', + 'kspace_style', 'lattice', 'mass', 'minimize', 'min_style', 'neighbor', + 'neigh_modify', 'newton', 'nthreads', 'pair_coeff', 'pair_modify', + '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'] + self.lmp.available_styles("command"))) + + def _wrap_args(self, x): + if callable(x): + if sys.version_info < (3,): + raise Exception("Passing functions or lambdas directly as arguments is only supported in Python 3 or newer") + import hashlib + import __main__ + sha = hashlib.sha256() + sha.update(str(x).encode()) + func_name = f"_lmp_cb_{sha.hexdigest()}" + def handler(*args, **kwargs): + args = list(args) + args[0] = lammps(ptr=args[0]) + x(*args) + setattr(__main__, func_name, handler) + return func_name + return x + + def __getattr__(self, name): + """ + This method is where the Python 'magic' happens. If a method is not + defined by the class command_wrapper, it assumes it is a LAMMPS command. It takes + all the arguments, concatinates them to a single string, and executes it using + :py:meth:`lammps.command()`. + + Starting with Python 3.6 it also supports keyword arguments. key=value is + transformed into 'key value'. Note, since these have come last in the + parameter list, only a subset of LAMMPS commands can be used with this + syntax. + + LAMMPS commands that accept callback functions (such as fix python/invoke) + can be passed functions and lambdas directly. The first argument of such + callbacks will be an lammps object constructed from the passed LAMMPS + pointer. + + :return: line or list of lines of output, None if no output + :rtype: list or string + """ + def handler(*args, **kwargs): + cmd_args = [name] + [str(self._wrap_args(x)) for x in args] + + if len(kwargs) > 0 and sys.version_info < (3,6): + raise Exception("Keyword arguments are only supported in Python 3.6 or newer") + + # Python 3.6+ maintains ordering of kwarg keys + for k in kwargs.keys(): + cmd_args.append(k) + if type(kwargs[k]) == bool: + cmd_args.append("true" if kwargs[k] else "false") + else: + cmd_args.append(str(self._wrap_args(kwargs[k]))) + + cmd = ' '.join(cmd_args) + self.lmp.command(cmd) + if self.auto_flush: + self.lmp.flush_buffers() + return handler + +# ------------------------------------------------------------------------- + class lammps(object): """Create an instance of the LAMMPS Python class. @@ -103,6 +184,8 @@ class lammps(object): winpath = os.environ.get("LAMMPSDLLPATH") self.lib = None self.lmp = None + self._cmd = None + self._ipython = None # if a pointer to a LAMMPS object is handed in # when being called from a Python interpreter @@ -509,6 +592,61 @@ class lammps(object): # ------------------------------------------------------------------------- + @property + def cmd(self): + """ Return object that acts as LAMMPS command wrapper + + It provides alternative to :py:meth:`lammps.command` to call LAMMPS + commands as if they were regular Python functions and enables auto-complete + in interactive Python sessions. + + .. code-block:: python + + from lammps import lammps + + # melt example + L = lammps() + L.cmd.units("lj") + L.cmd.atom_style("atomic") + L.cmd.lattice("fcc", 0.8442) + L.cmd.region("box block", 0, 10, 0, 10, 0, 10) + L.cmd.create_box(1, "box") + L.cmd.create_atoms(1, "box") + L.cmd.mass(1, 1.0) + L.cmd.velocity("all create", 3.0, 87287, "loop geom") + L.cmd.pair_style("lj/cut", 2.5) + L.cmd.pair_coeff(1, 1, 1.0, 1.0, 2.5) + L.cmd.neighbor(0.3, "bin") + L.cmd.neigh_modify(every=20, delay=0, check=False) + L.cmd.fix(1, "all nve") + L.cmd.thermo(50) + L.cmd.run(250) + + :return: instance of command_wrapper object + :rtype: command_wrapper + """ + if not self._cmd: + self._cmd = command_wrapper(self) + return self._cmd + + # ------------------------------------------------------------------------- + + @property + def ipython(self): + """ Return object to access ipython extensions + + Adds commands for visualization in IPython and Jupyter Notebooks. + + :return: instance of ipython wrapper object + :rtype: ipython.wrapper + """ + if not self._ipython: + from .ipython import wrapper + self._ipython = wrapper(self) + return self._ipython + + # ------------------------------------------------------------------------- + def close(self): """Explicitly delete a LAMMPS instance through the C-library interface. diff --git a/python/lammps/ipython/__init__.py b/python/lammps/ipython/__init__.py new file mode 100644 index 0000000000..c07a5ff5e5 --- /dev/null +++ b/python/lammps/ipython/__init__.py @@ -0,0 +1,23 @@ +# ---------------------------------------------------------------------- +# LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator +# https://www.lammps.org/ Sandia National Laboratories +# LAMMPS Development team: developers@lammps.org +# +# Copyright (2003) Sandia Corporation. Under the terms of Contract +# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains +# certain rights in this software. This software is distributed under +# the GNU General Public License. +# +# See the README file in the top-level LAMMPS directory. +# ------------------------------------------------------------------------- + +################################################################################ +# IPython/Jupyter Notebook additions +# Written by Richard Berger +################################################################################ + +from .wrapper import wrapper +from .magics import LammpsMagics + +def load_ipython_extension(ipython): + ipython.register_magics(LammpsMagics) diff --git a/python/lammps/ipython/magics.py b/python/lammps/ipython/magics.py new file mode 100644 index 0000000000..5ad2ae2d6f --- /dev/null +++ b/python/lammps/ipython/magics.py @@ -0,0 +1,75 @@ +# ---------------------------------------------------------------------- +# LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator +# https://www.lammps.org/ Sandia National Laboratories +# LAMMPS Development team: developers@lammps.org +# +# Copyright (2003) Sandia Corporation. Under the terms of Contract +# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains +# certain rights in this software. This software is distributed under +# the GNU General Public License. +# +# See the README file in the top-level LAMMPS directory. +# ------------------------------------------------------------------------- + +################################################################################ +# IPython/Jupyter Notebook additions +# Written by Richard Berger +################################################################################ + +import io +import os +import sys +import tempfile +from IPython.core.magic import (Magics, magics_class, cell_magic) +import IPython.core.magic_arguments as magic_arguments + +class OutputCapture(object): + """ Utility class to capture LAMMPS library output """ + def __init__(self): + self.stdout_fd = 1 + self.captured_output = "" + + def __enter__(self): + 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_orig, self.stdout_fd) + os.close(self.stdout_orig) + self.tmpfile.close() + + @property + def output(self): + 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 + +# ------------------------------------------------------------------------- + +@magics_class +class LammpsMagics(Magics): + @magic_arguments.magic_arguments() + @magic_arguments.argument('output', type=str, default='', nargs='?', + help="""The name of the variable in which to store output. + + If unspecified, captured output is discarded. + """ + ) + @cell_magic + def capture_lammps_output(self, line, cell): + """run the cell, capturing LAMMPS stdout and stderr.""" + args = magic_arguments.parse_argstring(self.capture_lammps_output, line) + with OutputCapture() as capture: + self.shell.run_cell(cell) + if args.output: + self.shell.user_ns[args.output] = capture.output diff --git a/python/lammps/ipython/wrapper.py b/python/lammps/ipython/wrapper.py new file mode 100644 index 0000000000..729c0d62bf --- /dev/null +++ b/python/lammps/ipython/wrapper.py @@ -0,0 +1,113 @@ +# ---------------------------------------------------------------------- +# LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator +# https://www.lammps.org/ Sandia National Laboratories +# LAMMPS Development team: developers@lammps.org +# +# Copyright (2003) Sandia Corporation. Under the terms of Contract +# DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains +# certain rights in this software. This software is distributed under +# the GNU General Public License. +# +# See the README file in the top-level LAMMPS directory. +# ------------------------------------------------------------------------- + +################################################################################ +# IPython/Jupyter Notebook additions +# Written by Richard Berger +################################################################################ + +class wrapper(object): + """lammps API IPython Wrapper + + This is a wrapper class that provides additional methods on top of an + existing :py:class:`lammps` instance. It provides additional methods + that allow create and/or embed visualizations created by native LAMMPS + commands. + + There is no need to explicitly instantiate this class. Each instance + of :py:class:`lammps` has a :py:attr:`ipython ` 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 image(self, filename="snapshot.png", group="all", color="type", diameter="type", + size=None, view=None, center=None, up=None, zoom=1.0, background_color="white"): + """ Generate image using write_dump command and display it + + See :doc:`dump image ` for more information. + + :param filename: Name of the image file that should be generated. The extension determines whether it is PNG or JPEG + :type filename: string + :param group: the group of atoms write_image should use + :type group: string + :param color: name of property used to determine color + :type color: string + :param diameter: name of property used to determine atom diameter + :type diameter: string + :param size: dimensions of image + :type size: tuple (width, height) + :param view: view parameters + :type view: tuple (theta, phi) + :param center: center parameters + :type center: tuple (flag, center_x, center_y, center_z) + :param up: vector pointing to up direction + :type up: tuple (up_x, up_y, up_z) + :param zoom: zoom factor + :type zoom: float + :param background_color: background color of scene + :type background_color: string + + :return: Image instance used to display image in notebook + :rtype: :py:class:`IPython.core.display.Image` + """ + cmd_args = [group, "image", filename, color, diameter] + + if size is not None: + width = size[0] + height = size[1] + cmd_args += ["size", width, height] + + if view is not None: + theta = view[0] + phi = view[1] + cmd_args += ["view", theta, phi] + + if center is not None: + flag = center[0] + Cx = center[1] + Cy = center[2] + Cz = center[3] + cmd_args += ["center", flag, Cx, Cy, Cz] + + if up is not None: + Ux = up[0] + Uy = up[1] + Uz = up[2] + cmd_args += ["up", Ux, Uy, Uz] + + if zoom is not None: + cmd_args += ["zoom", zoom] + + cmd_args.append("modify backcolor " + background_color) + + self.lmp.cmd.write_dump(*cmd_args) + from IPython.core.display import Image + return Image(filename) + + def video(self, filename): + """ + Load video from file + + Can be used to visualize videos from :doc:`dump movie `. + + :param filename: Path to video file + :type filename: string + :return: HTML Video Tag used by notebook to embed a video + :rtype: :py:class:`IPython.display.HTML` + """ + from IPython.display import HTML + return HTML("") diff --git a/python/setup.py b/python/setup.py index cf4f1e7c4a..3bcab8faa1 100644 --- a/python/setup.py +++ b/python/setup.py @@ -26,7 +26,7 @@ class BinaryDistribution(Distribution): return True if version_info.major >= 3: - pkgs = ['lammps', 'lammps.mliap'] + pkgs = ['lammps', 'lammps.mliap', 'lammps.ipython'] else: pkgs = ['lammps'] From e45ef5adc03629bad2d07583f1e1fee49272022e Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Sun, 24 Nov 2024 00:03:09 -0700 Subject: [PATCH 16/26] unittest: add Python command_wrapper test --- unittest/python/CMakeLists.txt | 5 ++ unittest/python/python-cmdwrapper.py | 97 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 unittest/python/python-cmdwrapper.py diff --git a/unittest/python/CMakeLists.txt b/unittest/python/CMakeLists.txt index f3b851620c..881c18423d 100644 --- a/unittest/python/CMakeLists.txt +++ b/unittest/python/CMakeLists.txt @@ -105,6 +105,11 @@ if(Python_EXECUTABLE) set_tests_properties(PythonPyLammps PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") endif() + add_test(NAME PythonCmdWrapper + COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-cmdwrapper.py -v + WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) + set_tests_properties(PythonCmdWrapper PROPERTIES ENVIRONMENT "${PYTHON_TEST_ENVIRONMENT}") + add_test(NAME PythonFormats COMMAND ${PYTHON_TEST_RUNNER} ${CMAKE_CURRENT_SOURCE_DIR}/python-formats.py -v WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) diff --git a/unittest/python/python-cmdwrapper.py b/unittest/python/python-cmdwrapper.py new file mode 100644 index 0000000000..50d2092e95 --- /dev/null +++ b/unittest/python/python-cmdwrapper.py @@ -0,0 +1,97 @@ +import os,unittest +from lammps import lammps + +try: + import numpy + NUMPY_INSTALLED = True +except ImportError: + NUMPY_INSTALLED = False + +@unittest.skipIf(not NUMPY_INSTALLED, "numpy is not available") +class PythonCmdWrapper(unittest.TestCase): + def setUp(self): + machine = None + if 'LAMMPS_MACHINE_NAME' in os.environ: + machine=os.environ['LAMMPS_MACHINE_NAME'] + self.lmp = lammps(name=machine, cmdargs=['-nocite', '-log','none', '-echo', 'screen']) + self.lmp.cmd.units("lj") + self.lmp.cmd.atom_style("atomic") + self.lmp.cmd.atom_modify("map array") + + if 'LAMMPS_CMAKE_CACHE' in os.environ: + self.cmake_cache = {} + + with open(os.environ['LAMMPS_CMAKE_CACHE'], 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#') or line.startswith('//'): continue + parts = line.split('=') + key, value_type = parts[0].split(':') + if len(parts) > 1: + value = parts[1] + if value_type == "BOOL": + value = (value.upper() == "ON") + else: + value = None + self.cmake_cache[key] = value + + def tearDown(self): + self.lmp.close() + del self.lmp + + def test_version(self): + self.assertGreaterEqual(self.lmp.version(), 20200824) + + def test_create_atoms(self): + self.lmp.cmd.region("box block", 0, 2, 0, 2, 0, 2) + self.lmp.cmd.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) + self.assertEqual(self.lmp.extract_global("natoms"), 2) + pos = self.lmp.numpy.extract_atom("x") + self.assertEqual(pos.shape[0], 2) + numpy.testing.assert_array_equal(pos[0], tuple(x[0:3])) + numpy.testing.assert_array_equal(pos[1], tuple(x[3:6])) + + def test_thermo_capture(self): + self.lmp.cmd.lattice("fcc", 0.8442), + self.lmp.cmd.region("box block", 0, 4, 0, 4, 0, 4) + self.lmp.cmd.create_box(1, "box") + self.lmp.cmd.create_atoms(1, "box") + self.lmp.cmd.mass(1, 1.0) + self.lmp.cmd.velocity("all create", 1.44, 87287, "loop geom") + self.lmp.cmd.pair_style("lj/cut", 2.5) + self.lmp.cmd.pair_coeff(1, 1, 1.0, 1.0, 2.5) + self.lmp.cmd.neighbor(0.3, "bin") + self.lmp.cmd.neigh_modify("delay 0 every 20 check no") + self.lmp.cmd.fix("1 all nve") + + current_run = {} + + def append_thermo_data(lmp): + for k, v in lmp.last_thermo().items(): + current_run.setdefault(k, []).append(v) + + # thermo data is only captured during a run if PYTHON package is enabled + # without it, it will only capture the final thermo at completion + nvalues = 1 + if self.lmp.has_package("PYTHON"): + self.lmp.cmd.fix("myfix", "all", "python/invoke", 10, "end_of_step", append_thermo_data) + nvalues = 2 + + self.lmp.cmd.run(10) + append_thermo_data(self.lmp) + + for k in ('Step', 'Temp', 'E_pair', 'E_mol', 'TotEng', 'Press'): + self.assertIn(k, current_run) + self.assertEqual(len(current_run[k]), nvalues) + +if __name__ == "__main__": + unittest.main() From 754aa1c73f306729abe5866cf22a29d1732d197d Mon Sep 17 00:00:00 2001 From: Richard Berger Date: Sun, 24 Nov 2024 01:36:01 -0700 Subject: [PATCH 17/26] python: doc and example updates --- doc/src/Howto.rst | 1 - doc/src/Howto_pylammps.rst | 2 +- doc/src/Python_atoms.rst | 16 ++ doc/src/Python_create.rst | 9 +- doc/src/Python_execute.rst | 28 +- doc/src/Python_head.rst | 1 + doc/src/Python_jupyter.rst | 48 ++++ doc/src/Python_module.rst | 4 +- doc/src/Python_objects.rst | 123 +++------ doc/src/Python_properties.rst | 135 ++-------- python/examples/ipython/atoms.ipynb | 253 +----------------- .../examples/ipython/dihedrals/dihedral.ipynb | 11 +- python/examples/ipython/index.ipynb | 8 +- python/examples/ipython/montecarlo/mc.ipynb | 11 +- python/examples/ipython/simple.ipynb | 2 +- python/examples/ipython/thermo.ipynb | 20 +- 16 files changed, 206 insertions(+), 466 deletions(-) create mode 100644 doc/src/Python_jupyter.rst diff --git a/doc/src/Howto.rst b/doc/src/Howto.rst index 16620bf47a..5a63e2b1c4 100644 --- a/doc/src/Howto.rst +++ b/doc/src/Howto.rst @@ -104,6 +104,5 @@ Tutorials howto Howto_lammps_gui Howto_moltemplate Howto_pylammps - Howto_python Howto_wsl diff --git a/doc/src/Howto_pylammps.rst b/doc/src/Howto_pylammps.rst index a8371f1366..4a9b985bf6 100644 --- a/doc/src/Howto_pylammps.rst +++ b/doc/src/Howto_pylammps.rst @@ -3,4 +3,4 @@ PyLammps Tutorial The PyLammps interface is deprecated and will be removed in a future release of LAMMPS. As such, the PyLammps version of this tutorial has been removed and is -replaced by the :doc:`Howto_python`. +replaced by the :doc:`Python_head`. diff --git a/doc/src/Python_atoms.rst b/doc/src/Python_atoms.rst index 0a445f9b6b..2d07cc2326 100644 --- a/doc/src/Python_atoms.rst +++ b/doc/src/Python_atoms.rst @@ -26,14 +26,30 @@ against invalid accesses. lmp = lammps() lmp.file("in.sysinit") + + # Read/Write access via ctypes nlocal = lmp.extract_global("nlocal") x = lmp.extract_atom("x") for i in range(nlocal): print("(x,y,z) = (", x[i][0], x[i][1], x[i][2], ")") + # Read/Write access via NumPy arrays + atom_id = L.numpy.extract_atom("id") + atom_type = L.numpy.extract_atom("type") + x = L.numpy.extract_atom("x") + v = L.numpy.extract_atom("v") + f = L.numpy.extract_atom("f") + + # set position in 2D simulation + x[0] = (1.0, 0.0) + + # set position in 3D simulation + x[0] = (1.0, 0.0, 1.) + lmp.close() + **Methods**: * :py:meth:`extract_atom() `: extract a per-atom quantity diff --git a/doc/src/Python_create.rst b/doc/src/Python_create.rst index c1444c400e..9301829ea9 100644 --- a/doc/src/Python_create.rst +++ b/doc/src/Python_create.rst @@ -6,11 +6,10 @@ Creating or deleting a LAMMPS object ==================================== With the Python interface the creation of a :cpp:class:`LAMMPS -` instance is included in the constructors for the -:py:class:`lammps `, :py:class:`PyLammps `, -and :py:class:`IPyLammps ` classes. -Internally it will call either :cpp:func:`lammps_open` or :cpp:func:`lammps_open_no_mpi` from the C -library API to create the class instance. +` instance is included in the constructor for the +:py:class:`lammps ` class. 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 diff --git a/doc/src/Python_execute.rst b/doc/src/Python_execute.rst index 28c3ff5575..a9d65133db 100644 --- a/doc/src/Python_execute.rst +++ b/doc/src/Python_execute.rst @@ -26,7 +26,7 @@ demonstrates the use of :py:func:`lammps.file()`, :py:func:`lammps.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)] + cmds = [f"group g{i} id {10*i+1}:{10*(i+1)}" for i in range(10)] lmp.commands_list(cmds) # run commands from a multi-line string @@ -38,10 +38,9 @@ demonstrates the use of :py:func:`lammps.file()`, :py:func:`lammps.command()`, """ lmp.commands_string(block) - -Unlike the lammps API, the PyLammps/IPyLammps APIs allow running LAMMPS -commands by calling equivalent member functions of :py:class:`PyLammps ` -and :py:class:`IPyLammps ` instances. +For convenience, the :py:class:`lammps ` class also provides a +command wrapper ``cmd`` that turns any LAMMPS command into a regular function +call. For instance, the following LAMMPS command @@ -49,8 +48,7 @@ For instance, the following LAMMPS command region box block 0 10 0 5 -0.5 0.5 -can be executed using with the lammps API with the following Python code if ``lmp`` is an -instance of :py:class:`lammps `: +would normally be executed with the following Python code: .. code-block:: python @@ -59,7 +57,7 @@ instance of :py:class:`lammps `: lmp = lammps() lmp.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. +With the ``cmd`` wrapper, any LAMMPS command can be split up into arbitrary parts. These parts are then passed to a member function with the name of the :doc:`command `. For the :doc:`region ` command that means the :code:`region()` method can be called. The arguments of the command can be passed as one string, or @@ -82,25 +80,31 @@ member function takes the entire parameter list and transparently merges it to a string. The benefit of this approach is avoiding redundant command calls and easier -parameterization. In the lammps API parameterization needed to be done -manually by creating formatted command strings. +parameterization. With `command`, `commands_list`, and `commands_string` the +parameterization needed to be done manually by creating formatted command +strings. .. code-block:: python lmp.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 +In contrast, methods of the `cmd` wrapper accept parameters directly and will convert them automatically to a final command string. .. code-block:: python L.cmd.region("box block", xlo, xhi, ylo, yhi, zlo, zhi) +.. note:: + + When running in IPython you can use Tab-completion after ``L.cmd.`` to see + all available LAMMPS commands. + Using these facilities, the previous example shown above can be rewritten as follows: .. code-block:: python - from lammps import PyLammps + from lammps import lammps L = lammps() # read commands from file 'in.melt' diff --git a/doc/src/Python_head.rst b/doc/src/Python_head.rst index 3aab3a0d4b..28b6f3d1d4 100644 --- a/doc/src/Python_head.rst +++ b/doc/src/Python_head.rst @@ -15,6 +15,7 @@ together. Python_call Python_formats Python_examples + Python_jupyter Python_error Python_trouble diff --git a/doc/src/Python_jupyter.rst b/doc/src/Python_jupyter.rst new file mode 100644 index 0000000000..df24bf7506 --- /dev/null +++ b/doc/src/Python_jupyter.rst @@ -0,0 +1,48 @@ +Using LAMMPS in IPython notebooks and Jupyter +============================================= + +If the LAMMPS Python package is installed for the same Python interpreter as +`IPython `_, you can use LAMMPS directly inside of an IPython notebook inside of +Jupyter. `Jupyter `_ is a powerful integrated development environment (IDE) for +many dynamic languages like Python, Julia and others, which operates inside of +any web browser. Besides auto-completion and syntax highlighting it allows you +to create formatted documents using Markup, mathematical formulas, graphics and +animations intermixed with executable Python code. It is a great format for +tutorials and showcasing your latest research. + +The easiest way to install it is via ``pip``: + +.. code-block:: bash + + pip install jupyter + +To launch an instance of Jupyter simply run the following command inside your +Python environment: + +.. code-block:: bash + + jupyter notebook + +.. _ipython: https://ipython.org/ +.. _jupyter: https://jupyter.org/ + +Interactive Python Examples +--------------------------- + +Examples of IPython notebooks can be found in the ``python/examples/ipython`` +subdirectory. They require LAMMPS to be compiled as shared library with PYTHON, +PNG, JPEG and FFMPEG support. + +To open these notebooks launch ``jupyter notebook index.ipynb`` inside this +directory. The opened file provides an overview of the available examples. + +- Example 1: Using LAMMPS with Python (``simple.ipynb``) +- Example 2: Analyzing LAMMPS thermodynamic data (``thermo.ipynb``) +- Example 3: Working with Per-Atom Data (``atoms.ipynb``) +- Example 4: Working with LAMMPS variables (``variables.ipynb``) +- Example 5: Validating a dihedral potential (``dihedrals/dihedral.ipynb``) +- Example 6: Running a Monte Carlo relaxation (``montecarlo/mc.ipynb``) + +.. note:: + + Typically clicking a link in Jupyter will open a new tab, which might be blocked by your pop-up blocker. diff --git a/doc/src/Python_module.rst b/doc/src/Python_module.rst index 9c60982e1b..30e585d143 100644 --- a/doc/src/Python_module.rst +++ b/doc/src/Python_module.rst @@ -14,9 +14,7 @@ session with the ``import`` command. Alternative interfaces such as :py:class:`PyLammps ` and :py:class:`IPyLammps ` classes have been deprecated and - will be removed in a future version of LAMMPS. The :doc:`Howto_pylammps` has - also been replaced by a reworked :doc:`Howto_python` that showcases how to - use the modern Python API facilities instead. + will be removed in a future version of LAMMPS. .. _mpi4py_url: https://mpi4py.readthedocs.io diff --git a/doc/src/Python_objects.rst b/doc/src/Python_objects.rst index 6e3a329a27..c3002ec5e6 100644 --- a/doc/src/Python_objects.rst +++ b/doc/src/Python_objects.rst @@ -4,95 +4,52 @@ Compute, fixes, variables This section documents accessing or modifying data from objects like computes, fixes, or variables in LAMMPS using the :py:mod:`lammps` module. -.. tabs:: +For :py:meth:`lammps.extract_compute() ` and +:py:meth:`lammps.extract_fix() `, the global, per-atom, +or local data calculated by the compute or fix can be accessed. What is returned +depends on whether the compute or fix calculates a scalar or vector or array. +For a scalar, a single double value is returned. If the compute or fix calculates +a vector or array, a pointer to the internal LAMMPS data is returned, which you can +use via normal Python subscripting. - .. tab:: lammps API +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 ` page for a discussion of +global, per-atom, and local data, and of scalar, vector, and array +data types. See the doc pages for individual :doc:`computes ` +and :doc:`fixes ` for a description of what they calculate and +store. - For :py:meth:`lammps.extract_compute() ` and - :py:meth:`lammps.extract_fix() `, the global, per-atom, - or local data calculated by the compute or fix can be accessed. What is returned - depends on whether the compute or fix calculates a scalar or vector or array. - For a scalar, a single double value is returned. If the compute or fix calculates - a vector or array, a pointer to the internal LAMMPS data is returned, which you can - use via normal Python subscripting. +For :py:meth:`lammps.extract_variable() `, +an :doc:`equal-style or atom-style variable ` is evaluated and +its result returned. - 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 ` page for a discussion of - global, per-atom, and local data, and of scalar, vector, and array - data types. See the doc pages for individual :doc:`computes ` - and :doc:`fixes ` for a description of what they calculate and - store. +For 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. - For :py:meth:`lammps.extract_variable() `, - an :doc:`equal-style or atom-style variable ` is evaluated and - its result returned. +:py:meth:`lammps.numpy.extract_compute() `, +:py:meth:`lammps.numpy.extract_fix() `, and +:py:meth:`lammps.numpy.extract_variable() ` are +equivalent NumPy implementations that return NumPy arrays instead of ``ctypes`` pointers. - 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. +The :py:meth:`lammps.set_variable() ` method sets an +existing string-style variable to a new string value, so that subsequent LAMMPS +commands can access the variable. - :py:meth:`lammps.numpy.extract_compute() `, - :py:meth:`lammps.numpy.extract_fix() `, and - :py:meth:`lammps.numpy.extract_variable() ` are - equivalent NumPy implementations that return NumPy arrays instead of ``ctypes`` pointers. +**Methods**: - The :py:meth:`lammps.set_variable() ` method sets an - existing string-style variable to a new string value, so that subsequent LAMMPS - commands can access the variable. +* :py:meth:`lammps.extract_compute() `: extract value(s) from a compute +* :py:meth:`lammps.extract_fix() `: extract value(s) from a fix +* :py:meth:`lammps.extract_variable() `: extract value(s) from a variable +* :py:meth:`lammps.set_variable() `: set existing named string-style variable to value - **Methods**: +**NumPy Methods**: - * :py:meth:`lammps.extract_compute() `: extract value(s) from a compute - * :py:meth:`lammps.extract_fix() `: extract value(s) from a fix - * :py:meth:`lammps.extract_variable() `: extract value(s) from a variable - * :py:meth:`lammps.set_variable() `: set existing named string-style variable to value - - **NumPy Methods**: - - * :py:meth:`lammps.numpy.extract_compute() `: extract value(s) from a compute, return arrays as numpy arrays - * :py:meth:`lammps.numpy.extract_fix() `: extract value(s) from a fix, return arrays as numpy arrays - * :py:meth:`lammps.numpy.extract_variable() `: extract value(s) from a variable, return arrays as numpy arrays - - - .. tab:: PyLammps/IPyLammps API - - PyLammps and IPyLammps classes currently do not add any additional ways of - retrieving information out of computes and fixes. This information can still be accessed by using the lammps API: - - .. code-block:: python - - L.lmp.extract_compute(...) - L.lmp.extract_fix(...) - # OR - L.lmp.numpy.extract_compute(...) - L.lmp.numpy.extract_fix(...) - - LAMMPS variables can be both defined and accessed via the :py:class:`PyLammps ` interface. - - To define a variable you can use the :doc:`variable ` command: - - .. code-block:: python - - L.variable("a index 2") - - A dictionary of all variables is returned by the :py:attr:`PyLammps.variables ` property: - - you can access an individual variable by retrieving a variable object from the - ``L.variables`` dictionary by name - - .. code-block:: python - - a = L.variables['a'] - - The variable value can then be easily read and written by accessing the value - property of this object. - - .. code-block:: python - - print(a.value) - a.value = 4 +* :py:meth:`lammps.numpy.extract_compute() `: extract value(s) from a compute, return arrays as numpy arrays +* :py:meth:`lammps.numpy.extract_fix() `: extract value(s) from a fix, return arrays as numpy arrays +* :py:meth:`lammps.numpy.extract_variable() `: extract value(s) from a variable, return arrays as numpy arrays diff --git a/doc/src/Python_properties.rst b/doc/src/Python_properties.rst index 031461660a..25576e90be 100644 --- a/doc/src/Python_properties.rst +++ b/doc/src/Python_properties.rst @@ -2,14 +2,8 @@ System properties ================= Similar to what is described in :doc:`Library_properties`, the instances of -:py:class:`lammps `, :py:class:`PyLammps `, or -:py:class:`IPyLammps ` can be used to extract different kinds -of information about the active LAMMPS instance and also to modify some of it. The -main difference between the interfaces is how the information is exposed. - -While the :py:class:`lammps ` is just a thin layer that wraps C API calls, -:py:class:`PyLammps ` and :py:class:`IPyLammps ` expose -information as objects and properties. +:py:class:`lammps ` can be used to extract different kinds +of information about the active LAMMPS instance and also to modify some of it. 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 @@ -25,113 +19,38 @@ against invalid accesses. accordingly. These arrays can change sizes and order at every neighbor list rebuild and atom sort event as atoms are migrating between subdomains. -.. tabs:: +.. code-block:: python - .. tab:: lammps API + from lammps import lammps - .. code-block:: python + lmp = lammps() + lmp.file("in.sysinit") - from lammps import lammps + natoms = lmp.get_natoms() + print(f"running simulation with {natoms} atoms") - lmp = lammps() - lmp.file("in.sysinit") + lmp.command("run 1000 post no"); - natoms = lmp.get_natoms() - print(f"running simulation with {natoms} atoms") + 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.command("run 1000 post no"); + lmp.close() - 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}") +**Methods**: - lmp.close() +* :py:meth:`version() `: return the numerical version id, e.g. LAMMPS 2 Sep 2015 -> 20150902 +* :py:meth:`get_thermo() `: return current value of a thermo keyword +* :py:meth:`last_thermo() `: return a dictionary of the last thermodynamic output +* :py:meth:`get_natoms() `: total # of atoms as int +* :py:meth:`reset_box() `: reset the simulation box size +* :py:meth:`extract_setting() `: return a global setting +* :py:meth:`extract_global() `: extract a global quantity +* :py:meth:`extract_box() `: extract box info +* :py:meth:`create_atoms() `: create N atoms with IDs, types, x, v, and image flags - **Methods**: +**Properties**: - * :py:meth:`version() `: return the numerical version id, e.g. LAMMPS 2 Sep 2015 -> 20150902 - * :py:meth:`get_thermo() `: return current value of a thermo keyword - * :py:meth:`last_thermo() `: return a dictionary of the last thermodynamic output - * :py:meth:`get_natoms() `: total # of atoms as int - * :py:meth:`reset_box() `: reset the simulation box size - * :py:meth:`extract_setting() `: return a global setting - * :py:meth:`extract_global() `: extract a global quantity - * :py:meth:`extract_box() `: extract box info - * :py:meth:`create_atoms() `: create N atoms with IDs, types, x, v, and image flags - - **Properties**: - - * :py:attr:`last_thermo_step `: the last timestep thermodynamic output was computed - - .. tab:: PyLammps/IPyLammps API - - In addition to the functions provided by :py:class:`lammps `, :py:class:`PyLammps ` objects - have several properties which allow you to query the system state: - - L.system - Is a dictionary describing the system such as the bounding box or number of atoms - - L.system.xlo, L.system.xhi - bounding box limits along x-axis - - L.system.ylo, L.system.yhi - bounding box limits along y-axis - - L.system.zlo, L.system.zhi - bounding box limits along z-axis - - L.communication - configuration of communication subsystem, such as the number of threads or processors - - L.communication.nthreads - number of threads used by each LAMMPS process - - L.communication.nprocs - number of MPI processes used by LAMMPS - - L.fixes - List of fixes in the current system - - L.computes - List of active computes in the current system - - L.dump - List of active dumps in the current system - - L.groups - List of groups present in the current system - - **Retrieving the value of an arbitrary LAMMPS expressions** - - LAMMPS expressions can be immediately evaluated by using the ``eval`` method. The - passed string parameter can be any expression containing global :doc:`thermo` values, - variables, compute or fix data (see :doc:`Howto_output`): - - - .. code-block:: python - - result = L.eval("ke") # kinetic energy - result = L.eval("pe") # potential energy - - result = L.eval("v_t/2.0") - - **Example** - - .. code-block:: python - - from lammps import PyLammps - - L = PyLammps() - L.file("in.sysinit") - - print(f"running simulation with {L.system.natoms} atoms") - - L.run(1000, "post no"); - - for i in range(10): - L.run(100, "pre no post no") - pe = L.eval("pe") - ke = L.eval("ke") - print(f"PE = {pe}\nKE = {ke}") +* :py:attr:`last_thermo_step `: the last timestep thermodynamic output was computed diff --git a/python/examples/ipython/atoms.ipynb b/python/examples/ipython/atoms.ipynb index 14b60d4e28..a18d6addaa 100644 --- a/python/examples/ipython/atoms.ipynb +++ b/python/examples/ipython/atoms.ipynb @@ -4,7 +4,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Example 3: Example 3: Using Atom Data" + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 3: Working with Per-Atom Data" ] }, { @@ -133,248 +140,6 @@ "L.ipython.image(zoom=1.8)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Queries about LAMMPS simulation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system.natoms" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system.nbonds" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.system.nbondtypes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.communication" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.fixes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.computes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.dumps" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.groups" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Working with LAMMPS Variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"a index 2\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"t equal temp\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import sys\n", - "\n", - "if sys.version_info < (3, 0):\n", - " # In Python 2 'print' is a restricted keyword, which is why you have to use the lmp_print function instead.\n", - " x = float(L.lmp_print('\"${a}\"'))\n", - "else:\n", - " # In Python 3 the print function can be redefined.\n", - " # x = float(L.print('\"${a}\"')\")\n", - " \n", - " # To avoid a syntax error in Python 2 executions of this notebook, this line is packed into an eval statement\n", - " x = float(eval(\"L.print('\\\"${a}\\\"')\"))\n", - "x" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['t'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.eval(\"v_t/2.0\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"b index a b c\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['b'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.eval(\"v_b\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['b'].definition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variable(\"i loop 10\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.variables['i'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.next(\"i\")\n", - "L.variables['i'].value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "L.expand(\"ke\")" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -451,7 +216,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/examples/ipython/dihedrals/dihedral.ipynb b/python/examples/ipython/dihedrals/dihedral.ipynb index 4fece8aa58..ed8faeaa86 100644 --- a/python/examples/ipython/dihedrals/dihedral.ipynb +++ b/python/examples/ipython/dihedrals/dihedral.ipynb @@ -4,7 +4,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Validating a dihedral potential" + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 4: Validating a dihedral potential" ] }, { @@ -232,7 +239,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/examples/ipython/index.ipynb b/python/examples/ipython/index.ipynb index 9fdac385fd..47d1ec63ce 100644 --- a/python/examples/ipython/index.ipynb +++ b/python/examples/ipython/index.ipynb @@ -5,7 +5,7 @@ "id": "666d3036-47d5-44d2-bc1a-ca4b00a9e9b8", "metadata": {}, "source": [ - "# LAMMPS IPython Tutorial" + "# LAMMPS Python Tutorials" ] }, { @@ -25,7 +25,9 @@ "\n", "- [Example 1: Using LAMMPS with Python](simple.ipynb)\n", "- [Example 2: Analyzing LAMMPS thermodynamic data](thermo.ipynb)\n", - "- [Example 3: Using Atom Data](atom.ipynb)" + "- [Example 3: Using Atom Data](atoms.ipynb)\n", + "- [Example 4: Validating a dihedral potential](dihedrals/dihedral.ipynb)\n", + "- [Example 5: Running a Monte Carlo relaxation](montecarlo/mc.ipynb)" ] }, { @@ -53,7 +55,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/examples/ipython/montecarlo/mc.ipynb b/python/examples/ipython/montecarlo/mc.ipynb index b1cfa488eb..d6c1a03e54 100644 --- a/python/examples/ipython/montecarlo/mc.ipynb +++ b/python/examples/ipython/montecarlo/mc.ipynb @@ -4,7 +4,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Monte Carlo Relaxation" + "" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example 5: Monte Carlo Relaxation" ] }, { @@ -343,7 +350,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/examples/ipython/simple.ipynb b/python/examples/ipython/simple.ipynb index d45c56db18..77b0844a59 100644 --- a/python/examples/ipython/simple.ipynb +++ b/python/examples/ipython/simple.ipynb @@ -298,7 +298,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/python/examples/ipython/thermo.ipynb b/python/examples/ipython/thermo.ipynb index ea465f5f79..02a7c49e8c 100644 --- a/python/examples/ipython/thermo.ipynb +++ b/python/examples/ipython/thermo.ipynb @@ -279,6 +279,24 @@ "source": [ "current_run.plot(x='Step', y='TotEng')" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Conclusion\n", + "\n", + "The Python interface gives you a powerful way of invoking and extracting simulation data while the simulation is running. Next we'll look at how to extract information about the atoms in your system.\n", + "\n", + "
Next" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -297,7 +315,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.6" + "version": "3.12.7" } }, "nbformat": 4, From 4c33bf663da24d4c8da42e0d397363a9b30e4d36 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Dec 2024 16:58:58 -0500 Subject: [PATCH 18/26] whitespace --- doc/src/Python_jupyter.rst | 2 +- doc/src/Python_properties.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/Python_jupyter.rst b/doc/src/Python_jupyter.rst index df24bf7506..8bdfd1758a 100644 --- a/doc/src/Python_jupyter.rst +++ b/doc/src/Python_jupyter.rst @@ -43,6 +43,6 @@ directory. The opened file provides an overview of the available examples. - Example 5: Validating a dihedral potential (``dihedrals/dihedral.ipynb``) - Example 6: Running a Monte Carlo relaxation (``montecarlo/mc.ipynb``) -.. note:: +.. note:: Typically clicking a link in Jupyter will open a new tab, which might be blocked by your pop-up blocker. diff --git a/doc/src/Python_properties.rst b/doc/src/Python_properties.rst index 25576e90be..25040ad5bd 100644 --- a/doc/src/Python_properties.rst +++ b/doc/src/Python_properties.rst @@ -3,7 +3,7 @@ System properties Similar to what is described in :doc:`Library_properties`, the instances of :py:class:`lammps ` can be used to extract different kinds -of information about the active LAMMPS instance and also to modify some of it. +of information about the active LAMMPS instance and also to modify some of it. 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 From 5fa4be4597056726acb76891f4b9eb44389e5316 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 10 Dec 2024 23:48:44 -0500 Subject: [PATCH 19/26] correct references --- doc/src/fix_rheo.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/src/fix_rheo.rst b/doc/src/fix_rheo.rst index 0214289dee..6add84a5ca 100644 --- a/doc/src/fix_rheo.rst +++ b/doc/src/fix_rheo.rst @@ -64,7 +64,7 @@ Description Perform time integration for RHEO particles, updating positions, velocities, and densities. For a detailed breakdown of the integration timestep and -numerical details, see :ref:`(Palermo) `. For an overview +numerical details, see :ref:`(Palermo) `. For an overview and list of other features available in the RHEO package, see :doc:`the RHEO howto `. @@ -218,11 +218,11 @@ Default ---------- -.. _rheo_palermo: +.. _fix_rheo_palermo: **(Palermo)** Palermo, Wolf, Clemmer, O'Connor, Phys. Fluids, 36, 113337 (2024). -.. _rheo_yang: +.. _fix_rheo_yang: **(Yang)** Yang, Rakhsha, Hu, Negrut, J. Comp. Physics, 458, 111079 (2022). From c9d0ebadd912a5d840417487c9a93dc27f60b8fd Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 10 Dec 2024 23:48:33 -0500 Subject: [PATCH 20/26] address spelling issues --- doc/src/bond_bpm_rotational.rst | 2 +- doc/src/bond_bpm_spring.rst | 2 +- doc/src/fix_rheo.rst | 4 ++-- doc/utils/sphinx-config/false_positives.txt | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/src/bond_bpm_rotational.rst b/doc/src/bond_bpm_rotational.rst index e4cfef3d51..cfbd01dd99 100644 --- a/doc/src/bond_bpm_rotational.rst +++ b/doc/src/bond_bpm_rotational.rst @@ -155,7 +155,7 @@ page on BPMs. If the *break* keyword is set to *no*, LAMMPS assumes bonds should not break during a simulation run. This will prevent some unnecessary calculation. The recommended bond communication distance no longer depends on bond failure -coefficients (which are ignored) but instead corresponds to the typical heurestic +coefficients (which are ignored) but instead corresponds to the typical heuristic maximum strain used by typical non-bpm bond styles. Similar behavior to *break no* can also be attained by setting arbitrarily high values for all four failure coefficients. One cannot use *break no* with *smooth yes*. diff --git a/doc/src/bond_bpm_spring.rst b/doc/src/bond_bpm_spring.rst index c868a47a12..0a43a62159 100644 --- a/doc/src/bond_bpm_spring.rst +++ b/doc/src/bond_bpm_spring.rst @@ -119,7 +119,7 @@ If the *break* keyword is set to *no*, LAMMPS assumes bonds should not break during a simulation run. This will prevent some unnecessary calculation. The recommended bond communication distance no longer depends on the value of :math:`\epsilon_c` (which is ignored) but instead corresponds to the typical -heurestic maximum strain used by typical non-bpm bond styles. Similar behavior +heuristic maximum strain used by typical non-bpm bond styles. Similar behavior to *break no* can also be attained by setting an arbitrarily high value of :math:`\epsilon_c`. One cannot use *break no* with *smooth yes*. diff --git a/doc/src/fix_rheo.rst b/doc/src/fix_rheo.rst index 6add84a5ca..ef18d19f82 100644 --- a/doc/src/fix_rheo.rst +++ b/doc/src/fix_rheo.rst @@ -101,7 +101,7 @@ A modified form of Fickian particle shifting can be enabled with the more uniform spatial distribution. By default, shifting does not consider the type of a particle and therefore may be inappropriate in systems consisting of multiple atom types representing multiple fluid phases. However, two -optional subarguments can follow the *shift* keyword, *exclude/type* and +optional sub-arguments can follow the *shift* keyword, *exclude/type* and *scale/cross/type* to adjust shifting at fluid interfaces. The *exclude/type* option lets the user specify a list of atom types which @@ -155,7 +155,7 @@ threshold for this classification is set by the numerical value of By default, RHEO integrates particles' densities using a mass diffusion equation. Alternatively, one can update densities every timestep by performing a kernel summation of the masses of neighboring particles by specifying the *rho/sum* -keyword. Following this keyword, one may include the optional *self/mass* subargument +keyword. Following this keyword, one may include the optional *self/mass* sub-argument which modifies the behavior of the density summation. Typically, the density :math:`\rho` of a particle is calculated as the sum over neighbors diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index 8e601d6c16..67db18a17d 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -2499,6 +2499,7 @@ neel Neel Neelov Negre +Negrut nelem Nelement Nelements @@ -3116,6 +3117,7 @@ Rafferty rahman Rahman Rajamanickam +Rakhsha Ralf Raman ramped From afe0d94122587fd821bcf38d16edcc2489aebd6c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 14 Dec 2024 17:12:51 -0500 Subject: [PATCH 21/26] small fixes for the manual --- doc/src/Howto.rst | 1 + doc/src/Howto_python.rst | 2 +- doc/src/Python_jupyter.rst | 5 +---- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/src/Howto.rst b/doc/src/Howto.rst index 5a63e2b1c4..df42f6bd9d 100644 --- a/doc/src/Howto.rst +++ b/doc/src/Howto.rst @@ -103,6 +103,7 @@ Tutorials howto Howto_github Howto_lammps_gui Howto_moltemplate + Howto_python Howto_pylammps Howto_wsl diff --git a/doc/src/Howto_python.rst b/doc/src/Howto_python.rst index f668532f44..c4f12a38fc 100644 --- a/doc/src/Howto_python.rst +++ b/doc/src/Howto_python.rst @@ -76,7 +76,7 @@ Next install the LAMMPS Python package into your current Python installation wit Installation inside of a virtual environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -You can use virtual environemnts to create a custom Python environment +You can use virtual environments to create a custom Python environment specifically tuned for your workflow. Benefits of using a virtualenv diff --git a/doc/src/Python_jupyter.rst b/doc/src/Python_jupyter.rst index 8bdfd1758a..9e9a1c917e 100644 --- a/doc/src/Python_jupyter.rst +++ b/doc/src/Python_jupyter.rst @@ -14,7 +14,7 @@ The easiest way to install it is via ``pip``: .. code-block:: bash - pip install jupyter + pip install --user jupyter To launch an instance of Jupyter simply run the following command inside your Python environment: @@ -23,9 +23,6 @@ Python environment: jupyter notebook -.. _ipython: https://ipython.org/ -.. _jupyter: https://jupyter.org/ - Interactive Python Examples --------------------------- From 994a631150d0b79bcecfae7930681b008deaefb2 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 11 Jan 2025 17:43:05 -0500 Subject: [PATCH 22/26] add "post no" for single-shot runs --- python/examples/ipython/dihedrals/dihedral.ipynb | 6 +++--- python/examples/ipython/montecarlo/mc.ipynb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/examples/ipython/dihedrals/dihedral.ipynb b/python/examples/ipython/dihedrals/dihedral.ipynb index ed8faeaa86..6a214a51dc 100644 --- a/python/examples/ipython/dihedrals/dihedral.ipynb +++ b/python/examples/ipython/dihedrals/dihedral.ipynb @@ -94,7 +94,7 @@ "metadata": {}, "outputs": [], "source": [ - "cmd.run(0);" + "cmd.run(0, \"post\", \"no\");" ] }, { @@ -158,7 +158,7 @@ "metadata": {}, "outputs": [], "source": [ - "cmd.run(0)" + "cmd.run(0, \"post\", \"no\")" ] }, { @@ -200,7 +200,7 @@ "pe = []\n", "for p in pos:\n", " x[3] = p\n", - " cmd.run(0);\n", + " cmd.run(0, \"post\", \"no\");\n", " pe.append(L.get_thermo(\"pe\"))" ] }, diff --git a/python/examples/ipython/montecarlo/mc.ipynb b/python/examples/ipython/montecarlo/mc.ipynb index d6c1a03e54..e480955694 100644 --- a/python/examples/ipython/montecarlo/mc.ipynb +++ b/python/examples/ipython/montecarlo/mc.ipynb @@ -100,7 +100,7 @@ "metadata": {}, "outputs": [], "source": [ - "cmd.run(0)" + "cmd.run(0, \"post\", \"no\")" ] }, { @@ -158,7 +158,7 @@ "metadata": {}, "outputs": [], "source": [ - "cmd.run(0)" + "cmd.run(0, \"post\", \"no\")" ] }, { From 8b2c85212166fc6496a8dc6534ffdba3f94d7e52 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sat, 11 Jan 2025 17:46:01 -0500 Subject: [PATCH 23/26] correct and update the Python Howto --- doc/src/Howto_python.rst | 273 ++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 160 deletions(-) diff --git a/doc/src/Howto_python.rst b/doc/src/Howto_python.rst index c4f12a38fc..bfb182d989 100644 --- a/doc/src/Howto_python.rst +++ b/doc/src/Howto_python.rst @@ -3,16 +3,21 @@ LAMMPS Python Tutorial .. contents:: +----- + Overview -------- -:py:class:`lammps ` is a Python wrapper class for the +The :py:class:`lammps ` Python module is a wrapper class for the LAMMPS :ref:`C language library interface API ` which is written using -`Python ctypes `_. +`Python ctypes `_. The design choice of this wrapper class is to +follow the C language API closely with only small changes related to Python +specific requirements and to better accommodate object oriented programming. -In addition to the flat `ctypes `_ interface, this class exposes a -discoverable API that doesn't require knowledge of the underlying C++ -code implementation. +In addition to this flat `ctypes `_ interface, the +:py:class:`lammps ` wrapper class exposes a discoverable +API that doesn't require as much knowledge of the underlying C language +library interface or LAMMPS C++ code implementation. Finally, the API exposes some additional features for `IPython integration `_ into `Jupyter notebooks `_, e.g. for embedded @@ -22,43 +27,47 @@ visualization output from :doc:`dump style image `. .. _ipython: https://ipython.org/ .. _jupyter: https://jupyter.org/ +----- + Quick Start ----------- -System-wide Installation -^^^^^^^^^^^^^^^^^^^^^^^^ +System-wide or User Installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Step 1: Building LAMMPS as a shared library """"""""""""""""""""""""""""""""""""""""""" -To use LAMMPS inside of Python it has to be compiled as shared -library. This library is then loaded by the Python interface. In this -example we enable the MOLECULE package and compile LAMMPS with PNG, JPEG -and FFMPEG output support enabled. +To use LAMMPS inside of Python it has to be compiled as shared library. +This library is then loaded by the Python interface. In this example we +enable the :ref:`MOLECULE package ` and compile LAMMPS +with :ref:`PNG, JPEG and FFMPEG output support ` enabled. -Step 1a: For the CMake based build system, the steps are: +.. tabs:: -.. code-block:: bash + .. tab:: CMake build - mkdir $LAMMPS_DIR/build-shared - cd $LAMMPS_DIR/build-shared + .. code-block:: bash - # MPI, PNG, Jpeg, FFMPEG are auto-detected - cmake ../cmake -DPKG_MOLECULE=yes -DPKG_PYTHON=on -DBUILD_SHARED_LIBS=yes - make + mkdir $LAMMPS_DIR/build-shared + cd $LAMMPS_DIR/build-shared -Step 1b: For the legacy, make based build system, the steps are: + # MPI, PNG, Jpeg, FFMPEG are auto-detected + cmake ../cmake -DPKG_MOLECULE=yes -DPKG_PYTHON=on -DBUILD_SHARED_LIBS=yes + make -.. code-block:: bash + .. tab:: Traditional make - cd $LAMMPS_DIR/src + .. code-block:: bash - # add packages if necessary - make yes-MOLECULE - make yes-PYTHON + cd $LAMMPS_DIR/src - # compile shared library using Makefile - make mpi mode=shlib LMP_INC="-DLAMMPS_PNG -DLAMMPS_JPEG -DLAMMPS_FFMPEG" JPG_LIB="-lpng -ljpeg" + # add packages if necessary + make yes-MOLECULE + make yes-PYTHON + + # compile shared library using Makefile + make mpi mode=shlib LMP_INC="-DLAMMPS_PNG -DLAMMPS_JPEG -DLAMMPS_FFMPEG" JPG_LIB="-lpng -ljpeg" Step 2: Installing the LAMMPS Python package """""""""""""""""""""""""""""""""""""""""""" @@ -69,9 +78,16 @@ Next install the LAMMPS Python package into your current Python installation wit make install-python +This will create a so-called `"wheel" +`_ +and then install the LAMMPS Python module from that "wheel" into either +into a system folder (provided the command is executed with root +privileges) or into your personal Python module folder. + .. note:: - Recompiling the shared library requires re-installing the Python package + Recompiling the shared library requires re-installing the Python + package. Installation inside of a virtual environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -117,13 +133,15 @@ to the location in the virtual environment with: (testing) make install-python # install other useful packages - (testing) pip install matplotlib jupyter mpi4py + (testing) pip install matplotlib jupyter mpi4py pandas ... # return to original shell (testing) deactivate +------- + Creating a new lammps instance ------------------------------ @@ -136,6 +154,11 @@ module. By using the default constructor, a new :py:class:`lammps from lammps import lammps L = lammps() +See the :doc:`LAMMPS Python documentation ` for how to customize +the instance creation with optional arguments. + +----- + Commands -------- @@ -187,105 +210,47 @@ them automatically to a final command string. When running in IPython you can use Tab-completion after ``L.cmd.`` to see all available LAMMPS commands. -System state ------------- - -In addition to dispatching commands directly through the PyLammps object, it -also provides 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 - -Working with LAMMPS variables ------------------------------ - -LAMMPS variables can be both defined and accessed via the PyLammps interface. - -To define a variable you can use the :doc:`variable ` command: - -.. code-block:: python - - L.variable("a index 2") - -A dictionary of all variables is returned by L.variables - -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 - -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 thermo values, -variables, compute or fix data. - -.. code-block:: python - - result = L.get_thermo("ke") # kinetic energy - result = L.get_thermo("pe") # potential energy - - result = L.extract_variable("t") / 2.0 +----- Accessing atom data ------------------- -All atoms in the current simulation can be accessed by using the L.atoms list. -Each element of this list is an object which exposes its properties (id, type, -position, velocity, force, etc.). +All per-atom properties that are part of the :doc:`atom style +` in the current simulation can be accessed using the +:py:meth:`extract_atoms() ` method. This +can be retrieved as ctypes objects or as NumPy arrays through the +lammps.numpy module. Those represent the *local* atoms of the +individual sub-domain for the current MPI process and may contain +information for the local ghost atoms or not depending on the property. +Both can be accessed as lists, but for the ctypes list object the size +is not known and hast to be retrieved first to avoid out-of-bounds +accesses. .. code-block:: python - # access first atom - atom_id = L.numpy.extract_atom("id") + nlocal = L.extract_setting("nlocal") + nall = L.extract_setting("nall") + print("Number of local atoms ", nlocal, " Number of local and ghost atoms ", nall); + + # access via ctypes directly + atom_id = L.extract_atom("id") + print("Atom IDs", atom_id[0:nlocal]) + + # access through numpy wrapper atom_type = L.numpy.extract_atom("type") + print("Atom types", atom_type) x = L.numpy.extract_atom("x") v = L.numpy.extract_atom("v") - f = L.numpy.extract_atom("f") + print("positions array shape", x.shape) + print("velocity array shape", v.shape) + # turn on communicating velocities to ghost atoms + L.cmd.comm_modify("vel", "yes") + v = L.numpy.extract_atom('v') + print("velocity array shape", v.shape) -Some properties can also be used to set: +Some properties can also be set from Python since internally the +data of the C++ code is accessed directly: .. code-block:: python @@ -295,65 +260,53 @@ Some properties can also be used to set: # set position in 3D simulation x[0] = (1.0, 0.0, 1.) -Evaluating thermo data ----------------------- +------ -Each simulation run usually produces thermo output based on system state, -computes, fixes or variables. The trajectories of these values can be queried -after a run via the L.runs list. This list contains a growing list of run data. -The first element is the output of the first run, the second element that of -the second run. +Retrieving the values of thermodynamic data and variables +--------------------------------------------------------- + +To access thermodynamic data from the last completed timestep, +you can use the :py:meth:`get_thermo() ` +method, and to extract the value of (compatible) variables, you +can use the :py:meth:`extract_variable() ` +method. .. code-block:: python - L.run(1000) - L.runs[0] # data of first 1000 time steps + result = L.get_thermo("ke") # kinetic energy + result = L.get_thermo("pe") # potential energy - L.run(1000) - L.runs[1] # data of second 1000 time steps + result = L.extract_variable("t") / 2.0 -Each run contains a dictionary of all trajectories. Each trajectory is -accessible through its thermo name: +Error handling +-------------- -.. code-block:: python - - L.runs[0].thermo.Step # list of time steps in first run - L.runs[0].thermo.Ke # list of kinetic energy values in first run - -Together with matplotlib plotting data out of LAMMPS becomes simple: - -.. code-block:: python - - import matplotlib.plot as plt - steps = L.runs[0].thermo.Step - ke = L.runs[0].thermo.Ke - plt.plot(steps, ke) - -Error handling with PyLammps ----------------------------- - -Using C++ exceptions in LAMMPS for errors allows capturing them on the -C++ side and rethrowing them on the Python side. This way you can handle -LAMMPS errors through the Python exception handling mechanism. +We are using C++ exceptions in LAMMPS for errors and the C language +library interface captures and records them. This allows checking +whether errors have happened in Python during a call into LAMMPS and +then re-throw the error as a Python exception. This way you can handle +LAMMPS errors in the conventional way 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 + terminated. It is advised to save your data and terminate the Python instance as quickly as possible. Using LAMMPS in IPython notebooks and Jupyter --------------------------------------------- -If the LAMMPS Python package is installed for the same Python interpreter as -IPython, you can use LAMMPS directly inside of an IPython notebook inside of -Jupyter. Jupyter is a powerful integrated development environment (IDE) for -many dynamic languages like Python, Julia and others, which operates inside of -any web browser. Besides auto-completion and syntax highlighting it allows you -to create formatted documents using Markup, mathematical formulas, graphics and -animations intermixed with executable Python code. It is a great format for -tutorials and showcasing your latest research. +If the LAMMPS Python package is installed for the same Python +interpreter as IPython, you can use LAMMPS directly inside of an IPython +notebook inside of Jupyter. Jupyter is a powerful integrated development +environment (IDE) for many dynamic languages like Python, Julia and +others, which operates inside of any web browser. Besides +auto-completion and syntax highlighting it allows you to create +formatted documents using Markup, mathematical formulas, graphics and +animations intermixed with executable Python code. It is a great format +for tutorials and showcasing your latest research. To launch an instance of Jupyter simply run the following command inside your Python environment (this assumes you followed the Quick Start instructions): @@ -365,7 +318,7 @@ Python environment (this assumes you followed the Quick Start instructions): Interactive Python Examples --------------------------- -Examples of IPython notebooks can be found in the ``python/examples/juypter`` +Examples of IPython notebooks can be found in the ``python/examples/ipython`` subdirectory. To open these notebooks launch ``jupyter notebook`` inside this directory and navigate to one of them. If you compiled and installed a LAMMPS shared library with PNG, JPEG and FFMPEG support @@ -391,7 +344,7 @@ setting its position from Python, which changes the dihedral angle. pe = [] for p in pos: x[3] = p - L.cmd.run(0) + L.cmd.run(0, "post", "no") pe.append(L.get_thermo("pe")) By evaluating the potential energy for each position we can verify that @@ -429,7 +382,7 @@ It is then disordered by moving each atom by a random delta. x[i][0] += dx x[i][1] += dy - L.cmd.run(0) + L.cmd.run(0, "post", "no") .. image:: JPG/pylammps_mc_disordered.jpg :align: center From 7c990f8b0e8f35f7b931a6e6f30627cb22d7ea2d Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 12 Jan 2025 21:56:54 -0500 Subject: [PATCH 24/26] improve wording --- src/lmptype.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lmptype.h b/src/lmptype.h index 9a0a0f9220..ec2cd3f7e9 100644 --- a/src/lmptype.h +++ b/src/lmptype.h @@ -37,7 +37,7 @@ // C++17 check #ifndef LAMMPS_CXX11 #if __cplusplus < 201703L -#error LAMMPS is planning to transition to C++17. To disable this error please use a C++17 compliant compiler, enable C++17 support, or define -DLAMMPS_CXX11 in your makefile or when running cmake +#error LAMMPS is planning to transition to requiring C++17. To disable this error please use a C++17 compliant compiler, enable C++17 support, or define -DLAMMPS_CXX11 in your makefile or when running cmake #endif #endif From 78850efaed521d4e35a130d9565ad9b4ccba9de4 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 12 Jan 2025 21:57:25 -0500 Subject: [PATCH 25/26] also warn that KOKKOS will end support for legacy builds in Summer 2025 --- src/KOKKOS/Install.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/KOKKOS/Install.sh b/src/KOKKOS/Install.sh index 64ba0c6b03..1ec3646de2 100755 --- a/src/KOKKOS/Install.sh +++ b/src/KOKKOS/Install.sh @@ -7,6 +7,19 @@ mode=$1 LC_ALL=C export LC_ALL +cat < Date: Mon, 13 Jan 2025 00:04:14 -0500 Subject: [PATCH 26/26] print PyLammps deprecation warning only once and only from MPI rank 0 --- python/lammps/pylammps.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/python/lammps/pylammps.py b/python/lammps/pylammps.py index 7eea2dd448..bd058943c8 100644 --- a/python/lammps/pylammps.py +++ b/python/lammps/pylammps.py @@ -449,12 +449,6 @@ class PyLammps(object): """ def __init__(self, name="", cmdargs=None, ptr=None, comm=None, verbose=False): - print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") - print() - print("The PyLammps interface is deprecated and will be removed in future versions.") - print("Please use the lammps Python class instead.") - print() - print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") self.has_echo = False self.verbose = verbose @@ -477,7 +471,10 @@ class PyLammps(object): self.comm_me = self.lmp.extract_setting("world_rank") if self.comm_me == 0: print("\nWARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING") + print("WARNING:") print("WARNING: The PyLammps class is obsolete and will be removed from LAMMPS soon.") + print("WARNING: Please use the lammps Python class instead.") + print("WARNING:") print("WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING-WARNING\n") print("LAMMPS output is captured by PyLammps wrapper") if self.comm_nprocs > 1: