Apply clang-format to source tree
[alexxy/gromacs.git] / src / api / cpp / session.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35
36 #include "gmxpre.h"
37
38 #include "config.h"
39
40 #include "gmxapi/session.h"
41
42 #include <memory>
43 #include "gromacs/mdlib/sighandler.h"
44 #include "gromacs/mdrunutility/logging.h"
45 #include "gromacs/restraint/restraintpotential.h"
46 #include "gromacs/utility/gmxassert.h"
47 #include "gromacs/utility/basenetwork.h"
48 #include "gromacs/utility/init.h"
49
50 #include "gmxapi/context.h"
51 #include "gmxapi/exceptions.h"
52 #include "gmxapi/status.h"
53 #include "gmxapi/md/mdmodule.h"
54
55 #include "createsession.h"
56 #include "mdsignals.h"
57 #include "session_impl.h"
58 #include "sessionresources.h"
59
60 namespace gmxapi
61 {
62
63 /*!
64  * \brief Provide RAII management of communications resource state.
65  *
66  * To acquire an MpiContextManager is to have assurance that any external MPI
67  * environment is ready to use. When the MpiContextManager is released or
68  * goes out of scope, the destructor finalizes the resources.
69  *
70  * Note that thread-MPI chooses the number of ranks and constructs its
71  * MPI communicator internally, so does not and is unlikely to ever
72  * participate here.
73  *
74  * \todo There is no resource for logging or reporting errors during initialization
75  * \todo Remove this class by managing the MPI context with mpi4py and so
76  *       configuring the SimulationContext externally
77  *
78  * \ingroup gmxapi
79  */
80 class MpiContextManager
81 {
82 public:
83     MpiContextManager()
84     {
85         gmx::init(nullptr, nullptr);
86         GMX_RELEASE_ASSERT(!GMX_LIB_MPI || gmx_mpi_initialized(),
87                            "MPI should be initialized before reaching this point.");
88     };
89
90     ~MpiContextManager()
91     {
92         // This is always safe to call. It is a no-op if
93         // thread-MPI, and if the constructor completed then the
94         // MPI library is initialized with reference counting.
95         gmx::finalize();
96     }
97
98     /*!
99      * \brief Exclusive ownership of a scoped context means copying is impossible.
100      *
101      * \{
102      */
103     MpiContextManager(const MpiContextManager&) = delete;
104     MpiContextManager& operator=(const MpiContextManager&) = delete;
105     //! \}
106
107     /*!
108      * \brief Move semantics are trivial.
109      *
110      * \{
111      */
112     MpiContextManager(MpiContextManager&&) noexcept = default;
113     MpiContextManager& operator=(MpiContextManager&&) noexcept = default;
114     //! \}
115 };
116
117 SignalManager::SignalManager(gmx::StopHandlerBuilder* stopHandlerBuilder) :
118     state_(std::make_shared<gmx::StopSignal>(gmx::StopSignal::noSignal))
119 {
120
121     /*!
122      * \brief Signal issuer managed by this object.
123      *
124      * Created and bound when the runner is built. Subsequently, client
125      * stop signals are proxied to the simulation through the session
126      * resources. The MD internal signal handler calls this functor
127      * during the MD loop to see if the simulation should be stopped.
128      * Thus, this should execute within a very small fraction of an MD
129      * step and not require any synchronization.
130      */
131     auto currentState     = state_;
132     auto stopSignalIssuer = [currentState]() { return *currentState; };
133     stopHandlerBuilder->registerStopCondition(stopSignalIssuer);
134 }
135
136 //! \cond
137 SignalManager::~SignalManager() = default;
138 //! \endcond
139
140 bool SessionImpl::isOpen() const noexcept
141 {
142     // Currently, an active session is equivalent to an active Mdrunner.
143     return bool(runner_);
144 }
145
146 Status SessionImpl::close()
147 {
148     // Assume unsuccessful until proven otherwise.
149     auto successful = Status(false);
150
151     // When the Session is closed, we need to know that the MD output has been finalized.
152     runner_.reset();
153     logFilePtr_.reset();
154
155     // \todo provide meaningful result.
156     // We should be checking that resources were properly shut down, but
157     // there isn't currently a way to do that.
158     successful = true;
159     return successful;
160 }
161
162 Status SessionImpl::run() noexcept
163 {
164     // Status is failure until proven otherwise.
165     auto successful = Status(false);
166     GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
167     auto rc = runner_->mdrunner();
168     if (rc == 0)
169     {
170         successful = true;
171     }
172     return successful;
173 }
174
175 std::unique_ptr<SessionImpl> SessionImpl::create(std::shared_ptr<ContextImpl> context,
176                                                  gmx::MdrunnerBuilder&&       runnerBuilder,
177                                                  gmx::SimulationContext&&     simulationContext,
178                                                  gmx::LogFilePtr              logFilehandle)
179 {
180     // We should be able to get a communicator (or subcommunicator) through the
181     // Context.
182     return std::make_unique<SessionImpl>(std::move(context), std::move(runnerBuilder),
183                                          std::move(simulationContext), std::move(logFilehandle));
184 }
185
186 SessionImpl::SessionImpl(std::shared_ptr<ContextImpl> context,
187                          gmx::MdrunnerBuilder&&       runnerBuilder,
188                          gmx::SimulationContext&&     simulationContext,
189                          gmx::LogFilePtr              fplog) :
190     context_(std::move(context)),
191     mpiContextManager_(std::make_unique<MpiContextManager>()),
192     simulationContext_(std::move(simulationContext)),
193     logFilePtr_(std::move(fplog))
194 {
195     GMX_ASSERT(context_, "SessionImpl invariant implies valid ContextImpl handle.");
196     GMX_ASSERT(mpiContextManager_, "SessionImpl invariant implies valid MpiContextManager guard.");
197
198     // \todo Session objects can have logic specialized for the runtime environment.
199
200     auto stopHandlerBuilder = std::make_unique<gmx::StopHandlerBuilder>();
201     signalManager_          = std::make_unique<SignalManager>(stopHandlerBuilder.get());
202     GMX_ASSERT(signalManager_, "SessionImpl invariant includes a valid SignalManager.");
203
204     runnerBuilder.addStopHandlerBuilder(std::move(stopHandlerBuilder));
205     runner_ = std::make_unique<gmx::Mdrunner>(runnerBuilder.build());
206     GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
207
208     // For the libgromacs context, a session should explicitly reset global variables that could
209     // have been set in a previous simulation during the same process.
210     gmx_reset_stop_condition();
211 }
212
213 std::shared_ptr<Session> createSession(std::shared_ptr<ContextImpl> context,
214                                        gmx::MdrunnerBuilder&&       runnerBuilder,
215                                        gmx::SimulationContext&&     simulationContext,
216                                        gmx::LogFilePtr              logFilehandle)
217 {
218     auto newSession      = SessionImpl::create(std::move(context), std::move(runnerBuilder),
219                                           std::move(simulationContext), std::move(logFilehandle));
220     auto launchedSession = std::make_shared<Session>(std::move(newSession));
221     return launchedSession;
222 }
223
224 Status SessionImpl::addRestraint(std::shared_ptr<gmxapi::MDModule> module)
225 {
226     GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
227     Status status{ false };
228
229     if (module != nullptr)
230     {
231         const auto& name = module->name();
232         if (restraints_.find(name) == restraints_.end())
233         {
234             auto restraint = module->getRestraint();
235             if (restraint != nullptr)
236             {
237                 restraints_.emplace(std::make_pair(name, restraint));
238                 auto sessionResources = createResources(module);
239                 if (!sessionResources)
240                 {
241                     status = false;
242                 }
243                 else
244                 {
245                     runner_->addPotential(restraint, module->name());
246                     status = true;
247                 }
248             }
249         }
250     }
251     return status;
252 }
253
254
255 SignalManager* SessionImpl::getSignalManager()
256 {
257     SignalManager* ptr = nullptr;
258     if (isOpen())
259     {
260         ptr = signalManager_.get();
261     }
262     return ptr;
263 }
264
265 gmx::Mdrunner* SessionImpl::getRunner()
266 {
267     gmx::Mdrunner* runner = nullptr;
268     if (runner_)
269     {
270         runner = runner_.get();
271     }
272     return runner;
273 }
274
275 gmxapi::SessionResources* SessionImpl::getResources(const std::string& name) const noexcept
276 {
277     gmxapi::SessionResources* resources = nullptr;
278     try
279     {
280         resources = resources_.at(name).get();
281     }
282     catch (const std::out_of_range& e)
283     {
284         // named operation does not have any resources registered.
285     }
286
287     return resources;
288 }
289
290 gmxapi::SessionResources* SessionImpl::createResources(std::shared_ptr<gmxapi::MDModule> module) noexcept
291 {
292     // check if resources already exist for this module
293     // If not, create resources and return handle.
294     // Return nullptr for any failure.
295     gmxapi::SessionResources* resources = nullptr;
296     const auto&               name      = module->name();
297     if (resources_.find(name) == resources_.end())
298     {
299         auto resourcesInstance = std::make_unique<SessionResources>(this, name);
300         resources_.emplace(std::make_pair(name, std::move(resourcesInstance)));
301         resources = resources_.at(name).get();
302         // To do: This should be more dynamic.
303         getSignalManager()->addSignaller(name);
304         if (restraints_.find(name) != restraints_.end())
305         {
306             auto restraintRef = restraints_.at(name);
307             auto restraint    = restraintRef.lock();
308             if (restraint)
309             {
310                 restraint->bindSession(resources);
311             }
312         }
313     };
314     return resources;
315 }
316
317 Session::Session(std::unique_ptr<SessionImpl> impl) noexcept
318 {
319     if (impl != nullptr)
320     {
321         impl_ = std::move(impl);
322     }
323     GMX_ASSERT(impl_->isOpen(), "SessionImpl invariant implies valid Mdrunner handle.");
324 }
325
326 Status Session::run() noexcept
327 {
328     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
329
330     const Status status = impl_->run();
331     return status;
332 }
333
334 Status Session::close()
335 {
336     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
337
338     auto status = Status(false);
339     if (isOpen())
340     {
341         // \todo catch exceptions when we know what they might be
342         status = impl_->close();
343     }
344
345     return status;
346 }
347
348 Session::~Session()
349 {
350     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
351     if (isOpen())
352     {
353         try
354         {
355             impl_->close();
356         }
357         catch (const std::exception&)
358         {
359             // \todo find some exception-safe things to do with this via the Context interface.
360         }
361     }
362 }
363
364 bool Session::isOpen() const noexcept
365 {
366     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
367     const auto result = impl_->isOpen();
368     return result;
369 }
370
371 Status addSessionRestraint(Session* session, std::shared_ptr<gmxapi::MDModule> restraint)
372 {
373     auto status = gmxapi::Status(false);
374
375     if (session != nullptr && restraint != nullptr)
376     {
377         // \todo Improve the external / library API facets
378         // so the public API does not need to offer raw pointers.
379         auto sessionImpl = session->getRaw();
380
381         GMX_RELEASE_ASSERT(sessionImpl,
382                            "Session invariant implies valid implementation object handle.");
383         // GMX_ASSERT alone is not strong enough to convince linters not to warn of possible nullptr.
384         if (sessionImpl)
385         {
386             status = sessionImpl->addRestraint(std::move(restraint));
387         }
388     }
389     return status;
390 }
391
392 //! \cond internal
393 SessionImpl* Session::getRaw() const noexcept
394 {
395     return impl_.get();
396 }
397 //! \endcond
398
399 std::shared_ptr<Session> launchSession(Context* context, const Workflow& work) noexcept
400 {
401     auto session = context->launch(work);
402     return session;
403 }
404
405 SessionImpl::~SessionImpl() = default;
406
407 SessionResources::SessionResources(gmxapi::SessionImpl* session, std::string name) :
408     sessionImpl_(session),
409     name_(std::move(name))
410 {
411 }
412
413 SessionResources::~SessionResources() = default;
414
415 std::string SessionResources::name() const
416 {
417     return name_;
418 }
419
420 Signal SessionResources::getMdrunnerSignal(md::signals signal)
421 {
422     //// while there is only one choice...
423     if (signal != md::signals::STOP)
424     {
425         throw gmxapi::NotImplementedError("This signaller only handles stop signals.");
426     }
427
428     // Get a signalling proxy for the caller.
429     auto signalManager = sessionImpl_->getSignalManager();
430     if (signalManager == nullptr)
431     {
432         throw gmxapi::ProtocolError(
433                 "Client requested access to a signaller that is not available.");
434     }
435     auto functor = signalManager->getSignal(name_, signal);
436
437     return functor;
438 }
439
440 } // end namespace gmxapi