2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
36 * \brief Header for public GROMACS C++ API
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
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.
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.
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.
68 * Refer to the <a href="modules.html">modules</a> section for a hierarchical
69 * overview of the API documentation.
72 * \defgroup gmxapi gmxapi
74 * \brief Provide external access to an installed GROMACS binary through the gmxapi library.
76 * API client code primarily operates on Proxy objects, which can be copied,
77 * serialized, and configured independently of execution context, data location,
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.
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.
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.
93 * The Implementation class supporting a given proxy object is likely to change between
94 * successive handle requests, using a "state" behavioral design pattern.
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.
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.
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.
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`.
132 * Configure a system and work specification from a TPR file, configure locally-detected computing resources,
133 * and run the workflow.
135 * std::string filename;
137 * auto system = gmxapi::fromTprFile(filename);
138 * auto session = system.launch();
139 * auto status = session.run();
140 * return status.success();
142 * Load a TPR file, extract some data, modify the workflow, then run.
145 * auto system = gmxapi::fromTprFile(filename);
146 * // Load some custom code:
147 * auto potential = myplugin::MyPotential::newSpec();
149 * auto atoms = system.atoms();
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)
157 * // Release the read handle in the current code block (a shared reference may still be held elsewhere)
159 * system.addPotential(potential);
160 * auto session = system.launch();
161 * auto status = session.run();
162 * return status.success();
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
173 * gmxapi client code:
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);
180 * `MyExtension` implementation:
182 * class MyExtension : public gmxapi::SomeExtensionInterface
184 * void bind(gmxapi::SomeThinWrapper wrapper)
186 * std::shared_ptr<gmxapi::SomeWrappedInterface> api_object = wrapper.getSpec();
187 * api_object->register(this);
194 * void gmxapi::SomeWrappedInterface::register(gmxapi::SomeExtensionInterface* client_object)
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);
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.
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
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.
218 * external Restraint implementation:
220 * class MyRestraintModule : public gmxapi::MDModule
222 * // not a specified interface at the C++ level. Shown for Python module implementation.
223 * void bind(gmxapi::MDHolder wrapper)
225 * auto workSpec = wrapper->getSpec();
226 * shared_ptr<gmxapi::MDModule> module = this->getModule();
227 * workSpec->addModule(module);
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.
234 * Possible client code
236 * gmxapi::System system;
237 * // System can provide an interface with which
239 * auto session = gmxapi::Session::create(system);
242 * Possible API implementation
244 * static std::unique_ptr<gmxapi::Session> gmxapi::Session::create(shared_ptr<gmxapi::System> system)
246 * auto spec = system->getWorkSpec();
247 * for (auto&& module : spec->getModules())
249 * if (providesRestraint(module))
251 * // gmxapi restraint management protocol
252 * runner->setRestraint(module);
258 * // gmxapi restraint management protocol
259 * void gmxapi::Session::setRestraint(std::shared_ptr<gmxapi::MDModule> module)
261 * auto runner = impl_->getRunner();
262 * auto restraint = module->getRestraint();
263 * runner->addPullPotential(restraint, module->name());
274 /*! \brief Contains the external C++ Gromacs API.
276 * High-level interfaces for client code is provided in the gmxapi namespace.
283 // Forward declarations for other gmxapi classes.
288 * \brief API name for MDHolder struct.
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.
293 * \todo Consider if this is in the right place.
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.
299 static constexpr const char MDHolder_Name[] = "__GMXAPI_MDHolder_v1__";
302 * \brief Minimal holder for exchanging MD specifications.
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.
309 * Objects of this type are intended to serve as wrappers used briefly to establish
310 * shared ownership of a MDWorkSpec between binary objects.
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.
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
323 * py::class_< ::gmxapi::MDModule, std::shared_ptr<::gmxapi::MDModule> >
324 * gmxapi_mdmodule(m, "MDModule", py::module_local());
325 * gmxapi_mdmodule.def(
327 * [](std::shared_ptr<TestModule> self, py::object object){
328 * if (PyCapsule_IsValid(object.ptr(), gmxapi::MDHolder::api_name))
330 * auto holder = (gmxapi::MDHolder*) PyCapsule_GetPointer(
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);
342 * throw gmxapi::ProtocolError("MDModule bind method requires properly named PyCapsule input.");
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)
352 * return system->launch(context);
354 * "Launch the configured workflow in the provided context.");
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"))
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))
369 * auto holder_ptr = (gmxapi::MDHolder *) PyCapsule_GetPointer(o, gmxapi::MDHolder_Name);
373 * auto capsule = py::capsule(holder,
374 * gmxapi::MDHolder_Name,
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;
380 * std::cout << "Work specification now has " << spec->getModules().size() << " modules." << std::endl;
384 * // Note: Exception behavior is likely to change.
385 * // Ref: https://github.com/kassonlab/gmxapi/issues/125
386 * throw PyExc_RuntimeError;
389 * "Set a restraint potential for the system.");
397 * \brief Declare the schema for extra checks before casting.
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.
404 static const char* api_name;
409 * \brief For convenience and logging, give the object an identifying string.
413 explicit MDHolder(std::string name);
416 * \brief Wrap a Molecular Dynamics work specification.
418 * The container allows portable specification of MD work to be performed.
419 * It is used when setting up and then launching the simulation.
421 * \param spec references a container with interfaces for client and library APIs
425 * # With `system` as a gmxapi::System object
426 * auto spec = system->getSpec();
427 * auto holder = std::make_unique<gmxapi::MDHolder>(spec);
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.
434 * auto deleter = [](PyObject *o) {
435 * if (PyCapsule_IsValid(o, gmxapi::MDHolder_Name))
437 * auto holder_ptr = (gmxapi::MDHolder *) PyCapsule_GetPointer(o,
438 * gmxapi::MDHolder_Name); delete holder_ptr;
441 * # With pybind11 PyCapsule bindings:
442 * auto capsule = py::capsule(holder,
443 * gmxapi::MDHolder_Name,
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.
449 * Such a bind method could be implemented as follows. Assume object.ptr() returns a
454 * PyObject* capsule = object.ptr();
455 * if (PyCapsule_IsValid(capsule, gmxapi::MDHolder::api_name))
457 * auto holder = static_cast<gmxapi::MDHolder*>(PyCapsule_GetPointer(capsule,
458 * gmxapi::MDHolder::api_name));
459 * auto workSpec = holder->getSpec();
460 * workSpec->addModule(module);
464 * throw gmxapi::ProtocolError("bind method requires a python capsule as input");
467 explicit MDHolder(std::shared_ptr<MDWorkSpec> spec);
470 * \brief Get client-provided name.
472 * \return Name as string.
474 std::string name() const;
476 /*! \brief Instance name.
482 * \brief Get the wrapped work specification
483 * \return shared ownership of the api object.
485 std::shared_ptr<MDWorkSpec> getSpec();
487 * \brief Get the wrapped work specification
488 * \return smart pointer to const object
490 std::shared_ptr<const MDWorkSpec> getSpec() const;
494 * \brief private implementation class
497 /// \brief opaque pointer to implementation
498 std::shared_ptr<Impl> impl_{ nullptr };
503 * \brief Label the types recognized by gmxapi.
505 * Provide an enumeration to aid in translating data between languages, APIs,
506 * and storage formats.
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.
512 * \see https://redmine.gromacs.org/issues/2993 for discussion.
514 enum class GmxapiType
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
524 } // end namespace gmxapi
526 #endif // header guard