1 mdrun modules {#page_mdmodules}
4 Currently, most of mdrun is constructed as a set of C routines calling each
5 other, and sharing data through a couple of common data structures (t_inputrec,
6 t_forcerec, t_state etc.) that flow throughout the code.
8 The electric field code (in `src/gromacs/applied-forces/`) implements an
9 alternative concept that allows keeping everything related to the electric
10 field functionality in a single place. At least for most special-purpose
11 functionality, this would hopefully provide a more maintainable approach that
12 would also support more easily adding new functionality. Some core features
13 may still need stronger coupling than this provides.
15 The rest of the page documents those parts of the modularity mechanism that
16 have taken a clear form. Generalizing and designing other parts may require
17 more code to be converted to modules to have clearer requirements on what the
18 mechanism needs to support and what is the best way to express that in a
19 generally usable form.
24 Each module implements a factory that returns an instance of gmx::IMDModule.
25 This interface has methods that in turn refer to other interfaces:
26 gmx::IMdpOptionProvider, gmx::IMDOutputProvider, and gmx::IForceProvider.
27 The module also implements these interfaces (or a subset of them), and code
28 outside the module only calls methods in these interfaces.
30 See documentation of the individual interfaces for details of what they
33 Implementation of a module
34 --------------------------
36 Modules are constructed by composition of interfaces (i.e. abstract classes,
37 general with pure virtual methods lacking implementations), so that e.g.
38 trajectory-writing code can loop over containers of pointers to
39 gmx::IMDOutputProvider without needing to know about all the concrete types
40 that might implement that interface.
42 The module classes should not be extended by using them as a base
43 class, which is expressed with the final keyword in the class
44 definition. Generally, modules will implement different flavours of
45 functionality, perhaps based on user choices, or available computing
46 resources. This should generally be implemented by providing variable
47 behavior for the methods that are called through the above
48 interfaces. Either code should branch at run time upon some data
49 contained by the module (e.g. read from the mdp options), or that the
50 module class should contain a pointer to an internal interface class
51 whose concrete type might be chosen during setup from the set of
52 implementations of that internal interface. Such an approach keeps
53 separate the set of interfaces characteristic of "MD modules" from
54 those that are particular to flavours of any specific module.
56 The virtual methods that the module classes inherit from their
57 interfaces should be declared as `override`, to express the intent
58 that they implement a virtual function from the interface. This
59 permits the compiler to check that this is true, e.g. if the interface
60 class changes. The `virtual` keyword should not be specified,
61 because this is redundant when `override` is used. This follows
62 the Cpp Core Guidelines (guideline C.128).
67 To accept parameters from an mdp file, a module needs to implement
68 gmx::IMdpOptionProvider.
70 initMdpOptions() should declare the required input parameters using the options
71 module. In most cases, the parameters should be declared as nested sections
72 instead of a flat set of options. The structure used should be such that in
73 the future, we can get the input values from a structured mdp file (e.g., JSON
74 or XML), where the structure matches the declared options. As with other uses
75 of the options module, the module needs to declare local variables where the
76 values from the options will be assigned. The defined structure will also be
77 used for storing in the tpr file (see below).
79 initMdpTransform() should declare the mapping from current flat mdp format to
80 the structured format defined in initMdpOptions(). For now, this makes it
81 possible to have an internal forward-looking structured representation while
82 the input is still a flat list of values, but in the future it also allows
83 supporting both formats side-by-side as long as that is deemed necessary.
85 On the implementation side, the framework (and other code that interacts with
86 the modules) will do the following things to make mdp input work:
88 * When grompp reads the mdp file, it will first construct a flat
89 KeyValueTreeObject, where each input option is set as a property.
91 It then calls initMdpTransform() for the module(s), and uses the produced
92 transform to convert the flat tree into a structured tree, performing any
93 defined conversions in the process. This transformation is one-way only,
94 although the framework keeps track of the origin of each value to provide
95 sensible error messages that have the original mdp option name included.
97 It calls initMdpOptions() for the module(s), initializing a single Options
98 object that has the input options.
100 It processes the structured tree using the options in two steps:
102 * For any option that is not specified in the input, it adds a property to
103 the tree with a default value. For options specified in the input, the
104 values in the tree are converted to native values for the options (e.g.,
105 from string to int for integer options).
106 * It assigns the values from the tree to the Options object. This will make
107 the values available in the local variables the module defined in
110 Note that currently, the module(s) cannot use storeIsSet() in options to know
111 whether a particular option has been provided from the mdp file. This will
112 always return true for all the options. This is a limitation in the current
113 implementation, but it also, in part, enforces that the mdp file written out
114 by `gmx grompp -po` cannot produce different behavior because of set/not-set
117 * grompp -po writes an mdp file that was equivalent to the input,
118 which is implemented by calling buildMdpOutput() for each module, to
119 prepare a builder object that is used with writeKeyValueTreeAsMdp().
120 As with the old flat tree, the values given by the user's input are
121 preserved, but not the ordering of options, or their formatting.
123 * When grompp writes the tpr file, it writes the structured tree (after the
124 default value and native value conversion) into the tpr file.
126 * When mdrun reads the tpr file, it reads the structured tree.
127 It then broadcasts the structure to all ranks. Each rank calls
128 initMdpOptions() for the modules, and assigns the values from the tree to the
129 Options object. After this, the modules will be exactly in the same state as
132 * When other tools (gmx dump or gmx check in particular) read the tpr file,
133 they read the structured tree. In principle, they could operate directly on
134 this tree (and `gmx dump` in particular does, with the `-orgir` option).
135 However, in the future with proper tpr backward compatibility support, they
136 need to call to the modules to ensure that the tree has the structure that
137 this version expects, instead of what the original version that wrote the
138 file had. Currently, these tools only call initMdpOptions() and do the basic
139 default+native value conversion.
141 * Any code that is not interested in the parameters for these modules can just
142 read the t_inputrec from the tpr file and ignore the tree.
144 * For compatibility with old tpr files that did not yet have the structured
145 tree, the I/O code converts old values for the modules to parameters in the
146 structured tree (in tpxio.cpp).
148 Currently, there is no mechanism for changing the mdp input parameters (adding
149 new or removing old ones) that would maintain tpr and mdp backward
150 compatibility. The vision for this is to use the same transformation engine as
151 for initMdpTransform() to support specifying version-to-version conversions for
152 any changed options, and applying the necessary conversions in sequence. The
153 main challenge is keeping track of the versions to know which conversions to
156 Callbacks to modules during setup and simulation
157 ------------------------------------------------
159 During setup and simulation, modules receive required information like topologies
160 and local atom sets by subscribing to callback functions.
162 To include a notification for your module
164 * Add the function signature for the callback function to the
165 `MdModulesNotifier` in `mdmodulenotification.h`,
168 registerMdModulesNotification<...,
169 YourCallbackSignature,
173 (keep alphabetical order for ease of git merge)
175 * Hand the notifier_ member of the MdModules Implementation class to your
176 builder createYourModule(¬ifier_)
178 * Add the function you want to subscribe with in the builder,
179 `notifier->subscribe(yourFunction)`
181 * To subscribe class member functions of your module, you can use lambda expressions
184 notifier->notifier_.subscribe([modulePointer = yourModule.get()]
185 (YourCallbackSignature argument){modulePointer(argument);});
188 * During setup in , e.g., within `Mdrunner` use
191 YourCallbackSignature argument();
192 mdModules_.notifier().notifier_.notify(argument);
195 Storing non-mdp option module parameters
196 ----------------------------------------
198 Some mdrun modules want to store data that is non-mdp input, e.g., the result of
199 computation during setup. Atom indices of index groups are one example:
200 they are evaluated from strings during grompp time and stored as list of
201 integers in the run input file. During the mdrun setup the information to
202 evaluate the index groups is no longer available.
204 To store parameters, subscribe to the `KeyValueTreeBuilder*` notification that
205 provides a handle to a KeyValueTreeBuilder that allows adding own information to
208 To restore parameters, subscribe to the `const KeyValueTreeObject &`
209 notification that returns the tree that is build by the KeyValueTreeBuilder*.