diff --git a/doc/src/Fortran.rst b/doc/src/Fortran.rst index d041946470..3112440bb0 100644 --- a/doc/src/Fortran.rst +++ b/doc/src/Fortran.rst @@ -389,6 +389,12 @@ Procedures Bound to the lammps Derived Type :r real(c_double): number of atoms + .. note:: + + If you would prefer to get the number of atoms in its native format + (i.e., as a 32- or 64-bit integer, depending on how LAMMPS was compiled), + this can be extracted with :f:func:`extract_global`. + -------- .. f:function:: get_thermo(name) @@ -575,7 +581,26 @@ Procedures Bound to the lammps Derived Type greater than or equal to the length of the string (not including the terminal ``NULL`` character) that LAMMPS returns. If the variable's length is too short, the string will be truncated. As usual in Fortran, - strings are padded with spaces at the end. + strings are padded with spaces at the end. If you use an allocatable + string, the string **must be allocated** prior to calling this function, + but you can automatically reallocate it to the correct length after the + function returns, viz., + + .. code-block :: Fortran + + PROGRAM test + USE LIBLAMMPS + TYPE(lammps) :: lmp + CHARACTER(LEN=:), ALLOCATABLE :: str + lmp = lammps() + CALL lmp%command('units metal') + ALLOCATE ( CHARACTER(LEN=80) :: str ) + str = lmp%extract_global('units') + str = TRIM(str) ! re-allocates to length len_trim(str) here + PRINT*, LEN(str), LEN_TRIM(str) + END PROGRAM test + + will print the number 5 (the length of the word "metal") twice. :p character(len=\*) name: string with the name of the property to extract :r polymorphic: pointer to LAMMPS data. The left-hand side of the assignment @@ -737,8 +762,8 @@ Procedures Bound to the lammps Derived Type Two-dimensional arrays returned from :f:func:`extract_compute` will be **transposed** from equivalent arrays in C, and they will be indexed - from 1 instead of 0. See the similar note under - :f:func:`extract_atom` for further details. + from 1 instead of 0. See the note at :f:func:`extract_atom` for + further details. The following combinations are possible (assuming ``lmp`` is the name of your LAMMPS instance): @@ -749,7 +774,7 @@ Procedures Bound to the lammps Derived Type * - Style - Type - - Pointer type to assign to + - Type to assign to - Returned data * - ``lmp%style%global`` - ``lmp%type%scalar`` @@ -786,7 +811,7 @@ Procedures Bound to the lammps Derived Type :p integer(c_int) type: value indicating the type of data to extract (scalar, vector, or array) :r polymorphic: pointer to LAMMPS data. The left-hand side of the assignment - should be a C-compatible pointer (e.g., ``REAL (C_double), POINTER :: x``) + should be a C-compatible pointer (e.g., ``REAL (c_double), POINTER :: x``) to the extracted property. If expecting vector data, the pointer should have dimension ":"; if expecting array (matrix) data, the pointer should have dimension ":,:". @@ -828,10 +853,76 @@ Procedures Bound to the lammps Derived Type user wishes LAMMPS to return. The *ncol* variable is optional for global scalar or vector data, and both *nrow* and *ncol* are optional when a global scalar is requested, as well as when per-atom or local data are - requested. + requested. The following combinations are possible (assuming ``lmp`` is the + name of your LAMMPS instance): + + .. list-table:: + :header-rows: 1 + :widths: auto + + * - Style + - Type + - nrow + - ncol + - Type to assign to + - Returned data + * - ``lmp%style%global`` + - ``lmp%type%scalar`` + - Ignored + - Ignored + - ``REAL(c_double)`` + - Global scalar + * - ``lmp%style%global`` + - ``lmp%type%vector`` + - Required + - Ignored + - ``REAL(c_double)`` + - Element of global vector + * - ``lmp%style%global`` + - ``lmp%type%array`` + - Required + - Required + - ``REAL(c_double)`` + - Element of global array + * - ``lmp%style%atom`` + - ``lmp%type%scalar`` + - + - + - + - (not allowed) + * - ``lmp%style%atom`` + - ``lmp%type%vector`` + - Ignored + - Ignored + - ``REAL(c_double), DIMENSION(:), POINTER`` + - Per-atom vector + * - ``lmp%style%atom`` + - ``lmp%type%array`` + - Ignored + - Ignored + - ``REAL(c_double), DIMENSION(:,:), POINTER`` + - Per-atom array + * - ``lmp%style%local`` + - ``lmp%type%scalar`` + - + - + - + - (not allowed) + * - ``lmp%style%local`` + - ``lmp%type%vector`` + - Ignored + - Ignored + - ``REAL(c_double), DIMENSION(:), POINTER`` + - Per-atom vector + * - ``lmp%style%local`` + - ``lmp%type%array`` + - Ignored + - Ignored + - ``REAL(c_double), DIMENSION(:,:), POINTER`` + - Per-atom array In the case of global data, this function returns a value of type - ``real(C_double)``. For per-atom or local data, this function does not + ``real(c_double)``. For per-atom or local data, this function does not return a value but instead associates the pointer on the left side of the assignment to point to internal LAMMPS data. Pointers must be of the correct data type to point to said data (i.e., ``REAL(c_double)``) and have @@ -914,7 +1005,7 @@ Procedures Bound to the lammps Derived Type :p integer(c_int) ncol: column index (only used for global arrays) :r polymorphic: LAMMPS data (for global data) or a pointer to LAMMPS data (for per-atom or local data). The left-hand side of the assignment should - be of type ``REAL(C_double)`` and have appropriate rank (i.e., + be of type ``REAL(c_double)`` and have appropriate rank (i.e., ``DIMENSION(:)`` if expecting per-atom or local vector data and ``DIMENSION(:,:)`` if expecting per-atom or local array data). If expecting local or per-atom data, it should have the ``POINTER`` attribute, but @@ -950,7 +1041,7 @@ Procedures Bound to the lammps Derived Type This function returns the values of the variables, not pointers to them. Vectors pointing to *atom*-style variables should be of type - ``REAL(C_double)``, be of rank 1 (i.e., ``DIMENSION(:)``), and either have + ``REAL(c_double)``, be of rank 1 (i.e., ``DIMENSION(:)``), and either have the ``ALLOCATABLE`` attribute or be long enough to contain the data without reallocation. @@ -977,19 +1068,20 @@ Procedures Bound to the lammps Derived Type :p character(len=\*) name: variable name to evaluate :o character(len=\*) group [optional]: group for which to extract per-atom data (if absent, use "all") - :r polymorphic: scalar of type ``REAL(C_double)`` (for *equal*-style + :r polymorphic: scalar of type ``REAL(c_double)`` (for *equal*-style variables and others that are *equal*-compatible), vector of type - ``REAL(C_double), DIMENSION(nlocal)`` for *atom*-style variables, or - ``CHARACTER(LEN=:), ALLOCATABLE`` for *string*-style and compatible - variables. Non-allocatable strings whose length is too short to hold the - result will be truncated. + ``REAL(c_double), DIMENSION(nlocal)`` for *atom*-style variables, or + ``CHARACTER(LEN=*)`` for *string*-style and compatible variables. Strings + whose length is too short to hold the result will be truncated. + Allocatable strings must be allocated before this function is called; + see note at :f:func:`extract_global` regarding allocatable strings. .. note:: LAMMPS cannot easily check if it is valid to access the data referenced by the variables (e.g., computes, fixes, or thermodynamic info), so it may fail with an error. The caller has to make certain - that the data are extracted only when it safe to evaluate the variable + that the data are extracted only when it is safe to evaluate the variable and thus an error and crash are avoided. -------- @@ -1003,7 +1095,187 @@ Procedures Bound to the lammps Derived Type -------- -.. f:subroutine:: flush_buffers +.. f:subroutine:: get_os_info(buffer) + + This function can be used to retrieve detailed information about the hosting + operating system and compiler/runtime environment. + + .. versionadded:: TBD + + A suitable buffer has to be provided. The assembled text will be truncated + to not overflow this buffer. The string is typically a few hundred bytes + long. + +-------- + +.. f:function:: config_has_mpi_support() + + This function is used to query whether LAMMPS was compiled with a real MPI + library or in serial. + + .. versionadded:: TBD + + :r logical: ``.FALSE.`` when compiled with STUBS, ``.TRUE.`` if complied + with MPI. + +-------- + +.. f:function:: config_has_gzip_support() + + Check if the LAMMPS library supports reading or writing compressed + files via a pipe to gzip or similar compression programs. + + .. versionadded:: TBD + + Several LAMMPS commands (e.g., :doc:`read_data`, :doc:`write_data`, + :doc:`dump styles atom, custom, and xyz `) support reading and writing + compressed files via creating a pipe to the ``gzip`` program. This function + checks whether this feature was :ref:`enabled at compile time `. + It does **not** check whether ``gzip`` or any other supported compression + programs themselves are installed and usable. + + :r logical: + +-------- + +.. f:function:: config_has_png_support() + + Check if the LAMMPS library supports writing PNG format images. + + .. versionadded:: TBD + + The LAMMPS :doc:`dump style image ` supports writing multiple + image file formats. Most of them, however, need support from an external + library, and using that has to be :ref:`enabled at compile time `. + This function checks whether support for the `PNG image file format + `_ is available + in the current LAMMPS library. + + :r logical: + +-------- + +.. f:function:: config_has_jpeg_support() + + Check if the LAMMPS library supports writing JPEG format images. + + .. versionadded:: TBD + + The LAMMPS :doc:`dump style image ` supports writing multiple + image file formats. Most of them, however, need support from an external + library, and using that has to be :ref:`enabled at compile time `. + This function checks whether support for the `JPEG image file format + `_ is available in the current LAMMPS library. + + :r logical: + +-------- + +.. f:function:: config_has_ffmpeg_support() + + Check if the LAMMPS library supports creating movie files via a pipe to + ffmpeg. + + .. versionadded:: TBD + + The LAMMPS :doc:`dump style movie ` supports generating movies + from images on-the-fly via creating a pipe to the + `ffmpeg `_ program. + This function checks whether this feature was + :ref:`enabled at compile time `. + It does **not** check whether the ``ffmpeg`` itself is installed and usable. + + :r logical: + +-------- + +.. f:function:: config_has_exceptions() + + Check whether LAMMPS errors will throw C++ exceptions. + + .. versionadded:: TBD + + In case of an error, LAMMPS will either abort or throw a C++ exception. + The latter has to be :ref:`enabled at compile time `. + This function checks if exceptions were enabled. + + When using the library interface with C++ exceptions enabled, the library + interface functions will "catch" them, and the error status can then be + checked by calling :f:func:`has_error`. The most recent error message can be + retrieved via :f:func:`get_last_error_message`. + This can allow one to restart a calculation or delete and recreate + the LAMMPS instance when a C++ exception occurs. One application + of using exceptions this way is the :ref:`lammps_shell`. If C++ + exceptions are disabled and an error happens during a call to + LAMMPS or the Fortran API, the application will terminate. + + :r logical: + +-------- + +.. f:function:: config_has_package(name) + + Check whether a specific package has been included in LAMMPS + + .. versionadded:: TBD + + This function checks whether the LAMMPS library in use includes the specific + :doc:`LAMMPS package ` provided as argument. + + :r logical: + +-------- + +.. f:function:: config_package_count() + + Count the number of installed packages in the LAMMPS library. + + .. versionadded:: TBD + + This function counts how many :doc:`LAMMPS packages ` are + included in the LAMMPS library in use. It directly calls the C library + function :cpp:func:`lammps_config_package_count`. + + :r integer(c_int): number of packages installed + +-------- + +.. f:subroutine:: config_package_name(idx, buffer) + + Get the name of a package in the list of installed packages in the LAMMPS + library. + + .. versionadded:: TBD + + This subroutine copies the name of the package with the index *idx* into the + provided string *buffer*. If the name of the package exceeds the length of + the buffer, it will be truncated accordingly. If the index is out of range, + *buffer* is set to an empty string. + + :p integer(c_int) idx: index of the package in the list of included packages + :math:`(0 \le idx < \text{package count})` + :p character(len=\*) buffer: string to hold the name of the package + +-------- + +.. f:subroutine:: installed_packages(package[, length]) + + Obtain a list of the names of enabled packages in the LAMMPS shared library + and store it in *package*. + + This function is analogous to the :py:func`installed_packages` function in + the Python API. The optional argument *length* sets the length of each + string in the vector *package* (default: 31). + + :p character(len=:) package [dimension(:),allocatable]: list of packages; + *must* have the ``ALLOCATABLE`` attribute and be of rank-1 + (``DIMENSION(:)``) with allocatable length. + :o integer length [optional]: length of each string in the list. + Default: 31. + +-------- + +.. f:subroutine:: flush_buffers() This function calls :cpp:func:`lammps_flush_buffers`, which flushes buffered output to be written to screen and logfile. This can simplify capturing @@ -1013,7 +1285,7 @@ Procedures Bound to the lammps Derived Type -------- -.. f:function:: is_running +.. f:function:: is_running() Check if LAMMPS is currently inside a run or minimization. @@ -1026,7 +1298,18 @@ Procedures Bound to the lammps Derived Type -------- -.. f:function:: has_error +.. f:subroutine:: force_timeout() + + Force a timeout to stop an ongoing run cleanly. + + .. versionadded:: TBD + + This function can be used from signal handlers or multi-threaded + applications to cleanly terminate an ongoing run. + +-------- + +.. f:function:: has_error() Check if there is a (new) error message available. @@ -1069,8 +1352,8 @@ Procedures Bound to the lammps Derived Type This function will do nothing when the LAMMPS library has been compiled without ``-DLAMMPS_EXCEPTIONS``, which turns errors aborting LAMMPS into C++ exceptions. You can use the function - :f:func:`config_has_exceptions` to check whethher this is the case. + :f:func:`config_has_exceptions` to check whether this is the case. :p character(len=\*) buffer: string buffer to copy the error message into - :o integer(C_int) status [optional]: 1 when all ranks had the error, + :o integer(c_int) status [optional]: 1 when all ranks had the error, 2 on a single-rank error. diff --git a/fortran/lammps.f90 b/fortran/lammps.f90 index 9f49a1fcb5..eb83ce3058 100644 --- a/fortran/lammps.f90 +++ b/fortran/lammps.f90 @@ -32,7 +32,6 @@ MODULE LIBLAMMPS USE, INTRINSIC :: ISO_C_BINDING, ONLY: c_ptr, c_null_ptr, c_associated, & c_loc, c_int, c_int64_t, c_char, c_null_char, c_double, c_size_t, & c_f_pointer - USE, INTRINSIC :: ISO_FORTRAN_ENV, ONLY : ERROR_UNIT IMPLICIT NONE PRIVATE @@ -80,36 +79,48 @@ MODULE LIBLAMMPS END TYPE lammps_type TYPE lammps - TYPE(c_ptr) :: handle - TYPE(lammps_style) :: style - TYPE(lammps_type) :: type - CONTAINS - PROCEDURE :: close => lmp_close - PROCEDURE :: error => lmp_error - PROCEDURE :: file => lmp_file - PROCEDURE :: command => lmp_command - PROCEDURE :: commands_list => lmp_commands_list - PROCEDURE :: commands_string => lmp_commands_string - PROCEDURE :: get_natoms => lmp_get_natoms - PROCEDURE :: get_thermo => lmp_get_thermo - PROCEDURE :: extract_box => lmp_extract_box - PROCEDURE :: reset_box => lmp_reset_box - PROCEDURE :: memory_usage => lmp_memory_usage - PROCEDURE :: get_mpi_comm => lmp_get_mpi_comm - PROCEDURE :: extract_setting => lmp_extract_setting - PROCEDURE :: extract_global => lmp_extract_global - PROCEDURE :: extract_atom => lmp_extract_atom - PROCEDURE :: extract_compute => lmp_extract_compute - PROCEDURE :: extract_fix => lmp_extract_fix - PROCEDURE :: extract_variable => lmp_extract_variable + TYPE(c_ptr) :: handle = c_null_ptr + TYPE(lammps_style) :: style + TYPE(lammps_type) :: type + CONTAINS + PROCEDURE :: close => lmp_close + PROCEDURE :: error => lmp_error + PROCEDURE :: file => lmp_file + PROCEDURE :: command => lmp_command + PROCEDURE :: commands_list => lmp_commands_list + PROCEDURE :: commands_string => lmp_commands_string + PROCEDURE :: get_natoms => lmp_get_natoms + PROCEDURE :: get_thermo => lmp_get_thermo + PROCEDURE :: extract_box => lmp_extract_box + PROCEDURE :: reset_box => lmp_reset_box + PROCEDURE :: memory_usage => lmp_memory_usage + PROCEDURE :: get_mpi_comm => lmp_get_mpi_comm + PROCEDURE :: extract_setting => lmp_extract_setting + PROCEDURE :: extract_global => lmp_extract_global + PROCEDURE :: extract_atom => lmp_extract_atom + PROCEDURE :: extract_compute => lmp_extract_compute + PROCEDURE :: extract_fix => lmp_extract_fix + PROCEDURE :: extract_variable => lmp_extract_variable ! - PROCEDURE :: version => lmp_version + PROCEDURE :: version => lmp_version + PROCEDURE,NOPASS :: get_os_info => lmp_get_os_info + PROCEDURE,NOPASS :: config_has_mpi_support => lmp_config_has_mpi_support + PROCEDURE,NOPASS :: config_has_gzip_support => lmp_config_has_gzip_support + PROCEDURE,NOPASS :: config_has_png_support => lmp_config_has_png_support + PROCEDURE,NOPASS :: config_has_jpeg_support => lmp_config_has_jpeg_support + PROCEDURE,NOPASS :: config_has_ffmpeg_support & + => lmp_config_has_ffmpeg_support + PROCEDURE,NOPASS :: config_has_exceptions => lmp_config_has_exceptions + PROCEDURE,NOPASS :: config_has_package => lmp_config_has_package + PROCEDURE,NOPASS :: config_package_count => lammps_config_package_count + PROCEDURE,NOPASS :: config_package_name => lmp_config_package_name + PROCEDURE,NOPASS :: installed_packages => lmp_installed_packages ! - PROCEDURE :: flush_buffers => lmp_flush_buffers - PROCEDURE :: is_running => lmp_is_running -! force_timeout - PROCEDURE :: has_error => lmp_has_error - PROCEDURE :: get_last_error_message => lmp_get_last_error_message + PROCEDURE :: flush_buffers => lmp_flush_buffers + PROCEDURE :: is_running => lmp_is_running + PROCEDURE :: force_timeout => lmp_force_timeout + PROCEDURE :: has_error => lmp_has_error + PROCEDURE :: get_last_error_message => lmp_get_last_error_message END TYPE lammps INTERFACE lammps @@ -180,6 +191,9 @@ MODULE LIBLAMMPS MODULE PROCEDURE assign_double_to_lammps_fix_data, & assign_doublevec_to_lammps_fix_data, & assign_doublemat_to_lammps_fix_data + ! Variables, too + MODULE PROCEDURE assign_double_to_lammps_variable_data, & + assign_string_to_lammps_variable_data END INTERFACE ! interface definitions for calling functions in library.cpp @@ -412,17 +426,69 @@ MODULE LIBLAMMPS INTEGER(c_int) :: lammps_version END FUNCTION lammps_version - !SUBROUTINE lammps_get_os_info + SUBROUTINE lammps_get_os_info (buffer, buf_size) BIND(C) + IMPORT :: C_ptr, C_int + IMPLICIT NONE + TYPE (C_ptr), VALUE :: buffer + INTEGER (C_int), VALUE :: buf_size + END SUBROUTINE lammps_get_os_info - !LOGICAL FUNCTION lammps_config_has_mpi_support - !LOGICAL FUNCTION lammps_config_has_gzip_support - !LOGICAL FUNCTION lammps_config_has_png_support - !LOGICAL FUNCTION lammps_config_has_jpeg_support - !LOGICAL FUNCTION lammps_config_has_ffmpeg_support - !LOGICAL FUNCTION lammps_config_has_exceptions - !LOGICAL FUNCTION lammps_config_has_package - !INTEGER (C_int) FUNCTION lammps_config_package_count - !SUBROUTINE lammps_config_package_name + FUNCTION lammps_config_has_mpi_support() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER(c_int) :: lammps_config_has_mpi_support + END FUNCTION lammps_config_has_mpi_support + + FUNCTION lammps_config_has_gzip_support() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER(c_int) :: lammps_config_has_gzip_support + END FUNCTION lammps_config_has_gzip_support + + FUNCTION lammps_config_has_png_support() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER(c_int) :: lammps_config_has_png_support + END FUNCTION lammps_config_has_png_support + + FUNCTION lammps_config_has_jpeg_support() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER(c_int) :: lammps_config_has_jpeg_support + END FUNCTION lammps_config_has_jpeg_support + + FUNCTION lammps_config_has_ffmpeg_support() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER(c_int) :: lammps_config_has_ffmpeg_support + END FUNCTION lammps_config_has_ffmpeg_support + + FUNCTION lammps_config_has_exceptions() BIND(C) + IMPORT :: c_int + IMPLICIT NONE + INTEGER (c_int) :: lammps_config_has_exceptions + END FUNCTION lammps_config_has_exceptions + + FUNCTION lammps_config_has_package(name) BIND(C) + IMPORT :: C_int, C_ptr + IMPLICIT NONE + TYPE (C_ptr), VALUE :: name + INTEGER (c_int) :: lammps_config_has_package + END FUNCTION lammps_config_has_package + + FUNCTION lammps_config_package_count() BIND(C) + IMPORT :: C_int + IMPLICIT NONE + INTEGER (C_int) :: lammps_config_package_count + END FUNCTION lammps_config_package_count + + FUNCTION lammps_config_package_name (idx, buffer, buf_size) BIND(C) + IMPORT :: C_int, C_ptr + IMPLICIT NONE + INTEGER (C_int) :: lammps_config_package_name + INTEGER (C_int), VALUE :: idx, buf_size + TYPE (C_ptr), VALUE :: buffer + END FUNCTION lammps_config_package_name !LOGICAL FUNCTION lammps_config_accelerator !LOGICAL FUNCTION lammps_has_gpu_device @@ -474,7 +540,11 @@ MODULE LIBLAMMPS TYPE(c_ptr), VALUE :: handle END FUNCTION lammps_is_running - !SUBROUTINE lammps_force_timeout + SUBROUTINE lammps_force_timeout (handle) BIND(C) + IMPORT :: c_ptr + IMPLICIT NONE + TYPE(c_ptr), VALUE :: handle + END SUBROUTINE lammps_force_timeout INTEGER (C_int) FUNCTION lammps_has_error (handle) BIND(C) IMPORT :: c_ptr, c_int @@ -622,14 +692,14 @@ CONTAINS END SUBROUTINE lmp_commands_string ! equivalent function to lammps_get_natoms - DOUBLE PRECISION FUNCTION lmp_get_natoms(self) + REAL (c_double) FUNCTION lmp_get_natoms(self) CLASS(lammps) :: self lmp_get_natoms = lammps_get_natoms(self%handle) END FUNCTION lmp_get_natoms ! equivalent function to lammps_get_thermo - REAL (C_double) FUNCTION lmp_get_thermo(self,name) + REAL (c_double) FUNCTION lmp_get_thermo(self,name) CLASS(lammps), INTENT(IN) :: self CHARACTER(LEN=*) :: name TYPE(C_ptr) :: Cname @@ -1025,6 +1095,7 @@ CONTAINS CALL lammps_free(Cname) CALL lammps_free(Cgroup) + variable_data%lammps_instance => self SELECT CASE (datatype) CASE (LMP_VAR_EQUAL) variable_data%datatype = DATA_DOUBLE @@ -1059,12 +1130,125 @@ CONTAINS lmp_version = lammps_version(self%handle) END FUNCTION lmp_version - ! equivalent function to lammps_is_running - LOGICAL FUNCTION lmp_is_running(self) - CLASS(lammps), INTENT(IN) :: self + ! equivalent function to lammps_get_os_info + SUBROUTINE lmp_get_os_info (buffer) + CHARACTER(LEN=*) :: buffer + INTEGER(c_int) :: buf_size + CHARACTER(LEN=1,KIND=c_char), DIMENSION(LEN(buffer)), TARGET :: Cbuffer + TYPE(c_ptr) :: ptr + INTEGER :: i - lmp_is_running = ( lammps_is_running(self%handle) /= 0_C_int ) - END FUNCTION lmp_is_running + buffer = '' + ptr = C_LOC(Cbuffer(1)) + buf_size = LEN(buffer) + CALL lammps_get_os_info (ptr, buf_size) + DO i=1,buf_size + IF ( Cbuffer(i) == C_NULL_CHAR ) EXIT + buffer(i:i) = Cbuffer(i) + END DO + END SUBROUTINE lmp_get_os_info + + ! equivalent function to lammps_config_has_mpi_support + LOGICAL FUNCTION lmp_config_has_mpi_support() + INTEGER(c_int) :: has_mpi_support + + has_mpi_support = lammps_config_has_mpi_support() + lmp_config_has_mpi_support = (has_mpi_support /= 0_c_int) + END FUNCTION lmp_config_has_mpi_support + + ! equivalent function to lammps_config_has_gzip_support + LOGICAL FUNCTION lmp_config_has_gzip_support() + INTEGER(c_int) :: has_gzip_support + + has_gzip_support = lammps_config_has_gzip_support() + lmp_config_has_gzip_support = (has_gzip_support /= 0_c_int) + END FUNCTION lmp_config_has_gzip_support + + ! equivalent function to lammps_config_has_png_support + LOGICAL FUNCTION lmp_config_has_png_support() + INTEGER(C_int) :: has_png_support + + has_png_support = lammps_config_has_png_support() + lmp_config_has_png_support = (has_png_support /= 0_c_int) + END FUNCTION lmp_config_has_png_support + + ! equivalent function to lammps_config_has_jpeg_support + LOGICAL FUNCTION lmp_config_has_jpeg_support() + INTEGER(c_int) :: has_jpeg_support + + has_jpeg_support = lammps_config_has_jpeg_support() + lmp_config_has_jpeg_support = (has_jpeg_support /= 0_c_int) + END FUNCTION lmp_config_has_jpeg_support + + ! equivalent function to lammps_config_has_ffmpeg_support + LOGICAL FUNCTION lmp_config_has_ffmpeg_support() + INTEGER(c_int) :: has_ffmpeg_support + + has_ffmpeg_support = lammps_config_has_ffmpeg_support() + lmp_config_has_ffmpeg_support = (has_ffmpeg_support /= 0_c_int) + END FUNCTION lmp_config_has_ffmpeg_support + + ! equivalent function to lammps_config_has_exceptions + LOGICAL FUNCTION lmp_config_has_exceptions() + INTEGER(c_int) :: has_exceptions + + has_exceptions = lammps_config_has_exceptions() + lmp_config_has_exceptions = (has_exceptions /= 0_c_int) + END FUNCTION lmp_config_has_exceptions + + ! equivalent function to lammps_config_has_package + LOGICAL FUNCTION lmp_config_has_package(name) + CHARACTER(LEN=*), INTENT(IN) :: name + INTEGER (c_int) :: has_package + TYPE (c_ptr) :: Cname + + Cname = f2c_string(name) + has_package = lammps_config_has_package(Cname) + lmp_config_has_package = (has_package /= 0_c_int) + CALL lammps_free(Cname) + END FUNCTION lmp_config_has_package + + ! equivalent subroutine to lammps_config_package_name + SUBROUTINE lmp_config_package_name (idx, buffer) + INTEGER, INTENT(IN) :: idx + CHARACTER(LEN=*), INTENT(OUT) :: buffer + INTEGER(c_int) :: Cidx, Csuccess + TYPE(c_ptr) :: Cptr + CHARACTER(LEN=1,KIND=c_char), TARGET :: Cbuffer(LEN(buffer)+1) + INTEGER :: i, strlen + + Cidx = idx - 1 + Cptr = C_LOC(Cbuffer(1)) + Csuccess = lammps_config_package_name(Cidx, Cptr, LEN(buffer)+1) + buffer = '' + IF ( Csuccess /= 0_c_int ) THEN + strlen = c_strlen(Cptr) + FORALL ( i = 1:strlen ) + buffer(i:i) = Cbuffer(i) + END FORALL + END IF + END SUBROUTINE lmp_config_package_name + + ! equivalent function to Python routine .installed_packages() + SUBROUTINE lmp_installed_packages (package, length) + CHARACTER(LEN=:), DIMENSION(:), ALLOCATABLE, INTENT(OUT) :: package + INTEGER, INTENT(IN), OPTIONAL :: length + INTEGER, PARAMETER :: MAX_BUFFER_LENGTH = 31 + INTEGER :: i, npackage, buf_length + + IF ( PRESENT(length) ) THEN + buf_length = length + ELSE + buf_length = MAX_BUFFER_LENGTH + END IF + + IF ( ALLOCATED(package) ) DEALLOCATE(package) + npackage = lammps_config_package_count() + ALLOCATE( CHARACTER(LEN=MAX_BUFFER_LENGTH) :: package(npackage) ) + DO i=1, npackage + CALL lmp_config_package_name(i, package(i)) + END DO + END SUBROUTINE lmp_installed_packages ! equivalent function to lammps_flush_buffers SUBROUTINE lmp_flush_buffers(self) @@ -1073,6 +1257,20 @@ CONTAINS CALL lammps_flush_buffers(self%handle) END SUBROUTINE lmp_flush_buffers + ! equivalent function to lammps_is_running + LOGICAL FUNCTION lmp_is_running(self) + CLASS(lammps), INTENT(IN) :: self + + lmp_is_running = ( lammps_is_running(self%handle) /= 0_C_int ) + END FUNCTION lmp_is_running + + ! equivalent function to lammps_force_timeout + SUBROUTINE lmp_force_timeout (self) + CLASS(lammps), INTENT(IN) :: self + + CALL lammps_force_timeout(self%handle) + END SUBROUTINE + ! equivalent function to lammps_has_error LOGICAL FUNCTION lmp_has_error(self) CLASS(lammps), INTENT(IN) :: self @@ -1264,6 +1462,17 @@ CONTAINS END IF END SUBROUTINE assign_doublevec_to_lammps_variable_data + SUBROUTINE assign_string_to_lammps_variable_data (lhs, rhs) + CHARACTER(LEN=*), INTENT(OUT) :: lhs + CLASS(lammps_variable_data), INTENT(IN) :: rhs + + IF ( rhs%datatype == DATA_STRING ) THEN + lhs = rhs%str + ELSE + CALL assignment_error(rhs, 'string') + END IF + END SUBROUTINE assign_string_to_lammps_variable_data + ! ---------------------------------------------------------------------- ! Generic function to catch all errors in assignments of LAMMPS data to ! user-space variables/pointers @@ -1292,6 +1501,8 @@ CONTAINS str1 = 'vector of doubles' CASE (DATA_DOUBLE_2D) str1 = 'matrix of doubles' + CASE (DATA_STRING) + str1 = 'string' CASE DEFAULT str1 = 'that type' END SELECT diff --git a/python/lammps/core.py b/python/lammps/core.py index aa4aae13db..45f49332a7 100644 --- a/python/lammps/core.py +++ b/python/lammps/core.py @@ -301,6 +301,8 @@ class lammps(object): self.lib.lammps_extract_fix.argtypes = [c_void_p, c_char_p, c_int, c_int, c_int, c_int] self.lib.lammps_extract_variable.argtypes = [c_void_p, c_char_p, c_char_p] + self.lib.lammps_extract_variable_datatype.argtypes = [c_void_p, c_char_p] + self.lib.lammps_extract_variable_datatype.restype = c_int self.lib.lammps_fix_external_get_force.argtypes = [c_void_p, c_char_p] self.lib.lammps_fix_external_get_force.restype = POINTER(POINTER(c_double)) @@ -1083,21 +1085,22 @@ class lammps(object): # for vector, must copy nlocal returned values to local c_double vector # memory was allocated by library interface function - def extract_variable(self, name, group=None, vartype=LMP_VAR_EQUAL): + def extract_variable(self, name, group=None, vartype=None): """ Evaluate a LAMMPS variable and return its data This function is a wrapper around the function - :cpp:func:`lammps_extract_variable` of the C-library interface, + :cpp:func:`lammps_extract_variable` of the C library interface, evaluates variable name and returns a copy of the computed data. The memory temporarily allocated by the C-interface is deleted after the data is copied to a Python variable or list. The variable must be either an equal-style (or equivalent) - variable or an atom-style variable. The variable type has to - provided as ``vartype`` parameter which may be one of two constants: - ``LMP_VAR_EQUAL`` or ``LMP_VAR_ATOM``; it defaults to - equal-style variables. - The group parameter is only used for atom-style variables and - defaults to the group "all" if set to ``None``, which is the default. + variable or an atom-style variable. The variable type can be + provided as the ``vartype`` parameter, which may be one of several + constants: ``LMP_VAR_EQUAL``, ``LMP_VAR_ATOM``, or ``LMP_VAR_STRING``. + If omitted or ``None``, LAMMPS will determine its value for you based on + a call to :cpp:func:`lammps_extract_variable_datatype` from the C library + interface. The group parameter is only used for atom-style variables and + defaults to the group "all". :param name: name of the variable to execute :type name: string @@ -1111,6 +1114,9 @@ class lammps(object): if name: name = name.encode() else: return None if group: group = group.encode() + if vartype is None : + vartype = self.lib.lammps_extract_variable_datatype(self.lmp, name) + #vartype = LMP_VAR_EQUAL if vartype == LMP_VAR_EQUAL: self.lib.lammps_extract_variable.restype = POINTER(c_double) with ExceptionCheck(self): @@ -1130,6 +1136,11 @@ class lammps(object): self.lib.lammps_free(ptr) else: return None return result + elif vartype == LMP_VAR_STRING : + self.lib.lammps_extract_variable.restype = c_char_p + with ExceptionCheck(self) : + ptr = self.lib.lammps_extract_variable(self.lmp, name, group) + return ptr.decode('utf-8') return None # ------------------------------------------------------------------------- diff --git a/src/library.cpp b/src/library.cpp index cd4742a2e4..194c0d9674 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -4572,8 +4572,8 @@ int lammps_config_has_gzip_support() { \verbatim embed:rst The LAMMPS :doc:`dump style image ` supports writing multiple -image file formats. Most of them need, however, support from an external -library and using that has to be :ref:`enabled at compile time `. +image file formats. Most of them, however, need support from an external +library, and using that has to be :ref:`enabled at compile time `. This function checks whether support for the `PNG image file format `_ is available in the current LAMMPS library. @@ -4591,8 +4591,8 @@ int lammps_config_has_png_support() { \verbatim embed:rst The LAMMPS :doc:`dump style image ` supports writing multiple -image file formats. Most of them need, however, support from an external -library and using that has to be :ref:`enabled at compile time `. +image file formats. Most of them, however, need support from an external +library, and using that has to be :ref:`enabled at compile time `. This function checks whether support for the `JPEG image file format `_ is available in the current LAMMPS library. \endverbatim @@ -4609,7 +4609,7 @@ int lammps_config_has_jpeg_support() { \verbatim embed:rst The LAMMPS :doc:`dump style movie ` supports generating movies -from images on-the-fly via creating a pipe to the +from images on-the-fly via creating a pipe to the `ffmpeg `_ program. This function checks whether this feature was :ref:`enabled at compile time `. It does **not** check whether the ``ffmpeg`` itself is installed and usable. @@ -4623,14 +4623,14 @@ int lammps_config_has_ffmpeg_support() { /* ---------------------------------------------------------------------- */ -/** Check whether LAMMPS errors will throw a C++ exception +/** Check whether LAMMPS errors will throw C++ exceptions. * \verbatim embed:rst -In case of errors LAMMPS will either abort or throw a C++ exception. +In case of an error, LAMMPS will either abort or throw a C++ exception. The latter has to be :ref:`enabled at compile time `. This function checks if exceptions were enabled. -When using the library interface and C++ exceptions are enabled, +When using the library interface with C++ exceptions enabled, the library interface functions will "catch" them and the error status can then be checked by calling :cpp:func:`lammps_has_error` and the most recent error message @@ -4649,10 +4649,10 @@ int lammps_config_has_exceptions() { /* ---------------------------------------------------------------------- */ -/** Check if a specific package has been included in LAMMPS +/** Check whether a specific package has been included in LAMMPS * \verbatim embed:rst -This function checks if the LAMMPS library in use includes the +This function checks whether the LAMMPS library in use includes the specific :doc:`LAMMPS package ` provided as argument. \endverbatim * @@ -5609,7 +5609,7 @@ int lammps_is_running(void *handle) return lmp->update->whichflag; } -/** Force a timeout to cleanly stop an ongoing run +/** Force a timeout to stop an ongoing run cleanly. * * This function can be used from signal handlers or multi-threaded * applications to cleanly terminate an ongoing run. diff --git a/unittest/fortran/CMakeLists.txt b/unittest/fortran/CMakeLists.txt index fc0d0dc956..796416b7fb 100644 --- a/unittest/fortran/CMakeLists.txt +++ b/unittest/fortran/CMakeLists.txt @@ -69,6 +69,11 @@ if(CMAKE_Fortran_COMPILER) target_link_libraries(test_fortran_extract_fix PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTestMain) add_test(NAME FortranExtractFix COMMAND test_fortran_extract_fix) + add_executable(test_fortran_extract_variable wrap_extract_variable.cpp test_fortran_extract_variable.f90) + target_link_libraries(test_fortran_extract_variable PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTestMain) + add_test(NAME FortranExtractVariable COMMAND test_fortran_extract_variable) + + else() message(STATUS "Skipping Tests for the LAMMPS Fortran Module: no Fortran compiler") endif() diff --git a/unittest/fortran/atomdata.txt b/unittest/fortran/atomdata.txt new file mode 100644 index 0000000000..83a34e9f2b --- /dev/null +++ b/unittest/fortran/atomdata.txt @@ -0,0 +1,8 @@ +3 +2 1.6 +1 5.2 +3 -1.4 + +2 +3 2.5 +1 -1.1 diff --git a/unittest/fortran/greetings.txt b/unittest/fortran/greetings.txt new file mode 100644 index 0000000000..cb547f0adf --- /dev/null +++ b/unittest/fortran/greetings.txt @@ -0,0 +1,9 @@ +hello +god dag +hola +bonjour +guten Tag +konnichiwa +shalom +salve +goedendag diff --git a/unittest/fortran/test_fortran_extract_variable.f90 b/unittest/fortran/test_fortran_extract_variable.f90 new file mode 100644 index 0000000000..fda68b0cad --- /dev/null +++ b/unittest/fortran/test_fortran_extract_variable.f90 @@ -0,0 +1,161 @@ +MODULE keepvar + USE liblammps + IMPLICIT NONE + TYPE(LAMMPS) :: lmp + CHARACTER(LEN=40), DIMENSION(3), PARAMETER :: demo_input = & + [ CHARACTER(LEN=40) :: & + 'region box block 0 $x 0 3 0 4', & + 'create_box 1 box', & + 'create_atoms 1 single 1.0 1.0 ${zpos}' ] + CHARACTER(LEN=40), DIMENSION(3), PARAMETER :: cont_input = & + [ CHARACTER(LEN=40) :: & + 'create_atoms 1 single &', & + ' 0.2 0.1 0.1', & + 'create_atoms 1 single 0.5 0.5 0.5' ] + CHARACTER(LEN=40), DIMENSION(3), PARAMETER :: pair_input = & + [ CHARACTER(LEN=40) :: & + 'pair_style lj/cut 2.5', & + 'pair_coeff 1 1 1.0 1.0', & + 'mass 1 2.0' ] + CHARACTER(LEN=60), DIMENSION(4), PARAMETER :: py_input = & + [ CHARACTER(LEN=60) :: & + 'python square_it input 1 v_lp return v_square here """', & + 'def square_it(N) :', & + ' return N*N', & + '"""' ] + +CONTAINS + + FUNCTION absolute_path(filename) + CHARACTER(LEN=:), ALLOCATABLE :: absolute_path + CHARACTER(LEN=*), INTENT(IN) :: filename + CHARACTER(LEN=:), ALLOCATABLE :: test_input_directory + +print *, 'GOT HERE! filename is ', filename + test_input_directory = lmp%extract_variable('input_dir') +print *, ' test_input_directory is ', test_input_directory + absolute_path = test_input_directory // '/' // TRIM(filename) + END FUNCTION absolute_path + +END MODULE keepvar + +FUNCTION f_lammps_with_C_args(argc, argv) BIND(C) + USE ISO_C_BINDING, ONLY: c_ptr, c_char, c_int, c_size_t, c_f_pointer + USE liblammps + USE keepvar, ONLY: lmp + IMPLICIT NONE + INTEGER(c_int), INTENT(IN), VALUE :: argc + TYPE(c_ptr), VALUE :: argv + TYPE(c_ptr), DIMENSION(:), POINTER :: Fargv + INTEGER, PARAMETER :: ARG_LENGTH = 80 + TYPE(c_ptr) :: f_lammps_with_C_args + CHARACTER(LEN=ARG_LENGTH), DIMENSION(argc) :: args + CHARACTER(LEN=1,KIND=c_char), DIMENSION(:), POINTER :: Cstr + INTEGER :: i, length, j + + INTERFACE + FUNCTION c_strlen (str) BIND(C,name='strlen') + IMPORT :: c_ptr, c_size_t + IMPLICIT NONE + TYPE(c_ptr), INTENT(IN), VALUE :: str + INTEGER(c_size_t) :: c_strlen + END FUNCTION c_strlen + END INTERFACE + + CALL C_F_POINTER(argv, Fargv, [argc]) + DO i = 1, argc + args(i) = '' + length = c_strlen(Fargv(i)) + CALL C_F_POINTER(Fargv(i), Cstr, [length]) + FORALL (j = 1:length) + args(i)(j:j) = Cstr(j) + END FORALL + END DO + + lmp = lammps(args) + f_lammps_with_C_args = lmp%handle +END FUNCTION f_lammps_with_C_args + +SUBROUTINE f_lammps_close() BIND(C) + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepvar, ONLY: lmp + IMPLICIT NONE + + CALL lmp%close() + lmp%handle = c_null_ptr +END SUBROUTINE f_lammps_close + +SUBROUTINE f_lammps_setup_extract_variable () BIND(C) + USE LIBLAMMPS + USE keepvar, ONLY : lmp, demo_input, cont_input, pair_input, absolute_path + IMPLICIT NONE + + CALL lmp%commands_list(demo_input) + CALL lmp%commands_list(cont_input) + CALL lmp%commands_list(pair_input) + CALL lmp%command('variable idx index "hello" "goodbye"') + CALL lmp%command('variable lp loop 10') + CALL lmp%command('variable lp_pad loop 10 pad') + !CALL lmp%command('variable wld world "group1" "group2" "group3"') + CALL lmp%command('variable wld world "group1"') + CALL lmp%command('variable uni universe "universe1" "universeA"') + CALL lmp%command('variable ulp uloop 2') + CALL lmp%command('variable str index "this is a string"') + CALL lmp%command('variable fmt format lp %.6G') + CALL lmp%command('variable fmt_pad format lp %0.6g') + CALL lmp%command('variable shell getenv SHELL') +! CALL lmp%command('variable greet file ' // absolute_path('greetings.txt')) +! CALL lmp%command('variable atfile atomfile ' // absolute_path('atomdata.txt') + IF ( lmp%config_has_package('PYTHON') ) THEN + CALL lmp%command('variable py python square_it') + END IF + CALL lmp%command('variable time timer') + CALL lmp%command('variable int internal 4') + CALL lmp%command("variable nat equal count(all)") + CALL lmp%command("variable ts equal step") +END SUBROUTINE f_lammps_setup_extract_variable + +FUNCTION f_lammps_extract_variable_index_1 () BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : C_int + USE LIBLAMMPS + USE keepvar, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int) :: f_lammps_extract_variable_index_1 + CHARACTER(LEN=80) :: str + + str = lmp%extract_variable("idx") + IF ( trim(str) == 'hello' ) THEN + f_lammps_extract_variable_index_1 = 1_c_int + ELSE + f_lammps_extract_variable_index_1 = 0_c_int + END IF +END FUNCTION f_lammps_extract_variable_index_1 + +FUNCTION f_lammps_extract_variable_index_2 () BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : C_int + USE LIBLAMMPS + USE keepvar, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int) :: f_lammps_extract_variable_index_2 + CHARACTER(LEN=80) :: str + + str = lmp%extract_variable("idx") + IF ( trim(str) == 'goodbye' ) THEN + f_lammps_extract_variable_index_2 = 1_c_int + ELSE + f_lammps_extract_variable_index_2 = 0_c_int + END IF +END FUNCTION f_lammps_extract_variable_index_2 + +FUNCTION f_lammps_extract_variable_loop () BIND(C) + USE, INTRINSIC :: ISO_C_BINDING, ONLY : C_int, C_double + USE LIBLAMMPS + USE keepvar, ONLY : lmp + IMPLICIT NONE + INTEGER(c_int) :: f_lammps_extract_variable_loop + CHARACTER(LEN=80) :: loop + + loop = lmp%extract_variable('lp') + READ(loop,*) f_lammps_extract_variable_loop +END FUNCTION f_lammps_extract_variable_loop diff --git a/unittest/fortran/wrap_extract_variable.cpp b/unittest/fortran/wrap_extract_variable.cpp new file mode 100644 index 0000000000..0e3ffecfac --- /dev/null +++ b/unittest/fortran/wrap_extract_variable.cpp @@ -0,0 +1,87 @@ +// unit tests for extracting compute data from a LAMMPS instance through the +// Fortran wrapper +#include + +#include "lammps.h" +#include "library.h" +#include +#include +#include +#include + +#include "gtest/gtest.h" + +#define STRINGIFY(val) XSTR(val) +#define XSTR(val) #val + +// prototypes for Fortran reverse wrapper functions +extern "C" { +void *f_lammps_with_c_args(int,char**); +void f_lammps_close(); +void f_lammps_setup_extract_variable(); +int f_lammps_extract_variable_index_1(); +int f_lammps_extract_variable_index_2(); +int f_lammps_extract_variable_loop(); +double f_lammps_extract_variable_loop_pad(); +void f_lammps_setup_extract_variable_world(); +void f_lammps_setup_extract_variable_universe(); +int f_lammps_setup_extract_variable_uloop(); +void f_lammps_setup_extract_variable_string(); +void f_lammps_setup_extract_variable_format(); +void f_lammps_setup_extract_variable_getenv(); +void f_lammps_setup_extract_variable_file(); +void f_lammps_setup_extract_variable_atomfile(); +double f_lammps_setup_extract_variable_python(); +double f_lammps_setup_extract_variable_timer(); +double f_lammps_setup_extract_variable_internal(); +double f_lammps_extract_variable_equal_natoms(); +double f_lammps_extract_variable_equal_dt(); +double f_lammps_extract_variable_vector(int); +double f_lammps_extract_variable_atom(int); +} + +class LAMMPS_extract_variable : public ::testing::Test { +protected: + LAMMPS_NS::LAMMPS *lmp; + LAMMPS_extract_variable() = default; + ~LAMMPS_extract_variable() override = default; + + void SetUp() override + { + const char *args[] = {"LAMMPS_Fortran_test", "-l", "none", + "-echo", "screen", "-nocite", "-var", + "input_dir", STRINGIFY(TEST_INPUT_FOLDER), + "-var", "zpos", "1.5", "-var", "x", "2"}; + char** argv = (char**) args; + int argc = sizeof(args) / sizeof(const char*); + ::testing::internal::CaptureStdout(); +std::fprintf(stderr,"THIS IS A TEST\n"); + lmp = (LAMMPS_NS::LAMMPS*)f_lammps_with_c_args(argc, argv); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0, 8).c_str(), "LAMMPS ("); + } + void TearDown() override + { + ::testing::internal::CaptureStdout(); + f_lammps_close(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0, 16).c_str(), "Total wall time:"); + lmp = nullptr; + } +}; + +TEST_F(LAMMPS_extract_variable, index) +{ + f_lammps_setup_extract_variable(); + EXPECT_EQ(f_lammps_extract_variable_index_1(), 1); + EXPECT_EQ(f_lammps_extract_variable_index_2(), 0); + lammps_command(lmp, "next idx"); + EXPECT_EQ(f_lammps_extract_variable_index_1(), 0); + EXPECT_EQ(f_lammps_extract_variable_index_2(), 1); +}; + +TEST_F(LAMMPS_extract_variable, loop) +{ + f_lammps_setup_extract_variable(); + EXPECT_EQ(f_lammps_extract_variable_loop(), 1); +};