From 782daee58d75e7f7a7b713d97d69d7d246ad2736 Mon Sep 17 00:00:00 2001 From: "M. Eric Irrgang" Date: Wed, 6 Jun 2018 16:50:31 +0300 Subject: [PATCH] gmxapi-30 Tutorial / working Jupyter notebook examples * Add a bunch of clarifying documentation to the source code. * Remove stale comments, dead code, and other cruft. * Expand the description of how the plugins work. --- README.rst | 29 +++- src/cpp/ensemblepotential.cpp | 35 +++-- src/cpp/ensemblepotential.h | 216 ++++++++++++++++++++++++----- src/cpp/harmonicpotential.cpp | 2 +- src/cpp/harmonicpotential.h | 66 +++++++-- src/pythonmodule/export_plugin.cpp | 185 +++++++++++++++++------- 6 files changed, 420 insertions(+), 113 deletions(-) diff --git a/README.rst b/README.rst index 1912af1..47f66d1 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ This repository uses CMake to build and install a Python C++ extension package. * ``CMakeLists.txt``, ``cmake/FindGROMACS.cmake``, and ``src/CMakeLists.txt`` provide necessary CMake infrastructure. You should not need to edit these. * ``src/cpp`` contains a header and ``cpp`` file for each restraint potential built with this module. When adding new potentials, you will update ``CMakeLists.txt`` to create build targets. Use the existing potentials as examples. -* ``src/pythonmodule/`` contains ``CMakeLists.txt``, ``export_plugin.h``, and ``export_plugin.cpp``. When you have written a new potential, you can add it to ``CMakeLists.txt`` and ``export_plugin.cpp``. This is the code that produces the C++ extension for Python. +* ``src/pythonmodule/`` contains ``CMakeLists.txt``, ``export_plugin.h``, and ``export_plugin.cpp``. When you have written a new potential, you can add it to ``CMakeLists.txt`` and ``export_plugin.cpp``. This is the code that produces the C++ extension for Python. ``HarmonicRestraint`` is a simple example that applies a Hooke's Law spring between two atoms. ``EnsembleHarmonic`` applies a more complicated potential and uses additional facilities provided by gmxapi. * ``src/pybind11`` is just a copy of the Python bindings framework from the Pybind project (ref https://github.com/pybind/pybind11 ). It is used to wrap the C++ restraint code and give it a Python interface. * ``tests/`` contains C++ and Python tests for the provided code. Update ``CMakeLists.txt`` to add your own, based on these examples. C++ unit tests use `googletest`_. Python tests use the `pytest `_. Refer to those respective projects for more about how they make test-writing easier. * ``examples`` contains a sample SLURM job script and ``restrained-ensemble.py`` gmxapi script that have been used to do restrained ensemble simulations. ``example.py`` and ``example.ipynb`` explore a toy alanine dipeptide system. ``strip_notebook.py`` is a helper script to remove extra output and state data from an iPython notebook before checking updates back into the repository. @@ -141,6 +141,33 @@ The actual filename will be something like ``libharmonicpotential.so`` or ``harm or something depending on your operating system. These libraries are used to build a Python module named ``myplugin``. +When setting up a workflow, a Python script provides gmxapi with parameters and a factory function +for a plugin restraint potential. This Python interface is defined in ``src/pythonmodule/export_plugin.cpp``. +When a Session is launched, an C++ object that performs restraint force calculations is created and +given to the GROMACS library. During each MD step, part of the MD force evaluation includes a call +to the calculations performed by the restraint. For the pair restraints demonstrated here, GROMACS +provides relative coordinates of two atomic sites to the calculation code in the plugin. If multiple +restrained pairs are needed, multiple restraints are attached to the simulation. Coordination across +an ensemble of simulations is possible using resources provided by the Session. + +Fundamentally, a new restraint potential is implemented by creating a class that provides a +``calculate()`` method and using wrappers to give it interfaces to GROMACS and to Python. +C++ wrappers allow the basic class implementing the potential to be presented to the GROMACS +library in a way that can be used to evaluate forces during a simulation. Other C++ template + code wraps the potential in a portable way so that it can be passed to GROMACS through a Python + interface and to receive parameters from the Python interpreter. Pybind11 syntax in +``export_plugin.cpp`` provides the code to actually expose the plugin as a class in a Python module +that is compatible with the ``gmx`` package provided in the ``gmxapi`` project. + +By version +0.1.0, additional wrappers and boilerplate code will be migrated out of the files that +define the ``calculate()`` methods. Until then, some amount of copy-and-paste or editing is +necessary to implement a new potential. Refer to ``src/cpp/harmonicpotential.h`` and to + ``src/cpp/harmonicpotential.cpp`` for a documented example of a simple pair restraint. A more +complex example is found in the ``ensemblepotential`` files. The code in ``src/cpp`` is sufficient +to produce testable object code, but the Python module is exported in ``src/pythonmodule/export_plugin.cpp``. If you add additional source files for a new potential, +you will need to update ``src/cpp/CMakeLists.txt`` as well. + Python tests ============ diff --git a/src/cpp/ensemblepotential.cpp b/src/cpp/ensemblepotential.cpp index 56ea76d..33e555a 100644 --- a/src/cpp/ensemblepotential.cpp +++ b/src/cpp/ensemblepotential.cpp @@ -2,6 +2,16 @@ // Created by Eric Irrgang on 2/26/18. // +/*! \file + * \brief Code to implement the potential declared in ensemblepotential.h + * + * This file currently contains boilerplate that will not be necessary in future gmxapi releases, as + * well as additional code used in implementing the restrained ensemble example workflow. + * + * A simpler restraint potential would only update the calculate() function. If a callback function is + * not needed or desired, remove the callback() code from this file and from ensemblepotential.h + */ + #include "ensemblepotential.h" #include @@ -21,14 +31,6 @@ void EnsembleResourceHandle::reduce(const Matrix &send, (*_reduce)(send, receive); } -template -void EnsembleResourceHandle::map_reduce(const T_I &iterable, - T_O *output, - void (*function)(double, const PairHist & input, - PairHist * output) - ) -{} - /*! * \brief Apply a Gaussian blur when building a density grid for a list of values. * @@ -154,7 +156,13 @@ EnsembleHarmonic::EnsembleHarmonic(const input_param_type ¶ms) : { } -// Todo: reference coordinate for PBC problems. +// +// +// HERE is the (optional) function that updates the state of the restraint periodically. +// It is called before calculate() once per timestep per simulation (on the master rank of +// a parallelized simulation). +// +// void EnsembleHarmonic::callback(gmx::Vector v, gmx::Vector v0, double t, @@ -255,6 +263,12 @@ void EnsembleHarmonic::callback(gmx::Vector v, } + +// +// +// HERE is the function that does the calculation of the restraint force. +// +// gmx::PotentialPointData EnsembleHarmonic::calculate(gmx::Vector v, gmx::Vector v0, double t) @@ -316,7 +330,8 @@ EnsembleResourceHandle EnsembleResources::getHandle() const return handle; } -// Explicitly instantiate a definition. +// Important: Explicitly instantiate a definition for the templated class declared in ensemblepotential.h. +// Failing to do this will cause a linker error. template class ::plugin::RestraintModule; } // end namespace plugin diff --git a/src/cpp/ensemblepotential.h b/src/cpp/ensemblepotential.h index aa0f56d..86107bb 100644 --- a/src/cpp/ensemblepotential.h +++ b/src/cpp/ensemblepotential.h @@ -5,6 +5,20 @@ #ifndef HARMONICRESTRAINT_ENSEMBLEPOTENTIAL_H #define HARMONICRESTRAINT_ENSEMBLEPOTENTIAL_H +/*! \file + * \brief Provide restrained ensemble MD potential for GROMACS plugin. + * + * The restraint implemented here uses a facility provided by gmxapi to perform averaging of some + * array data across an ensemble of simulations. Simpler pair restraints can use less of this + * example code. + * + * Contains a lot of boiler plate that is being generalized and migrate out of this file, but other + * pair restraints can be implemented by following the example in this and ``ensemblepotential.cpp``. + * The ``CMakeLists.txt`` file will need to be updated if you add additional source files, and + * ``src/pythonmodule/export_plugin.cpp`` will need to be updated if you add or change the name of + * potentials. + */ + #include #include #include @@ -15,6 +29,7 @@ #include "gromacs/restraint/restraintpotential.h" #include "gromacs/utility/real.h" +// We do not require C++14, so we have a back-ported C++14 feature for C++11 code. #include "make_unique.h" namespace plugin @@ -23,7 +38,6 @@ namespace plugin // Histogram for a single restrained pair. using PairHist = std::vector; - // Stop-gap for cross-language data exchange pending SharedData implementation and inclusion of Eigen. // Adapted from pybind docs. template @@ -59,6 +73,8 @@ extern template class Matrix; /*! * \brief An active handle to ensemble resources provided by the Context. * + * gmxapi version 0.1.0 will provide this functionality through SessionResources. + * * The semantics of holding this handle aren't determined yet, but it should be held as briefly as possible since it * may involve locking global resources or preventing the simulation from advancing. Basically, though, it allows the * Context implementation flexibility in how or where it provides services. @@ -69,33 +85,13 @@ class EnsembleResourceHandle /*! * \brief Ensemble reduce. * - * For first draft, assume an all-to-all sum. Reduce the input into the stored Matrix. - * // Template later... \tparam T - * \param data - */ -// void reduce(const Matrix& input); - - /*! - * \brief Ensemble reduce. * \param send Matrices to be summed across the ensemble using Context resources. * \param receive destination of reduced data instead of updating internal Matrix. */ void reduce(const Matrix &send, Matrix *receive) const; - /*! - * \brief Apply a function to each input and accumulate the output. - * - * \tparam I Iterable. - * \tparam T Output type. - * \param iterable iterable object to produce inputs to function - * \param output structure that should be present and up-to-date on all ranks. - * \param function map each input in iterable through this function to accumulate output. - */ - template - void map_reduce(const I& iterable, T* output, void (*function)(double, const PairHist&, PairHist*)); - - // to be abstracted and hidden... + // to be abstracted and hidden in an upcoming version... const std::function&, Matrix*)>* _reduce; }; @@ -105,14 +101,36 @@ class EnsembleResourceHandle * Provides a connection to the higher-level workflow management with which to access resources and operations. The * reference provides no resources directly and we may find that it should not extend the life of a Session or Context. * Resources are accessed through Handle objects returned by member functions. + * + * gmxapi version 0.1.0 will provide this functionality through SessionResources. */ class EnsembleResources { public: + /*! + * \brief Create a new resources object. + * + * This constructor is called by the framework during Session launch to provide the plugin + * potential with external resources. + * + * \param reduce ownership of a function object providing ensemble averaging of a 2D matrix. + */ explicit EnsembleResources(std::function&, Matrix*)>&& reduce) : reduce_(reduce) {}; + /*! + * \brief Get a handle to the resources for the current timestep. + * + * Objects should not keep resource handles open for longer than a single block of code. + * calculate() and callback() functions get a handle to the resources for the current time step + * by calling getHandle(). + * + * \return resource handle + * + * In this release, the only facility provided by the resources is a function object for + * the ensemble averaging function provided by the Context. + */ EnsembleResourceHandle getHandle() const; private: @@ -123,14 +141,34 @@ class EnsembleResources /*! * \brief Template for MDModules from restraints. * + * Allows a GROMACS module to be produced easily from the provided class. Refer to + * src/pythonmodule/export_plugin.cpp for how this template is used. + * * \tparam R a class implementing the gmx::IRestraintPotential interface. + * + * The template type parameter should define a ``input_param_type`` member type. + * + * \todo move this to a template header in gmxapi */ template -class RestraintModule : public gmxapi::MDModule // consider names +class RestraintModule : public gmxapi::MDModule { public: using param_t = typename R::input_param_type; + /*! + * \brief Construct a named restraint module. + * + * Objects of this type are created during Session launch, so this code really doesn't belong + * here. The Director / Builder for the restraint uses a generic interface to pass standard + * parameters for pair restraints: a list of sites, a (custom) parameters structure, and + * resources provided by the Session. + * + * \param name + * \param sites + * \param params + * \param resources + */ RestraintModule(std::string name, std::vector sites, const typename R::input_param_type& params, @@ -145,12 +183,27 @@ class RestraintModule : public gmxapi::MDModule // consider names ~RestraintModule() override = default; + /*! + * \brief Implement gmxapi::MDModule interface to get module name. + * + * name is provided during the building stage. + * \return + */ // \todo make member function const const char *name() override { return name_.c_str(); } + /*! + * \brief Implement gmxapi::MDModule interface to create a restraint for libgromacs. + * + * \return Ownership of a new restraint instance + * + * Note this interface is not stable but requires other GROMACS and gmxapi infrastructure + * to mature before it is clear whether we will be creating a new instance or sharing ownership + * of the object. A future version may use a std::unique_ptr. + */ std::shared_ptr getRestraint() override { auto restraint = std::make_shared(sites_, params_, resources_); @@ -167,7 +220,30 @@ class RestraintModule : public gmxapi::MDModule // consider names const std::string name_; }; - +/*! + * \brief A simple plain-old-data structure to hold input parameters to the potential calculations. + * + * This structure will be initialized when the Session is launched. It is currently populated by + * keyword arguments processed in ``export_plugin.cpp`` in the EnsembleRestraintBuilder using the + * helper function makeEnsembleParams() defined below. + * + * Restraint potentials will express their (const) input parameters by defining a structure like this and + * providing a type alias for ``input_param_type``. + * + * Example: + * + * class EnsembleHarmonic + * { + * public: + * using input_param_type = ensemble_input_param_type; + * // ... + * } + * + * In future versions, a developer will continue to define a custom structure to hold their input + * parameters, but the meaning of the parameters and the key words with which they are expressed in + * Python will be specified with syntax similar to the pybind11 syntax in ``export_plugin.cpp``. + * + */ struct ensemble_input_param_type { /// distance histogram parameters @@ -195,11 +271,6 @@ struct ensemble_input_param_type }; -// \todo We should be able to automate a lot of the parameter setting stuff -// by having the developer specify a map of parameter names and the corresponding type, but that could get tricky. -// The statically compiled fast parameter structure would be generated with a recursive variadic template -// the way a tuple is. ref https://eli.thegreenplace.net/2014/variadic-templates-in-c/ - std::unique_ptr makeEnsembleParams(size_t nbins, double binWidth, @@ -247,10 +318,33 @@ class EnsembleHarmonic public: using input_param_type = ensemble_input_param_type; + /* No default constructor. Parameters must be provided. */ // EnsembleHarmonic(); + /*! + * \brief Constructor called by the wrapper code to produce a new instance. + * + * This constructor is called once per simulation per GROMACS process. Note that until + * gmxapi 0.0.8 there is only one instance per simulation in a thread-MPI simulation. + * + * \param params + */ explicit EnsembleHarmonic(const input_param_type ¶ms); + /*! + * \brief Deprecated constructor taking a parameter list. + * + * \param nbins + * \param binWidth + * \param minDist + * \param maxDist + * \param experimental + * \param nSamples + * \param samplePeriod + * \param nWindows + * \param k + * \param sigma + */ EnsembleHarmonic(size_t nbins, double binWidth, double minDist, @@ -262,13 +356,37 @@ class EnsembleHarmonic double k, double sigma); - // If dispatching this virtual function is not fast enough, the compiler may be able to better optimize a free + /*! + * \brief Evaluates the pair restraint potential. + * + * In parallel simulations, the gmxapi framework does not make guarantees about where or + * how many times this function is called. It should be simple and stateless; it should not + * update class member data (see ``ensemblepotential.cpp``. For a more controlled API hook + * and to manage state in the object, use ``callback()``. + * + * \param v position of the site for which force is being calculated. + * \param v0 reference site (other member of the pair). + * \param t current simulation time (ps). + * \return container for force and potential energy data. + */ + // Implementation note for the future: If dispatching this virtual function is not fast + // enough, the compiler may be able to better optimize a free // function that receives the current restraint as an argument. gmx::PotentialPointData calculate(gmx::Vector v, gmx::Vector v0, gmx_unused double t); - // An update function to be called on the simulation master rank/thread periodically by the Restraint framework. + /*! + * \brief An update function to be called on the simulation master rank/thread periodically by the Restraint framework. + * + * Defining this function in a plugin potential is optional. If the function is defined, + * the restraint framework calls this function (on the first rank only in a parallel simulation) before calling calculate(). + * + * The callback may use resources provided by the Session in the callback to perform updates + * to the local or global state of an ensemble of simulations. Future gmxapi releases will + * include additional optimizations, allowing call-back frequency to be expressed, and more + * general Session resources, as well as more flexible call signatures. + */ void callback(gmx::Vector v, gmx::Vector v0, double t, @@ -328,11 +446,31 @@ class EnsembleRestraint : public ::gmx::IRestraintPotential, private EnsembleHar resources_{std::move(resources)} {} + /*! + * \brief Implement required interface of gmx::IRestraintPotential + * + * \return list of configured site indices. + * + * \todo remove to template header + * \todo abstraction of site references + */ std::vector sites() const override { return sites_; } + /*! + * \brief Implement the interface gmx::IRestraintPotential + * + * Dispatch to calculate() method. + * + * \param r1 coordinate of first site + * \param r2 reference coordinate (second site) + * \param t simulation time + * \return calculated force and energy + * + * \todo remove to template header. + */ gmx::PotentialPointData evaluate(gmx::Vector r1, gmx::Vector r2, double t) override @@ -340,8 +478,13 @@ class EnsembleRestraint : public ::gmx::IRestraintPotential, private EnsembleHar return calculate(r1, r2, t); }; - - // An update function to be called on the simulation master rank/thread periodically by the Restraint framework. + /*! + * \brief An update function to be called on the simulation master rank/thread periodically by the Restraint framework. + * + * Implements optional override of gmx::IRestraintPotential::update + * + * This boilerplate will disappear into the Restraint template in an upcoming gmxapi release. + */ void update(gmx::Vector v, gmx::Vector v0, double t) override @@ -353,6 +496,11 @@ class EnsembleRestraint : public ::gmx::IRestraintPotential, private EnsembleHar *resources_); }; + /*! + * \brief Allow the Session to provide a resource object. + * + * \param resources object to take ownership of. + */ void setResources(std::unique_ptr&& resources) { resources_ = std::move(resources); @@ -366,7 +514,7 @@ class EnsembleRestraint : public ::gmx::IRestraintPotential, private EnsembleHar }; -// Just declare the template instantiation here for client code. +// Important: Just declare the template instantiation here for client code. // We will explicitly instantiate a definition in the .cpp file where the input_param_type is defined. extern template class RestraintModule; diff --git a/src/cpp/harmonicpotential.cpp b/src/cpp/harmonicpotential.cpp index 5e359c3..bad4fc9 100644 --- a/src/cpp/harmonicpotential.cpp +++ b/src/cpp/harmonicpotential.cpp @@ -40,7 +40,6 @@ gmx::PotentialPointData Harmonic::calculate(gmx::Vector v, output.force = k * (double(R0)/R - 1.0)*rdiff; } -// history.emplace_back(magnitude - R0); return output; } @@ -48,6 +47,7 @@ gmx::PotentialPointData HarmonicRestraint::evaluate(gmx::Vector r1, gmx::Vector r2, double t) { + // Use calculate() method inherited from HarmonicPotential return calculate(r1, r2, t); } diff --git a/src/cpp/harmonicpotential.h b/src/cpp/harmonicpotential.h index ab9d510..5ec372e 100644 --- a/src/cpp/harmonicpotential.h +++ b/src/cpp/harmonicpotential.h @@ -13,6 +13,19 @@ #include "gromacs/restraint/restraintpotential.h" #include "gromacs/utility/real.h" +/*! \file + * \brief Implement a harmonic pair force. + * + * Calculations and additional behavior is defined in harmonicpotential.cpp + * + * \todo This code has not been updated in a while... + * This needs to be updated and tested more rigorously. + * + * Ref. https://github.com/kassonlab/gmxapi/issues/55 + * https://github.com/kassonlab/gmxapi/issues/77 + * https://github.com/kassonlab/gmxapi/issues/78 + */ + namespace plugin { @@ -30,12 +43,9 @@ class Harmonic // Allow easier automatic generation of bindings. struct input_param_type { - float whateverIwant; +// not yet used }; - struct output_type - {}; - /*! * \brief Calculate harmonic force on particle at position v in reference to position v0. * @@ -61,23 +71,15 @@ class Harmonic gmx::Vector v0, gmx_unused double t); - // Cache of historical distance data. Not thread safe -// std::vector history{}; - // The class will either be inherited as a mix-in or inherit a CRTP base class. Either way, it probably needs proper virtual destructor management. virtual ~Harmonic() { -// for (auto&& distance: history) -// { -// std::cout << distance << "\n"; -// } -// std::cout << std::endl; } private: - // set equilibrium separation distance + // set equilibrium separation distance in GROMACS units. // TODO: be clearer about units real R0; - // set spring constant + // set spring constant in native GROMACS units. // TODO: be clearer about units real k; }; @@ -87,6 +89,16 @@ class Harmonic class HarmonicRestraint : public ::gmx::IRestraintPotential, private Harmonic { public: + /*! + * \brief Create an instance of the restraint (used in libgromacs) + * + * Each pair restraint instance operates on one pair of atomic sites. + * + * \param site1 first atomic site in the pair. + * \param site2 second atomic site in the pair. + * \param R0 targeted equilibrium pair separation. + * \param k spring constant. + */ HarmonicRestraint(unsigned long int site1, unsigned long int site2, real R0, @@ -96,9 +108,28 @@ class HarmonicRestraint : public ::gmx::IRestraintPotential, private Harmonic site2_{site2} {}; + /*! + * \brief Implement required interface of gmx::IRestraintPotential + * + * \return list of configured site indices. + * + * \todo remove to template header + * \todo abstraction of site references + */ std::vector sites() const override; - // \todo provide this facility automatically + /*! + * \brief Implement the interface gmx::IRestraintPotential + * + * Dispatch to calculate() method. + * + * \param r1 coordinate of first site + * \param r2 reference coordinate (second site) + * \param t simulation time + * \return calculated force and energy + * + * \todo remove to template header. + */ gmx::PotentialPointData evaluate(gmx::Vector r1, gmx::Vector r2, double t) override; @@ -108,6 +139,11 @@ class HarmonicRestraint : public ::gmx::IRestraintPotential, private Harmonic unsigned long int site2_{0}; }; +/*! + * \brief Wraps HarmonicPotential with a gmxapi compatible "module". + * + * Objects of this type allow the potential class to be instantiated as the simulation is launched. + */ class HarmonicModule : public gmxapi::MDModule { public: diff --git a/src/pythonmodule/export_plugin.cpp b/src/pythonmodule/export_plugin.cpp index 6a6ff28..d6622c2 100644 --- a/src/pythonmodule/export_plugin.cpp +++ b/src/pythonmodule/export_plugin.cpp @@ -2,6 +2,14 @@ // Created by Eric Irrgang on 11/3/17. // +/*! \file + * \brief Provide Python bindings and helper functions for setting up restraint potentials. + * + * There is currently a lot of boilerplate here that will be generalized and removed in a future version. + * In the mean time, follow the example for EnsembleRestraint to create the proper helper functions + * and instantiate the necessary templates. + */ + #include "export_plugin.h" #include "pybind11/pybind11.h" @@ -18,13 +26,18 @@ // Make a convenient alias to save some typing... namespace py = pybind11; +//////////////////////////////// +// Begin PyRestraint static code /*! * \brief Templated wrapper to use in Python bindings. * + * Boilerplate + * * Mix-in from below. Adds a bind behavior, a getModule() method to get a gmxapi::MDModule adapter, * and a create() method that assures a single shared_ptr record for an object that may sometimes * be referred to by a raw pointer and/or have shared_from_this called. - * \tparam T + * \tparam T class implementing gmx::IRestraintPotential + * */ template class PyRestraint : public T, public std::enable_shared_from_this> @@ -66,6 +79,14 @@ class PyRestraint : public T, public std::enable_shared_from_this }; +/*! + * \brief Implement the gmxapi binding protocol for restraints. + * + * All restraints will use this same code automatically. + * + * \tparam T restraint class exported below. + * \param object Python Capsule object to allow binding with a simple C API. + */ template void PyRestraint::bind(py::object object) { @@ -87,7 +108,20 @@ void PyRestraint::bind(py::object object) throw gmxapi::ProtocolError("bind method requires a python capsule as input"); } } +// end PyRestraint static code +////////////////////////////// + +/*! + * \brief Interact with the restraint framework and gmxapi when launching a simulation. + * + * This should be generalized and removed from here. Unfortunately, some things need to be + * standardized first. If a potential follows the example of EnsembleRestraint or HarmonicRestraint, + * the template specializations below can be mimicked to give GROMACS access to the potential. + * + * \tparam T class implementing the gmxapi::MDModule interface. + * \return shared ownership of a T object via the gmxapi::MDModule interface. + */ // If T is derived from gmxapi::MDModule, create a default-constructed std::shared_ptr // \todo Need a better default that can call a shared_from_this() template @@ -108,8 +142,17 @@ std::shared_ptr PyRestraint PyRestraint::getModule() const char* MyRestraint::docstring = R"rawdelimiter(Some sort of custom potential. )rawdelimiter"; - -/*! - * \brief Allow the Context to launch this graph node. - * - * Produced by RestraintBuilder::build(), objects of this type are functors that fit the Session launch(rank) - * call signature. - */ -class RestraintLauncher -{ - public: - /*! - * \brief Use objects as functors. - * - * \param rank Index of this worker in the ensemble of simulations. - * - * Called once by the Context when launching a Session, performs start-up tasks for the API object(s). - */ - void operator()(int rank) - {}; -}; +// end MyRestraint +////////////////// /*! * \brief Graph updater for Restraint element. @@ -228,6 +253,10 @@ class HarmonicRestraintBuilder real springConstant_; }; + + + + class EnsembleRestraintBuilder { public: @@ -343,12 +372,24 @@ class EnsembleRestraintBuilder std::string name_; }; +/*! + * \brief Factory function to create a new builder for use during Session launch. + * + * \param element WorkElement provided through Context + * \return ownership of new builder object + */ std::unique_ptr createHarmonicBuilder(const py::object element) { std::unique_ptr builder{new HarmonicRestraintBuilder(element)}; return builder; } +/*! + * \brief Factory function to create a new builder for use during Session launch. + * + * \param element WorkElement provided through Context + * \return ownership of new builder object + */ std::unique_ptr createEnsembleBuilder(const py::object element) { using gmx::compat::make_unique; @@ -356,12 +397,47 @@ std::unique_ptr createEnsembleBuilder(const py::object return builder; } + +//////////////////////////////////////////////////////////////////////////////////////////// +// New potentials modeled after EnsembleRestraint should define a Builder class and define a +// factory function here, following the previous two examples. The factory function should be +// exposed to Python following the examples near the end of the PYBIND11_MODULE block. +//////////////////////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// The PYBIND11_MODULE block uses the pybind11 framework (ref https://github.com/pybind/pybind11 ) +// to generate Python bindings to the C++ code elsewhere in this repository. A copy of the pybind11 +// source code is included with this repository. Use syntax from the examples below when exposing +// a new potential, along with its builder and parameters structure. In future releases, there will +// be less code to include elsewhere, but more syntax in the block below to define and export the +// interface to a plugin. pybind11 is not required to write a GROMACS extension module or for +// compatibility with the ``gmx`` module provided with gmxapi. It is sufficient to implement the +// various protocols, C API and Python function names, but we do not provide example code +// for other Python bindings frameworks. +////////////////////////////////////////////////////////////////////////////////////////////////// + // The first argument is the name of the module when importing to Python. This should be the same as the name specified // as the OUTPUT_NAME for the shared object library in the CMakeLists.txt file. The second argument, 'm', can be anything // but it might as well be short since we use it to refer to aspects of the module we are defining. PYBIND11_MODULE(myplugin, m) { m.doc() = "sample plugin"; // This will be the text of the module's docstring. + // Matrix utility class (temporary). Borrowed from http://pybind11.readthedocs.io/en/master/advanced/pycpp/numpy.html#arrays + py::class_, std::shared_ptr>>(m, "Matrix", py::buffer_protocol()) + .def_buffer([](plugin::Matrix &matrix) -> py::buffer_info { + return py::buffer_info( + matrix.data(), /* Pointer to buffer */ + sizeof(double), /* Size of one scalar */ + py::format_descriptor::format(), /* Python struct-style format descriptor */ + 2, /* Number of dimensions */ + { matrix.rows(), matrix.cols() }, /* Buffer dimensions */ + { sizeof(double) * matrix.cols(), /* Strides (in bytes) for each index */ + sizeof(double) } + ); + }); + + // New plan: Instead of inheriting from gmx.core.MDModule, we can use a local import of // gmxapi::MDModule in both gmxpy and in extension modules. When md.add_potential() is // called, instead of relying on a binary interface to the MDModule, it will pass itself @@ -393,18 +469,13 @@ PYBIND11_MODULE(myplugin, m) { "Create default MyRestraint" ); md_module.def("bind", &PyRestraint::bind); - - // The template parameters specify the C++ class to export and the handle type. - // The function parameters specify the containing module and the Python name for the class. -// py::class_> potential(m, "Potential"); -// potential.def(py::init()); -// // Set the Python docstring. -// potential.doc() = MyRestraint::docstring; - - // This bindings specification could actually be done in a templated function to automatically // generate parameter setters/getters + + ///////////////////////////////////////////////////// + // Begin HarmonicRestraint + // // Builder to be returned from create_restraint, py::class_ harmonicBuilder(m, "HarmonicBuilder"); harmonicBuilder.def("add_subscriber", &HarmonicRestraintBuilder::add_subscriber); @@ -414,6 +485,7 @@ PYBIND11_MODULE(myplugin, m) { // We use a shared_ptr handle because both the Python interpreter and libgromacs may need to extend // the lifetime of the object. py::class_, std::shared_ptr>> harmonic(m, "HarmonicRestraint"); + // Deprecated constructor directly taking restraint paramaters. harmonic.def( py::init( [](unsigned long int site1, @@ -427,24 +499,40 @@ PYBIND11_MODULE(myplugin, m) { "Construct HarmonicRestraint" ); harmonic.def("bind", &PyRestraint::bind); - //harmonic.def_property(name, getter, setter, extra) -// harmonic.def_property("pairs", &PyRestraint::getPairs, &PyRestraint::setPairs, "The indices of particle pairs to restrain"); + /* + * To implement gmxapi_workspec_1_0, the module needs a function that a Context can import that + * produces a builder that translates workspec elements for session launching. The object returned + * by our function needs to have an add_subscriber(other_builder) method and a build(graph) method. + * The build() method returns None or a launcher. A launcher has a signature like launch(rank) and + * returns None or a runner. + */ + m.def("create_restraint", [](const py::object element){ return createHarmonicBuilder(element); }); + // + // End HarmonicRestraint + /////////////////////////////////////////////////////// + - // Builder to be returned from create_restraint + ////////////////////////////////////////////////////////////////////////// + // Begin EnsembleRestraint + // + // Define Builder to be returned from ensemble_restraint Python function defined further down. pybind11::class_ ensembleBuilder(m, "EnsembleBuilder"); ensembleBuilder.def("add_subscriber", &EnsembleRestraintBuilder::addSubscriber); ensembleBuilder.def("build", &EnsembleRestraintBuilder::build); + // Get more concise name for the template instantiation... using PyEnsemble = PyRestraint>; + + // Export a Python class for our parameters struct py::class_ ensembleParams(m, "EnsembleRestraintParams"); - // Builder to be returned from ensemble_restraint + m.def("make_ensemble_params", + &plugin::makeEnsembleParams); + // API object to build. py::class_> ensemble(m, "EnsembleRestraint"); // EnsembleRestraint can only be created via builder for now. ensemble.def("bind", &PyEnsemble::bind, "Implement binding protocol"); - - /* * To implement gmxapi_workspec_1_0, the module needs a function that a Context can import that * produces a builder that translates workspec elements for session launching. The object returned @@ -452,22 +540,15 @@ PYBIND11_MODULE(myplugin, m) { * The build() method returns None or a launcher. A launcher has a signature like launch(rank) and * returns None or a runner. */ - m.def("make_ensemble_params", - &plugin::makeEnsembleParams); - m.def("create_restraint", [](const py::object element){ return createHarmonicBuilder(element); }); + + // Generate the name operation that will be used to specify elements of Work in gmxapi workflows. + // WorkElements will then have namespace: "myplugin" and operation: "ensemble_restraint" m.def("ensemble_restraint", [](const py::object element){ return createEnsembleBuilder(element); }); + // + // End EnsembleRestraint + /////////////////////////////////////////////////////////////////////////// + + + - // Matrix utility class (temporary). Borrowed from http://pybind11.readthedocs.io/en/master/advanced/pycpp/numpy.html#arrays - py::class_, std::shared_ptr>>(m, "Matrix", py::buffer_protocol()) - .def_buffer([](plugin::Matrix &matrix) -> py::buffer_info { - return py::buffer_info( - matrix.data(), /* Pointer to buffer */ - sizeof(double), /* Size of one scalar */ - py::format_descriptor::format(), /* Python struct-style format descriptor */ - 2, /* Number of dimensions */ - { matrix.rows(), matrix.cols() }, /* Buffer dimensions */ - { sizeof(double) * matrix.cols(), /* Strides (in bytes) for each index */ - sizeof(double) } - ); - }); }