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/state.h"
59 #include "gromacs/utility/exceptions.h"
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"
72 enum class IntegrationStep;
74 class ModularSimulator;
76 template<IntegrationStep algorithm>
81 * \ingroup module_modularsimulator
82 * \brief The modular simulator
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.
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.
95 class ModularSimulatorAlgorithm final
98 //! Get next task in queue
99 [[nodiscard]] const SimulatorRunFunction* getNextTask();
101 // Only builder can construct
102 friend class ModularSimulatorAlgorithmBuilder;
106 ModularSimulatorAlgorithm(std::string topologyName,
109 const MDLogger& mdlog,
110 const MdrunOptions& mdrunOptions,
111 const t_inputrec* inputrec,
113 gmx_wallcycle* wcycle,
115 gmx_walltime_accounting* walltime_accounting);
117 //! Calls setup for the simulator and all elements and signallers
119 //! Calls teardown for the simulator and all elements
121 //! Re-populate task queue
122 void updateTaskQueue();
124 /*! \brief A function called once before the simulation run
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.
130 void simulatorSetup();
132 /*! \brief A function called once after the simulation run
134 * This function collects calls which need to be made once at the
135 * end of the simulation run.
137 void simulatorTeardown();
139 /*! \brief A function called before every step
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.
146 void preStep(Step step, Time time, bool isNeighborSearchingStep);
148 /*! \brief A function called after every step
150 * This function collects calls which need to be made after every
153 void postStep(Step step, Time time);
155 /*! \brief Add run functions to the task queue
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.
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).
171 void populateTaskQueue();
174 std::vector<SimulatorRunFunction> taskQueue_;
175 //! The task iterator
176 std::vector<SimulatorRunFunction>::const_iterator taskIterator_;
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.
186 * Objects being both a signaller and an element are not supported.
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 (calling sequence)
193 std::vector<ISimulatorElement*> elementCallList_;
195 // Infrastructure elements
196 //! The domain decomposition element
197 std::unique_ptr<DomDecHelper> domDecHelper_;
198 //! The PME load balancing element
199 std::unique_ptr<PmeLoadBalanceHelper> pmeLoadBalanceHelper_;
200 //! The checkpoint helper
201 std::unique_ptr<CheckpointHelper> checkpointHelper_;
203 std::unique_ptr<StopHandler> stopHandler_;
204 //! The reset handler
205 std::unique_ptr<ResetHandler> resetHandler_;
206 //! Signal vector (used by stop / reset / checkpointing signaller)
207 std::unique_ptr<SimulationSignals> signals_;
209 std::unique_ptr<TopologyHolder> topologyHolder_;
212 //! The state propagator data
213 std::unique_ptr<StatePropagatorData> statePropagatorData_;
215 std::unique_ptr<EnergyData> energyData_;
216 //! The free energy data
217 std::unique_ptr<FreeEnergyPerturbationData> freeEnergyPerturbationData_;
221 //! Whether the simulation run is finished
225 * \brief Signal helper
227 * The simulator needs to know about the last step and the
228 * neighbor searching step, which are determined in signallers.
229 * This object can be registered to the signals and accessed by
230 * the methods of the simulator.
232 class SignalHelper : public ILastStepSignallerClient, public INeighborSearchSignallerClient
236 Step lastStep_ = std::numeric_limits<Step>::max();
238 Step nextNSStep_ = -1;
239 //! ILastStepSignallerClient implementation
240 std::optional<SignallerCallback> registerLastStepCallback() override;
241 //! INeighborSearchSignallerClient implementation
242 std::optional<SignallerCallback> registerNSCallback() override;
244 //! The signal helper object
245 std::unique_ptr<SignalHelper> signalHelper_;
247 // TODO: This is a hack for stop handler - needs to go once StopHandler
248 // is adapted to the modular simulator
249 //! Whether this is a neighbor-searching step
250 bool stophandlerIsNSStep_ = false;
252 Step stophandlerCurrentStep_ = -1;
254 // ISimulator data - used for setup, teardown, pre step and post step
255 //! Name of the topology
256 std::string topologyName_;
259 //! Handles communication.
262 const MDLogger& mdlog;
263 //! Contains command-line options to mdrun.
264 const MdrunOptions& mdrunOptions;
265 //! Contains user input mdp options.
266 const t_inputrec* inputrec;
267 //! Manages flop accounting.
269 //! Manages wall cycle accounting.
270 gmx_wallcycle* wcycle;
271 //! Parameters for force calculations.
273 //! Manages wall time accounting.
274 gmx_walltime_accounting* walltime_accounting;
278 * \brief Helper container with data connected to global communication
280 * This includes data that needs to be shared between elements involved in
281 * global communication. This will become obsolete as soon as global
282 * communication is moved to a client system (#3421).
284 class GlobalCommunicationHelper
288 GlobalCommunicationHelper(int nstglobalcomm, SimulationSignals* simulationSignals);
290 //! Get the compute globals communication period
291 [[nodiscard]] int nstglobalcomm() const;
292 //! Get a pointer to the signals vector
293 [[nodiscard]] SimulationSignals* simulationSignals();
295 //! Set the callback to check the number of bonded interactions
296 void setCheckBondedInteractionsCallback(CheckBondedInteractionsCallback callback);
297 //! Move the callback to check the number of bonded interactions
298 [[nodiscard]] CheckBondedInteractionsCallback moveCheckBondedInteractionsCallback();
301 //! Compute globals communication period
302 const int nstglobalcomm_;
303 //! Signal vector (used by stop / reset / checkpointing signaller)
304 SimulationSignals* simulationSignals_;
305 //! Callback to check the number of bonded interactions
306 std::optional<CheckBondedInteractionsCallback> checkBondedInteractionsCallback_;
309 class ModularSimulatorAlgorithmBuilder;
312 * \brief Helper for element addition
314 * Such an object will be given to each invocation of getElementPointer
316 * Note: It would be nicer to define this as a member type of
317 * ModularSimulatorAlgorithmBuilder, but this would break forward declaration.
318 * This object is therefore defined as friend class.
320 class ModularSimulatorAlgorithmBuilderHelper
324 ModularSimulatorAlgorithmBuilderHelper(ModularSimulatorAlgorithmBuilder* builder);
325 //! Store an element to the ModularSimulatorAlgorithmBuilder
326 ISimulatorElement* storeElement(std::unique_ptr<ISimulatorElement> element);
327 //! Check if an element is stored in the ModularSimulatorAlgorithmBuilder
328 bool elementIsStored(const ISimulatorElement* element) const;
329 //! Set arbitrary data in the ModularSimulatorAlgorithmBuilder. Helpful for stateful elements.
330 template<typename ValueType>
331 void storeValue(const std::string& key, const ValueType& value);
332 //! Get previously stored data. Returns std::nullopt if key is not found.
333 std::optional<std::any> getStoredValue(const std::string& key) const;
334 //! Register a thermostat that accepts propagator registrations
335 void registerThermostat(std::function<void(const PropagatorThermostatConnection&)> registrationFunction);
336 //! Register a barostat that accepts propagator registrations
337 void registerBarostat(std::function<void(const PropagatorBarostatConnection&)> registrationFunction);
338 //! Register a propagator to the thermostat used
339 void registerWithThermostat(PropagatorThermostatConnection connectionData);
340 //! Register a propagator to the barostat used
341 void registerWithBarostat(PropagatorBarostatConnection connectionData);
344 //! Pointer to the associated ModularSimulatorAlgorithmBuilder
345 ModularSimulatorAlgorithmBuilder* builder_;
346 std::map<std::string, std::any> values_;
350 * \brief Builder for ModularSimulatorAlgorithm objects
352 * This builds a ModularSimulatorAlgorithm.
354 * Users can add elements and define their call order by calling the templated
355 * add<Element> function. Note that only elements that have a static
356 * getElementPointerImpl factory method can be built in that way.
358 * Note that each ModularSimulatorAlgorithmBuilder can only be used to build
359 * one ModularSimulatorAlgorithm object, i.e. build() can only be called once.
360 * During the call to build, all elements and other infrastructure objects will
361 * be moved to the built ModularSimulatorAlgorithm object, such that further use
362 * of the builder would not make sense.
363 * Any access to the build or add<> methods after the first call to
364 * build() will result in an exception being thrown.
366 class ModularSimulatorAlgorithmBuilder final
370 ModularSimulatorAlgorithmBuilder(compat::not_null<LegacySimulatorData*> legacySimulatorData,
371 std::unique_ptr<ReadCheckpointDataHolder> checkpointDataHolder);
373 ModularSimulatorAlgorithm build();
375 /*! \brief Add element to the modular simulator algorithm builder
377 * This function has a general implementation, which will call the getElementPointer(...)
380 * \tparam Element The element type
381 * \tparam Args A variable number of argument types
382 * \param args A variable number of arguments
384 template<typename Element, typename... Args>
385 void add(Args&&... args);
387 //! Allow access from helper
388 friend class ModularSimulatorAlgorithmBuilderHelper;
391 //! The state of the builder
392 bool algorithmHasBeenBuilt_ = false;
395 //! The state propagator data
396 std::unique_ptr<StatePropagatorData> statePropagatorData_;
398 std::unique_ptr<EnergyData> energyData_;
399 //! The free energy data
400 std::unique_ptr<FreeEnergyPerturbationData> freeEnergyPerturbationData_;
402 //! Pointer to the LegacySimulatorData object
403 compat::not_null<LegacySimulatorData*> legacySimulatorData_;
406 //! Signal vector (used by stop / reset / checkpointing signaller)
407 std::unique_ptr<SimulationSignals> signals_;
408 //! Helper object passed to element factory functions
409 ModularSimulatorAlgorithmBuilderHelper elementAdditionHelper_;
410 //! Container for global computation data
411 GlobalCommunicationHelper globalCommunicationHelper_;
413 /*! \brief Register an element to all applicable signallers and infrastructure elements
415 * \tparam Element Type of the Element
416 * \param element Pointer to the element
418 template<typename Element>
419 void registerWithInfrastructureAndSignallers(Element* element);
421 /*! \brief Take ownership of element
423 * This function returns a non-owning pointer to the new location of that
424 * element, allowing further usage (e.g. adding the element to the call list).
425 * Note that simply addin an element using this function will not call it
426 * during the simulation - it needs to be added to the call list separately.
427 * Note that generally, users will want to add elements to the call list, but
428 * it might not be practical to do this in the same order.
430 * \param element A unique pointer to the element
431 * \return A non-owning (raw) pointer to the element for further usage
433 ISimulatorElement* addElementToSimulatorAlgorithm(std::unique_ptr<ISimulatorElement> element);
435 /*! \brief Check if element is owned by *this
437 * \param element Pointer to the element
438 * \return Bool indicating whether element is owned by *this
440 [[nodiscard]] bool elementExists(const ISimulatorElement* element) const;
442 /*! \brief Add element to setupAndTeardownList_ if it's not already there
444 * \param element Element pointer to be added
446 void addElementToSetupTeardownList(ISimulatorElement* element);
448 //! Vector to store elements, allowing the SimulatorAlgorithm to control their lifetime
449 std::vector<std::unique_ptr<ISimulatorElement>> elements_;
450 /*! \brief List defining in which order elements are called every step
452 * Elements may be referenced more than once if they should be called repeatedly
454 std::vector<ISimulatorElement*> callList_;
455 /*! \brief List defining in which order elements are set up and torn down
457 * Elements should only appear once in this list
459 std::vector<ISimulatorElement*> setupAndTeardownList_;
461 //! Builder for the NeighborSearchSignaller
462 SignallerBuilder<NeighborSearchSignaller> neighborSearchSignallerBuilder_;
463 //! Builder for the LastStepSignaller
464 SignallerBuilder<LastStepSignaller> lastStepSignallerBuilder_;
465 //! Builder for the LoggingSignaller
466 SignallerBuilder<LoggingSignaller> loggingSignallerBuilder_;
467 //! Builder for the EnergySignaller
468 SignallerBuilder<EnergySignaller> energySignallerBuilder_;
469 //! Builder for the TrajectorySignaller
470 SignallerBuilder<TrajectorySignaller> trajectorySignallerBuilder_;
471 //! Builder for the TrajectoryElementBuilder
472 TrajectoryElementBuilder trajectoryElementBuilder_;
473 //! Builder for the TopologyHolder
474 TopologyHolder::Builder topologyHolderBuilder_;
475 //! Builder for the CheckpointHelper
476 CheckpointHelperBuilder checkpointHelperBuilder_;
478 /*! \brief List of clients for the CheckpointHelper
480 * \todo Replace this by proper builder (#3422)
482 std::vector<ICheckpointHelperClient*> checkpointClients_;
484 //! List of thermostat registration functions
485 std::vector<std::function<void(const PropagatorThermostatConnection&)>> thermostatRegistrationFunctions_;
486 //! List of barostat registration functions
487 std::vector<std::function<void(const PropagatorBarostatConnection&)>> barostatRegistrationFunctions_;
488 //! List of data to connect propagators to thermostats
489 std::vector<PropagatorThermostatConnection> propagatorThermostatConnections_;
490 //! List of data to connect propagators to barostats
491 std::vector<PropagatorBarostatConnection> propagatorBarostatConnections_;
495 * \brief Factory function for elements that can be added via ModularSimulatorAlgorithmBuilder:
496 * Get a pointer to an object of type \c Element to add to the call list
498 * This allows elements to be built via the templated ModularSimulatorAlgorithmBuilder::add<Element>
499 * method. Elements buildable throught this factor function are required to implement a static
500 * function with minimal signature
502 * static ISimulatorElement* getElementPointerImpl(
503 * LegacySimulatorData* legacySimulatorData,
504 * ModularSimulatorAlgorithmBuilderHelper* builderHelper,
505 * StatePropagatorData* statePropagatorData,
506 * EnergyData* energyData,
507 * FreeEnergyPerturbationData* freeEnergyPerturbationData,
508 * GlobalCommunicationHelper* globalCommunicationHelper)
510 * This function may also accept additional parameters which are passed using the variadic
511 * template parameter pack forwarded in getElementPointer.
513 * This function returns a pointer to an object of the Element type. Note that the caller will
514 * check whether the returned object has previously been stored using the `storeElement`
515 * function, and throw an exception if the element is not found.
516 * The function can check whether a previously stored pointer is valid using
517 * the `checkElementExistence` function. Most implementing functions will simply want
518 * to create an object, store it using `storeElement`, and then use the return value of
519 * `storeElement` as a return value to the caller. However, this setup allows the function
520 * to store a created element (using a static pointer inside the function) and return it
521 * in case that the factory function is called repeatedly. This allows to create an element
522 * once, but have it called multiple times during the simulation run.
524 * \see ModularSimulatorAlgorithmBuilder::add
525 * Function using this functionality
526 * \see ComputeGlobalsElement<ComputeGlobalsAlgorithm::VelocityVerlet>::getElementPointerImpl
527 * Implementation using the single object / multiple call sites functionality
529 * \tparam Element The type of the element
530 * \tparam Args Variable number of argument types allowing specific implementations to have
531 * additional arguments
533 * \param legacySimulatorData Pointer allowing access to simulator level data
534 * \param builderHelper ModularSimulatorAlgorithmBuilder helper object
535 * \param statePropagatorData Pointer to the \c StatePropagatorData object
536 * \param energyData Pointer to the \c EnergyData object
537 * \param freeEnergyPerturbationData Pointer to the \c FreeEnergyPerturbationData object
538 * \param globalCommunicationHelper Pointer to the \c GlobalCommunicationHelper object
539 * \param args Variable number of additional parameters to be forwarded
541 * \return Pointer to the element to be added. Element needs to have been stored using \c storeElement
543 template<typename Element, typename... Args>
544 ISimulatorElement* getElementPointer(LegacySimulatorData* legacySimulatorData,
545 ModularSimulatorAlgorithmBuilderHelper* builderHelper,
546 StatePropagatorData* statePropagatorData,
547 EnergyData* energyData,
548 FreeEnergyPerturbationData* freeEnergyPerturbationData,
549 GlobalCommunicationHelper* globalCommunicationHelper,
552 return Element::getElementPointerImpl(legacySimulatorData,
556 freeEnergyPerturbationData,
557 globalCommunicationHelper,
558 std::forward<Args>(args)...);
561 template<typename Element, typename... Args>
562 void ModularSimulatorAlgorithmBuilder::add(Args&&... args)
564 if (algorithmHasBeenBuilt_)
566 throw SimulationAlgorithmSetupError(
567 "Tried to add an element after ModularSimulationAlgorithm was built.");
570 // Get element from factory method
571 auto* element = static_cast<Element*>(getElementPointer<Element>(legacySimulatorData_,
572 &elementAdditionHelper_,
573 statePropagatorData_.get(),
575 freeEnergyPerturbationData_.get(),
576 &globalCommunicationHelper_,
577 std::forward<Args>(args)...));
579 // Make sure returned element pointer is owned by *this
580 // Ensuring this makes sure we can control the life time
581 if (!elementExists(element))
583 throw ElementNotFoundError("Tried to append non-existing element to call list.");
586 callList_.emplace_back(element);
587 // Add to setup / teardown list if element hasn't been added yet
588 addElementToSetupTeardownList(element);
589 // Register element to all applicable signallers
590 registerWithInfrastructureAndSignallers(element);
593 //! Returns a pointer casted to type Base if the Element is derived from Base
594 template<typename Base, typename Element>
595 static std::enable_if_t<std::is_base_of<Base, Element>::value, Base*> castOrNull(Element* element)
597 return static_cast<Base*>(element);
600 //! Returns a nullptr of type Base if Element is not derived from Base
601 template<typename Base, typename Element>
602 static std::enable_if_t<!std::is_base_of<Base, Element>::value, Base*> castOrNull(Element gmx_unused* element)
607 template<typename Element>
608 void ModularSimulatorAlgorithmBuilder::registerWithInfrastructureAndSignallers(Element* element)
610 // Register element to all applicable signallers
611 neighborSearchSignallerBuilder_.registerSignallerClient(
612 castOrNull<INeighborSearchSignallerClient, Element>(element));
613 lastStepSignallerBuilder_.registerSignallerClient(castOrNull<ILastStepSignallerClient, Element>(element));
614 loggingSignallerBuilder_.registerSignallerClient(castOrNull<ILoggingSignallerClient, Element>(element));
615 energySignallerBuilder_.registerSignallerClient(castOrNull<IEnergySignallerClient, Element>(element));
616 trajectorySignallerBuilder_.registerSignallerClient(
617 castOrNull<ITrajectorySignallerClient, Element>(element));
618 // Register element to trajectory element (if applicable)
619 trajectoryElementBuilder_.registerWriterClient(castOrNull<ITrajectoryWriterClient, Element>(element));
620 // Register element to topology holder (if applicable)
621 topologyHolderBuilder_.registerClient(castOrNull<ITopologyHolderClient, Element>(element));
622 // Register element to checkpoint client (if applicable)
623 checkpointHelperBuilder_.registerClient(castOrNull<ICheckpointHelperClient, Element>(element));
627 template<typename ValueType>
628 void ModularSimulatorAlgorithmBuilderHelper::storeValue(const std::string& key, const ValueType& value)
630 values_[key] = std::any(value);
636 #endif // GROMACS_MODULARSIMULATOR_SIMULATORALGORITHM_H