2b909663b094a2d9b579957fb6bccbffe6a333cd
[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), std::move(runnerBuilder),
126                                          std::move(simulationContext), std::move(logFilehandle));
127 }
128
129 SessionImpl::SessionImpl(std::shared_ptr<ContextImpl> context,
130                          gmx::MdrunnerBuilder&&       runnerBuilder,
131                          gmx::SimulationContext&&     simulationContext,
132                          gmx::LogFilePtr              fplog) :
133     context_(std::move(context)),
134     simulationContext_(std::move(simulationContext)),
135     logFilePtr_(std::move(fplog))
136 {
137     GMX_ASSERT(context_, "SessionImpl invariant implies valid ContextImpl handle.");
138
139     // \todo Session objects can have logic specialized for the runtime environment.
140
141     auto stopHandlerBuilder = std::make_unique<gmx::StopHandlerBuilder>();
142     signalManager_          = std::make_unique<SignalManager>(stopHandlerBuilder.get());
143     GMX_ASSERT(signalManager_, "SessionImpl invariant includes a valid SignalManager.");
144
145     runnerBuilder.addStopHandlerBuilder(std::move(stopHandlerBuilder));
146     runner_ = std::make_unique<gmx::Mdrunner>(runnerBuilder.build());
147     GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
148
149     // For the libgromacs context, a session should explicitly reset global variables that could
150     // have been set in a previous simulation during the same process.
151     gmx_reset_stop_condition();
152 }
153
154 std::shared_ptr<Session> createSession(std::shared_ptr<ContextImpl> context,
155                                        gmx::MdrunnerBuilder&&       runnerBuilder,
156                                        gmx::SimulationContext&&     simulationContext,
157                                        gmx::LogFilePtr              logFilehandle)
158 {
159     auto newSession      = SessionImpl::create(std::move(context), std::move(runnerBuilder),
160                                           std::move(simulationContext), std::move(logFilehandle));
161     auto launchedSession = std::make_shared<Session>(std::move(newSession));
162     return launchedSession;
163 }
164
165 Status SessionImpl::addRestraint(std::shared_ptr<gmxapi::MDModule> module)
166 {
167     GMX_ASSERT(runner_, "SessionImpl invariant implies valid Mdrunner handle.");
168     Status status{ false };
169
170     if (module != nullptr)
171     {
172         const auto& name = module->name();
173         if (restraints_.find(name) == restraints_.end())
174         {
175             auto restraint = module->getRestraint();
176             if (restraint != nullptr)
177             {
178                 restraints_.emplace(std::make_pair(name, restraint));
179                 auto sessionResources = createResources(module);
180                 if (!sessionResources)
181                 {
182                     status = false;
183                 }
184                 else
185                 {
186                     runner_->addPotential(restraint, module->name());
187                     status = true;
188                 }
189             }
190         }
191     }
192     return status;
193 }
194
195
196 SignalManager* SessionImpl::getSignalManager()
197 {
198     SignalManager* ptr = nullptr;
199     if (isOpen())
200     {
201         ptr = signalManager_.get();
202     }
203     return ptr;
204 }
205
206 gmx::Mdrunner* SessionImpl::getRunner()
207 {
208     gmx::Mdrunner* runner = nullptr;
209     if (runner_)
210     {
211         runner = runner_.get();
212     }
213     return runner;
214 }
215
216 gmxapi::SessionResources* SessionImpl::getResources(const std::string& name) const noexcept
217 {
218     gmxapi::SessionResources* resources = nullptr;
219     try
220     {
221         resources = resources_.at(name).get();
222     }
223     catch (const std::out_of_range& e)
224     {
225         // named operation does not have any resources registered.
226     }
227
228     return resources;
229 }
230
231 gmxapi::SessionResources* SessionImpl::createResources(std::shared_ptr<gmxapi::MDModule> module) noexcept
232 {
233     // check if resources already exist for this module
234     // If not, create resources and return handle.
235     // Return nullptr for any failure.
236     gmxapi::SessionResources* resources = nullptr;
237     const auto&               name      = module->name();
238     if (resources_.find(name) == resources_.end())
239     {
240         auto resourcesInstance = std::make_unique<SessionResources>(this, name);
241         resources_.emplace(std::make_pair(name, std::move(resourcesInstance)));
242         resources = resources_.at(name).get();
243         // To do: This should be more dynamic.
244         getSignalManager()->addSignaller(name);
245         if (restraints_.find(name) != restraints_.end())
246         {
247             auto restraintRef = restraints_.at(name);
248             auto restraint    = restraintRef.lock();
249             if (restraint)
250             {
251                 restraint->bindSession(resources);
252             }
253         }
254     };
255     return resources;
256 }
257
258 Session::Session(std::unique_ptr<SessionImpl> impl) noexcept
259 {
260     if (impl != nullptr)
261     {
262         impl_ = std::move(impl);
263     }
264     GMX_ASSERT(impl_->isOpen(), "SessionImpl invariant implies valid Mdrunner handle.");
265 }
266
267 Status Session::run() noexcept
268 {
269     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
270
271     const Status status = impl_->run();
272     return status;
273 }
274
275 Status Session::close()
276 {
277     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
278
279     auto status = Status(false);
280     if (isOpen())
281     {
282         // \todo catch exceptions when we know what they might be
283         status = impl_->close();
284     }
285
286     return status;
287 }
288
289 Session::~Session()
290 {
291     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
292     if (isOpen())
293     {
294         try
295         {
296             impl_->close();
297         }
298         catch (const std::exception&)
299         {
300             // \todo find some exception-safe things to do with this via the Context interface.
301         }
302     }
303 }
304
305 bool Session::isOpen() const noexcept
306 {
307     GMX_ASSERT(impl_, "Session invariant implies valid implementation object handle.");
308     const auto result = impl_->isOpen();
309     return result;
310 }
311
312 Status addSessionRestraint(Session* session, std::shared_ptr<gmxapi::MDModule> restraint)
313 {
314     auto status = gmxapi::Status(false);
315
316     if (session != nullptr && restraint != nullptr)
317     {
318         // \todo Improve the external / library API facets
319         // so the public API does not need to offer raw pointers.
320         auto sessionImpl = session->getRaw();
321
322         GMX_RELEASE_ASSERT(sessionImpl,
323                            "Session invariant implies valid implementation object handle.");
324         // GMX_ASSERT alone is not strong enough to convince linters not to warn of possible nullptr.
325         if (sessionImpl)
326         {
327             status = sessionImpl->addRestraint(std::move(restraint));
328         }
329     }
330     return status;
331 }
332
333 //! \cond internal
334 SessionImpl* Session::getRaw() const noexcept
335 {
336     return impl_.get();
337 }
338 //! \endcond
339
340 std::shared_ptr<Session> launchSession(Context* context, const Workflow& work) noexcept
341 {
342     auto session = context->launch(work);
343     return session;
344 }
345
346 SessionImpl::~SessionImpl() = default;
347
348 SessionResources::SessionResources(gmxapi::SessionImpl* session, std::string name) :
349     sessionImpl_(session),
350     name_(std::move(name))
351 {
352 }
353
354 SessionResources::~SessionResources() = default;
355
356 std::string SessionResources::name() const
357 {
358     return name_;
359 }
360
361 Signal SessionResources::getMdrunnerSignal(md::signals signal)
362 {
363     //// while there is only one choice...
364     if (signal != md::signals::STOP)
365     {
366         throw gmxapi::NotImplementedError("This signaller only handles stop signals.");
367     }
368
369     // Get a signalling proxy for the caller.
370     auto signalManager = sessionImpl_->getSignalManager();
371     if (signalManager == nullptr)
372     {
373         throw gmxapi::ProtocolError(
374                 "Client requested access to a signaller that is not available.");
375     }
376     auto functor = signalManager->getSignal(name_, signal);
377
378     return functor;
379 }
380
381 } // end namespace gmxapi