12. Examples of OpenMM Integration

12.1. GROMACS

GROMACS is a large, complex application written primarily in C. The considerations involved in adapting it to use OpenMM are likely to be similar to those faced by developers of other existing applications.

The first principle we followed in adapting GROMACS was to keep all OpenMM-related code isolated to just a few files, while modifying as little of the existing GROMACS code as possible. This minimized the risk of breaking existing parts of the code, while making the OpenMM-related parts as easy to work with as possible. It also minimized the need for C code to invoke the C++ API. (This would not be an issue if we used the OpenMM C API wrapper, but that is less convenient than the C++ API, and placing all of the OpenMM calls into separate C++ files solves the problem equally well.) Nearly all of the OpenMM-specific code is contained in a single file, openmm_wrapper.cpp. It defines four functions which encapsulate all of the interaction between OpenMM and the rest of GROMACS:

openmm_init(): As arguments, this function takes pointers to lots of internal GROMACS data structures that describe the simulation to be run. It creates a System, Integrator, and Context based on them, then returns an opaque reference to an object containing them. That reference is an input argument to all of the other functions defined in openmm_wrapper.cpp. This allows information to be passed between those functions without exposing it to the rest of GROMACS.

openmm_take_one_step(): This calls step(1) on the Integrator that was created by openmm_init().

openmm_copy_state(): This calls getState() on the Context that was created by openmm_init(), and then copies information from the resulting State into various GROMACS data structures. This function is how state data generated by OpenMM is passed back to GROMACS for output, analysis, etc.

openmm_cleanup(): This is called at the end of the simulation. It deletes all the objects that were created by openmm_init().

This set of functions defines the interactions between GROMACS and OpenMM: copying information from the application to OpenMM, performing integration, copying information from OpenMM back to the application, and freeing resources at the end of the simulation. While the details of their implementations are specific to GROMACS, this overall pattern is fairly generic. A similar set of functions can be used for many other applications as well.

12.2. TINKER-OpenMM

TINKER is written primarily in Fortran, and uses common blocks extensively to store application-wide parameters. Rather than modify the TINKER build scripts to allow C++ code, it was decided to use the OpenMM C API instead. Despite these differences, the overall approach used to add OpenMM support was very similar to that used for GROMACS.

TINKER-OpenMM allows OpenMM to be used to calculate forces and energies and to perform the integration in the main molecular dynamics loop. The only changes to the TINKER source code are in the file dynamic.f for the setup and running of a simulation. An added file, dynamic_openmm.c, contains the interface C code between TINKER and OpenMM.

The flow of the molecular dynamics simulation using OpenMM is as follows:

  1. The TINKER code is used to read the AMOEBA parameter file, the *.xyz and *.key files. It then parses the command-line options.

  2. The routine map_common_blocks_to_c_data_structs() is called to map the FORTRAN common blocks to C data structures used in setting the parameters used by OpenMM.

  3. The routine openmm_validate() is called from dynamic.f before the main loop. This routine checks that all required options and settings obtained from the input in step (1) and common blocks in step (2) are available. If an option or setting is unsupported, the program exits with an appropriate message. The routine openmm_validate() and the other OpenMM interface methods are in the file dynamic_openmm.c.

  4. openmm_init() is called to create the OpenMM System, Integrator and Context objects..

  5. openmm_take_steps() is called to take a specified number of time steps.

  6. openmm_update() is then called to retrieve the state (energies/positions/velocities) and populate the appropriate TINKER data structures. These values are converted from the OpenMM units of kJ/nm to kcal/Å when populating the TINKER arrays.

  7. Once the main loop has completed, the routine openmm_cleanup() is called to delete the OpenMM objects and release resources being used on the GPU.