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.
40 #include "gmxapi/session.h"
43 #include "gromacs/gmxlib/network.h"
44 #include "gromacs/mdlib/sighandler.h"
45 #include "gromacs/mdrun/logging.h"
46 #include "gromacs/mdrun/multisim.h"
47 #include "gromacs/restraint/restraintpotential.h"
48 #include "gromacs/utility/gmxassert.h"
49 #include "gromacs/utility/basenetwork.h"
50 #include "gromacs/utility/init.h"
52 #include "gmxapi/context.h"
53 #include "gmxapi/exceptions.h"
54 #include "gmxapi/status.h"
55 #include "gmxapi/md/mdmodule.h"
57 #include "createsession.h"
58 #include "mdsignals.h"
59 #include "session_impl.h"
60 #include "sessionresources.h"
66 * \brief Provide RAII management of communications resource state.
68 * To acquire an MpiContextManager is to have assurance that the GROMACS MPI
69 * environment is ready to use. When the MpiContextManager is released or
70 * goes out of scope, the destructor finalizes the resources.
72 * \todo Figure out how to manage MPI versus tMPI.
73 * \todo gmx::init should take a subcommunicator rather than use MPI_COMM_WORLD
74 * \todo There is no resource for logging or reporting errors during initialization
75 * \todo Clarify relationship with gmx::SimulationContext.
79 class MpiContextManager
84 gmx::init(nullptr, nullptr);
88 // With thread-MPI, gmx_mpi_initialized() is false until after
89 // spawnThreads in the middle of gmx::Mdrunner::mdrunner(), but
90 // without thread-MPI, MPI is initialized by the time gmx::init()
91 // returns. In other words, this is not an effective context manager
92 // for thread-MPI, but it should be effective for MPI.
93 // \todo Distinguish scope / lifetime for comm resources from implementation details.
94 // \todo Normalize scope / lifetime of comm resources.
96 GMX_ASSERT(gmx_mpi_initialized(), "MPI should be initialized before reaching this point.");
97 #endif // GMX_THREAD_MPI
99 #endif // defined(GMX_MPI)
104 // This is safe to call. It is a no-op if thread-MPI, and if the
105 // constructor completed then MPI is initialized.
110 * \brief Exclusive ownership of a scoped context means copying is impossible.
114 MpiContextManager(const MpiContextManager &) = delete;
115 MpiContextManager &operator=(const MpiContextManager &) = delete;
119 * \brief Move semantics are trivial.
123 MpiContextManager(MpiContextManager &&) noexcept = default;
124 MpiContextManager &operator=(MpiContextManager &&) noexcept = default;
128 SignalManager::SignalManager(gmx::StopHandlerBuilder* stopHandlerBuilder) :
129 state_(std::make_shared<gmx::StopSignal>(gmx::StopSignal::noSignal))
133 * \brief Signal issuer managed by this object.
135 * Created and bound when the runner is built. Subsequently, client
136 * stop signals are proxied to the simulation through the session
137 * resources. The MD internal signal handler calls this functor
138 * during the MD loop to see if the simulation should be stopped.
139 * Thus, this should execute within a very small fraction of an MD
140 * step and not require any synchronization.
142 auto currentState = state_;
143 auto stopSignalIssuer = [currentState](){
144 return *currentState;
146 stopHandlerBuilder->registerStopCondition(stopSignalIssuer);
150 SignalManager::~SignalManager() = default;
153 bool SessionImpl::isOpen() const noexcept
155 // Currently, an active session is equivalent to an active Mdrunner.
156 return bool(runner_);
159 Status SessionImpl::close()
161 // Assume unsuccessful until proven otherwise.
162 auto successful = Status(false);
164 // When the Session is closed, we need to know that the MD output has been finalized.
168 // \todo provide meaningful result.
169 // We should be checking that resources were properly shut down, but
170 // there isn't currently a way to do that.
175 Status SessionImpl::run() noexcept
177 // Status is failure until proven otherwise.
178 auto successful = Status(false);
179 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
180 auto rc = runner_->mdrunner();
188 std::unique_ptr<SessionImpl> SessionImpl::create(std::shared_ptr<ContextImpl> context,
189 gmx::MdrunnerBuilder &&runnerBuilder,
190 const gmx::SimulationContext &simulationContext,
191 gmx::LogFilePtr logFilehandle,
192 gmx_multisim_t * multiSim)
194 // We should be able to get a communicator (or subcommunicator) through the
196 return std::make_unique<SessionImpl>(std::move(context),
197 std::move(runnerBuilder),
199 std::move(logFilehandle),
203 SessionImpl::SessionImpl(std::shared_ptr<ContextImpl> context,
204 gmx::MdrunnerBuilder &&runnerBuilder,
205 const gmx::SimulationContext &simulationContext,
206 gmx::LogFilePtr fplog,
207 gmx_multisim_t * multiSim) :
208 context_(std::move(context)),
209 mpiContextManager_(std::make_unique<MpiContextManager>()),
210 simulationContext_(simulationContext),
211 logFilePtr_(std::move(fplog)),
214 GMX_ASSERT(context_, "SessionImpl invariant implies valid ContextImpl handle.");
215 GMX_ASSERT(mpiContextManager_, "SessionImpl invariant implies valid MpiContextManager guard.");
216 GMX_ASSERT(simulationContext_.communicationRecord_, "SessionImpl invariant implies valid commrec.");
217 GMX_UNUSED_VALUE(multiSim_);
218 GMX_UNUSED_VALUE(simulationContext_);
220 // \todo Session objects can have logic specialized for the runtime environment.
222 auto stopHandlerBuilder = std::make_unique<gmx::StopHandlerBuilder>();
223 signalManager_ = std::make_unique<SignalManager>(stopHandlerBuilder.get());
224 GMX_ASSERT(signalManager_, "SessionImpl invariant includes a valid SignalManager.");
226 runnerBuilder.addStopHandlerBuilder(std::move(stopHandlerBuilder));
227 runner_ = std::make_unique<gmx::Mdrunner>(runnerBuilder.build());
228 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
230 // For the libgromacs context, a session should explicitly reset global variables that could
231 // have been set in a previous simulation during the same process.
232 gmx_reset_stop_condition();
235 std::shared_ptr<Session> createSession(std::shared_ptr<ContextImpl> context,
236 gmx::MdrunnerBuilder &&runnerBuilder,
237 const gmx::SimulationContext &simulationContext,
238 gmx::LogFilePtr logFilehandle,
239 gmx_multisim_t * multiSim)
241 auto newSession = SessionImpl::create(std::move(context),
242 std::move(runnerBuilder),
244 std::move(logFilehandle),
246 auto launchedSession = std::make_shared<Session>(std::move(newSession));
247 return launchedSession;
250 Status SessionImpl::addRestraint(std::shared_ptr<gmxapi::MDModule> module)
252 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
257 if (module != nullptr)
259 const auto &name = module->name();
260 if (restraints_.find(name) == restraints_.end())
262 auto restraint = module->getRestraint();
263 if (restraint != nullptr)
265 restraints_.emplace(std::make_pair(name, restraint));
266 auto sessionResources = createResources(module);
267 if (!sessionResources)
273 runner_->addPotential(restraint, module->name());
283 SignalManager *SessionImpl::getSignalManager()
285 SignalManager* ptr = nullptr;
288 ptr = signalManager_.get();
293 gmx::Mdrunner *SessionImpl::getRunner()
295 gmx::Mdrunner * runner = nullptr;
298 runner = runner_.get();
303 gmxapi::SessionResources *SessionImpl::getResources(const std::string &name) const noexcept
305 gmxapi::SessionResources * resources = nullptr;
308 resources = resources_.at(name).get();
310 catch (const std::out_of_range &e)
312 // named operation does not have any resources registered.
318 gmxapi::SessionResources *SessionImpl::createResources(std::shared_ptr<gmxapi::MDModule> module) noexcept
320 // check if resources already exist for this module
321 // If not, create resources and return handle.
322 // Return nullptr for any failure.
323 gmxapi::SessionResources * resources = nullptr;
324 const auto &name = module->name();
325 if (resources_.find(name) == resources_.end())
327 auto resourcesInstance = std::make_unique<SessionResources>(this, name);
328 resources_.emplace(std::make_pair(name, std::move(resourcesInstance)));
329 resources = resources_.at(name).get();
330 // To do: This should be more dynamic.
331 getSignalManager()->addSignaller(name);
332 if (restraints_.find(name) != restraints_.end())
334 auto restraintRef = restraints_.at(name);
335 auto restraint = restraintRef.lock();
338 restraint->bindSession(resources);
346 Session::Session(std::unique_ptr<SessionImpl> impl) noexcept
350 impl_ = std::move(impl);
352 GMX_ASSERT(impl_->isOpen(), "SessionImpl invariant implies valid Mdrunner handle.");
355 Status Session::run() noexcept
357 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
359 const Status status = impl_->run();
363 Status Session::close()
365 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
367 auto status = Status(false);
370 // \todo catch exceptions when we know what they might be
371 status = impl_->close();
379 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
386 catch (const std::exception &)
388 // \todo find some exception-safe things to do with this via the Context interface.
393 bool Session::isOpen() const noexcept
395 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
396 const auto result = impl_->isOpen();
400 Status addSessionRestraint(Session * session,
401 std::shared_ptr<gmxapi::MDModule> restraint)
403 auto status = gmxapi::Status(false);
405 if (session != nullptr && restraint != nullptr)
407 // \todo Improve the external / library API facets
408 // so the public API does not need to offer raw pointers.
409 auto sessionImpl = session->getRaw();
411 GMX_RELEASE_ASSERT(sessionImpl, "Session invariant implies valid implementation object handle.");
412 // GMX_ASSERT alone is not strong enough to convince linters not to warn of possible nullptr.
415 status = sessionImpl->addRestraint(std::move(restraint));
422 SessionImpl *Session::getRaw() const noexcept
428 std::shared_ptr<Session> launchSession(Context* context, const Workflow &work) noexcept
430 auto session = context->launch(work);
434 SessionImpl::~SessionImpl() = default;
436 SessionResources::SessionResources(gmxapi::SessionImpl *session,
438 sessionImpl_(session),
439 name_(std::move(name))
443 SessionResources::~SessionResources() = default;
445 const std::string SessionResources::name() const
450 Signal SessionResources::getMdrunnerSignal(md::signals signal)
452 //// while there is only one choice...
453 if (signal != md::signals::STOP)
455 throw gmxapi::NotImplementedError("This signaller only handles stop signals.");
458 // Get a signalling proxy for the caller.
459 auto signalManager = sessionImpl_->getSignalManager();
460 if (signalManager == nullptr)
462 throw gmxapi::ProtocolError("Client requested access to a signaller that is not available.");
464 auto functor = signalManager->getSignal(name_, signal);
469 } // end namespace gmxapi