eb559245a4acf553231ce51caa9354120770d6d0
[alexxy/gromacs.git] / src / gromacs / modularsimulator / simulatoralgorithm.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2020,2021, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief Provides the modular simulator algorithm.
37  *
38  * Defines the ModularSimulatorAlgorithm class and its builder.
39  *
40  * \author Pascal Merz <pascal.merz@me.com>
41  * \ingroup module_modularsimulator
42  *
43  * This header is only used within the modular simulator module.
44  * Moving forward, the ModularSimulatorAlgorithmBuilder could be exposed to allow users to
45  * create custom algorithm - currently algorithms are only created an used by the ModularSimulator,
46  * meaning that this file is not exposed outside of the modular simulator module.
47  */
48 #ifndef GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H
49 #define GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H
50
51 #include <any>
52 #include <map>
53 #include <optional>
54 #include <string>
55 #include <typeinfo>
56
57 #include "gromacs/mdrun/isimulator.h"
58 #include "gromacs/mdtypes/state.h"
59 #include "gromacs/utility/exceptions.h"
60
61 #include "checkpointhelper.h"
62 #include "domdechelper.h"
63 #include "freeenergyperturbationdata.h"
64 #include "modularsimulatorinterfaces.h"
65 #include "pmeloadbalancehelper.h"
66 #include "signallers.h"
67 #include "topologyholder.h"
68 #include "trajectoryelement.h"
69
70 namespace gmx
71 {
72 enum class IntegrationStage;
73 class EnergyData;
74 class ModularSimulator;
75 class ResetHandler;
76 template<IntegrationStage integrationStage>
77 class Propagator;
78 class TopologyHolder;
79
80 /*! \internal
81  * \ingroup module_modularsimulator
82  * \brief The modular simulator
83  *
84  * Based on the input given, this simulator builds independent elements and
85  * signallers and stores them in a respective vector. The run function
86  * runs the simulation by, in turn, building a task list from the elements
87  * for a predefined number of steps, then running the task list, and repeating
88  * until the stop criterion is fulfilled.
89  *
90  * The simulator algorithm is owning all elements involved in the simulation
91  * and is hence controlling their lifetime. This ensures that pointers and
92  * callbacks exchanged between elements remain valid throughout the duration
93  * of the simulation run.
94  */
95 class ModularSimulatorAlgorithm final
96 {
97 public:
98     //! Get next task in queue
99     [[nodiscard]] const SimulatorRunFunction* getNextTask();
100
101     // Only builder can construct
102     friend class ModularSimulatorAlgorithmBuilder;
103
104 private:
105     //! Constructor
106     ModularSimulatorAlgorithm(std::string              topologyName,
107                               FILE*                    fplog,
108                               t_commrec*               cr,
109                               const MDLogger&          mdlog,
110                               const MdrunOptions&      mdrunOptions,
111                               const t_inputrec*        inputrec,
112                               t_nrnb*                  nrnb,
113                               gmx_wallcycle*           wcycle,
114                               t_forcerec*              fr,
115                               gmx_walltime_accounting* walltime_accounting);
116
117     //! Calls setup for the simulator and all elements and signallers
118     void setup();
119     //! Calls teardown for the simulator and all elements
120     void teardown();
121     //! Re-populate task queue
122     void updateTaskQueue();
123
124     /*! \brief A function called once before the simulation run
125      *
126      * This function collects calls which need to be made once at the
127      * beginning of the simulation run. These are mainly printing information
128      * to the user and starting the wall time.
129      */
130     void simulatorSetup();
131
132     /*! \brief A function called once after the simulation run
133      *
134      * This function collects calls which need to be made once at the
135      * end of the simulation run.
136      */
137     void simulatorTeardown();
138
139     /*! \brief A function called before every step
140      *
141      * This function collects calls which need to be made before every
142      * simulation step. The need for this function could likely be
143      * eliminated by rewriting the stop and reset handler to fit the
144      * modular simulator approach.
145      */
146     void preStep(Step step, Time time, bool isNeighborSearchingStep);
147
148     /*! \brief A function called after every step
149      *
150      * This function collects calls which need to be made after every
151      * simulation step.
152      */
153     void postStep(Step step, Time time);
154
155     /*! \brief Add run functions to the task queue
156      *
157      * This function calls PME load balancing and domain decomposition first,
158      * and then queries the elements for all steps of the life time of the
159      * current neighbor list for their run functions. When the next step
160      * would be a neighbor-searching step, this function returns, such that
161      * the simulator can run the pre-computed task list before updating the
162      * neighbor list and re-filling the task list.
163      *
164      * TODO: In the current approach, unique pointers to tasks are created
165      *       repeatedly. While preliminary measurements do not seem to indicate
166      *       performance problems, a pool allocator would be an obvious
167      *       optimization possibility, as would reusing the task queue if
168      *       the task queue is periodic around the rebuild point (i.e. the
169      *       task queue is identical between rebuilds).
170      */
171     void populateTaskQueue();
172
173     //! The run queue
174     std::vector<SimulatorRunFunction> taskQueue_;
175     //! The task iterator
176     std::vector<SimulatorRunFunction>::const_iterator taskIterator_;
177
178     /* Note that the Simulator is owning the signallers and elements.
179      * The ownership list and the call list of the elements are kept
180      * separate, to allow to have elements more than once in the call
181      * lists - for example, using velocity verlet, the compute globals
182      * element needs to be scheduled more than once per step. For the
183      * signallers, no distinction between ownership and call list is
184      * made, all signallers are called exactly once per scheduling step.
185      *
186      * Objects being both a signaller and an element are not supported.
187      */
188     //! List of signalers (ownership and calling sequence)
189     std::vector<std::unique_ptr<ISignaller>> signallerList_;
190     //! List of schedulerElements (ownership)
191     std::vector<std::unique_ptr<ISimulatorElement>> elementsOwnershipList_;
192     //! List of schedulerElements (run calling sequence)
193     std::vector<ISimulatorElement*> elementCallList_;
194     //! List of schedulerElements (setup / teardown calling sequence)
195     std::vector<ISimulatorElement*> elementSetupTeardownList_;
196     //! List of pre-step scheduling functions
197     std::vector<SchedulingFunction> preStepScheduling_;
198     //! List of post-step scheduling functions
199     std::vector<SchedulingFunction> postStepScheduling_;
200
201     // Infrastructure elements
202     //! The domain decomposition element
203     std::unique_ptr<DomDecHelper> domDecHelper_;
204     //! The PME load balancing element
205     std::unique_ptr<PmeLoadBalanceHelper> pmeLoadBalanceHelper_;
206     //! The checkpoint helper
207     std::unique_ptr<CheckpointHelper> checkpointHelper_;
208     //! The stop handler
209     std::unique_ptr<StopHandler> stopHandler_;
210     //! The reset handler
211     std::unique_ptr<ResetHandler> resetHandler_;
212     //! Signal vector (used by stop / reset / checkpointing signaller)
213     std::unique_ptr<SimulationSignals> signals_;
214     //! The topology
215     std::unique_ptr<TopologyHolder> topologyHolder_;
216
217     // Data structures
218     //! The state propagator data
219     std::unique_ptr<StatePropagatorData> statePropagatorData_;
220     //! The energy data
221     std::unique_ptr<EnergyData> energyData_;
222     //! The free energy data
223     std::unique_ptr<FreeEnergyPerturbationData> freeEnergyPerturbationData_;
224     //! Arbitrary data with lifetime equal to the simulation (used to share data between elements)
225     std::map<std::string, std::unique_ptr<std::any>> simulationData_;
226
227     //! The current step
228     Step step_;
229     //! Whether the simulation run is finished
230     bool runFinished_;
231
232     /*! \internal
233      * \brief Signal helper
234      *
235      * The simulator needs to know about the last step and the
236      * neighbor searching step, which are determined in signallers.
237      * This object can be registered to the signals and accessed by
238      * the methods of the simulator.
239      */
240     class SignalHelper : public ILastStepSignallerClient, public INeighborSearchSignallerClient
241     {
242     public:
243         //! The last step
244         Step lastStep_ = std::numeric_limits<Step>::max();
245         //! The next NS step
246         Step nextNSStep_ = -1;
247         //! ILastStepSignallerClient implementation
248         std::optional<SignallerCallback> registerLastStepCallback() override;
249         //! INeighborSearchSignallerClient implementation
250         std::optional<SignallerCallback> registerNSCallback() override;
251     };
252     //! The signal helper object
253     std::unique_ptr<SignalHelper> signalHelper_;
254
255     // TODO: This is a hack for stop handler - needs to go once StopHandler
256     //       is adapted to the modular simulator
257     //! Whether this is a neighbor-searching step
258     bool stophandlerIsNSStep_ = false;
259     //! The current step
260     Step stophandlerCurrentStep_ = -1;
261
262     // ISimulator data - used for setup, teardown, pre step and post step
263     //! Name of the topology
264     std::string topologyName_;
265     //! Handles logging.
266     FILE* fplog;
267     //! Handles communication.
268     t_commrec* cr;
269     //! Handles logging.
270     const MDLogger& mdlog;
271     //! Contains command-line options to mdrun.
272     const MdrunOptions& mdrunOptions;
273     //! Contains user input mdp options.
274     const t_inputrec* inputrec;
275     //! Manages flop accounting.
276     t_nrnb* nrnb;
277     //! Manages wall cycle accounting.
278     gmx_wallcycle* wcycle;
279     //! Parameters for force calculations.
280     t_forcerec* fr;
281     //! Manages wall time accounting.
282     gmx_walltime_accounting* walltime_accounting;
283 };
284
285 /*! \internal
286  * \brief Helper container with data connected to global communication
287  *
288  * This includes data that needs to be shared between elements involved in
289  * global communication. This will become obsolete as soon as global
290  * communication is moved to a client system (#3421).
291  */
292 class GlobalCommunicationHelper
293 {
294 public:
295     //! Constructor
296     GlobalCommunicationHelper(int nstglobalcomm, SimulationSignals* simulationSignals);
297
298     //! Get the compute globals communication period
299     [[nodiscard]] int nstglobalcomm() const;
300     //! Get a pointer to the signals vector
301     [[nodiscard]] SimulationSignals* simulationSignals();
302
303 private:
304     //! Compute globals communication period
305     const int nstglobalcomm_;
306     //! Signal vector (used by stop / reset / checkpointing signaller)
307     SimulationSignals* simulationSignals_;
308 };
309
310 class ModularSimulatorAlgorithmBuilder;
311
312 /*! \internal
313  * \brief Helper for element addition
314  *
315  * Such an object will be given to each invocation of getElementPointer
316  *
317  * Note: It would be nicer to define this as a member type of
318  * ModularSimulatorAlgorithmBuilder, but this would break forward declaration.
319  * This object is therefore defined as friend class.
320  */
321 class ModularSimulatorAlgorithmBuilderHelper
322 {
323 public:
324     //! Constructor
325     ModularSimulatorAlgorithmBuilderHelper(ModularSimulatorAlgorithmBuilder* builder);
326     //! Store an element to the ModularSimulatorAlgorithmBuilder
327     template<typename Element>
328     Element* storeElement(std::unique_ptr<Element> element);
329     //! Check if an element is stored in the ModularSimulatorAlgorithmBuilder
330     bool elementIsStored(const ISimulatorElement* element) const;
331     /*! \brief Register callback to schedule a pre-step run
332      *
333      * This allows elements to schedule a function call before the integration step.
334      * The function call is guaranteed to happen before any functions scheduled for
335      * the integration step. It is not guaranteed to happen in any specific order
336      * compared to other elements registering a pre-step scheduling function.
337      */
338     void registerPreStepScheduling(SchedulingFunction schedulingFunction);
339     /*! \brief Register callback to schedule a post-step run
340      *
341      * This allows elements to schedule a function call after the integration step.
342      * The function call is guaranteed to happen after all functions scheduled for
343      * the integration step. It is not guaranteed to happen in any specific order
344      * compared to other elements registering a post-step scheduling function.
345      */
346     void registerPostStepScheduling(SchedulingFunction schedulingFunction);
347     /*! \brief Set arbitrary data in the ModularSimulatorAlgorithmBuilder
348      *
349      * Allows to store arbitrary data with lifetime equal to the builder. Functionality is used
350      * by stateful static builder functions.
351      */
352     template<typename ValueType>
353     void storeBuilderData(const std::string& key, const ValueType& value);
354     //! Get previously stored builder data. Returns std::nullopt if key is not found.
355     std::optional<std::any> builderData(const std::string& key) const;
356     //! \copydoc ModularSimulatorAlgorithmBuilder::storeSimulationData()
357     template<typename ValueType>
358     void storeSimulationData(const std::string& key, ValueType&& value);
359     //! \copydoc ModularSimulatorAlgorithmBuilder::simulationData()
360     template<typename ValueType>
361     std::optional<ValueType*> simulationData(const std::string& key);
362     //! Register temperature / pressure control algorithm to be matched with a propagator
363     void registerTemperaturePressureControl(std::function<void(const PropagatorConnection&)> registrationFunction);
364     //! Register a propagator to be used with a temperature / pressure control algorithm
365     void registerPropagator(PropagatorConnection connectionData);
366     //! Register for callback after an update to the reference temperature
367     void registerReferenceTemperatureUpdate(ReferenceTemperatureCallback referenceTemperatureCallback);
368     //! Get a callback to change reference temperature
369     ReferenceTemperatureCallback changeReferenceTemperatureCallback();
370
371 private:
372     //! Pointer to the associated ModularSimulatorAlgorithmBuilder
373     ModularSimulatorAlgorithmBuilder* builder_;
374 };
375
376 /*!\internal
377  * \brief Builder for ModularSimulatorAlgorithm objects
378  *
379  * This builds a ModularSimulatorAlgorithm.
380  *
381  * Users can add elements and define their call order by calling the templated
382  * add<Element> function. Note that only elements that have a static
383  * getElementPointerImpl factory method can be built in that way.
384  *
385  * Note that each ModularSimulatorAlgorithmBuilder can only be used to build
386  * one ModularSimulatorAlgorithm object, i.e. build() can only be called once.
387  * During the call to build, all elements and other infrastructure objects will
388  * be moved to the built ModularSimulatorAlgorithm object, such that further use
389  * of the builder would not make sense.
390  * Any access to the build or add<> methods after the first call to
391  * build() will result in an exception being thrown.
392  */
393 class ModularSimulatorAlgorithmBuilder final
394 {
395 public:
396     //! Constructor
397     ModularSimulatorAlgorithmBuilder(compat::not_null<LegacySimulatorData*>    legacySimulatorData,
398                                      std::unique_ptr<ReadCheckpointDataHolder> checkpointDataHolder);
399     //! Build algorithm
400     ModularSimulatorAlgorithm build();
401
402     /*! \brief  Add element to the modular simulator algorithm builder
403      *
404      * This function has a general implementation, which will call the getElementPointer(...)
405      * factory function.
406      *
407      * \tparam Element  The element type
408      * \tparam Args     A variable number of argument types
409      * \param args      A variable number of arguments
410      */
411     template<typename Element, typename... Args>
412     void add(Args&&... args);
413
414     //! Allow access from helper
415     friend class ModularSimulatorAlgorithmBuilderHelper;
416
417 private:
418     //! The state of the builder
419     bool algorithmHasBeenBuilt_ = false;
420
421     // Data structures
422     //! The state propagator data
423     std::unique_ptr<StatePropagatorData> statePropagatorData_;
424     //! The energy data
425     std::unique_ptr<EnergyData> energyData_;
426     //! The free energy data
427     std::unique_ptr<FreeEnergyPerturbationData> freeEnergyPerturbationData_;
428     //! Arbitrary data with lifetime equal to the builder (used by stateful static builder functions)
429     std::map<std::string, std::any> builderData_;
430     //! Arbitrary data with lifetime equal to the simulation (used to share data between elements)
431     std::map<std::string, std::unique_ptr<std::any>> simulationData_;
432
433     //! Pointer to the LegacySimulatorData object
434     compat::not_null<LegacySimulatorData*> legacySimulatorData_;
435
436     // Helper objects
437     //! Signal vector (used by stop / reset / checkpointing signaller)
438     std::unique_ptr<SimulationSignals> signals_;
439     //! Helper object passed to element factory functions
440     ModularSimulatorAlgorithmBuilderHelper elementAdditionHelper_;
441     //! Container for global computation data
442     GlobalCommunicationHelper globalCommunicationHelper_;
443
444     /*! \brief Set arbitrary data in the ModularSimulatorAlgorithm
445      *
446      * Allows to store arbitrary data with lifetime equal to the simulator algorithm.
447      * Functionality allows elements to share arbitrary data.
448      */
449     template<typename ValueType>
450     void storeSimulationData(const std::string& key, ValueType&& value);
451
452     /*! \brief Get previously stored simulation data.
453      *
454      * Returns std::nullopt if key is not found.
455      */
456     template<typename ValueType>
457     std::optional<ValueType*> simulationData(const std::string& key);
458
459     /*! \brief  Register an element to all applicable signallers and infrastructure elements
460      *
461      * \tparam Element  Type of the Element
462      * \param element   Pointer to the element
463      */
464     template<typename Element>
465     void registerWithInfrastructureAndSignallers(Element* element);
466
467     /*! \brief Take ownership of element
468      *
469      * This function returns a non-owning pointer to the new location of that
470      * element, allowing further usage (e.g. adding the element to the call list).
471      * This will also add the element to the setup / teardown list, and register
472      * it with all applicable signallers and infrastructure objects.
473      * Note that simply adding an element using this function will not call it
474      * during the simulation - it needs to be added to the call list separately.
475      * Also note that generally, users will want to add elements to the call list,
476      * but it might not be practical to do this in the same order.
477      *
478      * \tparam Element  Type of the Element
479      * \param element  A unique pointer to the element
480      * \return  A non-owning (raw) pointer to the element for further usage
481      */
482     template<typename Element>
483     Element* addElementToSimulatorAlgorithm(std::unique_ptr<Element> element);
484
485     /*! \brief Register existing element to infrastructure
486      *
487      * This function adds existing elements to the setup / teardown list, and
488      * registers them with all applicable signallers and infrastructure objects.
489      * This is only permissible for elements owned directly by the builder or
490      * indirectly through data objects. Before registering the element, the function
491      * checks that the element is owned by the builder or a known object.
492      *
493      * \tparam Element  Type of the Element
494      * \param element   A non-owning (raw) pointer to the element
495      */
496     template<typename Element>
497     void registerExistingElement(Element* element);
498
499     /*! \brief Check if element is owned by *this
500      *
501      * \param element  Pointer to the element
502      * \return  Bool indicating whether element is owned by *this
503      */
504     [[nodiscard]] bool elementExists(const ISimulatorElement* element) const;
505
506     //! Vector to store elements, allowing the SimulatorAlgorithm to control their lifetime
507     std::vector<std::unique_ptr<ISimulatorElement>> elements_;
508     /*! \brief List defining in which order elements are called every step
509      *
510      * Elements may be referenced more than once if they should be called repeatedly
511      */
512     std::vector<ISimulatorElement*> callList_;
513     /*! \brief  List defining in which order elements are set up and torn down
514      *
515      * Elements should only appear once in this list
516      */
517     std::vector<ISimulatorElement*> setupAndTeardownList_;
518     //! List of pre-step scheduling functions
519     std::vector<SchedulingFunction> preStepScheduling_;
520     //! List of post-step scheduling functions
521     std::vector<SchedulingFunction> postStepScheduling_;
522
523     //! Builder for the NeighborSearchSignaller
524     SignallerBuilder<NeighborSearchSignaller> neighborSearchSignallerBuilder_;
525     //! Builder for the LastStepSignaller
526     SignallerBuilder<LastStepSignaller> lastStepSignallerBuilder_;
527     //! Builder for the LoggingSignaller
528     SignallerBuilder<LoggingSignaller> loggingSignallerBuilder_;
529     //! Builder for the EnergySignaller
530     SignallerBuilder<EnergySignaller> energySignallerBuilder_;
531     //! Builder for the TrajectorySignaller
532     SignallerBuilder<TrajectorySignaller> trajectorySignallerBuilder_;
533     //! Builder for the TrajectoryElementBuilder
534     TrajectoryElementBuilder trajectoryElementBuilder_;
535     //! Builder for the TopologyHolder
536     TopologyHolder::Builder topologyHolderBuilder_;
537     //! Builder for the CheckpointHelper
538     CheckpointHelperBuilder checkpointHelperBuilder_;
539     //! Builder for the DomDecHelper
540     DomDecHelperBuilder domDecHelperBuilder_;
541
542     /*! \brief List of clients for the CheckpointHelper
543      *
544      * \todo Replace this by proper builder (#3422)
545      */
546     std::vector<ICheckpointHelperClient*> checkpointClients_;
547
548     //! List of data to connect propagators to thermostats / barostats
549     std::vector<PropagatorConnection> propagatorConnections_;
550     //! List of temperature / pressure control registration functions
551     std::vector<std::function<void(const PropagatorConnection&)>> pressureTemperatureControlRegistrationFunctions_;
552 };
553
554 /*! \internal
555  * \brief Factory function for elements that can be added via ModularSimulatorAlgorithmBuilder:
556  *        Get a pointer to an object of type \c Element to add to the call list
557  *
558  * This allows elements to be built via the templated ModularSimulatorAlgorithmBuilder::add<Element>
559  * method. Elements buildable throught this factor function are required to implement a static
560  * function with minimal signature
561  *
562  *     static ISimulatorElement* getElementPointerImpl(
563  *             LegacySimulatorData*                    legacySimulatorData,
564  *             ModularSimulatorAlgorithmBuilderHelper* builderHelper,
565  *             StatePropagatorData*                    statePropagatorData,
566  *             EnergyData*                             energyData,
567  *             FreeEnergyPerturbationData*             freeEnergyPerturbationData,
568  *             GlobalCommunicationHelper*              globalCommunicationHelper)
569  *
570  * This function may also accept additional parameters which are passed using the variadic
571  * template parameter pack forwarded in getElementPointer.
572  *
573  * This function returns a pointer to an object of the Element type. Note that the caller will
574  * check whether the returned object has previously been stored using the `storeElement`
575  * function, and throw an exception if the element is not found.
576  * The function can check whether a previously stored pointer is valid using
577  * the `checkElementExistence` function. Most implementing functions will simply want
578  * to create an object, store it using `storeElement`, and then use the return value of
579  * `storeElement` as a return value to the caller. However, this setup allows the function
580  * to store a created element (using a static pointer inside the function) and return it
581  * in case that the factory function is called repeatedly. This allows to create an element
582  * once, but have it called multiple times during the simulation run.
583  *
584  * \see ModularSimulatorAlgorithmBuilder::add
585  *      Function using this functionality
586  * \see ComputeGlobalsElement<ComputeGlobalsAlgorithm::VelocityVerlet>::getElementPointerImpl
587  *      Implementation using the single object / multiple call sites functionality
588  *
589  * \tparam Element The type of the element
590  * \tparam Args  Variable number of argument types allowing specific implementations to have
591  *               additional arguments
592  *
593  * \param legacySimulatorData  Pointer allowing access to simulator level data
594  * \param builderHelper  ModularSimulatorAlgorithmBuilder helper object
595  * \param statePropagatorData  Pointer to the \c StatePropagatorData object
596  * \param energyData  Pointer to the \c EnergyData object
597  * \param freeEnergyPerturbationData  Pointer to the \c FreeEnergyPerturbationData object
598  * \param globalCommunicationHelper  Pointer to the \c GlobalCommunicationHelper object
599  * \param args  Variable number of additional parameters to be forwarded
600  *
601  * \return  Pointer to the element to be added. Element needs to have been stored using \c storeElement
602  */
603 template<typename Element, typename... Args>
604 ISimulatorElement* getElementPointer(LegacySimulatorData*                    legacySimulatorData,
605                                      ModularSimulatorAlgorithmBuilderHelper* builderHelper,
606                                      StatePropagatorData*                    statePropagatorData,
607                                      EnergyData*                             energyData,
608                                      FreeEnergyPerturbationData* freeEnergyPerturbationData,
609                                      GlobalCommunicationHelper*  globalCommunicationHelper,
610                                      Args&&... args)
611 {
612     return Element::getElementPointerImpl(legacySimulatorData,
613                                           builderHelper,
614                                           statePropagatorData,
615                                           energyData,
616                                           freeEnergyPerturbationData,
617                                           globalCommunicationHelper,
618                                           std::forward<Args>(args)...);
619 }
620
621 template<typename Element, typename... Args>
622 void ModularSimulatorAlgorithmBuilder::add(Args&&... args)
623 {
624     if (algorithmHasBeenBuilt_)
625     {
626         GMX_THROW(SimulationAlgorithmSetupError(
627                 "Tried to add an element after ModularSimulationAlgorithm was built."));
628     }
629
630     // Get element from factory method
631     auto* element = static_cast<Element*>(getElementPointer<Element>(legacySimulatorData_,
632                                                                      &elementAdditionHelper_,
633                                                                      statePropagatorData_.get(),
634                                                                      energyData_.get(),
635                                                                      freeEnergyPerturbationData_.get(),
636                                                                      &globalCommunicationHelper_,
637                                                                      std::forward<Args>(args)...));
638
639     // Make sure returned element pointer is owned by *this
640     // Ensuring this makes sure we can control the life time
641     if (!elementExists(element))
642     {
643         GMX_THROW(ElementNotFoundError("Tried to append non-existing element to call list."));
644     }
645     // Add to call list
646     callList_.emplace_back(element);
647 }
648
649 //! Returns a pointer casted to type Base if the Element is derived from Base
650 template<typename Base, typename Element>
651 static std::enable_if_t<std::is_base_of<Base, Element>::value, Base*> castOrNull(Element* element)
652 {
653     return static_cast<Base*>(element);
654 }
655
656 //! Returns a nullptr of type Base if Element is not derived from Base
657 template<typename Base, typename Element>
658 static std::enable_if_t<!std::is_base_of<Base, Element>::value, Base*> castOrNull(Element gmx_unused* element)
659 {
660     return nullptr;
661 }
662
663 template<typename Element>
664 void ModularSimulatorAlgorithmBuilder::registerWithInfrastructureAndSignallers(Element* element)
665 {
666     // Register element to all applicable signallers
667     neighborSearchSignallerBuilder_.registerSignallerClient(
668             castOrNull<INeighborSearchSignallerClient, Element>(element));
669     lastStepSignallerBuilder_.registerSignallerClient(castOrNull<ILastStepSignallerClient, Element>(element));
670     loggingSignallerBuilder_.registerSignallerClient(castOrNull<ILoggingSignallerClient, Element>(element));
671     energySignallerBuilder_.registerSignallerClient(castOrNull<IEnergySignallerClient, Element>(element));
672     trajectorySignallerBuilder_.registerSignallerClient(
673             castOrNull<ITrajectorySignallerClient, Element>(element));
674     // Register element to trajectory element (if applicable)
675     trajectoryElementBuilder_.registerWriterClient(castOrNull<ITrajectoryWriterClient, Element>(element));
676     // Register element to topology holder (if applicable)
677     topologyHolderBuilder_.registerClient(castOrNull<ITopologyHolderClient, Element>(element));
678     // Register element to checkpoint client (if applicable)
679     checkpointHelperBuilder_.registerClient(castOrNull<ICheckpointHelperClient, Element>(element));
680     // Register element to DomDecHelper builder (if applicable)
681     domDecHelperBuilder_.registerClient(castOrNull<IDomDecHelperClient, Element>(element));
682 }
683
684 template<typename Element>
685 Element* ModularSimulatorAlgorithmBuilder::addElementToSimulatorAlgorithm(std::unique_ptr<Element> element)
686 {
687     // Store element
688     elements_.emplace_back(std::move(element));
689     // Get non-owning pointer for further use
690     Element* elementPtr = static_cast<Element*>(elements_.back().get());
691     // Register element to infrastructure
692     registerExistingElement(elementPtr);
693
694     return elementPtr;
695 }
696
697 template<typename Element>
698 void ModularSimulatorAlgorithmBuilder::registerExistingElement(Element* element)
699 {
700     // Make sure the element pointer is owned by *this
701     // Ensuring this makes sure we can control the life time
702     if (!elementExists(element))
703     {
704         GMX_THROW(
705                 ElementNotFoundError("Tried to register non-existing element to infrastructure."));
706     }
707
708     // Add to setup / teardown list
709     setupAndTeardownList_.emplace_back(element);
710     // Register element to all applicable signallers
711     registerWithInfrastructureAndSignallers(element);
712 }
713
714 template<typename Element>
715 Element* ModularSimulatorAlgorithmBuilderHelper::storeElement(std::unique_ptr<Element> element)
716 {
717     return builder_->addElementToSimulatorAlgorithm(std::move(element));
718 }
719
720 template<typename ValueType>
721 void ModularSimulatorAlgorithmBuilderHelper::storeBuilderData(const std::string& key, const ValueType& value)
722 {
723     builder_->builderData_[key] = std::any(value);
724 }
725
726 template<typename ValueType>
727 void ModularSimulatorAlgorithmBuilderHelper::storeSimulationData(const std::string& key, ValueType&& value)
728 {
729     builder_->storeSimulationData(key, std::forward<ValueType>(value));
730 }
731
732 template<typename ValueType>
733 std::optional<ValueType*> ModularSimulatorAlgorithmBuilderHelper::simulationData(const std::string& key)
734 {
735     return builder_->simulationData<ValueType>(key);
736 }
737
738 template<typename ValueType>
739 void ModularSimulatorAlgorithmBuilder::storeSimulationData(const std::string& key, ValueType&& value)
740 {
741     GMX_RELEASE_ASSERT(simulationData_.count(key) == 0,
742                        formatString("Key %s was already stored in simulation data.", key.c_str()).c_str());
743     simulationData_[key] = std::make_unique<std::any>(std::forward<ValueType>(value));
744     auto* ptrToData      = simulationData<ValueType>(key).value();
745     registerWithInfrastructureAndSignallers(ptrToData);
746 }
747
748 template<typename ValueType>
749 std::optional<ValueType*> ModularSimulatorAlgorithmBuilder::simulationData(const std::string& key)
750 {
751     const auto iter = simulationData_.find(key);
752     if (iter == simulationData_.end())
753     {
754         return std::nullopt;
755     }
756     ValueType* data = std::any_cast<ValueType>(iter->second.get());
757     GMX_RELEASE_ASSERT(data != nullptr,
758                        formatString("Object stored in simulation data under key %s does not have "
759                                     "the expected type.",
760                                     key.c_str())
761                                .c_str());
762     return data;
763 }
764
765
766 } // namespace gmx
767
768 #endif // GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H