4.8.8. Writing a new command style — LAMMPS documentation

\(\renewcommand{\AA}{\text{Å}}\)

# 4.8.8. Writing a new command style

Command styles allow to do system manipulations or interfaces to the operating system.

In the text below, we will discuss the implementation of one example. As shown on the page for writing or extending command styles, in order to implement a new command style, a new class must be written that is either directly or indirectly derived from the `Command` class. There is just one method that must be implemented: `Command::command()`. In addition, a custom constructor is needed to get access to the members of the `LAMMPS` class like the `Error` class to print out error messages. The `Command::command()` method processes the arguments passed to the command in the input and executes it. Any other methods would be for the convenience of implementation of the new command.

In general, new command styles should be added to the EXTRA-COMMAND package. If you feel that your contribution should be added to a different package, please consult with the LAMMPS developers first. The contributed code needs to support the traditional GNU make build process and the CMake build process.

# 4.8.9. Case 1: Implementing the geturl command

In this section, we will describe the procedure of adding a simple command style to LAMMPS: the geturl command that allows to download files directly without having to rely on an external program like “wget” or “curl”. The complete implementation can be found in the files `src/EXTRA-COMMAND/geturl.cpp` and `src/EXTRA-COMMAND/geturl.h` of the LAMMPS source code.

## Interfacing the libcurl library

Rather than implementing the various protocols for downloading files, we rely on an external library: libcurl library. This requires that the library and its headers are installed. For the traditional GNU make build system, this simply requires edits to the machine makefile to add compilation flags like for other libraries. For the CMake based build system, we need to add some lines to the file `cmake/Modules/Packages/EXTRA-COMMAND.cmake`:

    find_package(CURL QUIET COMPONENTS HTTP HTTPS)
    option(WITH_CURL "Enable libcurl support" ${CURL_FOUND})
    if(WITH_CURL)
      find_package(CURL REQUIRED COMPONENTS HTTP HTTPS)
      target_compile_definitions(lammps PRIVATE -DLAMMPS_CURL)
      target_link_libraries(lammps PRIVATE CURL::libcurl)
    endif()

The first `find_package()` command uses a built-in CMake module to find an existing libcurl installation with development headers and support for using the HTTP and HTTPS protocols. The “QUIET” flag ensures that there is no screen output and no error if the search fails. The status of the search is recorded in the “${CURL_FOUND}” variable. That variable sets the default of the WITH_CURL option, which toggles whether support for libcurl is included or not.

The second `find_package()` uses the “REQUIRED” flag to produce an error if the WITH_CURL option was set to `True`, but no suitable libcurl implementation with development support was found. This construct is used so that the CMake script code inside the `if(WITH_CURL)` and `endif()` block can be expanded later to download and compile libcurl as part of the LAMMPS build process, if it is not found locally. The `target_compile_definitions()` function added the define `-DLAMMPS_CURL` to the compilation flags when compiling objects for the LAMMPS library. This allows to always compile the geturl command, but use pre-processing to compile in the interface to libcurl only when it is present and usable and otherwise stop with an error message about the unavailability of libcurl to execute the functionality of the command.

## Header file

The first segment of any LAMMPS source should be the copyright and license statement. Note the marker in the first line to indicate to editors like emacs that this file is a C++ source, even though the .h extension suggests a C source (this is a convention inherited from the very beginning of the C++ version of LAMMPS).

    /* -*- c++ -*- ----------------------------------------------------------
       LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
       https://www.lammps.org/, Sandia National Laboratories
       LAMMPS development team: developers@lammps.org

       Copyright (2003) Sandia Corporation.  Under the terms of Contract
       DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
       certain rights in this software.  This software is distributed under
       the GNU General Public License.

       See the README file in the top-level LAMMPS directory.
    ------------------------------------------------------------------------- */

Every command style must be registered in LAMMPS by including the following lines of code in the second part of the header after the copyright message and before the include guards for the class definition:

    #ifdef COMMAND_CLASS
    // clang-format off
    CommandStyle(geturl,GetURL);
    // clang-format on
    #else

This block between `#ifdef COMMAND_CLASS` and `#else` will be included by the `Input` class in `input.cpp` to build a map of “factory functions” that will create an instance of a Command class and call its `command()` method. The map connects the name of the command `geturl` with the name of the class `GetURL`. During compilation, LAMMPS constructs a file `style_command.h` that contains `#include` statements for all “installed” command styles. Before including `style_command.h` into `input.cpp`, the `COMMAND_CLASS` define is set and the `CommandStyle(name,class)` macro defined. The code of the macro adds the installed command styles to the “factory map” which enables the `Input` to execute the command.

The list of header files to include in `style_command.h` is automatically updated by the build system if there are new files, so the presence of the new header file in the `src/EXTRA-COMMAND` folder and the enabling of the EXTRA-COMMAND package will trigger LAMMPS to include the new command style when it is (re-)compiled. The “// clang-format” format comments are needed so that running clang-format on the file will not insert unwanted blanks which would break the `CommandStyle` macro.

The third part of the header file is the actual class definition of the `GetURL` class. This has the custom constructor and the `command()` method implemented by this command style. For the constructor there is nothing to do but to pass the `lmp` pointer to the base class. Since the `command()` method is labeled “virtual” in the base class, it must be given the “override” property.

    #ifndef LMP_GETURL_H
    #define LMP_GETURL_H

    #include "command.h"

    namespace LAMMPS_NS {

    class GetURL : public Command {
     public:
      GetURL(class LAMMPS *lmp) : Command(lmp) {};
      void command(int, char **) override;
    };
    }    // namespace LAMMPS_NS
    #endif
    #endif

The “override” property helps to detect unexpected mismatches because compilation will stop with an error in case the signature of a function is changed in the base class without also changing it in all derived classes.

## Implementation file

We move on to the implementation of the `GetURL` class in the `geturl.cpp` file. This file also starts with a LAMMPS copyright and license header. Below that notice is typically the space where comments may be added with additional information about this specific file, the author(s), affiliation(s), and email address(es). This way the contributing author(s) can be easily contacted, when there are questions about the implementation later. Since the file(s) may be around for a long time, it is beneficial to use some kind of “permanent” email address, if possible.

    /* ----------------------------------------------------------------------
       LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
       https://www.lammps.org/, Sandia National Laboratories
       LAMMPS development team: developers@lammps.org

       Copyright (2003) Sandia Corporation.  Under the terms of Contract
       DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
       certain rights in this software.  This software is distributed under
       the GNU General Public License.

       See the README file in the top-level LAMMPS directory.
    ------------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------
       Contributing authors:  Axel Kohlmeyer (Temple U),
    ------------------------------------------------------------------------- */

    #include "geturl.h"

    #include "comm.h"
    #include "error.h"

    #if defined(LAMMPS_CURL)
    #include <curl/curl.h>
    #endif

    using namespace LAMMPS_NS;

The second section of the implementation file has various include statements. The include file for the class header has to come first, then a couple of LAMMPS classes (sorted alphabetically) followed by the header for the libcurl interface. This is wrapped into an `#ifdef` block so that LAMMPS will compile this file without error when the libcurl header is not available and thus the define not set. The final statement of this segment imports the `LAMMPS_NS::` namespace globally for this file. This way, all LAMMPS specific functions and classes do not have to be prefixed with `LAMMPS_NS::`.

## The command() function (required)

Since the required custom constructor is trivial and implemented in the header, there is only one function that must be implemented for a command style and that is the `command()` function.

    void GetURL::command(int narg, char **arg)
    {
    #if !defined(LAMMPS_CURL)
      error->all(FLERR, "LAMMPS has not been compiled with libcurl support");
    #else
      if (narg < 1) utils::missing_cmd_args(FLERR, "geturl", error);
      int verify = 1;
      int overwrite = 1;
      int verbose = 0;

This first part also has the `#ifdef` block depending on the LAMMPS_CURL define. This way the command will simply print an error, if libcurl is not available but will not fail to compile. Furthermore, it sets the defaults for the following optional arguments.

    // process arguments

    std::string url = arg[0];

    // sanity check

    if ((url.find(':') == std::string::npos) || (url.find('/') == std::string::npos))
      error->all(FLERR, "URL '{}' is not a supported URL", url);

    std::string output = url.substr(url.find_last_of('/') + 1);
    if (output.empty()) error->all(FLERR, "URL '{}' must end in a file string", url);

This block stores the positional, i.e. non-optional argument of the URL to be downloaded and adds a couple of sanity checks on the string to make sure it is a valid URL. Also it derives the default name of the output file from the URL.

    int iarg = 1;
    while (iarg < narg) {
      if (strcmp(arg[iarg], "output") == 0) {
        if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "geturl output", error);
        output = arg[iarg + 1];
        ++iarg;
      } else if (strcmp(arg[iarg], "overwrite") == 0) {
        if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "geturl overwrite", error);
        overwrite = utils::logical(FLERR, arg[iarg + 1], false, lmp);
        ++iarg;
      } else if (strcmp(arg[iarg], "verify") == 0) {
        if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "geturl verify", error);
        verify = utils::logical(FLERR, arg[iarg + 1], false, lmp);
        ++iarg;
      } else if (strcmp(arg[iarg], "verbose") == 0) {
        if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "geturl verbose", error);
        verbose = utils::logical(FLERR, arg[iarg + 1], false, lmp);
        ++iarg;
      } else {
        error->all(FLERR, "Unknown geturl keyword: {}", arg[iarg]);
      }
      ++iarg;
    }

This block parses the optional arguments following the URL and stops with an error if there are arguments missing or an unknown argument is encountered.

    // only download files from rank 0

    if (comm->me != 0) return;

    if (!overwrite && platform::file_is_readable(output)) return;

    // open output file for writing

    FILE *out = fopen(output.c_str(), "wb");
    if (!out)
      error->all(FLERR, "Cannot open output file {} for writing: {}", output, utils::getsyserror());

Here all MPI ranks other than 0 will return, so that the URL download will only happen from a single MPI rank. For that rank the output file is opened for writing using the C library function `fopen()`.

    // initialize curl and perform download

    CURL *curl;
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();
    if (curl) {
      (void) curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
      (void) curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) out);
      (void) curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);
      (void) curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
      if (verbose && screen) {
        (void) curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        (void) curl_easy_setopt(curl, CURLOPT_STDERR, (void *) screen);
      }
      if (!verify) {
        (void) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
        (void) curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
      }
      auto res = curl_easy_perform(curl);
      if (res != CURLE_OK) {
        long response = 0L;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
        error->one(FLERR, "Download of {} failed with: {} {}", output, curl_easy_strerror(res),
                   response);
      }
      curl_easy_cleanup(curl);

This block now implements the actual URL download with the selected options via the “easy” interface of libcurl. For the details of what these function calls do, please have a look at the *libcurl documentation.

>       }
>       curl_global_cleanup();
>       fclose(out);
>     #endif
>     }

Finally, the previously opened file is closed and the command is complete.