2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2018, 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.
38 #include "gromacs/math/functions.h"
39 #include "gromacs/math/vectypes.h"
40 #include "gromacs/restraint/restraintpotential.h"
41 #include "gromacs/utility.h"
43 #include "gmxapi/context.h"
44 #include "gmxapi/md.h"
45 #include "gmxapi/session.h"
46 #include "gmxapi/session/resources.h"
47 #include "gmxapi/status.h"
48 #include "gmxapi/system.h"
49 #include "gmxapi/md/mdmodule.h"
50 #include "gmxapi/md/mdsignals.h"
52 #include "testingconfiguration.h"
64 * \brief Restraint that can optionally issue an immediate stop signal.
66 class StopSignalIssuer : public gmx::IRestraintPotential
70 * \brief Construct a restraint that does nothing.
72 StopSignalIssuer() : StopSignalIssuer(false) {}
75 * \brief Choose whether or not to issue stop signal when called.
77 * \param sendStopSignal If true, issue stop signal at every opportunity.
79 explicit StopSignalIssuer(bool sendStopSignal) : sendStopSignal_{ sendStopSignal } {}
81 /*! \cond Implement IRestraintPotential */
82 gmx::PotentialPointData evaluate(gmx::Vector /* r_site */, gmx::Vector /* r_ref */, double t) override
84 // Note that evaluate gets called once for each site,
85 // which is twice per time step for a pair restraint.
86 // The following initialization logic is not atomic, but it is sufficient.
89 // Force is also calculated for initial step.
90 simulationStartTime_ = t;
92 lastSimulationTime_ = t;
96 auto signalSender = gmxapi::getMdrunnerSignal(resources_, gmxapi::md::signals::STOP);
100 return { { 0., 0., 0. }, 0. };
103 std::vector<int> sites() const override { return { { 0, 1 } }; }
105 void bindSession(gmxapi::SessionResources* resources) override { resources_ = resources; }
109 * \brief Note simulation start time when called on the zeroeth step.
111 double simulationStartTime_ = 0.;
114 * \brief Record the simulation time at the last step active.
116 std::optional<double> lastSimulationTime_;
119 * \brief Whether restraint was ever used
121 bool isInitialized() const { return lastSimulationTime_.has_value(); }
125 * \brief Handle through which to get signalling resources.
127 gmxapi::SessionResources* resources_ = nullptr;
130 * \brief Whether to issue stop signal when called.
132 bool sendStopSignal_ = false;
136 * \brief Wrap a StopSignalIssuer for testing purposes.
138 class SimpleSignalingClient : public gmxapi::MDModule
142 * Implement gmxapi::MDModule interface.
144 SimpleSignalingClient() : restraint_(std::make_shared<StopSignalIssuer>()) {}
146 explicit SimpleSignalingClient(bool sendStopSignal) :
147 restraint_(std::make_shared<StopSignalIssuer>(sendStopSignal))
151 const char* name() const override { return "SimpleSignalingClient"; }
153 std::shared_ptr<gmx::IRestraintPotential> getRestraint() override { return restraint_; }
157 * \brief Last time this restraint was active, minus the simulation start time.
159 * \return Time elapsed since start.
161 double timeElapsedSinceStart() const
163 if (!restraint_->isInitialized())
165 GMX_THROW(gmx::InternalError("timeElapsedSinceStart called before restraint was used"));
167 return restraint_->lastSimulationTime_.value() - restraint_->simulationStartTime_;
171 //! restraint to provide to client or MD simulator.
172 std::shared_ptr<StopSignalIssuer> restraint_;
176 * \brief Check that we can bind to and use the stop signaler.
178 TEST_F(GmxApiTest, ApiRunnerStopSignalClient)
180 const int nsteps = 4;
183 // Check assumptions about basic simulation behavior.
185 const int nstlist = 1;
187 auto system = gmxapi::fromTprFile(runner_.tprFileName_);
188 auto context = std::make_shared<gmxapi::Context>(gmxapi::createContext());
190 gmxapi::MDArgs args = makeMdArgs();
191 args.emplace_back("-nstlist");
192 args.emplace_back(std::to_string(nstlist));
194 context->setMDArgs(args);
196 auto restraint = std::make_shared<SimpleSignalingClient>();
198 auto session = system.launch(context);
199 EXPECT_TRUE(session);
201 gmxapi::addSessionRestraint(session.get(), restraint);
202 EXPECT_THROW(restraint->timeElapsedSinceStart(), gmx::InternalError);
204 gmxapi::Status status;
205 ASSERT_NO_THROW(status = session->run());
206 EXPECT_TRUE(status.success());
207 EXPECT_EQ(nsteps, gmx::roundToInt(restraint->timeElapsedSinceStart() / getTestStepSize()));
209 status = session->close();
210 EXPECT_TRUE(status.success());
213 // Make sure that stop signal shortens simulation.
215 /* StopHandler promises to stop a simulation at the next NS step after the signal got communicated.
216 * We don't know the communication interval, but we know that it is at most nstlist. We cannot assume
217 * that the signal gets communicated on the step it is set, even if that step is a communication step.
218 * As the signal is set on the first step, we know that the restraint will be called at
219 * most 2*nstlist + 1 times.
220 * Since the time elapsed after the first step is 0, however, we expect the elapsed time
221 * divided by the step size to be at most 2*nstlist.
224 const int nstlist = 1;
225 constexpr const int maxsteps = nstlist * 2 + 1;
226 // This test is meaningless if the the simulation ends early without a signal.
229 "Simulation is already scheduled to end before it can receive a stop signal.");
231 auto system = gmxapi::fromTprFile(runner_.tprFileName_);
232 auto context = std::make_shared<gmxapi::Context>(gmxapi::createContext());
234 gmxapi::MDArgs args = makeMdArgs();
235 args.emplace_back("-nstlist");
236 args.emplace_back(std::to_string(nstlist));
237 // TODO (Ref #3256) use api functionality to extend simulation instead
238 args.emplace_back("-nsteps");
239 args.emplace_back(std::to_string(nsteps));
241 context->setMDArgs(args);
243 const bool issueImmediateStopSignal = true;
244 auto restraint = std::make_shared<SimpleSignalingClient>(issueImmediateStopSignal);
246 auto session = system.launch(context);
247 EXPECT_TRUE(session);
249 gmxapi::addSessionRestraint(session.get(), restraint);
250 EXPECT_THROW(restraint->timeElapsedSinceStart(), gmx::InternalError);
252 gmxapi::Status status;
253 ASSERT_NO_THROW(status = session->run());
254 EXPECT_TRUE(status.success());
256 const int steps_just_run =
257 gmx::roundToInt(restraint->timeElapsedSinceStart() / getTestStepSize());
258 EXPECT_LT(steps_just_run, maxsteps);
260 status = session->close();
261 EXPECT_TRUE(status.success());
265 } // end anonymous namespace
267 } // end namespace testing
269 } // end namespace gmxapi