diff --git a/doc/src/Fortran.rst b/doc/src/Fortran.rst index 19182321b5..4e093dc49b 100644 --- a/doc/src/Fortran.rst +++ b/doc/src/Fortran.rst @@ -80,7 +80,7 @@ the optional logical argument set to ``.true.``. Here is a simple example: END PROGRAM testlib It is also possible to pass command line flags from Fortran to C/C++ and -thus make the resulting executable behave similar to the standalone +thus make the resulting executable behave similarly to the standalone executable (it will ignore the `-in/-i` flag, though). This allows to use the command line to configure accelerator and suffix settings, configure screen and logfile output, or to set index style variables @@ -190,6 +190,7 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. :f reset_box: :f:func:`reset_box` :f memory_usage: :f:func:`memory_usage` :f extract_setting: :f:func:`extract_setting` + :f extract_global: :f:func:`extract_global` -------- @@ -210,7 +211,57 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. :o integer comm [optional]: MPI communicator :r lammps: an instance of the :f:type:`lammps` derived type --------- + .. note:: + + The ``MPI_F08`` module, which defines Fortran 2008 bindings for MPI, + is not directly supported by this interface due to the complexities of + supporting both the ``MPI_F08`` and ``MPI`` modules at the same time. + However, you should be able to use the ``MPI_VAL`` member of the + ``MPI_comm`` derived type to access the integer value of the + communicator, such as in + + .. code-block:: Fortran + + PROGRAM testmpi + USE LIBLAMMPS + USE MPI_F08 + TYPE(lammps) :: lmp + lmp = lammps(MPI_COMM_SELF%MPI_VAL) + END PROGRAM testmpi + +Constants Defined by the API +============================ + +The following constants are declared by the Fortran API to resolve the +type/kind/rank signature for return values. These serve the same role as +``LAMMPS_INT``, ``LAMMPS_DOUBLE``, and similar constants in ``src/library.h`` +and those in ``python/lammps/constants.py`` for the C and Python APIs, +respectively. Unlike their C and Python bretheren, however, it is the type +(e.g., ``INTEGER``), kind (e.g., ``C_int``), and rank (e.g., ``DIMENSION(:)``) +of these constants that is used by the calling routine, rather than their +numerical values. + +:f:LMP_INT: 32-bit integer scalars +:f:LMP_INT_1D: 32-bit integer vectors +:f:LMP_INT_2D: 32-bit integer matrices +:f:LMP_DOUBLE: 64-bit real scalars +:f:LMP_DOUBLE_1D: 64-bit real vectors +:f:LMP_DOUBLE_2D: 64-bit real matrices +:f:LMP_INT64: 64-bit integer scalars +:f:LMP_INT64_1D: 64-bit integer vectors +:f:LMP_INT64_2D: 64-bit integer matrices + +.. admonition:: Interaction with LAMMPS_BIGBIG and such + + LAMMPS uses different-sized integers to store various entities, such as + the number of timesteps or the total number of atoms, depending on certain + compiler flags (see the :doc:`size limits ` + documentation). This API is currently agnostic to these settings, and it + is up to the user to know the size of LAMMPS_BIGINT and such and pass + LMP_INT or LMP_INT64, as appropriate, for such entities. + +Procedures Bound to the lammps Derived Type +=========================================== .. f:subroutine:: close([finalize]) @@ -278,12 +329,20 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. .. f:function:: get_thermo(name) + This function will call :cpp:func:`lammps_get_thermo` and return the value + of the corresponding thermodynamic keyword. + :p character(len=*) name: string with the name of the thermo keyword :r real(C_double): value of the requested thermo property or 0.0_C_double -------- .. f:subroutine:: extract_box(boxlo, boxhi, xy, yz, xz, pflags, boxflag) + + This subroutine will call :cpp:func:`lammps_extract_box`. All parameters + are optional, though obviously at least one should be present. The + parameters *pflags* and *boxflag* are stored in LAMMPS as integers, but + should be declared as ``LOGICAL`` variables when calling from Fortran. :p real(c_double) boxlo [dimension(3),optional]: vector in which to store lower-bounds of simulation box @@ -302,6 +361,9 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. .. f:subroutine:: reset_box(boxlo, boxhi, xy, yz, xz) + This subroutine will call :cpp:func:`lammps_reset_box`. All parameters + are required. + :p real(c_double) boxlo [dimension(3)]: vector of three doubles containing the lower box boundary :p real(c_double) boxhi [dimension(3)]: vector of three doubles containing @@ -314,6 +376,9 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. .. f:subroutine:: memory_usage(meminfo) + This subroutine will call :cpp:func:`lammps_memory_usage` and store the + result in the three-element array *meminfo*. + :p real(c_double) meminfo [dimension(3)]: vector of three doubles in which to store memory usage data @@ -321,30 +386,92 @@ of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. .. f:function:: get_mpi_comm() + This function returns a Fortran representation of the LAMMPS "world" + communicator. + :r integer: Fortran integer equivalent to the MPI communicator LAMMPS is using -.. note:: + .. note:: - The MPI_F08 module, which is in compliance with the Fortran 2008 standard, - is not directly supported by this function. However, you should be able to - convert between the two using the MPI_VAL member of the communicator. For - example, + The C library interface currently returns type "int" instead of type + "MPI_Fint", which is the C type correspending to Fortran "INTEGER" + types of the default kind. On most compilers, these are the same anyway, + but this interface exchanges values this way to avoid warning messages. - .. code-block:: fortran + .. note:: - USE MPI_F08 - USE LIBLAMMPS - TYPE (LAMMPS) :: lmp - TYPE (MPI_Comm) :: comm - ! ... [commands to set up LAMMPS/etc.] - comm%MPI_VAL = lmp%get_mpi_comm() + The MPI_F08 module, which defines Fortran 2008 bindings for MPI, is not + directly supported by this function. However, you should be able to + convert between the two using the MPI_VAL member of the communicator. + For example, - should assign an MPI_F08 communicator properly. + .. code-block:: fortran + + USE MPI_F08 + USE LIBLAMMPS + TYPE (LAMMPS) :: lmp + TYPE (MPI_Comm) :: comm + ! ... [commands to set up LAMMPS/etc.] + comm%MPI_VAL = lmp%get_mpi_comm() + + should assign an MPI_F08 communicator properly. -------- .. f:function:: extract_setting(keyword) + Query LAMMPS about global settings. See the documentation for the + :c:func:`lammps_extract_setting` function from the C library. + :p character(len=*) keyword: string containing the name of the thermo keyword :r integer(c_int): value of the queried setting or :math:`-1` if unknown + +-------- + +.. f:function:: extract_global(name, dtype) + + Overloaded function to get internal global LAMMPS data. Note that all + currently implemented global types only return scalars or strings; all + array-returning entities currently supported use :f:func:`extract_box`. + + Note that type/kind/rank of the *dtype* argument is used to determine + whether to return a type correspending to a C int, a C int64_t, or a + C double. The type/kind/rank signature of dtype is checked at runtime to + match that of the return value; this type of check cannot be performed at + compile time. For example, + + .. code-block:: fortran + + PROGRAM demo + USE, INTRINSIC :: ISO_C_BINDING, ONLY : C_int64_t + USE LIBLAMMPS + TYPE(lammps) :: lmp + INTEGER(C_int) :: nlocal + INTEGER(C_int64_t) :: ntimestep + CHARACTER(LEN=10) :: units + REAL(C_double) :: dt + lmp = lammps() + ! other commands + nlocal = lmp%extract_global('nlocal', LMP_INT) + ntimestep = lmp%extract_global('ntimestep', LMP_INT64) + dt = lmp%extract_global('dt', LMP_DOUBLE) + units = lmp%extract_global('units', LMP_STRING) + ! more commands + lmp.close(.TRUE.) + END PROGRAM demo + + would extract the number of atoms on this processor, the current time step, + the size of the current time step, and the units being used into the + variables *nlocal*, *ntimestep*, *dt*, and *units*, respectively. + + *Note*: if this function returns a string, the string must have + length 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. + + :p character(len=*) name: string with the name of the extracted property + :p polymorphic dtype: one of *LMP_INT*, *LMP_INT64*, *LMP_DOUBLE*, or + *LMP_STRING* designating the type/kind/rank of the return value + :r polymorphic: value of the extracted property diff --git a/fortran/lammps.f90 b/fortran/lammps.f90 index 0b4f8d4f4a..f7a58bc572 100644 --- a/fortran/lammps.f90 +++ b/fortran/lammps.f90 @@ -38,10 +38,10 @@ MODULE LIBLAMMPS ! These are public-interface constants that have the same purpose as the ! constants in library.h, except that their types match the type of the - ! constant in question. Their purpose is to determine the type of the + ! constant in question. Their purpose is to specify the type of the ! return value without something akin to a C/C++ type cast INTEGER (c_int), PUBLIC, PARAMETER :: LMP_INT = 0_c_int - INTEGER (c_int), PUBLIC, DIMENSION(3), PARAMETER :: LMP_INT_1D = 1_c_int + INTEGER (c_int), PUBLIC, DIMENSION(3), PARAMETER :: LMP_INT_1D = 0_c_int INTEGER (c_int), PUBLIC, DIMENSION(3,3), PARAMETER :: LMP_INT_2D = 1_c_int REAL (c_double), PUBLIC, PARAMETER :: LMP_DOUBLE = 2.0_c_double REAL (c_double), PUBLIC, DIMENSION(3), PARAMETER :: & @@ -59,6 +59,8 @@ MODULE LIBLAMMPS ! ! Must be kept in sync with the equivalent declarations in ! src/library.h and python/lammps/constants.py + ! + ! NOT part of the API (the part the user sees) INTEGER (c_int), PARAMETER :: & LAMMPS_INT = 0_c_int, & ! 32-bit integer (array) LAMMPS_INT_2D = 1, & ! two-dimensional 32-bit integer array @@ -84,8 +86,12 @@ MODULE LIBLAMMPS PROCEDURE :: get_mpi_comm => lmp_get_mpi_comm PROCEDURE :: extract_setting => lmp_extract_setting PROCEDURE, PRIVATE :: lmp_extract_global_int - GENERIC :: extract_global => lmp_extract_global_int ! TODO - + PROCEDURE, PRIVATE :: lmp_extract_global_int64_t + PROCEDURE, PRIVATE :: lmp_extract_global_double + PROCEDURE, PRIVATE :: lmp_extract_global_str + GENERIC :: extract_global => lmp_extract_global_int, & + lmp_extract_global_int64_t, lmp_extract_global_double, & + lmp_extract_global_str PROCEDURE :: version => lmp_version END TYPE lammps @@ -207,6 +213,13 @@ MODULE LIBLAMMPS INTEGER (c_int) :: lammps_extract_global_datatype END FUNCTION lammps_extract_global_datatype + FUNCTION c_strlen (str) bind(C,name='strlen') + IMPORT :: c_ptr, c_size_t + IMPLICIT NONE + TYPE(c_ptr) :: str + INTEGER(c_size_t) :: c_strlen + END FUNCTION c_strlen + FUNCTION lammps_extract_global(handle, name) BIND(C) IMPORT :: c_ptr TYPE(c_ptr), VALUE :: handle, name @@ -530,49 +543,94 @@ CONTAINS ! END FUNCTION lmp_extract_global_datatype ! equivalent functions to lammps_extract_global (overloaded) + ! This implementation assumes there are no non-scalar data that can be + ! extracted through lammps_extract_global FUNCTION lmp_extract_global_int (self, name, dtype) CLASS(lammps), INTENT(IN) :: self CHARACTER(LEN=*), INTENT(IN) :: name - INTEGER(c_int) :: dtype + INTEGER(c_int), INTENT(IN) :: dtype INTEGER(c_int) :: lmp_extract_global_int TYPE(c_ptr) :: Cname, Cptr INTEGER(c_int) :: datatype INTEGER(c_int), POINTER :: ptr Cname = f2c_string(name) - datatype = lammps_extract_global_datatype(Cname) + datatype = lammps_extract_global_datatype(c_null_ptr, Cname) IF ( datatype /= LAMMPS_INT ) THEN ! throw an exception or something; data type doesn't match! - WRITE(0,*) 'WARNING: global data type is inconsistent' + WRITE(0,*) 'WARNING: global data type is inconsistent (not an int)' END IF Cptr = lammps_extract_global(self%handle, Cname) CALL c_f_pointer(Cptr, ptr) lmp_extract_global_int = ptr CALL lammps_free(Cname) END FUNCTION lmp_extract_global_int - FUNCTION lmp_extract_global_int_1d (self, name, dtype) - ! This implementation assumes there are three elements to all arrays + FUNCTION lmp_extract_global_int64_t (self, name, dtype) CLASS(lammps), INTENT(IN) :: self CHARACTER(LEN=*), INTENT(IN) :: name - INTEGER(c_int), DIMENSION(3) :: dtype - INTEGER(c_int) :: lmp_extract_global_int + INTEGER(c_int64_t), INTENT(IN) :: dtype + INTEGER(c_int64_t) :: lmp_extract_global_int64_t TYPE(c_ptr) :: Cname, Cptr INTEGER(c_int) :: datatype - INTEGER(c_int), DIMENSION(3), POINTER :: ptr + INTEGER(c_int64_t), POINTER :: ptr Cname = f2c_string(name) - datatype = lammps_extract_global_datatype(Cname) - IF ( datatype /= LAMMPS_INT ) THEN + datatype = lammps_extract_global_datatype(c_null_ptr, Cname) + IF ( datatype /= LAMMPS_INT64 ) THEN ! throw an exception or something; data type doesn't match! + WRITE(0,*) 'WARNING: global data type is inconsistent (not an int64_t)' END IF Cptr = lammps_extract_global(self%handle, Cname) - CALL c_f_pointer(Cptr, ptr, shape(dtype)) - lmp_extract_global_int = ptr + CALL c_f_pointer(Cptr, ptr) + lmp_extract_global_int64_t = ptr CALL lammps_free(Cname) - END FUNCTION lmp_extract_global_int_1d - ! TODO need more generics here to match TKR of LMP_INT_1D, LMP_BIGINT, - ! LMP_DOUBLE, LMP_DOUBLE_1D, LMS_STRING [this assumes no one adds anything - ! requiring LMP_DOUBLE_2D and the like!] + END FUNCTION lmp_extract_global_int64_t + FUNCTION lmp_extract_global_double (self, name, dtype) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name + REAL(c_double), INTENT(IN) :: dtype + REAL(c_double) :: lmp_extract_global_double + TYPE(c_ptr) :: Cname, Cptr + INTEGER(c_int) :: datatype + REAL(c_double), POINTER :: ptr + + Cname = f2c_string(name) + datatype = lammps_extract_global_datatype(c_null_ptr, Cname) + IF ( datatype /= LAMMPS_DOUBLE ) THEN + ! throw an exception or something; data type doesn't match! + WRITE(0,*) 'WARNING: global data type is inconsistent (not a double)' + END IF + Cptr = lammps_extract_global(self%handle, Cname) + CALL c_f_pointer(Cptr, ptr) + lmp_extract_global_double = ptr + CALL lammps_free(Cname) + END FUNCTION lmp_extract_global_double + FUNCTION lmp_extract_global_str (self, name, dtype) + CLASS(lammps), INTENT(IN) :: self + CHARACTER(LEN=*), INTENT(IN) :: name, dtype + CHARACTER(LEN=:), ALLOCATABLE :: lmp_extract_global_str + TYPE(c_ptr) :: Cname, Cptr + INTEGER(c_int) :: datatype + CHARACTER(KIND=c_char,LEN=1), dimension(:), POINTER :: ptr + INTEGER(c_size_t) :: length + INTEGER :: i + + Cname = f2c_string(name) + datatype = lammps_extract_global_datatype(c_null_ptr, Cname) + IF ( datatype /= LAMMPS_STRING ) THEN + ! throw an exception or something; data type doesn't match! + WRITE(0,*) 'WARNING: global data type is inconsistent (not a string)' + END IF + Cptr = lammps_extract_global(self%handle, Cname) + length = c_strlen(Cptr) + CALL c_f_pointer(Cptr, ptr, [length]) + ALLOCATE ( CHARACTER(LEN=length) :: lmp_extract_global_str ) + FORALL ( I=1:length ) + lmp_extract_global_str(i:i) = ptr(i) + END FORALL + CALL lammps_free(Cname) + ! the allocatable scalar (return value) gets auto-deallocated on return + END FUNCTION lmp_extract_global_str ! equivalent function to lammps_version() INTEGER FUNCTION lmp_version(self)