2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
36 * \brief Provides the modular simulator algorithm.
38 * Defines the ModularSimulatorAlgorithm class and its builder.
40 * \author Pascal Merz <pascal.merz@me.com>
41 * \ingroup module_modularsimulator
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.
48 #ifndef GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H
49 #define GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H
57 #include "gromacs/mdrun/isimulator.h"
58 #include "gromacs/mdtypes/observablesreducer.h"
59 #include "gromacs/mdtypes/state.h"
60 #include "gromacs/utility/exceptions.h"
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"
73 enum class IntegrationStage;
75 class ModularSimulator;
77 template<IntegrationStage integrationStage>
82 * \ingroup module_modularsimulator
83 * \brief The modular simulator
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.
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.
96 class ModularSimulatorAlgorithm final
99 //! Get next task in queue
100 [[nodiscard]] const SimulatorRunFunction* getNextTask();
102 // Only builder can construct
103 friend class ModularSimulatorAlgorithmBuilder;
107 ModularSimulatorAlgorithm(std::string topologyName,
110 const MDLogger& mdlog,
111 const MdrunOptions& mdrunOptions,
112 const t_inputrec* inputrec,
114 gmx_wallcycle* wcycle,
116 gmx_walltime_accounting* walltime_accounting);
118 //! Calls setup for the simulator and all elements and signallers
120 //! Calls teardown for the simulator and all elements
122 //! Re-populate task queue
123 void updateTaskQueue();
125 /*! \brief A function called once before the simulation run
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.
131 void simulatorSetup();
133 /*! \brief A function called once after the simulation run
135 * This function collects calls which need to be made once at the
136 * end of the simulation run.
138 void simulatorTeardown();
140 /*! \brief A function called before every step
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.
147 void preStep(Step step, Time time, bool isNeighborSearchingStep);
149 /*! \brief A function called after every step
151 * This function collects calls which need to be made after every
154 void postStep(Step step, Time time);
156 /*! \brief Add run functions to the task queue
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.
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).
172 void populateTaskQueue();
175 std::vector<SimulatorRunFunction> taskQueue_;
176 //! The task iterator
177 std::vector<SimulatorRunFunction>::const_iterator taskIterator_;
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.
187 * Objects being both a signaller and an element are not supported.
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_;
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_;
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_;
216 std::unique_ptr<TopologyHolder> topologyHolder_;
219 //! The state propagator data
220 std::unique_ptr<StatePropagatorData> statePropagatorData_;
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_;
230 //! Whether the simulation run is finished
234 * \brief Signal helper
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.
241 class SignalHelper : public ILastStepSignallerClient, public INeighborSearchSignallerClient
245 Step lastStep_ = std::numeric_limits<Step>::max();
247 Step nextNSStep_ = -1;
248 //! ILastStepSignallerClient implementation
249 std::optional<SignallerCallback> registerLastStepCallback() override;
250 //! INeighborSearchSignallerClient implementation
251 std::optional<SignallerCallback> registerNSCallback() override;
253 //! The signal helper object
254 std::unique_ptr<SignalHelper> signalHelper_;
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;
261 Step stophandlerCurrentStep_ = -1;
263 // ISimulator data - used for setup, teardown, pre step and post step
264 //! Name of the topology
265 std::string topologyName_;
268 //! Handles communication.
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.
278 //! Manages wall cycle accounting.
279 gmx_wallcycle* wcycle;
280 //! Parameters for force calculations.
282 //! Manages wall time accounting.
283 gmx_walltime_accounting* walltime_accounting;
287 * \brief Helper container with data connected to global communication
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).
293 class GlobalCommunicationHelper
297 GlobalCommunicationHelper(int nstglobalcomm, SimulationSignals* simulationSignals);
299 //! Get the compute globals communication period
300 [[nodiscard]] int nstglobalcomm() const;
301 //! Get a pointer to the signals vector
302 [[nodiscard]] SimulationSignals* simulationSignals();
305 //! Compute globals communication period
306 const int nstglobalcomm_;
307 //! Signal vector (used by stop / reset / checkpointing signaller)
308 SimulationSignals* simulationSignals_;
311 class ModularSimulatorAlgorithmBuilder;
314 * \brief Helper for element addition
316 * Such an object will be given to each invocation of getElementPointer
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.
322 class ModularSimulatorAlgorithmBuilderHelper
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
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.
339 void registerPreStepScheduling(SchedulingFunction schedulingFunction);
340 /*! \brief Register callback to schedule a post-step run
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.
347 void registerPostStepScheduling(SchedulingFunction schedulingFunction);
348 /*! \brief Set arbitrary data in the ModularSimulatorAlgorithmBuilder
350 * Allows to store arbitrary data with lifetime equal to the builder. Functionality is used
351 * by stateful static builder functions.
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();
373 //! Pointer to the associated ModularSimulatorAlgorithmBuilder
374 ModularSimulatorAlgorithmBuilder* builder_;
378 * \brief Builder for ModularSimulatorAlgorithm objects
380 * This builds a ModularSimulatorAlgorithm.
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.
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.
394 class ModularSimulatorAlgorithmBuilder final
398 ModularSimulatorAlgorithmBuilder(compat::not_null<LegacySimulatorData*> legacySimulatorData,
399 std::unique_ptr<ReadCheckpointDataHolder> checkpointDataHolder);
401 ModularSimulatorAlgorithm build();
403 /*! \brief Add element to the modular simulator algorithm builder
405 * This function has a general implementation, which will call the getElementPointer(...)
408 * \tparam Element The element type
409 * \tparam Args A variable number of argument types
410 * \param args A variable number of arguments
412 template<typename Element, typename... Args>
413 void add(Args&&... args);
415 //! Allow access from helper
416 friend class ModularSimulatorAlgorithmBuilderHelper;
419 //! The state of the builder
420 bool algorithmHasBeenBuilt_ = false;
423 //! The state propagator data
424 std::unique_ptr<StatePropagatorData> statePropagatorData_;
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_;
434 //! Pointer to the LegacySimulatorData object
435 compat::not_null<LegacySimulatorData*> legacySimulatorData_;
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_;
447 /*! \brief Set arbitrary data in the ModularSimulatorAlgorithm
449 * Allows to store arbitrary data with lifetime equal to the simulator algorithm.
450 * Functionality allows elements to share arbitrary data.
452 template<typename ValueType>
453 void storeSimulationData(const std::string& key, ValueType&& value);
455 /*! \brief Get previously stored simulation data.
457 * Returns std::nullopt if key is not found.
459 template<typename ValueType>
460 std::optional<ValueType*> simulationData(const std::string& key);
462 /*! \brief Register an element to all applicable signallers and infrastructure elements
464 * \tparam Element Type of the Element
465 * \param element Pointer to the element
467 template<typename Element>
468 void registerWithInfrastructureAndSignallers(Element* element);
470 /*! \brief Take ownership of element
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.
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
485 template<typename Element>
486 Element* addElementToSimulatorAlgorithm(std::unique_ptr<Element> element);
488 /*! \brief Register existing element to infrastructure
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.
496 * \tparam Element Type of the Element
497 * \param element A non-owning (raw) pointer to the element
499 template<typename Element>
500 void registerExistingElement(Element* element);
502 /*! \brief Check if element is owned by *this
504 * \param element Pointer to the element
505 * \return Bool indicating whether element is owned by *this
507 [[nodiscard]] bool elementExists(const ISimulatorElement* element) const;
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
513 * Elements may be referenced more than once if they should be called repeatedly
515 std::vector<ISimulatorElement*> callList_;
516 /*! \brief List defining in which order elements are set up and torn down
518 * Elements should only appear once in this list
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_;
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_;
545 /*! \brief List of clients for the CheckpointHelper
547 * \todo Replace this by proper builder (#3422)
549 std::vector<ICheckpointHelperClient*> checkpointClients_;
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_;
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
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
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)
574 * This function may also accept additional parameters which are passed using the variadic
575 * template parameter pack forwarded in getElementPointer.
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.
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
593 * \tparam Element The type of the element
594 * \tparam Args Variable number of argument types allowing specific implementations to have
595 * additional arguments
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
606 * \return Pointer to the element to be added. Element needs to have been stored using \c storeElement
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,
618 return Element::getElementPointerImpl(legacySimulatorData,
622 freeEnergyPerturbationData,
623 globalCommunicationHelper,
625 std::forward<Args>(args)...);
628 template<typename Element, typename... Args>
629 void ModularSimulatorAlgorithmBuilder::add(Args&&... args)
631 if (algorithmHasBeenBuilt_)
633 GMX_THROW(SimulationAlgorithmSetupError(
634 "Tried to add an element after ModularSimulationAlgorithm was built."));
637 // Get element from factory method
638 auto* element = static_cast<Element*>(getElementPointer<Element>(legacySimulatorData_,
639 &elementAdditionHelper_,
640 statePropagatorData_.get(),
642 freeEnergyPerturbationData_.get(),
643 &globalCommunicationHelper_,
644 &observablesReducer_,
645 std::forward<Args>(args)...));
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))
651 GMX_THROW(ElementNotFoundError("Tried to append non-existing element to call list."));
654 callList_.emplace_back(element);
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)
661 return static_cast<Base*>(element);
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)
671 template<typename Element>
672 void ModularSimulatorAlgorithmBuilder::registerWithInfrastructureAndSignallers(Element* element)
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));
692 template<typename Element>
693 Element* ModularSimulatorAlgorithmBuilder::addElementToSimulatorAlgorithm(std::unique_ptr<Element> 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);
705 template<typename Element>
706 void ModularSimulatorAlgorithmBuilder::registerExistingElement(Element* element)
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))
713 ElementNotFoundError("Tried to register non-existing element to infrastructure."));
716 // Add to setup / teardown list
717 setupAndTeardownList_.emplace_back(element);
718 // Register element to all applicable signallers
719 registerWithInfrastructureAndSignallers(element);
722 template<typename Element>
723 Element* ModularSimulatorAlgorithmBuilderHelper::storeElement(std::unique_ptr<Element> element)
725 return builder_->addElementToSimulatorAlgorithm(std::move(element));
728 template<typename ValueType>
729 void ModularSimulatorAlgorithmBuilderHelper::storeBuilderData(const std::string& key, const ValueType& value)
731 builder_->builderData_[key] = std::any(value);
734 template<typename ValueType>
735 void ModularSimulatorAlgorithmBuilderHelper::storeSimulationData(const std::string& key, ValueType&& value)
737 builder_->storeSimulationData(key, std::forward<ValueType>(value));
740 template<typename ValueType>
741 std::optional<ValueType*> ModularSimulatorAlgorithmBuilderHelper::simulationData(const std::string& key)
743 return builder_->simulationData<ValueType>(key);
746 template<typename ValueType>
747 void ModularSimulatorAlgorithmBuilder::storeSimulationData(const std::string& key, ValueType&& value)
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);
756 template<typename ValueType>
757 std::optional<ValueType*> ModularSimulatorAlgorithmBuilder::simulationData(const std::string& key)
759 const auto iter = simulationData_.find(key);
760 if (iter == simulationData_.end())
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.",
776 #endif // GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H