From bedd003e5c1fc4fa5461fca0539a35cd41f40a5f Mon Sep 17 00:00:00 2001 From: Pascal Merz Date: Wed, 11 Sep 2019 18:50:28 -0600 Subject: [PATCH] Introduce the modular simulator The modular simulator builds independent elements and signallers based on the input and stores them in a respective vector. It then calls the setup function of all elements. It then repeats the following loop until a stop condition is met: * Perform domdec and load balancing if necessary * Until the next neighbor-searching step: - Call signallers in order - Call elements in order, allow them to register run function in queue - increase step number * Once the next neighbor-searching step is reached, run through the entire queue of function pointers * Repeat Supressed some warnings about redundant std::move necessary for legacy compilers. Increased default trajectory tolerance for force comparisons, which were chosen arbitrarily originally. Change-Id: Iaae1e20581df464090979da9b05d04829f267300 --- docs/doxygen/cycle-suppressions.txt | 3 + src/gromacs/mdlib/coupling.cpp | 22 +- src/gromacs/mdlib/update.h | 3 + src/gromacs/mdrun/simulatorbuilder.h | 22 +- src/gromacs/modularsimulator/CMakeLists.txt | 7 + .../modularsimulator/compositesimulatorelement.cpp | 10 +- .../modularsimulator/compositesimulatorelement.h | 23 +- src/gromacs/modularsimulator/modularsimulator.cpp | 726 +++++++++++++++++++++ src/gromacs/modularsimulator/modularsimulator.h | 321 +++++++++ src/programs/mdrun/tests/trajectorycomparison.cpp | 2 +- 10 files changed, 1118 insertions(+), 21 deletions(-) create mode 100644 src/gromacs/modularsimulator/modularsimulator.cpp create mode 100644 src/gromacs/modularsimulator/modularsimulator.h diff --git a/docs/doxygen/cycle-suppressions.txt b/docs/doxygen/cycle-suppressions.txt index 57261031d1..577d39c856 100644 --- a/docs/doxygen/cycle-suppressions.txt +++ b/docs/doxygen/cycle-suppressions.txt @@ -23,3 +23,6 @@ simd -> hardware gpu_utils -> hardware listed_forces -> mdlib utility -> math + +# modular simulator uses shellfc from mdrun, but is later included in mdrun by simulator builder +modularsimulator -> mdrun diff --git a/src/gromacs/mdlib/coupling.cpp b/src/gromacs/mdlib/coupling.cpp index 1713a16009..49c0791dfd 100644 --- a/src/gromacs/mdlib/coupling.cpp +++ b/src/gromacs/mdlib/coupling.cpp @@ -1620,25 +1620,31 @@ void rescale_velocities(const gmx_ekindata_t *ekind, const t_mdatoms *mdatoms, } } -// TODO If we keep simulated annealing, make a proper module that -// does not rely on changing inputrec. -bool initSimulatedAnnealing(t_inputrec *ir, - gmx::Update *upd) +//! Check whether we're doing simulated annealing +bool doSimulatedAnnealing(const t_inputrec *ir) { - bool doSimulatedAnnealing = false; for (int i = 0; i < ir->opts.ngtc; i++) { /* set bSimAnn if any group is being annealed */ if (ir->opts.annealing[i] != eannNO) { - doSimulatedAnnealing = true; + return true; } } - if (doSimulatedAnnealing) + return false; +} + +// TODO If we keep simulated annealing, make a proper module that +// does not rely on changing inputrec. +bool initSimulatedAnnealing(t_inputrec *ir, + gmx::Update *upd) +{ + bool doSimAnnealing = doSimulatedAnnealing(ir); + if (doSimAnnealing) { update_annealing_target_temp(ir, ir->init_t, upd); } - return doSimulatedAnnealing; + return doSimAnnealing; } /* set target temperatures if we are annealing */ diff --git a/src/gromacs/mdlib/update.h b/src/gromacs/mdlib/update.h index 39aac96f75..d42e9d1748 100644 --- a/src/gromacs/mdlib/update.h +++ b/src/gromacs/mdlib/update.h @@ -265,6 +265,9 @@ void rescale_velocities(const gmx_ekindata_t *ekind, const t_mdatoms *mdatoms, int start, int end, rvec v[]); /* Rescale the velocities with the scaling factor in ekind */ +//! Check whether we do simulated annealing. +bool doSimulatedAnnealing(const t_inputrec *ir); + //! Initialize simulated annealing. bool initSimulatedAnnealing(t_inputrec *ir, gmx::Update *upd); diff --git a/src/gromacs/mdrun/simulatorbuilder.h b/src/gromacs/mdrun/simulatorbuilder.h index 3d26aee990..cdeb48d7fc 100644 --- a/src/gromacs/mdrun/simulatorbuilder.h +++ b/src/gromacs/mdrun/simulatorbuilder.h @@ -43,6 +43,8 @@ #include +#include "gromacs/modularsimulator/modularsimulator.h" + #include "legacysimulator.h" class energyhistory_t; @@ -109,10 +111,22 @@ class SimulatorBuilder template std::unique_ptr SimulatorBuilder::build(Args && ... args) { - // NOLINTNEXTLINE(modernize-make-unique): make_unique does not work with private constructor - return std::unique_ptr( - new LegacySimulator( - std::forward(args) ...)); + // The feature flag + const auto useModularSimulator = (getenv("GMX_USE_MODULAR_SIMULATOR") != nullptr); + if (!useModularSimulator) + { + // NOLINTNEXTLINE(modernize-make-unique): make_unique does not work with private constructor + return std::unique_ptr( + new LegacySimulator( + std::forward(args) ...)); + } + else + { + // NOLINTNEXTLINE(modernize-make-unique): make_unique does not work with private constructor + return std::unique_ptr( + new ModularSimulator( + std::forward(args) ...)); + } } } // namespace gmx diff --git a/src/gromacs/modularsimulator/CMakeLists.txt b/src/gromacs/modularsimulator/CMakeLists.txt index 951404d0fe..28d6eaac28 100644 --- a/src/gromacs/modularsimulator/CMakeLists.txt +++ b/src/gromacs/modularsimulator/CMakeLists.txt @@ -46,6 +46,13 @@ endif() if (WIN32) gmx_target_warning_suppression(modularsimulator /wd4244 HAS_NO_MSVC_LOSSY_CONVERSION) + gmx_target_warning_suppression(modularsimulator /wd4996 HAS_NO_MSVC_UNSAFE_FUNCTION) +else() + # Several std::move uses are redundant in C++14, but clang before + # 3.9 had a bug that needed them. gcc 9 can warn about their use, + # which we need to supress. This suppression should be removed + # when GROMACS requires clang 3.9 or higher. + gmx_target_warning_suppression(modularsimulator "-Wno-redundant-move" HAS_NO_REDUNDANT_MOVE) endif() list(APPEND libgromacs_object_library_dependencies modularsimulator) diff --git a/src/gromacs/modularsimulator/compositesimulatorelement.cpp b/src/gromacs/modularsimulator/compositesimulatorelement.cpp index fcb8220d37..fa55eca570 100644 --- a/src/gromacs/modularsimulator/compositesimulatorelement.cpp +++ b/src/gromacs/modularsimulator/compositesimulatorelement.cpp @@ -45,15 +45,17 @@ namespace gmx { CompositeSimulatorElement::CompositeSimulatorElement( + std::vector< compat::not_null > elementCallList, std::vector< std::unique_ptr > elements) : - elements_(std::move(elements)) + elementCallList_(std::move(elementCallList)), + elementOwnershipList_(std::move(elements)) {} void CompositeSimulatorElement::scheduleTask( Step step, Time time, const RegisterRunFunctionPtr ®isterRunFunction) { - for (auto &element : elements_) + for (auto &element : elementCallList_) { element->scheduleTask(step, time, registerRunFunction); } @@ -61,7 +63,7 @@ void CompositeSimulatorElement::scheduleTask( void CompositeSimulatorElement::elementSetup() { - for (auto &element : elements_) + for (auto &element : elementOwnershipList_) { element->elementSetup(); } @@ -69,7 +71,7 @@ void CompositeSimulatorElement::elementSetup() void CompositeSimulatorElement::elementTeardown() { - for (auto &element : elements_) + for (auto &element : elementOwnershipList_) { element->elementTeardown(); } diff --git a/src/gromacs/modularsimulator/compositesimulatorelement.h b/src/gromacs/modularsimulator/compositesimulatorelement.h index 70bbc37e67..0a1b3008ee 100644 --- a/src/gromacs/modularsimulator/compositesimulatorelement.h +++ b/src/gromacs/modularsimulator/compositesimulatorelement.h @@ -42,6 +42,8 @@ #include +#include "gromacs/compat/pointers.h" + #include "modularsimulatorinterfaces.h" namespace gmx @@ -53,9 +55,19 @@ namespace gmx /*! \libinternal * \brief Composite simulator element * - * The composite simulator element takes a list of elements and implements + * The composite simulator element takes a call list of elements and implements * the ISimulatorElement interface, making a group of elements effectively * behave as one. This simplifies building algorithms. + * + * The CompositeSimulatorElement can optionally also own the elements, but does + * not require this. The owner of a CompositeSimulatorElement object can hence + * decide to either pass the ownership to CompositeSimulatorElement, or keep + * the ownership (and guarantee that they remain valid during the life time + * of the CompositeSimulatorElement object). CompositeSimulatorElement will only + * call the setup and teardown methods on the owned elements, thereby avoiding + * to call them more than once. Consequently, the owner of the elements not + * owned by CompositeSimulatorElement is responsible to call setup and teardown + * methods on these elements. */ class CompositeSimulatorElement final : public ISimulatorElement @@ -63,7 +75,8 @@ class CompositeSimulatorElement final : public: //! Constructor explicit CompositeSimulatorElement( - std::vector< std::unique_ptr > elements); + std::vector< compat::not_null > elementCallList, + std::vector< std::unique_ptr > elements); /*! \brief Register run function for step / time * @@ -91,8 +104,10 @@ class CompositeSimulatorElement final : void elementTeardown() override; private: - //! List of elements forming the composite element - std::vector< std::unique_ptr > elements_; + //! The call list of elements forming the composite element + std::vector< compat::not_null > elementCallList_; + //! List of elements owned by composite element + std::vector< std::unique_ptr > elementOwnershipList_; }; //! \} diff --git a/src/gromacs/modularsimulator/modularsimulator.cpp b/src/gromacs/modularsimulator/modularsimulator.cpp new file mode 100644 index 0000000000..ce3fa7e5ab --- /dev/null +++ b/src/gromacs/modularsimulator/modularsimulator.cpp @@ -0,0 +1,726 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2019, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \internal + * \brief Defines the modular simulator + * + * \author Pascal Merz + * \ingroup module_mdrun + */ + +#include "gmxpre.h" + +#include "modularsimulator.h" + +#include "gromacs/commandline/filenm.h" +#include "gromacs/domdec/domdec.h" +#include "gromacs/ewald/pme.h" +#include "gromacs/ewald/pme_load_balancing.h" +#include "gromacs/gmxlib/nrnb.h" +#include "gromacs/mdlib/constr.h" +#include "gromacs/mdlib/energyoutput.h" +#include "gromacs/mdlib/mdatoms.h" +#include "gromacs/mdlib/resethandler.h" +#include "gromacs/mdlib/stat.h" +#include "gromacs/mdlib/update.h" +#include "gromacs/mdrun/replicaexchange.h" +#include "gromacs/mdrun/shellfc.h" +#include "gromacs/mdrunutility/handlerestart.h" +#include "gromacs/mdrunutility/printtime.h" +#include "gromacs/mdtypes/commrec.h" +#include "gromacs/mdtypes/fcdata.h" +#include "gromacs/mdtypes/inputrec.h" +#include "gromacs/mdtypes/mdrunoptions.h" +#include "gromacs/mdtypes/observableshistory.h" +#include "gromacs/mdtypes/state.h" +#include "gromacs/nbnxm/nbnxm.h" +#include "gromacs/timing/walltime_accounting.h" +#include "gromacs/topology/topology.h" +#include "gromacs/utility/cstringutil.h" +#include "gromacs/utility/fatalerror.h" + +#include "compositesimulatorelement.h" +#include "computeglobalselement.h" +#include "constraintelement.h" +#include "energyelement.h" +#include "forceelement.h" +#include "propagator.h" +#include "shellfcelement.h" +#include "signallers.h" +#include "statepropagatordata.h" +#include "trajectoryelement.h" + +namespace gmx +{ +void ModularSimulator::run() +{ + GMX_LOG(mdlog.info).asParagraph(). + appendText("Using the modular simulator."); + constructElementsAndSignallers(); + simulatorSetup(); + for (auto &signaller : signallerCallList_) + { + signaller->signallerSetup(); + } + if (pmeLoadBalanceHelper_) + { + pmeLoadBalanceHelper_->setup(); + } + if (domDecHelper_) + { + domDecHelper_->setup(); + } + + for (auto &element : elementsOwnershipList_) + { + element->elementSetup(); + } + + while (step_ <= signalHelper_->lastStep_) + { + populateTaskQueue(); + + while (!taskQueue_.empty()) + { + auto task = std::move(taskQueue_.front()); + taskQueue_.pop(); + // run function + (*task)(); + } + } + + for (auto &element : elementsOwnershipList_) + { + element->elementTeardown(); + } + if (pmeLoadBalanceHelper_) + { + pmeLoadBalanceHelper_->teardown(); + } + simulatorTeardown(); +} + +void ModularSimulator::simulatorSetup() +{ + if (!mdrunOptions.writeConfout) + { + // This is on by default, and the main known use case for + // turning it off is for convenience in benchmarking, which is + // something that should not show up in the general user + // interface. + GMX_LOG(mdlog.info).asParagraph(). + appendText("The -noconfout functionality is deprecated, and " + "may be removed in a future version."); + } + + if (MASTER(cr)) + { + char sbuf[STEPSTRSIZE], sbuf2[STEPSTRSIZE]; + std::string timeString; + fprintf(stderr, "starting mdrun '%s'\n", + *(top_global->name)); + if (inputrec->nsteps >= 0) + { + timeString = formatString( + "%8.1f", static_cast(inputrec->init_step+inputrec->nsteps)*inputrec->delta_t); + } + else + { + timeString = "infinite"; + } + if (inputrec->init_step > 0) + { + fprintf(stderr, "%s steps, %s ps (continuing from step %s, %8.1f ps).\n", + gmx_step_str(inputrec->init_step+inputrec->nsteps, sbuf), + timeString.c_str(), + gmx_step_str(inputrec->init_step, sbuf2), + inputrec->init_step*inputrec->delta_t); + } + else + { + fprintf(stderr, "%s steps, %s ps.\n", + gmx_step_str(inputrec->nsteps, sbuf), timeString.c_str()); + } + fprintf(fplog, "\n"); + } + + walltime_accounting_start_time(walltime_accounting); + wallcycle_start(wcycle, ewcRUN); + print_start(fplog, cr, walltime_accounting, "mdrun"); + + step_ = inputrec->init_step; +} + +void ModularSimulator::preStep( + Step step, Time gmx_unused time, + bool isNeighborSearchingStep) +{ + if (stopHandler_->stoppingAfterCurrentStep(isNeighborSearchingStep) && + step != signalHelper_->lastStep_) + { + /* + * Stop handler wants to stop after the current step, which was + * not known when building the current task queue. This happens + * e.g. when a stop is signalled by OS. We therefore want to purge + * the task queue now, and re-schedule this step as last step. + */ + // clear task queue + std::queue().swap(taskQueue_); + // rewind step + step_ = step; + return; + } + + resetHandler_->setSignal(walltime_accounting); + // This is a hack to avoid having to rewrite StopHandler to be a NeighborSearchSignaller + // and accept the step as input. Eventually, we want to do that, but currently this would + // require introducing NeighborSearchSignaller in the legacy do_md or a lot of code + // duplication. + stophandlerIsNSStep_ = isNeighborSearchingStep; + stophandlerCurrentStep_ = step; + stopHandler_->setSignal(); + + wallcycle_start(wcycle, ewcSTEP); +} + +void ModularSimulator::postStep(Step step, Time gmx_unused time) +{ + // Output stuff + if (MASTER(cr)) + { + if (do_per_step(step, inputrec->nstlog)) + { + if (fflush(fplog) != 0) + { + gmx_fatal(FARGS, "Cannot flush logfile - maybe you are out of disk space?"); + } + } + } + const bool do_verbose = mdrunOptions.verbose && + (step % mdrunOptions.verboseStepPrintInterval == 0 || + step == inputrec->init_step || step == signalHelper_->lastStep_); + // Print the remaining wall clock time for the run + if (MASTER(cr) && + (do_verbose || gmx_got_usr_signal()) && + !(pmeLoadBalanceHelper_ && pmeLoadBalanceHelper_->pmePrinting())) + { + print_time(stderr, walltime_accounting, step, inputrec, cr); + } + + double cycles = wallcycle_stop(wcycle, ewcSTEP); + if (DOMAINDECOMP(cr) && wcycle) + { + dd_cycles_add(cr->dd, static_cast(cycles), ddCyclStep); + } + + resetHandler_->resetCounters( + step, step - inputrec->init_step, mdlog, fplog, cr, fr->nbv.get(), + nrnb, fr->pmedata, + pmeLoadBalanceHelper_ ? pmeLoadBalanceHelper_->loadBalancingObject() : nullptr, + wcycle, walltime_accounting); +} + +void ModularSimulator::simulatorTeardown() +{ + + // Stop measuring walltime + walltime_accounting_end_time(walltime_accounting); + + if (!thisRankHasDuty(cr, DUTY_PME)) + { + /* Tell the PME only node to finish */ + gmx_pme_send_finish(cr); + } + + walltime_accounting_set_nsteps_done(walltime_accounting, step_ - inputrec->init_step); +} + +void ModularSimulator::populateTaskQueue() +{ + auto registerRunFunction = std::make_unique( + [this](SimulatorRunFunctionPtr ptr) + {taskQueue_.push(std::move(ptr)); }); + + Time startTime = inputrec->init_t; + Time timeStep = inputrec->delta_t; + Time time = startTime + step_*timeStep; + + // Run an initial call to the signallers + for (auto &signaller : signallerCallList_) + { + signaller->signal(step_, time); + } + + if (pmeLoadBalanceHelper_) + { + pmeLoadBalanceHelper_->run(step_, time); + } + if (domDecHelper_) + { + domDecHelper_->run(step_, time); + } + + do + { + // local variables for lambda capturing + const int step = step_; + const bool isNSStep = step == signalHelper_->nextNSStep_; + + // register pre-step + (*registerRunFunction)( + std::make_unique( + [this, step, time, isNSStep](){preStep(step, time, isNSStep); })); + // register elements for step + for (auto &element : elementCallList_) + { + element->scheduleTask(step_, time, registerRunFunction); + } + // register post-step + (*registerRunFunction)( + std::make_unique( + [this, step, time](){postStep(step, time); })); + + // prepare next step + step_++; + time = startTime + step_*timeStep; + for (auto &signaller : signallerCallList_) + { + signaller->signal(step_, time); + } + } + while (step_ != signalHelper_->nextNSStep_ && step_ <= signalHelper_->lastStep_); +} + +void ModularSimulator::constructElementsAndSignallers() +{ + /* + * Build data structures + */ + auto statePropagatorData = std::make_unique( + top_global->natoms, fplog, cr, state_global, + inputrec->nstxout, inputrec->nstvout, + inputrec->nstfout, inputrec->nstxout_compressed, + fr->nbv->useGpu(), inputrec, mdAtoms->mdatoms()); + auto statePropagatorDataPtr = compat::make_not_null(statePropagatorData.get()); + + auto energyElement = std::make_unique( + statePropagatorDataPtr, top_global, inputrec, mdAtoms, enerd, ekind, + constr, fplog, fcd, mdModulesNotifier, MASTER(cr)); + auto energyElementPtr = compat::make_not_null(energyElement.get()); + + topologyHolder_ = std::make_unique( + *top_global, cr, inputrec, fr, + mdAtoms, constr, vsite); + + /* + * Build stop handler + */ + const bool simulationsShareState = false; + stopHandler_ = stopHandlerBuilder->getStopHandlerMD( + compat::not_null(&signals_[eglsSTOPCOND]), + simulationsShareState, MASTER(cr), inputrec->nstlist, mdrunOptions.reproducible, + nstglobalcomm_, mdrunOptions.maximumHoursToRun, inputrec->nstlist == 0, fplog, + stophandlerCurrentStep_, stophandlerIsNSStep_, walltime_accounting); + + /* + * Create simulator builders + */ + SignallerBuilder neighborSearchSignallerBuilder; + SignallerBuilder lastStepSignallerBuilder; + SignallerBuilder loggingSignallerBuilder; + SignallerBuilder energySignallerBuilder; + TrajectoryElementBuilder trajectoryElementBuilder; + + /* + * Register data structures to signallers + */ + trajectoryElementBuilder.registerWriterClient(statePropagatorDataPtr); + trajectoryElementBuilder.registerSignallerClient(statePropagatorDataPtr); + + trajectoryElementBuilder.registerWriterClient(energyElementPtr); + trajectoryElementBuilder.registerSignallerClient(energyElementPtr); + energySignallerBuilder.registerSignallerClient(energyElementPtr); + loggingSignallerBuilder.registerSignallerClient(energyElementPtr); + + // Register the simulator itself to the neighbor search / last step signaller + neighborSearchSignallerBuilder.registerSignallerClient(compat::make_not_null(signalHelper_.get())); + lastStepSignallerBuilder.registerSignallerClient(compat::make_not_null(signalHelper_.get())); + + /* + * Build integrator - this takes care of force calculation, propagation, + * constraining, and of the place the statePropagatorData and the energy element + * have a full timestep state. + */ + CheckBondedInteractionsCallbackPtr checkBondedInteractionsCallback = nullptr; + auto integrator = buildIntegrator( + &neighborSearchSignallerBuilder, + &energySignallerBuilder, + &loggingSignallerBuilder, + &trajectoryElementBuilder, + &checkBondedInteractionsCallback, + statePropagatorDataPtr, + energyElementPtr); + + /* + * Build infrastructure elements + */ + + if (PmeLoadBalanceHelper::doPmeLoadBalancing(mdrunOptions, inputrec, fr)) + { + pmeLoadBalanceHelper_ = std::make_unique( + mdrunOptions.verbose, statePropagatorDataPtr, fplog, + cr, mdlog, inputrec, wcycle, fr); + neighborSearchSignallerBuilder.registerSignallerClient(compat::make_not_null(pmeLoadBalanceHelper_.get())); + } + + if (DOMAINDECOMP(cr)) + { + GMX_ASSERT( + checkBondedInteractionsCallback, + "Domain decomposition needs a callback for check the number of bonded interactions."); + domDecHelper_ = std::make_unique( + mdrunOptions.verbose, mdrunOptions.verboseStepPrintInterval, + statePropagatorDataPtr, topologyHolder_.get(), std::move(checkBondedInteractionsCallback), + nstglobalcomm_, fplog, cr, mdlog, constr, inputrec, mdAtoms, + nrnb, wcycle, fr, vsite, imdSession, pull_work); + neighborSearchSignallerBuilder.registerSignallerClient(compat::make_not_null(domDecHelper_.get())); + } + + const bool simulationsShareResetCounters = false; + resetHandler_ = std::make_unique( + compat::make_not_null(&signals_[eglsRESETCOUNTERS]), + simulationsShareResetCounters, inputrec->nsteps, MASTER(cr), + mdrunOptions.timingOptions.resetHalfway, mdrunOptions.maximumHoursToRun, + mdlog, wcycle, walltime_accounting); + + /* + * Build signaller list + * + * Note that as signallers depend on each others, the order of calling the signallers + * matters. It is the responsibility of this builder to ensure that the order is + * maintained. + */ + auto energySignaller = energySignallerBuilder.build( + inputrec->nstcalcenergy); + trajectoryElementBuilder.registerSignallerClient(compat::make_not_null(energySignaller.get())); + loggingSignallerBuilder.registerSignallerClient(compat::make_not_null(energySignaller.get())); + auto trajectoryElement = trajectoryElementBuilder.build( + fplog, nfile, fnm, mdrunOptions, cr, outputProvider, mdModulesNotifier, + inputrec, top_global, oenv, wcycle, startingBehavior); + lastStepSignallerBuilder.registerSignallerClient(compat::make_not_null(trajectoryElement.get())); + auto loggingSignaller = loggingSignallerBuilder.build( + inputrec->nstlog, + inputrec->init_step, + inputrec->init_t); + lastStepSignallerBuilder.registerSignallerClient(compat::make_not_null(loggingSignaller.get())); + auto lastStepSignaller = lastStepSignallerBuilder.build( + inputrec->nsteps, + inputrec->init_step, + stopHandler_.get()); + neighborSearchSignallerBuilder.registerSignallerClient(compat::make_not_null(lastStepSignaller.get())); + auto neighborSearchSignaller = neighborSearchSignallerBuilder.build( + inputrec->nstlist, + inputrec->init_step, + inputrec->init_t); + + addToCallListAndMove(std::move(neighborSearchSignaller), signallerCallList_, signallersOwnershipList_); + addToCallListAndMove(std::move(lastStepSignaller), signallerCallList_, signallersOwnershipList_); + addToCallListAndMove(std::move(loggingSignaller), signallerCallList_, signallersOwnershipList_); + addToCallList(trajectoryElement, signallerCallList_); + addToCallListAndMove(std::move(energySignaller), signallerCallList_, signallersOwnershipList_); + + /* + * Build the element list + * + * This is the actual sequence of (non-infrastructure) elements to be run. + * For NVE, only the trajectory element is used outside of the integrator + * (composite) element. + */ + addToCallListAndMove(std::move(integrator), elementCallList_, elementsOwnershipList_); + addToCallListAndMove(std::move(trajectoryElement), elementCallList_, elementsOwnershipList_); + // for vv, we need to setup statePropagatorData after the compute + // globals so that we reset the right velocities + // TODO: Avoid this by getting rid of the need of resetting velocities in vv + elementsOwnershipList_.emplace_back(std::move(statePropagatorData)); + elementsOwnershipList_.emplace_back(std::move(energyElement)); +} + +std::unique_ptr ModularSimulator::buildForces( + SignallerBuilder *neighborSearchSignallerBuilder, + SignallerBuilder *energySignallerBuilder, + StatePropagatorData *statePropagatorDataPtr, + EnergyElement *energyElementPtr) +{ + const bool isVerbose = mdrunOptions.verbose; + const bool isDynamicBox = inputrecDynamicBox(inputrec); + // Check for polarizable models and flexible constraints + if (ShellFCElement::doShellsOrFlexConstraints( + &topologyHolder_->globalTopology(), constr ? constr->numFlexibleConstraints() : 0)) + { + auto shellFCElement = std::make_unique( + statePropagatorDataPtr, energyElementPtr, isVerbose, isDynamicBox, fplog, + cr, inputrec, mdAtoms, nrnb, fr, fcd, wcycle, mdScheduleWork, + vsite, imdSession, pull_work, constr, &topologyHolder_->globalTopology()); + topologyHolder_->registerClient(shellFCElement.get()); + neighborSearchSignallerBuilder->registerSignallerClient(compat::make_not_null(shellFCElement.get())); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(shellFCElement.get())); + + // std::move *should* not be needed with c++-14, but clang-3.6 still requires it + return std::move(shellFCElement); + } + else + { + auto forceElement = std::make_unique( + statePropagatorDataPtr, energyElementPtr, isDynamicBox, fplog, + cr, inputrec, mdAtoms, nrnb, fr, fcd, wcycle, + mdScheduleWork, vsite, imdSession, pull_work); + topologyHolder_->registerClient(forceElement.get()); + neighborSearchSignallerBuilder->registerSignallerClient(compat::make_not_null(forceElement.get())); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(forceElement.get())); + + // std::move *should* not be needed with c++-14, but clang-3.6 still requires it + return std::move(forceElement); + } +} + +std::unique_ptr ModularSimulator::buildIntegrator( + SignallerBuilder *neighborSearchSignallerBuilder, + SignallerBuilder *energySignallerBuilder, + SignallerBuilder *loggingSignallerBuilder, + TrajectoryElementBuilder *trajectoryElementBuilder, + CheckBondedInteractionsCallbackPtr *checkBondedInteractionsCallback, + compat::not_null statePropagatorDataPtr, + compat::not_null energyElementPtr) +{ + auto forceElement = buildForces( + neighborSearchSignallerBuilder, + energySignallerBuilder, + statePropagatorDataPtr, + energyElementPtr); + + // list of elements owned by the simulator composite object + std::vector< std::unique_ptr > elementsOwnershipList; + // call list of the simulator composite object + std::vector< compat::not_null > elementCallList; + + std::function needToCheckNumberOfBondedInteractions; + if (inputrec->eI == eiMD) + { + auto computeGlobalsElement = + std::make_unique< ComputeGlobalsElement >( + statePropagatorDataPtr, energyElementPtr, nstglobalcomm_, fplog, mdlog, cr, + inputrec, mdAtoms, nrnb, wcycle, fr, + &topologyHolder_->globalTopology(), constr); + topologyHolder_->registerClient(computeGlobalsElement.get()); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElement.get())); + trajectoryElementBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElement.get())); + + *checkBondedInteractionsCallback = computeGlobalsElement->getCheckNumberOfBondedInteractionsCallback(); + + auto propagator = std::make_unique< Propagator >( + inputrec->delta_t, statePropagatorDataPtr, mdAtoms, wcycle); + + addToCallListAndMove(std::move(forceElement), elementCallList, elementsOwnershipList); + addToCallList(statePropagatorDataPtr, elementCallList); // we have a full microstate at time t here! + addToCallListAndMove(std::move(propagator), elementCallList, elementsOwnershipList); + if (constr) + { + auto constraintElement = std::make_unique< ConstraintsElement >( + constr, statePropagatorDataPtr, energyElementPtr, MASTER(cr), + fplog, inputrec, mdAtoms->mdatoms()); + auto constraintElementPtr = compat::make_not_null(constraintElement.get()); + energySignallerBuilder->registerSignallerClient(constraintElementPtr); + trajectoryElementBuilder->registerSignallerClient(constraintElementPtr); + loggingSignallerBuilder->registerSignallerClient(constraintElementPtr); + + addToCallListAndMove(std::move(constraintElement), elementCallList, elementsOwnershipList); + } + + addToCallListAndMove(std::move(computeGlobalsElement), elementCallList, elementsOwnershipList); + addToCallList(energyElementPtr, elementCallList); // we have the energies at time t here! + } + else if (inputrec->eI == eiVV) + { + auto computeGlobalsElementAtFullTimeStep = + std::make_unique< ComputeGlobalsElement >( + statePropagatorDataPtr, energyElementPtr, nstglobalcomm_, fplog, mdlog, cr, + inputrec, mdAtoms, nrnb, wcycle, fr, + &topologyHolder_->globalTopology(), constr); + topologyHolder_->registerClient(computeGlobalsElementAtFullTimeStep.get()); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElementAtFullTimeStep.get())); + trajectoryElementBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElementAtFullTimeStep.get())); + + auto computeGlobalsElementAfterCoordinateUpdate = + std::make_unique >( + statePropagatorDataPtr, energyElementPtr, nstglobalcomm_, fplog, mdlog, cr, + inputrec, mdAtoms, nrnb, wcycle, fr, + &topologyHolder_->globalTopology(), constr); + topologyHolder_->registerClient(computeGlobalsElementAfterCoordinateUpdate.get()); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElementAfterCoordinateUpdate.get())); + trajectoryElementBuilder->registerSignallerClient(compat::make_not_null(computeGlobalsElementAfterCoordinateUpdate.get())); + + *checkBondedInteractionsCallback = computeGlobalsElementAfterCoordinateUpdate->getCheckNumberOfBondedInteractionsCallback(); + + auto propagatorVelocities = std::make_unique< Propagator >( + inputrec->delta_t * 0.5, statePropagatorDataPtr, mdAtoms, wcycle); + auto propagatorVelocitiesAndPositions = std::make_unique< Propagator >( + inputrec->delta_t, statePropagatorDataPtr, mdAtoms, wcycle); + + addToCallListAndMove(std::move(forceElement), elementCallList, elementsOwnershipList); + addToCallListAndMove(std::move(propagatorVelocities), elementCallList, elementsOwnershipList); + if (constr) + { + auto constraintElement = std::make_unique< ConstraintsElement >( + constr, statePropagatorDataPtr, energyElementPtr, MASTER(cr), + fplog, inputrec, mdAtoms->mdatoms()); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + trajectoryElementBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + loggingSignallerBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + + addToCallListAndMove(std::move(constraintElement), elementCallList, elementsOwnershipList); + } + addToCallListAndMove(std::move(computeGlobalsElementAtFullTimeStep), elementCallList, elementsOwnershipList); + addToCallList(statePropagatorDataPtr, elementCallList); // we have a full microstate at time t here! + addToCallListAndMove(std::move(propagatorVelocitiesAndPositions), elementCallList, elementsOwnershipList); + if (constr) + { + auto constraintElement = std::make_unique< ConstraintsElement >( + constr, statePropagatorDataPtr, energyElementPtr, MASTER(cr), + fplog, inputrec, mdAtoms->mdatoms()); + energySignallerBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + trajectoryElementBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + loggingSignallerBuilder->registerSignallerClient(compat::make_not_null(constraintElement.get())); + + addToCallListAndMove(std::move(constraintElement), elementCallList, elementsOwnershipList); + } + addToCallListAndMove(std::move(computeGlobalsElementAfterCoordinateUpdate), elementCallList, elementsOwnershipList); + addToCallList(energyElementPtr, elementCallList); // we have the energies at time t here! + } + else + { + gmx_fatal(FARGS, "Integrator not implemented for the modular simulator."); + } + + auto integrator = std::make_unique( + std::move(elementCallList), std::move(elementsOwnershipList)); + // std::move *should* not be needed with c++-14, but clang-3.6 still requires it + return std::move(integrator); +} + +void ModularSimulator::checkInputForDisabledFunctionality() +{ + GMX_RELEASE_ASSERT( + inputrec->eI == eiMD || inputrec->eI == eiVV, + "Only integrators md and md-vv are supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !doRerun, + "Rerun is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + startingBehavior == StartingBehavior::NewSimulation, + "Checkpointing is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + inputrec->etc == etcNO, + "Temperature coupling is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + inputrec->epc == epcNO, + "Pressure coupling is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !(inputrecNptTrotter(inputrec) || inputrecNphTrotter(inputrec) || inputrecNvtTrotter(inputrec)), + "Legacy Trotter decomposition is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + inputrec->efep == efepNO, + "Free energy calculation is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + vsite == nullptr, + "Virtual sites are not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !inputrec->bDoAwh, + "AWH is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + ms == nullptr, + "Multi-sim are not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + replExParams.exchangeInterval == 0, + "Replica exchange is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + fcd->disres.nsystems <= 1, + "Ensemble restraints are not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !doSimulatedAnnealing(inputrec), + "Simulated annealing is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !inputrec->bSimTemp, + "Simulated tempering is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !inputrec->bExpanded, + "Expanded ensemble simulations are not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !(opt2bSet("-ei", nfile, fnm) || observablesHistory->edsamHistory != nullptr), + "Essential dynamics is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + inputrec->eSwapCoords == eswapNO, + "Ion / water position swapping is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !inputrec->bIMD, + "Interactive MD is not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + membed == nullptr, + "Membrane embedding is not supported by the modular simulator."); + // TODO: Change this to the boolean passed when we merge the user interface change for the GPU update. + GMX_RELEASE_ASSERT( + getenv("GMX_UPDATE_CONSTRAIN_GPU") == nullptr, + "Integration on the GPU is not supported by the modular simulator."); + // Modular simulator is centered around NS updates + // TODO: think how to handle nstlist == 0 + GMX_RELEASE_ASSERT( + inputrec->nstlist != 0, + "Simulations without neighbor list update are not supported by the modular simulator."); + GMX_RELEASE_ASSERT( + !GMX_FAHCORE, + "GMX_FAHCORE not supported by the modular simulator."); +} + +SignallerCallbackPtr ModularSimulator::SignalHelper::registerLastStepCallback() +{ + return std::make_unique( + [this](Step step, Time gmx_unused time){this->lastStep_ = step; }); +} + +SignallerCallbackPtr ModularSimulator::SignalHelper::registerNSCallback() +{ + return std::make_unique( + [this](Step step, Time gmx_unused time) + {this->nextNSStep_ = step; }); +} +} diff --git a/src/gromacs/modularsimulator/modularsimulator.h b/src/gromacs/modularsimulator/modularsimulator.h new file mode 100644 index 0000000000..e3489f2d0a --- /dev/null +++ b/src/gromacs/modularsimulator/modularsimulator.h @@ -0,0 +1,321 @@ +/* + * This file is part of the GROMACS molecular simulation package. + * + * Copyright (c) 2019, by the GROMACS development team, led by + * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl, + * and including many others, as listed in the AUTHORS file in the + * top-level source directory and at http://www.gromacs.org. + * + * GROMACS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 + * of the License, or (at your option) any later version. + * + * GROMACS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with GROMACS; if not, see + * http://www.gnu.org/licenses, or write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * If you want to redistribute modifications to GROMACS, please + * consider that scientific software is very special. Version + * control is crucial - bugs must be traceable. We will be happy to + * consider code for inclusion in the official distribution, but + * derived work must not be called official GROMACS. Details are found + * in the README & COPYING files - if they are missing, get the + * official version at http://www.gromacs.org. + * + * To help us fund GROMACS development, we humbly ask that you cite + * the research papers on the package. Check out http://www.gromacs.org. + */ +/*! \libinternal + * \brief Declares the modular simulator + * + * \author Pascal Merz + * \ingroup module_modularsimulator + */ +#ifndef GROMACS_MODULARSIMULATOR_MODULARSIMULATOR_H +#define GROMACS_MODULARSIMULATOR_MODULARSIMULATOR_H + +#include + +#include "gromacs/mdlib/md_support.h" +#include "gromacs/mdlib/resethandler.h" +#include "gromacs/mdrun/isimulator.h" + +#include "domdechelper.h" +#include "modularsimulatorinterfaces.h" +#include "pmeloadbalancehelper.h" +#include "topologyholder.h" + +namespace gmx +{ +class DomDecHelper; +class EnergyElement; +class EnergySignaller; +class LoggingSignaller; +class StatePropagatorData; +class NeighborSearchSignaller; +class PmeLoadBalanceHelper; +class TrajectoryElementBuilder; + +//! \addtogroup module_modularsimulator +//! \{ + +/*! \libinternal + * \brief The modular simulator + * + * Based on the input given, this simulator builds independent elements and + * signallers and stores them in a respective vector. The run function + * runs the simulation by, in turn, building a task list from the elements + * for a predefined number of steps, then running the task list, and repeating + * until the stop criterion is fulfilled. + */ +class ModularSimulator final : + public ISimulator +{ + public: + //! Run the simulator + void run() override; + + // Only builder can construct + friend class SimulatorBuilder; + + private: + //! Constructor + template + explicit ModularSimulator(Args && ... args); + + /*! \brief The initialisation + * + * This builds all signallers and elements, and is responsible to put + * them in the correct order. + */ + void constructElementsAndSignallers(); + + /*! \brief A function called once before the simulation run + * + * This function collects calls which need to be made once at the + * beginning of the simulation run. These are mainly printing information + * to the user and starting the wall time. + */ + void simulatorSetup(); + + /*! \brief A function called once after the simulation run + * + * This function collects calls which need to be made once at the + * end of the simulation run. + */ + void simulatorTeardown(); + + /*! \brief A function called before every step + * + * This function collects calls which need to be made before every + * simulation step. The need for this function could likely be + * eliminated by rewriting the stop and reset handler to fit the + * modular simulator approach. + */ + void preStep(Step step, Time time, bool isNeighborSearchingStep); + + /*! \brief A function called after every step + * + * This function collects calls which need to be made after every + * simulation step. + */ + void postStep(Step step, Time time); + + /*! \brief Build the integrator part of the simulator + * + * This includes the force calculation, state propagation, constraints, + * global computation, and the points during the process at which valid + * micro state / energy states are found. Currently, buildIntegrator + * knows about NVE md and md-vv algorithms. + */ + std::unique_ptr buildIntegrator( + SignallerBuilder *neighborSearchSignallerBuilder, + SignallerBuilder *energySignallerBuilder, + SignallerBuilder *loggingSignallerBuilder, + TrajectoryElementBuilder *trajectoryElementBuilder, + CheckBondedInteractionsCallbackPtr *checkBondedInteractionsCallback, + compat::not_null statePropagatorDataPtr, + compat::not_null energyElementPtr); + + //! Build the force element - can be normal forces or shell / flex constraints + std::unique_ptr buildForces( + SignallerBuilder *neighborSearchSignallerBuilder, + SignallerBuilder *energySignallerBuilder, + StatePropagatorData *statePropagatorDataPtr, + EnergyElement *energyElementPtr); + + /*! \brief Add run functions to the task queue + * + * This function calls PME load balancing and domain decomposition first, + * and then queries the elements for all steps of the life time of the + * current neighbor list for their run functions. When the next step + * would be a neighbor-searching step, this function returns, such that + * the simulator can run the pre-computed task list before updating the + * neighbor list and re-filling the task list. + * + * TODO: In the current approach, unique pointers to tasks are created + * repeatedly. While preliminary measures do not seem to indicate + * performance problems, a pool allocator would be an obvious + * optimization possibility, as would reusing the task queue if + * the task queue is periodic around the rebuild point (i.e. the + * task queue is identical between rebuilds). + */ + void populateTaskQueue(); + + //! Check for disabled functionality (during construction time) + void checkInputForDisabledFunctionality(); + + //! The run queue + std::queue taskQueue_; + + /* Note that the Simulator is owning the signallers and elements. + * The ownership list and the call list are kept separate, however, + * to allow to have elements more than once in the call lists - + * either as signaller AND element (such as the TrajectoryElement), + * or to have an element twice in the scheduling sequence (currently + * not used). + * + * For the elements, the setup and teardown is applied on the + * elementsOwnershipList_, to ensure that it is called only once per + * element. For the signallers, the setup is applied on the + * signallerCallList_ - this makes sure that both the elementSetup() + * and signallerSetup() of an object being both an element and a + * signaller is called. It is also not expected to run have a signaller + * more than once in the signallerCallList_, so we don't have to worry + * about calling the setup method twice. Consequently, this means that + * objects being both a signaller and an element should be stored in + * the elementsOwnershipList_. + */ + //! List of signalers (ownership) + std::vector< std::unique_ptr > signallersOwnershipList_; + //! List of signalers (calling sequence) + std::vector< compat::not_null > signallerCallList_; + //! List of schedulerElements (ownership) + std::vector< std::unique_ptr > elementsOwnershipList_; + //! List of schedulerElements (calling sequence) + std::vector< compat::not_null > elementCallList_; + + //! \cond + //! Helper function to add elements or signallers to the call list via raw pointer + template + static void addToCallList ( + compat::not_null element, + std::vector< compat::not_null > &callList); + //! Helper function to add elements or signallers to the call list via smart pointer + template + static void addToCallList( + std::unique_ptr &element, + std::vector< compat::not_null > &callList); + /*! \brief Helper function to add elements or signallers to the call list + * and move the ownership to the ownership list + */ + template + static void addToCallListAndMove( + std::unique_ptr element, + std::vector< compat::not_null > &callList, + std::vector< std::unique_ptr > &elementList); + //! \endcond + + // Infrastructure elements + //! The domain decomposition element + std::unique_ptr domDecHelper_; + //! The PME load balancing element + std::unique_ptr pmeLoadBalanceHelper_; + //! The stop handler + std::unique_ptr stopHandler_; + //! The reset handler + std::unique_ptr resetHandler_; + //! Signal vector (used by stop / reset / checkpointing signaller) + SimulationSignals signals_; + //! Compute globals communication period + int nstglobalcomm_; + + //! The topology + std::unique_ptr topologyHolder_; + + //! The current step + Step step_ = -1; + + /*! \internal + * \brief Signal helper + * + * The simulator needs to know about the last step and the + * neighbor searching step, which are determined in signallers. + * This object can be registered to the signals and accessed by + * the methods of the simulator. + */ + class SignalHelper : + public ILastStepSignallerClient, + public INeighborSearchSignallerClient + { + public: + //! The last step + Step lastStep_ = std::numeric_limits::max(); + //! The next NS step + Step nextNSStep_ = -1; + //! ILastStepSignallerClient implementation + SignallerCallbackPtr registerLastStepCallback() override; + //! INeighborSearchSignallerClient implementation + SignallerCallbackPtr registerNSCallback() override; + }; + //! The signal helper object + std::unique_ptr signalHelper_; + + // TODO: This is a hack for stop handler - needs to go once StopHandler + // is adapted to the modular simulator + //! Whether this is a neighbor-searching step + bool stophandlerIsNSStep_ = false; + //! The current step + Step stophandlerCurrentStep_ = -1; +}; + +//! \} + +//! Constructor implementation (here to avoid template-related linker problems) +template +ModularSimulator::ModularSimulator(Args && ... args) : + ISimulator(std::forward(args) ...) +{ + nstglobalcomm_ = computeGlobalCommunicationPeriod(mdlog, inputrec, cr); + signalHelper_ = std::make_unique(); + checkInputForDisabledFunctionality(); +} + +//! \cond +template +void ModularSimulator::addToCallList( + gmx::compat::not_null element, + std::vector < compat::not_null < T *>> &callList) +{ + callList.emplace_back(element); +} + +template +void ModularSimulator::addToCallList( + std::unique_ptr &element, + std::vector < compat::not_null < T *>> &callList) +{ + callList.emplace_back(compat::make_not_null(element.get())); +} + +template +void ModularSimulator::addToCallListAndMove( + std::unique_ptr element, + std::vector < compat::not_null < T *>> &callList, + std::vector < std::unique_ptr < T>> &elementList) +{ + callList.emplace_back(compat::make_not_null(element.get())); + elementList.emplace_back(std::move(element)); +} +//! \endcond + +} // namespace gmx + +#endif // GROMACS_MODULARSIMULATOR_MODULARSIMULATOR_H diff --git a/src/programs/mdrun/tests/trajectorycomparison.cpp b/src/programs/mdrun/tests/trajectorycomparison.cpp index 20d99f60be..de5ea639e1 100644 --- a/src/programs/mdrun/tests/trajectorycomparison.cpp +++ b/src/programs/mdrun/tests/trajectorycomparison.cpp @@ -248,7 +248,7 @@ const TrajectoryTolerances TrajectoryComparison::s_defaultTrajectoryTolerances { defaultRealTolerance(), // box relativeToleranceAsFloatingPoint(1.0, 1.0e-3), // positions defaultRealTolerance(), // velocities - relativeToleranceAsFloatingPoint(100.0, GMX_DOUBLE ? 1.0e-7 : 1.0e-5) // forces + relativeToleranceAsFloatingPoint(100.0, GMX_DOUBLE ? 1.0e-7 : 5.0e-5) // forces }; -- 2.11.4.GIT