From cb1a2601e12a92aa374cc0ca33030ecd50d1c817 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:25:09 -0400 Subject: [PATCH 01/22] add more existing programmer guide docs --- doc/src/pg_cplusplus.rst | 91 +++++++++++++++++++ doc/src/pg_lib_add.rst | 33 +++++++ doc/src/pg_lib_config.rst | 67 ++++++++++++++ doc/src/pg_lib_create.rst | 104 ++++++++++++++++++++++ doc/src/pg_lib_execute.rst | 69 +++++++++++++++ doc/src/pg_lib_neighbor.rst | 30 +++++++ doc/src/pg_lib_objects.rst | 31 +++++++ doc/src/pg_lib_properties.rst | 62 +++++++++++++ doc/src/pg_lib_scatter.rst | 29 +++++++ doc/src/pg_lib_utility.rst | 30 +++++++ doc/src/pg_library.rst | 20 ++--- doc/src/pg_python.rst | 159 ++++++++++++++++++++++++++++++++++ 12 files changed, 715 insertions(+), 10 deletions(-) create mode 100644 doc/src/pg_cplusplus.rst create mode 100644 doc/src/pg_lib_add.rst create mode 100644 doc/src/pg_lib_config.rst create mode 100644 doc/src/pg_lib_create.rst create mode 100644 doc/src/pg_lib_execute.rst create mode 100644 doc/src/pg_lib_neighbor.rst create mode 100644 doc/src/pg_lib_objects.rst create mode 100644 doc/src/pg_lib_properties.rst create mode 100644 doc/src/pg_lib_scatter.rst create mode 100644 doc/src/pg_lib_utility.rst diff --git a/doc/src/pg_cplusplus.rst b/doc/src/pg_cplusplus.rst new file mode 100644 index 0000000000..2a7028b0f8 --- /dev/null +++ b/doc/src/pg_cplusplus.rst @@ -0,0 +1,91 @@ +Using the C++ API directly +************************** + +Using the C++ classes of the LAMMPS library is lacking some of the +convenience of the C library API, but it allows a more direct access to +simulation data and thus more low-level manipulations and tighter +integration of LAMMPS into another code. While for the complete C +library API is provided in the ``library.h`` header file, for using +the C++ API it is required to include the individual header files +defining the individual classes in use. Typically the name of the +class and the name of the header follow some simple rule. Examples +are given below. + + +Creating or deleting a LAMMPS object +************************************* + +When using the LAMMPS library interfaces, the core task is to create an +instance of the :cpp:class:`LAMMPS_NS::LAMMPS` class. In C++ this can +be done directly through the ``new`` operator. All further operations +are then initiated through calling member functions of some of the +components of the LAMMPS class or accessing their data members. The +destruction of the LAMMPS instance is correspondingly initiated by using +the ``delete`` operator. Here is a simple example: + +.. code-block:: c++ + + #include "lammps.h" + #include "universe.h" + + #include + #include + + int main(int argc, char **argv) + { + LAMMPS_NS::LAMMPS *lmp; + // custom argument vector for LAMMPS library + const char *lmpargv[] {"liblammps", "-log", "none"}; + int lmpargc = sizeof(lmpargv)/sizeof(const char *); + + // explicitly initialize MPI + MPI_Init(&argc, &argv); + + // create LAMMPS instance + lmp = new LAMMPS_NS::LAMMPS(lmpargc, (char **)lmpargv, MPI_COMM_WORLD); + // output numerical version string + std::cout << "LAMMPS version: " << lmp->universe->num_ver << std::endl; + // delete LAMMPS instance + delete lmp; + + // stop MPI environment + MPI_Finalize(); + return 0; + } + +Please note that this requires to include the ``lammps.h`` header for accessing +the members of the LAMMPS class and then the ``universe.h`` header for accessing the ``num_ver`` member of the :cpp:class:`Universe` class. + + +Executing LAMMPS commands +************************* + +Once a LAMMPS instance is created by your C++ code, you need to set up a +simulation and that is most conveniently done by "driving" it through +issuing commands like you would do when running a LAMMPS simulation from +an input script. Processing of input in LAMMPS is handled by the +:cpp:class:`Input ` class an instance of which is a +member of the :cpp:class:`LAMMPS ` class. You have +two options: reading commands from a file, or executing a single +command from a string. See below for a small example: + +.. code-block:: c++ + + #include "lammps.h" + #include "input.h" + #include + + using namespace LAMMPS_NS; + + int main(int argc, char **argv) + { + const char *lmpargv[] {"liblammps", "-log", "none"}; + int lmpargc = sizeof(lmpargv)/sizeof(const char *); + + MPI_Init(&argc, &argv); + LAMMPS *lmp = new LAMMPS(lmpargc, (char **)lmpargv, MPI_COMM_WORLD); + lmp->input->file("in.melt"); + lmp->input->one("run 100 post no"); + delete lmp; + return 0; + } diff --git a/doc/src/pg_lib_add.rst b/doc/src/pg_lib_add.rst new file mode 100644 index 0000000000..139476c683 --- /dev/null +++ b/doc/src/pg_lib_add.rst @@ -0,0 +1,33 @@ +Adding code to the Library interface +==================================== + +The functionality of the LAMMPS library interface has historically +always been motivated by the needs of its users and functions were +added or expanded as they were needed and used. Contributions to +the interface are always welcome. However with a refactoring of +the library interface and its documentation that started in 2020, +there are now a few requirements for inclusion of changes. + + - New functions should be orthogonal to existing ones and not + implement functionality that can already be achieved with the + existing APIs. + - All changes and additions should be documented with + `Doxygen `_ style comments and references + to those functions added to the corresponding files in the + ``doc/src`` folder. + - If possible, new unit tests to test those new features should + be added. + - The new feature should also be implemented and documented for + the Python and Fortran modules. + - All additions should work and be compatible with ``-DLAMMPS_BIGBIG``, + ``-DLAMMPS_SMALLBIG``, ``-DLAMMPS_SMALLSMALL`` and compiling + with and without MPI support. + - The ``library.h`` file should be kept compatible to C code at + a level similar to C89. Its interfaces may not reference any + custom data types (e.g. ``bigint``, ``tagint``, and so on) only + known inside of LAMMPS. + - only C style comments, not C++ style + +Please note, that these are *not* *strict* requirements, but the +LAMMPS developers appreciate if they are followed closely and will +assist with implementing what is missing. diff --git a/doc/src/pg_lib_config.rst b/doc/src/pg_lib_config.rst new file mode 100644 index 0000000000..9576e31dcd --- /dev/null +++ b/doc/src/pg_lib_config.rst @@ -0,0 +1,67 @@ +Retrieving LAMMPS configuration information +=========================================== + +The following library functions can be used to query the +LAMMPS library about compile time settings and included +packages and styles. + +----------------------- + +.. doxygenfunction:: lammps_config_has_mpi_support + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_gzip_support + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_png_support + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_jpeg_support + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_ffmpeg_support + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_exceptions + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_has_package + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_package_count + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_config_package_name + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_has_style + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_style_count + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_style_name + :project: progguide + diff --git a/doc/src/pg_lib_create.rst b/doc/src/pg_lib_create.rst new file mode 100644 index 0000000000..f8a8fb7829 --- /dev/null +++ b/doc/src/pg_lib_create.rst @@ -0,0 +1,104 @@ +Creating or deleting a LAMMPS object +==================================== + +The :cpp:func:`lammps_open` and :cpp:func:`lammps_open_no_mpi` +functions are used to create and initialize a +:cpp:func:`LAMMPS` instance. The calling program has to +provide a handle where a reference to this instance can be stored and +which has to be used in all subsequent function calls until that +instance is destroyed by calling :cpp:func:`lammps_close`. +Here is a simple example demonstrating its use: + +.. code-block:: C + + #include "library.h" + #include + + int main(int argc, char **argv) + { + void *handle; + int version; + const char *lmpargv[] = { "liblammps", "-log", "none"}; + int lmpargc = sizeof(lmpargv)/sizeof(const char *); + + /* create LAMMPS instance */ + handle = lammps_open_no_mpi(lmpargc, lmpargv, NULL); + if (handle == NULL) { + printf("LAMMPS initialization failed"); + lammps_mpi_finalize(); + return 1; + } + + /* get and print numerical version code */ + version = lammps_version(handle); + printf("LAMMPS Version: %d\n",version); + + /* delete LAMMPS instance and shut down MPI */ + lammps_close(handle); + lammps_mpi_finalize(); + return 0; + } + +The LAMMPS library will be using the MPI library it was compiled with +and will either run on all processors in the ``MPI_COMM_WORLD`` +communicator or on the set of processors in the communicator given in +the ``comm`` argument of :cpp:func:`lammps_open`. This means +the calling code can run LAMMPS on all or a subset of processors. For +example, a wrapper code might decide to alternate between LAMMPS and +another code, allowing them both to run on all the processors. Or it +might allocate part of the processors to LAMMPS and the rest to the +other code by creating a custom communicator with ``MPI_Comm_split()`` +and running both codes concurrently before syncing them up periodically. +Or it might instantiate multiple instances of LAMMPS to perform +different calculations and either alternate between them, run them +concurrently on split communicators, or run them one after the other. +The :cpp:func:`lammps_open` function may be called multiple +times for this latter purpose. + +The :cpp:func:`lammps_close` function is used to shut down +the :cpp:class:`LAMMPS ` class pointed to by the handle +passed as an argument and free all its memory. This has to be called for +every instance created with any of the :cpp:func:`lammps_open` functions. It will, however, **not** call +``MPI_Finalize()``, since that may only be called once. See +:cpp:func:`lammps_mpi_finalize` for an alternative to calling +``MPI_Finalize()`` explicitly in the calling program. + +The :cpp:func:`lammps_free` function is a clean-up +function to free memory that the library allocated previously +via other function calls. See below for notes in the descriptions +of the individual commands where such memory buffers were allocated. + +----------------------- + +.. doxygenfunction:: lammps_open + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_open_no_mpi + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_open_fortran + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_close + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_mpi_init + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_mpi_finalize + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_free + :project: progguide diff --git a/doc/src/pg_lib_execute.rst b/doc/src/pg_lib_execute.rst new file mode 100644 index 0000000000..3e79cb3cb4 --- /dev/null +++ b/doc/src/pg_lib_execute.rst @@ -0,0 +1,69 @@ +Executing LAMMPS commands +========================= + +Once a LAMMPS instance is created, there are multiple ways to "drive" a +simulation. In most cases it is easiest to process single or multiple +LAMMPS commands like in an input file. This can be done through reading +a file or passing single commands or lists of commands or blocks of +commands with the following functions. + +Via these functions, the calling code can have the LAMMPS instance act +on a series of :doc:`input file commands ` that are either +read from a file or passed as strings. This for, for example, allows to +setup a problem from a template file and then run it in stages while +performing other operations in between or concurrently. The caller can +interleave the LAMMPS function calls with operations it performs, calls +to extract information from or set information within LAMMPS, or calls +to another code's library. + +Also equivalent to regular :doc:`input script parsing ` +is the handling of comments and expansion of variables with ``${name}`` +or ``$(expression)`` syntax before the commands are parsed and +executed. Below is a short example using some of these functions. + +.. code-block:: C + + #include "library.h" + #include + #include + + int main(int argc, char **argv) + { + void *handle; + int i; + + MPI_Init(&argc, &argv); + handle = lammps_open(0, NULL, MPI_COMM_WORLD, NULL); + lammps_file(handle,"in.sysinit"); + lammps_command(handle,"run 1000 post no"); + + for (i=0; i < 100; ++i) { + lammps_commands_string(handle,"run 100 pre no post no\n" + "print 'PE = $(pe)'\n" + "print 'KE = $(ke)'\n"); + } + lammps_close(handle); + MPI_Finalize(); + return 0; + } + +----------------------- + +.. doxygenfunction:: lammps_file + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_command + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_commands_list + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_commands_string + :project: progguide + diff --git a/doc/src/pg_lib_neighbor.rst b/doc/src/pg_lib_neighbor.rst new file mode 100644 index 0000000000..b004e85d0e --- /dev/null +++ b/doc/src/pg_lib_neighbor.rst @@ -0,0 +1,30 @@ +Accessing LAMMPS Neighbor lists +=============================== + +The following functions allow to access neighbor lists +generated by LAMMPS or query their properties. + +----------------------- + +.. doxygenfunction:: lammps_find_compute_neighlist + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_find_fix_neighlist + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_find_pair_neighlist + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_neighlist_num_elements + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_neighlist_element_neighbors + :project: progguide diff --git a/doc/src/pg_lib_objects.rst b/doc/src/pg_lib_objects.rst new file mode 100644 index 0000000000..5da858db0c --- /dev/null +++ b/doc/src/pg_lib_objects.rst @@ -0,0 +1,31 @@ +Retrieving or setting properties of LAMMPS objects +================================================== + +This section documents accessing or modifying data from objects like +computes, fixes, or variables in LAMMPS. + +----------------------- + +.. doxygenfunction:: lammps_extract_compute + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_extract_fix + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_extract_variable + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_set_variable + :project: progguide + +----------------------- + +.. doxygenenum:: _LMP_STYLE_CONST + +.. doxygenenum:: _LMP_TYPE_CONST diff --git a/doc/src/pg_lib_properties.rst b/doc/src/pg_lib_properties.rst new file mode 100644 index 0000000000..789c7e2441 --- /dev/null +++ b/doc/src/pg_lib_properties.rst @@ -0,0 +1,62 @@ +Retrieving or setting LAMMPS system properties +============================================== + +The library interface allows to extract different kinds of information +about the active simulation instance and also to modify some of them. +This allows to combine MD simulation steps with other processing and +simulation methods computed in the calling code or another code that is +coupled to LAMMPS via the library interface. In some cases the data +returned is direct reference to the original data inside LAMMPS cast +to a void pointer. In that case the data needs to be cast to a suitable +pointer to be able to access it, and you need to know the correct dimensions +and lengths. When accessing per-atom data, please note that this data +is the per-processor **local** data and indexed accordingly. These arrays +can change sizes and order at every neighbor list rebuild and atom sort +event as atoms are migrating between sub-domains. + +----------------------- + +.. doxygenfunction:: lammps_version + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_get_natoms + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_get_thermo + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_extract_box + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_reset_box + :project: progguide + +------------------- + +.. doxygenfunction:: lammps_extract_setting + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_extract_global + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_extract_atom + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_create_atoms(void *handle, int n, int *id, int *type, double *x, double *v, int *image, int bexpand) + :project: progguide + + diff --git a/doc/src/pg_lib_scatter.rst b/doc/src/pg_lib_scatter.rst new file mode 100644 index 0000000000..5865d22349 --- /dev/null +++ b/doc/src/pg_lib_scatter.rst @@ -0,0 +1,29 @@ +Library functions for scatter/gather operations +================================================ + +.. TODO add description + +----------------------- + +.. doxygenfunction:: lammps_gather_atoms + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_gather_atoms_concat + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_gather_atoms_subset + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_scatter_atoms + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_scatter_atoms_subset + :project: progguide diff --git a/doc/src/pg_lib_utility.rst b/doc/src/pg_lib_utility.rst new file mode 100644 index 0000000000..8eda4e4988 --- /dev/null +++ b/doc/src/pg_lib_utility.rst @@ -0,0 +1,30 @@ +Library interface utility functions +=================================== + +To simplify some of the tasks, the library interface contains +some utility functions that are not directly calling LAMMPS. + +----------------------- + +.. doxygenfunction:: lammps_encode_image_flags + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_decode_image_flags(int image, int *flags) + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_set_fix_external_callback(void *, char *, FixExternalFnPtr, void*) + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_has_error + :project: progguide + +----------------------- + +.. doxygenfunction:: lammps_get_last_error_message + :project: progguide diff --git a/doc/src/pg_library.rst b/doc/src/pg_library.rst index 3cd8d2196d..871ac5aaa5 100644 --- a/doc/src/pg_library.rst +++ b/doc/src/pg_library.rst @@ -91,15 +91,15 @@ and consequently the function :cpp:func:`lammps_open` may not be used. .. toctree:: :maxdepth: 1 -.. pg_lib_create -.. pg_lib_execute -.. pg_lib_properties -.. pg_lib_objects -.. pg_lib_scatter -.. pg_lib_neighbor -.. pg_lib_config -.. pg_lib_utility -.. pg_lib_add + pg_lib_create + pg_lib_execute + pg_lib_properties + pg_lib_objects + pg_lib_scatter + pg_lib_neighbor + pg_lib_config + pg_lib_utility + pg_lib_add -------------------- @@ -153,6 +153,6 @@ The following links provide some examples and references to the C++ API. .. toctree:: :maxdepth: 1 -.. pg_cplusplus + pg_cplusplus diff --git a/doc/src/pg_python.rst b/doc/src/pg_python.rst index 66f5c08cd0..72d07c5983 100644 --- a/doc/src/pg_python.rst +++ b/doc/src/pg_python.rst @@ -26,4 +26,163 @@ There are multiple Python interface classes in the :py:mod:`lammps` module: .. _mpi4py_url: https://mpi4py.readthedocs.io +---------- + +Creating or deleting a LAMMPS object +************************************ + +With the Python interface the creation of a :cpp:class:`LAMMPS +` instance is included in the constructor for the +:py:func:`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 is to allow loading a +LAMMPS shared library that is named ``liblammps_machine.so`` instead of +the default name of ``liblammps.so``. In most cases the latter will be +installed or used. The *ptr* argument is for use of the +:py:mod:`lammps` module from inside a LAMMPS instance, e.g. with the +:doc:`python ` command, where a pointer to the already existing +:cpp:class:`LAMMPS ` class instance can be passed +to the Python class and used instead of creating a new instance. The +*comm* argument may be used in combination with the `mpi4py `_ +module to pass an MPI communicator to LAMMPS and thus it is possible +to run the Python module like the library interface on a subset of the +MPI ranks after splitting the communicator. Here is a simple example: + +.. code-block:: python + + from lammps import lammps + + # NOTE: argv[0] is set by the Python module + args = ["-log", "none"] + # create LAMMPS instance + lmp = lammps(cmdargs=args) + # get and print numerical version code + print("LAMMPS Version: ", lmp.version()) + # explicitly close and delete LAMMPS instance (optional) + lmp.close() + +Same as with the :doc:`C library API ` this will use the +``MPI_COMM_WORLD`` communicator for the MPI library that LAMMPS was +compiled with. The :py:func:`lmp.close() ` call is +optional since the LAMMPS class instance will also be deleted +automatically during the :py:class:`lammps ` class +destructor. + +Executing LAMMPS commands +************************* + +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. Same as in the equivalent `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`: + +.. code-block:: python + + from lammps import lammps + + lmp = lammps() + # read commands from file 'in.melt' + lmp.file('in.melt') + # issue a single 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)] + lmp.commands_list(cmds) + # 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) + +---------- + +The ``lammps`` class API +************************ + +The :py:class:`lammps ` class is the core of the LAMMPS +Python interfaces. It is a wrapper around the :doc:`LAMMPS C library +API ` using the `Python ctypes module +`_ and a shared library +compiled from the LAMMPS sources code. The individual methods in this +class try to closely follow the corresponding C functions. The handle +argument that needs to be passed to the C functions is stored internally +in the class and automatically added when calling the C library +functions. Below is a detailed documentation of the API. + +.. autoclass:: lammps.lammps + :members: + +---------- + +The ``PyLammps`` class API +************************** + +.. autoclass:: lammps.PyLammps + :members: + +---------- + +The ``IPyLammps`` class API +*************************** + +.. autoclass:: lammps.IPyLammps + :members: + +---------- + +Additional components of the ``lammps`` module +********************************************** + +The :py:mod:`lammps` module additionally contains several constants +and the :py:class:`NeighList ` class: + +.. _py_data_constants: +.. py:data:: LAMMPS_INT, LAMMPS_DOUBLE, LAMMPS_BIGINT, LAMMPS_TAGINT, LAMMPS_STRING + :type: int + + Constants in the :py:mod:`lammps` module to indicate how to + cast data when the C library function returns a void pointer. + Used in :py:func:`lammps.extract_global`. + +.. _py_style_constants: +.. py:data:: LMP_STYLE_GLOBAL, LMP_STYLE_ATOM, LMP_STYLE_LOCAL + :type: int + + Constants in the :py:mod:`lammps` module to select what style of data + to request from computes or fixes. See :cpp:enum:`_LMP_STYLE_CONST` + for the equivalent constants in the C library interface. Used in + :py:func:`lammps.extract_compute` and :py:func:`lammps.extract_fix`. + +.. _py_type_constants: +.. py:data:: LMP_TYPE_SCALAR, LMP_TYLE_VECTOR, LMP_TYPE_ARRAY, LMP_SIZE_VECTOR, LMP_SIZE_ROWS, LMP_SIZE_COLS + :type: int + + Constants in the :py:mod:`lammps` module to select what type of data + to request from computes or fixes. See :cpp:enum:`_LMP_TYPE_CONST` + for the equivalent constants in the C library interface. Used in + :py:func:`lammps.extract_compute` and :py:func:`lammps.extract_fix`. + +.. _py_var_constants: +.. py:data:: LMP_VAR_EQUAL, LMP_VAR_ATOM + :type: int + + Constants in the :py:mod:`lammps` module to select what style of + variable to query when calling :py:func:`lammps.extract_variable`. + +.. autoclass:: lammps.NeighList + :members: + :no-undoc-members: From 7b6924329f43523ce1357eaf49d3827cf6e5e13c Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:25:38 -0400 Subject: [PATCH 02/22] make doxygen docs and code consistent --- src/library.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/library.cpp b/src/library.cpp index c86de6e538..6ba0799085 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -752,29 +752,29 @@ recognized, the function returns -1. Please also see :cpp:func:`lammps_extract_ * \param keyword string with the name of the thermo keyword * \return value of the queried setting or -1 if unknown */ -int lammps_extract_setting(void * handle, char *name) +int lammps_extract_setting(void * handle, char *keyword) { LAMMPS *lmp = (LAMMPS *) handle; // This can be customized by adding keywords and documenting them in the section above. - if (strcmp(name,"bigint") == 0) return sizeof(bigint); - if (strcmp(name,"tagint") == 0) return sizeof(tagint); - if (strcmp(name,"imageint") == 0) return sizeof(imageint); + if (strcmp(keyword,"bigint") == 0) return sizeof(bigint); + if (strcmp(keyword,"tagint") == 0) return sizeof(tagint); + if (strcmp(keyword,"imageint") == 0) return sizeof(imageint); - if (strcmp(name,"dimension") == 0) return lmp->domain->dimension; - if (strcmp(name,"box_exist") == 0) return lmp->domain->box_exist; - if (strcmp(name,"triclinic") == 0) return lmp->domain->triclinic; + if (strcmp(keyword,"dimension") == 0) return lmp->domain->dimension; + if (strcmp(keyword,"box_exist") == 0) return lmp->domain->box_exist; + if (strcmp(keyword,"triclinic") == 0) return lmp->domain->triclinic; - if (strcmp(name,"nlocal") == 0) return lmp->atom->nlocal; - if (strcmp(name,"nghost") == 0) return lmp->atom->nghost; - if (strcmp(name,"nall") == 0) return lmp->atom->nlocal+lmp->atom->nghost; - if (strcmp(name,"nmax") == 0) return lmp->atom->nmax; - if (strcmp(name,"ntypes") == 0) return lmp->atom->ntypes; + if (strcmp(keyword,"nlocal") == 0) return lmp->atom->nlocal; + if (strcmp(keyword,"nghost") == 0) return lmp->atom->nghost; + if (strcmp(keyword,"nall") == 0) return lmp->atom->nlocal+lmp->atom->nghost; + if (strcmp(keyword,"nmax") == 0) return lmp->atom->nmax; + if (strcmp(keyword,"ntypes") == 0) return lmp->atom->ntypes; - if (strcmp(name,"molecule_flag") == 0) return lmp->atom->molecule_flag; - if (strcmp(name,"q_flag") == 0) return lmp->atom->q_flag; - if (strcmp(name,"mu_flag") == 0) return lmp->atom->mu_flag; - if (strcmp(name,"rmass_flag") == 0) return lmp->atom->rmass_flag; + if (strcmp(keyword,"molecule_flag") == 0) return lmp->atom->molecule_flag; + if (strcmp(keyword,"q_flag") == 0) return lmp->atom->q_flag; + if (strcmp(keyword,"mu_flag") == 0) return lmp->atom->mu_flag; + if (strcmp(keyword,"rmass_flag") == 0) return lmp->atom->rmass_flag; return -1; } From 2e1b4498bd900fc73d7876ecc1c65e7d4a510e45 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:30:00 -0400 Subject: [PATCH 03/22] update false positives --- doc/utils/sphinx-config/false_positives.txt | 47 ++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index cd68c3ddda..a300874e41 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -115,6 +115,7 @@ Archlinux arcsin arg args +argv arrhenius Arun arXiv @@ -138,6 +139,8 @@ atc AtC ATC athermal +atime +atimestep athomps atm atomeye @@ -207,7 +210,6 @@ bcolor bdiam bdw Beckman -behaviour Belak Bellott benchmarking @@ -267,6 +269,7 @@ bodystyle Bogaerts Bogusz Bohrs +boltz Boltzman BondAngle BondBond @@ -285,6 +288,14 @@ Botu Bouguet Bourne boxcolor +boxlo +boxhi +boxxlo +boxxhi +boxylo +boxyhi +boxzlo +boxzhi bp bpclermont bpls @@ -411,6 +422,7 @@ cmap Cmax cmd cmdlist +cmds Cmin cmm CMM @@ -675,6 +687,8 @@ Donadio dotc Doty doxygen +doxygenclass +doxygenfunction downarrow Doye dpd @@ -982,6 +996,7 @@ Fock Fogarty Foiles fopenmp +forceclear forestgreen formatarg formulae @@ -1006,6 +1021,7 @@ fs fsh fstyle fsw +ftm ftol fugacity Fumi @@ -1176,6 +1192,7 @@ hexorder Heyes HfO hgrid +hhmrr Hibbs Higdon Hijazi @@ -1185,6 +1202,7 @@ histogrammed histogramming hma hmaktulga +hplanck hoc Hochbruck Hofling @@ -1227,6 +1245,7 @@ hyperspherical hysteretic hz Ibanez +iatom ibar ibm icc @@ -1269,6 +1288,7 @@ indices inertiax inertiay inertiaz +infile infty inhomogeneities inhomogeneous @@ -1309,6 +1329,7 @@ ipp Ippolito IPv IPython +ipython Isele isenthalpic ish @@ -1457,6 +1478,7 @@ Kloza kmax Kmax KMP +kmu Knizhnik knl Kofke @@ -1943,6 +1965,7 @@ muz mv mV Mvapich +mvh mvv MxN myCompute @@ -1955,11 +1978,13 @@ na nabla Nagaosa Nakano +nall namespace namespaces nan NaN Nandor +nangles Nangletype nangletypes Nangletypes @@ -1988,6 +2013,7 @@ Nbin Nbins nbody Nbody +nbonds nbondtype Nbondtype nbondtypes @@ -2000,9 +2026,11 @@ Nc nchunk Nchunk ncoeff +ncol ncorr ncount nd +ndihedrals Ndihedraltype Ndirango ndof @@ -2048,6 +2076,7 @@ NiAlH Nicklas Niklasson Nikolskiy +nimpropers Nimpropertype Ninteger Nissila @@ -2056,6 +2085,7 @@ nitride nitrides niu Nk +nktv nl nlen Nlines @@ -2066,6 +2096,7 @@ nlp nm Nm Nmax +nmax Nmin Nmols nn @@ -2119,6 +2150,7 @@ Nrepeat nreset Nrho Nroff +nrow nrun Ns Nsample @@ -2137,6 +2169,7 @@ Nt Ntable ntheta nthreads +ntimestep Ntptask Ntriples Ntype @@ -2366,6 +2399,7 @@ polydisperse polydispersity polyelectrolyte polyhedra +polymorphism popen Popov popstore @@ -2399,6 +2433,7 @@ proc Proc procs Prony +progguide ps Ps pscreen @@ -2445,7 +2480,9 @@ qbmsst qcore qdist qE +qe qeff +qelectron qeq QeQ QEq @@ -2463,6 +2500,8 @@ qmol qoffload qopenmp qoverride +qqr +qqrd qtb quadratically quadrupolar @@ -2518,6 +2557,7 @@ rebo recursing Ree refactored +refactoring reflectionstyle regoin Reinders @@ -2603,6 +2643,7 @@ Rkouter RkouterN rmask Rmask +rmass rmax Rmax rmdir @@ -2737,6 +2778,7 @@ shlib SHM shm shockvel +shrinkexceed Shugaev si SiC @@ -3244,6 +3286,7 @@ vv vx Vx vxcm +vxmu vy Vy vycm @@ -3319,6 +3362,7 @@ Xmax xmgrace xMIC xmin +xml xmovie Xmovie xmu @@ -3333,6 +3377,7 @@ xsu xtc xu Xu +xxt xxxxx xy xyz From 3e92647abbe85cf47e2b24916c33c1aa68158e33 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:56:15 -0400 Subject: [PATCH 04/22] add new "official" fortran interface to LAMMPS in new folder "fortran" --- fortran/README | 11 ++ fortran/lammps.f90 | 281 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 fortran/README create mode 100644 fortran/lammps.f90 diff --git a/fortran/README b/fortran/README new file mode 100644 index 0000000000..653b6a966e --- /dev/null +++ b/fortran/README @@ -0,0 +1,11 @@ +This directory contains Fortran code which interface LAMMPS as a library +and allows the LAMMPS library interface to be invoked from Fortran codes. +It requires a Fortran compiler that supports the Fortran 2003 standard. + +This interface is based on and supersedes the previous Fortran interfaces +in the examples/COUPLE/fortran* folders. But is fully supported by the +LAMMPS developers and included in the documentation and unit testing. + +Details on this Fortran interface and how to build programs using it +are in the manual in the doc/html/pg_fortran.html file. + diff --git a/fortran/lammps.f90 b/fortran/lammps.f90 new file mode 100644 index 0000000000..ba2e376b77 --- /dev/null +++ b/fortran/lammps.f90 @@ -0,0 +1,281 @@ +! ------------------------------------------------------------------------- +! LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator +! http://lammps.sandia.gov, Sandia National Laboratories +! Steve Plimpton, sjplimp@sandia.gov +! +! 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. +! ------------------------------------------------------------------------- +! +! Fortran interface to the LAMMPS library implemented as a Fortran 2003 +! style module that wraps the C-style library interface in library.cpp +! and library.h using the ISO_C_BINDING module of the Fortran compiler. +! +! Based on the LAMMPS Fortran 2003 module contributed by: +! Karl D. Hammond +! University of Tennessee, Knoxville (USA), 2012 +! +! The Fortran module tries to follow the API of the C-library interface +! closely, but like the Python wrapper it employs an object oriented +! approach. To accommodate the object oriented approach, all exported +! subroutine and functions have to be implemented in Fortran to then +! call the interfaced C style functions with adapted calling conventions +! as needed. The C-library interfaced functions retain their names +! starting with "lammps_" while the Fortran versions start with "lmp_". +! +MODULE LIBLAMMPS + + USE, INTRINSIC :: ISO_C_BINDING, ONLY: c_ptr, c_null_ptr, c_loc, & + c_int, c_char, c_null_char, c_double + + IMPLICIT NONE + PRIVATE + PUBLIC :: lammps + + TYPE lammps + TYPE(c_ptr) :: handle + CONTAINS + PROCEDURE :: close => lmp_close + PROCEDURE :: file => lmp_file + PROCEDURE :: command => lmp_command + PROCEDURE :: commands_list => lmp_commands_list + PROCEDURE :: commands_string => lmp_commands_string + PROCEDURE :: version => lmp_version + PROCEDURE :: get_natoms => lmp_get_natoms + END TYPE lammps + + INTERFACE lammps + MODULE PROCEDURE lmp_open + END INTERFACE lammps + + ! interface definitions for calling functions in library.cpp + INTERFACE + FUNCTION lammps_open(argc,argv,comm,handle) & + BIND(C, name='lammps_open_fortran') + IMPORT :: c_ptr, c_int + INTEGER(c_int), VALUE, INTENT(in) :: argc, comm + TYPE(c_ptr), DIMENSION(*), INTENT(in) :: argv + TYPE(c_ptr), INTENT(out) :: handle + TYPE(c_ptr) :: lammps_open + END FUNCTION lammps_open + + FUNCTION lammps_open_no_mpi(argc,argv,handle) & + BIND(C, name='lammps_open_no_mpi') + IMPORT :: c_ptr, c_int + INTEGER(c_int), VALUE, INTENT(in) :: argc + TYPE(c_ptr), DIMENSION(*), INTENT(in) :: argv + TYPE(c_ptr), INTENT(out) :: handle + TYPE(c_ptr) :: lammps_open_no_mpi + END FUNCTION lammps_open_no_mpi + + SUBROUTINE lammps_close(handle) BIND(C, name='lammps_close') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + END SUBROUTINE lammps_close + + SUBROUTINE lammps_mpi_init(handle) BIND(C, name='lammps_mpi_init') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + END SUBROUTINE lammps_mpi_init + + SUBROUTINE lammps_mpi_finalize(handle) & + BIND(C, name='lammps_mpi_finalize') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + END SUBROUTINE lammps_mpi_finalize + + SUBROUTINE lammps_file(handle,filename) BIND(C, name='lammps_file') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + TYPE(c_ptr), VALUE :: filename + END SUBROUTINE lammps_file + + SUBROUTINE lammps_command(handle,cmd) BIND(C, name='lammps_command') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + TYPE(c_ptr), VALUE :: cmd + END SUBROUTINE lammps_command + + SUBROUTINE lammps_commands_list(handle,ncmd,cmds) & + BIND(C, name='lammps_commands_list') + IMPORT :: c_ptr, c_int + TYPE(c_ptr), VALUE :: handle + INTEGER(c_int), VALUE, INTENT(in) :: ncmd + TYPE(c_ptr), DIMENSION(*), INTENT(in) :: cmds + END SUBROUTINE lammps_commands_list + + SUBROUTINE lammps_commands_string(handle,str) & + BIND(C, name='lammps_commands_string') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: handle + TYPE(c_ptr), VALUE :: str + END SUBROUTINE lammps_commands_string + + SUBROUTINE lammps_free(ptr) BIND(C, name='lammps_free') + IMPORT :: c_ptr + TYPE(c_ptr), VALUE :: ptr + END SUBROUTINE lammps_free + + FUNCTION lammps_version(handle) BIND(C, name='lammps_version') + IMPORT :: c_ptr, c_int + TYPE(c_ptr), VALUE :: handle + INTEGER(c_int) :: lammps_version + END FUNCTION lammps_version + + FUNCTION lammps_get_natoms(handle) BIND(C, name='lammps_get_natoms') + IMPORT :: c_ptr, c_double + TYPE(c_ptr), VALUE :: handle + REAL(c_double) :: lammps_get_natoms + END FUNCTION lammps_get_natoms + END INTERFACE + +CONTAINS + + ! Fortran wrappers and helper functions. + + ! Constructor for the LAMMPS class. + ! Combined wrapper around lammps_open_fortran() and lammps_open_no_mpi() + TYPE(lammps) FUNCTION lmp_open(args,comm) + IMPLICIT NONE + INTEGER,INTENT(in), OPTIONAL :: comm + CHARACTER(len=*), INTENT(in), OPTIONAL :: args(:) + TYPE(c_ptr), ALLOCATABLE :: argv(:) + TYPE(c_ptr) :: dummy=c_null_ptr + INTEGER :: i,argc + + IF (PRESENT(args)) THEN + ! convert argument list to c style + argc = SIZE(args) + ALLOCATE(argv(argc)) + DO i=1,argc + argv(i) = f2c_string(args(i)) + END DO + ELSE + argc = 1 + ALLOCATE(argv(1)) + argv(1) = f2c_string("liblammps") + ENDIF + + IF (PRESENT(comm)) THEN + lmp_open%handle = lammps_open(argc,argv,comm,dummy) + ELSE + lmp_open%handle = lammps_open_no_mpi(argc,argv,dummy) + END IF + + ! Clean up allocated memory + DO i=1,argc + CALL lammps_free(argv(i)) + END DO + DEALLOCATE(argv) + END FUNCTION lmp_open + + ! Combined Fortran wrapper around lammps_close() and lammps_mpi_finalize() + SUBROUTINE lmp_close(self,finalize) + IMPLICIT NONE + CLASS(lammps) :: self + LOGICAL,INTENT(in),OPTIONAL :: finalize + + CALL lammps_close(self%handle) + + IF (PRESENT(finalize)) THEN + IF (finalize) THEN + CALL lammps_mpi_finalize(self%handle) + END IF + END IF + END SUBROUTINE lmp_close + + INTEGER FUNCTION lmp_version(self) + IMPLICIT NONE + CLASS(lammps) :: self + + lmp_version = lammps_version(self%handle) + END FUNCTION lmp_version + + DOUBLE PRECISION FUNCTION lmp_get_natoms(self) + IMPLICIT NONE + CLASS(lammps) :: self + + lmp_get_natoms = lammps_get_natoms(self%handle) + END FUNCTION lmp_get_natoms + + SUBROUTINE lmp_file(self,filename) + IMPLICIT NONE + CLASS(lammps) :: self + CHARACTER(len=*) :: filename + TYPE(c_ptr) :: str + + str = f2c_string(filename) + CALL lammps_file(self%handle,str) + CALL lammps_free(str) + END SUBROUTINE lmp_file + + ! equivalent function to lammps_command() + SUBROUTINE lmp_command(self,cmd) + IMPLICIT NONE + CLASS(lammps) :: self + CHARACTER(len=*) :: cmd + TYPE(c_ptr) :: str + + str = f2c_string(cmd) + CALL lammps_command(self%handle,str) + CALL lammps_free(str) + END SUBROUTINE lmp_command + + ! equivalent function to lammps_commands_list() + SUBROUTINE lmp_commands_list(self,cmds) + IMPLICIT NONE + CLASS(lammps) :: self + CHARACTER(len=*), INTENT(in), OPTIONAL :: cmds(:) + TYPE(c_ptr), ALLOCATABLE :: cmdv(:) + INTEGER :: i,ncmd + + ! convert command list to c style + ncmd = SIZE(cmds) + ALLOCATE(cmdv(ncmd)) + DO i=1,ncmd + cmdv(i) = f2c_string(cmds(i)) + END DO + + CALL lammps_commands_list(self%handle,ncmd,cmdv) + + ! Clean up allocated memory + DO i=1,ncmd + CALL lammps_free(cmdv(i)) + END DO + DEALLOCATE(cmdv) + END SUBROUTINE lmp_commands_list + + ! equivalent function to lammps_commands_string() + SUBROUTINE lmp_commands_string(self,str) + IMPLICIT NONE + CLASS(lammps) :: self + CHARACTER(len=*) :: str + TYPE(c_ptr) :: tmp + + tmp = f2c_string(str) + CALL lammps_commands_string(self%handle,tmp) + CALL lammps_free(tmp) + END SUBROUTINE lmp_commands_string + + ! ---------------------------------------------------------------------- + ! local helper functions + ! copy fortran string to zero terminated c string + FUNCTION f2c_string(f_string) RESULT(ptr) + CHARACTER (len=*), INTENT(in) :: f_string + CHARACTER (len=1, kind=c_char), POINTER :: c_string(:) + TYPE(c_ptr) :: ptr + INTEGER :: i, n + + n = LEN_TRIM(f_string) + ALLOCATE(c_string(n+1)) + DO i=1,n + c_string(i) = f_string(i:i) + END DO + c_string(n+1) = c_null_char + ptr = c_loc(c_string(1)) + END FUNCTION f2c_string +END MODULE LIBLAMMPS From e44707d5e17faf8a37eb6bab6ec8f03528781eba Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:56:52 -0400 Subject: [PATCH 05/22] add unittest support for the fortran interface to LAMMPS --- unittest/CMakeLists.txt | 1 + unittest/fortran/CMakeLists.txt | 30 +++++++ unittest/fortran/fortran-commands.f90 | 111 ++++++++++++++++++++++++++ unittest/fortran/fortran-create.f90 | 86 ++++++++++++++++++++ unittest/fortran/mpi_stubs.f90 | 20 +++++ unittest/fortran/wrap-commands.cpp | 65 +++++++++++++++ unittest/fortran/wrap-create.cpp | 97 ++++++++++++++++++++++ 7 files changed, 410 insertions(+) create mode 100644 unittest/fortran/CMakeLists.txt create mode 100644 unittest/fortran/fortran-commands.f90 create mode 100644 unittest/fortran/fortran-create.f90 create mode 100644 unittest/fortran/mpi_stubs.f90 create mode 100644 unittest/fortran/wrap-commands.cpp create mode 100644 unittest/fortran/wrap-create.cpp diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index bfa4c7d002..dd375112b7 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(formats) add_subdirectory(commands) add_subdirectory(c-library) add_subdirectory(cplusplus) +add_subdirectory(fortran) add_subdirectory(python) add_subdirectory(force-styles) diff --git a/unittest/fortran/CMakeLists.txt b/unittest/fortran/CMakeLists.txt new file mode 100644 index 0000000000..75382479d4 --- /dev/null +++ b/unittest/fortran/CMakeLists.txt @@ -0,0 +1,30 @@ +include(CheckGeneratorSupport) +if(NOT CMAKE_GENERATOR_SUPPORT_FORTRAN) + message(STATUS "Skipping Tests for the LAMMPS Fortran Module: no Fortran support in build tool") + return() +endif() + +include(CheckLanguage) +check_language(Fortran) +if(CMAKE_Fortran_COMPILER) + enable_language(Fortran) + get_filename_component(LAMMPS_FORTRAN_MODULE ${LAMMPS_SOURCE_DIR}/../fortran/lammps.f90 ABSOLUTE) + if(BUILD_MPI) + find_package(MPI REQUIRED) + else() + add_library(fmpi_stubs STATIC mpi_stubs.f90) + add_library(MPI::MPI_Fortran ALIAS fmpi_stubs) + endif() + + add_library(flammps STATIC ${LAMMPS_FORTRAN_MODULE}) + + add_executable(fortran-create wrap-create.cpp fortran-create.f90) + target_link_libraries(fortran-create PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTest GTest::GTestMain) + add_test(FortranOpen fortran-create) + + add_executable(fortran-commands wrap-commands.cpp fortran-commands.f90) + target_link_libraries(fortran-commands PRIVATE flammps lammps MPI::MPI_Fortran GTest::GTest GTest::GTestMain) + add_test(FortranCommands fortran-commands) +else() + message(STATUS "Skipping Tests for the LAMMPS Fortran Module: no Fortran compiler") +endif() diff --git a/unittest/fortran/fortran-commands.f90 b/unittest/fortran/fortran-commands.f90 new file mode 100644 index 0000000000..748bc5774a --- /dev/null +++ b/unittest/fortran/fortran-commands.f90 @@ -0,0 +1,111 @@ +MODULE keepcmds + USE liblammps + TYPE(LAMMPS) :: lmp + CHARACTER(len=*), DIMENSION(*), PARAMETER :: demo_input = & + [ CHARACTER(len=40) :: & + 'region box block 0 $x 0 2 0 2', & + 'create_box 1 box', & + 'create_atoms 1 single 1.0 1.0 ${zpos}' ] + CHARACTER(len=*), DIMENSION(*), PARAMETER :: cont_input = & + [ CHARACTER(len=40) :: & + 'create_atoms 1 single &', & + ' 0.2 0.1 0.1' ] +END MODULE keepcmds + +FUNCTION f_lammps_with_args() BIND(C, name="f_lammps_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_with_args + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', & + '-echo','screen','-nocite','-var','zpos','1.5','-var','x','2'] + + lmp = lammps(args) + f_lammps_with_args = lmp%handle +END FUNCTION f_lammps_with_args + +SUBROUTINE f_lammps_close() BIND(C, name="f_lammps_close") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + + CALL lmp%close() + lmp%handle = c_null_ptr +END SUBROUTINE f_lammps_close + +FUNCTION f_lammps_get_natoms() BIND(C, name="f_lammps_get_natoms") + USE ISO_C_BINDING, ONLY: c_null_ptr, c_double + USE liblammps + USE keepcmds, ONLY: lmp + IMPLICIT NONE + REAL(c_double) :: f_lammps_get_natoms + + f_lammps_get_natoms = lmp%get_natoms() +END FUNCTION f_lammps_get_natoms + +SUBROUTINE f_lammps_file() BIND(C, name="f_lammps_file") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + INTEGER :: i + CHARACTER(len=*), PARAMETER :: demo_file = 'in.test', cont_file = 'in.cont' + + OPEN(10, file=demo_file, status='replace') + WRITE(10, fmt='(A)') (demo_input(i),i=1,SIZE(demo_input)) + CLOSE(10) + OPEN(11, file=cont_file, status='replace') + WRITE(11, fmt='(A)') (cont_input(i),i=1,SIZE(cont_input)) + CLOSE(11) + CALL lmp%file(demo_file) + CALL lmp%file(cont_file) + OPEN(12, file=demo_file, status='old') + CLOSE(12, status='delete') + OPEN(13, file=cont_file, status='old') + CLOSE(13, status='delete') +END SUBROUTINE f_lammps_file + +SUBROUTINE f_lammps_command() BIND(C, name="f_lammps_command") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input + IMPLICIT NONE + INTEGER :: i + + DO i=1,SIZE(demo_input) + call lmp%command(demo_input(i)) + END DO +END SUBROUTINE f_lammps_command + +SUBROUTINE f_lammps_commands_list() BIND(C, name="f_lammps_commands_list") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + + CALL lmp%commands_list(demo_input) + CALL lmp%commands_list(cont_input) +END SUBROUTINE f_lammps_commands_list + +SUBROUTINE f_lammps_commands_string() BIND(C, name="f_lammps_commands_string") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcmds, ONLY: lmp, demo_input, cont_input + IMPLICIT NONE + INTEGER :: i + CHARACTER(len=512) :: cmds + + cmds = '' + DO i=1,SIZE(demo_input) + cmds = TRIM(cmds) // TRIM(demo_input(i)) // NEW_LINE('A') + END DO + DO i=1,SIZE(cont_input) + cmds = TRIM(cmds) // TRIM(cont_input(i)) // NEW_LINE('A') + END DO + + CALL lmp%commands_string(cmds) +END SUBROUTINE f_lammps_commands_string diff --git a/unittest/fortran/fortran-create.f90 b/unittest/fortran/fortran-create.f90 new file mode 100644 index 0000000000..694646e9bd --- /dev/null +++ b/unittest/fortran/fortran-create.f90 @@ -0,0 +1,86 @@ +MODULE keepcreate + USE liblammps + TYPE(LAMMPS) :: lmp + INTEGER :: mycomm +END MODULE keepcreate + +FUNCTION f_lammps_no_mpi_no_args() BIND(C, name="f_lammps_no_mpi_no_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_no_mpi_no_args + + lmp = lammps() + f_lammps_no_mpi_no_args = lmp%handle +END FUNCTION f_lammps_no_mpi_no_args + +FUNCTION f_lammps_no_mpi_with_args() BIND(C, name="f_lammps_no_mpi_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_no_mpi_with_args + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', '-nocite' ] + + lmp = lammps(args) + f_lammps_no_mpi_with_args = lmp%handle +END FUNCTION f_lammps_no_mpi_with_args + +FUNCTION f_lammps_open_no_args() BIND(C, name="f_lammps_open_no_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE MPI, ONLY: MPI_COMM_WORLD, mpi_comm_split + USE liblammps + USE keepcreate, ONLY: lmp,mycomm + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_open_no_args + INTEGER :: color, key, ierr + + color = 1 + key = 1 + CALL mpi_comm_split(MPI_COMM_WORLD, color, key, mycomm, ierr) + lmp = lammps(comm=mycomm) + f_lammps_open_no_args = lmp%handle +END FUNCTION f_lammps_open_no_args + +FUNCTION f_lammps_open_with_args() BIND(C, name="f_lammps_open_with_args") + USE ISO_C_BINDING, ONLY: c_ptr + USE MPI, ONLY: MPI_COMM_WORLD, mpi_comm_split + USE liblammps + USE keepcreate, ONLY: lmp,mycomm + IMPLICIT NONE + TYPE(c_ptr) :: f_lammps_open_with_args + INTEGER :: color, key, ierr + + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none', '-nocite' ] + + color = 2 + key = 1 + CALL mpi_comm_split(MPI_COMM_WORLD, color, key, mycomm, ierr) + lmp = lammps(args,mycomm) + f_lammps_open_with_args = lmp%handle +END FUNCTION f_lammps_open_with_args + +SUBROUTINE f_lammps_close() BIND(C, name="f_lammps_close") + USE ISO_C_BINDING, ONLY: c_null_ptr + USE liblammps + USE keepcreate, ONLY: lmp + IMPLICIT NONE + + CALL lmp%close() + lmp%handle = c_null_ptr +END SUBROUTINE f_lammps_close + +FUNCTION f_lammps_get_comm() BIND(C, name="f_lammps_get_comm") + USE liblammps + USE keepcreate, ONLY: mycomm + IMPLICIT NONE + INTEGER :: f_lammps_get_comm + + f_lammps_get_comm = mycomm +END FUNCTION f_lammps_get_comm + + diff --git a/unittest/fortran/mpi_stubs.f90 b/unittest/fortran/mpi_stubs.f90 new file mode 100644 index 0000000000..3f87fc38f7 --- /dev/null +++ b/unittest/fortran/mpi_stubs.f90 @@ -0,0 +1,20 @@ +MODULE MPI + IMPLICIT NONE + PRIVATE + + INTEGER, PARAMETER :: MPI_COMM_WORLD=0 + INTEGER, PARAMETER :: MPI_SUCCESS=0 + + PUBLIC :: MPI_COMM_WORLD, MPI_SUCCESS, & + mpi_comm_split + +CONTAINS + + SUBROUTINE mpi_comm_split(comm,color,key,newcomm,ierr) + INTEGER, INTENT(in) :: comm,color,key + INTEGER, INTENT(out) :: newcomm,ierr + + newcomm = comm + 1 + ierr = 0 + END SUBROUTINE mpi_comm_split +END MODULE MPI diff --git a/unittest/fortran/wrap-commands.cpp b/unittest/fortran/wrap-commands.cpp new file mode 100644 index 0000000000..493e7c4590 --- /dev/null +++ b/unittest/fortran/wrap-commands.cpp @@ -0,0 +1,65 @@ +// unit tests for issuing command to a LAMMPS instance through the Fortran wrapper + +#include "lammps.h" +#include +#include // for stdin, stdout +#include + +#include "gtest/gtest.h" + +// prototypes for fortran reverse wrapper functions +extern "C" { + void *f_lammps_with_args(); + void f_lammps_close(); + void f_lammps_file(); + void f_lammps_command(); + void f_lammps_commands_list(); + void f_lammps_commands_string(); + double f_lammps_get_natoms(); +} + +class LAMMPS_commands : public ::testing::Test +{ +protected: + LAMMPS_NS::LAMMPS *lmp; + LAMMPS_commands() {}; + ~LAMMPS_commands() override {}; + + void SetUp() override { + ::testing::internal::CaptureStdout(); + lmp = (LAMMPS_NS::LAMMPS *)f_lammps_with_args(); + 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_commands, from_file) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_file(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; + +TEST_F(LAMMPS_commands, from_line) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_command(); + EXPECT_EQ(f_lammps_get_natoms(),1); +}; + +TEST_F(LAMMPS_commands, from_list) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_commands_list(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; + +TEST_F(LAMMPS_commands, from_string) { + EXPECT_EQ(f_lammps_get_natoms(),0); + f_lammps_commands_string(); + EXPECT_EQ(f_lammps_get_natoms(),2); +}; diff --git a/unittest/fortran/wrap-create.cpp b/unittest/fortran/wrap-create.cpp new file mode 100644 index 0000000000..37443aba64 --- /dev/null +++ b/unittest/fortran/wrap-create.cpp @@ -0,0 +1,97 @@ +// unit tests for the LAMMPS base class + +#include "lammps.h" +#include +#include // for stdin, stdout +#include + +#include "gtest/gtest.h" + +// prototypes for fortran reverse wrapper functions +extern "C" { + void *f_lammps_open_no_args(); + void *f_lammps_open_with_args(); + void *f_lammps_no_mpi_no_args(); + void *f_lammps_no_mpi_with_args(); + void f_lammps_close(); + int f_lammps_get_comm(); +} + +TEST(open_no_mpi, no_args) { + ::testing::internal::CaptureStdout(); + int mpi_init=0; + MPI_Initialized(&mpi_init); + EXPECT_EQ(mpi_init,0); + void *handle = f_lammps_no_mpi_no_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + MPI_Initialized(&mpi_init); + EXPECT_NE(mpi_init,0); + EXPECT_EQ(lmp->world, MPI_COMM_WORLD); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_NE(lmp->citeme, nullptr); + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(open_no_mpi, with_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_no_mpi_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_EQ(lmp->logfile, nullptr); + EXPECT_EQ(lmp->citeme, nullptr); + EXPECT_EQ(lmp->world, MPI_COMM_WORLD); + + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(fortran_open, no_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_open_no_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + + int f_comm = f_lammps_get_comm(); + MPI_Comm mycomm = MPI_Comm_f2c(f_comm); + EXPECT_EQ(lmp->world, mycomm); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_NE(lmp->citeme, nullptr); + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} + +TEST(fortran_open, with_args) { + ::testing::internal::CaptureStdout(); + void *handle = f_lammps_open_with_args(); + std::string output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,6).c_str(),"LAMMPS"); + LAMMPS_NS::LAMMPS *lmp = (LAMMPS_NS::LAMMPS *)handle; + + int f_comm = f_lammps_get_comm(); + MPI_Comm mycomm = MPI_Comm_f2c(f_comm); + EXPECT_EQ(lmp->world, mycomm); + EXPECT_EQ(lmp->infile, stdin); + EXPECT_EQ(lmp->screen, stdout); + EXPECT_EQ(lmp->logfile, nullptr); + EXPECT_EQ(lmp->citeme, nullptr); + + ::testing::internal::CaptureStdout(); + f_lammps_close(); + output = ::testing::internal::GetCapturedStdout(); + EXPECT_STREQ(output.substr(0,16).c_str(), "Total wall time:"); +} From cec18b6aef75e3237e2822ad533506ba536ad14e Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 20:57:19 -0400 Subject: [PATCH 06/22] add docs for the new fortran interface --- doc/src/pg_fortran.rst | 174 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 2 deletions(-) diff --git a/doc/src/pg_fortran.rst b/doc/src/pg_fortran.rst index d028337b2c..a6085da13a 100644 --- a/doc/src/pg_fortran.rst +++ b/doc/src/pg_fortran.rst @@ -10,7 +10,7 @@ used from multiple compiler versions from different vendors for as long as they are compatible with the hosting operating system, the same is not true for Fortran codes. Thus the LAMMPS Fortran module needs to be compiled alongside the code using it from the source code in -``examples/COUPLE/fortran/lammps.f90``. When linking, you also need to +``fortran/lammps.f90``. When linking, you also need to :doc:`link to the LAMMPS library `. A typical command line for a simple program using the Fortran interface would be: @@ -19,5 +19,175 @@ for a simple program using the Fortran interface would be: mpifort -o testlib.x lammps.f90 testlib.f90 -L. -llammps Please note, that the MPI compiler wrapper is only required when the -calling the library from an MPI parallel code. +calling the library from an MPI parallel code. Please also note the order +of the source files: the lammps.f90 file needs to be compiled first, +since it provides the ``LIBLAMMPS`` module that is imported by the +fortran code using the interface. + +---------- + +Creating or deleting a LAMMPS object +************************************ + +With the Fortran interface the creation of a :cpp:class:`LAMMPS +` instance is included in the constructor for +creating the :f:func:`lammps` derived type. To import the definition of +that type and its type bound procedures you need to add a ``USE +LIBLAMMPS`` statement. Internally it will call either +:cpp:func:`lammps_open_fortran` or :cpp:func:`lammps_open_no_mpi` from +the C library API to create the class instance. All arguments are +optional and :cpp:func:`lammps_mpi_init` will be called automatically, +if it is needed. Similarly, a possible call to :cpp:func:`lammps_finalize` +is integrated into the :f:func:`close` function and triggered with +the optional logical argument set to ``.true.``. Here is a simple example: + +.. code-block:: fortran + + PROGRAM testlib + USE LIBLAMMPS ! include the LAMMPS library interface + TYPE(lammps) :: lmp ! derived type to hold LAMMPS instance + CHARACTER(len=*), DIMENSION(*), PARAMETER :: args = & + [ CHARACTER(len=12) :: 'liblammps', '-log', 'none' ] + + ! create a LAMMPS instance (and initialize MPI) + lmp = lammps(args) + ! get and print numerical version code + PRINT*, 'LAMMPS Version: ', lmp%version() + ! delete LAMMPS instance (and shuts down MPI) + CALL lmp%close(.true.) + + END PROGRAM testlib + +-------------------- + +Executing LAMMPS commands +========================= + +Once a LAMMPS instance is created, it is possible to "drive" the LAMMPS +simulation by telling LAMMPS to read commands from a file, or pass +individual or multiple commands from strings or lists of strings. This +is done similar to how it is implemented in the `C-library +` interface. Before handing off the calls to the +C-library interface, the corresponding Fortran versions of the calls +(:f:func:`file`, :f:func:`command`, :f:func:`commands_list`, and +:f:func:`commands_string`) have to make a copy of the strings passed as +arguments so that they can be modified to be compatible with the +requirements of strings in C without affecting the original strings. +Those copies are automatically deleted after the functions return. +Below is a small demonstration of the uses of the different functions: + +.. code-block:: fortran + + PROGRAM testcmd + USE LIBLAMMPS + TYPE(lammps) :: lmp + CHARACTER(len=512) :: cmds + CHARACTER(len=40),ALLOCATABLE :: cmdlist(:) + CHARACTER(len=10) :: trimmed + INTEGER :: i + + lmp = lammps() + CALL lmp%file('in.melt') + CALL lmp%command('variable zpos index 1.0') + ! define 10 groups of 10 atoms each + ALLOCATE(cmdlist(10)) + DO i=1,10 + WRITE(trimmed,'(I10)') 10*i + WRITE(cmdlist(i),'(A,I1,A,I10,A,A)') & + 'group g',i-1,' id ',10*(i-1)+1,':',ADJUSTL(trimmed) + END DO + CALL lmp%commands_list(cmdlist) + ! run multiple commands from multi-line string + cmds = 'clear' // NEW_LINE('A') // & + 'region box block 0 2 0 2 0 2' // NEW_LINE('A') // & + 'create_box 1 box' // NEW_LINE('A') // & + 'create_atoms 1 single 1.0 1.0 ${zpos}' + CALL lmp%commands_string(cmds) + CALL lmp%close() + + END PROGRAM testcmd + +--------------- + +The ``LIBLAMMPS`` module API +**************************** + +Below are the detailed descriptions of definitions and interfaces +of the contents of the ``LIBLAMMPS`` Fortran interface to LAMMPS. + +.. f:type:: lammps + + Derived type that is the general class of the Fortran interface. + It holds a reference to the :cpp:class:`LAMMPS ` class instance + that any of the included calls are forwarded to. + + :f c_ptr handle: reference to the LAMMPS class + :f close: :f:func:`close` + :f version: :f:func:`version` + :f file: :f:func:`file` + :f command: :f:func:`command` + :f commands_list: :f:func:`commands_list` + :f commands_string: :f:func:`commands_string` + +.. f:function:: lammps(args[,comm]) + + This is the constructor for the Fortran class and will forward + the arguments to a call to either :cpp:func:`lammps_open_fortran` + or :cpp:func:`lammps_open_no_mpi`. If the LAMMPS library has been + compiled with MPI support, it will also initialize MPI, if it has + not already been initialized before. + + The *args* argument with the list of command line parameters is + optional and so it the *comm* argument with the MPI communicator. + If *comm* is not provided, ``MPI_COMM_WORLD`` is assumed. For + more details please see the documentation of :cpp:func:`lammps_open`. + + :p character(len=*) args(*) [optional]: arguments as list of strings + :o integer comm [optional]: MPI communicator + :r lammps: an instance of the :f:type:`lammps` derived type + +.. f:subroutine:: close([finalize]) + + This method will close down the LAMMPS instance through calling + :cpp:func:`lammps_close`. If the *finalize* argument is present and + has a value of ``.true.``, then this subroutine also calls + :cpp:func:`lammps_mpi_finalize`. + + :o logical finalize [optional]: shut down the MPI environment of the LAMMPS library if true. + +.. f:function:: version() + + This method returns the numeric LAMMPS version like :cpp:func:`lammps_version` + + :r integer: LAMMPS version + +-------- + +.. f:subroutine:: file(filename) + + This method will call :cpp:func:`lammps_file` to have LAMMPS read + and process commands from a file. + + :p character(len=*) filename: name of file with LAMMPS commands + +.. f:subroutine:: command(cmd) + + This method will call :cpp:func:`lammps_command` to have LAMMPS + execute a single command. + + :p character(len=*) cmd: single LAMMPS command + +.. f:subroutine:: commands_list(cmds) + + This method will call :cpp:func:`lammps_commands_list` to have LAMMPS + execute a list of input lines. + + :p character(len=*) cmd(*): list of LAMMPS input lines + +.. f:subroutine:: commands_string(str) + + This method will call :cpp:func:`lammps_commands_string` to have LAMMPS + execute a block of commands from a string. + + :p character(len=*) str: LAMMPS input in string From 31c91a8928acbee2aa2fda265c8a11cec8551672 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Fri, 28 Aug 2020 21:22:06 -0400 Subject: [PATCH 07/22] fix typo --- doc/src/pg_library.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/pg_library.rst b/doc/src/pg_library.rst index 871ac5aaa5..9193b56214 100644 --- a/doc/src/pg_library.rst +++ b/doc/src/pg_library.rst @@ -111,7 +111,7 @@ LAMMPS Python APIs 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 dynamics loading, it is **required** that LAMMPS is compiled +Because of the dynamic loading, it is **required** that LAMMPS is compiled in :ref:`"shared" mode `. The Python interface is object oriented, but otherwise trying to be very similar to the C library API. Three different Python classes to run LAMMPS are available and they build on each other. From 60dfb6f77f2d9c0eb8bb8609da119696436500fb Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Tue, 12 May 2020 18:09:05 -0400 Subject: [PATCH 08/22] still need to disable variable tracking to silence unwanted warnings on ubuntu --- src/lmptype.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lmptype.h b/src/lmptype.h index 68af28af61..e2e7a88a42 100644 --- a/src/lmptype.h +++ b/src/lmptype.h @@ -250,17 +250,23 @@ The typecast prevents compiler warnings about possible truncations. // functions and avoid compiler warnings about variable tracking. // Disable for broken -D_FORTIFY_SOURCE feature. -#if defined(_FORTIFY_SOURCE) && (_FORTIFY_SOURCE > 0) -#define _noopt -#elif defined(__clang__) +#if defined(__clang__) # define _noopt __attribute__((optnone)) #elif defined(__INTEL_COMPILER) # define _noopt #elif defined(__GNUC__) # if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) -# define _noopt __attribute__((optimize("O0","no-var-tracking-assignments"))) +# if defined(_FORTIFY_SOURCE) && (_FORTIFY_SOURCE > 0) +# define _noopt __attribute__((optimize("no-var-tracking-assignments"))) +# else +# define _noopt __attribute__((optimize("O0","no-var-tracking-assignments"))) +# endif # else -# define _noopt __attribute__((optimize("O0"))) +# if defined(_FORTIFY_SOURCE) && (_FORTIFY_SOURCE > 0) +# define _noopt +# else +# define _noopt __attribute__((optimize("O0"))) +# endif # endif #else # define _noopt From 96ee132e85e4e06c6d1e9904cae53afd21e12764 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 01:50:37 -0400 Subject: [PATCH 09/22] start documenting tokenizer classes --- doc/doxygen/Doxyfile.in | 2 ++ doc/src/pg_developer.rst | 29 +++++++++++++++++++++++++++ src/tokenizer.h | 43 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index 78ac614ca3..d6b9327df1 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -422,6 +422,8 @@ INPUT = @LAMMPS_SOURCE_DIR@/utils.cpp \ @LAMMPS_SOURCE_DIR@/atom.h \ @LAMMPS_SOURCE_DIR@/input.cpp \ @LAMMPS_SOURCE_DIR@/input.h \ + @LAMMPS_SOURCE_DIR@/tokenizer.cpp \ + @LAMMPS_SOURCE_DIR@/tokenizer.h \ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index 9ea30d599f..430637746d 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -880,3 +880,32 @@ Convenience functions .. doxygenfunction:: timespec2seconds :project: progguide + +--------------------------- + +Tokenizer classes +================= + +The purpose of the tokenizer classes is to simplify the recurring task +of breaking lines of text down into words and/or numbers. +Traditionally, LAMMPS code would be using the ``strtok()`` function from +the C library for that purpose, but that function has two significant +disadvantages: 1) it cannot be used concurrently from different LAMMPS +instances since it stores its status in a global variable and 2) it +modifies the string that it is processing. These classes were +implemented to avoid both of these issues and also to reduce the amount +of code that needs to be written. + +The basic procedure is to create an instance of the class with the +string to be processed as an argument and then do a loop until all +available tokens are read. The constructor has a default set of +separator characters, but that can be overridden. The default separators +are all "whitespace" characters, i.e. the space character, the tabulator +character, the carriage return character, the linefeed character, and +the form feed character. + +.. doxygenclass:: LAMMPS_NS::Tokenizer + :project: progguide + +.. doxygenclass:: LAMMPS_NS::ValueTokenizer + :project: progguide diff --git a/src/tokenizer.h b/src/tokenizer.h index cc77c8ee0d..af8bb114f2 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -27,6 +27,11 @@ namespace LAMMPS_NS { #define TOKENIZER_DEFAULT_SEPARATORS " \t\r\n\f" +/*! Class for splitting text into words + * + * \sa ValueTokenizer + */ + class Tokenizer { std::string text; std::string separators; @@ -39,13 +44,46 @@ public: Tokenizer& operator=(const Tokenizer&) = default; Tokenizer& operator=(Tokenizer&&) = default; + /*! Reposition the tokenizer state to the first word, + * i.e. the first non-separator character + */ void reset(); + + /*! Skip over a given number of tokens + * + * \param n number of tokens to skip over + */ void skip(int n); + + /*! Indicate whether more tokens are available + * + * \return true if there are more tokens, false if not + */ bool has_next() const; + + /*! Search the text to be processed for a sub-string. + * + * \param str string to be searched for + * \return true if string was found, false if not + */ bool contains(const std::string & str) const; + + /*! Retrieve next token. + * + * \return string with the next token + */ std::string next(); + /*! Count number of tokens in text. + * + * \return number of counted tokens + */ size_t count(); + + /*! Retrieve the entire text converted to an STL vector of tokens. + * + * \return The STL vector + */ std::vector as_vector(); }; @@ -74,6 +112,11 @@ public: } }; +/*! Class for reading text with numbers + * + * \sa Tokenizer + */ + class ValueTokenizer { Tokenizer tokens; public: From 562300996285fdec4ef74542383276898555af06 Mon Sep 17 00:00:00 2001 From: Vsevak Date: Sun, 30 Aug 2020 01:29:48 +0300 Subject: [PATCH 10/22] arch 3.0 is dropped in CUDA 11 --- cmake/Modules/Packages/GPU.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Modules/Packages/GPU.cmake b/cmake/Modules/Packages/GPU.cmake index 04c320226a..ac2c975da3 100644 --- a/cmake/Modules/Packages/GPU.cmake +++ b/cmake/Modules/Packages/GPU.cmake @@ -75,7 +75,7 @@ if(GPU_API STREQUAL "CUDA") endif() # Kepler (GPU Arch 3.5) is supported by CUDA 5 to CUDA 11 if((CUDA_VERSION VERSION_GREATER_EQUAL "5.0") AND (CUDA_VERSION VERSION_LESS "12.0")) - string(APPEND GPU_CUDA_GENCODE " -gencode arch=compute_30,code=[sm_30,compute_30] -gencode arch=compute_35,code=[sm_35,compute_35]") + string(APPEND GPU_CUDA_GENCODE " -gencode arch=compute_35,code=[sm_35,compute_35]") endif() # Maxwell (GPU Arch 5.x) is supported by CUDA 6 and later if(CUDA_VERSION VERSION_GREATER_EQUAL "6.0") From 4b0999e167616f1a57077e8bb65a0e6ad2e42f81 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 14:11:14 -0400 Subject: [PATCH 11/22] complete documentation of tokenizer classes --- doc/src/pg_developer.rst | 50 +++++++++++++++++++++++++++++++++- src/tokenizer.cpp | 59 ++++++++++++++++++++++++++++++++++++++++ src/tokenizer.h | 48 ++++---------------------------- 3 files changed, 113 insertions(+), 44 deletions(-) diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index 430637746d..eaaa368425 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -902,10 +902,58 @@ available tokens are read. The constructor has a default set of separator characters, but that can be overridden. The default separators are all "whitespace" characters, i.e. the space character, the tabulator character, the carriage return character, the linefeed character, and -the form feed character. +the form feed character. Below is a small example code using the +tokenizer class to print the individual entries of the PATH environment +variable. + +.. code-block:: C++ + + #include "tokenizer.h" + #include + #include + #include + + using namespace LAMMPS_NS; + + int main(int, char **) + { + const char *path = getenv("PATH"); + + if (path != nullptr) { + Tokenizer p(path,":"); + while (p.has_next()) + std::cout << "Entry: " << p.next() << "\n"; + } + return 0; + } + +Most tokenizer operations cannot fail except for +:cpp:func:`LAMMPS_NS::Tokenizer::next` (when used without first +checking with :cpp:func:`LAMMPS_NS::Tokenizer::has_next`) and +:cpp:func:`LAMMPS_NS::Tokenizer::skip`. In case of failure, the class +will throw an exception, so you may need to wrap the code using the +tokenizer into a ``try`` / ``catch`` block to handle errors. The +:cpp:class:`LAMMPS_NS::ValueTokenizer` class may also throw an exception +when a (type of) number is requested as next token that is not +compatible with the string representing the next word. .. doxygenclass:: LAMMPS_NS::Tokenizer :project: progguide + :members: + +.. doxygenclass:: LAMMPS_NS::TokenizerException + :project: progguide + :members: .. doxygenclass:: LAMMPS_NS::ValueTokenizer :project: progguide + :members: + +.. doxygenclass:: LAMMPS_NS::InvalidIntegerException + :project: progguide + :members: what + +.. doxygenclass:: LAMMPS_NS::InvalidFloatException + :project: progguide + :members: what + diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 62605f76c3..032d3473a8 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -30,6 +30,13 @@ TokenizerException::TokenizerException(const std::string & msg, const std::strin } } +/** Class for splitting text into words + * + * This tokenizer will break down a string into sub-strings (i.e words) + * separated by the given separator characters. + * + * \sa LAMMPS_NS::ValueTokenizer TokenizerException */ + Tokenizer::Tokenizer(const std::string & str, const std::string & separators) : text(str), separators(separators), start(0), ntokens(std::string::npos) { @@ -48,14 +55,23 @@ Tokenizer::Tokenizer(Tokenizer && rhs) : reset(); } +/*! Reposition the tokenizer state to the first word, + * i.e. the first non-separator character */ void Tokenizer::reset() { start = text.find_first_not_of(separators); } +/*! Search the text to be processed for a sub-string. + * + * \param str string to be searched for + * \return true if string was found, false if not */ bool Tokenizer::contains(const std::string & str) const { return text.find(str) != std::string::npos; } +/*! Skip over a given number of tokens + * + * \param n number of tokens to skip over */ void Tokenizer::skip(int n) { for(int i = 0; i < n; ++i) { if(!has_next()) throw TokenizerException("No more tokens", ""); @@ -70,10 +86,16 @@ void Tokenizer::skip(int n) { } } +/*! Indicate whether more tokens are available + * + * \return true if there are more tokens, false if not */ bool Tokenizer::has_next() const { return start != std::string::npos; } +/*! Retrieve next token. + * + * \return string with the next token */ std::string Tokenizer::next() { if(!has_next()) throw TokenizerException("No more tokens", ""); @@ -90,6 +112,9 @@ std::string Tokenizer::next() { return token; } +/*! Count number of tokens in text. + * + * \return number of counted tokens */ size_t Tokenizer::count() { // lazy evaluation if (ntokens == std::string::npos) { @@ -98,6 +123,9 @@ size_t Tokenizer::count() { return ntokens; } +/*! Retrieve the entire text converted to an STL vector of tokens. + * + * \return The STL vector */ std::vector Tokenizer::as_vector() { // store current state size_t current = start; @@ -117,6 +145,9 @@ std::vector Tokenizer::as_vector() { return tokens; } +/*! Class for reading text with numbers + * + * \sa LAMMPS_NS::Tokenizer InvalidIntegerException InvalidFloatException */ ValueTokenizer::ValueTokenizer(const std::string & str, const std::string & separators) : tokens(str, separators) { } @@ -127,14 +158,24 @@ ValueTokenizer::ValueTokenizer(const ValueTokenizer & rhs) : tokens(rhs.tokens) ValueTokenizer::ValueTokenizer(ValueTokenizer && rhs) : tokens(std::move(rhs.tokens)) { } +/*! Indicate whether more tokens are available + * + * \return true if there are more tokens, false if not */ bool ValueTokenizer::has_next() const { return tokens.has_next(); } +/*! Search the text to be processed for a sub-string. + * + * \param value string with value to be searched for + * \return true if string was found, false if not */ bool ValueTokenizer::contains(const std::string & value) const { return tokens.contains(value); } +/*! Retrieve next token + * + * \return string with next token */ std::string ValueTokenizer::next_string() { if (has_next()) { std::string value = tokens.next(); @@ -143,6 +184,9 @@ std::string ValueTokenizer::next_string() { return ""; } +/*! Retrieve next token and convert to int + * + * \return value of next token */ int ValueTokenizer::next_int() { if (has_next()) { std::string current = tokens.next(); @@ -155,6 +199,9 @@ int ValueTokenizer::next_int() { return 0; } +/*! Retrieve next token and convert to bigint + * + * \return value of next token */ bigint ValueTokenizer::next_bigint() { if (has_next()) { std::string current = tokens.next(); @@ -167,6 +214,9 @@ bigint ValueTokenizer::next_bigint() { return 0; } +/*! Retrieve next token and convert to tagint + * + * \return value of next token */ tagint ValueTokenizer::next_tagint() { if (has_next()) { std::string current = tokens.next(); @@ -179,6 +229,9 @@ tagint ValueTokenizer::next_tagint() { return 0; } +/*! Retrieve next token and convert to double + * + * \return value of next token */ double ValueTokenizer::next_double() { if (has_next()) { std::string current = tokens.next(); @@ -191,10 +244,16 @@ double ValueTokenizer::next_double() { return 0.0; } +/*! Skip over a given number of tokens + * + * \param n number of tokens to skip over */ void ValueTokenizer::skip(int n) { tokens.skip(n); } +/*! Count number of tokens in text. + * + * \return number of counted tokens */ size_t ValueTokenizer::count() { return tokens.count(); } diff --git a/src/tokenizer.h b/src/tokenizer.h index af8bb114f2..fd034ddddb 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -27,11 +27,6 @@ namespace LAMMPS_NS { #define TOKENIZER_DEFAULT_SEPARATORS " \t\r\n\f" -/*! Class for splitting text into words - * - * \sa ValueTokenizer - */ - class Tokenizer { std::string text; std::string separators; @@ -44,49 +39,17 @@ public: Tokenizer& operator=(const Tokenizer&) = default; Tokenizer& operator=(Tokenizer&&) = default; - /*! Reposition the tokenizer state to the first word, - * i.e. the first non-separator character - */ void reset(); - - /*! Skip over a given number of tokens - * - * \param n number of tokens to skip over - */ void skip(int n); - - /*! Indicate whether more tokens are available - * - * \return true if there are more tokens, false if not - */ bool has_next() const; - - /*! Search the text to be processed for a sub-string. - * - * \param str string to be searched for - * \return true if string was found, false if not - */ bool contains(const std::string & str) const; - - /*! Retrieve next token. - * - * \return string with the next token - */ std::string next(); - /*! Count number of tokens in text. - * - * \return number of counted tokens - */ size_t count(); - - /*! Retrieve the entire text converted to an STL vector of tokens. - * - * \return The STL vector - */ std::vector as_vector(); }; +/** \exception TokenizerException. Contains an error message string. */ class TokenizerException : public std::exception { std::string message; public: @@ -95,28 +58,27 @@ public: ~TokenizerException() throw() { } + /** Retrieve message describing the thrown exception + * \return string with error message */ virtual const char * what() const throw() { return message.c_str(); } }; +/** \exception InvalidIntegerException. Contains an error message string. */ class InvalidIntegerException : public TokenizerException { public: InvalidIntegerException(const std::string & token) : TokenizerException("Not a valid integer number", token) { } }; +/** \exception FloatIntegerException. Contains an error message string. */ class InvalidFloatException : public TokenizerException { public: InvalidFloatException(const std::string & token) : TokenizerException("Not a valid floating-point number", token) { } }; -/*! Class for reading text with numbers - * - * \sa Tokenizer - */ - class ValueTokenizer { Tokenizer tokens; public: From 8d45b724a61bef9cfd654ec25fda2ec2cb27a3f3 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 14:12:53 -0400 Subject: [PATCH 12/22] fix bug in conventional build makefile preventing the correct Install.py in the lib folder to be run --- src/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Makefile b/src/Makefile index 4528c027cf..8216d5164c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -85,8 +85,8 @@ PACKUSERUC = $(call uppercase,$(PACKUSER)) YESDIR = $(call uppercase,$(@:yes-%=%)) NODIR = $(call uppercase,$(@:no-%=%)) -LIBDIR = $($(@:lib-%=%)) -LIBUSERDIR = $($(@:lib-user-%=%)) +LIBDIR = $(@:lib-%=%) +LIBUSERDIR = $(@:lib-user-%=%) # List of all targets From f3ed1dea4d23f5ee012830a6a4094443895168a6 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 14:28:19 -0400 Subject: [PATCH 13/22] minor tweaks --- src/tokenizer.cpp | 6 ++++-- src/tokenizer.h | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 032d3473a8..bad6250df5 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -35,7 +35,9 @@ TokenizerException::TokenizerException(const std::string & msg, const std::strin * This tokenizer will break down a string into sub-strings (i.e words) * separated by the given separator characters. * - * \sa LAMMPS_NS::ValueTokenizer TokenizerException */ + * \exception TokenizerException + * + * \sa ValueTokenizer TokenizerException */ Tokenizer::Tokenizer(const std::string & str, const std::string & separators) : text(str), separators(separators), start(0), ntokens(std::string::npos) @@ -147,7 +149,7 @@ std::vector Tokenizer::as_vector() { /*! Class for reading text with numbers * - * \sa LAMMPS_NS::Tokenizer InvalidIntegerException InvalidFloatException */ + * \sa Tokenizer InvalidIntegerException InvalidFloatException */ ValueTokenizer::ValueTokenizer(const std::string & str, const std::string & separators) : tokens(str, separators) { } diff --git a/src/tokenizer.h b/src/tokenizer.h index fd034ddddb..688e6dcbbc 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -49,10 +49,10 @@ public: std::vector as_vector(); }; -/** \exception TokenizerException. Contains an error message string. */ class TokenizerException : public std::exception { std::string message; public: + /** Thrown during retrieving or skipping tokens */ TokenizerException(const std::string & msg, const std::string & token); ~TokenizerException() throw() { From cd0cdf0b747a6d97d99946ec7976cf4a07221039 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 14:28:29 -0400 Subject: [PATCH 14/22] silence compiler warning --- src/USER-REACTION/fix_bond_react.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/USER-REACTION/fix_bond_react.cpp b/src/USER-REACTION/fix_bond_react.cpp index 0b0e41581d..9485d6cd27 100644 --- a/src/USER-REACTION/fix_bond_react.cpp +++ b/src/USER-REACTION/fix_bond_react.cpp @@ -169,7 +169,7 @@ FixBondReact::FixBondReact(LAMMPS *lmp, int narg, char **arg) : } else if (strcmp(arg[iarg],"reset_mol_ids") == 0) { if (iarg+2 > narg) error->all(FLERR,"Illegal fix bond/react command: " "'reset_mol_ids' keyword has too few arguments"); - if (strcmp(arg[iarg+1],"yes") == 0) ; // default + if (strcmp(arg[iarg+1],"yes") == 0) reset_mol_ids_flag = 1; // default if (strcmp(arg[iarg+1],"no") == 0) reset_mol_ids_flag = 0; iarg += 2; } else if (strcmp(arg[iarg],"react") == 0) { From 199cfeba78d8eec5528cb46a702a459995f1059f Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 14:32:53 -0400 Subject: [PATCH 15/22] more tweaks. doxygen translation has no more warnings now. --- src/tokenizer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tokenizer.h b/src/tokenizer.h index 688e6dcbbc..17f4159ad7 100644 --- a/src/tokenizer.h +++ b/src/tokenizer.h @@ -65,16 +65,16 @@ public: } }; -/** \exception InvalidIntegerException. Contains an error message string. */ class InvalidIntegerException : public TokenizerException { public: + /** Thrown during converting string to integer number */ InvalidIntegerException(const std::string & token) : TokenizerException("Not a valid integer number", token) { } }; -/** \exception FloatIntegerException. Contains an error message string. */ class InvalidFloatException : public TokenizerException { public: + /** Thrown during converting string to floating point number */ InvalidFloatException(const std::string & token) : TokenizerException("Not a valid floating-point number", token) { } }; From 65d2ee74644e7b068e6d3ef7b074f0d9ce888901 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 15:23:02 -0400 Subject: [PATCH 16/22] add work-in-progress marker to fortran library wrapper --- doc/src/pg_fortran.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/src/pg_fortran.rst b/doc/src/pg_fortran.rst index a6085da13a..0942d5a303 100644 --- a/doc/src/pg_fortran.rst +++ b/doc/src/pg_fortran.rst @@ -24,6 +24,15 @@ of the source files: the lammps.f90 file needs to be compiled first, since it provides the ``LIBLAMMPS`` module that is imported by the fortran code using the interface. +.. versionadded:: 30Sep2020 + +.. admonition:: Work in Progress + + This fortran module is work in progress and only the documented + functionality is currently available. The final implementation should + cover the entire range of functionality available in the C and + Python library interfaces. + ---------- Creating or deleting a LAMMPS object From 4484699ab64142fe1332bb04b7f34ef36387e247 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 15:52:42 -0400 Subject: [PATCH 17/22] fix spelling --- doc/src/pg_fortran.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/src/pg_fortran.rst b/doc/src/pg_fortran.rst index 0942d5a303..50738330d2 100644 --- a/doc/src/pg_fortran.rst +++ b/doc/src/pg_fortran.rst @@ -22,13 +22,13 @@ Please note, that the MPI compiler wrapper is only required when the calling the library from an MPI parallel code. Please also note the order of the source files: the lammps.f90 file needs to be compiled first, since it provides the ``LIBLAMMPS`` module that is imported by the -fortran code using the interface. +Fortran code using the interface. .. versionadded:: 30Sep2020 .. admonition:: Work in Progress - This fortran module is work in progress and only the documented + This Fortran module is work in progress and only the documented functionality is currently available. The final implementation should cover the entire range of functionality available in the C and Python library interfaces. From 569b6f95a3712736979643bb6d81e612b5186448 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 23:32:17 -0400 Subject: [PATCH 18/22] no need to use ValueTokenizer. Make code more compact. --- src/utils.cpp | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index e9058d4c2d..7b97ce44fc 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -641,14 +641,10 @@ std::string utils::get_potential_date(const std::string &path, const std::string reader.ignore_comments = false; char *line = reader.next_line(); - ValueTokenizer values(line); - while (values.has_next()) { - std::string word = values.next_string(); - if (word == "DATE:") { - if (values.has_next()) { - std::string date = values.next_string(); - return date; - } + Tokenizer words(line); + while (words.has_next()) { + if (words.next() == "DATE:") { + if (words.has_next()) return words.next(); } } return ""; @@ -664,14 +660,10 @@ std::string utils::get_potential_units(const std::string &path, const std::strin reader.ignore_comments = false; char *line = reader.next_line(); - ValueTokenizer values(line); - while (values.has_next()) { - std::string word = values.next_string(); - if (word == "UNITS:") { - if (values.has_next()) { - std::string units = values.next_string(); - return units; - } + Tokenizer words(line); + while (words.has_next()) { + if (words.next() == "UNITS:") { + if (words.has_next()) return words.next(); } } return ""; From 39a9974f3dade1046cc3c88b81f8c5efa24f7cd7 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Sun, 30 Aug 2020 23:57:02 -0400 Subject: [PATCH 19/22] add ValueTokenizer example and use captions with code-blocks --- doc/src/pg_developer.rst | 48 +++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index eaaa368425..f6cd3cd6c3 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -896,17 +896,16 @@ modifies the string that it is processing. These classes were implemented to avoid both of these issues and also to reduce the amount of code that needs to be written. -The basic procedure is to create an instance of the class with the -string to be processed as an argument and then do a loop until all +The basic procedure is to create an instance of the tokenizer class with +the string to be processed as an argument and then do a loop until all available tokens are read. The constructor has a default set of separator characters, but that can be overridden. The default separators are all "whitespace" characters, i.e. the space character, the tabulator character, the carriage return character, the linefeed character, and -the form feed character. Below is a small example code using the -tokenizer class to print the individual entries of the PATH environment -variable. +the form feed character. .. code-block:: C++ + :caption: Tokenizer class example listing entries of the PATH environment variable #include "tokenizer.h" #include @@ -937,6 +936,45 @@ tokenizer into a ``try`` / ``catch`` block to handle errors. The when a (type of) number is requested as next token that is not compatible with the string representing the next word. +.. code-block:: C++ + :caption: ValueTokenizer class example with exception handling + + #include "tokenizer.h" + #include + #include + #include + + using namespace LAMMPS_NS; + + int main(int, char **) + { + const char *text = "1 2 3 4 5 20.0 21 twentytwo 2.3"; + double num1(0),num2(0),num3(0),num4(0); + + ValueTokenizer t(text); + // read 4 doubles after skipping over 5 numbers + try { + t.skip(5); + num1 = t.next_double(); + num2 = t.next_double(); + num3 = t.next_double(); + num4 = t.next_double(); + } catch (TokenizerException &e) { + std::cout << "Reading numbers failed: " << e.what() << "\n"; + } + std::cout << "Values: " << num1 << " " << num2 << " " << num3 << " " << num4 << "\n"; + return 0; + } + +This code example should produce the following output: + +.. code-block:: + + Reading numbers failed: Not a valid floating-point number: 'twentytwo' + Values: 20 21 0 0 + +---------- + .. doxygenclass:: LAMMPS_NS::Tokenizer :project: progguide :members: From 33f2cbc71304b45f4fbe26eb36c23d8707f9c2d5 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 31 Aug 2020 06:57:16 -0400 Subject: [PATCH 20/22] add docs for TextFileReader class to developer guide --- doc/doxygen/Doxyfile.in | 2 ++ doc/src/pg_developer.rst | 20 ++++++++++++++++ src/text_file_reader.cpp | 49 +++++++++++++++++++++++++++++++++++++++- src/text_file_reader.h | 2 +- src/tokenizer.cpp | 6 ++++- 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index d6b9327df1..47e61a4ca8 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -424,6 +424,8 @@ INPUT = @LAMMPS_SOURCE_DIR@/utils.cpp \ @LAMMPS_SOURCE_DIR@/input.h \ @LAMMPS_SOURCE_DIR@/tokenizer.cpp \ @LAMMPS_SOURCE_DIR@/tokenizer.h \ + @LAMMPS_SOURCE_DIR@/text_file_reader.cpp \ + @LAMMPS_SOURCE_DIR@/text_file_reader.h \ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index f6cd3cd6c3..737a1597b1 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -762,6 +762,8 @@ on reading or an unexpected end-of-file state was reached. In that case, the functions will stop the calculation with an error message, indicating the name of the problematic file, if possible. +---------- + .. doxygenfunction:: sfgets :project: progguide @@ -785,6 +787,8 @@ return the result of a partial conversion or zero in cases where the string is not a valid number. This behavior allows to more easily detect typos or issues when processing input files. +---------- + .. doxygenfunction:: numeric :project: progguide @@ -803,6 +807,8 @@ String processing functions The following are functions to help with processing strings and parsing files or arguments. +---------- + .. doxygenfunction:: trim :project: progguide @@ -995,3 +1001,17 @@ This code example should produce the following output: :project: progguide :members: what +File reader classes +==================== + +The purpose of the file reader classes is to simplify the recurring task +of reading and parsing files. They are built on top of the tokenizer +classes discussed in the previous section, but include operations that +are optimized for processing larger chunks of data more efficiently. + +---------- + +.. doxygenclass:: LAMMPS_NS::TextFileReader + :project: progguide + :members: + diff --git a/src/text_file_reader.cpp b/src/text_file_reader.cpp index 8abe1b001f..1e1e5e2ec1 100644 --- a/src/text_file_reader.cpp +++ b/src/text_file_reader.cpp @@ -28,6 +28,17 @@ using namespace LAMMPS_NS; +/** Class for reading and parsing text files + * + * The value of the class member variable *ignore_comments* controls + * whether any text following the pound sign (#) should be ignored (true) + * or not (false). Default: true, i.e. ignore. + * + * \param filename Name of file to be read + * \param filetype Description of file type for error messages + * + * \sa PotentialFileReader */ + TextFileReader::TextFileReader(const std::string &filename, const std::string &filetype) : filename(filename), filetype(filetype), ignore_comments(true) { @@ -38,10 +49,14 @@ TextFileReader::TextFileReader(const std::string &filename, const std::string &f } } +/** Closes the file */ + TextFileReader::~TextFileReader() { fclose(fp); } +/** Read the next line and ignore it */ + void TextFileReader::skip_line() { char *ptr = fgets(line, MAXLINE, fp); if (ptr == nullptr) { @@ -50,6 +65,20 @@ void TextFileReader::skip_line() { } } +/** Read the next line(s) until *nparams* words have been read. + * + * This reads a line and counts the words in it, if the number + * is less than the requested number, it will read the next + * line, as well. Output will be a string with all read lines + * combined. The purpose is to somewhat replicate the reading + * behavior of formatted files in Fortran. + * + * If the *ignore_comments* class member has the value *true*, + * then any text read in is truncated at the first '#' character. + * + * \param nparams Number of words that must be read. Default: 0 + * \return String with the concatenated text */ + char *TextFileReader::next_line(int nparams) { // concatenate lines until have nparams words int n = 0; @@ -82,7 +111,6 @@ char *TextFileReader::next_line(int nparams) { return nullptr; } - // strip comment if (ignore_comments && (ptr = strchr(line, '#'))) *ptr = '\0'; @@ -97,6 +125,15 @@ char *TextFileReader::next_line(int nparams) { return line; } +/** Read lines until *n* doubles have been read and stored in array *list* + * + * This reads lines from the file using the next_line() function, + * and splits them into floating-point numbers using the + * ValueTokenizer class and stores the number is the provided list. + * + * \param list Pointer to array with suitable storage for *n* doubles + * \param n Number of doubles to be read */ + void TextFileReader::next_dvector(double * list, int n) { int i = 0; while (i < n) { @@ -116,6 +153,16 @@ void TextFileReader::next_dvector(double * list, int n) { } } +/** Read text until *nparams* words are read and passed to a tokenizer object for custom parsing. + * + * This reads lines from the file using the next_line() function, + * and splits them into floating-point numbers using the + * ValueTokenizer class and stores the number is the provided list. + * + * \param nparams Number of words to be read + * \param separators String with list of separators. + * \return ValueTokenizer object for read in text */ + ValueTokenizer TextFileReader::next_values(int nparams, const std::string & separators) { return ValueTokenizer(next_line(nparams), separators); } diff --git a/src/text_file_reader.h b/src/text_file_reader.h index 80a5d756ea..b64407701f 100644 --- a/src/text_file_reader.h +++ b/src/text_file_reader.h @@ -33,7 +33,7 @@ namespace LAMMPS_NS FILE *fp; public: - bool ignore_comments; + bool ignore_comments; //!< Controls whether comments are ignored TextFileReader(const std::string &filename, const std::string &filetype); ~TextFileReader(); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index bad6250df5..c08d66664f 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -35,7 +35,8 @@ TokenizerException::TokenizerException(const std::string & msg, const std::strin * This tokenizer will break down a string into sub-strings (i.e words) * separated by the given separator characters. * - * \exception TokenizerException + * \param str string to be processed + * \param separators string with separator characters (default: " \t\r\n\f") * * \sa ValueTokenizer TokenizerException */ @@ -148,6 +149,9 @@ std::vector Tokenizer::as_vector() { } /*! Class for reading text with numbers + * + * \param str string to be processed + * \param separators string with separator characters (default: " \t\r\n\f") * * \sa Tokenizer InvalidIntegerException InvalidFloatException */ From c84033677cc8a3e9808f9e7885eac0db7d4e9299 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 31 Aug 2020 07:31:28 -0400 Subject: [PATCH 21/22] add documentation for potential file reader --- doc/doxygen/Doxyfile.in | 2 + doc/src/pg_developer.rst | 19 +++++- doc/utils/sphinx-config/false_positives.txt | 1 + src/potential_file_reader.cpp | 76 +++++++++++++++++++++ src/tokenizer.cpp | 6 +- 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/doc/doxygen/Doxyfile.in b/doc/doxygen/Doxyfile.in index 47e61a4ca8..9acd994a68 100644 --- a/doc/doxygen/Doxyfile.in +++ b/doc/doxygen/Doxyfile.in @@ -426,6 +426,8 @@ INPUT = @LAMMPS_SOURCE_DIR@/utils.cpp \ @LAMMPS_SOURCE_DIR@/tokenizer.h \ @LAMMPS_SOURCE_DIR@/text_file_reader.cpp \ @LAMMPS_SOURCE_DIR@/text_file_reader.h \ + @LAMMPS_SOURCE_DIR@/potential_file_reader.cpp \ + @LAMMPS_SOURCE_DIR@/potential_file_reader.h \ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index 737a1597b1..d5a3c81199 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -1005,9 +1005,18 @@ File reader classes ==================== The purpose of the file reader classes is to simplify the recurring task -of reading and parsing files. They are built on top of the tokenizer -classes discussed in the previous section, but include operations that -are optimized for processing larger chunks of data more efficiently. +of reading and parsing files. They can use the +:cpp:class:`LAMMPS_NS::ValueTokenizer` class to process the read in +text. The :cpp:class:`LAMMPS_NS::TextFileReader` is a more general +version while :cpp:class:`LAMMPS_NS::PotentialFileReader` is specialized +to implement the behavior expected for looking up and reading/parsing +files with potential parameters in LAMMPS. The potential file reader +class requires a LAMMPS instance, requires to be run on MPI rank 0 only, +will use the :cpp:func:`utils::get_potential_file_path` function to look up and +open the file, and will call the :cpp:class:`LAMMPS_NS::Error` class in +case of failures to read or to convert numbers, so that LAMMPS will be +aborted. + ---------- @@ -1015,3 +1024,7 @@ are optimized for processing larger chunks of data more efficiently. :project: progguide :members: +.. doxygenclass:: LAMMPS_NS::PotentialFileReader + :project: progguide + :members: + diff --git a/doc/utils/sphinx-config/false_positives.txt b/doc/utils/sphinx-config/false_positives.txt index a300874e41..fa1a564e6f 100644 --- a/doc/utils/sphinx-config/false_positives.txt +++ b/doc/utils/sphinx-config/false_positives.txt @@ -2916,6 +2916,7 @@ strstr Stukowski Su subbox +Subclassed subcutoff subcycle subcycling diff --git a/src/potential_file_reader.cpp b/src/potential_file_reader.cpp index 47283fd574..62658a9ac5 100644 --- a/src/potential_file_reader.cpp +++ b/src/potential_file_reader.cpp @@ -29,6 +29,19 @@ using namespace LAMMPS_NS; +/** Class for reading and parsing LAMMPS potential files + * + * The value of the class member variable *ignore_comments* controls + * whether any text following the pound sign (#) should be ignored (true) + * or not (false). Default: true, i.e. ignore. + * + * \param lmp Pointer to LAMMPS instance + * \param filename Name of file to be read + * \param potential_name Name of potential style for error messages + * \param auto_convert Bitmask of supported unit conversions + * + * \sa TextFileReader */ + PotentialFileReader::PotentialFileReader(LAMMPS *lmp, const std::string &filename, const std::string &potential_name, @@ -53,14 +66,21 @@ PotentialFileReader::PotentialFileReader(LAMMPS *lmp, } } +/** Closes the file */ + PotentialFileReader::~PotentialFileReader() { delete reader; } +/** Set comment (= text after '#') handling preference for the file to be read + * + * \param value Comment text is ignored if true, or not if false */ void PotentialFileReader::ignore_comments(bool value) { reader->ignore_comments = value; } +/** Read a line but ignore its content */ + void PotentialFileReader::skip_line() { try { reader->skip_line(); @@ -69,6 +89,17 @@ void PotentialFileReader::skip_line() { } } +/** Read the next line(s) until *nparams* words have been read. + * + * This reads a line and counts the words in it, if the number + * is less than the requested number, it will read the next + * line, as well. Output will be a string with all read lines + * combined. The purpose is to somewhat replicate the reading + * behavior of formatted files in Fortran. + * + * \param nparams Number of words that must be read. Default: 0 + * \return String with the concatenated text */ + char *PotentialFileReader::next_line(int nparams) { try { return reader->next_line(nparams); @@ -78,6 +109,15 @@ char *PotentialFileReader::next_line(int nparams) { return nullptr; } +/** Read lines until *n* doubles have been read and stored in array *list* + * + * This reads lines from the file using the next_line() function, + * and splits them into floating-point numbers using the + * ValueTokenizer class and stores the number is the provided list. + * + * \param list Pointer to array with suitable storage for *n* doubles + * \param n Number of doubles to be read */ + void PotentialFileReader::next_dvector(double * list, int n) { try { return reader->next_dvector(list, n); @@ -86,6 +126,16 @@ void PotentialFileReader::next_dvector(double * list, int n) { } } +/** Read text until *nparams* words are read and passed to a tokenizer object for custom parsing. + * + * This reads lines from the file using the next_line() function, + * and splits them into floating-point numbers using the + * ValueTokenizer class and stores the number is the provided list. + * + * \param nparams Number of words to be read + * \param separators String with list of separators. + * \return ValueTokenizer object for read in text */ + ValueTokenizer PotentialFileReader::next_values(int nparams, const std::string & separators) { try { return reader->next_values(nparams, separators); @@ -95,6 +145,10 @@ ValueTokenizer PotentialFileReader::next_values(int nparams, const std::string & return ValueTokenizer(""); } +/** Read next line and convert first word to a double + * + * \return Value of first word in line as double */ + double PotentialFileReader::next_double() { try { char * line = reader->next_line(1); @@ -105,6 +159,10 @@ double PotentialFileReader::next_double() { return 0.0; } +/** Read next line and convert first word to an int + * + * \return Value of first word in line as int */ + int PotentialFileReader::next_int() { try { char * line = reader->next_line(1); @@ -115,6 +173,10 @@ int PotentialFileReader::next_int() { return 0; } +/** Read next line and convert first word to a tagint + * + * \return Value of first word in line as tagint */ + tagint PotentialFileReader::next_tagint() { try { char * line = reader->next_line(1); @@ -125,6 +187,10 @@ tagint PotentialFileReader::next_tagint() { return 0; } +/** Read next line and convert first word to a bigint + * + * \return Value of first word in line as bigint */ + bigint PotentialFileReader::next_bigint() { try { char * line = reader->next_line(1); @@ -135,6 +201,10 @@ bigint PotentialFileReader::next_bigint() { return 0; } +/** Read next line and return first word + * + * \return First word of read in line */ + std::string PotentialFileReader::next_string() { try { char * line = reader->next_line(1); @@ -145,6 +215,12 @@ std::string PotentialFileReader::next_string() { return ""; } +/** Look up and open the potential file + * + * \param path Path of the potential file to open + * \return Pointer to TextFileReader object created + * \sa TextFileReader */ + TextFileReader *PotentialFileReader::open_potential(const std::string &path) { std::string filepath = utils::get_potential_file_path(path); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index c08d66664f..eadff698cb 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -58,7 +58,7 @@ Tokenizer::Tokenizer(Tokenizer && rhs) : reset(); } -/*! Reposition the tokenizer state to the first word, +/*! Re-position the tokenizer state to the first word, * i.e. the first non-separator character */ void Tokenizer::reset() { start = text.find_first_not_of(separators); @@ -150,8 +150,8 @@ std::vector Tokenizer::as_vector() { /*! Class for reading text with numbers * - * \param str string to be processed - * \param separators string with separator characters (default: " \t\r\n\f") + * \param str String to be processed + * \param separators String with separator characters (default: " \t\r\n\f") * * \sa Tokenizer InvalidIntegerException InvalidFloatException */ From c7360fb8084426baee1550eafd3d097cceaceb70 Mon Sep 17 00:00:00 2001 From: Axel Kohlmeyer Date: Mon, 31 Aug 2020 07:45:52 -0400 Subject: [PATCH 22/22] include example use of PotentialFileReader class --- doc/src/pg_developer.rst | 51 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/doc/src/pg_developer.rst b/doc/src/pg_developer.rst index d5a3c81199..1cb1b7dbcb 100644 --- a/doc/src/pg_developer.rst +++ b/doc/src/pg_developer.rst @@ -1012,10 +1012,53 @@ version while :cpp:class:`LAMMPS_NS::PotentialFileReader` is specialized to implement the behavior expected for looking up and reading/parsing files with potential parameters in LAMMPS. The potential file reader class requires a LAMMPS instance, requires to be run on MPI rank 0 only, -will use the :cpp:func:`utils::get_potential_file_path` function to look up and -open the file, and will call the :cpp:class:`LAMMPS_NS::Error` class in -case of failures to read or to convert numbers, so that LAMMPS will be -aborted. +will use the :cpp:func:`LAMMPS_NS::utils::get_potential_file_path` +function to look up and open the file, and will call the +:cpp:class:`LAMMPS_NS::Error` class in case of failures to read or to +convert numbers, so that LAMMPS will be aborted. + +.. code-block:: C++ + :caption: Use of PotentialFileReader class in pair style coul/streitz + + PotentialFileReader reader(lmp, file, "coul/streitz"); + char * line; + + while((line = reader.next_line(NPARAMS_PER_LINE))) { + try { + ValueTokenizer values(line); + std::string iname = values.next_string(); + + int ielement; + for (ielement = 0; ielement < nelements; ielement++) + if (iname == elements[ielement]) break; + + if (nparams == maxparam) { + maxparam += DELTA; + params = (Param *) memory->srealloc(params,maxparam*sizeof(Param), + "pair:params"); + } + + params[nparams].ielement = ielement; + params[nparams].chi = values.next_double(); + params[nparams].eta = values.next_double(); + params[nparams].gamma = values.next_double(); + params[nparams].zeta = values.next_double(); + params[nparams].zcore = values.next_double(); + + } catch (TokenizerException & e) { + error->one(FLERR, e.what()); + } + nparams++; + } + +A file that would be parsed by the reader code fragment looks like this: + + # DATE: 2015-02-19 UNITS: metal CONTRIBUTOR: Ray Shan CITATION: Streitz and Mintmire, Phys Rev B, 50, 11996-12003 (1994) + # + # X (eV) J (eV) gamma (1/\AA) zeta (1/\AA) Z (e) + + Al 0.000000 10.328655 0.000000 0.968438 0.763905 + O 5.484763 14.035715 0.000000 2.143957 0.000000 ----------