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:
The TINKER code is used to read the AMOEBA parameter file, the
*.xyz
and*.key
files. It then parses the command-line options.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.The routine
openmm_validate()
is called fromdynamic.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 routineopenmm_validate()
and the other OpenMM interface methods are in the filedynamic_openmm.c
.openmm_init()
is called to create the OpenMM System, Integrator and Context objects..openmm_take_steps()
is called to take a specified number of time steps.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.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.