Use MpiContextManager to provide MPI communicator.
[alexxy/gromacs.git] / api / gmxapi / cpp / context_impl.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2018,2019,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 #ifndef GMXAPI_CONTEXT_IMPL_H
36 #define GMXAPI_CONTEXT_IMPL_H
37 /*! \file
38  * \brief Declare gmxapi::ContextImpl
39  *
40  * \author M. Eric Irrgang <ericirrgang@gmail.com>
41  * \ingroup gmxapi
42  */
43
44 #include <memory>
45 #include <string>
46
47 #include "gromacs/mdrun/legacymdrunoptions.h"
48 #include "gromacs/mdtypes/mdrunoptions.h"
49 #include "gromacs/utility/gmxmpi.h"
50
51 #include "gmxapi/context.h"
52 #include "gmxapi/session.h"
53
54 namespace gmxapi
55 {
56
57 /*!
58  * \brief Provide RAII management of library MPI context.
59  *
60  * To acquire an MpiContextManager is to have assurance that any external MPI
61  * environment is ready to use. When the MpiContextManager is released or
62  * goes out of scope, the destructor finalizes the resources.
63  *
64  * It is plausible that one or more ranks may not participate in an API session,
65  * but we have not historically handled such use cases at this level.
66  * For GROMACS built with an MPI library, the root communicator in the MpiContextManager
67  * must be valid. For other builds (e.g. tMPI), the communicator member must be MPI_COMM_NULL.
68  *
69  * If the client provides a communicator, the client promises to maintain the validity
70  * of the communicator for the life of the MpiContextManager.
71  *
72  * Note that thread-MPI chooses the number of ranks and constructs its
73  * MPI communicator internally, so does not and is unlikely to ever
74  * participate here.
75  *
76  * \todo Log errors during set up or tear down.
77  * There is no resource for logging or reporting errors during initialization or when misused.
78  *
79  * \ingroup gmxapi
80  */
81 class MpiContextManager
82 {
83 public:
84     /*!
85      * \brief Default constructor.
86      *
87      * Construct a valid instance with an appropriate default value for the
88      * base communicator. (Note that appropriate default value depends on whether
89      * the library was compiled with an external MPI library.)
90      */
91     MpiContextManager();
92
93     ~MpiContextManager();
94
95     /*!
96      * \brief Exclusive ownership of a scoped context means copying is impossible.
97      *
98      * \{
99      */
100     MpiContextManager(const MpiContextManager&) = delete;
101     MpiContextManager& operator=(const MpiContextManager&) = delete;
102     //! \}
103
104     /*!
105      * \brief Transfer ownership of the managed GROMACS MPI context.
106      *
107      * \{
108      */
109     MpiContextManager(MpiContextManager&& source) noexcept = default;
110     MpiContextManager& operator=(MpiContextManager&& source) noexcept = default;
111     //! \}
112
113     /*!
114      * \brief Get the communicator for this context.
115      *
116      * \return Communicator with an appropriate initialized state for the current library
117      * configuration.
118      *
119      * \throws gmxapi::UsageError if MpiContextManager is in an invalid state, such as after
120      *  being moved from.
121      */
122     [[nodiscard]] MPI_Comm communicator() const;
123
124 private:
125     /*!
126      * \brief The resources provided by this manager.
127      *
128      * A valid MpiContextManager has a value for communicator_ that is appropriate
129      * for the library configuration.
130      */
131     std::unique_ptr<MPI_Comm> communicator_;
132 };
133
134 /*!
135  * \brief Context implementation.
136  *
137  * Execution contexts have a uniform interface specified by the API. Implementations for
138  * particular execution environments can specialize / derive from this base.
139  *
140  * \todo Separate interface and implementation.
141  *
142  * \warning Definition and semantics depend on configure-time details. For example,
143  *          MPI-enabled libraries always hold a valid MPI communicator via MpiContextManager,
144  *          whereas tMPI and non-MPI builds hold a meaningless MpiContextManager.
145  *
146  * \todo Provide functions or traits for introspection.
147  *
148  * \ingroup gmxapi
149  */
150 class ContextImpl final : public std::enable_shared_from_this<ContextImpl>
151 {
152 public:
153     ~ContextImpl();
154
155     /*!
156      * \brief Factory function
157      *
158      * Since this class provides `shared_from_this`, we need to make sure
159      * that it never exists without a shared_ptr owning it.
160      *
161      * If we can confirm `shared_from_this` is no longer necessary, implementation may change.
162      * \todo: Use registration/deregistration of launched Sessions to log warnings on shutdown
163      *        instead of letting Session keep ContextImpl alive.
164      *
165      * \return ownership of a new object
166      */
167     static std::shared_ptr<ContextImpl> create(MpiContextManager&& mpi);
168
169     /*!
170      * \brief Copy disallowed because Session state would become ambiguous.
171      *
172      * The API implementation needs to unambiguously determine
173      * which Sessions and Contexts are associated with each other.
174      * \{
175      */
176     ContextImpl(const ContextImpl&) = delete;
177     ContextImpl& operator=(const ContextImpl&) = delete;
178     //! \}
179
180     /*!
181      * \brief Objects are not trivial to move.
182      *
183      * \todo Implement move semantics. Requires a moveable const MpiContextManager and
184      *       LegacyMdrunOptions members.
185      *
186      * \{
187      */
188     ContextImpl(ContextImpl&&) = delete;
189     ContextImpl& operator=(ContextImpl&&) = delete;
190     //! \}
191
192     /*!
193      * \brief Translate the workflow to the execution context and launch.
194      *
195      * \param work workflow graph
196      * \return ownership of a new session
197      *
198      * \todo This probably makes more sense as a free function, but we need to determine access policies.
199      *
200      * Session is returned with shared_ptr ownership so that Context
201      * can hold a weak_ptr and because Session Resources handles
202      * are still evolving.
203      * \todo Hide lifetime management and ownership from handle object.
204      * We can achieve the necessary aspects of this shared_ptr at a lower level of implementation.
205      * Note also that returned value policies can be implemented in higher level wrappers to ensure
206      * correct object lifetime scope. (See pybind, for instance.)
207      */
208     std::shared_ptr<Session> launch(const Workflow& work);
209
210     /*!
211      * \brief Retain the ability to find a launched session while it exists.
212      *
213      * The client owns the Session launched by a Context, but it is helpful
214      * for the Context to know if it has an active Session associated with it.
215      *
216      * \todo Use registration/deregistration protocol instead.
217      *       Requires logging facility.
218      */
219     std::weak_ptr<Session> session_;
220
221     /*!
222      * \brief mdrun command line arguments.
223      *
224      * Store arguments provided by the client and pass them when launching
225      * a simulation runner. This allows client code to access the same
226      * options as are available to mdrun on the command line while the API
227      * evolves.
228      */
229     MDArgs mdArgs_;
230
231     /*!
232      * \brief Legacy option-handling and set up for mdrun.
233      *
234      * This object should not exist, but is necessary now to introduce
235      * the API in a way that means CLI and API work similarly and do not
236      * duplicate definitions e.g. of command-line options.
237      */
238     gmx::LegacyMdrunOptions options_;
239
240     /*!
241      * \brief Scoped MPI management.
242      *
243      * Part of the ContextImpl invariant establishes a point where MPI initialization status is
244      * known.
245      *
246      * Note that the MpiContextManager invariant is dependent on configure-time options to the
247      * GROMACS library. Specifically, MpiContextManager::communicator() is guaranteed to be
248      * MPI_COMM_NULL if and only if the library was built without an external MPI library.
249      * This is confusing and we should split either this class or the
250      * MpiContextManager class. Another alternative would be to use a std::optional here and to
251      * allow the std::unique_ptr<MpiContextManager> provided to ContextImpl::create() to be null,
252      * but that means a slight change of documented behavior protocol. See #3688 and #3650 for
253      * follow up.
254      *
255      * To ensure the MpiContextManager is initialized only once, we use a const member that must
256      * be initialized at construction.
257      */
258     const MpiContextManager mpi_;
259
260 private:
261     /*!
262      * \brief Basic constructor.
263      *
264      * Don't use this. Use create() to get a shared pointer right away.
265      * Otherwise, shared_from_this() is potentially dangerous.
266      */
267     explicit ContextImpl(MpiContextManager&& mpi) noexcept(std::is_nothrow_constructible_v<gmx::LegacyMdrunOptions>);
268 };
269
270
271 class CommHandle
272 {
273 public:
274     using commType = MPI_Comm;
275     MPI_Comm communicator{ MPI_COMM_NULL };
276 };
277
278 } // end namespace gmxapi
279 #endif // GMXAPI_CONTEXT_IMPL_H