Gromacs  2024.4
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
mdrun modules

Currently, most of mdrun is constructed as a set of C routines calling each other, and sharing data through a couple of common data structures (t_inputrec, t_forcerec, t_state etc.) that flow throughout the code.

The electric field code (in src/gromacs/applied-forces/) implements an alternative concept that allows keeping everything related to the electric field functionality in a single place. At least for most special-purpose functionality, this would hopefully provide a more maintainable approach that would also support more easily adding new functionality. Some core features may still need stronger coupling than this provides.

The rest of the page documents those parts of the modularity mechanism that have taken a clear form. Generalizing and designing other parts may require more code to be converted to modules to have clearer requirements on what the mechanism needs to support and what is the best way to express that in a generally usable form.

Structure of a module

Each module implements a factory that returns an instance of gmx::IMDModule. This interface has methods that in turn refer to other interfaces: gmx::IMdpOptionProvider, gmx::IMDOutputProvider, and gmx::IForceProvider. The module also implements these interfaces (or a subset of them), and code outside the module only calls methods in these interfaces.

See documentation of the individual interfaces for details of what they support.

Implementation of a module

Modules are constructed by composition of interfaces (i.e. abstract classes, general with pure virtual methods lacking implementations), so that e.g. trajectory-writing code can loop over containers of pointers to gmx::IMDOutputProvider without needing to know about all the concrete types that might implement that interface.

The module classes should not be extended by using them as a base class, which is expressed with the final keyword in the class definition. Generally, modules will implement different flavours of functionality, perhaps based on user choices, or available computing resources. This should generally be implemented by providing variable behavior for the methods that are called through the above interfaces. Either code should branch at run time upon some data contained by the module (e.g. read from the mdp options), or that the module class should contain a pointer to an internal interface class whose concrete type might be chosen during setup from the set of implementations of that internal interface. Such an approach keeps separate the set of interfaces characteristic of "MD modules" from those that are particular to flavours of any specific module.

The virtual methods that the module classes inherit from their interfaces should be declared as override, to express the intent that they implement a virtual function from the interface. This permits the compiler to check that this is true, e.g. if the interface class changes. The virtual keyword should not be specified, because this is redundant when override is used. This follows the Cpp Core Guidelines (guideline C.128).

Handling mdp input

To accept parameters from an mdp file, a module needs to implement gmx::IMdpOptionProvider.

initMdpOptions() should declare the required input parameters using the options module. In most cases, the parameters should be declared as nested sections instead of a flat set of options. The structure used should be such that in the future, we can get the input values from a structured mdp file (e.g., JSON or XML), where the structure matches the declared options. As with other uses of the options module, the module needs to declare local variables where the values from the options will be assigned. The defined structure will also be used for storing in the tpr file (see below).

initMdpTransform() should declare the mapping from current flat mdp format to the structured format defined in initMdpOptions(). For now, this makes it possible to have an internal forward-looking structured representation while the input is still a flat list of values, but in the future it also allows supporting both formats side-by-side as long as that is deemed necessary.

On the implementation side, the framework (and other code that interacts with the modules) will do the following things to make mdp input work:

Currently, there is no mechanism for changing the mdp input parameters (adding new or removing old ones) that would maintain tpr and mdp backward compatibility. The vision for this is to use the same transformation engine as for initMdpTransform() to support specifying version-to-version conversions for any changed options, and applying the necessary conversions in sequence. The main challenge is keeping track of the versions to know which conversions to apply.

Callbacks to modules during setup and simulation

During setup and simulation, modules receive required information like topologies and local atom sets by subscribing to callback functions.

To include a notification for your module

Storing non-mdp option module parameters

Some mdrun modules want to store data that is non-mdp input, e.g., the result of computation during setup. Atom indices of index groups are one example: they are evaluated from strings during grompp time and stored as list of integers in the run input file. During the mdrun setup the information to evaluate the index groups is no longer available.

To store parameters, subscribe to the KeyValueTreeBuilder* notification that provides a handle to a KeyValueTreeBuilder that allows adding own information to that tree.

To restore parameters, subscribe to the const KeyValueTreeObject & notification that returns the tree that is build by the KeyValueTreeBuilder*.