2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2018,2020, 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 #include "gmxapi/session.h"
40 #include "gromacs/mdlib/sighandler.h"
41 #include "gromacs/mdrunutility/logging.h"
42 #include "gromacs/restraint/restraintpotential.h"
43 #include "gromacs/utility/gmxassert.h"
44 #include "gromacs/utility/basenetwork.h"
45 #include "gromacs/utility/init.h"
47 #include "gmxapi/context.h"
48 #include "gmxapi/exceptions.h"
49 #include "gmxapi/status.h"
50 #include "gmxapi/md/mdmodule.h"
52 #include "createsession.h"
53 #include "mdsignals.h"
54 #include "session_impl.h"
55 #include "sessionresources.h"
61 * \brief Provide RAII management of communications resource state.
63 * To acquire an MpiContextManager is to have assurance that any external MPI
64 * environment is ready to use. When the MpiContextManager is released or
65 * goes out of scope, the destructor finalizes the resources.
67 * Note that thread-MPI chooses the number of ranks and constructs its
68 * MPI communicator internally, so does not and is unlikely to ever
71 * \todo There is no resource for logging or reporting errors during initialization
72 * \todo Remove this class by managing the MPI context with mpi4py and so
73 * configuring the SimulationContext externally
77 class MpiContextManager
82 gmx::init(nullptr, nullptr);
83 GMX_RELEASE_ASSERT(!GMX_LIB_MPI || gmx_mpi_initialized(),
84 "MPI should be initialized before reaching this point.");
89 // This is always safe to call. It is a no-op if
90 // thread-MPI, and if the constructor completed then the
91 // MPI library is initialized with reference counting.
96 * \brief Exclusive ownership of a scoped context means copying is impossible.
100 MpiContextManager(const MpiContextManager&) = delete;
101 MpiContextManager& operator=(const MpiContextManager&) = delete;
105 * \brief Move semantics are trivial.
109 MpiContextManager(MpiContextManager&&) noexcept = default;
110 MpiContextManager& operator=(MpiContextManager&&) noexcept = default;
114 SignalManager::SignalManager(gmx::StopHandlerBuilder* stopHandlerBuilder) :
115 state_(std::make_shared<gmx::StopSignal>(gmx::StopSignal::noSignal))
119 * \brief Signal issuer managed by this object.
121 * Created and bound when the runner is built. Subsequently, client
122 * stop signals are proxied to the simulation through the session
123 * resources. The MD internal signal handler calls this functor
124 * during the MD loop to see if the simulation should be stopped.
125 * Thus, this should execute within a very small fraction of an MD
126 * step and not require any synchronization.
128 auto currentState = state_;
129 auto stopSignalIssuer = [currentState]() { return *currentState; };
130 stopHandlerBuilder->registerStopCondition(stopSignalIssuer);
134 SignalManager::~SignalManager() = default;
137 bool SessionImpl::isOpen() const noexcept
139 // Currently, an active session is equivalent to an active Mdrunner.
140 return bool(runner_);
143 Status SessionImpl::close()
145 // Assume unsuccessful until proven otherwise.
146 auto successful = Status(false);
148 // When the Session is closed, we need to know that the MD output has been finalized.
152 // \todo provide meaningful result.
153 // We should be checking that resources were properly shut down, but
154 // there isn't currently a way to do that.
159 Status SessionImpl::run() noexcept
161 // Status is failure until proven otherwise.
162 auto successful = Status(false);
163 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
164 auto rc = runner_->mdrunner();
172 std::unique_ptr<SessionImpl> SessionImpl::create(std::shared_ptr<ContextImpl> context,
173 gmx::MdrunnerBuilder&& runnerBuilder,
174 gmx::SimulationContext&& simulationContext,
175 gmx::LogFilePtr logFilehandle)
177 // We should be able to get a communicator (or subcommunicator) through the
179 return std::make_unique<SessionImpl>(std::move(context), std::move(runnerBuilder),
180 std::move(simulationContext), std::move(logFilehandle));
183 SessionImpl::SessionImpl(std::shared_ptr<ContextImpl> context,
184 gmx::MdrunnerBuilder&& runnerBuilder,
185 gmx::SimulationContext&& simulationContext,
186 gmx::LogFilePtr fplog) :
187 context_(std::move(context)),
188 mpiContextManager_(std::make_unique<MpiContextManager>()),
189 simulationContext_(std::move(simulationContext)),
190 logFilePtr_(std::move(fplog))
192 GMX_ASSERT(context_, "SessionImpl invariant implies valid ContextImpl handle.");
193 GMX_ASSERT(mpiContextManager_, "SessionImpl invariant implies valid MpiContextManager guard.");
195 // \todo Session objects can have logic specialized for the runtime environment.
197 auto stopHandlerBuilder = std::make_unique<gmx::StopHandlerBuilder>();
198 signalManager_ = std::make_unique<SignalManager>(stopHandlerBuilder.get());
199 GMX_ASSERT(signalManager_, "SessionImpl invariant includes a valid SignalManager.");
201 runnerBuilder.addStopHandlerBuilder(std::move(stopHandlerBuilder));
202 runner_ = std::make_unique<gmx::Mdrunner>(runnerBuilder.build());
203 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
205 // For the libgromacs context, a session should explicitly reset global variables that could
206 // have been set in a previous simulation during the same process.
207 gmx_reset_stop_condition();
210 std::shared_ptr<Session> createSession(std::shared_ptr<ContextImpl> context,
211 gmx::MdrunnerBuilder&& runnerBuilder,
212 gmx::SimulationContext&& simulationContext,
213 gmx::LogFilePtr logFilehandle)
215 auto newSession = SessionImpl::create(std::move(context), std::move(runnerBuilder),
216 std::move(simulationContext), std::move(logFilehandle));
217 auto launchedSession = std::make_shared<Session>(std::move(newSession));
218 return launchedSession;
221 Status SessionImpl::addRestraint(std::shared_ptr<gmxapi::MDModule> module)
223 GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
224 Status status{ false };
226 if (module != nullptr)
228 const auto& name = module->name();
229 if (restraints_.find(name) == restraints_.end())
231 auto restraint = module->getRestraint();
232 if (restraint != nullptr)
234 restraints_.emplace(std::make_pair(name, restraint));
235 auto sessionResources = createResources(module);
236 if (!sessionResources)
242 runner_->addPotential(restraint, module->name());
252 SignalManager* SessionImpl::getSignalManager()
254 SignalManager* ptr = nullptr;
257 ptr = signalManager_.get();
262 gmx::Mdrunner* SessionImpl::getRunner()
264 gmx::Mdrunner* runner = nullptr;
267 runner = runner_.get();
272 gmxapi::SessionResources* SessionImpl::getResources(const std::string& name) const noexcept
274 gmxapi::SessionResources* resources = nullptr;
277 resources = resources_.at(name).get();
279 catch (const std::out_of_range& e)
281 // named operation does not have any resources registered.
287 gmxapi::SessionResources* SessionImpl::createResources(std::shared_ptr<gmxapi::MDModule> module) noexcept
289 // check if resources already exist for this module
290 // If not, create resources and return handle.
291 // Return nullptr for any failure.
292 gmxapi::SessionResources* resources = nullptr;
293 const auto& name = module->name();
294 if (resources_.find(name) == resources_.end())
296 auto resourcesInstance = std::make_unique<SessionResources>(this, name);
297 resources_.emplace(std::make_pair(name, std::move(resourcesInstance)));
298 resources = resources_.at(name).get();
299 // To do: This should be more dynamic.
300 getSignalManager()->addSignaller(name);
301 if (restraints_.find(name) != restraints_.end())
303 auto restraintRef = restraints_.at(name);
304 auto restraint = restraintRef.lock();
307 restraint->bindSession(resources);
314 Session::Session(std::unique_ptr<SessionImpl> impl) noexcept
318 impl_ = std::move(impl);
320 GMX_ASSERT(impl_->isOpen(), "SessionImpl invariant implies valid Mdrunner handle.");
323 Status Session::run() noexcept
325 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
327 const Status status = impl_->run();
331 Status Session::close()
333 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
335 auto status = Status(false);
338 // \todo catch exceptions when we know what they might be
339 status = impl_->close();
347 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
354 catch (const std::exception&)
356 // \todo find some exception-safe things to do with this via the Context interface.
361 bool Session::isOpen() const noexcept
363 GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
364 const auto result = impl_->isOpen();
368 Status addSessionRestraint(Session* session, std::shared_ptr<gmxapi::MDModule> restraint)
370 auto status = gmxapi::Status(false);
372 if (session != nullptr && restraint != nullptr)
374 // \todo Improve the external / library API facets
375 // so the public API does not need to offer raw pointers.
376 auto sessionImpl = session->getRaw();
378 GMX_RELEASE_ASSERT(sessionImpl,
379 "Session invariant implies valid implementation object handle.");
380 // GMX_ASSERT alone is not strong enough to convince linters not to warn of possible nullptr.
383 status = sessionImpl->addRestraint(std::move(restraint));
390 SessionImpl* Session::getRaw() const noexcept
396 std::shared_ptr<Session> launchSession(Context* context, const Workflow& work) noexcept
398 auto session = context->launch(work);
402 SessionImpl::~SessionImpl() = default;
404 SessionResources::SessionResources(gmxapi::SessionImpl* session, std::string name) :
405 sessionImpl_(session),
406 name_(std::move(name))
410 SessionResources::~SessionResources() = default;
412 std::string SessionResources::name() const
417 Signal SessionResources::getMdrunnerSignal(md::signals signal)
419 //// while there is only one choice...
420 if (signal != md::signals::STOP)
422 throw gmxapi::NotImplementedError("This signaller only handles stop signals.");
425 // Get a signalling proxy for the caller.
426 auto signalManager = sessionImpl_->getSignalManager();
427 if (signalManager == nullptr)
429 throw gmxapi::ProtocolError(
430 "Client requested access to a signaller that is not available.");
432 auto functor = signalManager->getSignal(name_, signal);
437 } // end namespace gmxapi