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 Defines the modular simulator algorithm
38 * \author Pascal Merz <pascal.merz@me.com>
39 * \ingroup module_modularsimulator
44 #include "simulatoralgorithm.h"
46 #include "gromacs/commandline/filenm.h"
47 #include "gromacs/domdec/domdec.h"
48 #include "gromacs/ewald/pme.h"
49 #include "gromacs/ewald/pme_load_balancing.h"
50 #include "gromacs/ewald/pme_pp.h"
51 #include "gromacs/gmxlib/nrnb.h"
52 #include "gromacs/listed_forces/listed_forces.h"
53 #include "gromacs/mdlib/checkpointhandler.h"
54 #include "gromacs/mdlib/constr.h"
55 #include "gromacs/mdlib/energyoutput.h"
56 #include "gromacs/mdlib/md_support.h"
57 #include "gromacs/mdlib/mdatoms.h"
58 #include "gromacs/mdlib/resethandler.h"
59 #include "gromacs/mdlib/stat.h"
60 #include "gromacs/mdrun/replicaexchange.h"
61 #include "gromacs/mdrun/shellfc.h"
62 #include "gromacs/mdrunutility/freeenergy.h"
63 #include "gromacs/mdrunutility/handlerestart.h"
64 #include "gromacs/mdrunutility/printtime.h"
65 #include "gromacs/mdtypes/commrec.h"
66 #include "gromacs/mdtypes/fcdata.h"
67 #include "gromacs/mdtypes/forcerec.h"
68 #include "gromacs/mdtypes/inputrec.h"
69 #include "gromacs/mdtypes/mdatom.h"
70 #include "gromacs/mdtypes/mdrunoptions.h"
71 #include "gromacs/mdtypes/observableshistory.h"
72 #include "gromacs/nbnxm/nbnxm.h"
73 #include "gromacs/timing/walltime_accounting.h"
74 #include "gromacs/topology/topology.h"
75 #include "gromacs/utility/cstringutil.h"
76 #include "gromacs/utility/fatalerror.h"
78 #include "checkpointhelper.h"
79 #include "domdechelper.h"
80 #include "energydata.h"
81 #include "firstorderpressurecoupling.h"
82 #include "freeenergyperturbationdata.h"
83 #include "modularsimulator.h"
84 #include "pmeloadbalancehelper.h"
85 #include "propagator.h"
86 #include "referencetemperaturemanager.h"
87 #include "statepropagatordata.h"
91 ModularSimulatorAlgorithm::ModularSimulatorAlgorithm(std::string topologyName,
94 const MDLogger& mdlog,
95 const MdrunOptions& mdrunOptions,
96 const t_inputrec* inputrec,
98 gmx_wallcycle* wcycle,
100 gmx_walltime_accounting* walltime_accounting) :
101 taskIterator_(taskQueue_.end()),
102 statePropagatorData_(nullptr),
103 energyData_(nullptr),
104 freeEnergyPerturbationData_(nullptr),
107 topologyName_(std::move(topologyName)),
111 mdrunOptions(mdrunOptions),
116 walltime_accounting(walltime_accounting)
118 signalHelper_ = std::make_unique<SignalHelper>();
121 void ModularSimulatorAlgorithm::setup()
124 for (auto& signaller : signallerList_)
130 domDecHelper_->setup();
133 for (auto& element : elementSetupTeardownList_)
135 element->elementSetup();
137 statePropagatorData_->setup();
138 if (pmeLoadBalanceHelper_)
140 // State must have been initialized so pmeLoadBalanceHelper_ gets a valid box
141 pmeLoadBalanceHelper_->setup();
145 const SimulatorRunFunction* ModularSimulatorAlgorithm::getNextTask()
147 if (!taskQueue_.empty())
151 if (taskIterator_ == taskQueue_.end())
158 taskIterator_ = taskQueue_.begin();
160 return &*taskIterator_;
163 void ModularSimulatorAlgorithm::updateTaskQueue()
165 // For now, we'll just clean the task queue and then re-populate
166 // TODO: If tasks are periodic around updates of the task queue,
167 // we should reuse it instead
172 void ModularSimulatorAlgorithm::teardown()
174 for (auto& element : elementSetupTeardownList_)
176 element->elementTeardown();
178 energyData_->teardown();
179 if (pmeLoadBalanceHelper_)
181 pmeLoadBalanceHelper_->teardown();
186 void ModularSimulatorAlgorithm::simulatorSetup()
188 if (!mdrunOptions.writeConfout)
190 // This is on by default, and the main known use case for
191 // turning it off is for convenience in benchmarking, which is
192 // something that should not show up in the general user
197 "The -noconfout functionality is deprecated, and "
198 "may be removed in a future version.");
203 char sbuf[STEPSTRSIZE], sbuf2[STEPSTRSIZE];
204 std::string timeString;
205 fprintf(stderr, "starting mdrun '%s'\n", topologyName_.c_str());
206 if (inputrec->nsteps >= 0)
208 timeString = formatString(
209 "%8.1f", static_cast<double>(inputrec->init_step + inputrec->nsteps) * inputrec->delta_t);
213 timeString = "infinite";
215 if (inputrec->init_step > 0)
218 "%s steps, %s ps (continuing from step %s, %8.1f ps).\n",
219 gmx_step_str(inputrec->init_step + inputrec->nsteps, sbuf),
221 gmx_step_str(inputrec->init_step, sbuf2),
222 inputrec->init_step * inputrec->delta_t);
226 fprintf(stderr, "%s steps, %s ps.\n", gmx_step_str(inputrec->nsteps, sbuf), timeString.c_str());
228 fprintf(fplog, "\n");
231 walltime_accounting_start_time(walltime_accounting);
232 wallcycle_start(wcycle, WallCycleCounter::Run);
233 print_start(fplog, cr, walltime_accounting, "mdrun");
235 step_ = inputrec->init_step;
238 void ModularSimulatorAlgorithm::simulatorTeardown()
241 // Stop measuring walltime
242 walltime_accounting_end_time(walltime_accounting);
244 if (!thisRankHasDuty(cr, DUTY_PME))
246 /* Tell the PME only node to finish */
247 gmx_pme_send_finish(cr);
250 walltime_accounting_set_nsteps_done(walltime_accounting, step_ - inputrec->init_step);
253 void ModularSimulatorAlgorithm::preStep(Step step, Time gmx_unused time, bool isNeighborSearchingStep)
255 if (stopHandler_->stoppingAfterCurrentStep(isNeighborSearchingStep) && step != signalHelper_->lastStep_)
258 * Stop handler wants to stop after the current step, which was
259 * not known when building the current task queue. This happens
260 * e.g. when a stop is signalled by OS. We therefore want to purge
261 * the task queue now, and re-schedule this step as last step.
270 resetHandler_->setSignal(walltime_accounting);
271 // This is a hack to avoid having to rewrite StopHandler to be a NeighborSearchSignaller
272 // and accept the step as input. Eventually, we want to do that, but currently this would
273 // require introducing NeighborSearchSignaller in the legacy do_md or a lot of code
275 stophandlerIsNSStep_ = isNeighborSearchingStep;
276 stophandlerCurrentStep_ = step;
277 stopHandler_->setSignal();
279 wallcycle_start(wcycle, WallCycleCounter::Step);
282 void ModularSimulatorAlgorithm::postStep(Step step, Time gmx_unused time)
287 if (do_per_step(step, inputrec->nstlog))
289 if (fflush(fplog) != 0)
291 gmx_fatal(FARGS, "Cannot flush logfile - maybe you are out of disk space?");
295 const bool do_verbose = mdrunOptions.verbose
296 && (step % mdrunOptions.verboseStepPrintInterval == 0
297 || step == inputrec->init_step || step == signalHelper_->lastStep_);
298 // Print the remaining wall clock time for the run
299 if (MASTER(cr) && (do_verbose || gmx_got_usr_signal())
300 && !(pmeLoadBalanceHelper_ && pmeLoadBalanceHelper_->pmePrinting()))
302 print_time(stderr, walltime_accounting, step, inputrec, cr);
305 double cycles = wallcycle_stop(wcycle, WallCycleCounter::Step);
306 if (haveDDAtomOrdering(*cr) && wcycle)
308 dd_cycles_add(cr->dd, static_cast<float>(cycles), ddCyclStep);
311 resetHandler_->resetCounters(step,
312 step - inputrec->init_step,
319 pmeLoadBalanceHelper_ ? pmeLoadBalanceHelper_->loadBalancingObject() : nullptr,
321 walltime_accounting);
324 void ModularSimulatorAlgorithm::populateTaskQueue()
327 * The registerRunFunction emplaces functions to the task queue.
328 * All elements are owned by the ModularSimulatorAlgorithm, as is the task queue.
329 * Elements can hence register lambdas capturing their `this` pointers without expecting
330 * life time issues, as the task queue and the elements are in the same scope.
332 auto registerRunFunction = [this](SimulatorRunFunction function) {
333 taskQueue_.emplace_back(std::move(function));
336 Time startTime = inputrec->init_t;
337 Time timeStep = inputrec->delta_t;
338 Time time = startTime + step_ * timeStep;
340 // Run an initial call to the signallers
341 for (auto& signaller : signallerList_)
343 signaller->signal(step_, time);
346 if (checkpointHelper_)
348 checkpointHelper_->run(step_, time);
351 if (pmeLoadBalanceHelper_)
353 pmeLoadBalanceHelper_->run(step_, time);
357 domDecHelper_->run(step_, time);
362 // local variables for lambda capturing
363 const int step = step_;
364 const bool isNSStep = step == signalHelper_->nextNSStep_;
366 // register pre-step (task queue is local, so no problem with `this`)
367 registerRunFunction([this, step, time, isNSStep]() { preStep(step, time, isNSStep); });
368 // register pre step functions
369 for (const auto& schedulingFunction : preStepScheduling_)
371 schedulingFunction(step_, time, registerRunFunction);
373 // register elements for step
374 for (auto& element : elementCallList_)
376 element->scheduleTask(step_, time, registerRunFunction);
378 // register post step functions
379 for (const auto& schedulingFunction : postStepScheduling_)
381 schedulingFunction(step_, time, registerRunFunction);
383 // register post-step (task queue is local, so no problem with `this`)
384 registerRunFunction([this, step, time]() { postStep(step, time); });
388 time = startTime + step_ * timeStep;
389 for (auto& signaller : signallerList_)
391 signaller->signal(step_, time);
393 } while (step_ != signalHelper_->nextNSStep_ && step_ <= signalHelper_->lastStep_);
395 runFinished_ = (step_ > signalHelper_->lastStep_);
399 // task queue is local, so no problem with `this`
400 registerRunFunction([this]() { teardown(); });
404 ModularSimulatorAlgorithmBuilder::ModularSimulatorAlgorithmBuilder(
405 compat::not_null<LegacySimulatorData*> legacySimulatorData,
406 std::unique_ptr<ReadCheckpointDataHolder> checkpointDataHolder) :
407 legacySimulatorData_(legacySimulatorData),
408 signals_(std::make_unique<SimulationSignals>()),
409 elementAdditionHelper_(this),
410 globalCommunicationHelper_(computeGlobalCommunicationPeriod(legacySimulatorData->mdlog,
411 legacySimulatorData->inputrec,
412 legacySimulatorData->cr),
414 observablesReducer_(legacySimulatorData->observablesReducerBuilder->build()),
415 checkpointHelperBuilder_(std::move(checkpointDataHolder),
416 legacySimulatorData->startingBehavior,
417 legacySimulatorData->cr)
419 if (legacySimulatorData->inputrec->efep != FreeEnergyPerturbationType::No)
421 freeEnergyPerturbationData_ = std::make_unique<FreeEnergyPerturbationData>(
422 legacySimulatorData->fplog, *legacySimulatorData->inputrec, legacySimulatorData->mdAtoms);
423 registerExistingElement(freeEnergyPerturbationData_->element());
426 statePropagatorData_ = std::make_unique<StatePropagatorData>(
427 legacySimulatorData->top_global.natoms,
428 legacySimulatorData->fplog,
429 legacySimulatorData->cr,
430 legacySimulatorData->state_global,
431 legacySimulatorData->state,
432 legacySimulatorData->fr->nbv->useGpu(),
433 legacySimulatorData->fr->bMolPBC,
434 legacySimulatorData->mdrunOptions.writeConfout,
435 opt2fn("-c", legacySimulatorData->nfile, legacySimulatorData->fnm),
436 legacySimulatorData->inputrec,
437 legacySimulatorData->mdAtoms->mdatoms(),
438 legacySimulatorData->top_global);
439 registerExistingElement(statePropagatorData_->element());
441 // Multi sim is turned off
442 const bool simulationsShareState = false;
444 energyData_ = std::make_unique<EnergyData>(statePropagatorData_.get(),
445 freeEnergyPerturbationData_.get(),
446 legacySimulatorData->top_global,
447 legacySimulatorData->inputrec,
448 legacySimulatorData->mdAtoms,
449 legacySimulatorData->enerd,
450 legacySimulatorData->ekind,
451 legacySimulatorData->constr,
452 legacySimulatorData->fplog,
453 legacySimulatorData->fr->fcdata.get(),
454 legacySimulatorData->mdModulesNotifiers,
455 MASTER(legacySimulatorData->cr),
456 legacySimulatorData->observablesHistory,
457 legacySimulatorData->startingBehavior,
458 simulationsShareState,
459 legacySimulatorData->pull_work);
460 registerExistingElement(energyData_->element());
462 // This is the only modular simulator object which changes the inputrec
463 // TODO: Avoid changing inputrec (#3854)
465 "ReferenceTemperatureManager",
466 ReferenceTemperatureManager(const_cast<t_inputrec*>(legacySimulatorData->inputrec)));
467 auto* referenceTemperatureManager =
468 simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
470 // State propagator data is scaling velocities if reference temperature is updated
471 auto* statePropagatorDataPtr = statePropagatorData_.get();
472 referenceTemperatureManager->registerUpdateCallback(
473 [statePropagatorDataPtr](ArrayRef<const real> temperatures,
474 ReferenceTemperatureChangeAlgorithm algorithm) {
475 statePropagatorDataPtr->updateReferenceTemperature(temperatures, algorithm);
479 ModularSimulatorAlgorithm ModularSimulatorAlgorithmBuilder::build()
481 if (algorithmHasBeenBuilt_)
483 GMX_THROW(SimulationAlgorithmSetupError(
484 "Tried to build ModularSimulationAlgorithm more than once."));
486 algorithmHasBeenBuilt_ = true;
488 // Connect propagators with thermostat / barostat
489 for (const auto& registrationFunction : pressureTemperatureControlRegistrationFunctions_)
491 for (const auto& connection : propagatorConnections_)
493 registrationFunction(connection);
497 ModularSimulatorAlgorithm algorithm(*(legacySimulatorData_->top_global.name),
498 legacySimulatorData_->fplog,
499 legacySimulatorData_->cr,
500 legacySimulatorData_->mdlog,
501 legacySimulatorData_->mdrunOptions,
502 legacySimulatorData_->inputrec,
503 legacySimulatorData_->nrnb,
504 legacySimulatorData_->wcycle,
505 legacySimulatorData_->fr,
506 legacySimulatorData_->walltime_accounting);
507 registerWithInfrastructureAndSignallers(algorithm.signalHelper_.get());
508 algorithm.statePropagatorData_ = std::move(statePropagatorData_);
509 algorithm.energyData_ = std::move(energyData_);
510 algorithm.freeEnergyPerturbationData_ = std::move(freeEnergyPerturbationData_);
511 algorithm.signals_ = std::move(signals_);
512 algorithm.simulationData_ = std::move(simulationData_);
514 // Multi sim is turned off
515 const bool simulationsShareState = false;
517 // Build stop handler
518 algorithm.stopHandler_ = legacySimulatorData_->stopHandlerBuilder->getStopHandlerMD(
519 compat::not_null<SimulationSignal*>(
520 &(*globalCommunicationHelper_.simulationSignals())[eglsSTOPCOND]),
521 simulationsShareState,
522 MASTER(legacySimulatorData_->cr),
523 legacySimulatorData_->inputrec->nstlist,
524 legacySimulatorData_->mdrunOptions.reproducible,
525 globalCommunicationHelper_.nstglobalcomm(),
526 legacySimulatorData_->mdrunOptions.maximumHoursToRun,
527 legacySimulatorData_->inputrec->nstlist == 0,
528 legacySimulatorData_->fplog,
529 algorithm.stophandlerCurrentStep_,
530 algorithm.stophandlerIsNSStep_,
531 legacySimulatorData_->walltime_accounting);
533 // Build reset handler
534 const bool simulationsShareResetCounters = false;
535 algorithm.resetHandler_ = std::make_unique<ResetHandler>(
536 compat::make_not_null<SimulationSignal*>(
537 &(*globalCommunicationHelper_.simulationSignals())[eglsRESETCOUNTERS]),
538 simulationsShareResetCounters,
539 legacySimulatorData_->inputrec->nsteps,
540 MASTER(legacySimulatorData_->cr),
541 legacySimulatorData_->mdrunOptions.timingOptions.resetHalfway,
542 legacySimulatorData_->mdrunOptions.maximumHoursToRun,
543 legacySimulatorData_->mdlog,
544 legacySimulatorData_->wcycle,
545 legacySimulatorData_->walltime_accounting);
547 // Build topology holder
548 algorithm.topologyHolder_ = topologyHolderBuilder_.build(legacySimulatorData_->top_global,
549 legacySimulatorData_->top,
550 legacySimulatorData_->cr,
551 legacySimulatorData_->inputrec,
552 legacySimulatorData_->fr,
553 legacySimulatorData_->mdAtoms,
554 legacySimulatorData_->constr,
555 legacySimulatorData_->vsite);
556 registerWithInfrastructureAndSignallers(algorithm.topologyHolder_.get());
558 // Build PME load balance helper
559 if (PmeLoadBalanceHelper::doPmeLoadBalancing(legacySimulatorData_->mdrunOptions,
560 legacySimulatorData_->inputrec,
561 legacySimulatorData_->fr))
563 algorithm.pmeLoadBalanceHelper_ =
564 std::make_unique<PmeLoadBalanceHelper>(legacySimulatorData_->mdrunOptions.verbose,
565 algorithm.statePropagatorData_.get(),
566 legacySimulatorData_->fplog,
567 legacySimulatorData_->cr,
568 legacySimulatorData_->mdlog,
569 legacySimulatorData_->inputrec,
570 legacySimulatorData_->wcycle,
571 legacySimulatorData_->fr);
572 registerWithInfrastructureAndSignallers(algorithm.pmeLoadBalanceHelper_.get());
575 // Build trajectory element
576 auto trajectoryElement = trajectoryElementBuilder_.build(legacySimulatorData_->fplog,
577 legacySimulatorData_->nfile,
578 legacySimulatorData_->fnm,
579 legacySimulatorData_->mdrunOptions,
580 legacySimulatorData_->cr,
581 legacySimulatorData_->outputProvider,
582 legacySimulatorData_->mdModulesNotifiers,
583 legacySimulatorData_->inputrec,
584 legacySimulatorData_->top_global,
585 legacySimulatorData_->oenv,
586 legacySimulatorData_->wcycle,
587 legacySimulatorData_->startingBehavior,
588 simulationsShareState);
589 registerWithInfrastructureAndSignallers(trajectoryElement.get());
591 // Build domdec helper (free energy element is a client, so keep this after it is built)
592 if (haveDDAtomOrdering(*legacySimulatorData_->cr))
594 algorithm.domDecHelper_ =
595 domDecHelperBuilder_.build(legacySimulatorData_->mdrunOptions.verbose,
596 legacySimulatorData_->mdrunOptions.verboseStepPrintInterval,
597 algorithm.statePropagatorData_.get(),
598 algorithm.topologyHolder_.get(),
599 globalCommunicationHelper_.nstglobalcomm(),
600 legacySimulatorData_->fplog,
601 legacySimulatorData_->cr,
602 legacySimulatorData_->mdlog,
603 legacySimulatorData_->constr,
604 legacySimulatorData_->inputrec,
605 legacySimulatorData_->mdAtoms,
606 legacySimulatorData_->nrnb,
607 legacySimulatorData_->wcycle,
608 legacySimulatorData_->fr,
609 legacySimulatorData_->vsite,
610 legacySimulatorData_->imdSession,
611 legacySimulatorData_->pull_work);
612 registerWithInfrastructureAndSignallers(algorithm.domDecHelper_.get());
614 // Build checkpoint helper (do this last so everyone else can be a checkpoint client!)
616 checkpointHelperBuilder_.setCheckpointHandler(std::make_unique<CheckpointHandler>(
617 compat::make_not_null<SimulationSignal*>(&(*algorithm.signals_)[eglsCHKPT]),
618 simulationsShareState,
619 legacySimulatorData_->inputrec->nstlist == 0,
620 MASTER(legacySimulatorData_->cr),
621 legacySimulatorData_->mdrunOptions.writeConfout,
622 legacySimulatorData_->mdrunOptions.checkpointOptions.period));
623 algorithm.checkpointHelper_ =
624 checkpointHelperBuilder_.build(legacySimulatorData_->inputrec->init_step,
625 trajectoryElement.get(),
626 legacySimulatorData_->fplog,
627 legacySimulatorData_->cr,
628 legacySimulatorData_->observablesHistory,
629 legacySimulatorData_->walltime_accounting,
630 legacySimulatorData_->state_global,
631 legacySimulatorData_->mdrunOptions.writeConfout);
632 registerWithInfrastructureAndSignallers(algorithm.checkpointHelper_.get());
637 /* Signallers need to be called in an exact order. Some signallers are clients
638 * of other signallers, which requires the clients signallers to be called
639 * _after_ any signaller they are registered to - otherwise, they couldn't
640 * adapt their behavior to the information they got signalled.
642 * Signallers being clients of other signallers require registration.
643 * That registration happens during construction, which in turn means that
644 * we want to construct the signallers in the reverse order of their later
647 * For the above reasons, the `addSignaller` lambda defined below emplaces
648 * added signallers at the beginning of the signaller list, which will yield
649 * a signaller list which is inverse to the build order (and hence equal to
650 * the intended call order).
652 auto addSignaller = [this, &algorithm](auto signaller) {
653 registerWithInfrastructureAndSignallers(signaller.get());
654 algorithm.signallerList_.emplace(algorithm.signallerList_.begin(), std::move(signaller));
656 const auto* inputrec = legacySimulatorData_->inputrec;
657 auto virialMode = EnergySignallerVirialMode::Off;
658 if (inputrec->epc != PressureCoupling::No)
660 if (EI_VV(inputrec->eI))
662 virialMode = EnergySignallerVirialMode::OnStepAndNext;
666 virialMode = EnergySignallerVirialMode::OnStep;
669 addSignaller(energySignallerBuilder_.build(
670 inputrec->nstcalcenergy,
671 computeFepPeriod(*inputrec, legacySimulatorData_->replExParams),
672 inputrec->nstpcouple,
674 addSignaller(trajectorySignallerBuilder_.build(inputrec->nstxout,
677 inputrec->nstxout_compressed,
678 trajectoryElement->tngBoxOut(),
679 trajectoryElement->tngLambdaOut(),
680 trajectoryElement->tngBoxOutCompressed(),
681 trajectoryElement->tngLambdaOutCompressed(),
682 inputrec->nstenergy));
683 addSignaller(loggingSignallerBuilder_.build(
684 inputrec->nstlog, inputrec->init_step, legacySimulatorData_->startingBehavior));
685 addSignaller(lastStepSignallerBuilder_.build(
686 inputrec->nsteps, inputrec->init_step, algorithm.stopHandler_.get()));
687 addSignaller(neighborSearchSignallerBuilder_.build(
688 inputrec->nstlist, inputrec->init_step, inputrec->init_t));
691 // Move setup / teardown list
692 algorithm.elementSetupTeardownList_ = std::move(setupAndTeardownList_);
693 // Move pre- / post-step scheduling lists
694 algorithm.preStepScheduling_ = std::move(preStepScheduling_);
695 algorithm.postStepScheduling_ = std::move(postStepScheduling_);
697 // Create element list
698 // Checkpoint helper needs to be in the call list (as first element!) to react to last step
699 algorithm.elementCallList_.emplace_back(algorithm.checkpointHelper_.get());
700 // Next, update the free energy lambda vector if needed
701 if (algorithm.freeEnergyPerturbationData_)
703 algorithm.elementCallList_.emplace_back(algorithm.freeEnergyPerturbationData_->element());
705 // Then, move the built algorithm
706 algorithm.elementsOwnershipList_.insert(algorithm.elementsOwnershipList_.end(),
707 std::make_move_iterator(elements_.begin()),
708 std::make_move_iterator(elements_.end()));
709 algorithm.elementCallList_.insert(algorithm.elementCallList_.end(),
710 std::make_move_iterator(callList_.begin()),
711 std::make_move_iterator(callList_.end()));
712 // Finally, all trajectory writing is happening after the step
713 // (relevant data was stored by elements through energy signaller)
714 algorithm.elementsOwnershipList_.emplace_back(std::move(trajectoryElement));
715 algorithm.elementCallList_.emplace_back(algorithm.elementsOwnershipList_.back().get());
716 algorithm.elementSetupTeardownList_.emplace_back(algorithm.elementsOwnershipList_.back().get());
722 bool ModularSimulatorAlgorithmBuilder::elementExists(const ISimulatorElement* element) const
724 // Check whether element exists in element list
725 if (std::any_of(elements_.begin(), elements_.end(), [element](auto& existingElement) {
726 return element == existingElement.get();
731 // Check whether element exists in other places controlled by *this
732 return ((statePropagatorData_ && statePropagatorData_->element() == element)
733 || (energyData_ && energyData_->element() == element)
734 || (freeEnergyPerturbationData_ && freeEnergyPerturbationData_->element() == element));
737 std::optional<SignallerCallback> ModularSimulatorAlgorithm::SignalHelper::registerLastStepCallback()
739 return [this](Step step, Time gmx_unused time) { this->lastStep_ = step; };
742 std::optional<SignallerCallback> ModularSimulatorAlgorithm::SignalHelper::registerNSCallback()
744 return [this](Step step, Time gmx_unused time) { this->nextNSStep_ = step; };
747 GlobalCommunicationHelper::GlobalCommunicationHelper(int nstglobalcomm, SimulationSignals* simulationSignals) :
748 nstglobalcomm_(nstglobalcomm), simulationSignals_(simulationSignals)
752 int GlobalCommunicationHelper::nstglobalcomm() const
754 return nstglobalcomm_;
757 SimulationSignals* GlobalCommunicationHelper::simulationSignals()
759 return simulationSignals_;
762 ModularSimulatorAlgorithmBuilderHelper::ModularSimulatorAlgorithmBuilderHelper(
763 ModularSimulatorAlgorithmBuilder* builder) :
768 bool ModularSimulatorAlgorithmBuilderHelper::elementIsStored(const ISimulatorElement* element) const
770 return builder_->elementExists(element);
773 [[maybe_unused]] void ModularSimulatorAlgorithmBuilderHelper::registerPreStepScheduling(SchedulingFunction schedulingFunction)
775 builder_->preStepScheduling_.emplace_back(std::move(schedulingFunction));
778 [[maybe_unused]] void ModularSimulatorAlgorithmBuilderHelper::registerPostStepScheduling(SchedulingFunction schedulingFunction)
780 builder_->postStepScheduling_.emplace_back(std::move(schedulingFunction));
783 std::optional<std::any> ModularSimulatorAlgorithmBuilderHelper::builderData(const std::string& key) const
785 const auto iter = builder_->builderData_.find(key);
786 if (iter == builder_->builderData_.end())
796 void ModularSimulatorAlgorithmBuilderHelper::registerTemperaturePressureControl(
797 std::function<void(const PropagatorConnection&)> registrationFunction)
799 builder_->pressureTemperatureControlRegistrationFunctions_.emplace_back(std::move(registrationFunction));
802 void ModularSimulatorAlgorithmBuilderHelper::registerPropagator(PropagatorConnection connectionData)
804 builder_->propagatorConnections_.emplace_back(std::move(connectionData));
807 void ModularSimulatorAlgorithmBuilderHelper::registerReferenceTemperatureUpdate(
808 ReferenceTemperatureCallback referenceTemperatureCallback)
810 auto* referenceTemperatureManager =
811 simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
812 referenceTemperatureManager->registerUpdateCallback(std::move(referenceTemperatureCallback));
815 ReferenceTemperatureCallback ModularSimulatorAlgorithmBuilderHelper::changeReferenceTemperatureCallback()
817 // Capture is safe because SimulatorAlgorithm will manage life time of both the
818 // recipient of the callback and the reference temperature manager
819 auto* referenceTemperatureManager =
820 simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
821 return [referenceTemperatureManager](ArrayRef<const real> temperatures,
822 ReferenceTemperatureChangeAlgorithm algorithm) {
823 referenceTemperatureManager->setReferenceTemperature(temperatures, algorithm);