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