4e4bc2752d7d8bc7876a5f657ddb3599332fbb35
[alexxy/gromacs.git] / src / api / cpp / include / gmxapi / gmxapi.h
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2018,2019, 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 /*! \file
36  * \brief Header for public GROMACS C++ API
37  *
38  * API clients include this header. It is intended to provide a minimal set of
39  * declarations to allow independently implemented API clients to make compatible
40  * references to gmxapi objects. Each client will still need to include additional
41  * headers and to link against gmxapi, but clients don't need to be completely ABI
42  * or API compatible with each other. Clients should use the gmxapi versioning utilities
43  * to check for compatibility before accessing members of an object passed by
44  * another client.
45  *
46  * \ingroup gmxapi
47  */
48 /*! \mainpage
49  * The `gmxapi` library allows GROMACS extension and client code to interact with
50  * GROMACS internals without compile-time dependence on the GROMACS source code.
51  * It is sufficient to use the headers described here and to link against `libgmxapi`
52  * CMake helpers make it
53  * easy to compile your own code against an installed copy of GROMACS.
54  *
55  * Using the `Gromacs::gmxapi` CMake target, the headers described here can be
56  * included from `gmxapi/...` and symbols in the `::gmxapi` C++ namespace can be
57  * resolved in the library by link time.
58  *
59  * For API stability and unambiguous versioning, headers in the top-level `gmxapi/`
60  * directory should not refer to the `gmx` namespace used by the core GROMACS library,
61  * except for gromacsfwd.h, which consolidates forward declarations in the `gmx` namespace
62  * required by the extension APIs in subdirectories like `gmxapi/md`.
63  * \todo gromacsfwd.h probably shouldn't be in the top level either...
64  * To reduce dependencies, headers should not depend on headers at a deeper level
65  * than themselves, where versioning and compatibility guarantees are weaker
66  * (possibly dependent on GROMACS versions) or feature-dependent.
67  *
68  * Refer to the <a href="modules.html">modules</a> section for a hierarchical
69  * overview of the API documentation.
70  */
71 /*!
72  * \defgroup gmxapi gmxapi
73  *
74  * \brief Provide external access to an installed GROMACS binary through the gmxapi library.
75  *
76  * API client code primarily operates on Proxy objects, which can be copied,
77  * serialized, and configured independently of execution context, data location,
78  * or parallelism.
79  *
80  * When data needs to be accessed or moved, smart Handle objects are used.
81  * DataHandle objects explicitly describe ownership and access characteristics,
82  * and so are not copyable, but can have ownership transferred. DataHandle objects
83  * should be held briefly, since the implementation can depend on the current
84  * execution context and can affect performance.
85  *
86  * Computation Modules and Runners have Proxy objects whose lifetime is independent
87  * of execution context, and Implementation objects whose lifetimes are necessarily
88  * bounded by the execution Context in which they run.
89  *
90  * Proxy and Implementation classes use Interfaces defined by the API, which can also be
91  * subclassed in client code to extend the library functionality.
92  *
93  * The Implementation class supporting a given proxy object is likely to change between
94  * successive handle requests, using a "state" behavioral design pattern.
95  *
96  * API classes participating in behavioral protocols beyond RAII construction and
97  * destruction use implementation inheritance or separate manager classes to
98  * ensure proper behavior by classes
99  * implementing the relevant Interface. E.g. if the API specifies that an
100  * Interface requires calls to object->initialize() to be followed by calls to
101  * object->deinitialize(), the API library provides mechanisms to guarantee
102  * that clients of the interface execute an appropriate state machine.
103  *
104  * The runner for a workflow cannot be fully instantiated until the execution context
105  * is active, but the execution context cannot be configured without some knowledge
106  * of the work to be performed. Thus, initialization requires several stages in
107  * which the Context is configured with knowledge of the work to be performed,
108  * a Session is launched and can provide resources to the computational
109  * components at run time.
110  *
111  * For example:
112  *
113  * A client may create a MDProxy to pass to a RunnerProxy to pass to a Context, optionally
114  * keeping references to the proxies or not.
115  * The client asks the Context for a Session.
116  * Context asks RunnerProxy for a RunnerBuilder, which asks MDProxy for an MD Engine builder.
117  * Each builder (and product) gets a handle to the Context.
118  * MDEngineBuilder produces and returns a handle to a new MDEngine.
119  * RunnerBuilder produces a Runner with a handle to the MDEngine and returns runner handle to session.
120  * Session makes API calls to runner handle until it no longer needs it and releases it.
121  * Releasing the handle can provide notification to the runner.
122  * When no references remain (as via handles), the runner is destroyed (same for MDEngine).
123  * In the case of complex shutdown, the Context holds the last references to the runners and manages shutdown.
124  *
125  * For maximal compatibility with other libgmxapi clients (such as third-party
126  * Python modules), client code should use the wrappers and protocols in the
127  * gmxapi.h header. Note that there is a separate CMake target to build the full
128  * developer documentation for gmxapi: `gmxapi-cppdocs-dev`.
129  *
130  * # Possible Example
131  *
132  * Configure a system and work specification from a TPR file, configure locally-detected computing resources,
133  * and run the workflow.
134  *
135  *      std::string filename;
136  *      // ...
137  *      auto system = gmxapi::fromTprFile(filename);
138  *      auto session = system.launch();
139  *      auto status = session.run();
140  *      return status.success();
141  *
142  * Load a TPR file, extract some data, modify the workflow, then run.
143  *
144  *
145  *      auto system = gmxapi::fromTprFile(filename);
146  *      // Load some custom code:
147  *      auto potential = myplugin::MyPotential::newSpec();
148  *      // ...
149  *      auto atoms = system.atoms();
150  *      {
151  *          // Acquire read handle to global data in the current thread and/or MPI rank.
152  *          const auto scopedPositionsHandle = gmxapi::extractLocally(atoms.positions, gmxapi::readOnly());
153  *          for (auto&& position : scopedPositionsHandle)
154  *          {
155  *              // do something
156  *          }
157  *          // Release the read handle in the current code block (a shared reference may still be held elsewhere)
158  *      }
159  *      system.addPotential(potential);
160  *      auto session = system.launch();
161  *      auto status = session.run();
162  *      return status.success();
163  *
164  * \internal
165  * To extend the API may require GROMACS library
166  * headers and possibly linking against `libgromacs`.
167  * Where possible, though, gmxapi public interfaces never require the instantiation
168  * of a gmx object, but instead take them as parameters. The (private) API implementation
169  * code then uses binding protocols defined in the library API to connect the
170  * GROMACS library objects (or interfaces), generally in an exchange such as the
171  * following.
172  *
173  * gmxapi client code:
174  *
175  *     MyExtension local_object;
176  *     gmxapi::SomeThinWrapper wrapper_object = gmxapi::some_function();
177  *     local_object.bind(wrapper_object);
178  *     gmxapi::some_other_function(wrapper_object);
179  *
180  * `MyExtension` implementation:
181  *
182  *     class MyExtension : public gmxapi::SomeExtensionInterface
183  *     {
184  *          void bind(gmxapi::SomeThinWrapper wrapper)
185  *          {
186  *              std::shared_ptr<gmxapi::SomeWrappedInterface> api_object = wrapper.getSpec();
187  *              api_object->register(this);
188  *          };
189  *          //...
190  *     };
191  *
192  * gmxapi protocol:
193  *
194  *     void gmxapi::SomeWrappedInterface::register(gmxapi::SomeExtensionInterface* client_object)
195  *     {
196  *          gmx::InterestingCoreLibraryClass* core_object = this->non_public_interface_function();
197  *          gmx::SomeLibraryClass* library_object = client_object->required_protocol_function();
198  *          core_object->do_something(library_object);
199  *     };
200  *
201  *
202  * Refer to GMXAPI developer docs for the protocols that map gmxapi interfaces to
203  * GROMACS library interfaces.
204  * Refer to the GROMACS developer
205  * documentation for details on library interfaces forward-declared in this header.
206  *
207  * For binding Molecular Dynamics modules into a simulation, the thin wrapper class is gmxapi::MDHolder.
208  * It provides the MD work specification that can provide and interface that an IRestraintPotential
209  * can bind to.
210  *
211  * The gmxpy Python module provides a method gmx.core.MD().add_force(force) that immediately calls
212  * force.bind(mdholder), where mdholder is an object with Python bindings to gmxapi::MDHolder. The
213  * Python interface for a restraint must have a bind() method that takes the thin wrapper. The
214  * C++ extension implementing the restraint unpacks the wrapper and provides a gmxapi::MDModule to
215  * the gmxapi::MDWorkSpec. The MDModule provided must be able to produce a gmx::IRestraintPotential
216  * when called upon later during simulation launch.
217  *
218  * external Restraint implementation:
219  *
220  *      class MyRestraintModule : public gmxapi::MDModule
221  *      {
222  *          // not a specified interface at the C++ level. Shown for Python module implementation.
223  *          void bind(gmxapi::MDHolder wrapper)
224  *          {
225  *              auto workSpec = wrapper->getSpec();
226  *              shared_ptr<gmxapi::MDModule> module = this->getModule();
227  *              workSpec->addModule(module);
228  *          }
229  *      };
230  *
231  * When the simulation is launched, the work specification is passed to the runner, which binds to
232  * the restraint module as follows. The execution session is launched with access to the work specification.
233  *
234  * Possible client code
235  *
236  *      gmxapi::System system;
237  *      // System can provide an interface with which
238  *      //...
239  *      auto session = gmxapi::Session::create(system);
240  *      session.run();
241  *
242  * Possible API implementation
243  *
244  *      static std::unique_ptr<gmxapi::Session> gmxapi::Session::create(shared_ptr<gmxapi::System> system)
245  *      {
246  *          auto spec = system->getWorkSpec();
247  *          for (auto&& module : spec->getModules())
248  *          {
249  *              if (providesRestraint(module))
250  *              {
251  *                  // gmxapi restraint management protocol
252  *                  runner->setRestraint(module);
253  *              }
254  *          }
255  *
256  *      }
257  *
258  *      // gmxapi restraint management protocol
259  *      void gmxapi::Session::setRestraint(std::shared_ptr<gmxapi::MDModule> module)
260  *      {
261  *          auto runner = impl_->getRunner();
262  *          auto restraint = module->getRestraint();
263  *          runner->addPullPotential(restraint, module->name());
264  *      }
265  *
266  */
267
268 #ifndef GMXAPI_H
269 #define GMXAPI_H
270
271 #include <memory>
272 #include <string>
273
274 /*! \brief Contains the external C++ Gromacs API.
275  *
276  * High-level interfaces for client code is provided in the gmxapi namespace.
277  *
278  * \ingroup gmxapi
279  */
280 namespace gmxapi
281 {
282
283 // Forward declarations for other gmxapi classes.
284 class MDWorkSpec;
285 class MDModule;
286
287 /*!
288  * \brief API name for MDHolder struct.
289  *
290  * Any object that includes this header (translation unit) will have the const cstring embedded as
291  * defined here. The char array is not a global symbol.
292  *
293  * \todo Consider if this is in the right place.
294  *
295  * This header defines the API, but we may want to assure ABI compatibility. On the
296  * other hand, we may want to embed the API version in the structure itself and leave
297  * the higher-level name more general.
298  */
299 static constexpr const char MDHolder_Name[] = "__GMXAPI_MDHolder_v1__";
300
301 /*!
302  * \brief Minimal holder for exchanging MD specifications.
303  *
304  * The interface is minimal in the hopes of backwards and forwards compatibility
305  * across build systems. This type specification can be embedded in any client code
306  * so that arbitrary client code has a robust way to exchange data, each depending
307  * only on the gmxapi library and not on each other.
308  *
309  * Objects of this type are intended to serve as wrappers used briefly to establish
310  * shared ownership of a MDWorkSpec between binary objects.
311  *
312  * A sufficiently simple struct can be defined for maximum forward/backward compatibility
313  * and given a name that can be used to uniquely identify Python capsules or other data
314  * members that provide a pointer to such an object for a highly compatible interface.
315  *
316  * \todo Consider whether/how we might use a wrapper like this to enable a C API.
317  * We may be able to replace this public type specification with an opaque declaration
318  * and a set of free functions, but the change would need to be tested in the
319  * context of the Python bindings and use cases at https://github.com/kassonlab/gmxapi
320  *
321  * Example:
322  *
323  *     py::class_< ::gmxapi::MDModule, std::shared_ptr<::gmxapi::MDModule> >
324  *         gmxapi_mdmodule(m, "MDModule", py::module_local());
325  *     gmxapi_mdmodule.def(
326  *       "bind",
327  *       [](std::shared_ptr<TestModule> self, py::object object){
328  *           if (PyCapsule_IsValid(object.ptr(), gmxapi::MDHolder::api_name))
329  *           {
330  *               auto holder = (gmxapi::MDHolder*) PyCapsule_GetPointer(
331  *                   object.ptr(),
332  *                   gmxapi::MDHolder::api_name);
333  *               auto spec = holder->getSpec();
334  *               std::cout << self->name() << " received " << holder->name();
335  *               std::cout << " containing spec of size ";
336  *               std::cout << spec->getModules().size();
337  *               std::cout << std::endl;
338  *               spec->addModule(self);
339  *           }
340  *           else
341  *           {
342  *               throw gmxapi::ProtocolError("MDModule bind method requires properly named PyCapsule input.");
343  *           }
344  *       }
345  *     );
346  *
347  *     py::class_<System, std::shared_ptr<System> > system(m, "MDSystem");
348  *     system.def(py::init(), "A blank system object is possible, but not useful. Use a helper function.");
349  *     system.def("launch",
350  *                 [](System* system, std::shared_ptr<Context> context)
351  *                 {
352  *                     return system->launch(context);
353  *                 },
354  *                 "Launch the configured workflow in the provided context.");
355  *
356  *     system.def(
357  *         "add_mdmodule",
358  *         [](System* system, py::object force_object){
359  *             // If force_object has a bind method, give it a PyCapsule with a pointer
360  *             // to our C++ object.
361  *             if (py::hasattr(force_object, "bind"))
362  *             {
363  *                 auto spec = system->getSpec();
364  *                 auto holder = new gmxapi::MDHolder(spec);
365  *                 holder->name_ = "pygmx holder";
366  *                 auto deleter = [](PyObject *o) {
367  *                     if (PyCapsule_IsValid(o, gmxapi::MDHolder_Name))
368  *                     {
369  *                         auto holder_ptr = (gmxapi::MDHolder *) PyCapsule_GetPointer(o, gmxapi::MDHolder_Name);
370  *                         delete holder_ptr;
371  *                     };
372  *                 };
373  *                 auto capsule = py::capsule(holder,
374  *                                            gmxapi::MDHolder_Name,
375  *                                            deleter);
376  *                 py::object bind = force_object.attr("bind");
377  *                 // py::capsule does not have bindings and does not implicitly convert to py::object
378  *                 py::object obj = capsule;
379  *                 bind(obj);
380  *                 std::cout << "Work specification now has " << spec->getModules().size() << " modules." << std::endl;
381  *             }
382  *             else
383  *             {
384  *                 // Note: Exception behavior is likely to change.
385  *                 // Ref: https://github.com/kassonlab/gmxapi/issues/125
386  *                 throw PyExc_RuntimeError;
387  *             }
388  *         },
389  *         "Set a restraint potential for the system.");
390  *
391  * \ingroup gmxapi_md
392  */
393 class MDHolder
394 {
395 public:
396     /*!
397      * \brief Declare the schema for extra checks before casting.
398      *
399      * MDHolder exists for cases where API objects can only be passed by
400      * casting pointers. api_name is suitable as a descriptor of the schema
401      * of the object pointed to. See, for instance, usage of the PyCapsule_IsValid
402      * Python C API function.
403      */
404     static const char* api_name;
405
406     MDHolder();
407
408     /*!
409      * \brief For convenience and logging, give the object an identifying string.
410      *
411      * \param name
412      */
413     explicit MDHolder(std::string name);
414
415     /*!
416      * \brief Wrap a Molecular Dynamics work specification.
417      *
418      * The container allows portable specification of MD work to be performed.
419      * It is used when setting up and then launching the simulation.
420      *
421      * \param spec references a container with interfaces for client and library APIs
422      *
423      * Example:
424      *
425      *     # With `system` as a gmxapi::System object
426      *     auto spec = system->getSpec();
427      *     auto holder = std::make_unique<gmxapi::MDHolder>(spec);
428      *
429      * A PyCapsule object with the name given by gmxapi::MDHolder_Name is assumed to
430      * contain a pointer to an MDHolder and to have an appropriate deleter attached.
431      *
432      * Example:
433      *
434      *     auto deleter = [](PyObject *o) {
435      *         if (PyCapsule_IsValid(o, gmxapi::MDHolder_Name))
436      *         {
437      *             auto holder_ptr = (gmxapi::MDHolder *) PyCapsule_GetPointer(o,
438      * gmxapi::MDHolder_Name); delete holder_ptr;
439      *         };
440      *     };
441      *     # With pybind11 PyCapsule bindings:
442      *     auto capsule = py::capsule(holder,
443      *                                gmxapi::MDHolder_Name,
444      *                                deleter);
445      *
446      * The gmxapi Python package gives modules a chance to associate themselves with a
447      * gmxapi::System object by passing such a PyCapsule to its `bind` method, if implemented.
448      *
449      * Such a bind method could be implemented as follows. Assume object.ptr() returns a
450      * `PyObject*`
451      *
452      * Example:
453      *
454      *    PyObject* capsule = object.ptr();
455      *    if (PyCapsule_IsValid(capsule, gmxapi::MDHolder::api_name))
456      *    {
457      *        auto holder = static_cast<gmxapi::MDHolder*>(PyCapsule_GetPointer(capsule,
458      *            gmxapi::MDHolder::api_name));
459      *        auto workSpec = holder->getSpec();
460      *        workSpec->addModule(module);
461      *    }
462      *    else
463      *    {
464      *        throw gmxapi::ProtocolError("bind method requires a python capsule as input");
465      *    }
466      */
467     explicit MDHolder(std::shared_ptr<MDWorkSpec> spec);
468
469     /*!
470      * \brief Get client-provided name.
471      *
472      * \return Name as string.
473      */
474     std::string name() const;
475
476     /*! \brief Instance name.
477      */
478     std::string name_{};
479
480     /// \{
481     /*!
482      * \brief Get the wrapped work specification
483      * \return shared ownership of the api object.
484      */
485     std::shared_ptr<MDWorkSpec> getSpec();
486     /*!
487      * \brief Get the wrapped work specification
488      * \return smart pointer to const object
489      */
490     std::shared_ptr<const MDWorkSpec> getSpec() const;
491     /// \}
492 private:
493     /*! \cond internal
494      * \brief private implementation class
495      */
496     class Impl;
497     /// \brief opaque pointer to implementation
498     std::shared_ptr<Impl> impl_{ nullptr };
499     /*! \endcond */
500 };
501
502 /*!
503  * \brief Label the types recognized by gmxapi.
504  *
505  * Provide an enumeration to aid in translating data between languages, APIs,
506  * and storage formats.
507  *
508  * \todo The spec should explicitly map these to types in APIs already used.
509  * e.g. MPI, Python, numpy, GROMACS, JSON, etc.
510  * \todo Actually check the size of the types.
511  *
512  * \see https://redmine.gromacs.org/issues/2993 for discussion.
513  */
514 enum class GmxapiType
515 {
516     NULLTYPE, //! Reserved
517     MAP,      //! Mapping of key name (string) to a value of some MdParamType
518     BOOL,     //! Boolean logical type
519     INT64,    //! 64-bit integer type
520     FLOAT64,  //! 64-bit float type
521     STRING,   //! string with metadata
522     NDARRAY,  //! multi-dimensional array with metadata
523 };
524 } // end namespace gmxapi
525
526 #endif // header guard