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